mirror of
https://github.com/TomHarte/CLK.git
synced 2025-10-30 14:16:04 +00:00
Compare commits
619 Commits
2017-08-16
...
2018-01-15
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4cf2e16b5c | ||
|
|
9cbd61e709 | ||
|
|
0202c7afb2 | ||
|
|
c187c5a637 | ||
|
|
23c34a8c14 | ||
|
|
93ece2aec7 | ||
|
|
e12ab8fe2e | ||
|
|
2fe0ceb52a | ||
|
|
f354c12c81 | ||
|
|
def82cba49 | ||
|
|
e7bc7b94c9 | ||
|
|
aafdff49be | ||
|
|
4ef583813a | ||
|
|
9f97fb738e | ||
|
|
4e124047c6 | ||
|
|
6eb56a1564 | ||
|
|
35fc0a5c16 | ||
|
|
b36c917810 | ||
|
|
a5ac8c824e | ||
|
|
0ccc104027 | ||
|
|
8be6cb827b | ||
|
|
2f59226300 | ||
|
|
793ef68206 | ||
|
|
513c067f94 | ||
|
|
999a0c22d4 | ||
|
|
5d0832613f | ||
|
|
2ffde4c3c2 | ||
|
|
57ddfcd645 | ||
|
|
fc16e8eb8c | ||
|
|
655b971976 | ||
|
|
3e1d8ea082 | ||
|
|
772c320d5a | ||
|
|
bcc7ad0c30 | ||
|
|
73b4e1722b | ||
|
|
185cd3c123 | ||
|
|
ed564cb810 | ||
|
|
b78ece1f1e | ||
|
|
c8367a017f | ||
|
|
344a12566b | ||
|
|
c07113ea95 | ||
|
|
bc2879c412 | ||
|
|
1d47b55729 | ||
|
|
db25b4554b | ||
|
|
05b95ea2e0 | ||
|
|
250f7bf6b0 | ||
|
|
34db35b500 | ||
|
|
f75590253d | ||
|
|
4f6abc9059 | ||
|
|
c70dbc6a49 | ||
|
|
1c255b9e7d | ||
|
|
188bfa9c18 | ||
|
|
c7f8f37822 | ||
|
|
4a19dbb8cb | ||
|
|
bf0601123b | ||
|
|
9339f3413f | ||
|
|
c18517be4b | ||
|
|
eef34adcbd | ||
|
|
769d9dfbb9 | ||
|
|
6a0bb83716 | ||
|
|
6da8a3e24b | ||
|
|
e349161a53 | ||
|
|
d5b1a9d918 | ||
|
|
76af0228dd | ||
|
|
2cc1a2684a | ||
|
|
98a9d57c0b | ||
|
|
c481293aca | ||
|
|
5fd0a2b9ea | ||
|
|
11b73a9c0b | ||
|
|
c4950574ea | ||
|
|
0b297f2972 | ||
|
|
f9f870ad2d | ||
|
|
cbba6a5595 | ||
|
|
0a079b0f94 | ||
|
|
9a7e974579 | ||
|
|
f4d414d6e4 | ||
|
|
b4bfcd4279 | ||
|
|
e8ddff0ee0 | ||
|
|
b61fab9df7 | ||
|
|
28fb1ce2ae | ||
|
|
b9b107ee85 | ||
|
|
f17758e7f9 | ||
|
|
0bb24075b6 | ||
|
|
db6d9b59d0 | ||
|
|
51e82c10c5 | ||
|
|
2d892da225 | ||
|
|
b99ba2bc02 | ||
|
|
d36e9d0b0d | ||
|
|
2dc1d4443e | ||
|
|
f8a2459c91 | ||
|
|
ac80d10cd8 | ||
|
|
eb6b612052 | ||
|
|
d66a33f249 | ||
|
|
ec4c259695 | ||
|
|
ad50b6b1fb | ||
|
|
3da323c657 | ||
|
|
aca7842ca4 | ||
|
|
38c912b968 | ||
|
|
7a52e7d6d2 | ||
|
|
c36de4f640 | ||
|
|
504772bcda | ||
|
|
5d0c33d545 | ||
|
|
7bc1bcd493 | ||
|
|
b0616ee10c | ||
|
|
da57df55e8 | ||
|
|
4daea1121b | ||
|
|
afcdd64d5e | ||
|
|
798cdba979 | ||
|
|
f957344ac4 | ||
|
|
b3fbd0f352 | ||
|
|
042edc72f7 | ||
|
|
943418c434 | ||
|
|
7d7e2538bd | ||
|
|
7a544731e2 | ||
|
|
e1914b4f16 | ||
|
|
202958303e | ||
|
|
57b060ac3c | ||
|
|
8653eb8b55 | ||
|
|
a4f0a260fd | ||
|
|
d4a53e82bb | ||
|
|
6eedc99286 | ||
|
|
ec266d6c8e | ||
|
|
e3a5218e78 | ||
|
|
a473338abe | ||
|
|
ae21782adc | ||
|
|
ee44d671e7 | ||
|
|
3766bef962 | ||
|
|
ad3df36c20 | ||
|
|
38b11893e8 | ||
|
|
e4534775b0 | ||
|
|
fe7fc6b22e | ||
|
|
fe0cdc8d69 | ||
|
|
7f8a13a409 | ||
|
|
ca26ce8400 | ||
|
|
d3dd8f3f2a | ||
|
|
3c8d2d579d | ||
|
|
edcbb3dfed | ||
|
|
9c8158753e | ||
|
|
5da9cb2957 | ||
|
|
54c845b6e2 | ||
|
|
ee84f33ab5 | ||
|
|
f0f149c018 | ||
|
|
7dfbe4bb93 | ||
|
|
aa4eef41d8 | ||
|
|
69ec8a362e | ||
|
|
ecd7d4731b | ||
|
|
563aa051e4 | ||
|
|
642bb8333f | ||
|
|
c558e86e03 | ||
|
|
dbb14ea2e2 | ||
|
|
173e16b107 | ||
|
|
7d2adad67e | ||
|
|
d33612def5 | ||
|
|
9cb6ca3440 | ||
|
|
e957e40b14 | ||
|
|
7a8a43a96a | ||
|
|
0eb5dd9688 | ||
|
|
a14b53a9ab | ||
|
|
576d554a2c | ||
|
|
68a2895753 | ||
|
|
f90b3f06aa | ||
|
|
f067fa9923 | ||
|
|
ee9f89ccb5 | ||
|
|
573a9c6fb2 | ||
|
|
a46a37fba9 | ||
|
|
324b57c054 | ||
|
|
ae50ca9ab2 | ||
|
|
6e4bde00d3 | ||
|
|
d4d0dd87c9 | ||
|
|
221c05ca76 | ||
|
|
ff21ff90eb | ||
|
|
fcf295fd68 | ||
|
|
2008dec1ed | ||
|
|
b4f3c41aae | ||
|
|
90c4e3726f | ||
|
|
c83b3cefbc | ||
|
|
a8ac51da73 | ||
|
|
bc65ba3f9b | ||
|
|
79674fdbd3 | ||
|
|
adea4711f1 | ||
|
|
bded406caa | ||
|
|
85085a6375 | ||
|
|
d122d598d3 | ||
|
|
d6192b8c58 | ||
|
|
f02d4dbb59 | ||
|
|
f3818991f6 | ||
|
|
c7dd6247f0 | ||
|
|
99e17600d7 | ||
|
|
1d821ad459 | ||
|
|
c60a9ee3c3 | ||
|
|
ffcbd1e94d | ||
|
|
6c8b503402 | ||
|
|
55e1d25966 | ||
|
|
0bdd776114 | ||
|
|
c1b7bceec8 | ||
|
|
dc4f58e40c | ||
|
|
3b8cdd620c | ||
|
|
3365ff0200 | ||
|
|
89c3e2ba5a | ||
|
|
c6306db47c | ||
|
|
8ddc64c82a | ||
|
|
b887cb7255 | ||
|
|
d54ee2af82 | ||
|
|
723c113186 | ||
|
|
c368c4443e | ||
|
|
7b25b03cd5 | ||
|
|
9961d13e2d | ||
|
|
29b5ccc767 | ||
|
|
90af395df2 | ||
|
|
6f8d4d6c5c | ||
|
|
63381ff505 | ||
|
|
2ea050556b | ||
|
|
8dcac6561e | ||
|
|
90d33949f9 | ||
|
|
d3e68914dd | ||
|
|
82ad0354c4 | ||
|
|
073e439518 | ||
|
|
27b123549b | ||
|
|
de9db724a7 | ||
|
|
532ea35ee9 | ||
|
|
e9ddec35d6 | ||
|
|
7647f8089b | ||
|
|
f00f0353a6 | ||
|
|
e19ae5d43d | ||
|
|
0a9622435c | ||
|
|
f704932475 | ||
|
|
d0f096a20b | ||
|
|
949d0f3928 | ||
|
|
a2d48223c3 | ||
|
|
fc080c773f | ||
|
|
adb3811847 | ||
|
|
dbbea78b76 | ||
|
|
fd96e3e657 | ||
|
|
06d81b3a97 | ||
|
|
88551607a6 | ||
|
|
2a9dccff26 | ||
|
|
1027f85683 | ||
|
|
9bb9cb4a65 | ||
|
|
2de80646ec | ||
|
|
bf4ed57f68 | ||
|
|
9578f3dc44 | ||
|
|
a97c478a34 | ||
|
|
e0113d5dce | ||
|
|
980cf541d2 | ||
|
|
69c983f9ee | ||
|
|
70039d22f1 | ||
|
|
ebdb80c908 | ||
|
|
0eaac99d74 | ||
|
|
792061a82b | ||
|
|
d2ba7d7430 | ||
|
|
8713cfa613 | ||
|
|
aa77be1c10 | ||
|
|
e6aa2321cd | ||
|
|
c827d14d97 | ||
|
|
2979d19621 | ||
|
|
282e5c9d3e | ||
|
|
ede47d4ba7 | ||
|
|
5408efe9b5 | ||
|
|
d6141cb020 | ||
|
|
198d0fd1de | ||
|
|
6d80856f02 | ||
|
|
4778616fd7 | ||
|
|
2e025d85eb | ||
|
|
61f2191c86 | ||
|
|
c1eab8d5f3 | ||
|
|
91d2d59ae5 | ||
|
|
5aef81cf24 | ||
|
|
3550196bed | ||
|
|
bce58683fa | ||
|
|
c91a5875b2 | ||
|
|
2e15fab651 | ||
|
|
6a176082a0 | ||
|
|
fd346bac3e | ||
|
|
25e9dcc800 | ||
|
|
792cbb1536 | ||
|
|
2e12370251 | ||
|
|
7adc25694a | ||
|
|
ca80da7fbe | ||
|
|
f853d87884 | ||
|
|
524087805f | ||
|
|
916eb96b47 | ||
|
|
4add2c1051 | ||
|
|
cb0f58ab7a | ||
|
|
d9e56711ce | ||
|
|
d60692b6fd | ||
|
|
5b6ea35d96 | ||
|
|
4cbc87a17d | ||
|
|
46e7c199b2 | ||
|
|
ff7ba526fb | ||
|
|
a825da3715 | ||
|
|
fabaf4e607 | ||
|
|
153067c018 | ||
|
|
f7f2736d4d | ||
|
|
a16ca65825 | ||
|
|
cb015c83e1 | ||
|
|
2203499215 | ||
|
|
c0055a5a5f | ||
|
|
62218e81bf | ||
|
|
c45d4831ec | ||
|
|
9fd33bdfde | ||
|
|
6e1d69581c | ||
|
|
f95515ae81 | ||
|
|
09c855a659 | ||
|
|
16c96b605a | ||
|
|
e10d369e53 | ||
|
|
0d1b63a8c5 | ||
|
|
ddcdd07dd0 | ||
|
|
35da3edf60 | ||
|
|
d605022ea3 | ||
|
|
0da78065ce | ||
|
|
4b68c372c6 | ||
|
|
13406fedd8 | ||
|
|
a209ae76ca | ||
|
|
0116d7f071 | ||
|
|
512e877d06 | ||
|
|
1e1efcdcb8 | ||
|
|
bc2f58e9de | ||
|
|
fd10c42433 | ||
|
|
794437f20f | ||
|
|
23d5849cda | ||
|
|
5070a8414f | ||
|
|
5a3ca0e447 | ||
|
|
e384c50580 | ||
|
|
b9734278f6 | ||
|
|
f807a6b608 | ||
|
|
833f8c02a4 | ||
|
|
0248c6a282 | ||
|
|
218b976dbc | ||
|
|
513903890e | ||
|
|
1157bde453 | ||
|
|
46345c6a3e | ||
|
|
c13f8e5390 | ||
|
|
ad9df4bb90 | ||
|
|
e983854e71 | ||
|
|
ec999446e8 | ||
|
|
5e3e91373a | ||
|
|
c52348d8d7 | ||
|
|
9e0907ee76 | ||
|
|
9ad4025138 | ||
|
|
405f58d6a3 | ||
|
|
afbd1c425c | ||
|
|
b2c1b83fcd | ||
|
|
8d2b9a581a | ||
|
|
1825af0dd3 | ||
|
|
c2f6799f0c | ||
|
|
b5b6219cb7 | ||
|
|
185a699279 | ||
|
|
96b8f9ae9f | ||
|
|
88e2350b8f | ||
|
|
5c141af734 | ||
|
|
da580e4186 | ||
|
|
57ee09dffb | ||
|
|
7c8e830b90 | ||
|
|
ba5f668338 | ||
|
|
2c1e99858b | ||
|
|
7f2febeec9 | ||
|
|
2d7a4fe5f0 | ||
|
|
91b867a7b3 | ||
|
|
3944e734d3 | ||
|
|
ce78d9d12c | ||
|
|
edbc60a3fb | ||
|
|
6ea3ff62df | ||
|
|
88959571f1 | ||
|
|
b4583e976e | ||
|
|
92d9805f09 | ||
|
|
0c2dd62328 | ||
|
|
3f4d90d775 | ||
|
|
542ec4312f | ||
|
|
18798c9886 | ||
|
|
7aaf27389c | ||
|
|
ee179aa7bd | ||
|
|
3a05ce36de | ||
|
|
4f289ab10b | ||
|
|
78ee46270b | ||
|
|
edb632af52 | ||
|
|
19c03a08a6 | ||
|
|
44cdc124af | ||
|
|
b37787a414 | ||
|
|
53b99ea248 | ||
|
|
97a2be71e3 | ||
|
|
f623bff5c3 | ||
|
|
2511fc8401 | ||
|
|
d37ec9e5b0 | ||
|
|
95c82f5b36 | ||
|
|
ec202ed8be | ||
|
|
7190225603 | ||
|
|
52e7cabd4e | ||
|
|
064f1dfdbc | ||
|
|
f40e1fd840 | ||
|
|
e194a2a015 | ||
|
|
c39759333a | ||
|
|
edb9fd301c | ||
|
|
ea5023ac26 | ||
|
|
0fb363ea0e | ||
|
|
1cc85615d5 | ||
|
|
7b01c1bee6 | ||
|
|
35705c5345 | ||
|
|
f41da83d97 | ||
|
|
cd1e5dea4d | ||
|
|
ef605eda51 | ||
|
|
2f48ee59fa | ||
|
|
f86729c4ac | ||
|
|
5f99f4442c | ||
|
|
326857a84d | ||
|
|
5dd3945695 | ||
|
|
19eb975c73 | ||
|
|
698ffca51b | ||
|
|
fe3cc5c57c | ||
|
|
f488854720 | ||
|
|
51c0c45e04 | ||
|
|
c3e1489a8e | ||
|
|
e3420f62c6 | ||
|
|
970c80f2e3 | ||
|
|
9f4a407f94 | ||
|
|
5dda897334 | ||
|
|
3982e375e3 | ||
|
|
a8524daecb | ||
|
|
d1ce764201 | ||
|
|
8875982e1f | ||
|
|
3319a4f589 | ||
|
|
c7f27b2db4 | ||
|
|
631f630549 | ||
|
|
2a08bd9ecc | ||
|
|
f789ee4ff0 | ||
|
|
a295b42497 | ||
|
|
d8337492cc | ||
|
|
15c8debc16 | ||
|
|
67af153c16 | ||
|
|
d72dad2d1a | ||
|
|
698e4fe550 | ||
|
|
b5406b90cd | ||
|
|
05a93ba237 | ||
|
|
77548d14db | ||
|
|
b85dd608e7 | ||
|
|
231f13d810 | ||
|
|
704bfa114c | ||
|
|
44a56724cb | ||
|
|
5fbea625ae | ||
|
|
ac57b37e96 | ||
|
|
e3e9baeaa4 | ||
|
|
e071123f90 | ||
|
|
98adb01721 | ||
|
|
d6a5f9a29e | ||
|
|
0d84b4b9dd | ||
|
|
a85909198f | ||
|
|
98751e6ac8 | ||
|
|
da082673d7 | ||
|
|
35fe4d50d4 | ||
|
|
b835cb73e2 | ||
|
|
662d031e3c | ||
|
|
bf20c717fb | ||
|
|
4d4a0cf1d2 | ||
|
|
b62f3e726a | ||
|
|
82b13e98f2 | ||
|
|
9ac831b09c | ||
|
|
42616da7ff | ||
|
|
2f13517f38 | ||
|
|
fb9fd26af7 | ||
|
|
d3c385b471 | ||
|
|
96bf133924 | ||
|
|
6d6cac429d | ||
|
|
dc0b65f9c9 | ||
|
|
8882aa496f | ||
|
|
0622187ddf | ||
|
|
523e1288fa | ||
|
|
1a96cce26f | ||
|
|
a4e275e1fc | ||
|
|
6075064400 | ||
|
|
ff6e65cca9 | ||
|
|
90d2347c90 | ||
|
|
90c7056d12 | ||
|
|
fed2bc9fc9 | ||
|
|
ff510f3b84 | ||
|
|
3b12fca417 | ||
|
|
8eeb7e73cd | ||
|
|
7fd6699e0b | ||
|
|
ed70b15fc9 | ||
|
|
ff24e1de31 | ||
|
|
6547102511 | ||
|
|
d538ff5039 | ||
|
|
a49594c6a3 | ||
|
|
3544c0f014 | ||
|
|
f26fe3756c | ||
|
|
a42ca290cb | ||
|
|
da09098e49 | ||
|
|
450712f39c | ||
|
|
24b3faa427 | ||
|
|
40d11ea0e3 | ||
|
|
ab2bcb939f | ||
|
|
45499050b6 | ||
|
|
0c9197df30 | ||
|
|
a1e200cc65 | ||
|
|
8a612bb6ab | ||
|
|
e6ac939ae0 | ||
|
|
b034d4e6f8 | ||
|
|
de218611e4 | ||
|
|
615f7ce176 | ||
|
|
b306776ba9 | ||
|
|
0f85cffc78 | ||
|
|
96648df5fe | ||
|
|
2c99a2d6ec | ||
|
|
4af333d5ec | ||
|
|
a5f9869769 | ||
|
|
f10be2a18a | ||
|
|
c88d627b4e | ||
|
|
b30bb2a234 | ||
|
|
d498080eb4 | ||
|
|
334afbc710 | ||
|
|
17c13624e5 | ||
|
|
113349d272 | ||
|
|
0ced7866fc | ||
|
|
d06031dfcb | ||
|
|
3f22a71276 | ||
|
|
53a88a7e12 | ||
|
|
4a66dd9e82 | ||
|
|
522839143f | ||
|
|
b4c532c0d5 | ||
|
|
a3e2d142e3 | ||
|
|
63ee8c9d58 | ||
|
|
437023bff6 | ||
|
|
4465098157 | ||
|
|
56dd677e9c | ||
|
|
9aa150c338 | ||
|
|
fab6908129 | ||
|
|
e34d4ce903 | ||
|
|
d411827733 | ||
|
|
f1ba7755dd | ||
|
|
57bfec285f | ||
|
|
bdda701207 | ||
|
|
487fe83dca | ||
|
|
6c5a03187b | ||
|
|
97f57a3948 | ||
|
|
7d7aa2f5d5 | ||
|
|
e7ad79c79a | ||
|
|
28550c0227 | ||
|
|
6e99169348 | ||
|
|
1017bb9f6b | ||
|
|
3caa4705ca | ||
|
|
039aed1bd1 | ||
|
|
d77d7fdd78 | ||
|
|
c6e6c3fcfb | ||
|
|
ecd3350a6f | ||
|
|
fa19e2d9c2 | ||
|
|
95d360251d | ||
|
|
7af3de010e | ||
|
|
cefd421992 | ||
|
|
a914eadc85 | ||
|
|
131b340d75 | ||
|
|
e956740c56 | ||
|
|
8afd83b91f | ||
|
|
40d7a603db | ||
|
|
ee71be0e7e | ||
|
|
cde29c4bf4 | ||
|
|
e1aded0d95 | ||
|
|
1237f174fe | ||
|
|
0cbc1753b9 | ||
|
|
5cf0395936 | ||
|
|
6315c22b80 | ||
|
|
4614a56843 | ||
|
|
8f5ae4a326 | ||
|
|
8fdc5012e4 | ||
|
|
e88a51e75e | ||
|
|
49285e9caa | ||
|
|
e3f2118757 | ||
|
|
daeaa4752f | ||
|
|
5344e3098b | ||
|
|
cedb809c21 | ||
|
|
2d9efccc98 | ||
|
|
8ce46b6e49 | ||
|
|
f2699a3f2b | ||
|
|
85253a5876 | ||
|
|
911ee5a0d3 | ||
|
|
57c5b38a6d | ||
|
|
669e0caff5 | ||
|
|
b24d04fc09 | ||
|
|
ef07c33741 | ||
|
|
e559a65ede | ||
|
|
5bdd24d93f | ||
|
|
af61a7fa28 | ||
|
|
c8c1792c3f | ||
|
|
e6683e7f2d | ||
|
|
0c1714b695 | ||
|
|
dc0ca83003 | ||
|
|
2c2dd8073c | ||
|
|
4f8b89772e | ||
|
|
733ee5a5c3 | ||
|
|
fedf5a44a6 | ||
|
|
6a09022896 | ||
|
|
5b3c707959 | ||
|
|
9b21ef1507 | ||
|
|
da3e8655e9 | ||
|
|
41e4386164 | ||
|
|
b0a98bd239 | ||
|
|
42ad670ec8 | ||
|
|
58063b69a6 | ||
|
|
378f231499 | ||
|
|
f68565a33f | ||
|
|
175faebdc9 | ||
|
|
76c6b715a2 | ||
|
|
b476f06524 | ||
|
|
48290a8bbe | ||
|
|
9d9a1c341d | ||
|
|
952da1e581 | ||
|
|
a988255558 | ||
|
|
cca66ab450 | ||
|
|
bcd7a312a4 | ||
|
|
925e774015 | ||
|
|
4c15e46fd1 | ||
|
|
3c50903a2b | ||
|
|
75208b0762 | ||
|
|
f1e64169cd | ||
|
|
903a17ae11 | ||
|
|
de1c526789 | ||
|
|
b7e0f64892 | ||
|
|
148591b7f2 | ||
|
|
2105597910 | ||
|
|
3c148f5721 | ||
|
|
360c8a99a3 | ||
|
|
06e31f5102 | ||
|
|
42b5b66305 |
9
.gitignore
vendored
9
.gitignore
vendored
@@ -18,9 +18,14 @@ DerivedData
|
|||||||
*.xcuserstate
|
*.xcuserstate
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
|
||||||
# Exclude system ROMs
|
# Exclude system ROMs and unit test ROMs
|
||||||
ROMImages/*
|
ROMImages/*
|
||||||
OSBindings/Mac/Clock SignalTests/Atari\ ROMs
|
OSBindings/Mac/Clock SignalTests/Atari ROMs
|
||||||
|
OSBindings/Mac/Clock SignalTests/MSX ROMs
|
||||||
|
|
||||||
|
# Exclude intermediate build products
|
||||||
|
*.o
|
||||||
|
.sconsign.dblite
|
||||||
|
|
||||||
# CocoaPods
|
# CocoaPods
|
||||||
#
|
#
|
||||||
|
|||||||
30
BUILD.txt
Normal file
30
BUILD.txt
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
Linux, BSD
|
||||||
|
==========
|
||||||
|
|
||||||
|
Prerequisites are SDL 2, ZLib and OpenGL (or Mesa), and SCons for the provided build script. OpenGL 3.2 or better is required at runtime.
|
||||||
|
|
||||||
|
Build:
|
||||||
|
|
||||||
|
cd OSBindings/SDL
|
||||||
|
scons
|
||||||
|
|
||||||
|
Optionally:
|
||||||
|
|
||||||
|
cp clksignal /usr/bin
|
||||||
|
|
||||||
|
To launch:
|
||||||
|
|
||||||
|
clksignal file
|
||||||
|
|
||||||
|
Setting up clksignal as the associated program for supported file types in your favoured filesystem browser is recommended; it has no file navigation abilities of its own.
|
||||||
|
|
||||||
|
Some emulated systems require the provision of original machine ROMs. These are not included and may be located in either /usr/local/share/CLK/ or /usr/share/CLK/. You will be prompted for them if they are found to be missing. The structure should mirror that under OSBindings in the source archive; see the readme.txt in each folder to determine the proper files and names ahead of time.
|
||||||
|
|
||||||
|
macOS
|
||||||
|
=====
|
||||||
|
|
||||||
|
There are no prerequisites beyond the normal system libraries; the macOS build is a native Cocoa application.
|
||||||
|
|
||||||
|
Build: open the Xcode project in OSBindings/Mac and press command+b.
|
||||||
|
|
||||||
|
Machine ROMs are intended to be built into the application bundle; populate the dummy folders below ROMImages before building.
|
||||||
@@ -161,7 +161,7 @@ class HalfCycles: public WrappedInt<HalfCycles> {
|
|||||||
inline HalfCycles(int l) : WrappedInt<HalfCycles>(l) {}
|
inline HalfCycles(int l) : WrappedInt<HalfCycles>(l) {}
|
||||||
inline HalfCycles() : WrappedInt<HalfCycles>() {}
|
inline HalfCycles() : WrappedInt<HalfCycles>() {}
|
||||||
|
|
||||||
inline HalfCycles(const Cycles cycles) : WrappedInt<HalfCycles>(cycles.as_int() << 1) {}
|
inline HalfCycles(const Cycles cycles) : WrappedInt<HalfCycles>(cycles.as_int() * 2) {}
|
||||||
inline HalfCycles(const HalfCycles &half_cycles) : WrappedInt<HalfCycles>(half_cycles.length_) {}
|
inline HalfCycles(const HalfCycles &half_cycles) : WrappedInt<HalfCycles>(half_cycles.length_) {}
|
||||||
|
|
||||||
/// @returns The number of whole cycles completely covered by this span of half cycles.
|
/// @returns The number of whole cycles completely covered by this span of half cycles.
|
||||||
@@ -176,6 +176,13 @@ class HalfCycles: public WrappedInt<HalfCycles> {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Flushes the half cycles in @c this, returning the number stored and setting this total to zero.
|
||||||
|
inline HalfCycles flush() {
|
||||||
|
HalfCycles result(length_);
|
||||||
|
length_ = 0;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
Severs from @c this the effect of dividing by @c divisor — @c this will end up with
|
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 and @c divided by @c divisor is returned.
|
||||||
|
|||||||
@@ -6,8 +6,14 @@
|
|||||||
// Copyright © 2017 Thomas Harte. All rights reserved.
|
// Copyright © 2017 Thomas Harte. All rights reserved.
|
||||||
//
|
//
|
||||||
|
|
||||||
#ifndef ForceInline_h
|
#ifndef ForceInline_hpp
|
||||||
#define ForceInline_h
|
#define ForceInline_hpp
|
||||||
|
|
||||||
|
#ifdef DEBUG
|
||||||
|
|
||||||
|
#define forceinline
|
||||||
|
|
||||||
|
#else
|
||||||
|
|
||||||
#ifdef __GNUC__
|
#ifdef __GNUC__
|
||||||
#define forceinline __attribute__((always_inline)) inline
|
#define forceinline __attribute__((always_inline)) inline
|
||||||
@@ -15,4 +21,6 @@
|
|||||||
#define forceinline __forceinline
|
#define forceinline __forceinline
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
#endif /* ForceInline_h */
|
#endif /* ForceInline_h */
|
||||||
60
ClockReceiver/Sleeper.hpp
Normal file
60
ClockReceiver/Sleeper.hpp
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
//
|
||||||
|
// Sleeper.h
|
||||||
|
// Clock Signal
|
||||||
|
//
|
||||||
|
// Created by Thomas Harte on 20/08/2017.
|
||||||
|
// Copyright © 2017 Thomas Harte. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef Sleeper_hpp
|
||||||
|
#define Sleeper_hpp
|
||||||
|
|
||||||
|
/*!
|
||||||
|
A sleeper is any component that sometimes requires a clock but at other times is 'asleep' — i.e. is not doing
|
||||||
|
any clock-derived work, so needn't receive a clock. A disk controller is an archetypal example.
|
||||||
|
|
||||||
|
A sleeper will signal sleeps and wakes to an observer.
|
||||||
|
|
||||||
|
This is intended to allow for performance improvements to machines with components that can sleep. The observer
|
||||||
|
callout is virtual so the intended use case is that a machine holds a component that might sleep. Its transitions
|
||||||
|
into and out of sleep are sufficiently infrequent that a virtual call to announce them costs sufficiently little that
|
||||||
|
the saved ::run_fors add up to a substantial amount.
|
||||||
|
|
||||||
|
By convention, sleeper components must be willing to accept ::run_for even after announcing sleep. It's a hint,
|
||||||
|
not a command.
|
||||||
|
*/
|
||||||
|
class Sleeper {
|
||||||
|
public:
|
||||||
|
Sleeper() : sleep_observer_(nullptr) {}
|
||||||
|
|
||||||
|
class SleepObserver {
|
||||||
|
public:
|
||||||
|
/// Called to inform an observer that the component @c component has either gone to sleep or become awake.
|
||||||
|
virtual void set_component_is_sleeping(void *component, bool is_sleeping) = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Registers @c observer as the new sleep observer;
|
||||||
|
void set_sleep_observer(SleepObserver *observer) {
|
||||||
|
sleep_observer_ = observer;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @returns @c true if the component is currently sleeping; @c false otherwise.
|
||||||
|
virtual bool is_sleeping() = 0;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
/// Provided for subclasses; send sleep announcements to the sleep_observer_.
|
||||||
|
SleepObserver *sleep_observer_;
|
||||||
|
|
||||||
|
/*!
|
||||||
|
Provided for subclasses; call this whenever is_sleeping might have changed, and the observer will be notified,
|
||||||
|
if one exists.
|
||||||
|
|
||||||
|
@c is_sleeping will be called only if there is an observer.
|
||||||
|
*/
|
||||||
|
void update_sleep_observer() {
|
||||||
|
if(!sleep_observer_) return;
|
||||||
|
sleep_observer_->set_component_is_sleeping(this, is_sleeping());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif /* Sleeper_h */
|
||||||
@@ -7,47 +7,35 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
#include "1770.hpp"
|
#include "1770.hpp"
|
||||||
#include "../../Storage/Disk/Encodings/MFM.hpp"
|
#include "../../Storage/Disk/Encodings/MFM/Constants.hpp"
|
||||||
|
|
||||||
using namespace WD;
|
using namespace WD;
|
||||||
|
|
||||||
WD1770::Status::Status() :
|
|
||||||
type(Status::One),
|
|
||||||
write_protect(false),
|
|
||||||
record_type(false),
|
|
||||||
spin_up(false),
|
|
||||||
record_not_found(false),
|
|
||||||
crc_error(false),
|
|
||||||
seek_error(false),
|
|
||||||
lost_data(false),
|
|
||||||
data_request(false),
|
|
||||||
interrupt_request(false),
|
|
||||||
busy(false) {}
|
|
||||||
|
|
||||||
WD1770::WD1770(Personality p) :
|
WD1770::WD1770(Personality p) :
|
||||||
Storage::Disk::MFMController(8000000, 16, 300),
|
Storage::Disk::MFMController(8000000),
|
||||||
interesting_event_mask_((int)Event1770::Command),
|
|
||||||
resume_point_(0),
|
|
||||||
delay_time_(0),
|
|
||||||
index_hole_count_target_(-1),
|
|
||||||
delegate_(nullptr),
|
|
||||||
personality_(p),
|
personality_(p),
|
||||||
head_is_loaded_(false) {
|
interesting_event_mask_(static_cast<int>(Event1770::Command)) {
|
||||||
set_is_double_density(false);
|
set_is_double_density(false);
|
||||||
posit_event((int)Event1770::Command);
|
posit_event(static_cast<int>(Event1770::Command));
|
||||||
}
|
}
|
||||||
|
|
||||||
void WD1770::set_register(int address, uint8_t value) {
|
void WD1770::set_register(int address, uint8_t value) {
|
||||||
switch(address&3) {
|
switch(address&3) {
|
||||||
case 0: {
|
case 0: {
|
||||||
if((value&0xf0) == 0xd0) {
|
if((value&0xf0) == 0xd0) {
|
||||||
|
if(value == 0xd0) {
|
||||||
|
// Force interrupt **immediately**.
|
||||||
|
printf("Force interrupt immediately\n");
|
||||||
|
posit_event(static_cast<int>(Event1770::ForceInterrupt));
|
||||||
|
} else {
|
||||||
printf("!!!TODO: force interrupt!!!\n");
|
printf("!!!TODO: force interrupt!!!\n");
|
||||||
update_status([] (Status &status) {
|
update_status([] (Status &status) {
|
||||||
status.type = Status::One;
|
status.type = Status::One;
|
||||||
});
|
});
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
command_ = value;
|
command_ = value;
|
||||||
posit_event((int)Event1770::Command);
|
posit_event(static_cast<int>(Event1770::Command));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@@ -75,7 +63,7 @@ uint8_t WD1770::get_register(int address) {
|
|||||||
switch(status_.type) {
|
switch(status_.type) {
|
||||||
case Status::One:
|
case Status::One:
|
||||||
status |=
|
status |=
|
||||||
(get_is_track_zero() ? Flag::TrackZero : 0) |
|
(get_drive().get_is_track_zero() ? Flag::TrackZero : 0) |
|
||||||
(status_.seek_error ? Flag::SeekError : 0);
|
(status_.seek_error ? Flag::SeekError : 0);
|
||||||
// TODO: index hole
|
// TODO: index hole
|
||||||
break;
|
break;
|
||||||
@@ -91,11 +79,11 @@ uint8_t WD1770::get_register(int address) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if(!has_motor_on_line()) {
|
if(!has_motor_on_line()) {
|
||||||
status |= get_drive_is_ready() ? 0 : Flag::NotReady;
|
status |= get_drive().get_is_ready() ? 0 : Flag::NotReady;
|
||||||
if(status_.type == Status::One)
|
if(status_.type == Status::One)
|
||||||
status |= (head_is_loaded_ ? Flag::HeadLoaded : 0);
|
status |= (head_is_loaded_ ? Flag::HeadLoaded : 0);
|
||||||
} else {
|
} else {
|
||||||
status |= (get_motor_on() ? Flag::MotorOn : 0);
|
status |= (get_drive().get_motor_on() ? Flag::MotorOn : 0);
|
||||||
if(status_.type == Status::One)
|
if(status_.type == Status::One)
|
||||||
status |= (status_.spin_up ? Flag::SpinUp : 0);
|
status |= (status_.spin_up ? Flag::SpinUp : 0);
|
||||||
}
|
}
|
||||||
@@ -115,24 +103,24 @@ void WD1770::run_for(const Cycles cycles) {
|
|||||||
Storage::Disk::Controller::run_for(cycles);
|
Storage::Disk::Controller::run_for(cycles);
|
||||||
|
|
||||||
if(delay_time_) {
|
if(delay_time_) {
|
||||||
unsigned int number_of_cycles = (unsigned int)cycles.as_int();
|
unsigned int number_of_cycles = static_cast<unsigned int>(cycles.as_int());
|
||||||
if(delay_time_ <= number_of_cycles) {
|
if(delay_time_ <= number_of_cycles) {
|
||||||
delay_time_ = 0;
|
delay_time_ = 0;
|
||||||
posit_event((int)Event1770::Timer);
|
posit_event(static_cast<int>(Event1770::Timer));
|
||||||
} else {
|
} else {
|
||||||
delay_time_ -= number_of_cycles;
|
delay_time_ -= number_of_cycles;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#define WAIT_FOR_EVENT(mask) resume_point_ = __LINE__; interesting_event_mask_ = (int)mask; return; case __LINE__:
|
#define WAIT_FOR_EVENT(mask) resume_point_ = __LINE__; interesting_event_mask_ = static_cast<int>(mask); return; case __LINE__:
|
||||||
#define WAIT_FOR_TIME(ms) resume_point_ = __LINE__; delay_time_ = ms * 8000; WAIT_FOR_EVENT(Event1770::Timer);
|
#define WAIT_FOR_TIME(ms) resume_point_ = __LINE__; delay_time_ = ms * 8000; WAIT_FOR_EVENT(Event1770::Timer);
|
||||||
#define WAIT_FOR_BYTES(count) resume_point_ = __LINE__; distance_into_section_ = 0; WAIT_FOR_EVENT(Event::Token); if(get_latest_token().type == Token::Byte) distance_into_section_++; if(distance_into_section_ < count) { interesting_event_mask_ = (int)Event::Token; return; }
|
#define WAIT_FOR_BYTES(count) resume_point_ = __LINE__; distance_into_section_ = 0; WAIT_FOR_EVENT(Event::Token); if(get_latest_token().type == Token::Byte) distance_into_section_++; if(distance_into_section_ < count) { interesting_event_mask_ = static_cast<int>(Event::Token); return; }
|
||||||
#define BEGIN_SECTION() switch(resume_point_) { default:
|
#define BEGIN_SECTION() switch(resume_point_) { default:
|
||||||
#define END_SECTION() 0; }
|
#define END_SECTION() (void)0; }
|
||||||
|
|
||||||
#define READ_ID() \
|
#define READ_ID() \
|
||||||
if(new_event_type == (int)Event::Token) { \
|
if(new_event_type == static_cast<int>(Event::Token)) { \
|
||||||
if(!distance_into_section_ && get_latest_token().type == Token::ID) {set_data_mode(DataMode::Reading); distance_into_section_++; } \
|
if(!distance_into_section_ && get_latest_token().type == Token::ID) {set_data_mode(DataMode::Reading); distance_into_section_++; } \
|
||||||
else if(distance_into_section_ && distance_into_section_ < 7 && get_latest_token().type == Token::Byte) { \
|
else if(distance_into_section_ && distance_into_section_ < 7 && get_latest_token().type == Token::Byte) { \
|
||||||
header_[distance_into_section_ - 1] = get_latest_token().byte_value; \
|
header_[distance_into_section_ - 1] = get_latest_token().byte_value; \
|
||||||
@@ -169,10 +157,10 @@ void WD1770::run_for(const Cycles cycles) {
|
|||||||
// +--------+----------+-------------------------+
|
// +--------+----------+-------------------------+
|
||||||
|
|
||||||
void WD1770::posit_event(int new_event_type) {
|
void WD1770::posit_event(int new_event_type) {
|
||||||
if(new_event_type == (int)Event::IndexHole) {
|
if(new_event_type == static_cast<int>(Event::IndexHole)) {
|
||||||
index_hole_count_++;
|
index_hole_count_++;
|
||||||
if(index_hole_count_target_ == index_hole_count_) {
|
if(index_hole_count_target_ == index_hole_count_) {
|
||||||
posit_event((int)Event1770::IndexHoleTarget);
|
posit_event(static_cast<int>(Event1770::IndexHoleTarget));
|
||||||
index_hole_count_target_ = -1;
|
index_hole_count_target_ = -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -187,13 +175,23 @@ void WD1770::posit_event(int new_event_type) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!(interesting_event_mask_ & (int)new_event_type)) return;
|
if(new_event_type == static_cast<int>(Event1770::ForceInterrupt)) {
|
||||||
|
interesting_event_mask_ = 0;
|
||||||
|
resume_point_ = 0;
|
||||||
|
update_status([] (Status &status) {
|
||||||
|
status.type = Status::One;
|
||||||
|
status.data_request = false;
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
if(!(interesting_event_mask_ & static_cast<int>(new_event_type))) return;
|
||||||
interesting_event_mask_ &= ~new_event_type;
|
interesting_event_mask_ &= ~new_event_type;
|
||||||
|
}
|
||||||
|
|
||||||
Status new_status;
|
Status new_status;
|
||||||
BEGIN_SECTION()
|
BEGIN_SECTION()
|
||||||
|
|
||||||
// Wait for a new command, branch to the appropriate handler.
|
// Wait for a new command, branch to the appropriate handler.
|
||||||
|
case 0:
|
||||||
wait_for_command:
|
wait_for_command:
|
||||||
printf("Idle...\n");
|
printf("Idle...\n");
|
||||||
set_data_mode(DataMode::Scanning);
|
set_data_mode(DataMode::Scanning);
|
||||||
@@ -257,7 +255,7 @@ void WD1770::posit_event(int new_event_type) {
|
|||||||
goto test_type1_type;
|
goto test_type1_type;
|
||||||
|
|
||||||
begin_type1_spin_up:
|
begin_type1_spin_up:
|
||||||
if((command_&0x08) || get_motor_on()) goto test_type1_type;
|
if((command_&0x08) || get_drive().get_motor_on()) goto test_type1_type;
|
||||||
SPIN_UP();
|
SPIN_UP();
|
||||||
|
|
||||||
test_type1_type:
|
test_type1_type:
|
||||||
@@ -280,11 +278,11 @@ void WD1770::posit_event(int new_event_type) {
|
|||||||
if(step_direction_) track_++; else track_--;
|
if(step_direction_) track_++; else track_--;
|
||||||
|
|
||||||
perform_step:
|
perform_step:
|
||||||
if(!step_direction_ && get_is_track_zero()) {
|
if(!step_direction_ && get_drive().get_is_track_zero()) {
|
||||||
track_ = 0;
|
track_ = 0;
|
||||||
goto verify;
|
goto verify;
|
||||||
}
|
}
|
||||||
step(step_direction_ ? 1 : -1);
|
get_drive().step(step_direction_ ? 1 : -1);
|
||||||
unsigned int time_to_wait;
|
unsigned int time_to_wait;
|
||||||
switch(command_ & 3) {
|
switch(command_ & 3) {
|
||||||
default:
|
default:
|
||||||
@@ -310,7 +308,7 @@ void WD1770::posit_event(int new_event_type) {
|
|||||||
distance_into_section_ = 0;
|
distance_into_section_ = 0;
|
||||||
|
|
||||||
verify_read_data:
|
verify_read_data:
|
||||||
WAIT_FOR_EVENT((int)Event::IndexHole | (int)Event::Token);
|
WAIT_FOR_EVENT(static_cast<int>(Event::IndexHole) | static_cast<int>(Event::Token));
|
||||||
READ_ID();
|
READ_ID();
|
||||||
|
|
||||||
if(index_hole_count_ == 6) {
|
if(index_hole_count_ == 6) {
|
||||||
@@ -376,7 +374,7 @@ void WD1770::posit_event(int new_event_type) {
|
|||||||
goto test_type2_delay;
|
goto test_type2_delay;
|
||||||
|
|
||||||
begin_type2_spin_up:
|
begin_type2_spin_up:
|
||||||
if(get_motor_on()) goto test_type2_delay;
|
if(get_drive().get_motor_on()) goto test_type2_delay;
|
||||||
// Perform spin up.
|
// Perform spin up.
|
||||||
SPIN_UP();
|
SPIN_UP();
|
||||||
|
|
||||||
@@ -386,7 +384,7 @@ void WD1770::posit_event(int new_event_type) {
|
|||||||
WAIT_FOR_TIME(30);
|
WAIT_FOR_TIME(30);
|
||||||
|
|
||||||
test_type2_write_protection:
|
test_type2_write_protection:
|
||||||
if(command_&0x20 && get_drive_is_read_only()) {
|
if(command_&0x20 && get_drive().get_is_read_only()) {
|
||||||
update_status([] (Status &status) {
|
update_status([] (Status &status) {
|
||||||
status.write_protect = true;
|
status.write_protect = true;
|
||||||
});
|
});
|
||||||
@@ -394,7 +392,7 @@ void WD1770::posit_event(int new_event_type) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type2_get_header:
|
type2_get_header:
|
||||||
WAIT_FOR_EVENT((int)Event::IndexHole | (int)Event::Token);
|
WAIT_FOR_EVENT(static_cast<int>(Event::IndexHole) | static_cast<int>(Event::Token));
|
||||||
READ_ID();
|
READ_ID();
|
||||||
|
|
||||||
if(index_hole_count_ == 5) {
|
if(index_hole_count_ == 5) {
|
||||||
@@ -478,7 +476,7 @@ void WD1770::posit_event(int new_event_type) {
|
|||||||
sector_++;
|
sector_++;
|
||||||
goto test_type2_write_protection;
|
goto test_type2_write_protection;
|
||||||
}
|
}
|
||||||
printf("Read sector %d\n", sector_);
|
printf("Finished reading sector %d\n", sector_);
|
||||||
goto wait_for_command;
|
goto wait_for_command;
|
||||||
}
|
}
|
||||||
goto type2_check_crc;
|
goto type2_check_crc;
|
||||||
@@ -594,7 +592,7 @@ void WD1770::posit_event(int new_event_type) {
|
|||||||
goto type3_test_delay;
|
goto type3_test_delay;
|
||||||
|
|
||||||
begin_type3_spin_up:
|
begin_type3_spin_up:
|
||||||
if((command_&0x08) || get_motor_on()) goto type3_test_delay;
|
if((command_&0x08) || get_drive().get_motor_on()) goto type3_test_delay;
|
||||||
SPIN_UP();
|
SPIN_UP();
|
||||||
|
|
||||||
type3_test_delay:
|
type3_test_delay:
|
||||||
@@ -611,8 +609,8 @@ void WD1770::posit_event(int new_event_type) {
|
|||||||
distance_into_section_ = 0;
|
distance_into_section_ = 0;
|
||||||
|
|
||||||
read_address_get_header:
|
read_address_get_header:
|
||||||
WAIT_FOR_EVENT((int)Event::IndexHole | (int)Event::Token);
|
WAIT_FOR_EVENT(static_cast<int>(Event::IndexHole) | static_cast<int>(Event::Token));
|
||||||
if(new_event_type == (int)Event::Token) {
|
if(new_event_type == static_cast<int>(Event::Token)) {
|
||||||
if(!distance_into_section_ && get_latest_token().type == Token::ID) {set_data_mode(DataMode::Reading); distance_into_section_++; }
|
if(!distance_into_section_ && get_latest_token().type == Token::ID) {set_data_mode(DataMode::Reading); distance_into_section_++; }
|
||||||
else if(distance_into_section_ && distance_into_section_ < 7 && get_latest_token().type == Token::Byte) {
|
else if(distance_into_section_ && distance_into_section_ < 7 && get_latest_token().type == Token::Byte) {
|
||||||
if(status_.data_request) {
|
if(status_.data_request) {
|
||||||
@@ -652,7 +650,7 @@ void WD1770::posit_event(int new_event_type) {
|
|||||||
index_hole_count_ = 0;
|
index_hole_count_ = 0;
|
||||||
|
|
||||||
read_track_read_byte:
|
read_track_read_byte:
|
||||||
WAIT_FOR_EVENT((int)Event::Token | (int)Event::IndexHole);
|
WAIT_FOR_EVENT(static_cast<int>(Event::Token) | static_cast<int>(Event::IndexHole));
|
||||||
if(index_hole_count_) {
|
if(index_hole_count_) {
|
||||||
goto wait_for_command;
|
goto wait_for_command;
|
||||||
}
|
}
|
||||||
@@ -674,8 +672,7 @@ void WD1770::posit_event(int new_event_type) {
|
|||||||
status.lost_data = false;
|
status.lost_data = false;
|
||||||
});
|
});
|
||||||
|
|
||||||
write_track_test_write_protect:
|
if(get_drive().get_is_read_only()) {
|
||||||
if(get_drive_is_read_only()) {
|
|
||||||
update_status([] (Status &status) {
|
update_status([] (Status &status) {
|
||||||
status.write_protect = true;
|
status.write_protect = true;
|
||||||
});
|
});
|
||||||
@@ -720,7 +717,7 @@ void WD1770::posit_event(int new_event_type) {
|
|||||||
case 0xfd: case 0xfe:
|
case 0xfd: case 0xfe:
|
||||||
// clock is 0xc7 = 1010 0000 0010 1010 = 0xa022
|
// clock is 0xc7 = 1010 0000 0010 1010 = 0xa022
|
||||||
write_raw_short(
|
write_raw_short(
|
||||||
(uint16_t)(
|
static_cast<uint16_t>(
|
||||||
0xa022 |
|
0xa022 |
|
||||||
((data_ & 0x80) << 7) |
|
((data_ & 0x80) << 7) |
|
||||||
((data_ & 0x40) << 6) |
|
((data_ & 0x40) << 6) |
|
||||||
@@ -781,8 +778,9 @@ void WD1770::update_status(std::function<void(Status &)> updater) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void WD1770::set_head_load_request(bool head_load) {}
|
void WD1770::set_head_load_request(bool head_load) {}
|
||||||
|
void WD1770::set_motor_on(bool motor_on) {}
|
||||||
|
|
||||||
void WD1770::set_head_loaded(bool head_loaded) {
|
void WD1770::set_head_loaded(bool head_loaded) {
|
||||||
head_is_loaded_ = head_loaded;
|
head_is_loaded_ = head_loaded;
|
||||||
if(head_loaded) posit_event((int)Event1770::HeadLoad);
|
if(head_loaded) posit_event(static_cast<int>(Event1770::HeadLoad));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
#ifndef _770_hpp
|
#ifndef _770_hpp
|
||||||
#define _770_hpp
|
#define _770_hpp
|
||||||
|
|
||||||
#include "../../Storage/Disk/MFMDiskController.hpp"
|
#include "../../Storage/Disk/Controller/MFMDiskController.hpp"
|
||||||
|
|
||||||
namespace WD {
|
namespace WD {
|
||||||
|
|
||||||
@@ -76,6 +76,7 @@ class WD1770: public Storage::Disk::MFMController {
|
|||||||
|
|
||||||
protected:
|
protected:
|
||||||
virtual void set_head_load_request(bool head_load);
|
virtual void set_head_load_request(bool head_load);
|
||||||
|
virtual void set_motor_on(bool motor_on);
|
||||||
void set_head_loaded(bool head_loaded);
|
void set_head_loaded(bool head_loaded);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
@@ -84,20 +85,19 @@ class WD1770: public Storage::Disk::MFMController {
|
|||||||
inline bool has_head_load_line() { return (personality_ == P1793 ); }
|
inline bool has_head_load_line() { return (personality_ == P1793 ); }
|
||||||
|
|
||||||
struct Status {
|
struct Status {
|
||||||
Status();
|
bool write_protect = false;
|
||||||
bool write_protect;
|
bool record_type = false;
|
||||||
bool record_type;
|
bool spin_up = false;
|
||||||
bool spin_up;
|
bool record_not_found = false;
|
||||||
bool record_not_found;
|
bool crc_error = false;
|
||||||
bool crc_error;
|
bool seek_error = false;
|
||||||
bool seek_error;
|
bool lost_data = false;
|
||||||
bool lost_data;
|
bool data_request = false;
|
||||||
bool data_request;
|
bool interrupt_request = false;
|
||||||
bool interrupt_request;
|
bool busy = false;
|
||||||
bool busy;
|
|
||||||
enum {
|
enum {
|
||||||
One, Two, Three
|
One, Two, Three
|
||||||
} type;
|
} type = One;
|
||||||
} status_;
|
} status_;
|
||||||
uint8_t track_;
|
uint8_t track_;
|
||||||
uint8_t sector_;
|
uint8_t sector_;
|
||||||
@@ -105,7 +105,7 @@ class WD1770: public Storage::Disk::MFMController {
|
|||||||
uint8_t command_;
|
uint8_t command_;
|
||||||
|
|
||||||
int index_hole_count_;
|
int index_hole_count_;
|
||||||
int index_hole_count_target_;
|
int index_hole_count_target_ = -1;
|
||||||
int distance_into_section_;
|
int distance_into_section_;
|
||||||
|
|
||||||
int step_direction_;
|
int step_direction_;
|
||||||
@@ -116,21 +116,22 @@ class WD1770: public Storage::Disk::MFMController {
|
|||||||
Command = (1 << 3), // Indicates receipt of a new command.
|
Command = (1 << 3), // Indicates receipt of a new command.
|
||||||
HeadLoad = (1 << 4), // Indicates the head has been loaded (1973 only).
|
HeadLoad = (1 << 4), // Indicates the head has been loaded (1973 only).
|
||||||
Timer = (1 << 5), // Indicates that the delay_time_-powered timer has timed out.
|
Timer = (1 << 5), // Indicates that the delay_time_-powered timer has timed out.
|
||||||
IndexHoleTarget = (1 << 6) // Indicates that index_hole_count_ has reached index_hole_count_target_.
|
IndexHoleTarget = (1 << 6), // Indicates that index_hole_count_ has reached index_hole_count_target_.
|
||||||
|
ForceInterrupt = (1 << 7) // Indicates a forced interrupt.
|
||||||
};
|
};
|
||||||
void posit_event(int type);
|
void posit_event(int type);
|
||||||
int interesting_event_mask_;
|
int interesting_event_mask_;
|
||||||
int resume_point_;
|
int resume_point_ = 0;
|
||||||
unsigned int delay_time_;
|
unsigned int delay_time_ = 0;
|
||||||
|
|
||||||
// ID buffer
|
// ID buffer
|
||||||
uint8_t header_[6];
|
uint8_t header_[6];
|
||||||
|
|
||||||
// 1793 head-loading logic
|
// 1793 head-loading logic
|
||||||
bool head_is_loaded_;
|
bool head_is_loaded_ = false;
|
||||||
|
|
||||||
// delegate
|
// delegate
|
||||||
Delegate *delegate_;
|
Delegate *delegate_ = nullptr;
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,9 +13,83 @@
|
|||||||
#include <typeinfo>
|
#include <typeinfo>
|
||||||
#include <cstdio>
|
#include <cstdio>
|
||||||
|
|
||||||
|
#include "Implementation/6522Storage.hpp"
|
||||||
|
|
||||||
#include "../../ClockReceiver/ClockReceiver.hpp"
|
#include "../../ClockReceiver/ClockReceiver.hpp"
|
||||||
|
|
||||||
namespace MOS {
|
namespace MOS {
|
||||||
|
namespace MOS6522 {
|
||||||
|
|
||||||
|
enum Port {
|
||||||
|
A = 0,
|
||||||
|
B = 1
|
||||||
|
};
|
||||||
|
|
||||||
|
enum Line {
|
||||||
|
One = 0,
|
||||||
|
Two = 1
|
||||||
|
};
|
||||||
|
|
||||||
|
/*!
|
||||||
|
Provides the mechanism for just-int-time communication from a 6522; the normal use case is to compose a
|
||||||
|
6522 and a subclass of PortHandler in order to reproduce a 6522 and its original bus wiring.
|
||||||
|
*/
|
||||||
|
class PortHandler {
|
||||||
|
public:
|
||||||
|
/// Requests the current input value of @c port from the port handler.
|
||||||
|
uint8_t get_port_input(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(Port port, uint8_t value, uint8_t direction_mask) {}
|
||||||
|
|
||||||
|
/// Sets the current logical output level for line @c line on port @c port.
|
||||||
|
void set_control_line_output(Port port, Line line, bool value) {}
|
||||||
|
|
||||||
|
/// Sets the current logical value of the interrupt line.
|
||||||
|
void set_interrupt_status(bool status) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
/*!
|
||||||
|
Provided as an optional alternative base to @c PortHandler for port handlers; via the delegate pattern adds
|
||||||
|
a virtual level of indirection for receiving changes to the interrupt line.
|
||||||
|
*/
|
||||||
|
class IRQDelegatePortHandler: public PortHandler {
|
||||||
|
public:
|
||||||
|
class Delegate {
|
||||||
|
public:
|
||||||
|
/// Indicates that the interrupt status has changed for the IRQDelegatePortHandler provided.
|
||||||
|
virtual void mos6522_did_change_interrupt_status(void *irq_delegate) = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Sets the delegate that will receive notification of changes in the interrupt line.
|
||||||
|
void set_interrupt_delegate(Delegate *delegate);
|
||||||
|
|
||||||
|
/// Overrides PortHandler::set_interrupt_status, notifying the delegate if one is set.
|
||||||
|
void set_interrupt_status(bool new_status);
|
||||||
|
|
||||||
|
private:
|
||||||
|
Delegate *delegate_ = nullptr;
|
||||||
|
};
|
||||||
|
|
||||||
|
class MOS6522Base: public MOS6522Storage {
|
||||||
|
public:
|
||||||
|
/// Sets the input value of line @c line on port @c port.
|
||||||
|
void set_control_line_input(Port port, Line line, bool value);
|
||||||
|
|
||||||
|
/// Runs for a specified number of half cycles.
|
||||||
|
void run_for(const HalfCycles half_cycles);
|
||||||
|
|
||||||
|
/// Runs for a specified number of cycles.
|
||||||
|
void run_for(const Cycles cycles);
|
||||||
|
|
||||||
|
/// @returns @c true if the IRQ line is currently active; @c false otherwise.
|
||||||
|
bool get_interrupt_line();
|
||||||
|
|
||||||
|
private:
|
||||||
|
inline void do_phase1();
|
||||||
|
inline void do_phase2();
|
||||||
|
virtual void reevaluate_interrupts() = 0;
|
||||||
|
};
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
Implements a template for emulation of the MOS 6522 Versatile Interface Adaptor ('VIA').
|
Implements a template for emulation of the MOS 6522 Versatile Interface Adaptor ('VIA').
|
||||||
@@ -28,353 +102,27 @@ namespace MOS {
|
|||||||
Consumers should derive their own curiously-recurring-template-pattern subclass,
|
Consumers should derive their own curiously-recurring-template-pattern subclass,
|
||||||
implementing bus communications as required.
|
implementing bus communications as required.
|
||||||
*/
|
*/
|
||||||
template <class T> class MOS6522 {
|
template <class T> class MOS6522: public MOS6522Base {
|
||||||
private:
|
|
||||||
enum InterruptFlag: uint8_t {
|
|
||||||
CA2ActiveEdge = 1 << 0,
|
|
||||||
CA1ActiveEdge = 1 << 1,
|
|
||||||
ShiftRegister = 1 << 2,
|
|
||||||
CB2ActiveEdge = 1 << 3,
|
|
||||||
CB1ActiveEdge = 1 << 4,
|
|
||||||
Timer2 = 1 << 5,
|
|
||||||
Timer1 = 1 << 6,
|
|
||||||
};
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
enum Port {
|
MOS6522(T &bus_handler) noexcept : bus_handler_(bus_handler) {}
|
||||||
A = 0,
|
MOS6522(const MOS6522 &) = delete;
|
||||||
B = 1
|
|
||||||
};
|
|
||||||
|
|
||||||
enum Line {
|
|
||||||
One = 0,
|
|
||||||
Two = 1
|
|
||||||
};
|
|
||||||
|
|
||||||
/*! Sets a register value. */
|
/*! Sets a register value. */
|
||||||
inline void set_register(int address, uint8_t value) {
|
void set_register(int address, uint8_t value);
|
||||||
address &= 0xf;
|
|
||||||
// printf("6522 [%s]: %0x <- %02x\n", typeid(*this).name(), address, value);
|
|
||||||
switch(address) {
|
|
||||||
case 0x0:
|
|
||||||
registers_.output[1] = value;
|
|
||||||
static_cast<T *>(this)->set_port_output(Port::B, value, registers_.data_direction[1]); // TODO: handshake
|
|
||||||
|
|
||||||
registers_.interrupt_flags &= ~(InterruptFlag::CB1ActiveEdge | ((registers_.peripheral_control&0x20) ? 0 : InterruptFlag::CB2ActiveEdge));
|
|
||||||
reevaluate_interrupts();
|
|
||||||
break;
|
|
||||||
case 0xf:
|
|
||||||
case 0x1:
|
|
||||||
registers_.output[0] = value;
|
|
||||||
static_cast<T *>(this)->set_port_output(Port::A, value, registers_.data_direction[0]); // TODO: handshake
|
|
||||||
|
|
||||||
registers_.interrupt_flags &= ~(InterruptFlag::CA1ActiveEdge | ((registers_.peripheral_control&0x02) ? 0 : InterruptFlag::CB2ActiveEdge));
|
|
||||||
reevaluate_interrupts();
|
|
||||||
break;
|
|
||||||
// // No handshake, so write directly
|
|
||||||
// registers_.output[0] = value;
|
|
||||||
// static_cast<T *>(this)->set_port_output(0, value);
|
|
||||||
// break;
|
|
||||||
|
|
||||||
case 0x2:
|
|
||||||
registers_.data_direction[1] = value;
|
|
||||||
break;
|
|
||||||
case 0x3:
|
|
||||||
registers_.data_direction[0] = value;
|
|
||||||
break;
|
|
||||||
|
|
||||||
// Timer 1
|
|
||||||
case 0x6: case 0x4: registers_.timer_latch[0] = (registers_.timer_latch[0]&0xff00) | value; break;
|
|
||||||
case 0x5: case 0x7:
|
|
||||||
registers_.timer_latch[0] = (registers_.timer_latch[0]&0x00ff) | (uint16_t)(value << 8);
|
|
||||||
registers_.interrupt_flags &= ~InterruptFlag::Timer1;
|
|
||||||
if(address == 0x05) {
|
|
||||||
registers_.next_timer[0] = registers_.timer_latch[0];
|
|
||||||
timer_is_running_[0] = true;
|
|
||||||
}
|
|
||||||
reevaluate_interrupts();
|
|
||||||
break;
|
|
||||||
|
|
||||||
// Timer 2
|
|
||||||
case 0x8: registers_.timer_latch[1] = value; break;
|
|
||||||
case 0x9:
|
|
||||||
registers_.interrupt_flags &= ~InterruptFlag::Timer2;
|
|
||||||
registers_.next_timer[1] = registers_.timer_latch[1] | (uint16_t)(value << 8);
|
|
||||||
timer_is_running_[1] = true;
|
|
||||||
reevaluate_interrupts();
|
|
||||||
break;
|
|
||||||
|
|
||||||
// Shift
|
|
||||||
case 0xa: registers_.shift = value; break;
|
|
||||||
|
|
||||||
// Control
|
|
||||||
case 0xb:
|
|
||||||
registers_.auxiliary_control = value;
|
|
||||||
break;
|
|
||||||
case 0xc:
|
|
||||||
// printf("Peripheral control %02x\n", value);
|
|
||||||
registers_.peripheral_control = value;
|
|
||||||
|
|
||||||
// TODO: simplify below; trying to avoid improper logging of unimplemented warnings in input mode
|
|
||||||
if(value & 0x08) {
|
|
||||||
switch(value & 0x0e) {
|
|
||||||
default: printf("Unimplemented control line mode %d\n", (value >> 1)&7); break;
|
|
||||||
case 0x0c: static_cast<T *>(this)->set_control_line_output(Port::A, Line::Two, false); break;
|
|
||||||
case 0x0e: static_cast<T *>(this)->set_control_line_output(Port::A, Line::Two, true); break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if(value & 0x80) {
|
|
||||||
switch(value & 0xe0) {
|
|
||||||
default: printf("Unimplemented control line mode %d\n", (value >> 5)&7); break;
|
|
||||||
case 0xc0: static_cast<T *>(this)->set_control_line_output(Port::B, Line::Two, false); break;
|
|
||||||
case 0xe0: static_cast<T *>(this)->set_control_line_output(Port::B, Line::Two, true); break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
// Interrupt control
|
|
||||||
case 0xd:
|
|
||||||
registers_.interrupt_flags &= ~value;
|
|
||||||
reevaluate_interrupts();
|
|
||||||
break;
|
|
||||||
case 0xe:
|
|
||||||
if(value&0x80)
|
|
||||||
registers_.interrupt_enable |= value;
|
|
||||||
else
|
|
||||||
registers_.interrupt_enable &= ~value;
|
|
||||||
reevaluate_interrupts();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*! Gets a register value. */
|
/*! Gets a register value. */
|
||||||
inline uint8_t get_register(int address) {
|
uint8_t get_register(int address);
|
||||||
address &= 0xf;
|
|
||||||
// printf("6522 %p: %d\n", this, address);
|
|
||||||
switch(address) {
|
|
||||||
case 0x0:
|
|
||||||
registers_.interrupt_flags &= ~(InterruptFlag::CB1ActiveEdge | InterruptFlag::CB2ActiveEdge);
|
|
||||||
reevaluate_interrupts();
|
|
||||||
return get_port_input(Port::B, registers_.data_direction[1], registers_.output[1]);
|
|
||||||
case 0xf: // TODO: handshake, latching
|
|
||||||
case 0x1:
|
|
||||||
registers_.interrupt_flags &= ~(InterruptFlag::CA1ActiveEdge | InterruptFlag::CA2ActiveEdge);
|
|
||||||
reevaluate_interrupts();
|
|
||||||
return get_port_input(Port::A, registers_.data_direction[0], registers_.output[0]);
|
|
||||||
|
|
||||||
case 0x2: return registers_.data_direction[1];
|
|
||||||
case 0x3: return registers_.data_direction[0];
|
|
||||||
|
|
||||||
// Timer 1
|
|
||||||
case 0x4:
|
|
||||||
registers_.interrupt_flags &= ~InterruptFlag::Timer1;
|
|
||||||
reevaluate_interrupts();
|
|
||||||
return registers_.timer[0] & 0x00ff;
|
|
||||||
case 0x5: return registers_.timer[0] >> 8;
|
|
||||||
case 0x6: return registers_.timer_latch[0] & 0x00ff;
|
|
||||||
case 0x7: return registers_.timer_latch[0] >> 8;
|
|
||||||
|
|
||||||
// Timer 2
|
|
||||||
case 0x8:
|
|
||||||
registers_.interrupt_flags &= ~InterruptFlag::Timer2;
|
|
||||||
reevaluate_interrupts();
|
|
||||||
return registers_.timer[1] & 0x00ff;
|
|
||||||
case 0x9: return registers_.timer[1] >> 8;
|
|
||||||
|
|
||||||
case 0xa: return registers_.shift;
|
|
||||||
|
|
||||||
case 0xb: return registers_.auxiliary_control;
|
|
||||||
case 0xc: return registers_.peripheral_control;
|
|
||||||
|
|
||||||
case 0xd: return registers_.interrupt_flags | (get_interrupt_line() ? 0x80 : 0x00);
|
|
||||||
case 0xe: return registers_.interrupt_enable | 0x80;
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0xff;
|
|
||||||
}
|
|
||||||
|
|
||||||
inline void set_control_line_input(Port port, Line line, bool value) {
|
|
||||||
switch(line) {
|
|
||||||
case Line::One:
|
|
||||||
if( value != control_inputs_[port].line_one &&
|
|
||||||
value == !!(registers_.peripheral_control & (port ? 0x10 : 0x01))
|
|
||||||
) {
|
|
||||||
registers_.interrupt_flags |= port ? InterruptFlag::CB1ActiveEdge : InterruptFlag::CA1ActiveEdge;
|
|
||||||
reevaluate_interrupts();
|
|
||||||
}
|
|
||||||
control_inputs_[port].line_one = value;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case Line::Two:
|
|
||||||
// TODO: output modes, but probably elsewhere?
|
|
||||||
if( value != control_inputs_[port].line_two && // i.e. value has changed ...
|
|
||||||
!(registers_.peripheral_control & (port ? 0x80 : 0x08)) && // ... and line is input ...
|
|
||||||
value == !!(registers_.peripheral_control & (port ? 0x40 : 0x04)) // ... and it's either high or low, as required
|
|
||||||
) {
|
|
||||||
registers_.interrupt_flags |= port ? InterruptFlag::CB2ActiveEdge : InterruptFlag::CA2ActiveEdge;
|
|
||||||
reevaluate_interrupts();
|
|
||||||
}
|
|
||||||
control_inputs_[port].line_two = value;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#define phase2() \
|
|
||||||
registers_.last_timer[0] = registers_.timer[0];\
|
|
||||||
registers_.last_timer[1] = registers_.timer[1];\
|
|
||||||
\
|
|
||||||
if(registers_.timer_needs_reload) {\
|
|
||||||
registers_.timer_needs_reload = false;\
|
|
||||||
registers_.timer[0] = registers_.timer_latch[0];\
|
|
||||||
}\
|
|
||||||
else\
|
|
||||||
registers_.timer[0] --;\
|
|
||||||
\
|
|
||||||
registers_.timer[1] --; \
|
|
||||||
if(registers_.next_timer[0] >= 0) { registers_.timer[0] = (uint16_t)registers_.next_timer[0]; registers_.next_timer[0] = -1; }\
|
|
||||||
if(registers_.next_timer[1] >= 0) { registers_.timer[1] = (uint16_t)registers_.next_timer[1]; registers_.next_timer[1] = -1; }\
|
|
||||||
|
|
||||||
// IRQ is raised on the half cycle after overflow
|
|
||||||
#define phase1() \
|
|
||||||
if((registers_.timer[1] == 0xffff) && !registers_.last_timer[1] && timer_is_running_[1]) {\
|
|
||||||
timer_is_running_[1] = false;\
|
|
||||||
registers_.interrupt_flags |= InterruptFlag::Timer2;\
|
|
||||||
reevaluate_interrupts();\
|
|
||||||
}\
|
|
||||||
\
|
|
||||||
if((registers_.timer[0] == 0xffff) && !registers_.last_timer[0] && timer_is_running_[0]) {\
|
|
||||||
registers_.interrupt_flags |= InterruptFlag::Timer1;\
|
|
||||||
reevaluate_interrupts();\
|
|
||||||
\
|
|
||||||
if(registers_.auxiliary_control&0x40)\
|
|
||||||
registers_.timer_needs_reload = true;\
|
|
||||||
else\
|
|
||||||
timer_is_running_[0] = false;\
|
|
||||||
}
|
|
||||||
|
|
||||||
/*! Runs for a specified number of half cycles. */
|
|
||||||
inline void run_for(const HalfCycles half_cycles) {
|
|
||||||
int number_of_half_cycles = half_cycles.as_int();
|
|
||||||
|
|
||||||
if(is_phase2_) {
|
|
||||||
phase2();
|
|
||||||
number_of_half_cycles--;
|
|
||||||
}
|
|
||||||
|
|
||||||
while(number_of_half_cycles >= 2) {
|
|
||||||
phase1();
|
|
||||||
phase2();
|
|
||||||
number_of_half_cycles -= 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(number_of_half_cycles) {
|
|
||||||
phase1();
|
|
||||||
is_phase2_ = true;
|
|
||||||
} else {
|
|
||||||
is_phase2_ = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*! Runs for a specified number of cycles. */
|
|
||||||
inline void run_for(const Cycles cycles) {
|
|
||||||
int number_of_cycles = cycles.as_int();
|
|
||||||
while(number_of_cycles--) {
|
|
||||||
phase1();
|
|
||||||
phase2();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#undef phase1
|
|
||||||
#undef phase2
|
|
||||||
|
|
||||||
/*! @returns @c true if the IRQ line is currently active; @c false otherwise. */
|
|
||||||
inline bool get_interrupt_line() {
|
|
||||||
uint8_t interrupt_status = registers_.interrupt_flags & registers_.interrupt_enable & 0x7f;
|
|
||||||
return !!interrupt_status;
|
|
||||||
}
|
|
||||||
|
|
||||||
MOS6522() :
|
|
||||||
timer_is_running_{false, false},
|
|
||||||
last_posted_interrupt_status_(false),
|
|
||||||
is_phase2_(false) {}
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
// Expected to be overridden
|
T &bus_handler_;
|
||||||
uint8_t get_port_input(Port port) { return 0xff; }
|
|
||||||
void set_port_output(Port port, uint8_t value, uint8_t direction_mask) {}
|
|
||||||
void set_control_line_output(Port port, Line line, bool value) {}
|
|
||||||
void set_interrupt_status(bool status) {}
|
|
||||||
|
|
||||||
// Input/output multiplexer
|
uint8_t get_port_input(Port port, uint8_t output_mask, uint8_t output);
|
||||||
uint8_t get_port_input(Port port, uint8_t output_mask, uint8_t output) {
|
inline void reevaluate_interrupts();
|
||||||
uint8_t input = static_cast<T *>(this)->get_port_input(port);
|
|
||||||
return (input & ~output_mask) | (output & output_mask);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Phase toggle
|
|
||||||
bool is_phase2_;
|
|
||||||
|
|
||||||
// Delegate and communications
|
|
||||||
bool last_posted_interrupt_status_;
|
|
||||||
inline void reevaluate_interrupts() {
|
|
||||||
bool new_interrupt_status = get_interrupt_line();
|
|
||||||
if(new_interrupt_status != last_posted_interrupt_status_) {
|
|
||||||
last_posted_interrupt_status_ = new_interrupt_status;
|
|
||||||
static_cast<T *>(this)->set_interrupt_status(new_interrupt_status);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// The registers
|
|
||||||
struct Registers {
|
|
||||||
uint8_t output[2], input[2], data_direction[2];
|
|
||||||
uint16_t timer[2], timer_latch[2], last_timer[2];
|
|
||||||
int next_timer[2];
|
|
||||||
uint8_t shift;
|
|
||||||
uint8_t auxiliary_control, peripheral_control;
|
|
||||||
uint8_t interrupt_flags, interrupt_enable;
|
|
||||||
bool timer_needs_reload;
|
|
||||||
|
|
||||||
// "A low reset (RES) input clears all R6522 internal registers to logic 0"
|
|
||||||
Registers() :
|
|
||||||
output{0, 0}, input{0, 0}, data_direction{0, 0},
|
|
||||||
auxiliary_control(0), peripheral_control(0),
|
|
||||||
interrupt_flags(0), interrupt_enable(0),
|
|
||||||
last_timer{0, 0}, timer_needs_reload(false),
|
|
||||||
next_timer{-1, -1} {}
|
|
||||||
} registers_;
|
|
||||||
|
|
||||||
// control state
|
|
||||||
struct {
|
|
||||||
bool line_one, line_two;
|
|
||||||
} control_inputs_[2];
|
|
||||||
|
|
||||||
// Internal state other than the registers
|
|
||||||
bool timer_is_running_[2];
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/*!
|
#include "Implementation/6522Implementation.hpp"
|
||||||
Provided for optional composition with @c MOS6522, @c MOS6522IRQDelegate provides for a delegate
|
|
||||||
that will receive IRQ line change notifications.
|
|
||||||
*/
|
|
||||||
class MOS6522IRQDelegate {
|
|
||||||
public:
|
|
||||||
class Delegate {
|
|
||||||
public:
|
|
||||||
virtual void mos6522_did_change_interrupt_status(void *mos6522) = 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
inline void set_interrupt_delegate(Delegate *delegate) {
|
|
||||||
delegate_ = delegate;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
inline void set_interrupt_status(bool new_status) {
|
|
||||||
if(delegate_) delegate_->mos6522_did_change_interrupt_status(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
Delegate *delegate_;
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif /* _522_hpp */
|
#endif /* _522_hpp */
|
||||||
|
|||||||
116
Components/6522/Implementation/6522Base.cpp
Normal file
116
Components/6522/Implementation/6522Base.cpp
Normal file
@@ -0,0 +1,116 @@
|
|||||||
|
//
|
||||||
|
// 6522Base.cpp
|
||||||
|
// Clock Signal
|
||||||
|
//
|
||||||
|
// Created by Thomas Harte on 04/09/2017.
|
||||||
|
// Copyright © 2017 Thomas Harte. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
#include "../6522.hpp"
|
||||||
|
|
||||||
|
using namespace MOS::MOS6522;
|
||||||
|
|
||||||
|
void MOS6522Base::set_control_line_input(Port port, Line line, bool value) {
|
||||||
|
switch(line) {
|
||||||
|
case Line::One:
|
||||||
|
if( value != control_inputs_[port].line_one &&
|
||||||
|
value == !!(registers_.peripheral_control & (port ? 0x10 : 0x01))
|
||||||
|
) {
|
||||||
|
registers_.interrupt_flags |= port ? InterruptFlag::CB1ActiveEdge : InterruptFlag::CA1ActiveEdge;
|
||||||
|
reevaluate_interrupts();
|
||||||
|
}
|
||||||
|
control_inputs_[port].line_one = value;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Line::Two:
|
||||||
|
// TODO: output modes, but probably elsewhere?
|
||||||
|
if( value != control_inputs_[port].line_two && // i.e. value has changed ...
|
||||||
|
!(registers_.peripheral_control & (port ? 0x80 : 0x08)) && // ... and line is input ...
|
||||||
|
value == !!(registers_.peripheral_control & (port ? 0x40 : 0x04)) // ... and it's either high or low, as required
|
||||||
|
) {
|
||||||
|
registers_.interrupt_flags |= port ? InterruptFlag::CB2ActiveEdge : InterruptFlag::CA2ActiveEdge;
|
||||||
|
reevaluate_interrupts();
|
||||||
|
}
|
||||||
|
control_inputs_[port].line_two = value;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void MOS6522Base::do_phase2() {
|
||||||
|
registers_.last_timer[0] = registers_.timer[0];
|
||||||
|
registers_.last_timer[1] = registers_.timer[1];
|
||||||
|
|
||||||
|
if(registers_.timer_needs_reload) {
|
||||||
|
registers_.timer_needs_reload = false;
|
||||||
|
registers_.timer[0] = registers_.timer_latch[0];
|
||||||
|
} else {
|
||||||
|
registers_.timer[0] --;
|
||||||
|
}
|
||||||
|
|
||||||
|
registers_.timer[1] --;
|
||||||
|
if(registers_.next_timer[0] >= 0) {
|
||||||
|
registers_.timer[0] = static_cast<uint16_t>(registers_.next_timer[0]);
|
||||||
|
registers_.next_timer[0] = -1;
|
||||||
|
}
|
||||||
|
if(registers_.next_timer[1] >= 0) {
|
||||||
|
registers_.timer[1] = static_cast<uint16_t>(registers_.next_timer[1]);
|
||||||
|
registers_.next_timer[1] = -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void MOS6522Base::do_phase1() {
|
||||||
|
// IRQ is raised on the half cycle after overflow
|
||||||
|
if((registers_.timer[1] == 0xffff) && !registers_.last_timer[1] && timer_is_running_[1]) {
|
||||||
|
timer_is_running_[1] = false;
|
||||||
|
registers_.interrupt_flags |= InterruptFlag::Timer2;
|
||||||
|
reevaluate_interrupts();
|
||||||
|
}
|
||||||
|
|
||||||
|
if((registers_.timer[0] == 0xffff) && !registers_.last_timer[0] && timer_is_running_[0]) {
|
||||||
|
registers_.interrupt_flags |= InterruptFlag::Timer1;
|
||||||
|
reevaluate_interrupts();
|
||||||
|
|
||||||
|
if(registers_.auxiliary_control&0x40)
|
||||||
|
registers_.timer_needs_reload = true;
|
||||||
|
else
|
||||||
|
timer_is_running_[0] = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*! Runs for a specified number of half cycles. */
|
||||||
|
void MOS6522Base::run_for(const HalfCycles half_cycles) {
|
||||||
|
int number_of_half_cycles = half_cycles.as_int();
|
||||||
|
|
||||||
|
if(is_phase2_) {
|
||||||
|
do_phase2();
|
||||||
|
number_of_half_cycles--;
|
||||||
|
}
|
||||||
|
|
||||||
|
while(number_of_half_cycles >= 2) {
|
||||||
|
do_phase1();
|
||||||
|
do_phase2();
|
||||||
|
number_of_half_cycles -= 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(number_of_half_cycles) {
|
||||||
|
do_phase1();
|
||||||
|
is_phase2_ = true;
|
||||||
|
} else {
|
||||||
|
is_phase2_ = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*! Runs for a specified number of cycles. */
|
||||||
|
void MOS6522Base::run_for(const Cycles cycles) {
|
||||||
|
int number_of_cycles = cycles.as_int();
|
||||||
|
while(number_of_cycles--) {
|
||||||
|
do_phase1();
|
||||||
|
do_phase2();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*! @returns @c true if the IRQ line is currently active; @c false otherwise. */
|
||||||
|
bool MOS6522Base::get_interrupt_line() {
|
||||||
|
uint8_t interrupt_status = registers_.interrupt_flags & registers_.interrupt_enable & 0x7f;
|
||||||
|
return !!interrupt_status;
|
||||||
|
}
|
||||||
155
Components/6522/Implementation/6522Implementation.hpp
Normal file
155
Components/6522/Implementation/6522Implementation.hpp
Normal file
@@ -0,0 +1,155 @@
|
|||||||
|
//
|
||||||
|
// Implementation.hpp
|
||||||
|
// Clock Signal
|
||||||
|
//
|
||||||
|
// Created by Thomas Harte on 04/09/2017.
|
||||||
|
// Copyright © 2017 Thomas Harte. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
template <typename T> void MOS6522<T>::set_register(int address, uint8_t value) {
|
||||||
|
address &= 0xf;
|
||||||
|
switch(address) {
|
||||||
|
case 0x0:
|
||||||
|
registers_.output[1] = value;
|
||||||
|
bus_handler_.set_port_output(Port::B, value, registers_.data_direction[1]); // TODO: handshake
|
||||||
|
|
||||||
|
registers_.interrupt_flags &= ~(InterruptFlag::CB1ActiveEdge | ((registers_.peripheral_control&0x20) ? 0 : InterruptFlag::CB2ActiveEdge));
|
||||||
|
reevaluate_interrupts();
|
||||||
|
break;
|
||||||
|
case 0xf:
|
||||||
|
case 0x1:
|
||||||
|
registers_.output[0] = value;
|
||||||
|
bus_handler_.set_port_output(Port::A, value, registers_.data_direction[0]); // TODO: handshake
|
||||||
|
|
||||||
|
registers_.interrupt_flags &= ~(InterruptFlag::CA1ActiveEdge | ((registers_.peripheral_control&0x02) ? 0 : InterruptFlag::CB2ActiveEdge));
|
||||||
|
reevaluate_interrupts();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 0x2:
|
||||||
|
registers_.data_direction[1] = value;
|
||||||
|
break;
|
||||||
|
case 0x3:
|
||||||
|
registers_.data_direction[0] = value;
|
||||||
|
break;
|
||||||
|
|
||||||
|
// Timer 1
|
||||||
|
case 0x6: case 0x4: registers_.timer_latch[0] = (registers_.timer_latch[0]&0xff00) | value; break;
|
||||||
|
case 0x5: case 0x7:
|
||||||
|
registers_.timer_latch[0] = (registers_.timer_latch[0]&0x00ff) | static_cast<uint16_t>(value << 8);
|
||||||
|
registers_.interrupt_flags &= ~InterruptFlag::Timer1;
|
||||||
|
if(address == 0x05) {
|
||||||
|
registers_.next_timer[0] = registers_.timer_latch[0];
|
||||||
|
timer_is_running_[0] = true;
|
||||||
|
}
|
||||||
|
reevaluate_interrupts();
|
||||||
|
break;
|
||||||
|
|
||||||
|
// Timer 2
|
||||||
|
case 0x8: registers_.timer_latch[1] = value; break;
|
||||||
|
case 0x9:
|
||||||
|
registers_.interrupt_flags &= ~InterruptFlag::Timer2;
|
||||||
|
registers_.next_timer[1] = registers_.timer_latch[1] | static_cast<uint16_t>(value << 8);
|
||||||
|
timer_is_running_[1] = true;
|
||||||
|
reevaluate_interrupts();
|
||||||
|
break;
|
||||||
|
|
||||||
|
// Shift
|
||||||
|
case 0xa: registers_.shift = value; break;
|
||||||
|
|
||||||
|
// Control
|
||||||
|
case 0xb:
|
||||||
|
registers_.auxiliary_control = value;
|
||||||
|
break;
|
||||||
|
case 0xc:
|
||||||
|
// printf("Peripheral control %02x\n", value);
|
||||||
|
registers_.peripheral_control = value;
|
||||||
|
|
||||||
|
// TODO: simplify below; trying to avoid improper logging of unimplemented warnings in input mode
|
||||||
|
if(value & 0x08) {
|
||||||
|
switch(value & 0x0e) {
|
||||||
|
default: printf("Unimplemented control line mode %d\n", (value >> 1)&7); break;
|
||||||
|
case 0x0c: bus_handler_.set_control_line_output(Port::A, Line::Two, false); break;
|
||||||
|
case 0x0e: bus_handler_.set_control_line_output(Port::A, Line::Two, true); break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(value & 0x80) {
|
||||||
|
switch(value & 0xe0) {
|
||||||
|
default: printf("Unimplemented control line mode %d\n", (value >> 5)&7); break;
|
||||||
|
case 0xc0: bus_handler_.set_control_line_output(Port::B, Line::Two, false); break;
|
||||||
|
case 0xe0: bus_handler_.set_control_line_output(Port::B, Line::Two, true); break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
// Interrupt control
|
||||||
|
case 0xd:
|
||||||
|
registers_.interrupt_flags &= ~value;
|
||||||
|
reevaluate_interrupts();
|
||||||
|
break;
|
||||||
|
case 0xe:
|
||||||
|
if(value&0x80)
|
||||||
|
registers_.interrupt_enable |= value;
|
||||||
|
else
|
||||||
|
registers_.interrupt_enable &= ~value;
|
||||||
|
reevaluate_interrupts();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T> uint8_t MOS6522<T>::get_register(int address) {
|
||||||
|
address &= 0xf;
|
||||||
|
switch(address) {
|
||||||
|
case 0x0:
|
||||||
|
registers_.interrupt_flags &= ~(InterruptFlag::CB1ActiveEdge | InterruptFlag::CB2ActiveEdge);
|
||||||
|
reevaluate_interrupts();
|
||||||
|
return get_port_input(Port::B, registers_.data_direction[1], registers_.output[1]);
|
||||||
|
case 0xf: // TODO: handshake, latching
|
||||||
|
case 0x1:
|
||||||
|
registers_.interrupt_flags &= ~(InterruptFlag::CA1ActiveEdge | InterruptFlag::CA2ActiveEdge);
|
||||||
|
reevaluate_interrupts();
|
||||||
|
return get_port_input(Port::A, registers_.data_direction[0], registers_.output[0]);
|
||||||
|
|
||||||
|
case 0x2: return registers_.data_direction[1];
|
||||||
|
case 0x3: return registers_.data_direction[0];
|
||||||
|
|
||||||
|
// Timer 1
|
||||||
|
case 0x4:
|
||||||
|
registers_.interrupt_flags &= ~InterruptFlag::Timer1;
|
||||||
|
reevaluate_interrupts();
|
||||||
|
return registers_.timer[0] & 0x00ff;
|
||||||
|
case 0x5: return registers_.timer[0] >> 8;
|
||||||
|
case 0x6: return registers_.timer_latch[0] & 0x00ff;
|
||||||
|
case 0x7: return registers_.timer_latch[0] >> 8;
|
||||||
|
|
||||||
|
// Timer 2
|
||||||
|
case 0x8:
|
||||||
|
registers_.interrupt_flags &= ~InterruptFlag::Timer2;
|
||||||
|
reevaluate_interrupts();
|
||||||
|
return registers_.timer[1] & 0x00ff;
|
||||||
|
case 0x9: return registers_.timer[1] >> 8;
|
||||||
|
|
||||||
|
case 0xa: return registers_.shift;
|
||||||
|
|
||||||
|
case 0xb: return registers_.auxiliary_control;
|
||||||
|
case 0xc: return registers_.peripheral_control;
|
||||||
|
|
||||||
|
case 0xd: return registers_.interrupt_flags | (get_interrupt_line() ? 0x80 : 0x00);
|
||||||
|
case 0xe: return registers_.interrupt_enable | 0x80;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0xff;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T> uint8_t MOS6522<T>::get_port_input(Port port, uint8_t output_mask, uint8_t output) {
|
||||||
|
uint8_t input = bus_handler_.get_port_input(port);
|
||||||
|
return (input & ~output_mask) | (output & output_mask);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delegate and communications
|
||||||
|
template <typename T> void MOS6522<T>::reevaluate_interrupts() {
|
||||||
|
bool new_interrupt_status = get_interrupt_line();
|
||||||
|
if(new_interrupt_status != last_posted_interrupt_status_) {
|
||||||
|
last_posted_interrupt_status_ = new_interrupt_status;
|
||||||
|
bus_handler_.set_interrupt_status(new_interrupt_status);
|
||||||
|
}
|
||||||
|
}
|
||||||
63
Components/6522/Implementation/6522Storage.hpp
Normal file
63
Components/6522/Implementation/6522Storage.hpp
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
//
|
||||||
|
// 6522Storage.hpp
|
||||||
|
// Clock Signal
|
||||||
|
//
|
||||||
|
// Created by Thomas Harte on 04/09/2017.
|
||||||
|
// Copyright © 2017 Thomas Harte. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef _522Storage_hpp
|
||||||
|
#define _522Storage_hpp
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
namespace MOS {
|
||||||
|
namespace MOS6522 {
|
||||||
|
|
||||||
|
class MOS6522Storage {
|
||||||
|
protected:
|
||||||
|
// Phase toggle
|
||||||
|
bool is_phase2_ = false;
|
||||||
|
|
||||||
|
// The registers
|
||||||
|
struct Registers {
|
||||||
|
// "A low reset (RES) input clears all R6522 internal registers to logic 0"
|
||||||
|
uint8_t output[2] = {0, 0};
|
||||||
|
uint8_t input[2] = {0, 0};
|
||||||
|
uint8_t data_direction[2] = {0, 0};
|
||||||
|
uint16_t timer[2] = {0, 0};
|
||||||
|
uint16_t timer_latch[2] = {0, 0};
|
||||||
|
uint16_t last_timer[2] = {0, 0};
|
||||||
|
int next_timer[2] = {-1, -1};
|
||||||
|
uint8_t shift = 0;
|
||||||
|
uint8_t auxiliary_control = 0;
|
||||||
|
uint8_t peripheral_control = 0;
|
||||||
|
uint8_t interrupt_flags = 0;
|
||||||
|
uint8_t interrupt_enable = 0;
|
||||||
|
bool timer_needs_reload = false;
|
||||||
|
} registers_;
|
||||||
|
|
||||||
|
// control state
|
||||||
|
struct {
|
||||||
|
bool line_one = false;
|
||||||
|
bool line_two = false;
|
||||||
|
} control_inputs_[2];
|
||||||
|
|
||||||
|
bool timer_is_running_[2] = {false, false};
|
||||||
|
bool last_posted_interrupt_status_ = false;
|
||||||
|
|
||||||
|
enum InterruptFlag: uint8_t {
|
||||||
|
CA2ActiveEdge = 1 << 0,
|
||||||
|
CA1ActiveEdge = 1 << 1,
|
||||||
|
ShiftRegister = 1 << 2,
|
||||||
|
CB2ActiveEdge = 1 << 3,
|
||||||
|
CB1ActiveEdge = 1 << 4,
|
||||||
|
Timer2 = 1 << 5,
|
||||||
|
Timer1 = 1 << 6,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /* _522Storage_hpp */
|
||||||
19
Components/6522/Implementation/IRQDelegatePortHandler.cpp
Normal file
19
Components/6522/Implementation/IRQDelegatePortHandler.cpp
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
//
|
||||||
|
// IRQDelegatePortHandler.cpp
|
||||||
|
// Clock Signal
|
||||||
|
//
|
||||||
|
// Created by Thomas Harte on 04/09/2017.
|
||||||
|
// Copyright © 2017 Thomas Harte. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
#include "../6522.hpp"
|
||||||
|
|
||||||
|
using namespace MOS::MOS6522;
|
||||||
|
|
||||||
|
void IRQDelegatePortHandler::set_interrupt_delegate(Delegate *delegate) {
|
||||||
|
delegate_ = delegate;
|
||||||
|
}
|
||||||
|
|
||||||
|
void IRQDelegatePortHandler::set_interrupt_status(bool new_status) {
|
||||||
|
if(delegate_) delegate_->mos6522_did_change_interrupt_status(this);
|
||||||
|
}
|
||||||
@@ -51,7 +51,7 @@ template <class T> class MOS6532 {
|
|||||||
case 0x04: case 0x05: case 0x06: case 0x07:
|
case 0x04: case 0x05: case 0x06: case 0x07:
|
||||||
if(address & 0x10) {
|
if(address & 0x10) {
|
||||||
timer_.writtenShift = timer_.activeShift = (decodedAddress - 0x04) * 3 + (decodedAddress / 0x07); // i.e. 0, 3, 6, 10
|
timer_.writtenShift = timer_.activeShift = (decodedAddress - 0x04) * 3 + (decodedAddress / 0x07); // i.e. 0, 3, 6, 10
|
||||||
timer_.value = ((unsigned int)value << timer_.activeShift) ;
|
timer_.value = (static_cast<unsigned int>(value) << timer_.activeShift) ;
|
||||||
timer_.interrupt_enabled = !!(address&0x08);
|
timer_.interrupt_enabled = !!(address&0x08);
|
||||||
interrupt_status_ &= ~InterruptFlag::Timer;
|
interrupt_status_ &= ~InterruptFlag::Timer;
|
||||||
evaluate_interrupts();
|
evaluate_interrupts();
|
||||||
@@ -79,7 +79,7 @@ template <class T> class MOS6532 {
|
|||||||
|
|
||||||
// Timer and interrupt control
|
// Timer and interrupt control
|
||||||
case 0x04: case 0x06: {
|
case 0x04: case 0x06: {
|
||||||
uint8_t value = (uint8_t)(timer_.value >> timer_.activeShift);
|
uint8_t value = static_cast<uint8_t>(timer_.value >> timer_.activeShift);
|
||||||
timer_.interrupt_enabled = !!(address&0x08);
|
timer_.interrupt_enabled = !!(address&0x08);
|
||||||
interrupt_status_ &= ~InterruptFlag::Timer;
|
interrupt_status_ &= ~InterruptFlag::Timer;
|
||||||
evaluate_interrupts();
|
evaluate_interrupts();
|
||||||
@@ -107,7 +107,7 @@ template <class T> class MOS6532 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
inline void run_for(const Cycles cycles) {
|
inline void run_for(const Cycles cycles) {
|
||||||
unsigned int number_of_cycles = (unsigned int)cycles.as_int();
|
unsigned int number_of_cycles = static_cast<unsigned int>(cycles.as_int());
|
||||||
|
|
||||||
// permit counting _to_ zero; counting _through_ zero initiates the other behaviour
|
// permit counting _to_ zero; counting _through_ zero initiates the other behaviour
|
||||||
if(timer_.value >= number_of_cycles) {
|
if(timer_.value >= number_of_cycles) {
|
||||||
@@ -121,12 +121,9 @@ template <class T> class MOS6532 {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
MOS6532() :
|
MOS6532() {
|
||||||
interrupt_status_(0),
|
timer_.value = static_cast<unsigned int>((rand() & 0xff) << 10);
|
||||||
port_{{.output_mask = 0, .output = 0}, {.output_mask = 0, .output = 0}},
|
}
|
||||||
a7_interrupt_({.last_port_value = 0, .enabled = false}),
|
|
||||||
interrupt_line_(false),
|
|
||||||
timer_{.value = (unsigned int)((rand() & 0xff) << 10), .activeShift = 10, .writtenShift = 10, .interrupt_enabled = false} {}
|
|
||||||
|
|
||||||
inline void set_port_did_change(int port) {
|
inline void set_port_did_change(int port) {
|
||||||
if(!port) {
|
if(!port) {
|
||||||
@@ -154,26 +151,26 @@ template <class T> class MOS6532 {
|
|||||||
|
|
||||||
struct {
|
struct {
|
||||||
unsigned int value;
|
unsigned int value;
|
||||||
unsigned int activeShift, writtenShift;
|
unsigned int activeShift = 10, writtenShift = 10;
|
||||||
bool interrupt_enabled;
|
bool interrupt_enabled = false;
|
||||||
} timer_;
|
} timer_;
|
||||||
|
|
||||||
struct {
|
struct {
|
||||||
bool enabled;
|
bool enabled = false;
|
||||||
bool active_on_positive;
|
bool active_on_positive = false;
|
||||||
uint8_t last_port_value;
|
uint8_t last_port_value = 0;
|
||||||
} a7_interrupt_;
|
} a7_interrupt_;
|
||||||
|
|
||||||
struct {
|
struct {
|
||||||
uint8_t output_mask, output;
|
uint8_t output_mask = 0, output = 0;
|
||||||
} port_[2];
|
} port_[2];
|
||||||
|
|
||||||
uint8_t interrupt_status_;
|
uint8_t interrupt_status_ = 0;
|
||||||
enum InterruptFlag: uint8_t {
|
enum InterruptFlag: uint8_t {
|
||||||
Timer = 0x80,
|
Timer = 0x80,
|
||||||
PA7 = 0x40
|
PA7 = 0x40
|
||||||
};
|
};
|
||||||
bool interrupt_line_;
|
bool interrupt_line_ = false;
|
||||||
|
|
||||||
// expected to be overridden
|
// expected to be overridden
|
||||||
uint8_t get_port_input(int port) { return 0xff; }
|
uint8_t get_port_input(int port) { return 0xff; }
|
||||||
|
|||||||
@@ -8,22 +8,22 @@
|
|||||||
|
|
||||||
#include "6560.hpp"
|
#include "6560.hpp"
|
||||||
|
|
||||||
|
#include <cstring>
|
||||||
|
|
||||||
using namespace MOS;
|
using namespace MOS;
|
||||||
|
|
||||||
Speaker::Speaker() :
|
AudioGenerator::AudioGenerator(Concurrency::DeferringAsyncTaskQueue &audio_queue) :
|
||||||
volume_(0),
|
audio_queue_(audio_queue) {}
|
||||||
control_registers_{0, 0, 0, 0},
|
|
||||||
shift_registers_{0, 0, 0, 0},
|
|
||||||
counters_{2, 1, 0, 0} {} // create a slight phase offset for the three channels
|
|
||||||
|
|
||||||
void Speaker::set_volume(uint8_t volume) {
|
|
||||||
enqueue([=]() {
|
void AudioGenerator::set_volume(uint8_t volume) {
|
||||||
|
audio_queue_.defer([=]() {
|
||||||
volume_ = volume;
|
volume_ = volume;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void Speaker::set_control(int channel, uint8_t value) {
|
void AudioGenerator::set_control(int channel, uint8_t value) {
|
||||||
enqueue([=]() {
|
audio_queue_.defer([=]() {
|
||||||
control_registers_[channel] = value;
|
control_registers_[channel] = value;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -98,14 +98,14 @@ static uint8_t noise_pattern[] = {
|
|||||||
|
|
||||||
#define shift(r) shift_registers_[r] = (shift_registers_[r] << 1) | (((shift_registers_[r]^0x80)&control_registers_[r]) >> 7)
|
#define shift(r) shift_registers_[r] = (shift_registers_[r] << 1) | (((shift_registers_[r]^0x80)&control_registers_[r]) >> 7)
|
||||||
#define increment(r) shift_registers_[r] = (shift_registers_[r]+1)%8191
|
#define increment(r) shift_registers_[r] = (shift_registers_[r]+1)%8191
|
||||||
#define update(r, m, up) counters_[r]++; if((counters_[r] >> m) == 0x80) { up(r); counters_[r] = (unsigned int)(control_registers_[r]&0x7f) << m; }
|
#define update(r, m, up) counters_[r]++; if((counters_[r] >> m) == 0x80) { up(r); counters_[r] = static_cast<unsigned int>(control_registers_[r]&0x7f) << m; }
|
||||||
// Note on slightly askew test: as far as I can make out, if the value in the register is 0x7f then what's supposed to happen
|
// Note on slightly askew test: as far as I can make out, if the value in the register is 0x7f then what's supposed to happen
|
||||||
// is that the 0x7f is loaded, on the next clocked cycle the Vic spots a 0x7f, pumps the output, reloads, etc. No increment
|
// is that the 0x7f is loaded, on the next clocked cycle the Vic spots a 0x7f, pumps the output, reloads, etc. No increment
|
||||||
// ever occurs. It's conditional. I don't really want two conditionals if I can avoid it so I'm incrementing regardless and
|
// ever occurs. It's conditional. I don't really want two conditionals if I can avoid it so I'm incrementing regardless and
|
||||||
// testing against 0x80. The effect should be the same: loading with 0x7f means an output update every cycle, loading with 0x7e
|
// testing against 0x80. The effect should be the same: loading with 0x7f means an output update every cycle, loading with 0x7e
|
||||||
// means every second cycle, etc.
|
// means every second cycle, etc.
|
||||||
|
|
||||||
void Speaker::get_samples(unsigned int number_of_samples, int16_t *target) {
|
void AudioGenerator::get_samples(std::size_t number_of_samples, int16_t *target) {
|
||||||
for(unsigned int c = 0; c < number_of_samples; c++) {
|
for(unsigned int c = 0; c < number_of_samples; c++) {
|
||||||
update(0, 2, shift);
|
update(0, 2, shift);
|
||||||
update(1, 1, shift);
|
update(1, 1, shift);
|
||||||
@@ -123,7 +123,7 @@ void Speaker::get_samples(unsigned int number_of_samples, int16_t *target) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Speaker::skip_samples(unsigned int number_of_samples) {
|
void AudioGenerator::skip_samples(std::size_t number_of_samples) {
|
||||||
for(unsigned int c = 0; c < number_of_samples; c++) {
|
for(unsigned int c = 0; c < number_of_samples; c++) {
|
||||||
update(0, 2, shift);
|
update(0, 2, shift);
|
||||||
update(1, 1, shift);
|
update(1, 1, shift);
|
||||||
|
|||||||
@@ -9,28 +9,32 @@
|
|||||||
#ifndef _560_hpp
|
#ifndef _560_hpp
|
||||||
#define _560_hpp
|
#define _560_hpp
|
||||||
|
|
||||||
#include "../../Outputs/CRT/CRT.hpp"
|
|
||||||
#include "../../Outputs/Speaker.hpp"
|
|
||||||
#include "../../ClockReceiver/ClockReceiver.hpp"
|
#include "../../ClockReceiver/ClockReceiver.hpp"
|
||||||
|
#include "../../Concurrency/AsyncTaskQueue.hpp"
|
||||||
|
#include "../../Outputs/CRT/CRT.hpp"
|
||||||
|
#include "../../Outputs/Speaker/Implementation/LowpassSpeaker.hpp"
|
||||||
|
#include "../../Outputs/Speaker/Implementation/SampleSource.hpp"
|
||||||
|
|
||||||
namespace MOS {
|
namespace MOS {
|
||||||
|
|
||||||
// audio state
|
// audio state
|
||||||
class Speaker: public ::Outputs::Filter<Speaker> {
|
class AudioGenerator: public ::Outputs::Speaker::SampleSource {
|
||||||
public:
|
public:
|
||||||
Speaker();
|
AudioGenerator(Concurrency::DeferringAsyncTaskQueue &audio_queue);
|
||||||
|
|
||||||
void set_volume(uint8_t volume);
|
void set_volume(uint8_t volume);
|
||||||
void set_control(int channel, uint8_t value);
|
void set_control(int channel, uint8_t value);
|
||||||
|
|
||||||
void get_samples(unsigned int number_of_samples, int16_t *target);
|
void get_samples(std::size_t number_of_samples, int16_t *target);
|
||||||
void skip_samples(unsigned int number_of_samples);
|
void skip_samples(std::size_t number_of_samples);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
unsigned int counters_[4];
|
Concurrency::DeferringAsyncTaskQueue &audio_queue_;
|
||||||
unsigned int shift_registers_[4];
|
|
||||||
uint8_t control_registers_[4];
|
unsigned int counters_[4] = {2, 1, 0, 0}; // create a slight phase offset for the three channels
|
||||||
uint8_t volume_;
|
unsigned int shift_registers_[4] = {0, 0, 0, 0};
|
||||||
|
uint8_t control_registers_[4] = {0, 0, 0, 0};
|
||||||
|
uint8_t volume_ = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
@@ -44,13 +48,10 @@ class Speaker: public ::Outputs::Filter<Speaker> {
|
|||||||
template <class T> class MOS6560 {
|
template <class T> class MOS6560 {
|
||||||
public:
|
public:
|
||||||
MOS6560() :
|
MOS6560() :
|
||||||
crt_(new Outputs::CRT::CRT(65*4, 4, Outputs::CRT::NTSC60, 2)),
|
crt_(new Outputs::CRT::CRT(65*4, 4, Outputs::CRT::DisplayType::NTSC60, 2)),
|
||||||
speaker_(new Speaker),
|
audio_generator_(audio_queue_),
|
||||||
horizontal_counter_(0),
|
speaker_(audio_generator_)
|
||||||
vertical_counter_(0),
|
{
|
||||||
cycles_since_speaker_update_(0),
|
|
||||||
is_odd_frame_(false),
|
|
||||||
is_odd_line_(false) {
|
|
||||||
crt_->set_composite_sampling_function(
|
crt_->set_composite_sampling_function(
|
||||||
"float composite_sample(usampler2D texID, vec2 coordinate, vec2 iCoordinate, float phase, float amplitude)"
|
"float composite_sample(usampler2D texID, vec2 coordinate, vec2 iCoordinate, float phase, float amplitude)"
|
||||||
"{"
|
"{"
|
||||||
@@ -66,11 +67,15 @@ template <class T> class MOS6560 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void set_clock_rate(double clock_rate) {
|
void set_clock_rate(double clock_rate) {
|
||||||
speaker_->set_input_rate((float)(clock_rate / 4.0));
|
speaker_.set_input_rate(static_cast<float>(clock_rate / 4.0));
|
||||||
}
|
}
|
||||||
|
|
||||||
std::shared_ptr<Outputs::CRT::CRT> get_crt() { return crt_; }
|
Outputs::CRT::CRT *get_crt() { return crt_.get(); }
|
||||||
std::shared_ptr<Outputs::Speaker> get_speaker() { return speaker_; }
|
Outputs::Speaker::Speaker *get_speaker() { return &speaker_; }
|
||||||
|
|
||||||
|
void set_high_frequency_cutoff(float cutoff) {
|
||||||
|
speaker_.set_high_frequency_cutoff(cutoff);
|
||||||
|
}
|
||||||
|
|
||||||
enum OutputMode {
|
enum OutputMode {
|
||||||
PAL, NTSC
|
PAL, NTSC
|
||||||
@@ -109,9 +114,9 @@ template <class T> class MOS6560 {
|
|||||||
Outputs::CRT::DisplayType display_type;
|
Outputs::CRT::DisplayType display_type;
|
||||||
|
|
||||||
switch(output_mode) {
|
switch(output_mode) {
|
||||||
case OutputMode::PAL:
|
default:
|
||||||
chrominances = pal_chrominances;
|
chrominances = pal_chrominances;
|
||||||
display_type = Outputs::CRT::PAL50;
|
display_type = Outputs::CRT::DisplayType::PAL50;
|
||||||
timing_.cycles_per_line = 71;
|
timing_.cycles_per_line = 71;
|
||||||
timing_.line_counter_increment_offset = 0;
|
timing_.line_counter_increment_offset = 0;
|
||||||
timing_.lines_per_progressive_field = 312;
|
timing_.lines_per_progressive_field = 312;
|
||||||
@@ -120,7 +125,7 @@ template <class T> class MOS6560 {
|
|||||||
|
|
||||||
case OutputMode::NTSC:
|
case OutputMode::NTSC:
|
||||||
chrominances = ntsc_chrominances;
|
chrominances = ntsc_chrominances;
|
||||||
display_type = Outputs::CRT::NTSC60;
|
display_type = Outputs::CRT::DisplayType::NTSC60;
|
||||||
timing_.cycles_per_line = 65;
|
timing_.cycles_per_line = 65;
|
||||||
timing_.line_counter_increment_offset = 65 - 33; // TODO: this is a bit of a hack; separate vertical and horizontal counting
|
timing_.line_counter_increment_offset = 65 - 33; // TODO: this is a bit of a hack; separate vertical and horizontal counting
|
||||||
timing_.lines_per_progressive_field = 261;
|
timing_.lines_per_progressive_field = 261;
|
||||||
@@ -128,7 +133,7 @@ template <class T> class MOS6560 {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
crt_->set_new_display_type((unsigned int)(timing_.cycles_per_line*4), display_type);
|
crt_->set_new_display_type(static_cast<unsigned int>(timing_.cycles_per_line*4), display_type);
|
||||||
crt_->set_visible_area(Outputs::CRT::Rect(0.05f, 0.05f, 0.9f, 0.9f));
|
crt_->set_visible_area(Outputs::CRT::Rect(0.05f, 0.05f, 0.9f, 0.9f));
|
||||||
|
|
||||||
// switch(output_mode) {
|
// switch(output_mode) {
|
||||||
@@ -141,7 +146,7 @@ template <class T> class MOS6560 {
|
|||||||
// }
|
// }
|
||||||
|
|
||||||
for(int c = 0; c < 16; c++) {
|
for(int c = 0; c < 16; c++) {
|
||||||
uint8_t *colour = (uint8_t *)&colours_[c];
|
uint8_t *colour = reinterpret_cast<uint8_t *>(&colours_[c]);
|
||||||
colour[0] = luminances[c];
|
colour[0] = luminances[c];
|
||||||
colour[1] = chrominances[c];
|
colour[1] = chrominances[c];
|
||||||
}
|
}
|
||||||
@@ -218,7 +223,7 @@ template <class T> class MOS6560 {
|
|||||||
if(column_counter_&1) {
|
if(column_counter_&1) {
|
||||||
fetch_address = registers_.character_cell_start_address + (character_code_*(registers_.tall_characters ? 16 : 8)) + current_character_row_;
|
fetch_address = registers_.character_cell_start_address + (character_code_*(registers_.tall_characters ? 16 : 8)) + current_character_row_;
|
||||||
} else {
|
} else {
|
||||||
fetch_address = (uint16_t)(registers_.video_matrix_start_address + video_matrix_address_counter_);
|
fetch_address = static_cast<uint16_t>(registers_.video_matrix_start_address + video_matrix_address_counter_);
|
||||||
video_matrix_address_counter_++;
|
video_matrix_address_counter_++;
|
||||||
if(
|
if(
|
||||||
(current_character_row_ == 15) ||
|
(current_character_row_ == 15) ||
|
||||||
@@ -270,7 +275,7 @@ template <class T> class MOS6560 {
|
|||||||
|
|
||||||
pixel_pointer = nullptr;
|
pixel_pointer = nullptr;
|
||||||
if(output_state_ == State::Pixels) {
|
if(output_state_ == State::Pixels) {
|
||||||
pixel_pointer = (uint16_t *)crt_->allocate_write_area(260);
|
pixel_pointer = reinterpret_cast<uint16_t *>(crt_->allocate_write_area(260));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
cycles_in_state_++;
|
cycles_in_state_++;
|
||||||
@@ -325,7 +330,10 @@ template <class T> class MOS6560 {
|
|||||||
/*!
|
/*!
|
||||||
Causes the 6560 to flush as much pending CRT and speaker communications as possible.
|
Causes the 6560 to flush as much pending CRT and speaker communications as possible.
|
||||||
*/
|
*/
|
||||||
inline void flush() { update_audio(); speaker_->flush(); }
|
inline void flush() {
|
||||||
|
update_audio();
|
||||||
|
audio_queue_.perform();
|
||||||
|
}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
Writes to a 6560 register.
|
Writes to a 6560 register.
|
||||||
@@ -345,7 +353,7 @@ template <class T> class MOS6560 {
|
|||||||
|
|
||||||
case 0x2:
|
case 0x2:
|
||||||
registers_.number_of_columns = value & 0x7f;
|
registers_.number_of_columns = value & 0x7f;
|
||||||
registers_.video_matrix_start_address = (uint16_t)((registers_.video_matrix_start_address & 0x3c00) | ((value & 0x80) << 2));
|
registers_.video_matrix_start_address = static_cast<uint16_t>((registers_.video_matrix_start_address & 0x3c00) | ((value & 0x80) << 2));
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 0x3:
|
case 0x3:
|
||||||
@@ -354,8 +362,8 @@ template <class T> class MOS6560 {
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case 0x5:
|
case 0x5:
|
||||||
registers_.character_cell_start_address = (uint16_t)((value & 0x0f) << 10);
|
registers_.character_cell_start_address = static_cast<uint16_t>((value & 0x0f) << 10);
|
||||||
registers_.video_matrix_start_address = (uint16_t)((registers_.video_matrix_start_address & 0x0200) | ((value & 0xf0) << 6));
|
registers_.video_matrix_start_address = static_cast<uint16_t>((registers_.video_matrix_start_address & 0x0200) | ((value & 0xf0) << 6));
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 0xa:
|
case 0xa:
|
||||||
@@ -363,13 +371,13 @@ template <class T> class MOS6560 {
|
|||||||
case 0xc:
|
case 0xc:
|
||||||
case 0xd:
|
case 0xd:
|
||||||
update_audio();
|
update_audio();
|
||||||
speaker_->set_control(address - 0xa, value);
|
audio_generator_.set_control(address - 0xa, value);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 0xe:
|
case 0xe:
|
||||||
update_audio();
|
update_audio();
|
||||||
registers_.auxiliary_colour = colours_[value >> 4];
|
registers_.auxiliary_colour = colours_[value >> 4];
|
||||||
speaker_->set_volume(value & 0xf);
|
audio_generator_.set_volume(value & 0xf);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 0xf: {
|
case 0xf: {
|
||||||
@@ -399,18 +407,21 @@ template <class T> class MOS6560 {
|
|||||||
int current_line = (full_frame_counter_ + timing_.line_counter_increment_offset) / timing_.cycles_per_line;
|
int current_line = (full_frame_counter_ + timing_.line_counter_increment_offset) / timing_.cycles_per_line;
|
||||||
switch(address) {
|
switch(address) {
|
||||||
default: return registers_.direct_values[address];
|
default: return registers_.direct_values[address];
|
||||||
case 0x03: return (uint8_t)(current_line << 7) | (registers_.direct_values[3] & 0x7f);
|
case 0x03: return static_cast<uint8_t>(current_line << 7) | (registers_.direct_values[3] & 0x7f);
|
||||||
case 0x04: return (current_line >> 1) & 0xff;
|
case 0x04: return (current_line >> 1) & 0xff;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::shared_ptr<Outputs::CRT::CRT> crt_;
|
std::unique_ptr<Outputs::CRT::CRT> crt_;
|
||||||
|
|
||||||
|
Concurrency::DeferringAsyncTaskQueue audio_queue_;
|
||||||
|
AudioGenerator audio_generator_;
|
||||||
|
Outputs::Speaker::LowpassSpeaker<AudioGenerator> speaker_;
|
||||||
|
|
||||||
std::shared_ptr<Speaker> speaker_;
|
|
||||||
Cycles cycles_since_speaker_update_;
|
Cycles cycles_since_speaker_update_;
|
||||||
void update_audio() {
|
void update_audio() {
|
||||||
speaker_->run_for(Cycles(cycles_since_speaker_update_.divide(Cycles(4))));
|
speaker_.run_for(audio_queue_, Cycles(cycles_since_speaker_update_.divide(Cycles(4))));
|
||||||
}
|
}
|
||||||
|
|
||||||
// register state
|
// register state
|
||||||
@@ -432,7 +443,7 @@ template <class T> class MOS6560 {
|
|||||||
unsigned int cycles_in_state_;
|
unsigned int cycles_in_state_;
|
||||||
|
|
||||||
// counters that cover an entire field
|
// counters that cover an entire field
|
||||||
int horizontal_counter_, vertical_counter_, full_frame_counter_;
|
int horizontal_counter_ = 0, vertical_counter_ = 0, full_frame_counter_;
|
||||||
|
|
||||||
// latches dictating start and length of drawing
|
// latches dictating start and length of drawing
|
||||||
bool vertical_drawing_latch_, horizontal_drawing_latch_;
|
bool vertical_drawing_latch_, horizontal_drawing_latch_;
|
||||||
@@ -447,14 +458,14 @@ template <class T> class MOS6560 {
|
|||||||
// data latched from the bus
|
// data latched from the bus
|
||||||
uint8_t character_code_, character_colour_, character_value_;
|
uint8_t character_code_, character_colour_, character_value_;
|
||||||
|
|
||||||
bool is_odd_frame_, is_odd_line_;
|
bool is_odd_frame_ = false, is_odd_line_ = false;
|
||||||
|
|
||||||
// lookup table from 6560 colour index to appropriate PAL/NTSC value
|
// lookup table from 6560 colour index to appropriate PAL/NTSC value
|
||||||
uint16_t colours_[16];
|
uint16_t colours_[16];
|
||||||
|
|
||||||
uint16_t *pixel_pointer;
|
uint16_t *pixel_pointer;
|
||||||
void output_border(unsigned int number_of_cycles) {
|
void output_border(unsigned int number_of_cycles) {
|
||||||
uint16_t *colour_pointer = (uint16_t *)crt_->allocate_write_area(1);
|
uint16_t *colour_pointer = reinterpret_cast<uint16_t *>(crt_->allocate_write_area(1));
|
||||||
if(colour_pointer) *colour_pointer = registers_.borderColour;
|
if(colour_pointer) *colour_pointer = registers_.borderColour;
|
||||||
crt_->output_level(number_of_cycles);
|
crt_->output_level(number_of_cycles);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,141 +18,65 @@ namespace Motorola {
|
|||||||
namespace CRTC {
|
namespace CRTC {
|
||||||
|
|
||||||
struct BusState {
|
struct BusState {
|
||||||
bool display_enable;
|
bool display_enable = false;
|
||||||
bool hsync;
|
bool hsync = false;
|
||||||
bool vsync;
|
bool vsync = false;
|
||||||
bool cursor;
|
bool cursor = false;
|
||||||
uint16_t refresh_address;
|
uint16_t refresh_address = 0;
|
||||||
uint16_t row_address;
|
uint16_t row_address = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
class BusHandler {
|
class BusHandler {
|
||||||
public:
|
public:
|
||||||
void perform_bus_cycle(const BusState &) {}
|
/*!
|
||||||
|
Performs the first phase of a 6845 bus cycle; this is the phase in which it is intended that
|
||||||
|
systems using the 6845 respect the bus state and produce pixels, sync or whatever they require.
|
||||||
|
*/
|
||||||
|
void perform_bus_cycle_phase1(const BusState &) {}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
Performs the second phase of a 6845 bus cycle. Some bus state — including sync — is updated
|
||||||
|
directly after phase 1 and hence is visible to an observer during phase 2. Handlers may therefore
|
||||||
|
implement @c perform_bus_cycle_phase2 to be notified of the availability of that state without
|
||||||
|
having to wait until the next cycle has begun.
|
||||||
|
*/
|
||||||
|
void perform_bus_cycle_phase2(const BusState &) {}
|
||||||
};
|
};
|
||||||
|
|
||||||
enum Personality {
|
enum Personality {
|
||||||
HD6845S, //
|
HD6845S, // Type 0 in CPC parlance. Zero-width HSYNC available, no status, programmable VSYNC length.
|
||||||
UM6845R, //
|
// Considered exactly identical to the UM6845, so this enum covers both.
|
||||||
MC6845, //
|
UM6845R, // Type 1 in CPC parlance. Status register, fixed-length VSYNC.
|
||||||
AMS40226 //
|
MC6845, // Type 2. No status register, fixed-length VSYNC, no zero-length HSYNC.
|
||||||
|
AMS40226 // Type 3. Status is get register, fixed-length VSYNC, no zero-length HSYNC.
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// TODO UM6845R and R12/R13; see http://www.cpcwiki.eu/index.php/CRTC#CRTC_Differences
|
||||||
|
|
||||||
template <class T> class CRTC6845 {
|
template <class T> class CRTC6845 {
|
||||||
public:
|
public:
|
||||||
|
|
||||||
CRTC6845(Personality p, T &bus_handler) :
|
CRTC6845(Personality p, T &bus_handler) noexcept :
|
||||||
personality_(p), bus_handler_(bus_handler) {}
|
personality_(p), bus_handler_(bus_handler), status_(0) {}
|
||||||
|
|
||||||
void run_for(Cycles cycles) {
|
|
||||||
int cyles_remaining = cycles.as_int();
|
|
||||||
while(cyles_remaining--) {
|
|
||||||
// check for end of horizontal sync
|
|
||||||
if(hsync_down_counter_) {
|
|
||||||
hsync_down_counter_--;
|
|
||||||
if(!hsync_down_counter_) {
|
|
||||||
bus_state_.hsync = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// check for end of line
|
|
||||||
bool is_end_of_line = character_counter_ == registers_[0];
|
|
||||||
|
|
||||||
// increment counter
|
|
||||||
character_counter_++;
|
|
||||||
|
|
||||||
// check for start of horizontal sync
|
|
||||||
if(character_counter_ == registers_[2]) {
|
|
||||||
hsync_down_counter_ = registers_[3] & 15;
|
|
||||||
if(hsync_down_counter_) bus_state_.hsync = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// update refresh address
|
|
||||||
if(character_is_visible_) {
|
|
||||||
bus_state_.refresh_address++;
|
|
||||||
}
|
|
||||||
|
|
||||||
// check for end of visible characters
|
|
||||||
if(character_counter_ == registers_[1]) {
|
|
||||||
character_is_visible_ = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// check for end-of-line
|
|
||||||
if(is_end_of_line) {
|
|
||||||
// check for end of vertical sync
|
|
||||||
if(vsync_down_counter_) {
|
|
||||||
vsync_down_counter_--;
|
|
||||||
if(!vsync_down_counter_) {
|
|
||||||
bus_state_.vsync = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if(is_in_adjustment_period_) {
|
|
||||||
line_counter_++;
|
|
||||||
if(line_counter_ == registers_[5]) {
|
|
||||||
line_counter_ = 0;
|
|
||||||
is_in_adjustment_period_ = false;
|
|
||||||
line_is_visible_ = true;
|
|
||||||
line_address_ = (uint16_t)((registers_[12] << 8) | registers_[13]);
|
|
||||||
bus_state_.refresh_address = line_address_;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// advance vertical counter
|
|
||||||
if(bus_state_.row_address == registers_[9]) {
|
|
||||||
if(!character_is_visible_)
|
|
||||||
line_address_ = bus_state_.refresh_address;
|
|
||||||
bus_state_.row_address = 0;
|
|
||||||
|
|
||||||
bool is_at_end_of_frame = line_counter_ == registers_[4];
|
|
||||||
line_counter_ = (line_counter_ + 1) & 0x7f;
|
|
||||||
|
|
||||||
// check for end of visible lines
|
|
||||||
if(line_counter_ == registers_[6]) {
|
|
||||||
line_is_visible_ = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// check for start of vertical sync
|
|
||||||
if(line_counter_ == registers_[7]) {
|
|
||||||
bus_state_.vsync = true;
|
|
||||||
vsync_down_counter_ = registers_[3] >> 4;
|
|
||||||
if(!vsync_down_counter_) vsync_down_counter_ = 16;
|
|
||||||
}
|
|
||||||
|
|
||||||
// check for entry into the overflow area
|
|
||||||
if(is_at_end_of_frame) {
|
|
||||||
if(registers_[5]) {
|
|
||||||
is_in_adjustment_period_ = true;
|
|
||||||
} else {
|
|
||||||
line_is_visible_ = true;
|
|
||||||
line_address_ = (uint16_t)((registers_[12] << 8) | registers_[13]);
|
|
||||||
bus_state_.refresh_address = line_address_;
|
|
||||||
}
|
|
||||||
bus_state_.row_address = 0;
|
|
||||||
line_counter_ = 0;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
bus_state_.row_address = (bus_state_.row_address + 1) & 0x1f;
|
|
||||||
}
|
|
||||||
bus_state_.refresh_address = line_address_;
|
|
||||||
}
|
|
||||||
|
|
||||||
character_counter_ = 0;
|
|
||||||
character_is_visible_ = (registers_[1] != 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
perform_bus_cycle();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void select_register(uint8_t r) {
|
void select_register(uint8_t r) {
|
||||||
selected_register_ = r;
|
selected_register_ = r;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint8_t get_status() {
|
uint8_t get_status() {
|
||||||
|
switch(personality_) {
|
||||||
|
case UM6845R: return status_ | (bus_state_.vsync ? 0x20 : 0x00);
|
||||||
|
case AMS40226: return get_register();
|
||||||
|
default: return 0xff;
|
||||||
|
}
|
||||||
return 0xff;
|
return 0xff;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint8_t get_register() {
|
uint8_t get_register() {
|
||||||
|
if(selected_register_ == 31) status_ &= ~0x80;
|
||||||
|
if(selected_register_ == 16 || selected_register_ == 17) status_ &= ~0x40;
|
||||||
|
|
||||||
|
if(personality_ == UM6845R && selected_register_ == 31) return dummy_register_;
|
||||||
if(selected_register_ < 12 || selected_register_ > 17) return 0xff;
|
if(selected_register_ < 12 || selected_register_ > 17) return 0xff;
|
||||||
return registers_[selected_register_];
|
return registers_[selected_register_];
|
||||||
}
|
}
|
||||||
@@ -163,38 +87,186 @@ template <class T> class CRTC6845 {
|
|||||||
0xff, 0x1f, 0x7f, 0x1f, 0x3f, 0xff, 0x3f, 0xff
|
0xff, 0x1f, 0x7f, 0x1f, 0x3f, 0xff, 0x3f, 0xff
|
||||||
};
|
};
|
||||||
|
|
||||||
if(selected_register_ < 16)
|
// Per CPC documentation, skew doesn't work on a "type 1 or 2", i.e. an MC6845 or a UM6845R.
|
||||||
|
if(selected_register_ == 8 && personality_ != UM6845R && personality_ != MC6845) {
|
||||||
|
switch((value >> 4)&3) {
|
||||||
|
default: display_skew_mask_ = 1; break;
|
||||||
|
case 1: display_skew_mask_ = 2; break;
|
||||||
|
case 2: display_skew_mask_ = 4; break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(selected_register_ < 16) {
|
||||||
registers_[selected_register_] = value & masks[selected_register_];
|
registers_[selected_register_] = value & masks[selected_register_];
|
||||||
}
|
}
|
||||||
|
if(selected_register_ == 31 && personality_ == UM6845R) {
|
||||||
|
dummy_register_ = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void trigger_light_pen() {
|
void trigger_light_pen() {
|
||||||
registers_[17] = bus_state_.refresh_address & 0xff;
|
registers_[17] = bus_state_.refresh_address & 0xff;
|
||||||
registers_[16] = bus_state_.refresh_address >> 8;
|
registers_[16] = bus_state_.refresh_address >> 8;
|
||||||
|
status_ |= 0x40;
|
||||||
|
}
|
||||||
|
|
||||||
|
void run_for(Cycles cycles) {
|
||||||
|
int cyles_remaining = cycles.as_int();
|
||||||
|
while(cyles_remaining--) {
|
||||||
|
// check for end of visible characters
|
||||||
|
if(character_counter_ == registers_[1]) {
|
||||||
|
// TODO: consider skew in character_is_visible_. Or maybe defer until perform_bus_cycle?
|
||||||
|
character_is_visible_ = false;
|
||||||
|
end_of_line_address_ = bus_state_.refresh_address;
|
||||||
|
}
|
||||||
|
|
||||||
|
perform_bus_cycle_phase1();
|
||||||
|
bus_state_.refresh_address = (bus_state_.refresh_address + 1) & 0x3fff;
|
||||||
|
|
||||||
|
// check for end-of-line
|
||||||
|
if(character_counter_ == registers_[0]) {
|
||||||
|
character_counter_ = 0;
|
||||||
|
do_end_of_line();
|
||||||
|
character_is_visible_ = true;
|
||||||
|
} else {
|
||||||
|
// increment counter
|
||||||
|
character_counter_++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// check for start of horizontal sync
|
||||||
|
if(character_counter_ == registers_[2]) {
|
||||||
|
hsync_counter_ = 0;
|
||||||
|
bus_state_.hsync = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// check for end of horizontal sync; note that a sync time of zero will result in an immediate
|
||||||
|
// cancellation of the plan to perform sync if this is an HD6845S or UM6845R; otherwise zero
|
||||||
|
// will end up counting as 16 as it won't be checked until after overflow.
|
||||||
|
if(bus_state_.hsync) {
|
||||||
|
switch(personality_) {
|
||||||
|
case HD6845S:
|
||||||
|
case UM6845R:
|
||||||
|
bus_state_.hsync = hsync_counter_ != (registers_[3] & 15);
|
||||||
|
hsync_counter_ = (hsync_counter_ + 1) & 15;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
hsync_counter_ = (hsync_counter_ + 1) & 15;
|
||||||
|
bus_state_.hsync = hsync_counter_ != (registers_[3] & 15);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
perform_bus_cycle_phase2();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const BusState &get_bus_state() const {
|
||||||
|
return bus_state_;
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
inline void perform_bus_cycle() {
|
inline void perform_bus_cycle_phase1() {
|
||||||
bus_state_.display_enable = character_is_visible_ && line_is_visible_;
|
// Skew theory of operation: keep a history of the last three states, and apply whichever is selected.
|
||||||
bus_state_.refresh_address &= 0x3fff;
|
character_is_visible_shifter_ = (character_is_visible_shifter_ << 1) | static_cast<unsigned int>(character_is_visible_);
|
||||||
bus_handler_.perform_bus_cycle(bus_state_);
|
bus_state_.display_enable = (static_cast<int>(character_is_visible_shifter_) & display_skew_mask_) && line_is_visible_;
|
||||||
|
bus_handler_.perform_bus_cycle_phase1(bus_state_);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void perform_bus_cycle_phase2() {
|
||||||
|
bus_handler_.perform_bus_cycle_phase2(bus_state_);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void do_end_of_line() {
|
||||||
|
// check for end of vertical sync
|
||||||
|
if(bus_state_.vsync) {
|
||||||
|
vsync_counter_ = (vsync_counter_ + 1) & 15;
|
||||||
|
// on the UM6845R and AMS40226, honour the programmed vertical sync time; on the other CRTCs
|
||||||
|
// always use a vertical sync count of 16.
|
||||||
|
switch(personality_) {
|
||||||
|
case HD6845S:
|
||||||
|
case AMS40226:
|
||||||
|
bus_state_.vsync = vsync_counter_ != (registers_[3] >> 4);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
bus_state_.vsync = vsync_counter_ != 0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(is_in_adjustment_period_) {
|
||||||
|
line_counter_++;
|
||||||
|
if(line_counter_ == registers_[5]) {
|
||||||
|
is_in_adjustment_period_ = false;
|
||||||
|
do_end_of_frame();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// advance vertical counter
|
||||||
|
if(bus_state_.row_address == registers_[9]) {
|
||||||
|
bus_state_.row_address = 0;
|
||||||
|
line_address_ = end_of_line_address_;
|
||||||
|
|
||||||
|
// check for entry into the overflow area
|
||||||
|
if(line_counter_ == registers_[4]) {
|
||||||
|
if(registers_[5]) {
|
||||||
|
line_counter_ = 0;
|
||||||
|
is_in_adjustment_period_ = true;
|
||||||
|
} else {
|
||||||
|
do_end_of_frame();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
line_counter_ = (line_counter_ + 1) & 0x7f;
|
||||||
|
|
||||||
|
// check for start of vertical sync
|
||||||
|
if(line_counter_ == registers_[7]) {
|
||||||
|
bus_state_.vsync = true;
|
||||||
|
vsync_counter_ = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// check for end of visible lines
|
||||||
|
if(line_counter_ == registers_[6]) {
|
||||||
|
line_is_visible_ = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
bus_state_.row_address = (bus_state_.row_address + 1) & 0x1f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bus_state_.refresh_address = line_address_;
|
||||||
|
character_counter_ = 0;
|
||||||
|
character_is_visible_ = (registers_[1] != 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void do_end_of_frame() {
|
||||||
|
line_counter_ = 0;
|
||||||
|
line_is_visible_ = true;
|
||||||
|
line_address_ = static_cast<uint16_t>((registers_[12] << 8) | registers_[13]);
|
||||||
|
bus_state_.refresh_address = line_address_;
|
||||||
}
|
}
|
||||||
|
|
||||||
Personality personality_;
|
Personality personality_;
|
||||||
T &bus_handler_;
|
T &bus_handler_;
|
||||||
BusState bus_state_;
|
BusState bus_state_;
|
||||||
|
|
||||||
uint8_t registers_[18];
|
uint8_t registers_[18] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
|
||||||
int selected_register_;
|
uint8_t dummy_register_ = 0;
|
||||||
|
int selected_register_ = 0;
|
||||||
|
|
||||||
uint8_t character_counter_;
|
uint8_t character_counter_ = 0;
|
||||||
uint8_t line_counter_;
|
uint8_t line_counter_ = 0;
|
||||||
|
|
||||||
bool character_is_visible_, line_is_visible_;
|
bool character_is_visible_ = false, line_is_visible_ = false;
|
||||||
|
|
||||||
int hsync_down_counter_;
|
int hsync_counter_ = 0;
|
||||||
int vsync_down_counter_;
|
int vsync_counter_ = 0;
|
||||||
bool is_in_adjustment_period_;
|
bool is_in_adjustment_period_ = false;
|
||||||
uint16_t line_address_;
|
|
||||||
|
uint16_t line_address_ = 0;
|
||||||
|
uint16_t end_of_line_address_ = 0;
|
||||||
|
uint8_t status_ = 0;
|
||||||
|
|
||||||
|
int display_skew_mask_ = 1;
|
||||||
|
unsigned int character_is_visible_shifter_ = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -76,8 +76,8 @@ template <class T> class i8255 {
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
void update_outputs() {
|
void update_outputs() {
|
||||||
port_handler_.set_value(0, outputs_[0]);
|
if(!(control_ & 0x10)) port_handler_.set_value(0, outputs_[0]);
|
||||||
port_handler_.set_value(1, outputs_[1]);
|
if(!(control_ & 0x02)) port_handler_.set_value(1, outputs_[1]);
|
||||||
port_handler_.set_value(2, outputs_[2]);
|
port_handler_.set_value(2, outputs_[2]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
#include "i8272.hpp"
|
#include "i8272.hpp"
|
||||||
#include "../../Storage/Disk/Encodings/MFM.hpp"
|
//#include "../../Storage/Disk/Encodings/MFM/Encoder.hpp"
|
||||||
|
|
||||||
#include <cstdio>
|
#include <cstdio>
|
||||||
|
|
||||||
@@ -34,6 +34,7 @@ using namespace Intel::i8272;
|
|||||||
#define SetSeekEnd() (status_[0] |= 0x20)
|
#define SetSeekEnd() (status_[0] |= 0x20)
|
||||||
#define SetEquipmentCheck() (status_[0] |= 0x10)
|
#define SetEquipmentCheck() (status_[0] |= 0x10)
|
||||||
#define SetNotReady() (status_[0] |= 0x08)
|
#define SetNotReady() (status_[0] |= 0x08)
|
||||||
|
#define SetSide2() (status_[0] |= 0x04)
|
||||||
|
|
||||||
#define SetEndOfCylinder() (status_[1] |= 0x80)
|
#define SetEndOfCylinder() (status_[1] |= 0x80)
|
||||||
#define SetDataError() (status_[1] |= 0x20)
|
#define SetDataError() (status_[1] |= 0x20)
|
||||||
@@ -43,6 +44,7 @@ using namespace Intel::i8272;
|
|||||||
#define SetMissingAddressMark() (status_[1] |= 0x01)
|
#define SetMissingAddressMark() (status_[1] |= 0x01)
|
||||||
|
|
||||||
#define SetControlMark() (status_[2] |= 0x40)
|
#define SetControlMark() (status_[2] |= 0x40)
|
||||||
|
#define ClearControlMark() (status_[2] &= ~0x40)
|
||||||
#define ControlMark() (status_[2] & 0x40)
|
#define ControlMark() (status_[2] & 0x40)
|
||||||
|
|
||||||
#define SetDataFieldDataError() (status_[2] |= 0x20)
|
#define SetDataFieldDataError() (status_[2] |= 0x20)
|
||||||
@@ -75,27 +77,26 @@ namespace {
|
|||||||
const uint8_t CommandSenseDriveStatus = 0x04;
|
const uint8_t CommandSenseDriveStatus = 0x04;
|
||||||
}
|
}
|
||||||
|
|
||||||
i8272::i8272(BusHandler &bus_handler, Cycles clock_rate, int clock_rate_multiplier, int revolutions_per_minute) :
|
i8272::i8272(BusHandler &bus_handler, Cycles clock_rate) :
|
||||||
Storage::Disk::MFMController(clock_rate, clock_rate_multiplier, revolutions_per_minute),
|
Storage::Disk::MFMController(clock_rate),
|
||||||
bus_handler_(bus_handler),
|
bus_handler_(bus_handler) {
|
||||||
main_status_(0),
|
posit_event(static_cast<int>(Event8272::CommandByte));
|
||||||
interesting_event_mask_((int)Event8272::CommandByte),
|
}
|
||||||
resume_point_(0),
|
|
||||||
delay_time_(0),
|
bool i8272::is_sleeping() {
|
||||||
head_timers_running_(0),
|
return is_sleeping_ && Storage::Disk::MFMController::is_sleeping();
|
||||||
expects_input_(false),
|
|
||||||
drives_seeking_(0) {
|
|
||||||
posit_event((int)Event8272::CommandByte);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void i8272::run_for(Cycles cycles) {
|
void i8272::run_for(Cycles cycles) {
|
||||||
Storage::Disk::MFMController::run_for(cycles);
|
Storage::Disk::MFMController::run_for(cycles);
|
||||||
|
|
||||||
|
if(is_sleeping_) return;
|
||||||
|
|
||||||
// check for an expired timer
|
// check for an expired timer
|
||||||
if(delay_time_ > 0) {
|
if(delay_time_ > 0) {
|
||||||
if(cycles.as_int() >= delay_time_) {
|
if(cycles.as_int() >= delay_time_) {
|
||||||
delay_time_ = 0;
|
delay_time_ = 0;
|
||||||
posit_event((int)Event8272::Timer);
|
posit_event(static_cast<int>(Event8272::Timer));
|
||||||
} else {
|
} else {
|
||||||
delay_time_ -= cycles.as_int();
|
delay_time_ -= cycles.as_int();
|
||||||
}
|
}
|
||||||
@@ -103,6 +104,7 @@ void i8272::run_for(Cycles cycles) {
|
|||||||
|
|
||||||
// update seek status of any drives presently seeking
|
// update seek status of any drives presently seeking
|
||||||
if(drives_seeking_) {
|
if(drives_seeking_) {
|
||||||
|
int drives_left = drives_seeking_;
|
||||||
for(int c = 0; c < 4; c++) {
|
for(int c = 0; c < 4; c++) {
|
||||||
if(drives_[c].phase == Drive::Seeking) {
|
if(drives_[c].phase == Drive::Seeking) {
|
||||||
drives_[c].step_rate_counter += cycles.as_int();
|
drives_[c].step_rate_counter += cycles.as_int();
|
||||||
@@ -112,37 +114,52 @@ void i8272::run_for(Cycles cycles) {
|
|||||||
// Perform a step.
|
// Perform a step.
|
||||||
int direction = (drives_[c].target_head_position < drives_[c].head_position) ? -1 : 1;
|
int direction = (drives_[c].target_head_position < drives_[c].head_position) ? -1 : 1;
|
||||||
printf("Target %d versus believed %d\n", drives_[c].target_head_position, drives_[c].head_position);
|
printf("Target %d versus believed %d\n", drives_[c].target_head_position, drives_[c].head_position);
|
||||||
drives_[c].drive->step(direction);
|
select_drive(c);
|
||||||
|
get_drive().step(direction);
|
||||||
if(drives_[c].target_head_position >= 0) drives_[c].head_position += direction;
|
if(drives_[c].target_head_position >= 0) drives_[c].head_position += direction;
|
||||||
|
|
||||||
// Check for completion.
|
// Check for completion.
|
||||||
if(drives_[c].seek_is_satisfied()) {
|
if(seek_is_satisfied(c)) {
|
||||||
drives_[c].phase = Drive::CompletedSeeking;
|
drives_[c].phase = Drive::CompletedSeeking;
|
||||||
drives_seeking_--;
|
drives_seeking_--;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
drives_left--;
|
||||||
|
if(!drives_left) break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// check for any head unloads
|
// check for any head unloads
|
||||||
if(head_timers_running_) {
|
if(head_timers_running_) {
|
||||||
for(int c = 0; c < 4; c++) {
|
int timers_left = head_timers_running_;
|
||||||
for(int h = 0; h < 2; h++) {
|
for(int c = 0; c < 8; c++) {
|
||||||
if(drives_[c].head_unload_delay[c] > 0) {
|
int drive = (c >> 1);
|
||||||
if(cycles.as_int() >= drives_[c].head_unload_delay[c]) {
|
int head = c&1;
|
||||||
drives_[c].head_unload_delay[c] = 0;
|
|
||||||
drives_[c].head_is_loaded[c] = false;
|
if(drives_[drive].head_unload_delay[head] > 0) {
|
||||||
|
if(cycles.as_int() >= drives_[drive].head_unload_delay[head]) {
|
||||||
|
drives_[drive].head_unload_delay[head] = 0;
|
||||||
|
drives_[drive].head_is_loaded[head] = false;
|
||||||
head_timers_running_--;
|
head_timers_running_--;
|
||||||
if(!head_timers_running_) return;
|
|
||||||
} else {
|
} else {
|
||||||
drives_[c].head_unload_delay[c] -= cycles.as_int();
|
drives_[drive].head_unload_delay[head] -= cycles.as_int();
|
||||||
}
|
}
|
||||||
|
timers_left--;
|
||||||
|
if(!timers_left) break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// check for busy plus ready disabled
|
||||||
|
if(is_executing_ && !get_drive().get_is_ready()) {
|
||||||
|
posit_event(static_cast<int>(Event8272::NoLongerReady));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
is_sleeping_ = !delay_time_ && !drives_seeking_ && !head_timers_running_;
|
||||||
|
if(is_sleeping_) update_sleep_observer();
|
||||||
}
|
}
|
||||||
|
|
||||||
void i8272::set_register(int address, uint8_t value) {
|
void i8272::set_register(int address, uint8_t value) {
|
||||||
@@ -159,7 +176,7 @@ void i8272::set_register(int address, uint8_t value) {
|
|||||||
} else {
|
} else {
|
||||||
// accumulate latest byte in the command byte sequence
|
// accumulate latest byte in the command byte sequence
|
||||||
command_.push_back(value);
|
command_.push_back(value);
|
||||||
posit_event((int)Event8272::CommandByte);
|
posit_event(static_cast<int>(Event8272::CommandByte));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -168,7 +185,7 @@ uint8_t i8272::get_register(int address) {
|
|||||||
if(result_stack_.empty()) return 0xff;
|
if(result_stack_.empty()) return 0xff;
|
||||||
uint8_t result = result_stack_.back();
|
uint8_t result = result_stack_.back();
|
||||||
result_stack_.pop_back();
|
result_stack_.pop_back();
|
||||||
if(result_stack_.empty()) posit_event((int)Event8272::ResultEmpty);
|
if(result_stack_.empty()) posit_event(static_cast<int>(Event8272::ResultEmpty));
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
} else {
|
} else {
|
||||||
@@ -176,35 +193,29 @@ uint8_t i8272::get_register(int address) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void i8272::set_disk(std::shared_ptr<Storage::Disk::Disk> disk, int drive) {
|
|
||||||
if(drive < 4 && drive >= 0) {
|
|
||||||
drives_[drive].drive->set_disk(disk);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#define BEGIN_SECTION() switch(resume_point_) { default:
|
#define BEGIN_SECTION() switch(resume_point_) { default:
|
||||||
#define END_SECTION() }
|
#define END_SECTION() }
|
||||||
|
|
||||||
#define MS_TO_CYCLES(x) x * 8000
|
#define MS_TO_CYCLES(x) x * 8000
|
||||||
#define WAIT_FOR_EVENT(mask) resume_point_ = __LINE__; interesting_event_mask_ = (int)mask; return; case __LINE__:
|
#define WAIT_FOR_EVENT(mask) resume_point_ = __LINE__; interesting_event_mask_ = static_cast<int>(mask); return; case __LINE__:
|
||||||
#define WAIT_FOR_TIME(ms) resume_point_ = __LINE__; interesting_event_mask_ = (int)Event8272::Timer; delay_time_ = MS_TO_CYCLES(ms); case __LINE__: if(delay_time_) return;
|
#define WAIT_FOR_TIME(ms) resume_point_ = __LINE__; interesting_event_mask_ = static_cast<int>(Event8272::Timer); delay_time_ = MS_TO_CYCLES(ms); is_sleeping_ = false; update_sleep_observer(); case __LINE__: if(delay_time_) return;
|
||||||
|
|
||||||
#define PASTE(x, y) x##y
|
#define PASTE(x, y) x##y
|
||||||
#define CONCAT(x, y) PASTE(x, y)
|
#define CONCAT(x, y) PASTE(x, y)
|
||||||
|
|
||||||
#define FIND_HEADER() \
|
#define FIND_HEADER() \
|
||||||
set_data_mode(DataMode::Scanning); \
|
set_data_mode(DataMode::Scanning); \
|
||||||
CONCAT(find_header, __LINE__): WAIT_FOR_EVENT((int)Event::Token | (int)Event::IndexHole); \
|
CONCAT(find_header, __LINE__): WAIT_FOR_EVENT(static_cast<int>(Event::Token) | static_cast<int>(Event::IndexHole)); \
|
||||||
if(event_type == (int)Event::IndexHole) { index_hole_limit_--; } \
|
if(event_type == static_cast<int>(Event::IndexHole)) { index_hole_limit_--; } \
|
||||||
else if(get_latest_token().type == Token::ID) goto CONCAT(header_found, __LINE__); \
|
else if(get_latest_token().type == Token::ID) goto CONCAT(header_found, __LINE__); \
|
||||||
\
|
\
|
||||||
if(index_hole_limit_) goto CONCAT(find_header, __LINE__); \
|
if(index_hole_limit_) goto CONCAT(find_header, __LINE__); \
|
||||||
CONCAT(header_found, __LINE__): 0;\
|
CONCAT(header_found, __LINE__): (void)0;\
|
||||||
|
|
||||||
#define FIND_DATA() \
|
#define FIND_DATA() \
|
||||||
set_data_mode(DataMode::Scanning); \
|
set_data_mode(DataMode::Scanning); \
|
||||||
CONCAT(find_data, __LINE__): WAIT_FOR_EVENT((int)Event::Token | (int)Event::IndexHole); \
|
CONCAT(find_data, __LINE__): WAIT_FOR_EVENT(static_cast<int>(Event::Token) | static_cast<int>(Event::IndexHole)); \
|
||||||
if(event_type == (int)Event::Token) { \
|
if(event_type == static_cast<int>(Event::Token)) { \
|
||||||
if(get_latest_token().type == Token::Byte || get_latest_token().type == Token::Sync) goto CONCAT(find_data, __LINE__); \
|
if(get_latest_token().type == Token::Byte || get_latest_token().type == Token::Sync) goto CONCAT(find_data, __LINE__); \
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -219,10 +230,10 @@ void i8272::set_disk(std::shared_ptr<Storage::Disk::Disk> disk, int drive) {
|
|||||||
#define SET_DRIVE_HEAD_MFM() \
|
#define SET_DRIVE_HEAD_MFM() \
|
||||||
active_drive_ = command_[1]&3; \
|
active_drive_ = command_[1]&3; \
|
||||||
active_head_ = (command_[1] >> 2)&1; \
|
active_head_ = (command_[1] >> 2)&1; \
|
||||||
set_drive(drives_[active_drive_].drive); \
|
status_[0] = (command_[1]&7); \
|
||||||
drives_[active_drive_].drive->set_head((unsigned int)active_head_); \
|
select_drive(active_drive_); \
|
||||||
set_is_double_density(command_[0] & 0x40); \
|
get_drive().set_head(active_head_); \
|
||||||
invalidate_track();
|
set_is_double_density(command_[0] & 0x40);
|
||||||
|
|
||||||
#define WAIT_FOR_BYTES(n) \
|
#define WAIT_FOR_BYTES(n) \
|
||||||
distance_into_section_ = 0; \
|
distance_into_section_ = 0; \
|
||||||
@@ -245,12 +256,18 @@ void i8272::set_disk(std::shared_ptr<Storage::Disk::Disk> disk, int drive) {
|
|||||||
if(drives_[active_drive_].head_is_loaded[active_head_]) {\
|
if(drives_[active_drive_].head_is_loaded[active_head_]) {\
|
||||||
if(drives_[active_drive_].head_unload_delay[active_head_] == 0) { \
|
if(drives_[active_drive_].head_unload_delay[active_head_] == 0) { \
|
||||||
head_timers_running_++; \
|
head_timers_running_++; \
|
||||||
|
is_sleeping_ = false; \
|
||||||
|
update_sleep_observer(); \
|
||||||
} \
|
} \
|
||||||
drives_[active_drive_].head_unload_delay[active_head_] = MS_TO_CYCLES(head_unload_time_);\
|
drives_[active_drive_].head_unload_delay[active_head_] = MS_TO_CYCLES(head_unload_time_);\
|
||||||
}
|
}
|
||||||
|
|
||||||
void i8272::posit_event(int event_type) {
|
void i8272::posit_event(int event_type) {
|
||||||
if(event_type == (int)Event::IndexHole) index_hole_count_++;
|
if(event_type == static_cast<int>(Event::IndexHole)) index_hole_count_++;
|
||||||
|
if(event_type == static_cast<int>(Event8272::NoLongerReady)) {
|
||||||
|
SetNotReady();
|
||||||
|
goto abort;
|
||||||
|
}
|
||||||
if(!(interesting_event_mask_ & event_type)) return;
|
if(!(interesting_event_mask_ & event_type)) return;
|
||||||
interesting_event_mask_ &= ~event_type;
|
interesting_event_mask_ &= ~event_type;
|
||||||
|
|
||||||
@@ -274,7 +291,7 @@ void i8272::posit_event(int event_type) {
|
|||||||
WAIT_FOR_EVENT(Event8272::CommandByte)
|
WAIT_FOR_EVENT(Event8272::CommandByte)
|
||||||
SetBusy();
|
SetBusy();
|
||||||
|
|
||||||
static const size_t required_lengths[32] = {
|
static const std::size_t required_lengths[32] = {
|
||||||
0, 0, 9, 3, 2, 9, 9, 2,
|
0, 0, 9, 3, 2, 9, 9, 2,
|
||||||
1, 9, 2, 0, 9, 6, 0, 3,
|
1, 9, 2, 0, 9, 6, 0, 3,
|
||||||
0, 9, 0, 0, 0, 0, 0, 0,
|
0, 9, 0, 0, 0, 0, 0, 0,
|
||||||
@@ -320,9 +337,14 @@ void i8272::posit_event(int event_type) {
|
|||||||
}
|
}
|
||||||
// Establishes the drive and head being addressed, and whether in double density mode; populates the internal
|
// Establishes the drive and head being addressed, and whether in double density mode; populates the internal
|
||||||
// cylinder, head, sector and size registers from the command stream.
|
// cylinder, head, sector and size registers from the command stream.
|
||||||
|
is_executing_ = true;
|
||||||
if(!dma_mode_) SetNonDMAExecution();
|
if(!dma_mode_) SetNonDMAExecution();
|
||||||
SET_DRIVE_HEAD_MFM();
|
SET_DRIVE_HEAD_MFM();
|
||||||
LOAD_HEAD();
|
LOAD_HEAD();
|
||||||
|
if(!get_drive().get_is_ready()) {
|
||||||
|
SetNotReady();
|
||||||
|
goto abort;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Jump to the proper place.
|
// Jump to the proper place.
|
||||||
@@ -409,7 +431,8 @@ void i8272::posit_event(int event_type) {
|
|||||||
// flag doesn't match the sort the command was looking for.
|
// flag doesn't match the sort the command was looking for.
|
||||||
read_data_found_header:
|
read_data_found_header:
|
||||||
FIND_DATA();
|
FIND_DATA();
|
||||||
if(event_type == (int)Event::Token) {
|
ClearControlMark();
|
||||||
|
if(event_type == static_cast<int>(Event::Token)) {
|
||||||
if(get_latest_token().type != Token::Data && get_latest_token().type != Token::DeletedData) {
|
if(get_latest_token().type != Token::Data && get_latest_token().type != Token::DeletedData) {
|
||||||
// Something other than a data mark came next — impliedly an ID or index mark.
|
// Something other than a data mark came next — impliedly an ID or index mark.
|
||||||
SetMissingAddressMark();
|
SetMissingAddressMark();
|
||||||
@@ -440,24 +463,24 @@ void i8272::posit_event(int event_type) {
|
|||||||
//
|
//
|
||||||
// TODO: consider DTL.
|
// TODO: consider DTL.
|
||||||
read_data_get_byte:
|
read_data_get_byte:
|
||||||
WAIT_FOR_EVENT((int)Event::Token | (int)Event::IndexHole);
|
WAIT_FOR_EVENT(static_cast<int>(Event::Token) | static_cast<int>(Event::IndexHole));
|
||||||
if(event_type == (int)Event::Token) {
|
if(event_type == static_cast<int>(Event::Token)) {
|
||||||
result_stack_.push_back(get_latest_token().byte_value);
|
result_stack_.push_back(get_latest_token().byte_value);
|
||||||
distance_into_section_++;
|
distance_into_section_++;
|
||||||
SetDataRequest();
|
SetDataRequest();
|
||||||
SetDataDirectionToProcessor();
|
SetDataDirectionToProcessor();
|
||||||
WAIT_FOR_EVENT((int)Event8272::ResultEmpty | (int)Event::Token | (int)Event::IndexHole);
|
WAIT_FOR_EVENT(static_cast<int>(Event8272::ResultEmpty) | static_cast<int>(Event::Token) | static_cast<int>(Event::IndexHole));
|
||||||
}
|
}
|
||||||
switch(event_type) {
|
switch(event_type) {
|
||||||
case (int)Event8272::ResultEmpty: // The caller read the byte in time; proceed as normal.
|
case static_cast<int>(Event8272::ResultEmpty): // The caller read the byte in time; proceed as normal.
|
||||||
ResetDataRequest();
|
ResetDataRequest();
|
||||||
if(distance_into_section_ < (128 << size_)) goto read_data_get_byte;
|
if(distance_into_section_ < (128 << size_)) goto read_data_get_byte;
|
||||||
break;
|
break;
|
||||||
case (int)Event::Token: // The caller hasn't read the old byte yet and a new one has arrived
|
case static_cast<int>(Event::Token): // The caller hasn't read the old byte yet and a new one has arrived
|
||||||
SetOverrun();
|
SetOverrun();
|
||||||
goto abort;
|
goto abort;
|
||||||
break;
|
break;
|
||||||
case (int)Event::IndexHole:
|
case static_cast<int>(Event::IndexHole):
|
||||||
SetEndOfCylinder();
|
SetEndOfCylinder();
|
||||||
goto abort;
|
goto abort;
|
||||||
break;
|
break;
|
||||||
@@ -486,7 +509,7 @@ void i8272::posit_event(int event_type) {
|
|||||||
write_data:
|
write_data:
|
||||||
printf("Write [deleted] data [%02x %02x %02x %02x ... %02x %02x]\n", command_[2], command_[3], command_[4], command_[5], command_[6], command_[8]);
|
printf("Write [deleted] data [%02x %02x %02x %02x ... %02x %02x]\n", command_[2], command_[3], command_[4], command_[5], command_[6], command_[8]);
|
||||||
|
|
||||||
if(drives_[active_drive_].drive->get_is_read_only()) {
|
if(get_drive().get_is_read_only()) {
|
||||||
SetNotWriteable();
|
SetNotWriteable();
|
||||||
goto abort;
|
goto abort;
|
||||||
}
|
}
|
||||||
@@ -509,7 +532,6 @@ void i8272::posit_event(int event_type) {
|
|||||||
WAIT_FOR_EVENT(Event::DataWritten);
|
WAIT_FOR_EVENT(Event::DataWritten);
|
||||||
if(!has_input_) {
|
if(!has_input_) {
|
||||||
SetOverrun();
|
SetOverrun();
|
||||||
end_writing();
|
|
||||||
goto abort;
|
goto abort;
|
||||||
}
|
}
|
||||||
write_byte(input_);
|
write_byte(input_);
|
||||||
@@ -541,7 +563,6 @@ void i8272::posit_event(int event_type) {
|
|||||||
// Sets a maximum index hole limit of 2 then waits either until it finds a header mark or sees too many index holes.
|
// Sets a maximum index hole limit of 2 then waits either until it finds a header mark or sees too many index holes.
|
||||||
// If a header mark is found, reads in the following bytes that produce a header. Otherwise branches to data not found.
|
// If a header mark is found, reads in the following bytes that produce a header. Otherwise branches to data not found.
|
||||||
index_hole_limit_ = 2;
|
index_hole_limit_ = 2;
|
||||||
read_id_find_next_sector:
|
|
||||||
FIND_HEADER();
|
FIND_HEADER();
|
||||||
if(!index_hole_limit_) {
|
if(!index_hole_limit_) {
|
||||||
SetMissingAddressMark();
|
SetMissingAddressMark();
|
||||||
@@ -589,7 +610,7 @@ void i8272::posit_event(int event_type) {
|
|||||||
distance_into_section_++;
|
distance_into_section_++;
|
||||||
SetDataRequest();
|
SetDataRequest();
|
||||||
// TODO: other possible exit conditions; find a way to merge with the read_data version of this.
|
// TODO: other possible exit conditions; find a way to merge with the read_data version of this.
|
||||||
WAIT_FOR_EVENT((int)Event8272::ResultEmpty);
|
WAIT_FOR_EVENT(static_cast<int>(Event8272::ResultEmpty));
|
||||||
ResetDataRequest();
|
ResetDataRequest();
|
||||||
if(distance_into_section_ < (128 << header_[2])) goto read_track_get_byte;
|
if(distance_into_section_ < (128 << header_[2])) goto read_track_get_byte;
|
||||||
|
|
||||||
@@ -601,7 +622,7 @@ void i8272::posit_event(int event_type) {
|
|||||||
// Performs format [/write] track.
|
// Performs format [/write] track.
|
||||||
format_track:
|
format_track:
|
||||||
printf("Format track\n");
|
printf("Format track\n");
|
||||||
if(drives_[active_drive_].drive->get_is_read_only()) {
|
if(get_drive().get_is_read_only()) {
|
||||||
SetNotWriteable();
|
SetNotWriteable();
|
||||||
goto abort;
|
goto abort;
|
||||||
}
|
}
|
||||||
@@ -626,14 +647,13 @@ void i8272::posit_event(int event_type) {
|
|||||||
expects_input_ = true;
|
expects_input_ = true;
|
||||||
distance_into_section_ = 0;
|
distance_into_section_ = 0;
|
||||||
format_track_write_header:
|
format_track_write_header:
|
||||||
WAIT_FOR_EVENT((int)Event::DataWritten | (int)Event::IndexHole);
|
WAIT_FOR_EVENT(static_cast<int>(Event::DataWritten) | static_cast<int>(Event::IndexHole));
|
||||||
switch(event_type) {
|
switch(event_type) {
|
||||||
case (int)Event::IndexHole:
|
case static_cast<int>(Event::IndexHole):
|
||||||
SetOverrun();
|
SetOverrun();
|
||||||
end_writing();
|
|
||||||
goto abort;
|
goto abort;
|
||||||
break;
|
break;
|
||||||
case (int)Event::DataWritten:
|
case static_cast<int>(Event::DataWritten):
|
||||||
header_[distance_into_section_] = input_;
|
header_[distance_into_section_] = input_;
|
||||||
write_byte(input_);
|
write_byte(input_);
|
||||||
has_input_ = false;
|
has_input_ = false;
|
||||||
@@ -664,8 +684,8 @@ void i8272::posit_event(int event_type) {
|
|||||||
// Otherwise, pad out to the index hole.
|
// Otherwise, pad out to the index hole.
|
||||||
format_track_pad:
|
format_track_pad:
|
||||||
write_byte(get_is_double_density() ? 0x4e : 0xff);
|
write_byte(get_is_double_density() ? 0x4e : 0xff);
|
||||||
WAIT_FOR_EVENT((int)Event::DataWritten | (int)Event::IndexHole);
|
WAIT_FOR_EVENT(static_cast<int>(Event::DataWritten) | static_cast<int>(Event::IndexHole));
|
||||||
if(event_type != (int)Event::IndexHole) goto format_track_pad;
|
if(event_type != static_cast<int>(Event::IndexHole)) goto format_track_pad;
|
||||||
|
|
||||||
end_writing();
|
end_writing();
|
||||||
|
|
||||||
@@ -694,10 +714,13 @@ void i8272::posit_event(int event_type) {
|
|||||||
seek:
|
seek:
|
||||||
{
|
{
|
||||||
int drive = command_[1]&3;
|
int drive = command_[1]&3;
|
||||||
|
select_drive(drive);
|
||||||
|
|
||||||
// Increment the seeking count if this drive wasn't already seeking.
|
// Increment the seeking count if this drive wasn't already seeking.
|
||||||
if(drives_[drive].phase != Drive::Seeking) {
|
if(drives_[drive].phase != Drive::Seeking) {
|
||||||
drives_seeking_++;
|
drives_seeking_++;
|
||||||
|
is_sleeping_ = false;
|
||||||
|
update_sleep_observer();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set currently seeking, with a step to occur right now (yes, it sounds like jamming these
|
// Set currently seeking, with a step to occur right now (yes, it sounds like jamming these
|
||||||
@@ -721,7 +744,7 @@ void i8272::posit_event(int event_type) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Check whether any steps are even needed; if not then mark as completed already.
|
// Check whether any steps are even needed; if not then mark as completed already.
|
||||||
if(drives_[drive].seek_is_satisfied()) {
|
if(seek_is_satisfied(drive)) {
|
||||||
drives_[drive].phase = Drive::CompletedSeeking;
|
drives_[drive].phase = Drive::CompletedSeeking;
|
||||||
drives_seeking_--;
|
drives_seeking_--;
|
||||||
}
|
}
|
||||||
@@ -744,14 +767,13 @@ void i8272::posit_event(int event_type) {
|
|||||||
// If a drive was found, return its results. Otherwise return a single 0x80.
|
// If a drive was found, return its results. Otherwise return a single 0x80.
|
||||||
if(found_drive != -1) {
|
if(found_drive != -1) {
|
||||||
drives_[found_drive].phase = Drive::NotSeeking;
|
drives_[found_drive].phase = Drive::NotSeeking;
|
||||||
status_[0] = (uint8_t)found_drive;
|
status_[0] = static_cast<uint8_t>(found_drive);
|
||||||
main_status_ &= ~(1 << found_drive);
|
main_status_ &= ~(1 << found_drive);
|
||||||
SetSeekEnd();
|
SetSeekEnd();
|
||||||
|
|
||||||
result_stack_.push_back(drives_[found_drive].head_position);
|
result_stack_ = { drives_[found_drive].head_position, status_[0]};
|
||||||
result_stack_.push_back(status_[0]);
|
|
||||||
} else {
|
} else {
|
||||||
result_stack_.push_back(0x80);
|
result_stack_ = { 0x80 };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
goto post_result;
|
goto post_result;
|
||||||
@@ -773,24 +795,28 @@ void i8272::posit_event(int event_type) {
|
|||||||
printf("Sense drive status\n");
|
printf("Sense drive status\n");
|
||||||
{
|
{
|
||||||
int drive = command_[1] & 3;
|
int drive = command_[1] & 3;
|
||||||
result_stack_.push_back(
|
select_drive(drive);
|
||||||
|
result_stack_= {
|
||||||
|
static_cast<uint8_t>(
|
||||||
(command_[1] & 7) | // drive and head number
|
(command_[1] & 7) | // drive and head number
|
||||||
0x08 | // single sided
|
0x08 | // single sided
|
||||||
(drives_[drive].drive->get_is_track_zero() ? 0x10 : 0x00) |
|
(get_drive().get_is_track_zero() ? 0x10 : 0x00) |
|
||||||
(drives_[drive].drive->get_is_ready() ? 0x20 : 0x00) |
|
(get_drive().get_is_ready() ? 0x20 : 0x00) |
|
||||||
(drives_[drive].drive->get_is_read_only() ? 0x40 : 0x00)
|
(get_drive().get_is_read_only() ? 0x40 : 0x00)
|
||||||
);
|
)
|
||||||
|
};
|
||||||
}
|
}
|
||||||
goto post_result;
|
goto post_result;
|
||||||
|
|
||||||
// Performs any invalid command.
|
// Performs any invalid command.
|
||||||
invalid:
|
invalid:
|
||||||
// A no-op, but posts ST0 (but which ST0?)
|
// A no-op, but posts ST0 (but which ST0?)
|
||||||
result_stack_.push_back(0x80);
|
result_stack_ = {0x80};
|
||||||
goto post_result;
|
goto post_result;
|
||||||
|
|
||||||
// Sets abnormal termination of the current command and proceeds to an ST0, ST1, ST2, C, H, R, N result phase.
|
// Sets abnormal termination of the current command and proceeds to an ST0, ST1, ST2, C, H, R, N result phase.
|
||||||
abort:
|
abort:
|
||||||
|
end_writing();
|
||||||
SetAbnormalTermination();
|
SetAbnormalTermination();
|
||||||
goto post_st012chrn;
|
goto post_st012chrn;
|
||||||
|
|
||||||
@@ -798,14 +824,7 @@ void i8272::posit_event(int event_type) {
|
|||||||
post_st012chrn:
|
post_st012chrn:
|
||||||
SCHEDULE_HEAD_UNLOAD();
|
SCHEDULE_HEAD_UNLOAD();
|
||||||
|
|
||||||
result_stack_.push_back(size_);
|
result_stack_ = {size_, sector_, head_, cylinder_, status_[2], status_[1], status_[0]};
|
||||||
result_stack_.push_back(sector_);
|
|
||||||
result_stack_.push_back(head_);
|
|
||||||
result_stack_.push_back(cylinder_);
|
|
||||||
|
|
||||||
result_stack_.push_back(status_[2]);
|
|
||||||
result_stack_.push_back(status_[1]);
|
|
||||||
result_stack_.push_back(status_[0]);
|
|
||||||
|
|
||||||
goto post_result;
|
goto post_result;
|
||||||
|
|
||||||
@@ -813,12 +832,13 @@ void i8272::posit_event(int event_type) {
|
|||||||
// last thing in it will be returned first.
|
// last thing in it will be returned first.
|
||||||
post_result:
|
post_result:
|
||||||
printf("Result to %02x, main %02x: ", command_[0] & 0x1f, main_status_);
|
printf("Result to %02x, main %02x: ", command_[0] & 0x1f, main_status_);
|
||||||
for(size_t c = 0; c < result_stack_.size(); c++) {
|
for(std::size_t c = 0; c < result_stack_.size(); c++) {
|
||||||
printf("%02x ", result_stack_[result_stack_.size() - 1 - c]);
|
printf("%02x ", result_stack_[result_stack_.size() - 1 - c]);
|
||||||
}
|
}
|
||||||
printf("\n");
|
printf("\n");
|
||||||
|
|
||||||
// Set ready to send data to the processor, no longer in non-DMA execution phase.
|
// Set ready to send data to the processor, no longer in non-DMA execution phase.
|
||||||
|
is_executing_ = false;
|
||||||
ResetNonDMAExecution();
|
ResetNonDMAExecution();
|
||||||
SetDataRequest();
|
SetDataRequest();
|
||||||
SetDataDirectionToProcessor();
|
SetDataDirectionToProcessor();
|
||||||
@@ -833,9 +853,9 @@ void i8272::posit_event(int event_type) {
|
|||||||
END_SECTION()
|
END_SECTION()
|
||||||
}
|
}
|
||||||
|
|
||||||
bool i8272::Drive::seek_is_satisfied() {
|
bool i8272::seek_is_satisfied(int drive) {
|
||||||
return (target_head_position == head_position) ||
|
return (drives_[drive].target_head_position == drives_[drive].head_position) ||
|
||||||
(target_head_position == -1 && drive->get_is_track_zero());
|
(drives_[drive].target_head_position == -1 && get_drive().get_is_track_zero());
|
||||||
}
|
}
|
||||||
|
|
||||||
void i8272::set_dma_acknowledge(bool dack) {
|
void i8272::set_dma_acknowledge(bool dack) {
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
#ifndef i8272_hpp
|
#ifndef i8272_hpp
|
||||||
#define i8272_hpp
|
#define i8272_hpp
|
||||||
|
|
||||||
#include "../../Storage/Disk/MFMDiskController.hpp"
|
#include "../../Storage/Disk/Controller/MFMDiskController.hpp"
|
||||||
|
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
@@ -26,7 +26,7 @@ class BusHandler {
|
|||||||
|
|
||||||
class i8272: public Storage::Disk::MFMController {
|
class i8272: public Storage::Disk::MFMController {
|
||||||
public:
|
public:
|
||||||
i8272(BusHandler &bus_handler, Cycles clock_rate, int clock_rate_multiplier, int revolutions_per_minute);
|
i8272(BusHandler &bus_handler, Cycles clock_rate);
|
||||||
|
|
||||||
void run_for(Cycles);
|
void run_for(Cycles);
|
||||||
|
|
||||||
@@ -39,7 +39,10 @@ class i8272: public Storage::Disk::MFMController {
|
|||||||
void set_dma_acknowledge(bool dack);
|
void set_dma_acknowledge(bool dack);
|
||||||
void set_terminal_count(bool tc);
|
void set_terminal_count(bool tc);
|
||||||
|
|
||||||
void set_disk(std::shared_ptr<Storage::Disk::Disk> disk, int drive);
|
bool is_sleeping();
|
||||||
|
|
||||||
|
protected:
|
||||||
|
virtual void select_drive(int number) = 0;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
// The bus handler, for interrupt and DMA-driven usage.
|
// The bus handler, for interrupt and DMA-driven usage.
|
||||||
@@ -47,86 +50,83 @@ class i8272: public Storage::Disk::MFMController {
|
|||||||
std::unique_ptr<BusHandler> allocated_bus_handler_;
|
std::unique_ptr<BusHandler> allocated_bus_handler_;
|
||||||
|
|
||||||
// Status registers.
|
// Status registers.
|
||||||
uint8_t main_status_;
|
uint8_t main_status_ = 0;
|
||||||
uint8_t status_[3];
|
uint8_t status_[3] = {0, 0, 0};
|
||||||
|
|
||||||
// A buffer for accumulating the incoming command, and one for accumulating the result.
|
// A buffer for accumulating the incoming command, and one for accumulating the result.
|
||||||
std::vector<uint8_t> command_;
|
std::vector<uint8_t> command_;
|
||||||
std::vector<uint8_t> result_stack_;
|
std::vector<uint8_t> result_stack_;
|
||||||
uint8_t input_;
|
uint8_t input_ = 0;
|
||||||
bool has_input_;
|
bool has_input_ = false;
|
||||||
bool expects_input_;
|
bool expects_input_ = false;
|
||||||
|
|
||||||
// Event stream: the 8272-specific events, plus the current event state.
|
// Event stream: the 8272-specific events, plus the current event state.
|
||||||
enum class Event8272: int {
|
enum class Event8272: int {
|
||||||
CommandByte = (1 << 3),
|
CommandByte = (1 << 3),
|
||||||
Timer = (1 << 4),
|
Timer = (1 << 4),
|
||||||
ResultEmpty = (1 << 5),
|
ResultEmpty = (1 << 5),
|
||||||
|
NoLongerReady = (1 << 6)
|
||||||
};
|
};
|
||||||
void posit_event(int type);
|
void posit_event(int type);
|
||||||
int interesting_event_mask_;
|
int interesting_event_mask_ = static_cast<int>(Event8272::CommandByte);
|
||||||
int resume_point_;
|
int resume_point_ = 0;
|
||||||
bool is_access_command_;
|
bool is_access_command_ = false;
|
||||||
|
|
||||||
// The counter used for ::Timer events.
|
// The counter used for ::Timer events.
|
||||||
int delay_time_;
|
int delay_time_ = 0;
|
||||||
|
|
||||||
// The connected drives.
|
// The connected drives.
|
||||||
struct Drive {
|
struct Drive {
|
||||||
uint8_t head_position;
|
uint8_t head_position = 0;
|
||||||
|
|
||||||
// Seeking: persistent state.
|
// Seeking: persistent state.
|
||||||
enum Phase {
|
enum Phase {
|
||||||
NotSeeking,
|
NotSeeking,
|
||||||
Seeking,
|
Seeking,
|
||||||
CompletedSeeking
|
CompletedSeeking
|
||||||
} phase;
|
} phase = NotSeeking;
|
||||||
bool did_seek;
|
bool did_seek = false;
|
||||||
bool seek_failed;
|
bool seek_failed = false;
|
||||||
|
|
||||||
// Seeking: transient state.
|
// Seeking: transient state.
|
||||||
int step_rate_counter;
|
int step_rate_counter = 0;
|
||||||
int steps_taken;
|
int steps_taken = 0;
|
||||||
int target_head_position; // either an actual number, or -1 to indicate to step until track zero
|
int target_head_position = 0; // either an actual number, or -1 to indicate to step until track zero
|
||||||
|
|
||||||
/// @returns @c true if the currently queued-up seek or recalibrate has reached where it should be.
|
|
||||||
bool seek_is_satisfied();
|
|
||||||
|
|
||||||
// Head state.
|
// Head state.
|
||||||
int head_unload_delay[2];
|
int head_unload_delay[2] = {0, 0};
|
||||||
bool head_is_loaded[2];
|
bool head_is_loaded[2] = {false, false};
|
||||||
|
|
||||||
// The connected drive.
|
|
||||||
std::shared_ptr<Storage::Disk::Drive> drive;
|
|
||||||
|
|
||||||
Drive() :
|
|
||||||
head_position(0), phase(NotSeeking),
|
|
||||||
drive(new Storage::Disk::Drive),
|
|
||||||
head_is_loaded{false, false} {};
|
|
||||||
} drives_[4];
|
} drives_[4];
|
||||||
int drives_seeking_;
|
int drives_seeking_ = 0;
|
||||||
|
|
||||||
|
/// @returns @c true if the selected drive, which is number @c drive, can stop seeking.
|
||||||
|
bool seek_is_satisfied(int drive);
|
||||||
|
|
||||||
// User-supplied parameters; as per the specify command.
|
// User-supplied parameters; as per the specify command.
|
||||||
int step_rate_time_;
|
int step_rate_time_ = 1;
|
||||||
int head_unload_time_;
|
int head_unload_time_ = 1;
|
||||||
int head_load_time_;
|
int head_load_time_ = 1;
|
||||||
bool dma_mode_;
|
bool dma_mode_ = false;
|
||||||
|
bool is_executing_ = false;
|
||||||
|
|
||||||
// A count of head unload timers currently running.
|
// A count of head unload timers currently running.
|
||||||
int head_timers_running_;
|
int head_timers_running_ = 0;
|
||||||
|
|
||||||
// Transient storage and counters used while reading the disk.
|
// Transient storage and counters used while reading the disk.
|
||||||
uint8_t header_[6];
|
uint8_t header_[6] = {0, 0, 0, 0, 0, 0};
|
||||||
int distance_into_section_;
|
int distance_into_section_ = 0;
|
||||||
int index_hole_count_, index_hole_limit_;
|
int index_hole_count_ = 0, index_hole_limit_ = 0;
|
||||||
|
|
||||||
// Keeps track of the drive and head in use during commands.
|
// Keeps track of the drive and head in use during commands.
|
||||||
int active_drive_;
|
int active_drive_ = 0;
|
||||||
int active_head_;
|
int active_head_ = 0;
|
||||||
|
|
||||||
// Internal registers.
|
// Internal registers.
|
||||||
uint8_t cylinder_, head_, sector_, size_;
|
uint8_t cylinder_ = 0, head_ = 0, sector_ = 0, size_ = 0;
|
||||||
|
|
||||||
|
// Master switch on not performing any work.
|
||||||
|
bool is_sleeping_ = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
664
Components/9918/9918.cpp
Normal file
664
Components/9918/9918.cpp
Normal file
@@ -0,0 +1,664 @@
|
|||||||
|
//
|
||||||
|
// 9918.cpp
|
||||||
|
// Clock Signal
|
||||||
|
//
|
||||||
|
// Created by Thomas Harte on 25/11/2017.
|
||||||
|
// Copyright © 2017 Thomas Harte. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
#include "9918.hpp"
|
||||||
|
|
||||||
|
#include <cassert>
|
||||||
|
#include <cstring>
|
||||||
|
|
||||||
|
using namespace TI;
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
const uint32_t palette_pack(uint8_t r, uint8_t g, uint8_t b) {
|
||||||
|
uint32_t result = 0;
|
||||||
|
uint8_t *const result_ptr = reinterpret_cast<uint8_t *>(&result);
|
||||||
|
result_ptr[0] = r;
|
||||||
|
result_ptr[1] = g;
|
||||||
|
result_ptr[2] = b;
|
||||||
|
result_ptr[3] = 0;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
const uint32_t palette[16] = {
|
||||||
|
palette_pack(0, 0, 0),
|
||||||
|
palette_pack(0, 0, 0),
|
||||||
|
palette_pack(33, 200, 66),
|
||||||
|
palette_pack(94, 220, 120),
|
||||||
|
|
||||||
|
palette_pack(84, 85, 237),
|
||||||
|
palette_pack(125, 118, 252),
|
||||||
|
palette_pack(212, 82, 77),
|
||||||
|
palette_pack(66, 235, 245),
|
||||||
|
|
||||||
|
palette_pack(252, 85, 84),
|
||||||
|
palette_pack(255, 121, 120),
|
||||||
|
palette_pack(212, 193, 84),
|
||||||
|
palette_pack(230, 206, 128),
|
||||||
|
|
||||||
|
palette_pack(33, 176, 59),
|
||||||
|
palette_pack(201, 91, 186),
|
||||||
|
palette_pack(204, 204, 204),
|
||||||
|
palette_pack(255, 255, 255)
|
||||||
|
};
|
||||||
|
|
||||||
|
const uint8_t StatusInterrupt = 0x80;
|
||||||
|
const uint8_t StatusFifthSprite = 0x40;
|
||||||
|
|
||||||
|
const int StatusSpriteCollisionShift = 5;
|
||||||
|
const uint8_t StatusSpriteCollision = 0x20;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
TMS9918Base::TMS9918Base() :
|
||||||
|
// 342 internal cycles are 228/227.5ths of a line, so 341.25 cycles should be a whole
|
||||||
|
// line. Therefore multiply everything by four, but set line length to 1365 rather than 342*4 = 1368.
|
||||||
|
crt_(new Outputs::CRT::CRT(1365, 4, Outputs::CRT::DisplayType::NTSC60, 4)) {}
|
||||||
|
|
||||||
|
TMS9918::TMS9918(Personality p) {
|
||||||
|
// Unimaginatively, this class just passes RGB through to the shader. Investigation is needed
|
||||||
|
// into whether there's a more natural form.
|
||||||
|
crt_->set_rgb_sampling_function(
|
||||||
|
"vec3 rgb_sample(usampler2D sampler, vec2 coordinate, vec2 icoordinate)"
|
||||||
|
"{"
|
||||||
|
"return texture(sampler, coordinate).rgb / vec3(255.0);"
|
||||||
|
"}");
|
||||||
|
crt_->set_output_device(Outputs::CRT::OutputDevice::Monitor);
|
||||||
|
crt_->set_visible_area(Outputs::CRT::Rect(0.055f, 0.025f, 0.9f, 0.9f));
|
||||||
|
crt_->set_input_gamma(2.8f);
|
||||||
|
}
|
||||||
|
|
||||||
|
Outputs::CRT::CRT *TMS9918::get_crt() {
|
||||||
|
return crt_.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
void TMS9918Base::test_sprite(int sprite_number, int screen_row) {
|
||||||
|
if(!(status_ & StatusFifthSprite)) {
|
||||||
|
status_ = static_cast<uint8_t>((status_ & ~31) | sprite_number);
|
||||||
|
}
|
||||||
|
if(sprites_stopped_)
|
||||||
|
return;
|
||||||
|
|
||||||
|
const int sprite_position = ram_[sprite_attribute_table_address_ + (sprite_number << 2)];
|
||||||
|
// A sprite Y of 208 means "don't scan the list any further".
|
||||||
|
if(sprite_position == 208) {
|
||||||
|
sprites_stopped_ = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const int sprite_row = (screen_row - sprite_position)&255;
|
||||||
|
if(sprite_row < 0 || sprite_row >= sprite_height_) return;
|
||||||
|
|
||||||
|
const int active_sprite_slot = sprite_sets_[active_sprite_set_].active_sprite_slot;
|
||||||
|
if(active_sprite_slot == 4) {
|
||||||
|
status_ |= StatusFifthSprite;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
SpriteSet::ActiveSprite &sprite = sprite_sets_[active_sprite_set_].active_sprites[active_sprite_slot];
|
||||||
|
sprite.index = sprite_number;
|
||||||
|
sprite.row = sprite_row >> (sprites_magnified_ ? 1 : 0);
|
||||||
|
sprite_sets_[active_sprite_set_].active_sprite_slot++;
|
||||||
|
}
|
||||||
|
|
||||||
|
void TMS9918Base::get_sprite_contents(int field, int cycles_left, int screen_row) {
|
||||||
|
int sprite_id = field / 6;
|
||||||
|
field %= 6;
|
||||||
|
|
||||||
|
while(true) {
|
||||||
|
const int cycles_in_sprite = std::min(cycles_left, 6 - field);
|
||||||
|
cycles_left -= cycles_in_sprite;
|
||||||
|
const int final_field = cycles_in_sprite + field;
|
||||||
|
|
||||||
|
assert(sprite_id < 4);
|
||||||
|
SpriteSet::ActiveSprite &sprite = sprite_sets_[active_sprite_set_].active_sprites[sprite_id];
|
||||||
|
|
||||||
|
if(field < 4) {
|
||||||
|
std::memcpy(
|
||||||
|
&sprite.info[field],
|
||||||
|
&ram_[sprite_attribute_table_address_ + (sprite.index << 2) + field],
|
||||||
|
static_cast<size_t>(std::min(4, final_field) - field));
|
||||||
|
}
|
||||||
|
|
||||||
|
field = std::min(4, final_field);
|
||||||
|
const int sprite_offset = sprite.info[2] & ~(sprites_16x16_ ? 3 : 0);
|
||||||
|
const int sprite_address = sprite_generator_table_address_ + (sprite_offset << 3) + sprite.row; // TODO: recalclate sprite.row from screen_row (?)
|
||||||
|
while(field < final_field) {
|
||||||
|
sprite.image[field - 4] = ram_[sprite_address + ((field - 4) << 4)];
|
||||||
|
field++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!cycles_left) return;
|
||||||
|
field = 0;
|
||||||
|
sprite_id++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void TMS9918::run_for(const HalfCycles cycles) {
|
||||||
|
// As specific as I've been able to get:
|
||||||
|
// Scanline time is always 228 cycles.
|
||||||
|
// PAL output is 313 lines total. NTSC output is 262 lines total.
|
||||||
|
// Interrupt is signalled upon entering the lower border.
|
||||||
|
|
||||||
|
// Keep a count of cycles separate from internal counts to avoid
|
||||||
|
// potential errors mapping back and forth.
|
||||||
|
half_cycles_into_frame_ = (half_cycles_into_frame_ + cycles) % HalfCycles(frame_lines_ * 228 * 2);
|
||||||
|
|
||||||
|
// Convert 456 clocked half cycles per line to 342 internal cycles per line;
|
||||||
|
// the internal clock is 1.5 times the nominal 3.579545 Mhz that I've advertised
|
||||||
|
// for this part. So multiply by three quarters.
|
||||||
|
int int_cycles = (cycles.as_int() * 3) + cycles_error_;
|
||||||
|
cycles_error_ = int_cycles & 3;
|
||||||
|
int_cycles >>= 2;
|
||||||
|
if(!int_cycles) return;
|
||||||
|
|
||||||
|
while(int_cycles) {
|
||||||
|
// Determine how much time has passed in the remainder of this line, and proceed.
|
||||||
|
int cycles_left = std::min(342 - column_, int_cycles);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// ------------------------------------
|
||||||
|
// Potentially perform a memory access.
|
||||||
|
// ------------------------------------
|
||||||
|
if(queued_access_ != MemoryAccess::None) {
|
||||||
|
int time_until_access_slot = 0;
|
||||||
|
switch(line_mode_) {
|
||||||
|
case LineMode::Refresh:
|
||||||
|
if(column_ < 53 || column_ >= 307) time_until_access_slot = column_&1;
|
||||||
|
else time_until_access_slot = 3 - ((column_ - 53)&3);
|
||||||
|
// i.e. 53 -> 3, 52 -> 2, 51 -> 1, 50 -> 0, etc
|
||||||
|
break;
|
||||||
|
case LineMode::Text:
|
||||||
|
if(column_ < 59 || column_ >= 299) time_until_access_slot = column_&1;
|
||||||
|
else time_until_access_slot = 5 - ((column_ + 3)%6);
|
||||||
|
// i.e. 59 -> 3, 60 -> 2, 61 -> 1, etc
|
||||||
|
break;
|
||||||
|
case LineMode::Character:
|
||||||
|
if(column_ < 9) time_until_access_slot = column_&1;
|
||||||
|
else if(column_ < 30) time_until_access_slot = 30 - column_;
|
||||||
|
else if(column_ < 37) time_until_access_slot = column_&1;
|
||||||
|
else if(column_ < 311) time_until_access_slot = 31 - ((column_ + 7)&31);
|
||||||
|
// i.e. 53 -> 3, 54 -> 2, 55 -> 1, 56 -> 0, 57 -> 31, etc
|
||||||
|
else if(column_ < 313) time_until_access_slot = column_&1;
|
||||||
|
else time_until_access_slot = 342 - column_;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(cycles_left >= time_until_access_slot) {
|
||||||
|
if(queued_access_ == MemoryAccess::Write) {
|
||||||
|
ram_[ram_pointer_ & 16383] = read_ahead_buffer_;
|
||||||
|
} else {
|
||||||
|
read_ahead_buffer_ = ram_[ram_pointer_ & 16383];
|
||||||
|
}
|
||||||
|
ram_pointer_++;
|
||||||
|
queued_access_ = MemoryAccess::None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
column_ += cycles_left; // column_ is now the column that has been reached in this line.
|
||||||
|
int_cycles -= cycles_left; // Count down duration to run for.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// ------------------------------
|
||||||
|
// Perform video memory accesses.
|
||||||
|
// ------------------------------
|
||||||
|
if(((row_ < 192) || (row_ == frame_lines_-1)) && !blank_screen_) {
|
||||||
|
const int sprite_row = (row_ < 192) ? row_ : -1;
|
||||||
|
const int access_slot = column_ >> 1; // There are only 171 available memory accesses per line.
|
||||||
|
switch(line_mode_) {
|
||||||
|
default: break;
|
||||||
|
|
||||||
|
case LineMode::Text:
|
||||||
|
access_pointer_ = std::min(30, access_slot);
|
||||||
|
if(access_pointer_ >= 30 && access_pointer_ < 150) {
|
||||||
|
const int row_base = pattern_name_address_ + (row_ >> 3) * 40;
|
||||||
|
const int end = std::min(150, access_slot);
|
||||||
|
|
||||||
|
// Pattern names are collected every third window starting from window 30.
|
||||||
|
const int pattern_names_start = (access_pointer_ - 30 + 2) / 3;
|
||||||
|
const int pattern_names_end = (end - 30 + 2) / 3;
|
||||||
|
std::memcpy(&pattern_names_[pattern_names_start], &ram_[row_base + pattern_names_start], static_cast<size_t>(pattern_names_end - pattern_names_start));
|
||||||
|
|
||||||
|
// Patterns are collected every third window starting from window 32.
|
||||||
|
const int pattern_buffer_start = (access_pointer_ - 32 + 2) / 3;
|
||||||
|
const int pattern_buffer_end = (end - 32 + 2) / 3;
|
||||||
|
for(int column = pattern_buffer_start; column < pattern_buffer_end; ++column) {
|
||||||
|
pattern_buffer_[column] = ram_[pattern_generator_table_address_ + (pattern_names_[column] << 3) + (row_ & 7)];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case LineMode::Character:
|
||||||
|
// Four access windows: no collection.
|
||||||
|
if(access_pointer_ < 5)
|
||||||
|
access_pointer_ = std::min(5, access_slot);
|
||||||
|
|
||||||
|
// Then ten access windows are filled with collection of sprite 3 and 4 details.
|
||||||
|
if(access_pointer_ >= 5 && access_pointer_ < 15) {
|
||||||
|
int end = std::min(15, access_slot);
|
||||||
|
get_sprite_contents(access_pointer_ - 5 + 14, end - access_pointer_, sprite_row - 1);
|
||||||
|
access_pointer_ = std::min(15, access_slot);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Four more access windows: no collection.
|
||||||
|
if(access_pointer_ >= 15 && access_pointer_ < 19) {
|
||||||
|
access_pointer_ = std::min(19, access_slot);
|
||||||
|
|
||||||
|
// Start new sprite set if this is location 19.
|
||||||
|
if(access_pointer_ == 19) {
|
||||||
|
active_sprite_set_ ^= 1;
|
||||||
|
sprite_sets_[active_sprite_set_].active_sprite_slot = 0;
|
||||||
|
sprites_stopped_ = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Then eight access windows fetch the y position for the first eight sprites.
|
||||||
|
while(access_pointer_ < 27 && access_pointer_ < access_slot) {
|
||||||
|
test_sprite(access_pointer_ - 19, sprite_row);
|
||||||
|
access_pointer_++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The next 128 access slots are video and sprite collection interleaved.
|
||||||
|
if(access_pointer_ >= 27 && access_pointer_ < 155) {
|
||||||
|
int end = std::min(155, access_slot);
|
||||||
|
|
||||||
|
int row_base = pattern_name_address_;
|
||||||
|
int pattern_base = pattern_generator_table_address_;
|
||||||
|
int colour_base = colour_table_address_;
|
||||||
|
if(screen_mode_ == 1) {
|
||||||
|
pattern_base &= 0x2000 | ((row_ & 0xc0) << 5);
|
||||||
|
colour_base &= 0x2000 | ((row_ & 0xc0) << 5);
|
||||||
|
}
|
||||||
|
row_base += (row_ << 2)&~31;
|
||||||
|
|
||||||
|
// Pattern names are collected every fourth window starting from window 27.
|
||||||
|
const int pattern_names_start = (access_pointer_ - 27 + 3) >> 2;
|
||||||
|
const int pattern_names_end = (end - 27 + 3) >> 2;
|
||||||
|
std::memcpy(&pattern_names_[pattern_names_start], &ram_[row_base + pattern_names_start], static_cast<size_t>(pattern_names_end - pattern_names_start));
|
||||||
|
|
||||||
|
// Colours are collected ever fourth window starting from window 29.
|
||||||
|
const int colours_start = (access_pointer_ - 29 + 3) >> 2;
|
||||||
|
const int colours_end = (end - 29 + 3) >> 2;
|
||||||
|
if(screen_mode_ != 1) {
|
||||||
|
for(int column = colours_start; column < colours_end; ++column) {
|
||||||
|
colour_buffer_[column] = ram_[colour_base + (pattern_names_[column] >> 3)];
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for(int column = colours_start; column < colours_end; ++column) {
|
||||||
|
colour_buffer_[column] = ram_[colour_base + (pattern_names_[column] << 3) + (row_ & 7)];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Patterns are collected ever fourth window starting from window 30.
|
||||||
|
const int pattern_buffer_start = (access_pointer_ - 30 + 3) >> 2;
|
||||||
|
const int pattern_buffer_end = (end - 30 + 3) >> 2;
|
||||||
|
for(int column = pattern_buffer_start; column < pattern_buffer_end; ++column) {
|
||||||
|
pattern_buffer_[column] = ram_[pattern_base + (pattern_names_[column] << 3) + (row_ & 7)];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sprite slots occur in three quarters of ever fourth window starting from window 28.
|
||||||
|
const int sprite_start = (access_pointer_ - 28 + 3) >> 2;
|
||||||
|
const int sprite_end = (end - 28 + 3) >> 2;
|
||||||
|
for(int column = sprite_start; column < sprite_end; ++column) {
|
||||||
|
if(column&3) {
|
||||||
|
test_sprite(7 + column - (column >> 2), sprite_row);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
access_pointer_ = std::min(155, access_slot);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Two access windows: no collection.
|
||||||
|
if(access_pointer_ < 157)
|
||||||
|
access_pointer_ = std::min(157, access_slot);
|
||||||
|
|
||||||
|
// Fourteen access windows: collect initial sprite information.
|
||||||
|
if(access_pointer_ >= 157 && access_pointer_ < 171) {
|
||||||
|
int end = std::min(171, access_slot);
|
||||||
|
get_sprite_contents(access_pointer_ - 157, end - access_pointer_, sprite_row);
|
||||||
|
access_pointer_ = std::min(171, access_slot);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// --------------------------
|
||||||
|
// End video memory accesses.
|
||||||
|
// --------------------------
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// --------------------
|
||||||
|
// Output video stream.
|
||||||
|
// --------------------
|
||||||
|
if(row_ < 192 && !blank_screen_) {
|
||||||
|
// ----------------------
|
||||||
|
// Output horizontal sync
|
||||||
|
// ----------------------
|
||||||
|
if(!output_column_ && column_ >= 26) {
|
||||||
|
crt_->output_sync(13 * 4);
|
||||||
|
crt_->output_default_colour_burst(13 * 4);
|
||||||
|
output_column_ = 26;
|
||||||
|
}
|
||||||
|
|
||||||
|
// -------------------
|
||||||
|
// Output left border.
|
||||||
|
// -------------------
|
||||||
|
if(output_column_ >= 26) {
|
||||||
|
int pixels_end = std::min(first_pixel_column_, column_);
|
||||||
|
if(output_column_ < pixels_end) {
|
||||||
|
output_border(pixels_end - output_column_);
|
||||||
|
output_column_ = pixels_end;
|
||||||
|
|
||||||
|
// Grab a pointer for drawing pixels to, if the moment has arrived.
|
||||||
|
if(pixels_end == first_pixel_column_) {
|
||||||
|
pixel_base_ = pixel_target_ = reinterpret_cast<uint32_t *>(crt_->allocate_write_area(static_cast<unsigned int>(first_right_border_column_ - first_pixel_column_)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --------------
|
||||||
|
// Output pixels.
|
||||||
|
// --------------
|
||||||
|
if(output_column_ >= first_pixel_column_) {
|
||||||
|
int pixels_end = std::min(first_right_border_column_, column_);
|
||||||
|
|
||||||
|
if(output_column_ < pixels_end) {
|
||||||
|
switch(line_mode_) {
|
||||||
|
default: break;
|
||||||
|
|
||||||
|
case LineMode::Text: {
|
||||||
|
const uint32_t colours[2] = { palette[background_colour_], palette[text_colour_] };
|
||||||
|
|
||||||
|
const int shift = (output_column_ - first_pixel_column_) % 6;
|
||||||
|
int byte_column = (output_column_ - first_pixel_column_) / 6;
|
||||||
|
int pattern = pattern_buffer_[byte_column] << shift;
|
||||||
|
int pixels_left = pixels_end - output_column_;
|
||||||
|
int length = std::min(pixels_left, 6 - shift);
|
||||||
|
while(true) {
|
||||||
|
pixels_left -= length;
|
||||||
|
while(length--) {
|
||||||
|
*pixel_target_ = colours[(pattern >> 7)&0x01];
|
||||||
|
pixel_target_++;
|
||||||
|
pattern <<= 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!pixels_left) break;
|
||||||
|
length = std::min(6, pixels_left);
|
||||||
|
byte_column++;
|
||||||
|
pattern = pattern_buffer_[byte_column];
|
||||||
|
}
|
||||||
|
output_column_ = pixels_end;
|
||||||
|
} break;
|
||||||
|
|
||||||
|
case LineMode::Character: {
|
||||||
|
// If this is the start of the visible area, seed sprite shifter positions.
|
||||||
|
SpriteSet &sprite_set = sprite_sets_[active_sprite_set_ ^ 1];
|
||||||
|
if(line_mode_ == LineMode::Character && output_column_ == first_pixel_column_) {
|
||||||
|
int c = sprite_set.active_sprite_slot;
|
||||||
|
while(c--) {
|
||||||
|
SpriteSet::ActiveSprite &sprite = sprite_set.active_sprites[c];
|
||||||
|
sprite.shift_position = -sprite.info[1];
|
||||||
|
if(sprite.info[3] & 0x80) {
|
||||||
|
sprite.shift_position += 32;
|
||||||
|
if(sprite.shift_position > 0 && !sprites_magnified_)
|
||||||
|
sprite.shift_position *= 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Paint the background tiles.
|
||||||
|
const int shift = (output_column_ - first_pixel_column_) & 7;
|
||||||
|
int byte_column = (output_column_ - first_pixel_column_) >> 3;
|
||||||
|
|
||||||
|
const int pixels_left = pixels_end - output_column_;
|
||||||
|
int length = std::min(pixels_left, 8 - shift);
|
||||||
|
|
||||||
|
int pattern = pattern_buffer_[byte_column] << shift;
|
||||||
|
uint8_t colour = colour_buffer_[byte_column];
|
||||||
|
uint32_t colours[2] = {
|
||||||
|
palette[(colour & 15) ? (colour & 15) : background_colour_],
|
||||||
|
palette[(colour >> 4) ? (colour >> 4) : background_colour_]
|
||||||
|
};
|
||||||
|
|
||||||
|
int background_pixels_left = pixels_left;
|
||||||
|
while(true) {
|
||||||
|
background_pixels_left -= length;
|
||||||
|
while(length--) {
|
||||||
|
*pixel_target_ = colours[(pattern >> 7)&0x01];
|
||||||
|
pixel_target_++;
|
||||||
|
pattern <<= 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!background_pixels_left) break;
|
||||||
|
length = std::min(8, background_pixels_left);
|
||||||
|
byte_column++;
|
||||||
|
|
||||||
|
pattern = pattern_buffer_[byte_column];
|
||||||
|
colour = colour_buffer_[byte_column];
|
||||||
|
colours[0] = palette[(colour & 15) ? (colour & 15) : background_colour_];
|
||||||
|
colours[1] = palette[(colour >> 4) ? (colour >> 4) : background_colour_];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Paint sprites and check for collisions.
|
||||||
|
if(sprite_set.active_sprite_slot) {
|
||||||
|
int sprite_pixels_left = pixels_left;
|
||||||
|
const int shift_advance = sprites_magnified_ ? 1 : 2;
|
||||||
|
|
||||||
|
const uint32_t sprite_colour_selection_masks[2] = {0x00000000, 0xffffffff};
|
||||||
|
const int colour_masks[16] = {0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1};
|
||||||
|
|
||||||
|
while(sprite_pixels_left--) {
|
||||||
|
uint32_t sprite_colour = pixel_base_[output_column_ - first_pixel_column_];
|
||||||
|
int sprite_mask = 0;
|
||||||
|
int c = sprite_set.active_sprite_slot;
|
||||||
|
while(c--) {
|
||||||
|
SpriteSet::ActiveSprite &sprite = sprite_set.active_sprites[c];
|
||||||
|
|
||||||
|
if(sprite.shift_position < 0) {
|
||||||
|
sprite.shift_position++;
|
||||||
|
continue;
|
||||||
|
} else if(sprite.shift_position < 32) {
|
||||||
|
int mask = sprite.image[sprite.shift_position >> 4] << ((sprite.shift_position&15) >> 1);
|
||||||
|
mask = (mask >> 7) & 1;
|
||||||
|
status_ |= (mask & sprite_mask) << StatusSpriteCollisionShift;
|
||||||
|
sprite_mask |= mask;
|
||||||
|
sprite.shift_position += shift_advance;
|
||||||
|
|
||||||
|
mask &= colour_masks[sprite.info[3]&15];
|
||||||
|
sprite_colour = (sprite_colour & sprite_colour_selection_masks[mask^1]) | (palette[sprite.info[3]&15] & sprite_colour_selection_masks[mask]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pixel_base_[output_column_ - first_pixel_column_] = sprite_colour;
|
||||||
|
output_column_++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
output_column_ = pixels_end;
|
||||||
|
} break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(output_column_ == first_right_border_column_) {
|
||||||
|
crt_->output_data(static_cast<unsigned int>(first_right_border_column_ - first_pixel_column_) * 4, 4);
|
||||||
|
pixel_target_ = nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --------------------
|
||||||
|
// Output right border.
|
||||||
|
// --------------------
|
||||||
|
if(output_column_ >= first_right_border_column_) {
|
||||||
|
output_border(column_ - output_column_);
|
||||||
|
output_column_ = column_;
|
||||||
|
}
|
||||||
|
} else if(row_ >= first_vsync_line_ && row_ < first_vsync_line_+3) {
|
||||||
|
// Vertical sync.
|
||||||
|
if(column_ == 342) {
|
||||||
|
crt_->output_sync(342 * 4);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Blank.
|
||||||
|
if(!output_column_ && column_ >= 26) {
|
||||||
|
crt_->output_sync(13 * 4);
|
||||||
|
crt_->output_default_colour_burst(13 * 4);
|
||||||
|
output_column_ = 26;
|
||||||
|
}
|
||||||
|
if(output_column_ >= 26) {
|
||||||
|
output_border(column_ - output_column_);
|
||||||
|
output_column_ = column_;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// -----------------
|
||||||
|
// End video stream.
|
||||||
|
// -----------------
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// -----------------------------------
|
||||||
|
// Prepare for next line, potentially.
|
||||||
|
// -----------------------------------
|
||||||
|
if(column_ == 342) {
|
||||||
|
access_pointer_ = column_ = output_column_ = 0;
|
||||||
|
row_ = (row_ + 1) % frame_lines_;
|
||||||
|
if(row_ == 192) status_ |= StatusInterrupt;
|
||||||
|
|
||||||
|
screen_mode_ = next_screen_mode_;
|
||||||
|
blank_screen_ = next_blank_screen_;
|
||||||
|
switch(screen_mode_) {
|
||||||
|
case 2:
|
||||||
|
line_mode_ = LineMode::Text;
|
||||||
|
first_pixel_column_ = 69;
|
||||||
|
first_right_border_column_ = 309;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
line_mode_ = LineMode::Character;
|
||||||
|
first_pixel_column_ = 63;
|
||||||
|
first_right_border_column_ = 319;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if(blank_screen_ || (row_ >= 192 && row_ != frame_lines_-1)) line_mode_ = LineMode::Refresh;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void TMS9918Base::output_border(int cycles) {
|
||||||
|
pixel_target_ = reinterpret_cast<uint32_t *>(crt_->allocate_write_area(1));
|
||||||
|
if(pixel_target_) *pixel_target_ = palette[background_colour_];
|
||||||
|
crt_->output_level(static_cast<unsigned int>(cycles) * 4);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TMS9918::set_register(int address, uint8_t value) {
|
||||||
|
// Writes to address 0 are writes to the video RAM. Store
|
||||||
|
// the value and return.
|
||||||
|
if(!(address & 1)) {
|
||||||
|
write_phase_ = false;
|
||||||
|
|
||||||
|
// Enqueue the write to occur at the next available slot.
|
||||||
|
read_ahead_buffer_ = value;
|
||||||
|
queued_access_ = MemoryAccess::Write;
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Writes to address 1 are performed in pairs; if this is the
|
||||||
|
// low byte of a value, store it and wait for the high byte.
|
||||||
|
if(!write_phase_) {
|
||||||
|
low_write_ = value;
|
||||||
|
write_phase_ = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
write_phase_ = false;
|
||||||
|
if(value & 0x80) {
|
||||||
|
// This is a write to a register.
|
||||||
|
switch(value & 7) {
|
||||||
|
case 0:
|
||||||
|
next_screen_mode_ = (next_screen_mode_ & 6) | ((low_write_ & 2) >> 1);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 1:
|
||||||
|
next_blank_screen_ = !(low_write_ & 0x40);
|
||||||
|
generate_interrupts_ = !!(low_write_ & 0x20);
|
||||||
|
next_screen_mode_ = (next_screen_mode_ & 1) | ((low_write_ & 0x18) >> 3);
|
||||||
|
sprites_16x16_ = !!(low_write_ & 0x02);
|
||||||
|
sprites_magnified_ = !!(low_write_ & 0x01);
|
||||||
|
|
||||||
|
sprite_height_ = 8;
|
||||||
|
if(sprites_16x16_) sprite_height_ <<= 1;
|
||||||
|
if(sprites_magnified_) sprite_height_ <<= 1;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 2:
|
||||||
|
pattern_name_address_ = static_cast<uint16_t>((low_write_ & 0xf) << 10);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 3:
|
||||||
|
colour_table_address_ = static_cast<uint16_t>(low_write_ << 6);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 4:
|
||||||
|
pattern_generator_table_address_ = static_cast<uint16_t>((low_write_ & 0x07) << 11);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 5:
|
||||||
|
sprite_attribute_table_address_ = static_cast<uint16_t>((low_write_ & 0x7f) << 7);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 6:
|
||||||
|
sprite_generator_table_address_ = static_cast<uint16_t>((low_write_ & 0x07) << 11);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 7:
|
||||||
|
text_colour_ = low_write_ >> 4;
|
||||||
|
background_colour_ = low_write_ & 0xf;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// This is a write to the RAM pointer.
|
||||||
|
ram_pointer_ = static_cast<uint16_t>(low_write_ | (value << 8));
|
||||||
|
if(!(value & 0x40)) {
|
||||||
|
// Officially a 'read' set, so perform lookahead.
|
||||||
|
queued_access_ = MemoryAccess::Read;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t TMS9918::get_register(int address) {
|
||||||
|
write_phase_ = false;
|
||||||
|
|
||||||
|
// Reads from address 0 read video RAM, via the read-ahead buffer.
|
||||||
|
if(!(address & 1)) {
|
||||||
|
// Enqueue the write to occur at the next available slot.
|
||||||
|
uint8_t result = read_ahead_buffer_;
|
||||||
|
queued_access_ = MemoryAccess::Read;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reads from address 1 get the status register.
|
||||||
|
uint8_t result = status_;
|
||||||
|
status_ &= ~(StatusInterrupt | StatusFifthSprite | StatusSpriteCollision);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
HalfCycles TMS9918::get_time_until_interrupt() {
|
||||||
|
if(!generate_interrupts_) return HalfCycles(-1);
|
||||||
|
if(get_interrupt_line()) return HalfCycles(0);
|
||||||
|
|
||||||
|
const int half_cycles_per_frame = frame_lines_ * 228 * 2;
|
||||||
|
int half_cycles_remaining = (192 * 228 * 2 + half_cycles_per_frame - half_cycles_into_frame_.as_int()) % half_cycles_per_frame;
|
||||||
|
return HalfCycles(half_cycles_remaining ? half_cycles_remaining : half_cycles_per_frame);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TMS9918::get_interrupt_line() {
|
||||||
|
return (status_ & StatusInterrupt) && generate_interrupts_;
|
||||||
|
}
|
||||||
86
Components/9918/9918.hpp
Normal file
86
Components/9918/9918.hpp
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
//
|
||||||
|
// 9918.hpp
|
||||||
|
// Clock Signal
|
||||||
|
//
|
||||||
|
// Created by Thomas Harte on 25/11/2017.
|
||||||
|
// Copyright © 2017 Thomas Harte. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef TMS9918_hpp
|
||||||
|
#define TMS9918_hpp
|
||||||
|
|
||||||
|
#include "../../Outputs/CRT/CRT.hpp"
|
||||||
|
#include "../../ClockReceiver/ClockReceiver.hpp"
|
||||||
|
|
||||||
|
#include "Implementation/9918Base.hpp"
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
namespace TI {
|
||||||
|
|
||||||
|
/*!
|
||||||
|
Provides emulation of the TMS9918a, TMS9928 and TMS9929. Likely in the future to be the
|
||||||
|
vessel for emulation of sufficiently close derivatives, such as the Master System VDP.
|
||||||
|
|
||||||
|
The TMS9918 and descendants are video display generators that own their own RAM, making it
|
||||||
|
accessible through an implicitly-timed register interface, and (depending on model) can generate
|
||||||
|
PAL and NTSC component and composite video.
|
||||||
|
|
||||||
|
These chips have only one non-on-demand interaction with the outside world: an interrupt line.
|
||||||
|
See get_time_until_interrupt and get_interrupt_line for asynchronous operation options.
|
||||||
|
*/
|
||||||
|
class TMS9918: public TMS9918Base {
|
||||||
|
public:
|
||||||
|
enum Personality {
|
||||||
|
TMS9918A, // includes the 9928 and 9929; set TV standard and output device as desired.
|
||||||
|
};
|
||||||
|
|
||||||
|
/*!
|
||||||
|
Constructs an instance of the drive controller that behaves according to personality @c p.
|
||||||
|
@param p The type of controller to emulate.
|
||||||
|
*/
|
||||||
|
TMS9918(Personality p);
|
||||||
|
|
||||||
|
enum TVStandard {
|
||||||
|
/*! i.e. 50Hz output at around 312.5 lines/field */
|
||||||
|
PAL,
|
||||||
|
/*! i.e. 60Hz output at around 262.5 lines/field */
|
||||||
|
NTSC
|
||||||
|
};
|
||||||
|
|
||||||
|
/*! Sets the TV standard for this TMS, if that is hard-coded in hardware. */
|
||||||
|
void set_tv_standard(TVStandard standard);
|
||||||
|
|
||||||
|
/*! Provides the CRT this TMS is connected to. */
|
||||||
|
Outputs::CRT::CRT *get_crt();
|
||||||
|
|
||||||
|
/*!
|
||||||
|
Runs the VCP for the number of cycles indicate; it is an implicit assumption of the code
|
||||||
|
that the input clock rate is 3579545 Hz — the NTSC colour clock rate.
|
||||||
|
*/
|
||||||
|
void run_for(const HalfCycles cycles);
|
||||||
|
|
||||||
|
/*! Sets a register value. */
|
||||||
|
void set_register(int address, uint8_t value);
|
||||||
|
|
||||||
|
/*! Gets a register value. */
|
||||||
|
uint8_t get_register(int address);
|
||||||
|
|
||||||
|
/*!
|
||||||
|
Returns the amount of time until get_interrupt_line would next return true if
|
||||||
|
there are no interceding calls to set_register or get_register.
|
||||||
|
|
||||||
|
If get_interrupt_line is true now, returns zero. If get_interrupt_line would
|
||||||
|
never return true, returns -1.
|
||||||
|
*/
|
||||||
|
HalfCycles get_time_until_interrupt();
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@returns @c true if the interrupt line is currently active; @c false otherwise.
|
||||||
|
*/
|
||||||
|
bool get_interrupt_line();
|
||||||
|
};
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif /* TMS9918_hpp */
|
||||||
101
Components/9918/Implementation/9918Base.hpp
Normal file
101
Components/9918/Implementation/9918Base.hpp
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
//
|
||||||
|
// 9918Base.hpp
|
||||||
|
// Clock Signal
|
||||||
|
//
|
||||||
|
// Created by Thomas Harte on 14/12/2017.
|
||||||
|
// Copyright © 2017 Thomas Harte. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef TMS9918Base_hpp
|
||||||
|
#define TMS9918Base_hpp
|
||||||
|
|
||||||
|
#include "../../../Outputs/CRT/CRT.hpp"
|
||||||
|
#include "../../../ClockReceiver/ClockReceiver.hpp"
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
namespace TI {
|
||||||
|
|
||||||
|
class TMS9918Base {
|
||||||
|
protected:
|
||||||
|
TMS9918Base();
|
||||||
|
|
||||||
|
std::unique_ptr<Outputs::CRT::CRT> crt_;
|
||||||
|
|
||||||
|
uint8_t ram_[16384];
|
||||||
|
|
||||||
|
uint16_t ram_pointer_ = 0;
|
||||||
|
uint8_t read_ahead_buffer_ = 0;
|
||||||
|
enum class MemoryAccess {
|
||||||
|
Read, Write, None
|
||||||
|
} queued_access_ = MemoryAccess::None;
|
||||||
|
|
||||||
|
uint8_t status_ = 0;
|
||||||
|
|
||||||
|
bool write_phase_ = false;
|
||||||
|
uint8_t low_write_ = 0;
|
||||||
|
|
||||||
|
// The various register flags.
|
||||||
|
int next_screen_mode_ = 0, screen_mode_ = 0;
|
||||||
|
bool next_blank_screen_ = true, blank_screen_ = true;
|
||||||
|
bool sprites_16x16_ = false;
|
||||||
|
bool sprites_magnified_ = false;
|
||||||
|
bool generate_interrupts_ = false;
|
||||||
|
int sprite_height_ = 8;
|
||||||
|
uint16_t pattern_name_address_ = 0;
|
||||||
|
uint16_t colour_table_address_ = 0;
|
||||||
|
uint16_t pattern_generator_table_address_ = 0;
|
||||||
|
uint16_t sprite_attribute_table_address_ = 0;
|
||||||
|
uint16_t sprite_generator_table_address_ = 0;
|
||||||
|
|
||||||
|
uint8_t text_colour_ = 0;
|
||||||
|
uint8_t background_colour_ = 0;
|
||||||
|
|
||||||
|
HalfCycles half_cycles_into_frame_;
|
||||||
|
int column_ = 0, row_ = 0, output_column_ = 0;
|
||||||
|
int cycles_error_ = 0;
|
||||||
|
uint32_t *pixel_target_ = nullptr, *pixel_base_ = nullptr;
|
||||||
|
|
||||||
|
void output_border(int cycles);
|
||||||
|
|
||||||
|
// Vertical timing details.
|
||||||
|
int frame_lines_ = 262;
|
||||||
|
int first_vsync_line_ = 227;
|
||||||
|
|
||||||
|
// Horizontal selections.
|
||||||
|
enum class LineMode {
|
||||||
|
Text = 0,
|
||||||
|
Character = 1,
|
||||||
|
Refresh = 2
|
||||||
|
} line_mode_ = LineMode::Text;
|
||||||
|
int first_pixel_column_, first_right_border_column_;
|
||||||
|
|
||||||
|
uint8_t pattern_names_[40];
|
||||||
|
uint8_t pattern_buffer_[40];
|
||||||
|
uint8_t colour_buffer_[40];
|
||||||
|
|
||||||
|
struct SpriteSet {
|
||||||
|
struct ActiveSprite {
|
||||||
|
int index = 0;
|
||||||
|
int row = 0;
|
||||||
|
|
||||||
|
uint8_t info[4];
|
||||||
|
uint8_t image[2];
|
||||||
|
|
||||||
|
int shift_position = 0;
|
||||||
|
} active_sprites[4];
|
||||||
|
int active_sprite_slot = 0;
|
||||||
|
} sprite_sets_[2];
|
||||||
|
int active_sprite_set_ = 0;
|
||||||
|
bool sprites_stopped_ = false;
|
||||||
|
|
||||||
|
int access_pointer_ = 0;
|
||||||
|
|
||||||
|
inline void test_sprite(int sprite_number, int screen_row);
|
||||||
|
inline void get_sprite_contents(int start, int cycles, int screen_row);
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /* TMS9918Base_hpp */
|
||||||
@@ -8,18 +8,11 @@
|
|||||||
|
|
||||||
#include "AY38910.hpp"
|
#include "AY38910.hpp"
|
||||||
|
|
||||||
|
#include <cmath>
|
||||||
|
|
||||||
using namespace GI::AY38910;
|
using namespace GI::AY38910;
|
||||||
|
|
||||||
AY38910::AY38910() :
|
AY38910::AY38910(Concurrency::DeferringAsyncTaskQueue &task_queue) : task_queue_(task_queue) {
|
||||||
selected_register_(0),
|
|
||||||
tone_counters_{0, 0, 0}, tone_periods_{0, 0, 0}, tone_outputs_{0, 0, 0},
|
|
||||||
noise_shift_register_(0xffff), noise_period_(0), noise_counter_(0), noise_output_(0),
|
|
||||||
envelope_divider_(0), envelope_period_(0), envelope_position_(0),
|
|
||||||
master_divider_(0),
|
|
||||||
output_registers_{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
|
|
||||||
port_handler_(nullptr) {
|
|
||||||
output_registers_[8] = output_registers_[9] = output_registers_[10] = 0;
|
|
||||||
|
|
||||||
// set up envelope lookup tables
|
// set up envelope lookup tables
|
||||||
for(int c = 0; c < 16; c++) {
|
for(int c = 0; c < 16; c++) {
|
||||||
for(int p = 0; p < 32; p++) {
|
for(int p = 0; p < 32; p++) {
|
||||||
@@ -67,17 +60,13 @@ AY38910::AY38910() :
|
|||||||
float max_volume = 8192;
|
float max_volume = 8192;
|
||||||
float root_two = sqrtf(2.0f);
|
float root_two = sqrtf(2.0f);
|
||||||
for(int v = 0; v < 16; v++) {
|
for(int v = 0; v < 16; v++) {
|
||||||
volumes_[v] = (int)(max_volume / powf(root_two, (float)(v ^ 0xf)));
|
volumes_[v] = static_cast<int>(max_volume / powf(root_two, static_cast<float>(v ^ 0xf)));
|
||||||
}
|
}
|
||||||
volumes_[0] = 0;
|
volumes_[0] = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void AY38910::set_clock_rate(double clock_rate) {
|
void AY38910::get_samples(std::size_t number_of_samples, int16_t *target) {
|
||||||
set_input_rate((float)clock_rate);
|
std::size_t c = 0;
|
||||||
}
|
|
||||||
|
|
||||||
void AY38910::get_samples(unsigned int number_of_samples, int16_t *target) {
|
|
||||||
unsigned int c = 0;
|
|
||||||
while((master_divider_&7) && c < number_of_samples) {
|
while((master_divider_&7) && c < number_of_samples) {
|
||||||
target[c] = output_volume_;
|
target[c] = output_volume_;
|
||||||
master_divider_++;
|
master_divider_++;
|
||||||
@@ -160,14 +149,14 @@ void AY38910::evaluate_output_volume() {
|
|||||||
#undef channel_volume
|
#undef channel_volume
|
||||||
|
|
||||||
// Mix additively.
|
// Mix additively.
|
||||||
output_volume_ = (int16_t)(
|
output_volume_ = static_cast<int16_t>(
|
||||||
volumes_[volumes[0]] * channel_levels[0] +
|
volumes_[volumes[0]] * channel_levels[0] +
|
||||||
volumes_[volumes[1]] * channel_levels[1] +
|
volumes_[volumes[1]] * channel_levels[1] +
|
||||||
volumes_[volumes[2]] * channel_levels[2]
|
volumes_[volumes[2]] * channel_levels[2]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#pragma mark - Register manipulation
|
// MARK: - Register manipulation
|
||||||
|
|
||||||
void AY38910::select_register(uint8_t r) {
|
void AY38910::select_register(uint8_t r) {
|
||||||
selected_register_ = r;
|
selected_register_ = r;
|
||||||
@@ -178,7 +167,7 @@ void AY38910::set_register_value(uint8_t value) {
|
|||||||
registers_[selected_register_] = value;
|
registers_[selected_register_] = value;
|
||||||
if(selected_register_ < 14) {
|
if(selected_register_ < 14) {
|
||||||
int selected_register = selected_register_;
|
int selected_register = selected_register_;
|
||||||
enqueue([=] () {
|
task_queue_.defer([=] () {
|
||||||
uint8_t masked_value = value;
|
uint8_t masked_value = value;
|
||||||
switch(selected_register) {
|
switch(selected_register) {
|
||||||
case 0: case 2: case 4:
|
case 0: case 2: case 4:
|
||||||
@@ -186,7 +175,7 @@ void AY38910::set_register_value(uint8_t value) {
|
|||||||
int channel = selected_register >> 1;
|
int channel = selected_register >> 1;
|
||||||
|
|
||||||
if(selected_register & 1)
|
if(selected_register & 1)
|
||||||
tone_periods_[channel] = (tone_periods_[channel] & 0xff) | (uint16_t)((value&0xf) << 8);
|
tone_periods_[channel] = (tone_periods_[channel] & 0xff) | static_cast<uint16_t>((value&0xf) << 8);
|
||||||
else
|
else
|
||||||
tone_periods_[channel] = (tone_periods_[channel] & ~0xff) | value;
|
tone_periods_[channel] = (tone_periods_[channel] & ~0xff) | value;
|
||||||
}
|
}
|
||||||
@@ -201,7 +190,7 @@ void AY38910::set_register_value(uint8_t value) {
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case 12:
|
case 12:
|
||||||
envelope_period_ = (envelope_period_ & 0xff) | (int)(value << 8);
|
envelope_period_ = (envelope_period_ & 0xff) | static_cast<int>(value << 8);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 13:
|
case 13:
|
||||||
@@ -233,13 +222,13 @@ uint8_t AY38910::get_register_value() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#pragma mark - Port handling
|
// MARK: - Port handling
|
||||||
|
|
||||||
uint8_t AY38910::get_port_output(bool port_b) {
|
uint8_t AY38910::get_port_output(bool port_b) {
|
||||||
return registers_[port_b ? 15 : 14];
|
return registers_[port_b ? 15 : 14];
|
||||||
}
|
}
|
||||||
|
|
||||||
#pragma mark - Bus handling
|
// MARK: - Bus handling
|
||||||
|
|
||||||
void AY38910::set_port_handler(PortHandler *handler) {
|
void AY38910::set_port_handler(PortHandler *handler) {
|
||||||
port_handler_ = handler;
|
port_handler_ = handler;
|
||||||
@@ -262,15 +251,15 @@ uint8_t AY38910::get_data_output() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void AY38910::set_control_lines(ControlLines control_lines) {
|
void AY38910::set_control_lines(ControlLines control_lines) {
|
||||||
switch((int)control_lines) {
|
switch(static_cast<int>(control_lines)) {
|
||||||
default: control_state_ = Inactive; break;
|
default: control_state_ = Inactive; break;
|
||||||
|
|
||||||
case (int)(BDIR | BC2 | BC1):
|
case static_cast<int>(BDIR | BC2 | BC1):
|
||||||
case BDIR:
|
case BDIR:
|
||||||
case BC1: control_state_ = LatchAddress; break;
|
case BC1: control_state_ = LatchAddress; break;
|
||||||
|
|
||||||
case (int)(BC2 | BC1): control_state_ = Read; break;
|
case static_cast<int>(BC2 | BC1): control_state_ = Read; break;
|
||||||
case (int)(BDIR | BC2): control_state_ = Write; break;
|
case static_cast<int>(BDIR | BC2): control_state_ = Write; break;
|
||||||
}
|
}
|
||||||
|
|
||||||
update_bus();
|
update_bus();
|
||||||
|
|||||||
@@ -9,7 +9,8 @@
|
|||||||
#ifndef AY_3_8910_hpp
|
#ifndef AY_3_8910_hpp
|
||||||
#define AY_3_8910_hpp
|
#define AY_3_8910_hpp
|
||||||
|
|
||||||
#include "../../Outputs/Speaker.hpp"
|
#include "../../Outputs/Speaker/Implementation/SampleSource.hpp"
|
||||||
|
#include "../../Concurrency/AsyncTaskQueue.hpp"
|
||||||
|
|
||||||
namespace GI {
|
namespace GI {
|
||||||
namespace AY38910 {
|
namespace AY38910 {
|
||||||
@@ -55,13 +56,10 @@ enum ControlLines {
|
|||||||
noise generator and a volume envelope generator, which also provides two bidirectional
|
noise generator and a volume envelope generator, which also provides two bidirectional
|
||||||
interface ports.
|
interface ports.
|
||||||
*/
|
*/
|
||||||
class AY38910: public ::Outputs::Filter<AY38910> {
|
class AY38910: public ::Outputs::Speaker::SampleSource {
|
||||||
public:
|
public:
|
||||||
/// Creates a new AY38910.
|
/// Creates a new AY38910.
|
||||||
AY38910();
|
AY38910(Concurrency::DeferringAsyncTaskQueue &task_queue);
|
||||||
|
|
||||||
/// Sets the clock rate at which this AY38910 will be run.
|
|
||||||
void set_clock_rate(double clock_rate);
|
|
||||||
|
|
||||||
/// Sets the value the AY would read from its data lines if it were not outputting.
|
/// Sets the value the AY would read from its data lines if it were not outputting.
|
||||||
void set_data_input(uint8_t r);
|
void set_data_input(uint8_t r);
|
||||||
@@ -86,27 +84,30 @@ class AY38910: public ::Outputs::Filter<AY38910> {
|
|||||||
void set_port_handler(PortHandler *);
|
void set_port_handler(PortHandler *);
|
||||||
|
|
||||||
// to satisfy ::Outputs::Speaker (included via ::Outputs::Filter; not for public consumption
|
// to satisfy ::Outputs::Speaker (included via ::Outputs::Filter; not for public consumption
|
||||||
void get_samples(unsigned int number_of_samples, int16_t *target);
|
void get_samples(std::size_t number_of_samples, int16_t *target);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
int selected_register_;
|
Concurrency::DeferringAsyncTaskQueue &task_queue_;
|
||||||
uint8_t registers_[16], output_registers_[16];
|
|
||||||
|
int selected_register_ = 0;
|
||||||
|
uint8_t registers_[16];
|
||||||
|
uint8_t output_registers_[16] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
|
||||||
uint8_t port_inputs_[2];
|
uint8_t port_inputs_[2];
|
||||||
|
|
||||||
int master_divider_;
|
int master_divider_ = 0;
|
||||||
|
|
||||||
int tone_periods_[3];
|
int tone_periods_[3] = {0, 0, 0};
|
||||||
int tone_counters_[3];
|
int tone_counters_[3] = {0, 0, 0};
|
||||||
int tone_outputs_[3];
|
int tone_outputs_[3] = {0, 0, 0};
|
||||||
|
|
||||||
int noise_period_;
|
int noise_period_ = 0;
|
||||||
int noise_counter_;
|
int noise_counter_ = 0;
|
||||||
int noise_shift_register_;
|
int noise_shift_register_ = 0xffff;
|
||||||
int noise_output_;
|
int noise_output_ = 0;
|
||||||
|
|
||||||
int envelope_period_;
|
int envelope_period_ = 0;
|
||||||
int envelope_divider_;
|
int envelope_divider_ = 0;
|
||||||
int envelope_position_;
|
int envelope_position_ = 0;
|
||||||
int envelope_shapes_[16][32];
|
int envelope_shapes_[16][32];
|
||||||
int envelope_overflow_masks_[16];
|
int envelope_overflow_masks_[16];
|
||||||
|
|
||||||
@@ -129,7 +130,7 @@ class AY38910: public ::Outputs::Filter<AY38910> {
|
|||||||
inline void evaluate_output_volume();
|
inline void evaluate_output_volume();
|
||||||
|
|
||||||
inline void update_bus();
|
inline void update_bus();
|
||||||
PortHandler *port_handler_;
|
PortHandler *port_handler_ = nullptr;
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
107
Components/KonamiSCC/KonamiSCC.cpp
Normal file
107
Components/KonamiSCC/KonamiSCC.cpp
Normal file
@@ -0,0 +1,107 @@
|
|||||||
|
//
|
||||||
|
// KonamiSCC.cpp
|
||||||
|
// Clock Signal
|
||||||
|
//
|
||||||
|
// Created by Thomas Harte on 06/01/2018.
|
||||||
|
// Copyright © 2018 Thomas Harte. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
#include "KonamiSCC.hpp"
|
||||||
|
|
||||||
|
#include <cstring>
|
||||||
|
|
||||||
|
using namespace Konami;
|
||||||
|
|
||||||
|
SCC::SCC(Concurrency::DeferringAsyncTaskQueue &task_queue) :
|
||||||
|
task_queue_(task_queue) {}
|
||||||
|
|
||||||
|
bool SCC::is_silent() {
|
||||||
|
return !(channel_enable_ & 0x1f);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SCC::get_samples(std::size_t number_of_samples, std::int16_t *target) {
|
||||||
|
if(is_silent()) {
|
||||||
|
std::memset(target, 0, sizeof(std::int16_t) * number_of_samples);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::size_t c = 0;
|
||||||
|
while((master_divider_&7) && c < number_of_samples) {
|
||||||
|
target[c] = output_volume_;
|
||||||
|
master_divider_++;
|
||||||
|
c++;
|
||||||
|
}
|
||||||
|
|
||||||
|
while(c < number_of_samples) {
|
||||||
|
for(int channel = 0; channel < 5; ++channel) {
|
||||||
|
if(channels_[channel].tone_counter) channels_[channel].tone_counter--;
|
||||||
|
else {
|
||||||
|
channels_[channel].offset = (channels_[channel].offset + 1) & 0x1f;
|
||||||
|
channels_[channel].tone_counter = channels_[channel].period;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
evaluate_output_volume();
|
||||||
|
|
||||||
|
for(int ic = 0; ic < 8 && c < number_of_samples; ++ic) {
|
||||||
|
target[c] = output_volume_;
|
||||||
|
c++;
|
||||||
|
master_divider_++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SCC::write(uint16_t address, uint8_t value) {
|
||||||
|
address &= 0xff;
|
||||||
|
if(address < 0x80) ram_[address] = value;
|
||||||
|
|
||||||
|
task_queue_.defer([=] {
|
||||||
|
// Check for a write into waveform memory.
|
||||||
|
if(address < 0x80) {
|
||||||
|
waves_[address >> 5].samples[address & 0x1f] = value;
|
||||||
|
} else switch(address) {
|
||||||
|
default: break;
|
||||||
|
|
||||||
|
case 0x80: case 0x82: case 0x84: case 0x86: case 0x88: {
|
||||||
|
int channel = (address - 0x80) >> 1;
|
||||||
|
channels_[channel].period = (channels_[channel].period & ~0xff) | value;
|
||||||
|
} break;
|
||||||
|
|
||||||
|
case 0x81: case 0x83: case 0x85: case 0x87: case 0x89: {
|
||||||
|
int channel = (address - 0x80) >> 1;
|
||||||
|
channels_[channel].period = (channels_[channel].period & 0xff) | ((value & 0xf) << 8);
|
||||||
|
} break;
|
||||||
|
|
||||||
|
case 0x8a: case 0x8b: case 0x8c: case 0x8d: case 0x8e:
|
||||||
|
channels_[address - 0x8a].amplitude = value & 0xf;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 0x8f:
|
||||||
|
channel_enable_ = value;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
evaluate_output_volume();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void SCC::evaluate_output_volume() {
|
||||||
|
output_volume_ =
|
||||||
|
static_cast<int16_t>(
|
||||||
|
(
|
||||||
|
(channel_enable_ & 0x01) ? static_cast<int8_t>(waves_[0].samples[channels_[0].offset]) * channels_[0].amplitude : 0 +
|
||||||
|
(channel_enable_ & 0x02) ? static_cast<int8_t>(waves_[1].samples[channels_[1].offset]) * channels_[1].amplitude : 0 +
|
||||||
|
(channel_enable_ & 0x04) ? static_cast<int8_t>(waves_[2].samples[channels_[2].offset]) * channels_[2].amplitude : 0 +
|
||||||
|
(channel_enable_ & 0x08) ? static_cast<int8_t>(waves_[3].samples[channels_[3].offset]) * channels_[3].amplitude : 0 +
|
||||||
|
(channel_enable_ & 0x10) ? static_cast<int8_t>(waves_[3].samples[channels_[4].offset]) * channels_[4].amplitude : 0
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t SCC::read(uint16_t address) {
|
||||||
|
address &= 0xff;
|
||||||
|
if(address < 0x80) {
|
||||||
|
return ram_[address];
|
||||||
|
}
|
||||||
|
return 0xff;
|
||||||
|
}
|
||||||
72
Components/KonamiSCC/KonamiSCC.hpp
Normal file
72
Components/KonamiSCC/KonamiSCC.hpp
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
//
|
||||||
|
// KonamiSCC.hpp
|
||||||
|
// Clock Signal
|
||||||
|
//
|
||||||
|
// Created by Thomas Harte on 06/01/2018.
|
||||||
|
// Copyright © 2018 Thomas Harte. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef KonamiSCC_hpp
|
||||||
|
#define KonamiSCC_hpp
|
||||||
|
|
||||||
|
#include "../../Outputs/Speaker/Implementation/SampleSource.hpp"
|
||||||
|
#include "../../Concurrency/AsyncTaskQueue.hpp"
|
||||||
|
|
||||||
|
namespace Konami {
|
||||||
|
|
||||||
|
/*!
|
||||||
|
Provides an emulation of Konami's Sound Creative Chip ('SCC').
|
||||||
|
|
||||||
|
The SCC is a primitive wavetable synthesis chip, offering 32-sample tables,
|
||||||
|
and five channels of output. The original SCC uses the same wave for channels
|
||||||
|
four and five, the SCC+ supports different waves for the two channels.
|
||||||
|
*/
|
||||||
|
class SCC: public ::Outputs::Speaker::SampleSource {
|
||||||
|
public:
|
||||||
|
/// Creates a new SCC.
|
||||||
|
SCC(Concurrency::DeferringAsyncTaskQueue &task_queue);
|
||||||
|
|
||||||
|
/// As per ::SampleSource; provides a broadphase test for silence.
|
||||||
|
bool is_silent();
|
||||||
|
|
||||||
|
/// As per ::SampleSource; provides audio output.
|
||||||
|
void get_samples(std::size_t number_of_samples, std::int16_t *target);
|
||||||
|
|
||||||
|
/// Writes to the SCC.
|
||||||
|
void write(uint16_t address, uint8_t value);
|
||||||
|
|
||||||
|
/// Reads from the SCC.
|
||||||
|
uint8_t read(uint16_t address);
|
||||||
|
|
||||||
|
private:
|
||||||
|
Concurrency::DeferringAsyncTaskQueue &task_queue_;
|
||||||
|
|
||||||
|
// State from here on down is accessed ony from the audio thread.
|
||||||
|
int master_divider_ = 0;
|
||||||
|
int16_t output_volume_ = 0;
|
||||||
|
|
||||||
|
struct Channel {
|
||||||
|
int period = 0;
|
||||||
|
int amplitude = 0;
|
||||||
|
|
||||||
|
int tone_counter = 0;
|
||||||
|
int offset = 0;
|
||||||
|
} channels_[5];
|
||||||
|
|
||||||
|
struct Wavetable {
|
||||||
|
std::uint8_t samples[32];
|
||||||
|
} waves_[4];
|
||||||
|
|
||||||
|
std::uint8_t channel_enable_ = 0;
|
||||||
|
std::uint8_t test_register_ = 0;
|
||||||
|
|
||||||
|
void evaluate_output_volume();
|
||||||
|
|
||||||
|
// This keeps a copy of wave memory that is accessed from the
|
||||||
|
// main emulation thread.
|
||||||
|
std::uint8_t ram_[128];
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /* KonamiSCC_hpp */
|
||||||
@@ -79,3 +79,21 @@ void AsyncTaskQueue::flush() {
|
|||||||
flush_condition->wait(lock);
|
flush_condition->wait(lock);
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void DeferringAsyncTaskQueue::defer(std::function<void(void)> function) {
|
||||||
|
if(!deferred_tasks_) {
|
||||||
|
deferred_tasks_.reset(new std::list<std::function<void(void)>>);
|
||||||
|
}
|
||||||
|
deferred_tasks_->push_back(function);
|
||||||
|
}
|
||||||
|
|
||||||
|
void DeferringAsyncTaskQueue::perform() {
|
||||||
|
if(!deferred_tasks_) return;
|
||||||
|
std::shared_ptr<std::list<std::function<void(void)>>> deferred_tasks = deferred_tasks_;
|
||||||
|
deferred_tasks_.reset();
|
||||||
|
enqueue([deferred_tasks] {
|
||||||
|
for(auto &function : *deferred_tasks) {
|
||||||
|
function();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|||||||
@@ -9,10 +9,12 @@
|
|||||||
#ifndef AsyncTaskQueue_hpp
|
#ifndef AsyncTaskQueue_hpp
|
||||||
#define AsyncTaskQueue_hpp
|
#define AsyncTaskQueue_hpp
|
||||||
|
|
||||||
|
#include <atomic>
|
||||||
|
#include <condition_variable>
|
||||||
|
#include <functional>
|
||||||
|
#include <list>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <thread>
|
#include <thread>
|
||||||
#include <list>
|
|
||||||
#include <condition_variable>
|
|
||||||
|
|
||||||
#ifdef __APPLE__
|
#ifdef __APPLE__
|
||||||
#include <dispatch/dispatch.h>
|
#include <dispatch/dispatch.h>
|
||||||
@@ -57,6 +59,37 @@ class AsyncTaskQueue {
|
|||||||
#endif
|
#endif
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/*!
|
||||||
|
A deferring async task queue is one that accepts a list of functions to be performed but defers
|
||||||
|
any action until told to perform. It performs them by enquing a single asynchronous task that will
|
||||||
|
perform the deferred tasks in order.
|
||||||
|
|
||||||
|
It therefore offers similar semantics to an asynchronous task queue, but allows for management of
|
||||||
|
synchronisation costs, since neither defer nor perform make any effort to be thread safe.
|
||||||
|
*/
|
||||||
|
class DeferringAsyncTaskQueue: public AsyncTaskQueue {
|
||||||
|
public:
|
||||||
|
/*!
|
||||||
|
Adds a function to the deferral list.
|
||||||
|
|
||||||
|
This is not thread safe; it should be serialised with other calls to itself and to perform.
|
||||||
|
*/
|
||||||
|
void defer(std::function<void(void)> function);
|
||||||
|
|
||||||
|
/*!
|
||||||
|
Enqueues a function that will perform all currently deferred functions, in the
|
||||||
|
order that they were deferred.
|
||||||
|
|
||||||
|
This is not thread safe; it should be serialised with other calls to itself and to defer.
|
||||||
|
*/
|
||||||
|
void perform();
|
||||||
|
|
||||||
|
private:
|
||||||
|
// TODO: this is a shared_ptr because of the issues capturing moveables in C++11;
|
||||||
|
// switch to a unique_ptr if/when adapting to C++14
|
||||||
|
std::shared_ptr<std::list<std::function<void(void)>>> deferred_tasks_;
|
||||||
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif /* Concurrency_hpp */
|
#endif /* Concurrency_hpp */
|
||||||
|
|||||||
72
Concurrency/BestEffortUpdater.cpp
Normal file
72
Concurrency/BestEffortUpdater.cpp
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
//
|
||||||
|
// BestEffortUpdater.cpp
|
||||||
|
// Clock Signal
|
||||||
|
//
|
||||||
|
// Created by Thomas Harte on 04/10/2017.
|
||||||
|
// Copyright © 2017 Thomas Harte. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
#include "BestEffortUpdater.hpp"
|
||||||
|
|
||||||
|
#include <cmath>
|
||||||
|
|
||||||
|
using namespace Concurrency;
|
||||||
|
|
||||||
|
BestEffortUpdater::BestEffortUpdater() {
|
||||||
|
// ATOMIC_FLAG_INIT isn't necessarily safe to use, so establish default state by other means.
|
||||||
|
update_is_ongoing_.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
void BestEffortUpdater::update() {
|
||||||
|
// Perform an update only if one is not currently ongoing.
|
||||||
|
if(!update_is_ongoing_.test_and_set()) {
|
||||||
|
async_task_queue_.enqueue([this]() {
|
||||||
|
// Get time now using the highest-resolution clock provided by the implementation, and determine
|
||||||
|
// the duration since the last time this section was entered.
|
||||||
|
const std::chrono::time_point<std::chrono::high_resolution_clock> now = std::chrono::high_resolution_clock::now();
|
||||||
|
const auto elapsed = now - previous_time_point_;
|
||||||
|
previous_time_point_ = now;
|
||||||
|
|
||||||
|
if(has_previous_time_point_) {
|
||||||
|
// If the duration is valid, convert it to integer cycles, maintaining a rolling error and call the delegate
|
||||||
|
// if there is one. Proceed only if the number of cycles is positive, and cap it to the per-second maximum —
|
||||||
|
// it's possible this is an adjustable clock so be ready to swallow unexpected adjustments.
|
||||||
|
const int64_t duration = std::chrono::duration_cast<std::chrono::nanoseconds>(elapsed).count();
|
||||||
|
if(duration > 0) {
|
||||||
|
double cycles = ((static_cast<double>(duration) * clock_rate_) / 1e9) + error_;
|
||||||
|
error_ = fmod(cycles, 1.0);
|
||||||
|
|
||||||
|
if(delegate_) {
|
||||||
|
delegate_->update(this, static_cast<int>(std::min(cycles, clock_rate_)), has_skipped_);
|
||||||
|
}
|
||||||
|
has_skipped_ = false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
has_previous_time_point_ = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Allow furthers updates to occur.
|
||||||
|
update_is_ongoing_.clear();
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
async_task_queue_.enqueue([this]() {
|
||||||
|
has_skipped_ = true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void BestEffortUpdater::flush() {
|
||||||
|
async_task_queue_.flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
void BestEffortUpdater::set_delegate(Delegate *const delegate) {
|
||||||
|
async_task_queue_.enqueue([this, delegate]() {
|
||||||
|
delegate_ = delegate;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void BestEffortUpdater::set_clock_rate(const double clock_rate) {
|
||||||
|
async_task_queue_.enqueue([this, clock_rate]() {
|
||||||
|
this->clock_rate_ = clock_rate;
|
||||||
|
});
|
||||||
|
}
|
||||||
65
Concurrency/BestEffortUpdater.hpp
Normal file
65
Concurrency/BestEffortUpdater.hpp
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
//
|
||||||
|
// BestEffortUpdater.hpp
|
||||||
|
// Clock Signal
|
||||||
|
//
|
||||||
|
// Created by Thomas Harte on 04/10/2017.
|
||||||
|
// Copyright © 2017 Thomas Harte. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef BestEffortUpdater_hpp
|
||||||
|
#define BestEffortUpdater_hpp
|
||||||
|
|
||||||
|
#include <atomic>
|
||||||
|
#include <chrono>
|
||||||
|
|
||||||
|
#include "AsyncTaskQueue.hpp"
|
||||||
|
|
||||||
|
namespace Concurrency {
|
||||||
|
|
||||||
|
/*!
|
||||||
|
Accepts timing cues from multiple threads and ensures that a delegate receives calls to total
|
||||||
|
a certain number of cycles per second, that those calls are strictly serialised, and that no
|
||||||
|
backlog of calls accrues.
|
||||||
|
|
||||||
|
No guarantees about the thread that the delegate will be called on are made.
|
||||||
|
*/
|
||||||
|
class BestEffortUpdater {
|
||||||
|
public:
|
||||||
|
BestEffortUpdater();
|
||||||
|
|
||||||
|
/// A delegate receives timing cues.
|
||||||
|
struct Delegate {
|
||||||
|
virtual void update(BestEffortUpdater *updater, int cycles, bool did_skip_previous_update) = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Sets the current delegate.
|
||||||
|
void set_delegate(Delegate *);
|
||||||
|
|
||||||
|
/// Sets the clock rate of the delegate.
|
||||||
|
void set_clock_rate(double clock_rate);
|
||||||
|
|
||||||
|
/*!
|
||||||
|
If the delegate is not currently in the process of an `update` call, calls it now to catch up to the current time.
|
||||||
|
The call is asynchronous; this method will return immediately.
|
||||||
|
*/
|
||||||
|
void update();
|
||||||
|
|
||||||
|
/// Blocks until any ongoing update is complete.
|
||||||
|
void flush();
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::atomic_flag update_is_ongoing_;
|
||||||
|
AsyncTaskQueue async_task_queue_;
|
||||||
|
|
||||||
|
std::chrono::time_point<std::chrono::high_resolution_clock> previous_time_point_;
|
||||||
|
bool has_previous_time_point_ = false;
|
||||||
|
double error_ = 0.0;
|
||||||
|
bool has_skipped_ = false;
|
||||||
|
|
||||||
|
Delegate *delegate_ = nullptr;
|
||||||
|
double clock_rate_ = 1.0;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /* BestEffortUpdater_hpp */
|
||||||
27
Configurable/Configurable.cpp
Normal file
27
Configurable/Configurable.cpp
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
//
|
||||||
|
// Configurable.cpp
|
||||||
|
// Clock Signal
|
||||||
|
//
|
||||||
|
// Created by Thomas Harte on 18/11/2017.
|
||||||
|
// Copyright © 2017 Thomas Harte. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
#include "Configurable.hpp"
|
||||||
|
|
||||||
|
using namespace Configurable;
|
||||||
|
|
||||||
|
ListSelection *BooleanSelection::list_selection() {
|
||||||
|
return new ListSelection(value ? "yes" : "no");
|
||||||
|
}
|
||||||
|
|
||||||
|
ListSelection *ListSelection::list_selection() {
|
||||||
|
return new ListSelection(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
BooleanSelection *ListSelection::boolean_selection() {
|
||||||
|
return new BooleanSelection(value != "no" && value != "n");
|
||||||
|
}
|
||||||
|
|
||||||
|
BooleanSelection *BooleanSelection::boolean_selection() {
|
||||||
|
return new BooleanSelection(value);
|
||||||
|
}
|
||||||
88
Configurable/Configurable.hpp
Normal file
88
Configurable/Configurable.hpp
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
//
|
||||||
|
// Configurable.h
|
||||||
|
// Clock Signal
|
||||||
|
//
|
||||||
|
// Created by Thomas Harte on 17/11/2017.
|
||||||
|
// Copyright © 2017 Thomas Harte. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef Configurable_h
|
||||||
|
#define Configurable_h
|
||||||
|
|
||||||
|
#include <map>
|
||||||
|
#include <memory>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace Configurable {
|
||||||
|
|
||||||
|
/*!
|
||||||
|
The Option class hierarchy provides a way for components, machines, etc, to provide a named
|
||||||
|
list of typed options to which they can respond.
|
||||||
|
*/
|
||||||
|
struct Option {
|
||||||
|
std::string long_name;
|
||||||
|
std::string short_name;
|
||||||
|
virtual ~Option() {}
|
||||||
|
|
||||||
|
Option(const std::string &long_name, const std::string &short_name) : long_name(long_name), short_name(short_name) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct BooleanOption: public Option {
|
||||||
|
BooleanOption(const std::string &long_name, const std::string &short_name) : Option(long_name, short_name) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ListOption: public Option {
|
||||||
|
std::vector<std::string> options;
|
||||||
|
ListOption(const std::string &long_name, const std::string &short_name, const std::vector<std::string> &options) : Option(long_name, short_name), options(options) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct BooleanSelection;
|
||||||
|
struct ListSelection;
|
||||||
|
|
||||||
|
/*!
|
||||||
|
Selections are responses to Options.
|
||||||
|
*/
|
||||||
|
struct Selection {
|
||||||
|
virtual ~Selection() {}
|
||||||
|
virtual ListSelection *list_selection() = 0;
|
||||||
|
virtual BooleanSelection *boolean_selection() = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct BooleanSelection: public Selection {
|
||||||
|
bool value;
|
||||||
|
|
||||||
|
ListSelection *list_selection();
|
||||||
|
BooleanSelection *boolean_selection();
|
||||||
|
BooleanSelection(bool value) : value(value) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ListSelection: public Selection {
|
||||||
|
std::string value;
|
||||||
|
|
||||||
|
ListSelection *list_selection();
|
||||||
|
BooleanSelection *boolean_selection();
|
||||||
|
ListSelection(const std::string value) : value(value) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
using SelectionSet = std::map<std::string, std::unique_ptr<Selection>>;
|
||||||
|
|
||||||
|
/*!
|
||||||
|
A Configuratble provides the options that it responds to and allows selections to be set.
|
||||||
|
*/
|
||||||
|
struct Device {
|
||||||
|
virtual std::vector<std::unique_ptr<Option>> get_options() = 0;
|
||||||
|
virtual void set_selections(const SelectionSet &selection_by_option) = 0;
|
||||||
|
virtual SelectionSet get_accurate_selections() = 0;
|
||||||
|
virtual SelectionSet get_user_friendly_selections() = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename T> T *selection(const Configurable::SelectionSet &selections_by_option, const std::string &name) {
|
||||||
|
auto selection = selections_by_option.find(name);
|
||||||
|
if(selection == selections_by_option.end()) return nullptr;
|
||||||
|
return dynamic_cast<T *>(selection->second.get());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /* Configurable_h */
|
||||||
76
Configurable/StandardOptions.cpp
Normal file
76
Configurable/StandardOptions.cpp
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
//
|
||||||
|
// StandardOptions.cpp
|
||||||
|
// Clock Signal
|
||||||
|
//
|
||||||
|
// Created by Thomas Harte on 20/11/2017.
|
||||||
|
// Copyright © 2017 Thomas Harte. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
#include "StandardOptions.hpp"
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
/*!
|
||||||
|
Appends a Boolean selection of @c selection for option @c name to @c selection_set.
|
||||||
|
*/
|
||||||
|
void append_bool(Configurable::SelectionSet &selection_set, const std::string &name, bool selection) {
|
||||||
|
selection_set[name] = std::unique_ptr<Configurable::Selection>(new Configurable::BooleanSelection(selection));
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
Enquires for a Boolean selection for option @c name from @c selections_by_option, storing it to @c result if found.
|
||||||
|
*/
|
||||||
|
bool get_bool(const Configurable::SelectionSet &selections_by_option, const std::string &name, bool &result) {
|
||||||
|
auto quickload = Configurable::selection<Configurable::BooleanSelection>(selections_by_option, "quickload");
|
||||||
|
if(!quickload) return false;
|
||||||
|
result = quickload->value;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Standard option list builder
|
||||||
|
std::vector<std::unique_ptr<Configurable::Option>> Configurable::standard_options(Configurable::StandardOptions mask) {
|
||||||
|
std::vector<std::unique_ptr<Configurable::Option>> options;
|
||||||
|
if(mask & QuickLoadTape) options.emplace_back(new Configurable::BooleanOption("Load Tapes Quickly", "quickload"));
|
||||||
|
if(mask & DisplayRGBComposite) options.emplace_back(new Configurable::ListOption("Display", "display", {"composite", "rgb"}));
|
||||||
|
if(mask & AutomaticTapeMotorControl) options.emplace_back(new Configurable::BooleanOption("Automatic Tape Motor Control", "autotapemotor"));
|
||||||
|
return options;
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Selection appenders
|
||||||
|
void Configurable::append_quick_load_tape_selection(Configurable::SelectionSet &selection_set, bool selection) {
|
||||||
|
append_bool(selection_set, "quickload", selection);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Configurable::append_automatic_tape_motor_control_selection(SelectionSet &selection_set, bool selection) {
|
||||||
|
append_bool(selection_set, "autotapemotor", selection);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Configurable::append_display_selection(Configurable::SelectionSet &selection_set, Display selection) {
|
||||||
|
selection_set["display"] = std::unique_ptr<Configurable::Selection>(new Configurable::ListSelection((selection == Display::RGB) ? "rgb" : "composite"));
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Selection parsers
|
||||||
|
bool Configurable::get_quick_load_tape(const Configurable::SelectionSet &selections_by_option, bool &result) {
|
||||||
|
return get_bool(selections_by_option, "quickload", result);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Configurable::get_automatic_tape_motor_control_selection(const SelectionSet &selections_by_option, bool &result) {
|
||||||
|
return get_bool(selections_by_option, "autotapemotor", result);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Configurable::get_display(const Configurable::SelectionSet &selections_by_option, Configurable::Display &result) {
|
||||||
|
auto display = Configurable::selection<Configurable::ListSelection>(selections_by_option, "display");
|
||||||
|
if(display) {
|
||||||
|
if(display->value == "rgb") {
|
||||||
|
result = Configurable::Display::RGB;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if(display->value == "composite") {
|
||||||
|
result = Configurable::Display::Composite;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
76
Configurable/StandardOptions.hpp
Normal file
76
Configurable/StandardOptions.hpp
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
//
|
||||||
|
// StandardOptions.hpp
|
||||||
|
// Clock Signal
|
||||||
|
//
|
||||||
|
// Created by Thomas Harte on 20/11/2017.
|
||||||
|
// Copyright © 2017 Thomas Harte. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef StandardOptions_hpp
|
||||||
|
#define StandardOptions_hpp
|
||||||
|
|
||||||
|
#include "Configurable.hpp"
|
||||||
|
|
||||||
|
namespace Configurable {
|
||||||
|
|
||||||
|
enum StandardOptions {
|
||||||
|
DisplayRGBComposite = (1 << 0),
|
||||||
|
QuickLoadTape = (1 << 1),
|
||||||
|
AutomaticTapeMotorControl = (1 << 2)
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class Display {
|
||||||
|
RGB,
|
||||||
|
Composite
|
||||||
|
};
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@returns An option list comprised of the standard names for all the options indicated by @c mask.
|
||||||
|
*/
|
||||||
|
std::vector<std::unique_ptr<Option>> standard_options(StandardOptions mask);
|
||||||
|
|
||||||
|
/*!
|
||||||
|
Appends to @c selection_set a selection of @c selection for QuickLoadTape.
|
||||||
|
*/
|
||||||
|
void append_quick_load_tape_selection(SelectionSet &selection_set, bool selection);
|
||||||
|
|
||||||
|
/*!
|
||||||
|
Appends to @c selection_set a selection of @c selection for AutomaticTapeMotorControl.
|
||||||
|
*/
|
||||||
|
void append_automatic_tape_motor_control_selection(SelectionSet &selection_set, bool selection);
|
||||||
|
|
||||||
|
/*!
|
||||||
|
Appends to @c selection_set a selection of @c selection for DisplayRGBComposite.
|
||||||
|
*/
|
||||||
|
void append_display_selection(SelectionSet &selection_set, Display selection);
|
||||||
|
|
||||||
|
/*!
|
||||||
|
Attempts to discern a QuickLoadTape selection from @c selections_by_option.
|
||||||
|
|
||||||
|
@param selections_by_option The user selections.
|
||||||
|
@param result The location to which the selection will be stored if found.
|
||||||
|
@returns @c true if a selection is found; @c false otherwise.
|
||||||
|
*/
|
||||||
|
bool get_quick_load_tape(const SelectionSet &selections_by_option, bool &result);
|
||||||
|
|
||||||
|
/*!
|
||||||
|
Attempts to discern an AutomaticTapeMotorControl selection from @c selections_by_option.
|
||||||
|
|
||||||
|
@param selections_by_option The user selections.
|
||||||
|
@param result The location to which the selection will be stored if found.
|
||||||
|
@returns @c true if a selection is found; @c false otherwise.
|
||||||
|
*/
|
||||||
|
bool get_automatic_tape_motor_control_selection(const SelectionSet &selections_by_option, bool &result);
|
||||||
|
|
||||||
|
/*!
|
||||||
|
Attempts to discern a display RGB/composite selection from @c selections_by_option.
|
||||||
|
|
||||||
|
@param selections_by_option The user selections.
|
||||||
|
@param result The location to which the selection will be stored if found.
|
||||||
|
@returns @c true if a selection is found; @c false otherwise.
|
||||||
|
*/
|
||||||
|
bool get_display(const SelectionSet &selections_by_option, Display &result);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /* StandardOptions_hpp */
|
||||||
41
Inputs/Joystick.hpp
Normal file
41
Inputs/Joystick.hpp
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
//
|
||||||
|
// Joystick.hpp
|
||||||
|
// Clock Signal
|
||||||
|
//
|
||||||
|
// Created by Thomas Harte on 14/10/2017.
|
||||||
|
// Copyright © 2017 Thomas Harte. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef Joystick_hpp
|
||||||
|
#define Joystick_hpp
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace Inputs {
|
||||||
|
|
||||||
|
/*!
|
||||||
|
Provides an intermediate idealised model of a simple joystick, allowing a host
|
||||||
|
machine to toggle states, while an interested party either observes or polls.
|
||||||
|
*/
|
||||||
|
class Joystick {
|
||||||
|
public:
|
||||||
|
virtual ~Joystick() {}
|
||||||
|
|
||||||
|
enum class DigitalInput {
|
||||||
|
Up, Down, Left, Right, Fire
|
||||||
|
};
|
||||||
|
|
||||||
|
// Host interface.
|
||||||
|
virtual void set_digital_input(DigitalInput digital_input, bool is_active) = 0;
|
||||||
|
virtual void reset_all_inputs() {
|
||||||
|
set_digital_input(DigitalInput::Up, false);
|
||||||
|
set_digital_input(DigitalInput::Down, false);
|
||||||
|
set_digital_input(DigitalInput::Left, false);
|
||||||
|
set_digital_input(DigitalInput::Right, false);
|
||||||
|
set_digital_input(DigitalInput::Fire, false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /* Joystick_hpp */
|
||||||
38
Inputs/Keyboard.cpp
Normal file
38
Inputs/Keyboard.cpp
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
//
|
||||||
|
// Keyboard.cpp
|
||||||
|
// Clock Signal
|
||||||
|
//
|
||||||
|
// Created by Thomas Harte on 10/9/17.
|
||||||
|
// Copyright © 2017 Thomas Harte. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
#include "Keyboard.hpp"
|
||||||
|
|
||||||
|
using namespace Inputs;
|
||||||
|
|
||||||
|
Keyboard::Keyboard() {}
|
||||||
|
|
||||||
|
void Keyboard::set_key_pressed(Key key, bool is_pressed) {
|
||||||
|
std::size_t key_offset = static_cast<std::size_t>(key);
|
||||||
|
if(key_offset >= key_states_.size()) {
|
||||||
|
key_states_.resize(key_offset+1, false);
|
||||||
|
}
|
||||||
|
key_states_[key_offset] = is_pressed;
|
||||||
|
|
||||||
|
if(delegate_) delegate_->keyboard_did_change_key(this, key, is_pressed);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Keyboard::reset_all_keys() {
|
||||||
|
std::fill(key_states_.begin(), key_states_.end(), false);
|
||||||
|
if(delegate_) delegate_->reset_all_keys(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Keyboard::set_delegate(Delegate *delegate) {
|
||||||
|
delegate_ = delegate;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Keyboard::get_key_state(Key key) {
|
||||||
|
std::size_t key_offset = static_cast<std::size_t>(key);
|
||||||
|
if(key_offset >= key_states_.size()) return false;
|
||||||
|
return key_states_[key_offset];
|
||||||
|
}
|
||||||
61
Inputs/Keyboard.hpp
Normal file
61
Inputs/Keyboard.hpp
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
//
|
||||||
|
// Keyboard.hpp
|
||||||
|
// Clock Signal
|
||||||
|
//
|
||||||
|
// Created by Thomas Harte on 10/9/17.
|
||||||
|
// Copyright © 2017 Thomas Harte. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef Keyboard_hpp
|
||||||
|
#define Keyboard_hpp
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace Inputs {
|
||||||
|
|
||||||
|
/*!
|
||||||
|
Provides an intermediate idealised model of a modern-era computer keyboard
|
||||||
|
(so, heavily indebted to the current Windows and Mac layouts), allowing a host
|
||||||
|
machine to toggle states, while an interested party either observes or polls.
|
||||||
|
*/
|
||||||
|
class Keyboard {
|
||||||
|
public:
|
||||||
|
Keyboard();
|
||||||
|
|
||||||
|
enum class Key {
|
||||||
|
Escape, F1, F2, F3, F4, F5, F6, F7, F8, F9, F10, F11, F12, PrintScreen, ScrollLock, Pause,
|
||||||
|
BackTick, k1, k2, k3, k4, k5, k6, k7, k8, k9, k0, Hyphen, Equals, BackSpace,
|
||||||
|
Tab, Q, W, E, R, T, Y, U, I, O, P, OpenSquareBracket, CloseSquareBracket, BackSlash,
|
||||||
|
CapsLock, A, S, D, F, G, H, J, K, L, Semicolon, Quote, Hash, Enter,
|
||||||
|
LeftShift, Z, X, C, V, B, N, M, Comma, FullStop, ForwardSlash, RightShift,
|
||||||
|
LeftControl, LeftOption, LeftMeta, Space, RightMeta, RightOption, RightControl,
|
||||||
|
Left, Right, Up, Down,
|
||||||
|
Insert, Home, PageUp, Delete, End, PageDown,
|
||||||
|
NumLock, KeyPadSlash, KeyPadAsterisk, KeyPadDelete,
|
||||||
|
KeyPad7, KeyPad8, KeyPad9, KeyPadPlus,
|
||||||
|
KeyPad4, KeyPad5, KeyPad6, KeyPadMinus,
|
||||||
|
KeyPad1, KeyPad2, KeyPad3, KeyPadEnter,
|
||||||
|
KeyPad0, KeyPadDecimalPoint, KeyPadEquals,
|
||||||
|
Help
|
||||||
|
};
|
||||||
|
|
||||||
|
// Host interface.
|
||||||
|
virtual void set_key_pressed(Key key, bool is_pressed);
|
||||||
|
virtual void reset_all_keys();
|
||||||
|
|
||||||
|
// Delegate interface.
|
||||||
|
struct Delegate {
|
||||||
|
virtual void keyboard_did_change_key(Keyboard *keyboard, Key key, bool is_pressed) = 0;
|
||||||
|
virtual void reset_all_keys(Keyboard *keyboard) = 0;
|
||||||
|
};
|
||||||
|
void set_delegate(Delegate *delegate);
|
||||||
|
bool get_key_state(Key key);
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::vector<bool> key_states_;
|
||||||
|
Delegate *delegate_ = nullptr;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /* Keyboard_hpp */
|
||||||
@@ -8,7 +8,7 @@
|
|||||||
|
|
||||||
#include "AmstradCPC.hpp"
|
#include "AmstradCPC.hpp"
|
||||||
|
|
||||||
#include "CharacterMapper.hpp"
|
#include "Keyboard.hpp"
|
||||||
|
|
||||||
#include "../../Processors/Z80/Z80.hpp"
|
#include "../../Processors/Z80/Z80.hpp"
|
||||||
|
|
||||||
@@ -17,13 +17,26 @@
|
|||||||
#include "../../Components/8272/i8272.hpp"
|
#include "../../Components/8272/i8272.hpp"
|
||||||
#include "../../Components/AY38910/AY38910.hpp"
|
#include "../../Components/AY38910/AY38910.hpp"
|
||||||
|
|
||||||
#include "../MemoryFuzzer.hpp"
|
#include "../Utility/MemoryFuzzer.hpp"
|
||||||
#include "../Typer.hpp"
|
#include "../Utility/Typer.hpp"
|
||||||
|
|
||||||
#include "../../Storage/Tape/Tape.hpp"
|
#include "../../Storage/Tape/Tape.hpp"
|
||||||
|
|
||||||
|
#include "../../ClockReceiver/ForceInline.hpp"
|
||||||
|
#include "../../Outputs/Speaker/Implementation/LowpassSpeaker.hpp"
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
namespace AmstradCPC {
|
namespace AmstradCPC {
|
||||||
|
|
||||||
|
enum ROMType: int {
|
||||||
|
OS464 = 0, BASIC464,
|
||||||
|
OS664, BASIC664,
|
||||||
|
OS6128, BASIC6128,
|
||||||
|
AMSDOS
|
||||||
|
};
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
Models the CPC's interrupt timer. Inputs are vsync, hsync, interrupt acknowledge and reset, and its output
|
Models the CPC's interrupt timer. Inputs are vsync, hsync, interrupt acknowledge and reset, and its output
|
||||||
is simply yes or no on whether an interupt is currently requested. Internally it uses a counter with a period
|
is simply yes or no on whether an interupt is currently requested. Internally it uses a counter with a period
|
||||||
@@ -33,8 +46,6 @@ namespace AmstradCPC {
|
|||||||
*/
|
*/
|
||||||
class InterruptTimer {
|
class InterruptTimer {
|
||||||
public:
|
public:
|
||||||
InterruptTimer() : timer_(0), interrupt_request_(false) {}
|
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
Indicates that a new hsync pulse has been recognised. This should be
|
Indicates that a new hsync pulse has been recognised. This should be
|
||||||
supplied on the falling edge of the CRTC HSYNC signal, which is the
|
supplied on the falling edge of the CRTC HSYNC signal, which is the
|
||||||
@@ -76,7 +87,12 @@ class InterruptTimer {
|
|||||||
|
|
||||||
/// @returns @c true if an interrupt is currently requested; @c false otherwise.
|
/// @returns @c true if an interrupt is currently requested; @c false otherwise.
|
||||||
inline bool get_request() {
|
inline bool get_request() {
|
||||||
return interrupt_request_;
|
return last_interrupt_request_ = interrupt_request_;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Asks whether the interrupt status has changed.
|
||||||
|
inline bool request_has_changed() {
|
||||||
|
return last_interrupt_request_ != interrupt_request_;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Resets the timer.
|
/// Resets the timer.
|
||||||
@@ -86,9 +102,10 @@ class InterruptTimer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
int reset_counter_;
|
int reset_counter_ = 0;
|
||||||
bool interrupt_request_;
|
bool interrupt_request_ = false;
|
||||||
int timer_;
|
bool last_interrupt_request_ = false;
|
||||||
|
int timer_ = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
@@ -99,14 +116,8 @@ class InterruptTimer {
|
|||||||
class AYDeferrer {
|
class AYDeferrer {
|
||||||
public:
|
public:
|
||||||
/// Constructs a new AY instance and sets its clock rate.
|
/// Constructs a new AY instance and sets its clock rate.
|
||||||
inline void setup_output() {
|
AYDeferrer() : ay_(audio_queue_), speaker_(ay_) {
|
||||||
ay_.reset(new GI::AY38910::AY38910);
|
speaker_.set_input_rate(1000000);
|
||||||
ay_->set_input_rate(1000000);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Destructs the AY.
|
|
||||||
inline void close_output() {
|
|
||||||
ay_.reset();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Adds @c half_cycles half cycles to the amount of time that has passed.
|
/// Adds @c half_cycles half cycles to the amount of time that has passed.
|
||||||
@@ -116,26 +127,28 @@ class AYDeferrer {
|
|||||||
|
|
||||||
/// Enqueues an update-to-now into the AY's deferred queue.
|
/// Enqueues an update-to-now into the AY's deferred queue.
|
||||||
inline void update() {
|
inline void update() {
|
||||||
ay_->run_for(cycles_since_update_.divide_cycles(Cycles(4)));
|
speaker_.run_for(audio_queue_, cycles_since_update_.divide_cycles(Cycles(4)));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Issues a request to the AY to perform all processing up to the current time.
|
/// Issues a request to the AY to perform all processing up to the current time.
|
||||||
inline void flush() {
|
inline void flush() {
|
||||||
ay_->flush();
|
audio_queue_.perform();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// @returns the speaker the AY is using for output.
|
/// @returns the speaker the AY is using for output.
|
||||||
std::shared_ptr<Outputs::Speaker> get_speaker() {
|
Outputs::Speaker::Speaker *get_speaker() {
|
||||||
return ay_;
|
return &speaker_;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// @returns the AY itself.
|
/// @returns the AY itself.
|
||||||
GI::AY38910::AY38910 *ay() {
|
GI::AY38910::AY38910 &ay() {
|
||||||
return ay_.get();
|
return ay_;
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::shared_ptr<GI::AY38910::AY38910> ay_;
|
Concurrency::DeferringAsyncTaskQueue audio_queue_;
|
||||||
|
GI::AY38910::AY38910 ay_;
|
||||||
|
Outputs::Speaker::LowpassSpeaker<GI::AY38910::AY38910> speaker_;
|
||||||
HalfCycles cycles_since_update_;
|
HalfCycles cycles_since_update_;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -147,26 +160,17 @@ class AYDeferrer {
|
|||||||
class CRTCBusHandler {
|
class CRTCBusHandler {
|
||||||
public:
|
public:
|
||||||
CRTCBusHandler(uint8_t *ram, InterruptTimer &interrupt_timer) :
|
CRTCBusHandler(uint8_t *ram, InterruptTimer &interrupt_timer) :
|
||||||
cycles_(0),
|
|
||||||
was_enabled_(false),
|
|
||||||
was_sync_(false),
|
|
||||||
pixel_data_(nullptr),
|
|
||||||
pixel_pointer_(nullptr),
|
|
||||||
was_hsync_(false),
|
|
||||||
ram_(ram),
|
ram_(ram),
|
||||||
interrupt_timer_(interrupt_timer),
|
interrupt_timer_(interrupt_timer) {
|
||||||
pixel_divider_(1),
|
establish_palette_hits();
|
||||||
mode_(2),
|
build_mode_table();
|
||||||
next_mode_(2),
|
|
||||||
cycles_into_hsync_(0) {
|
|
||||||
build_mode_tables();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
The CRTC entry function; takes the current bus state and determines what output
|
The CRTC entry function for the main part of each clock cycle; takes the current
|
||||||
to produce based on the current palette and mode.
|
bus state and determines what output to produce based on the current palette and mode.
|
||||||
*/
|
*/
|
||||||
inline void perform_bus_cycle(const Motorola::CRTC::BusState &state) {
|
forceinline void perform_bus_cycle_phase1(const Motorola::CRTC::BusState &state) {
|
||||||
// The gate array waits 2µs to react to the CRTC's vsync signal, and then
|
// The gate array waits 2µs to react to the CRTC's vsync signal, and then
|
||||||
// caps output at 4µs. Since the clock rate is 1Mhz, that's 2 and 4 cycles,
|
// caps output at 4µs. Since the clock rate is 1Mhz, that's 2 and 4 cycles,
|
||||||
// respectively.
|
// respectively.
|
||||||
@@ -208,14 +212,14 @@ class CRTCBusHandler {
|
|||||||
// collect some more pixels if output is ongoing
|
// collect some more pixels if output is ongoing
|
||||||
if(!is_sync && state.display_enable) {
|
if(!is_sync && state.display_enable) {
|
||||||
if(!pixel_data_) {
|
if(!pixel_data_) {
|
||||||
pixel_pointer_ = pixel_data_ = crt_->allocate_write_area(320);
|
pixel_pointer_ = pixel_data_ = crt_->allocate_write_area(320, 8);
|
||||||
}
|
}
|
||||||
if(pixel_pointer_) {
|
if(pixel_pointer_) {
|
||||||
// the CPC shuffles output lines as:
|
// the CPC shuffles output lines as:
|
||||||
// MA13 MA12 RA2 RA1 RA0 MA9 MA8 MA7 MA6 MA5 MA4 MA3 MA2 MA1 MA0 CCLK
|
// MA13 MA12 RA2 RA1 RA0 MA9 MA8 MA7 MA6 MA5 MA4 MA3 MA2 MA1 MA0 CCLK
|
||||||
// ... so form the real access address.
|
// ... so form the real access address.
|
||||||
uint16_t address =
|
uint16_t address =
|
||||||
(uint16_t)(
|
static_cast<uint16_t>(
|
||||||
((state.refresh_address & 0x3ff) << 1) |
|
((state.refresh_address & 0x3ff) << 1) |
|
||||||
((state.row_address & 0x7) << 11) |
|
((state.row_address & 0x7) << 11) |
|
||||||
((state.refresh_address & 0x3000) << 2)
|
((state.refresh_address & 0x3000) << 2)
|
||||||
@@ -224,26 +228,26 @@ class CRTCBusHandler {
|
|||||||
// fetch two bytes and translate into pixels
|
// fetch two bytes and translate into pixels
|
||||||
switch(mode_) {
|
switch(mode_) {
|
||||||
case 0:
|
case 0:
|
||||||
((uint16_t *)pixel_pointer_)[0] = mode0_output_[ram_[address]];
|
reinterpret_cast<uint16_t *>(pixel_pointer_)[0] = mode0_output_[ram_[address]];
|
||||||
((uint16_t *)pixel_pointer_)[1] = mode0_output_[ram_[address+1]];
|
reinterpret_cast<uint16_t *>(pixel_pointer_)[1] = mode0_output_[ram_[address+1]];
|
||||||
pixel_pointer_ += 4;
|
pixel_pointer_ += 4;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 1:
|
case 1:
|
||||||
((uint32_t *)pixel_pointer_)[0] = mode1_output_[ram_[address]];
|
reinterpret_cast<uint32_t *>(pixel_pointer_)[0] = mode1_output_[ram_[address]];
|
||||||
((uint32_t *)pixel_pointer_)[1] = mode1_output_[ram_[address+1]];
|
reinterpret_cast<uint32_t *>(pixel_pointer_)[1] = mode1_output_[ram_[address+1]];
|
||||||
pixel_pointer_ += 8;
|
pixel_pointer_ += 8;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 2:
|
case 2:
|
||||||
((uint64_t *)pixel_pointer_)[0] = mode2_output_[ram_[address]];
|
reinterpret_cast<uint64_t *>(pixel_pointer_)[0] = mode2_output_[ram_[address]];
|
||||||
((uint64_t *)pixel_pointer_)[1] = mode2_output_[ram_[address+1]];
|
reinterpret_cast<uint64_t *>(pixel_pointer_)[1] = mode2_output_[ram_[address+1]];
|
||||||
pixel_pointer_ += 16;
|
pixel_pointer_ += 16;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 3:
|
case 3:
|
||||||
((uint16_t *)pixel_pointer_)[0] = mode3_output_[ram_[address]];
|
reinterpret_cast<uint16_t *>(pixel_pointer_)[0] = mode3_output_[ram_[address]];
|
||||||
((uint16_t *)pixel_pointer_)[1] = mode3_output_[ram_[address+1]];
|
reinterpret_cast<uint16_t *>(pixel_pointer_)[1] = mode3_output_[ram_[address+1]];
|
||||||
pixel_pointer_ += 4;
|
pixel_pointer_ += 4;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@@ -259,7 +263,13 @@ class CRTCBusHandler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
The CRTC entry function for phase 2 of each bus cycle — in which the next sync line state becomes
|
||||||
|
visible early. The CPC uses changes in sync to clock the interrupt timer.
|
||||||
|
*/
|
||||||
|
void perform_bus_cycle_phase2(const Motorola::CRTC::BusState &state) {
|
||||||
// check for a trailing CRTC hsync; if one occurred then that's the trigger potentially to change
|
// check for a trailing CRTC hsync; if one occurred then that's the trigger potentially to change
|
||||||
// modes, and should also be sent on to the interrupt timer
|
// modes, and should also be sent on to the interrupt timer
|
||||||
if(was_hsync_ && !state.hsync) {
|
if(was_hsync_ && !state.hsync) {
|
||||||
@@ -271,6 +281,7 @@ class CRTCBusHandler {
|
|||||||
case 1: pixel_divider_ = 2; break;
|
case 1: pixel_divider_ = 2; break;
|
||||||
case 2: pixel_divider_ = 1; break;
|
case 2: pixel_divider_ = 1; break;
|
||||||
}
|
}
|
||||||
|
build_mode_table();
|
||||||
}
|
}
|
||||||
|
|
||||||
interrupt_timer_.signal_hsync();
|
interrupt_timer_.signal_hsync();
|
||||||
@@ -296,7 +307,7 @@ class CRTCBusHandler {
|
|||||||
"return vec3(float((sample >> 4) & 3u), float((sample >> 2) & 3u), float(sample & 3u)) / 2.0;"
|
"return vec3(float((sample >> 4) & 3u), float((sample >> 2) & 3u), float(sample & 3u)) / 2.0;"
|
||||||
"}");
|
"}");
|
||||||
crt_->set_visible_area(Outputs::CRT::Rect(0.075f, 0.05f, 0.9f, 0.9f));
|
crt_->set_visible_area(Outputs::CRT::Rect(0.075f, 0.05f, 0.9f, 0.9f));
|
||||||
crt_->set_output_device(Outputs::CRT::Monitor);
|
crt_->set_output_device(Outputs::CRT::OutputDevice::Monitor);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Destructs the CRT.
|
/// Destructs the CRT.
|
||||||
@@ -305,8 +316,8 @@ class CRTCBusHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// @returns the CRT.
|
/// @returns the CRT.
|
||||||
std::shared_ptr<Outputs::CRT::CRT> get_crt() {
|
Outputs::CRT::CRT *get_crt() {
|
||||||
return crt_;
|
return crt_.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
@@ -317,11 +328,6 @@ class CRTCBusHandler {
|
|||||||
next_mode_ = mode;
|
next_mode_ = mode;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// @returns the current value of the CRTC's vertical sync output.
|
|
||||||
bool get_vsync() const {
|
|
||||||
return was_vsync_;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Palette management: selects a pen to modify.
|
/// Palette management: selects a pen to modify.
|
||||||
void select_pen(int pen) {
|
void select_pen(int pen) {
|
||||||
pen_ = pen;
|
pen_ = pen;
|
||||||
@@ -339,34 +345,70 @@ class CRTCBusHandler {
|
|||||||
border_ = mapped_palette_value(colour);
|
border_ = mapped_palette_value(colour);
|
||||||
} else {
|
} else {
|
||||||
palette_[pen_] = mapped_palette_value(colour);
|
palette_[pen_] = mapped_palette_value(colour);
|
||||||
// TODO: no need for a full regeneration, of every mode, every time
|
patch_mode_table(pen_);
|
||||||
build_mode_tables();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void output_border(unsigned int length) {
|
void output_border(unsigned int length) {
|
||||||
uint8_t *colour_pointer = (uint8_t *)crt_->allocate_write_area(1);
|
uint8_t *colour_pointer = static_cast<uint8_t *>(crt_->allocate_write_area(1));
|
||||||
if(colour_pointer) *colour_pointer = border_;
|
if(colour_pointer) *colour_pointer = border_;
|
||||||
crt_->output_level(length * 16);
|
crt_->output_level(length * 16);
|
||||||
}
|
}
|
||||||
|
|
||||||
void build_mode_tables() {
|
#define Mode0Colour0(c) ((c & 0x80) >> 7) | ((c & 0x20) >> 3) | ((c & 0x08) >> 2) | ((c & 0x02) << 2)
|
||||||
|
#define Mode0Colour1(c) ((c & 0x40) >> 6) | ((c & 0x10) >> 2) | ((c & 0x04) >> 1) | ((c & 0x01) << 3)
|
||||||
|
|
||||||
|
#define Mode1Colour0(c) ((c & 0x80) >> 7) | ((c & 0x08) >> 2)
|
||||||
|
#define Mode1Colour1(c) ((c & 0x40) >> 6) | ((c & 0x04) >> 1)
|
||||||
|
#define Mode1Colour2(c) ((c & 0x20) >> 5) | ((c & 0x02) >> 0)
|
||||||
|
#define Mode1Colour3(c) ((c & 0x10) >> 4) | ((c & 0x01) << 1)
|
||||||
|
|
||||||
|
#define Mode3Colour0(c) ((c & 0x80) >> 7) | ((c & 0x08) >> 2)
|
||||||
|
#define Mode3Colour1(c) ((c & 0x40) >> 6) | ((c & 0x04) >> 1)
|
||||||
|
|
||||||
|
void establish_palette_hits() {
|
||||||
|
for(int c = 0; c < 256; c++) {
|
||||||
|
mode0_palette_hits_[Mode0Colour0(c)].push_back(static_cast<uint8_t>(c));
|
||||||
|
mode0_palette_hits_[Mode0Colour1(c)].push_back(static_cast<uint8_t>(c));
|
||||||
|
|
||||||
|
mode1_palette_hits_[Mode1Colour0(c)].push_back(static_cast<uint8_t>(c));
|
||||||
|
mode1_palette_hits_[Mode1Colour1(c)].push_back(static_cast<uint8_t>(c));
|
||||||
|
mode1_palette_hits_[Mode1Colour2(c)].push_back(static_cast<uint8_t>(c));
|
||||||
|
mode1_palette_hits_[Mode1Colour3(c)].push_back(static_cast<uint8_t>(c));
|
||||||
|
|
||||||
|
mode3_palette_hits_[Mode3Colour0(c)].push_back(static_cast<uint8_t>(c));
|
||||||
|
mode3_palette_hits_[Mode3Colour1(c)].push_back(static_cast<uint8_t>(c));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void build_mode_table() {
|
||||||
|
switch(mode_) {
|
||||||
|
case 0:
|
||||||
|
// Mode 0: abcdefgh -> [gcea] [hdfb]
|
||||||
for(int c = 0; c < 256; c++) {
|
for(int c = 0; c < 256; c++) {
|
||||||
// prepare mode 0
|
// prepare mode 0
|
||||||
uint8_t *mode0_pixels = (uint8_t *)&mode0_output_[c];
|
uint8_t *mode0_pixels = reinterpret_cast<uint8_t *>(&mode0_output_[c]);
|
||||||
mode0_pixels[0] = palette_[((c & 0x80) >> 7) | ((c & 0x20) >> 3) | ((c & 0x08) >> 2) | ((c & 0x02) << 2)];
|
mode0_pixels[0] = palette_[Mode0Colour0(c)];
|
||||||
mode0_pixels[1] = palette_[((c & 0x40) >> 6) | ((c & 0x10) >> 2) | ((c & 0x04) >> 1) | ((c & 0x01) << 3)];
|
mode0_pixels[1] = palette_[Mode0Colour1(c)];
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 1:
|
||||||
|
for(int c = 0; c < 256; c++) {
|
||||||
// prepare mode 1
|
// prepare mode 1
|
||||||
uint8_t *mode1_pixels = (uint8_t *)&mode1_output_[c];
|
uint8_t *mode1_pixels = reinterpret_cast<uint8_t *>(&mode1_output_[c]);
|
||||||
mode1_pixels[0] = palette_[((c & 0x80) >> 7) | ((c & 0x08) >> 2)];
|
mode1_pixels[0] = palette_[Mode1Colour0(c)];
|
||||||
mode1_pixels[1] = palette_[((c & 0x40) >> 6) | ((c & 0x04) >> 1)];
|
mode1_pixels[1] = palette_[Mode1Colour1(c)];
|
||||||
mode1_pixels[2] = palette_[((c & 0x20) >> 5) | ((c & 0x02) >> 0)];
|
mode1_pixels[2] = palette_[Mode1Colour2(c)];
|
||||||
mode1_pixels[3] = palette_[((c & 0x10) >> 4) | ((c & 0x01) << 1)];
|
mode1_pixels[3] = palette_[Mode1Colour3(c)];
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 2:
|
||||||
|
for(int c = 0; c < 256; c++) {
|
||||||
// prepare mode 2
|
// prepare mode 2
|
||||||
uint8_t *mode2_pixels = (uint8_t *)&mode2_output_[c];
|
uint8_t *mode2_pixels = reinterpret_cast<uint8_t *>(&mode2_output_[c]);
|
||||||
mode2_pixels[0] = palette_[((c & 0x80) >> 7)];
|
mode2_pixels[0] = palette_[((c & 0x80) >> 7)];
|
||||||
mode2_pixels[1] = palette_[((c & 0x40) >> 6)];
|
mode2_pixels[1] = palette_[((c & 0x40) >> 6)];
|
||||||
mode2_pixels[2] = palette_[((c & 0x20) >> 5)];
|
mode2_pixels[2] = palette_[((c & 0x20) >> 5)];
|
||||||
@@ -375,14 +417,68 @@ class CRTCBusHandler {
|
|||||||
mode2_pixels[5] = palette_[((c & 0x04) >> 2)];
|
mode2_pixels[5] = palette_[((c & 0x04) >> 2)];
|
||||||
mode2_pixels[6] = palette_[((c & 0x03) >> 1)];
|
mode2_pixels[6] = palette_[((c & 0x03) >> 1)];
|
||||||
mode2_pixels[7] = palette_[((c & 0x01) >> 0)];
|
mode2_pixels[7] = palette_[((c & 0x01) >> 0)];
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 3:
|
||||||
|
for(int c = 0; c < 256; c++) {
|
||||||
// prepare mode 3
|
// prepare mode 3
|
||||||
uint8_t *mode3_pixels = (uint8_t *)&mode3_output_[c];
|
uint8_t *mode3_pixels = reinterpret_cast<uint8_t *>(&mode3_output_[c]);
|
||||||
mode3_pixels[0] = palette_[((c & 0x80) >> 7) | ((c & 0x08) >> 2)];
|
mode3_pixels[0] = palette_[Mode3Colour0(c)];
|
||||||
mode3_pixels[1] = palette_[((c & 0x40) >> 6) | ((c & 0x04) >> 1)];
|
mode3_pixels[1] = palette_[Mode3Colour1(c)];
|
||||||
|
}
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void patch_mode_table(int pen) {
|
||||||
|
switch(mode_) {
|
||||||
|
case 0: {
|
||||||
|
for(uint8_t c : mode0_palette_hits_[pen]) {
|
||||||
|
uint8_t *mode0_pixels = reinterpret_cast<uint8_t *>(&mode0_output_[c]);
|
||||||
|
mode0_pixels[0] = palette_[Mode0Colour0(c)];
|
||||||
|
mode0_pixels[1] = palette_[Mode0Colour1(c)];
|
||||||
|
}
|
||||||
|
} break;
|
||||||
|
case 1:
|
||||||
|
if(pen > 3) return;
|
||||||
|
for(uint8_t c : mode1_palette_hits_[pen]) {
|
||||||
|
uint8_t *mode1_pixels = reinterpret_cast<uint8_t *>(&mode1_output_[c]);
|
||||||
|
mode1_pixels[0] = palette_[Mode1Colour0(c)];
|
||||||
|
mode1_pixels[1] = palette_[Mode1Colour1(c)];
|
||||||
|
mode1_pixels[2] = palette_[Mode1Colour2(c)];
|
||||||
|
mode1_pixels[3] = palette_[Mode1Colour3(c)];
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
if(pen > 1) return;
|
||||||
|
// Whichever pen this is, there's only one table entry it doesn't touch, so just
|
||||||
|
// rebuild the whole thing.
|
||||||
|
build_mode_table();
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
if(pen > 3) return;
|
||||||
|
// Same argument applies here as to case 1, as the unused bits aren't masked out.
|
||||||
|
for(uint8_t c : mode3_palette_hits_[pen]) {
|
||||||
|
uint8_t *mode3_pixels = reinterpret_cast<uint8_t *>(&mode3_output_[c]);
|
||||||
|
mode3_pixels[0] = palette_[Mode3Colour0(c)];
|
||||||
|
mode3_pixels[1] = palette_[Mode3Colour1(c)];
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#undef Mode0Colour0
|
||||||
|
#undef Mode0Colour1
|
||||||
|
|
||||||
|
#undef Mode1Colour0
|
||||||
|
#undef Mode1Colour1
|
||||||
|
#undef Mode1Colour2
|
||||||
|
#undef Mode1Colour3
|
||||||
|
|
||||||
|
#undef Mode3Colour0
|
||||||
|
#undef Mode3Colour1
|
||||||
|
|
||||||
uint8_t mapped_palette_value(uint8_t colour) {
|
uint8_t mapped_palette_value(uint8_t colour) {
|
||||||
#define COL(r, g, b) (r << 4) | (g << 2) | b
|
#define COL(r, g, b) (r << 4) | (g << 2) | b
|
||||||
static const uint8_t mapping[32] = {
|
static const uint8_t mapping[32] = {
|
||||||
@@ -399,27 +495,31 @@ class CRTCBusHandler {
|
|||||||
return mapping[colour];
|
return mapping[colour];
|
||||||
}
|
}
|
||||||
|
|
||||||
unsigned int cycles_;
|
unsigned int cycles_ = 0;
|
||||||
|
|
||||||
bool was_enabled_, was_sync_, was_hsync_, was_vsync_;
|
bool was_enabled_ = false, was_sync_ = false, was_hsync_ = false, was_vsync_ = false;
|
||||||
int cycles_into_hsync_;
|
int cycles_into_hsync_ = 0;
|
||||||
|
|
||||||
std::shared_ptr<Outputs::CRT::CRT> crt_;
|
std::unique_ptr<Outputs::CRT::CRT> crt_;
|
||||||
uint8_t *pixel_data_, *pixel_pointer_;
|
uint8_t *pixel_data_ = nullptr, *pixel_pointer_ = nullptr;
|
||||||
|
|
||||||
uint8_t *ram_;
|
uint8_t *ram_ = nullptr;
|
||||||
|
|
||||||
int next_mode_, mode_;
|
int next_mode_ = 2, mode_ = 2;
|
||||||
|
|
||||||
unsigned int pixel_divider_;
|
unsigned int pixel_divider_ = 1;
|
||||||
uint16_t mode0_output_[256];
|
uint16_t mode0_output_[256];
|
||||||
uint32_t mode1_output_[256];
|
uint32_t mode1_output_[256];
|
||||||
uint64_t mode2_output_[256];
|
uint64_t mode2_output_[256];
|
||||||
uint16_t mode3_output_[256];
|
uint16_t mode3_output_[256];
|
||||||
|
|
||||||
int pen_;
|
std::vector<uint8_t> mode0_palette_hits_[16];
|
||||||
|
std::vector<uint8_t> mode1_palette_hits_[4];
|
||||||
|
std::vector<uint8_t> mode3_palette_hits_[4];
|
||||||
|
|
||||||
|
int pen_ = 0;
|
||||||
uint8_t palette_[16];
|
uint8_t palette_[16];
|
||||||
uint8_t border_;
|
uint8_t border_ = 0;
|
||||||
|
|
||||||
InterruptTimer &interrupt_timer_;
|
InterruptTimer &interrupt_timer_;
|
||||||
};
|
};
|
||||||
@@ -477,12 +577,25 @@ class KeyboardState: public GI::AY38910::PortHandler {
|
|||||||
class FDC: public Intel::i8272::i8272 {
|
class FDC: public Intel::i8272::i8272 {
|
||||||
private:
|
private:
|
||||||
Intel::i8272::BusHandler bus_handler_;
|
Intel::i8272::BusHandler bus_handler_;
|
||||||
|
std::shared_ptr<Storage::Disk::Drive> drive_;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
FDC() : i8272(bus_handler_, Cycles(8000000), 16, 300) {}
|
FDC() :
|
||||||
|
i8272(bus_handler_, Cycles(8000000)),
|
||||||
|
drive_(new Storage::Disk::Drive(8000000, 300, 1)) {
|
||||||
|
set_drive(drive_);
|
||||||
|
}
|
||||||
|
|
||||||
void set_motor_on(bool on) {
|
void set_motor_on(bool on) {
|
||||||
Intel::i8272::i8272::set_motor_on(on);
|
drive_->set_motor_on(on);
|
||||||
|
}
|
||||||
|
|
||||||
|
void select_drive(int c) {
|
||||||
|
// TODO: support more than one drive.
|
||||||
|
}
|
||||||
|
|
||||||
|
void set_disk(std::shared_ptr<Storage::Disk::Disk> disk, int drive) {
|
||||||
|
drive_->set_disk(disk);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -493,12 +606,12 @@ class i8255PortHandler : public Intel::i8255::PortHandler {
|
|||||||
public:
|
public:
|
||||||
i8255PortHandler(
|
i8255PortHandler(
|
||||||
KeyboardState &key_state,
|
KeyboardState &key_state,
|
||||||
const CRTCBusHandler &crtc_bus_handler,
|
const Motorola::CRTC::CRTC6845<CRTCBusHandler> &crtc,
|
||||||
AYDeferrer &ay,
|
AYDeferrer &ay,
|
||||||
Storage::Tape::BinaryTapePlayer &tape_player) :
|
Storage::Tape::BinaryTapePlayer &tape_player) :
|
||||||
key_state_(key_state),
|
|
||||||
crtc_bus_handler_(crtc_bus_handler),
|
|
||||||
ay_(ay),
|
ay_(ay),
|
||||||
|
crtc_(crtc),
|
||||||
|
key_state_(key_state),
|
||||||
tape_player_(tape_player) {}
|
tape_player_(tape_player) {}
|
||||||
|
|
||||||
/// The i8255 will call this to set a new output value of @c value for @c port.
|
/// The i8255 will call this to set a new output value of @c value for @c port.
|
||||||
@@ -507,7 +620,7 @@ class i8255PortHandler : public Intel::i8255::PortHandler {
|
|||||||
case 0:
|
case 0:
|
||||||
// Port A is connected to the AY's data bus.
|
// Port A is connected to the AY's data bus.
|
||||||
ay_.update();
|
ay_.update();
|
||||||
ay_.ay()->set_data_input(value);
|
ay_.ay().set_data_input(value);
|
||||||
break;
|
break;
|
||||||
case 1:
|
case 1:
|
||||||
// Port B is an input only. So output goes nowehere.
|
// Port B is an input only. So output goes nowehere.
|
||||||
@@ -523,7 +636,7 @@ class i8255PortHandler : public Intel::i8255::PortHandler {
|
|||||||
tape_player_.set_tape_output((value & 0x20) ? true : false);
|
tape_player_.set_tape_output((value & 0x20) ? true : false);
|
||||||
|
|
||||||
// Bits 6 and 7 set BDIR and BC1 for the AY.
|
// Bits 6 and 7 set BDIR and BC1 for the AY.
|
||||||
ay_.ay()->set_control_lines(
|
ay_.ay().set_control_lines(
|
||||||
(GI::AY38910::ControlLines)(
|
(GI::AY38910::ControlLines)(
|
||||||
((value & 0x80) ? GI::AY38910::BDIR : 0) |
|
((value & 0x80) ? GI::AY38910::BDIR : 0) |
|
||||||
((value & 0x40) ? GI::AY38910::BC1 : 0) |
|
((value & 0x40) ? GI::AY38910::BC1 : 0) |
|
||||||
@@ -536,9 +649,9 @@ class i8255PortHandler : public Intel::i8255::PortHandler {
|
|||||||
/// The i8255 will call this to obtain a new input for @c port.
|
/// The i8255 will call this to obtain a new input for @c port.
|
||||||
uint8_t get_value(int port) {
|
uint8_t get_value(int port) {
|
||||||
switch(port) {
|
switch(port) {
|
||||||
case 0: return ay_.ay()->get_data_output(); // Port A is wired to the AY
|
case 0: return ay_.ay().get_data_output(); // Port A is wired to the AY
|
||||||
case 1: return
|
case 1: return
|
||||||
(crtc_bus_handler_.get_vsync() ? 0x01 : 0x00) | // Bit 0 returns CRTC vsync.
|
(crtc_.get_bus_state().vsync ? 0x01 : 0x00) | // Bit 0 returns CRTC vsync.
|
||||||
(tape_player_.get_input() ? 0x80 : 0x00) | // Bit 7 returns cassette input.
|
(tape_player_.get_input() ? 0x80 : 0x00) | // Bit 7 returns cassette input.
|
||||||
0x7e; // Bits unimplemented:
|
0x7e; // Bits unimplemented:
|
||||||
//
|
//
|
||||||
@@ -552,8 +665,8 @@ class i8255PortHandler : public Intel::i8255::PortHandler {
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
AYDeferrer &ay_;
|
AYDeferrer &ay_;
|
||||||
|
const Motorola::CRTC::CRTC6845<CRTCBusHandler> &crtc_;
|
||||||
KeyboardState &key_state_;
|
KeyboardState &key_state_;
|
||||||
const CRTCBusHandler &crtc_bus_handler_;
|
|
||||||
Storage::Tape::BinaryTapePlayer &tape_player_;
|
Storage::Tape::BinaryTapePlayer &tape_player_;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -563,25 +676,36 @@ class i8255PortHandler : public Intel::i8255::PortHandler {
|
|||||||
class ConcreteMachine:
|
class ConcreteMachine:
|
||||||
public Utility::TypeRecipient,
|
public Utility::TypeRecipient,
|
||||||
public CPU::Z80::BusHandler,
|
public CPU::Z80::BusHandler,
|
||||||
|
public Sleeper::SleepObserver,
|
||||||
public Machine {
|
public Machine {
|
||||||
public:
|
public:
|
||||||
ConcreteMachine() :
|
ConcreteMachine() :
|
||||||
z80_(*this),
|
z80_(*this),
|
||||||
crtc_counter_(HalfCycles(4)), // This starts the CRTC exactly out of phase with the CPU's memory accesses
|
|
||||||
crtc_(Motorola::CRTC::HD6845S, crtc_bus_handler_),
|
|
||||||
crtc_bus_handler_(ram_, interrupt_timer_),
|
crtc_bus_handler_(ram_, interrupt_timer_),
|
||||||
|
crtc_(Motorola::CRTC::HD6845S, crtc_bus_handler_),
|
||||||
|
i8255_port_handler_(key_state_, crtc_, ay_, tape_player_),
|
||||||
i8255_(i8255_port_handler_),
|
i8255_(i8255_port_handler_),
|
||||||
i8255_port_handler_(key_state_, crtc_bus_handler_, ay_, tape_player_),
|
tape_player_(8000000),
|
||||||
tape_player_(8000000) {
|
crtc_counter_(HalfCycles(4)) // This starts the CRTC exactly out of phase with the CPU's memory accesses
|
||||||
|
{
|
||||||
// primary clock is 4Mhz
|
// primary clock is 4Mhz
|
||||||
set_clock_rate(4000000);
|
set_clock_rate(4000000);
|
||||||
|
|
||||||
// ensure memory starts in a random state
|
// ensure memory starts in a random state
|
||||||
Memory::Fuzz(ram_, sizeof(ram_));
|
Memory::Fuzz(ram_, sizeof(ram_));
|
||||||
|
|
||||||
|
// register this class as the sleep observer for the FDC and tape
|
||||||
|
fdc_.set_sleep_observer(this);
|
||||||
|
fdc_is_sleeping_ = fdc_.is_sleeping();
|
||||||
|
|
||||||
|
tape_player_.set_sleep_observer(this);
|
||||||
|
tape_player_is_sleeping_ = tape_player_.is_sleeping();
|
||||||
|
|
||||||
|
ay_.ay().set_port_handler(&key_state_);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The entry point for performing a partial Z80 machine cycle.
|
/// The entry point for performing a partial Z80 machine cycle.
|
||||||
inline HalfCycles perform_machine_cycle(const CPU::Z80::PartialMachineCycle &cycle) {
|
forceinline HalfCycles perform_machine_cycle(const CPU::Z80::PartialMachineCycle &cycle) {
|
||||||
// Amstrad CPC timing scheme: assert WAIT for three out of four cycles
|
// Amstrad CPC timing scheme: assert WAIT for three out of four cycles
|
||||||
clock_offset_ = (clock_offset_ + cycle.length) & HalfCycles(7);
|
clock_offset_ = (clock_offset_ + cycle.length) & HalfCycles(7);
|
||||||
z80_.set_wait_line(clock_offset_ >= HalfCycles(2));
|
z80_.set_wait_line(clock_offset_ >= HalfCycles(2));
|
||||||
@@ -593,17 +717,20 @@ class ConcreteMachine:
|
|||||||
crtc_counter_ += cycle.length;
|
crtc_counter_ += cycle.length;
|
||||||
Cycles crtc_cycles = crtc_counter_.divide_cycles(Cycles(4));
|
Cycles crtc_cycles = crtc_counter_.divide_cycles(Cycles(4));
|
||||||
if(crtc_cycles > Cycles(0)) crtc_.run_for(crtc_cycles);
|
if(crtc_cycles > Cycles(0)) crtc_.run_for(crtc_cycles);
|
||||||
z80_.set_interrupt_line(interrupt_timer_.get_request());
|
|
||||||
|
// Check whether that prompted a change in the interrupt line. If so then date
|
||||||
|
// it to whenever the cycle was triggered.
|
||||||
|
if(interrupt_timer_.request_has_changed()) z80_.set_interrupt_line(interrupt_timer_.get_request(), -crtc_counter_);
|
||||||
|
|
||||||
// TODO (in the player, not here): adapt it to accept an input clock rate and
|
// TODO (in the player, not here): adapt it to accept an input clock rate and
|
||||||
// run_for as HalfCycles
|
// run_for as HalfCycles
|
||||||
tape_player_.run_for(cycle.length.as_int());
|
if(!tape_player_is_sleeping_) tape_player_.run_for(cycle.length.as_int());
|
||||||
|
|
||||||
// Pump the AY
|
// Pump the AY
|
||||||
ay_.run_for(cycle.length);
|
ay_.run_for(cycle.length);
|
||||||
|
|
||||||
// Clock the FDC, if connected, using a lazy scale by two
|
// Clock the FDC, if connected, using a lazy scale by two
|
||||||
if(has_fdc_) fdc_.run_for(Cycles(cycle.length.as_int()));
|
if(has_fdc_ && !fdc_is_sleeping_) fdc_.run_for(Cycles(cycle.length.as_int()));
|
||||||
|
|
||||||
// Update typing activity
|
// Update typing activity
|
||||||
if(typer_) typer_->run_for(cycle.length);
|
if(typer_) typer_->run_for(cycle.length);
|
||||||
@@ -715,35 +842,32 @@ class ConcreteMachine:
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// A CRTMachine function; indicates that outputs should be created now.
|
/// A CRTMachine function; indicates that outputs should be created now.
|
||||||
void setup_output(float aspect_ratio) {
|
void setup_output(float aspect_ratio) override final {
|
||||||
crtc_bus_handler_.setup_output(aspect_ratio);
|
crtc_bus_handler_.setup_output(aspect_ratio);
|
||||||
ay_.setup_output();
|
|
||||||
ay_.ay()->set_port_handler(&key_state_);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A CRTMachine function; indicates that outputs should be destroyed now.
|
/// A CRTMachine function; indicates that outputs should be destroyed now.
|
||||||
void close_output() {
|
void close_output() override final {
|
||||||
crtc_bus_handler_.close_output();
|
crtc_bus_handler_.close_output();
|
||||||
ay_.close_output();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// @returns the CRT in use.
|
/// @returns the CRT in use.
|
||||||
std::shared_ptr<Outputs::CRT::CRT> get_crt() {
|
Outputs::CRT::CRT *get_crt() override final {
|
||||||
return crtc_bus_handler_.get_crt();
|
return crtc_bus_handler_.get_crt();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// @returns the speaker in use.
|
/// @returns the speaker in use.
|
||||||
std::shared_ptr<Outputs::Speaker> get_speaker() {
|
Outputs::Speaker::Speaker *get_speaker() override final {
|
||||||
return ay_.get_speaker();
|
return ay_.get_speaker();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Wires virtual-dispatched CRTMachine run_for requests to the static Z80 method.
|
/// Wires virtual-dispatched CRTMachine run_for requests to the static Z80 method.
|
||||||
void run_for(const Cycles cycles) {
|
void run_for(const Cycles cycles) override final {
|
||||||
z80_.run_for(cycles);
|
z80_.run_for(cycles);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The ConfigurationTarget entry point; should configure this meachine as described by @c target.
|
/// The ConfigurationTarget entry point; should configure this meachine as described by @c target.
|
||||||
void configure_as_target(const StaticAnalyser::Target &target) {
|
void configure_as_target(const StaticAnalyser::Target &target) override final {
|
||||||
switch(target.amstradcpc.model) {
|
switch(target.amstradcpc.model) {
|
||||||
case StaticAnalyser::AmstradCPCModel::CPC464:
|
case StaticAnalyser::AmstradCPCModel::CPC464:
|
||||||
rom_model_ = ROMType::OS464;
|
rom_model_ = ROMType::OS464;
|
||||||
@@ -776,55 +900,86 @@ class ConcreteMachine:
|
|||||||
read_pointers_[2] = write_pointers_[2];
|
read_pointers_[2] = write_pointers_[2];
|
||||||
read_pointers_[3] = roms_[upper_rom_].data();
|
read_pointers_[3] = roms_[upper_rom_].data();
|
||||||
|
|
||||||
|
// Type whatever is required.
|
||||||
|
if(target.loading_command.length()) {
|
||||||
|
type_string(target.loading_command);
|
||||||
|
}
|
||||||
|
|
||||||
|
insert_media(target.media);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool insert_media(const StaticAnalyser::Media &media) override final {
|
||||||
// If there are any tapes supplied, use the first of them.
|
// If there are any tapes supplied, use the first of them.
|
||||||
if(!target.tapes.empty()) {
|
if(!media.tapes.empty()) {
|
||||||
tape_player_.set_tape(target.tapes.front());
|
tape_player_.set_tape(media.tapes.front());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Insert up to four disks.
|
// Insert up to four disks.
|
||||||
int c = 0;
|
int c = 0;
|
||||||
for(auto &disk : target.disks) {
|
for(auto &disk : media.disks) {
|
||||||
fdc_.set_disk(disk, c);
|
fdc_.set_disk(disk, c);
|
||||||
c++;
|
c++;
|
||||||
if(c == 4) break;
|
if(c == 4) break;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Type whatever is required.
|
return !media.tapes.empty() || (!media.disks.empty() && has_fdc_);
|
||||||
if(target.loadingCommand.length()) {
|
|
||||||
set_typer_for_string(target.loadingCommand.c_str());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// See header; provides the system ROMs.
|
// Obtains the system ROMs.
|
||||||
void set_rom(ROMType type, std::vector<uint8_t> data) {
|
bool set_rom_fetcher(const std::function<std::vector<std::unique_ptr<std::vector<uint8_t>>>(const std::string &machine, const std::vector<std::string> &names)> &roms_with_names) override {
|
||||||
roms_[(int)type] = data;
|
auto roms = roms_with_names(
|
||||||
|
"AmstradCPC",
|
||||||
|
{
|
||||||
|
"os464.rom", "basic464.rom",
|
||||||
|
"os664.rom", "basic664.rom",
|
||||||
|
"os6128.rom", "basic6128.rom",
|
||||||
|
"amsdos.rom"
|
||||||
|
});
|
||||||
|
|
||||||
|
for(std::size_t index = 0; index < roms.size(); ++index) {
|
||||||
|
auto &data = roms[index];
|
||||||
|
if(!data) return false;
|
||||||
|
roms_[static_cast<int>(index)] = std::move(*data);
|
||||||
|
roms_[static_cast<int>(index)].resize(16384);
|
||||||
}
|
}
|
||||||
|
|
||||||
#pragma mark - Keyboard
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
void set_typer_for_string(const char *string) {
|
void set_component_is_sleeping(void *component, bool is_sleeping) override final {
|
||||||
|
fdc_is_sleeping_ = fdc_.is_sleeping();
|
||||||
|
tape_player_is_sleeping_ = tape_player_.is_sleeping();
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Keyboard
|
||||||
|
|
||||||
|
void type_string(const std::string &string) override final {
|
||||||
std::unique_ptr<CharacterMapper> mapper(new CharacterMapper());
|
std::unique_ptr<CharacterMapper> mapper(new CharacterMapper());
|
||||||
Utility::TypeRecipient::set_typer_for_string(string, std::move(mapper));
|
Utility::TypeRecipient::add_typer(string, std::move(mapper));
|
||||||
}
|
}
|
||||||
|
|
||||||
HalfCycles get_typer_delay() {
|
HalfCycles get_typer_delay() override final {
|
||||||
return Cycles(4000000); // Wait 1 second before typing.
|
return Cycles(4000000); // Wait 1 second before typing.
|
||||||
}
|
}
|
||||||
|
|
||||||
HalfCycles get_typer_frequency() {
|
HalfCycles get_typer_frequency() override final {
|
||||||
return Cycles(80000); // Type one character per frame.
|
return Cycles(160000); // Type one character per frame.
|
||||||
}
|
}
|
||||||
|
|
||||||
// See header; sets a key as either pressed or released.
|
// See header; sets a key as either pressed or released.
|
||||||
void set_key_state(uint16_t key, bool isPressed) {
|
void set_key_state(uint16_t key, bool isPressed) override final {
|
||||||
key_state_.set_is_pressed(isPressed, key >> 4, key & 7);
|
key_state_.set_is_pressed(isPressed, key >> 4, key & 7);
|
||||||
}
|
}
|
||||||
|
|
||||||
// See header; sets all keys to released.
|
// See header; sets all keys to released.
|
||||||
void clear_all_keys() {
|
void clear_all_keys() override final {
|
||||||
key_state_.clear_all_keys();
|
key_state_.clear_all_keys();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
KeyboardMapper &get_keyboard_mapper() override {
|
||||||
|
return keyboard_mapper_;
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
inline void write_to_gate_array(uint8_t value) {
|
inline void write_to_gate_array(uint8_t value) {
|
||||||
switch(value >> 6) {
|
switch(value >> 6) {
|
||||||
@@ -871,7 +1026,7 @@ class ConcreteMachine:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
CPU::Z80::Processor<ConcreteMachine> z80_;
|
CPU::Z80::Processor<ConcreteMachine, false, true> z80_;
|
||||||
|
|
||||||
CRTCBusHandler crtc_bus_handler_;
|
CRTCBusHandler crtc_bus_handler_;
|
||||||
Motorola::CRTC::CRTC6845<CRTCBusHandler> crtc_;
|
Motorola::CRTC::CRTC6845<CRTCBusHandler> crtc_;
|
||||||
@@ -893,7 +1048,8 @@ class ConcreteMachine:
|
|||||||
|
|
||||||
std::vector<uint8_t> roms_[7];
|
std::vector<uint8_t> roms_[7];
|
||||||
int rom_model_;
|
int rom_model_;
|
||||||
bool has_fdc_;
|
bool has_fdc_, fdc_is_sleeping_;
|
||||||
|
bool tape_player_is_sleeping_;
|
||||||
bool has_128k_;
|
bool has_128k_;
|
||||||
bool upper_rom_is_paged_;
|
bool upper_rom_is_paged_;
|
||||||
int upper_rom_;
|
int upper_rom_;
|
||||||
@@ -903,6 +1059,7 @@ class ConcreteMachine:
|
|||||||
uint8_t *write_pointers_[4];
|
uint8_t *write_pointers_[4];
|
||||||
|
|
||||||
KeyboardState key_state_;
|
KeyboardState key_state_;
|
||||||
|
AmstradCPC::KeyboardMapper keyboard_mapper_;
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,39 +13,10 @@
|
|||||||
#include "../CRTMachine.hpp"
|
#include "../CRTMachine.hpp"
|
||||||
#include "../KeyboardMachine.hpp"
|
#include "../KeyboardMachine.hpp"
|
||||||
|
|
||||||
#include <cstdint>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
namespace AmstradCPC {
|
namespace AmstradCPC {
|
||||||
|
|
||||||
enum ROMType: int {
|
|
||||||
OS464 = 0, BASIC464,
|
|
||||||
OS664, BASIC664,
|
|
||||||
OS6128, BASIC6128,
|
|
||||||
AMSDOS
|
|
||||||
};
|
|
||||||
|
|
||||||
enum Key: uint16_t {
|
|
||||||
#define Line(l, k1, k2, k3, k4, k5, k6, k7, k8) \
|
|
||||||
k1 = (l << 4) | 0x07, k2 = (l << 4) | 0x06, k3 = (l << 4) | 0x05, k4 = (l << 4) | 0x04,\
|
|
||||||
k5 = (l << 4) | 0x03, k6 = (l << 4) | 0x02, k7 = (l << 4) | 0x01, k8 = (l << 4) | 0x00,
|
|
||||||
|
|
||||||
Line(0, KeyFDot, KeyEnter, KeyF3, KeyF6, KeyF9, KeyDown, KeyRight, KeyUp)
|
|
||||||
Line(1, KeyF0, KeyF2, KeyF1, KeyF5, KeyF8, KeyF7, KeyCopy, KeyLeft)
|
|
||||||
Line(2, KeyControl, KeyBackSlash, KeyShift, KeyF4, KeyRightSquareBracket, KeyReturn, KeyLeftSquareBracket, KeyClear)
|
|
||||||
Line(3, KeyFullStop, KeyForwardSlash, KeyColon, KeySemicolon, KeyP, KeyAt, KeyMinus, KeyCaret)
|
|
||||||
Line(4, KeyComma, KeyM, KeyK, KeyL, KeyI, KeyO, Key9, Key0)
|
|
||||||
Line(5, KeySpace, KeyN, KeyJ, KeyH, KeyY, KeyU, Key7, Key8)
|
|
||||||
Line(6, KeyV, KeyB, KeyF, KeyG, KeyT, KeyR, Key5, Key6)
|
|
||||||
Line(7, KeyX, KeyC, KeyD, KeyS, KeyW, KeyE, Key3, Key4)
|
|
||||||
Line(8, KeyZ, KeyCapsLock, KeyA, KeyTab, KeyQ, KeyEscape, Key2, Key1)
|
|
||||||
Line(9, KeyDelete, KeyJoy1Fire3, KeyJoy2Fire2, KeyJoy1Fire1, KeyJoy1Right, KeyJoy1Left, KeyJoy1Down, KeyJoy1Up)
|
|
||||||
|
|
||||||
#undef Line
|
|
||||||
};
|
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
Models an Amstrad CPC, a CRT-outputting machine with a keyboard that can accept configuration targets.
|
Models an Amstrad CPC.
|
||||||
*/
|
*/
|
||||||
class Machine:
|
class Machine:
|
||||||
public CRTMachine::Machine,
|
public CRTMachine::Machine,
|
||||||
@@ -54,11 +25,8 @@ class Machine:
|
|||||||
public:
|
public:
|
||||||
virtual ~Machine();
|
virtual ~Machine();
|
||||||
|
|
||||||
/// Creates an returns an Amstrad CPC on the heap.
|
/// Creates and returns an Amstrad CPC.
|
||||||
static Machine *AmstradCPC();
|
static Machine *AmstradCPC();
|
||||||
|
|
||||||
/// Sets the contents of rom @c type to @c data. Assumed to be a setup step; has no effect once a machine is running.
|
|
||||||
virtual void set_rom(ROMType type, std::vector<uint8_t> data) = 0;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,23 +0,0 @@
|
|||||||
//
|
|
||||||
// CharacterMapper.hpp
|
|
||||||
// Clock Signal
|
|
||||||
//
|
|
||||||
// Created by Thomas Harte on 11/08/2017.
|
|
||||||
// Copyright © 2017 Thomas Harte. All rights reserved.
|
|
||||||
//
|
|
||||||
|
|
||||||
#ifndef Machines_AmstradCPC_CharacterMapper_hpp
|
|
||||||
#define Machines_AmstradCPC_CharacterMapper_hpp
|
|
||||||
|
|
||||||
#include "../Typer.hpp"
|
|
||||||
|
|
||||||
namespace AmstradCPC {
|
|
||||||
|
|
||||||
class CharacterMapper: public ::Utility::CharacterMapper {
|
|
||||||
public:
|
|
||||||
uint16_t *sequence_for_character(char character);
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif /* CharacterMapper_hpp */
|
|
||||||
@@ -1,20 +1,83 @@
|
|||||||
//
|
//
|
||||||
// CharacterMapper.cpp
|
// Keyboard.cpp
|
||||||
// Clock Signal
|
// Clock Signal
|
||||||
//
|
//
|
||||||
// Created by Thomas Harte on 11/08/2017.
|
// Created by Thomas Harte on 10/10/2017.
|
||||||
// Copyright © 2017 Thomas Harte. All rights reserved.
|
// Copyright © 2017 Thomas Harte. All rights reserved.
|
||||||
//
|
//
|
||||||
|
|
||||||
#include "CharacterMapper.hpp"
|
#include "Keyboard.hpp"
|
||||||
#include "AmstradCPC.hpp"
|
|
||||||
|
|
||||||
using namespace AmstradCPC;
|
using namespace AmstradCPC;
|
||||||
|
|
||||||
|
uint16_t KeyboardMapper::mapped_key_for_key(Inputs::Keyboard::Key key) {
|
||||||
|
#define BIND(source, dest) case Inputs::Keyboard::Key::source: return dest
|
||||||
|
switch(key) {
|
||||||
|
default: return KeyCopy;
|
||||||
|
|
||||||
|
BIND(k0, Key0); BIND(k1, Key1); BIND(k2, Key2); BIND(k3, Key3); BIND(k4, Key4);
|
||||||
|
BIND(k5, Key5); BIND(k6, Key6); BIND(k7, Key7); BIND(k8, Key8); BIND(k9, Key9);
|
||||||
|
BIND(Q, KeyQ); BIND(W, KeyW); BIND(E, KeyE); BIND(R, KeyR); BIND(T, KeyT);
|
||||||
|
BIND(Y, KeyY); BIND(U, KeyU); BIND(I, KeyI); BIND(O, KeyO); BIND(P, KeyP);
|
||||||
|
BIND(A, KeyA); BIND(S, KeyS); BIND(D, KeyD); BIND(F, KeyF); BIND(G, KeyG);
|
||||||
|
BIND(H, KeyH); BIND(J, KeyJ); BIND(K, KeyK); BIND(L, KeyL);
|
||||||
|
BIND(Z, KeyZ); BIND(X, KeyX); BIND(C, KeyC); BIND(V, KeyV);
|
||||||
|
BIND(B, KeyB); BIND(N, KeyN); BIND(M, KeyM);
|
||||||
|
|
||||||
|
BIND(Escape, KeyEscape);
|
||||||
|
BIND(F1, KeyF1); BIND(F2, KeyF2); BIND(F3, KeyF3); BIND(F4, KeyF4); BIND(F5, KeyF5);
|
||||||
|
BIND(F6, KeyF6); BIND(F7, KeyF7); BIND(F8, KeyF8); BIND(F9, KeyF9); BIND(F10, KeyF0);
|
||||||
|
|
||||||
|
BIND(F11, KeyRightSquareBracket);
|
||||||
|
BIND(F12, KeyClear);
|
||||||
|
|
||||||
|
BIND(Hyphen, KeyMinus); BIND(Equals, KeyCaret); BIND(BackSpace, KeyDelete);
|
||||||
|
BIND(Tab, KeyTab);
|
||||||
|
|
||||||
|
BIND(OpenSquareBracket, KeyAt);
|
||||||
|
BIND(CloseSquareBracket, KeyLeftSquareBracket);
|
||||||
|
BIND(BackSlash, KeyBackSlash);
|
||||||
|
|
||||||
|
BIND(CapsLock, KeyCapsLock);
|
||||||
|
BIND(Semicolon, KeyColon);
|
||||||
|
BIND(Quote, KeySemicolon);
|
||||||
|
BIND(Hash, KeyRightSquareBracket);
|
||||||
|
BIND(Enter, KeyReturn);
|
||||||
|
|
||||||
|
BIND(LeftShift, KeyShift);
|
||||||
|
BIND(Comma, KeyComma);
|
||||||
|
BIND(FullStop, KeyFullStop);
|
||||||
|
BIND(ForwardSlash, KeyForwardSlash);
|
||||||
|
BIND(RightShift, KeyShift);
|
||||||
|
|
||||||
|
BIND(LeftControl, KeyControl); BIND(LeftOption, KeyControl); BIND(LeftMeta, KeyControl);
|
||||||
|
BIND(Space, KeySpace);
|
||||||
|
BIND(RightMeta, KeyControl); BIND(RightOption, KeyControl); BIND(RightControl, KeyControl);
|
||||||
|
|
||||||
|
BIND(Left, KeyLeft); BIND(Right, KeyRight);
|
||||||
|
BIND(Up, KeyUp); BIND(Down, KeyDown);
|
||||||
|
|
||||||
|
BIND(KeyPad0, KeyF0);
|
||||||
|
BIND(KeyPad1, KeyF1); BIND(KeyPad2, KeyF2); BIND(KeyPad3, KeyF3);
|
||||||
|
BIND(KeyPad4, KeyF4); BIND(KeyPad5, KeyF5); BIND(KeyPad6, KeyF6);
|
||||||
|
BIND(KeyPad7, KeyF7); BIND(KeyPad8, KeyF8); BIND(KeyPad9, KeyF9);
|
||||||
|
BIND(KeyPadPlus, KeySemicolon);
|
||||||
|
BIND(KeyPadMinus, KeyMinus);
|
||||||
|
|
||||||
|
BIND(KeyPadEnter, KeyEnter);
|
||||||
|
BIND(KeyPadDecimalPoint, KeyFullStop);
|
||||||
|
BIND(KeyPadEquals, KeyMinus);
|
||||||
|
BIND(KeyPadSlash, KeyForwardSlash);
|
||||||
|
BIND(KeyPadAsterisk, KeyColon);
|
||||||
|
BIND(KeyPadDelete, KeyDelete);
|
||||||
|
}
|
||||||
|
#undef BIND
|
||||||
|
}
|
||||||
|
|
||||||
uint16_t *CharacterMapper::sequence_for_character(char character) {
|
uint16_t *CharacterMapper::sequence_for_character(char character) {
|
||||||
#define KEYS(...) {__VA_ARGS__, EndSequence}
|
#define KEYS(...) {__VA_ARGS__, KeyboardMachine::Machine::KeyEndSequence}
|
||||||
#define SHIFT(...) {KeyShift, __VA_ARGS__, EndSequence}
|
#define SHIFT(...) {KeyShift, __VA_ARGS__, KeyboardMachine::Machine::KeyEndSequence}
|
||||||
#define X {NotMapped}
|
#define X {KeyboardMachine::Machine::KeyNotMapped}
|
||||||
static KeySequence key_sequences[] = {
|
static KeySequence key_sequences[] = {
|
||||||
/* NUL */ X, /* SOH */ X,
|
/* NUL */ X, /* SOH */ X,
|
||||||
/* STX */ X, /* ETX */ X,
|
/* STX */ X, /* ETX */ X,
|
||||||
46
Machines/AmstradCPC/Keyboard.hpp
Normal file
46
Machines/AmstradCPC/Keyboard.hpp
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
//
|
||||||
|
// Keyboard.hpp
|
||||||
|
// Clock Signal
|
||||||
|
//
|
||||||
|
// Created by Thomas Harte on 10/10/2017.
|
||||||
|
// Copyright © 2017 Thomas Harte. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef Machines_AmstradCPC_Keyboard_hpp
|
||||||
|
#define Machines_AmstradCPC_Keyboard_hpp
|
||||||
|
|
||||||
|
#include "../KeyboardMachine.hpp"
|
||||||
|
#include "../Utility/Typer.hpp"
|
||||||
|
|
||||||
|
namespace AmstradCPC {
|
||||||
|
|
||||||
|
enum Key: uint16_t {
|
||||||
|
#define Line(l, k1, k2, k3, k4, k5, k6, k7, k8) \
|
||||||
|
k1 = (l << 4) | 0x07, k2 = (l << 4) | 0x06, k3 = (l << 4) | 0x05, k4 = (l << 4) | 0x04,\
|
||||||
|
k5 = (l << 4) | 0x03, k6 = (l << 4) | 0x02, k7 = (l << 4) | 0x01, k8 = (l << 4) | 0x00,
|
||||||
|
|
||||||
|
Line(0, KeyFDot, KeyEnter, KeyF3, KeyF6, KeyF9, KeyDown, KeyRight, KeyUp)
|
||||||
|
Line(1, KeyF0, KeyF2, KeyF1, KeyF5, KeyF8, KeyF7, KeyCopy, KeyLeft)
|
||||||
|
Line(2, KeyControl, KeyBackSlash, KeyShift, KeyF4, KeyRightSquareBracket, KeyReturn, KeyLeftSquareBracket, KeyClear)
|
||||||
|
Line(3, KeyFullStop, KeyForwardSlash, KeyColon, KeySemicolon, KeyP, KeyAt, KeyMinus, KeyCaret)
|
||||||
|
Line(4, KeyComma, KeyM, KeyK, KeyL, KeyI, KeyO, Key9, Key0)
|
||||||
|
Line(5, KeySpace, KeyN, KeyJ, KeyH, KeyY, KeyU, Key7, Key8)
|
||||||
|
Line(6, KeyV, KeyB, KeyF, KeyG, KeyT, KeyR, Key5, Key6)
|
||||||
|
Line(7, KeyX, KeyC, KeyD, KeyS, KeyW, KeyE, Key3, Key4)
|
||||||
|
Line(8, KeyZ, KeyCapsLock, KeyA, KeyTab, KeyQ, KeyEscape, Key2, Key1)
|
||||||
|
Line(9, KeyDelete, KeyJoy1Fire3, KeyJoy2Fire2, KeyJoy1Fire1, KeyJoy1Right, KeyJoy1Left, KeyJoy1Down, KeyJoy1Up)
|
||||||
|
|
||||||
|
#undef Line
|
||||||
|
};
|
||||||
|
|
||||||
|
struct KeyboardMapper: public KeyboardMachine::Machine::KeyboardMapper {
|
||||||
|
uint16_t mapped_key_for_key(Inputs::Keyboard::Key key);
|
||||||
|
};
|
||||||
|
|
||||||
|
struct CharacterMapper: public ::Utility::CharacterMapper {
|
||||||
|
uint16_t *sequence_for_character(char character);
|
||||||
|
};
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif /* KeyboardMapper_hpp */
|
||||||
@@ -7,70 +7,120 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
#include "Atari2600.hpp"
|
#include "Atari2600.hpp"
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <stdio.h>
|
#include <cstdio>
|
||||||
|
|
||||||
#include "Cartridges/CartridgeAtari8k.hpp"
|
#include "Cartridges/Atari8k.hpp"
|
||||||
#include "Cartridges/CartridgeAtari16k.hpp"
|
#include "Cartridges/Atari16k.hpp"
|
||||||
#include "Cartridges/CartridgeAtari32k.hpp"
|
#include "Cartridges/Atari32k.hpp"
|
||||||
#include "Cartridges/CartridgeActivisionStack.hpp"
|
#include "Cartridges/ActivisionStack.hpp"
|
||||||
#include "Cartridges/CartridgeCBSRAMPlus.hpp"
|
#include "Cartridges/CBSRAMPlus.hpp"
|
||||||
#include "Cartridges/CartridgeCommaVid.hpp"
|
#include "Cartridges/CommaVid.hpp"
|
||||||
#include "Cartridges/CartridgeMegaBoy.hpp"
|
#include "Cartridges/MegaBoy.hpp"
|
||||||
#include "Cartridges/CartridgeMNetwork.hpp"
|
#include "Cartridges/MNetwork.hpp"
|
||||||
#include "Cartridges/CartridgeParkerBros.hpp"
|
#include "Cartridges/ParkerBros.hpp"
|
||||||
#include "Cartridges/CartridgePitfall2.hpp"
|
#include "Cartridges/Pitfall2.hpp"
|
||||||
#include "Cartridges/CartridgeTigervision.hpp"
|
#include "Cartridges/Tigervision.hpp"
|
||||||
#include "Cartridges/CartridgeUnpaged.hpp"
|
#include "Cartridges/Unpaged.hpp"
|
||||||
|
|
||||||
using namespace Atari2600;
|
|
||||||
namespace {
|
namespace {
|
||||||
static const double NTSC_clock_rate = 1194720;
|
static const double NTSC_clock_rate = 1194720;
|
||||||
static const double PAL_clock_rate = 1182298;
|
static const double PAL_clock_rate = 1182298;
|
||||||
}
|
}
|
||||||
|
|
||||||
Machine::Machine() :
|
namespace Atari2600 {
|
||||||
|
|
||||||
|
class Joystick: public Inputs::Joystick {
|
||||||
|
public:
|
||||||
|
Joystick(Bus *bus, std::size_t shift, std::size_t fire_tia_input) :
|
||||||
|
bus_(bus), shift_(shift), fire_tia_input_(fire_tia_input) {}
|
||||||
|
|
||||||
|
void set_digital_input(DigitalInput digital_input, bool is_active) {
|
||||||
|
switch(digital_input) {
|
||||||
|
case DigitalInput::Up: bus_->mos6532_.update_port_input(0, 0x10 >> shift_, is_active); break;
|
||||||
|
case DigitalInput::Down: bus_->mos6532_.update_port_input(0, 0x20 >> shift_, is_active); break;
|
||||||
|
case DigitalInput::Left: bus_->mos6532_.update_port_input(0, 0x40 >> shift_, is_active); break;
|
||||||
|
case DigitalInput::Right: bus_->mos6532_.update_port_input(0, 0x80 >> shift_, is_active); break;
|
||||||
|
|
||||||
|
// TODO: latching
|
||||||
|
case DigitalInput::Fire:
|
||||||
|
if(is_active)
|
||||||
|
bus_->tia_input_value_[fire_tia_input_] &= ~0x80;
|
||||||
|
else
|
||||||
|
bus_->tia_input_value_[fire_tia_input_] |= 0x80;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
Bus *bus_;
|
||||||
|
std::size_t shift_, fire_tia_input_;
|
||||||
|
};
|
||||||
|
|
||||||
|
class ConcreteMachine:
|
||||||
|
public Machine,
|
||||||
|
public Outputs::CRT::Delegate {
|
||||||
|
public:
|
||||||
|
ConcreteMachine() :
|
||||||
frame_record_pointer_(0),
|
frame_record_pointer_(0),
|
||||||
is_ntsc_(true) {
|
is_ntsc_(true) {
|
||||||
set_clock_rate(NTSC_clock_rate);
|
set_clock_rate(NTSC_clock_rate);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Machine::setup_output(float aspect_ratio) {
|
~ConcreteMachine() {
|
||||||
bus_->tia_.reset(new TIA);
|
|
||||||
bus_->speaker_.reset(new Speaker);
|
|
||||||
bus_->speaker_->set_input_rate((float)(get_clock_rate() / (double)CPUTicksPerAudioTick));
|
|
||||||
bus_->tia_->get_crt()->set_delegate(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Machine::close_output() {
|
|
||||||
bus_.reset();
|
|
||||||
}
|
|
||||||
|
|
||||||
Machine::~Machine() {
|
|
||||||
close_output();
|
close_output();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Machine::set_digital_input(Atari2600DigitalInput input, bool state) {
|
void configure_as_target(const StaticAnalyser::Target &target) override {
|
||||||
switch (input) {
|
const std::vector<uint8_t> &rom = target.media.cartridges.front()->get_segments().front().data;
|
||||||
case Atari2600DigitalInputJoy1Up: bus_->mos6532_.update_port_input(0, 0x10, state); break;
|
switch(target.atari.paging_model) {
|
||||||
case Atari2600DigitalInputJoy1Down: bus_->mos6532_.update_port_input(0, 0x20, state); break;
|
case StaticAnalyser::Atari2600PagingModel::ActivisionStack: bus_.reset(new Cartridge::Cartridge<Cartridge::ActivisionStack>(rom)); break;
|
||||||
case Atari2600DigitalInputJoy1Left: bus_->mos6532_.update_port_input(0, 0x40, state); break;
|
case StaticAnalyser::Atari2600PagingModel::CBSRamPlus: bus_.reset(new Cartridge::Cartridge<Cartridge::CBSRAMPlus>(rom)); break;
|
||||||
case Atari2600DigitalInputJoy1Right: bus_->mos6532_.update_port_input(0, 0x80, state); break;
|
case StaticAnalyser::Atari2600PagingModel::CommaVid: bus_.reset(new Cartridge::Cartridge<Cartridge::CommaVid>(rom)); break;
|
||||||
|
case StaticAnalyser::Atari2600PagingModel::MegaBoy: bus_.reset(new Cartridge::Cartridge<Cartridge::MegaBoy>(rom)); break;
|
||||||
|
case StaticAnalyser::Atari2600PagingModel::MNetwork: bus_.reset(new Cartridge::Cartridge<Cartridge::MNetwork>(rom)); break;
|
||||||
|
case StaticAnalyser::Atari2600PagingModel::None: bus_.reset(new Cartridge::Cartridge<Cartridge::Unpaged>(rom)); break;
|
||||||
|
case StaticAnalyser::Atari2600PagingModel::ParkerBros: bus_.reset(new Cartridge::Cartridge<Cartridge::ParkerBros>(rom)); break;
|
||||||
|
case StaticAnalyser::Atari2600PagingModel::Pitfall2: bus_.reset(new Cartridge::Cartridge<Cartridge::Pitfall2>(rom)); break;
|
||||||
|
case StaticAnalyser::Atari2600PagingModel::Tigervision: bus_.reset(new Cartridge::Cartridge<Cartridge::Tigervision>(rom)); break;
|
||||||
|
|
||||||
case Atari2600DigitalInputJoy2Up: bus_->mos6532_.update_port_input(0, 0x01, state); break;
|
case StaticAnalyser::Atari2600PagingModel::Atari8k:
|
||||||
case Atari2600DigitalInputJoy2Down: bus_->mos6532_.update_port_input(0, 0x02, state); break;
|
if(target.atari.uses_superchip) {
|
||||||
case Atari2600DigitalInputJoy2Left: bus_->mos6532_.update_port_input(0, 0x04, state); break;
|
bus_.reset(new Cartridge::Cartridge<Cartridge::Atari8kSuperChip>(rom));
|
||||||
case Atari2600DigitalInputJoy2Right: bus_->mos6532_.update_port_input(0, 0x08, state); break;
|
} else {
|
||||||
|
bus_.reset(new Cartridge::Cartridge<Cartridge::Atari8k>(rom));
|
||||||
// TODO: latching
|
|
||||||
case Atari2600DigitalInputJoy1Fire: if(state) bus_->tia_input_value_[0] &= ~0x80; else bus_->tia_input_value_[0] |= 0x80; break;
|
|
||||||
case Atari2600DigitalInputJoy2Fire: if(state) bus_->tia_input_value_[1] &= ~0x80; else bus_->tia_input_value_[1] |= 0x80; break;
|
|
||||||
|
|
||||||
default: break;
|
|
||||||
}
|
}
|
||||||
|
break;
|
||||||
|
case StaticAnalyser::Atari2600PagingModel::Atari16k:
|
||||||
|
if(target.atari.uses_superchip) {
|
||||||
|
bus_.reset(new Cartridge::Cartridge<Cartridge::Atari16kSuperChip>(rom));
|
||||||
|
} else {
|
||||||
|
bus_.reset(new Cartridge::Cartridge<Cartridge::Atari16k>(rom));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case StaticAnalyser::Atari2600PagingModel::Atari32k:
|
||||||
|
if(target.atari.uses_superchip) {
|
||||||
|
bus_.reset(new Cartridge::Cartridge<Cartridge::Atari32kSuperChip>(rom));
|
||||||
|
} else {
|
||||||
|
bus_.reset(new Cartridge::Cartridge<Cartridge::Atari32k>(rom));
|
||||||
|
}
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Machine::set_switch_is_enabled(Atari2600Switch input, bool state) {
|
joysticks_.emplace_back(new Joystick(bus_.get(), 0, 0));
|
||||||
|
joysticks_.emplace_back(new Joystick(bus_.get(), 4, 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
bool insert_media(const StaticAnalyser::Media &media) override {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::unique_ptr<Inputs::Joystick>> &get_joysticks() override {
|
||||||
|
return joysticks_;
|
||||||
|
}
|
||||||
|
|
||||||
|
void set_switch_is_enabled(Atari2600Switch input, bool state) override {
|
||||||
switch(input) {
|
switch(input) {
|
||||||
case Atari2600SwitchReset: bus_->mos6532_.update_port_input(1, 0x01, state); break;
|
case Atari2600SwitchReset: bus_->mos6532_.update_port_input(1, 0x01, state); break;
|
||||||
case Atari2600SwitchSelect: bus_->mos6532_.update_port_input(1, 0x02, state); break;
|
case Atari2600SwitchSelect: bus_->mos6532_.update_port_input(1, 0x02, state); break;
|
||||||
@@ -80,47 +130,36 @@ void Machine::set_switch_is_enabled(Atari2600Switch input, bool state) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Machine::configure_as_target(const StaticAnalyser::Target &target) {
|
void set_reset_switch(bool state) override {
|
||||||
const std::vector<uint8_t> &rom = target.cartridges.front()->get_segments().front().data;
|
bus_->set_reset_line(state);
|
||||||
switch(target.atari.paging_model) {
|
|
||||||
case StaticAnalyser::Atari2600PagingModel::ActivisionStack: bus_.reset(new CartridgeActivisionStack(rom)); break;
|
|
||||||
case StaticAnalyser::Atari2600PagingModel::CBSRamPlus: bus_.reset(new CartridgeCBSRAMPlus(rom)); break;
|
|
||||||
case StaticAnalyser::Atari2600PagingModel::CommaVid: bus_.reset(new CartridgeCommaVid(rom)); break;
|
|
||||||
case StaticAnalyser::Atari2600PagingModel::MegaBoy: bus_.reset(new CartridgeMegaBoy(rom)); break;
|
|
||||||
case StaticAnalyser::Atari2600PagingModel::MNetwork: bus_.reset(new CartridgeMNetwork(rom)); break;
|
|
||||||
case StaticAnalyser::Atari2600PagingModel::None: bus_.reset(new CartridgeUnpaged(rom)); break;
|
|
||||||
case StaticAnalyser::Atari2600PagingModel::ParkerBros: bus_.reset(new CartridgeParkerBros(rom)); break;
|
|
||||||
case StaticAnalyser::Atari2600PagingModel::Pitfall2: bus_.reset(new CartridgePitfall2(rom)); break;
|
|
||||||
case StaticAnalyser::Atari2600PagingModel::Tigervision: bus_.reset(new CartridgeTigervision(rom)); break;
|
|
||||||
|
|
||||||
case StaticAnalyser::Atari2600PagingModel::Atari8k:
|
|
||||||
if(target.atari.uses_superchip) {
|
|
||||||
bus_.reset(new CartridgeAtari8kSuperChip(rom));
|
|
||||||
} else {
|
|
||||||
bus_.reset(new CartridgeAtari8k(rom));
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case StaticAnalyser::Atari2600PagingModel::Atari16k:
|
|
||||||
if(target.atari.uses_superchip) {
|
|
||||||
bus_.reset(new CartridgeAtari16kSuperChip(rom));
|
|
||||||
} else {
|
|
||||||
bus_.reset(new CartridgeAtari16k(rom));
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case StaticAnalyser::Atari2600PagingModel::Atari32k:
|
|
||||||
if(target.atari.uses_superchip) {
|
|
||||||
bus_.reset(new CartridgeAtari32kSuperChip(rom));
|
|
||||||
} else {
|
|
||||||
bus_.reset(new CartridgeAtari32k(rom));
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#pragma mark - CRT delegate
|
// to satisfy CRTMachine::Machine
|
||||||
|
void setup_output(float aspect_ratio) override {
|
||||||
|
bus_->tia_.reset(new TIA);
|
||||||
|
bus_->speaker_.set_input_rate(static_cast<float>(get_clock_rate() / static_cast<double>(CPUTicksPerAudioTick)));
|
||||||
|
bus_->tia_->get_crt()->set_delegate(this);
|
||||||
|
}
|
||||||
|
|
||||||
void Machine::crt_did_end_batch_of_frames(Outputs::CRT::CRT *crt, unsigned int number_of_frames, unsigned int number_of_unexpected_vertical_syncs) {
|
void close_output() override {
|
||||||
const size_t number_of_frame_records = sizeof(frame_records_) / sizeof(frame_records_[0]);
|
bus_.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
Outputs::CRT::CRT *get_crt() override {
|
||||||
|
return bus_->tia_->get_crt();
|
||||||
|
}
|
||||||
|
|
||||||
|
Outputs::Speaker::Speaker *get_speaker() override {
|
||||||
|
return &bus_->speaker_;
|
||||||
|
}
|
||||||
|
|
||||||
|
void run_for(const Cycles cycles) override {
|
||||||
|
bus_->run_for(cycles);
|
||||||
|
}
|
||||||
|
|
||||||
|
// to satisfy Outputs::CRT::Delegate
|
||||||
|
void crt_did_end_batch_of_frames(Outputs::CRT::CRT *crt, unsigned int number_of_frames, unsigned int number_of_unexpected_vertical_syncs) override {
|
||||||
|
const std::size_t number_of_frame_records = sizeof(frame_records_) / sizeof(frame_records_[0]);
|
||||||
frame_records_[frame_record_pointer_ % number_of_frame_records].number_of_frames = number_of_frames;
|
frame_records_[frame_record_pointer_ % number_of_frame_records].number_of_frames = number_of_frames;
|
||||||
frame_records_[frame_record_pointer_ % number_of_frame_records].number_of_unexpected_vertical_syncs = number_of_unexpected_vertical_syncs;
|
frame_records_[frame_record_pointer_ % number_of_frame_records].number_of_unexpected_vertical_syncs = number_of_unexpected_vertical_syncs;
|
||||||
frame_record_pointer_ ++;
|
frame_record_pointer_ ++;
|
||||||
@@ -128,13 +167,13 @@ void Machine::crt_did_end_batch_of_frames(Outputs::CRT::CRT *crt, unsigned int n
|
|||||||
if(frame_record_pointer_ >= 6) {
|
if(frame_record_pointer_ >= 6) {
|
||||||
unsigned int total_number_of_frames = 0;
|
unsigned int total_number_of_frames = 0;
|
||||||
unsigned int total_number_of_unexpected_vertical_syncs = 0;
|
unsigned int total_number_of_unexpected_vertical_syncs = 0;
|
||||||
for(size_t c = 0; c < number_of_frame_records; c++) {
|
for(std::size_t c = 0; c < number_of_frame_records; c++) {
|
||||||
total_number_of_frames += frame_records_[c].number_of_frames;
|
total_number_of_frames += frame_records_[c].number_of_frames;
|
||||||
total_number_of_unexpected_vertical_syncs += frame_records_[c].number_of_unexpected_vertical_syncs;
|
total_number_of_unexpected_vertical_syncs += frame_records_[c].number_of_unexpected_vertical_syncs;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(total_number_of_unexpected_vertical_syncs >= total_number_of_frames >> 1) {
|
if(total_number_of_unexpected_vertical_syncs >= total_number_of_frames >> 1) {
|
||||||
for(size_t c = 0; c < number_of_frame_records; c++) {
|
for(std::size_t c = 0; c < number_of_frame_records; c++) {
|
||||||
frame_records_[c].number_of_frames = 0;
|
frame_records_[c].number_of_frames = 0;
|
||||||
frame_records_[c].number_of_unexpected_vertical_syncs = 0;
|
frame_records_[c].number_of_unexpected_vertical_syncs = 0;
|
||||||
}
|
}
|
||||||
@@ -149,9 +188,35 @@ void Machine::crt_did_end_batch_of_frames(Outputs::CRT::CRT *crt, unsigned int n
|
|||||||
bus_->tia_->set_output_mode(TIA::OutputMode::PAL);
|
bus_->tia_->set_output_mode(TIA::OutputMode::PAL);
|
||||||
}
|
}
|
||||||
|
|
||||||
bus_->speaker_->set_input_rate((float)(clock_rate / (double)CPUTicksPerAudioTick));
|
bus_->speaker_.set_input_rate(static_cast<float>(clock_rate / static_cast<double>(CPUTicksPerAudioTick)));
|
||||||
bus_->speaker_->set_high_frequency_cut_off((float)(clock_rate / ((double)CPUTicksPerAudioTick * 2.0)));
|
bus_->speaker_.set_high_frequency_cutoff(static_cast<float>(clock_rate / (static_cast<double>(CPUTicksPerAudioTick) * 2.0)));
|
||||||
set_clock_rate(clock_rate);
|
set_clock_rate(clock_rate);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
// the bus
|
||||||
|
std::unique_ptr<Bus> bus_;
|
||||||
|
|
||||||
|
// output frame rate tracker
|
||||||
|
struct FrameRecord {
|
||||||
|
unsigned int number_of_frames;
|
||||||
|
unsigned int number_of_unexpected_vertical_syncs;
|
||||||
|
|
||||||
|
FrameRecord() : number_of_frames(0), number_of_unexpected_vertical_syncs(0) {}
|
||||||
|
} frame_records_[4];
|
||||||
|
unsigned int frame_record_pointer_;
|
||||||
|
bool is_ntsc_;
|
||||||
|
std::vector<std::unique_ptr<Inputs::Joystick>> joysticks_;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
using namespace Atari2600;
|
||||||
|
|
||||||
|
Machine *Machine::Atari2600() {
|
||||||
|
return new Atari2600::ConcreteMachine;
|
||||||
|
}
|
||||||
|
|
||||||
|
Machine::~Machine() {}
|
||||||
|
|||||||
@@ -9,59 +9,32 @@
|
|||||||
#ifndef Atari2600_cpp
|
#ifndef Atari2600_cpp
|
||||||
#define Atari2600_cpp
|
#define Atari2600_cpp
|
||||||
|
|
||||||
#include <stdint.h>
|
|
||||||
|
|
||||||
#include "../../Processors/6502/6502.hpp"
|
|
||||||
#include "../CRTMachine.hpp"
|
|
||||||
#include "Bus.hpp"
|
|
||||||
#include "PIA.hpp"
|
|
||||||
#include "Speaker.hpp"
|
|
||||||
#include "TIA.hpp"
|
|
||||||
|
|
||||||
#include "../ConfigurationTarget.hpp"
|
#include "../ConfigurationTarget.hpp"
|
||||||
|
#include "../CRTMachine.hpp"
|
||||||
|
#include "../JoystickMachine.hpp"
|
||||||
|
|
||||||
#include "Atari2600Inputs.h"
|
#include "Atari2600Inputs.h"
|
||||||
|
|
||||||
namespace Atari2600 {
|
namespace Atari2600 {
|
||||||
|
|
||||||
|
/*!
|
||||||
|
Models an Atari 2600.
|
||||||
|
*/
|
||||||
class Machine:
|
class Machine:
|
||||||
public CRTMachine::Machine,
|
public CRTMachine::Machine,
|
||||||
public ConfigurationTarget::Machine,
|
public ConfigurationTarget::Machine,
|
||||||
public Outputs::CRT::Delegate {
|
public JoystickMachine::Machine {
|
||||||
|
|
||||||
public:
|
public:
|
||||||
Machine();
|
virtual ~Machine();
|
||||||
~Machine();
|
|
||||||
|
|
||||||
void configure_as_target(const StaticAnalyser::Target &target);
|
/// Creates and returns an Atari 2600 on the heap.
|
||||||
void switch_region();
|
static Machine *Atari2600();
|
||||||
|
|
||||||
void set_digital_input(Atari2600DigitalInput input, bool state);
|
/// Sets the switch @c input to @c state.
|
||||||
void set_switch_is_enabled(Atari2600Switch input, bool state);
|
virtual void set_switch_is_enabled(Atari2600Switch input, bool state) = 0;
|
||||||
void set_reset_line(bool state) { bus_->set_reset_line(state); }
|
|
||||||
|
|
||||||
// to satisfy CRTMachine::Machine
|
// Presses or releases the reset button.
|
||||||
virtual void setup_output(float aspect_ratio);
|
virtual void set_reset_switch(bool state) = 0;
|
||||||
virtual void close_output();
|
|
||||||
virtual std::shared_ptr<Outputs::CRT::CRT> get_crt() { return bus_->tia_->get_crt(); }
|
|
||||||
virtual std::shared_ptr<Outputs::Speaker> get_speaker() { return bus_->speaker_; }
|
|
||||||
virtual void run_for(const Cycles cycles) { bus_->run_for(cycles); }
|
|
||||||
|
|
||||||
// to satisfy Outputs::CRT::Delegate
|
|
||||||
virtual void crt_did_end_batch_of_frames(Outputs::CRT::CRT *crt, unsigned int number_of_frames, unsigned int number_of_unexpected_vertical_syncs);
|
|
||||||
|
|
||||||
private:
|
|
||||||
// the bus
|
|
||||||
std::unique_ptr<Bus> bus_;
|
|
||||||
|
|
||||||
// output frame rate tracker
|
|
||||||
struct FrameRecord {
|
|
||||||
unsigned int number_of_frames;
|
|
||||||
unsigned int number_of_unexpected_vertical_syncs;
|
|
||||||
|
|
||||||
FrameRecord() : number_of_frames(0), number_of_unexpected_vertical_syncs(0) {}
|
|
||||||
} frame_records_[4];
|
|
||||||
unsigned int frame_record_pointer_;
|
|
||||||
bool is_ntsc_;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,16 +11,19 @@
|
|||||||
|
|
||||||
#include "Atari2600.hpp"
|
#include "Atari2600.hpp"
|
||||||
#include "PIA.hpp"
|
#include "PIA.hpp"
|
||||||
#include "Speaker.hpp"
|
|
||||||
#include "TIA.hpp"
|
#include "TIA.hpp"
|
||||||
|
#include "TIASound.hpp"
|
||||||
|
|
||||||
#include "../../ClockReceiver/ClockReceiver.hpp"
|
#include "../../ClockReceiver/ClockReceiver.hpp"
|
||||||
|
#include "../../Outputs/Speaker/Implementation/LowpassSpeaker.hpp"
|
||||||
|
|
||||||
namespace Atari2600 {
|
namespace Atari2600 {
|
||||||
|
|
||||||
class Bus {
|
class Bus {
|
||||||
public:
|
public:
|
||||||
Bus() :
|
Bus() :
|
||||||
|
tia_sound_(audio_queue_),
|
||||||
|
speaker_(tia_sound_),
|
||||||
tia_input_value_{0xff, 0xff},
|
tia_input_value_{0xff, 0xff},
|
||||||
cycles_since_speaker_update_(0) {}
|
cycles_since_speaker_update_(0) {}
|
||||||
|
|
||||||
@@ -30,7 +33,10 @@ class Bus {
|
|||||||
// the RIOT, TIA and speaker
|
// the RIOT, TIA and speaker
|
||||||
PIA mos6532_;
|
PIA mos6532_;
|
||||||
std::shared_ptr<TIA> tia_;
|
std::shared_ptr<TIA> tia_;
|
||||||
std::shared_ptr<Speaker> speaker_;
|
|
||||||
|
Concurrency::DeferringAsyncTaskQueue audio_queue_;
|
||||||
|
TIASound tia_sound_;
|
||||||
|
Outputs::Speaker::LowpassSpeaker<TIASound> speaker_;
|
||||||
|
|
||||||
// joystick state
|
// joystick state
|
||||||
uint8_t tia_input_value_[2];
|
uint8_t tia_input_value_[2];
|
||||||
@@ -39,7 +45,7 @@ class Bus {
|
|||||||
// speaker backlog accumlation counter
|
// speaker backlog accumlation counter
|
||||||
Cycles cycles_since_speaker_update_;
|
Cycles cycles_since_speaker_update_;
|
||||||
inline void update_audio() {
|
inline void update_audio() {
|
||||||
speaker_->run_for(cycles_since_speaker_update_.divide(Cycles(CPUTicksPerAudioTick * 3)));
|
speaker_.run_for(audio_queue_, cycles_since_speaker_update_.divide(Cycles(CPUTicksPerAudioTick * 3)));
|
||||||
}
|
}
|
||||||
|
|
||||||
// video backlog accumulation counter
|
// video backlog accumulation counter
|
||||||
|
|||||||
@@ -6,18 +6,18 @@
|
|||||||
// Copyright © 2017 Thomas Harte. All rights reserved.
|
// Copyright © 2017 Thomas Harte. All rights reserved.
|
||||||
//
|
//
|
||||||
|
|
||||||
#ifndef Atari2600_CartridgeActivisionStack_hpp
|
#ifndef Atari2600_ActivisionStack_hpp
|
||||||
#define Atari2600_CartridgeActivisionStack_hpp
|
#define Atari2600_ActivisionStack_hpp
|
||||||
|
|
||||||
namespace Atari2600 {
|
namespace Atari2600 {
|
||||||
|
namespace Cartridge {
|
||||||
|
|
||||||
class CartridgeActivisionStack: public Cartridge<CartridgeActivisionStack> {
|
class ActivisionStack: public BusExtender {
|
||||||
public:
|
public:
|
||||||
CartridgeActivisionStack(const std::vector<uint8_t> &rom) :
|
ActivisionStack(uint8_t *rom_base, std::size_t rom_size) :
|
||||||
Cartridge(rom),
|
BusExtender(rom_base, rom_size),
|
||||||
last_opcode_(0x00) {
|
rom_ptr_(rom_base),
|
||||||
rom_ptr_ = rom_.data();
|
last_opcode_(0x00) {}
|
||||||
}
|
|
||||||
|
|
||||||
void perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value) {
|
void perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value) {
|
||||||
if(!(address & 0x1000)) return;
|
if(!(address & 0x1000)) return;
|
||||||
@@ -27,9 +27,9 @@ class CartridgeActivisionStack: public Cartridge<CartridgeActivisionStack> {
|
|||||||
// RST or JSR.
|
// RST or JSR.
|
||||||
if(operation == CPU::MOS6502::BusOperation::ReadOpcode && (last_opcode_ == 0x20 || last_opcode_ == 0x60)) {
|
if(operation == CPU::MOS6502::BusOperation::ReadOpcode && (last_opcode_ == 0x20 || last_opcode_ == 0x60)) {
|
||||||
if(address & 0x2000) {
|
if(address & 0x2000) {
|
||||||
rom_ptr_ = rom_.data();
|
rom_ptr_ = rom_base_;
|
||||||
} else {
|
} else {
|
||||||
rom_ptr_ = rom_.data() + 4096;
|
rom_ptr_ = rom_base_ + 4096;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -45,6 +45,7 @@ class CartridgeActivisionStack: public Cartridge<CartridgeActivisionStack> {
|
|||||||
uint8_t last_opcode_;
|
uint8_t last_opcode_;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif /* Atari2600_CartridgeActivisionStack_hpp */
|
#endif /* Atari2600_CartridgeActivisionStack_hpp */
|
||||||
@@ -12,19 +12,19 @@
|
|||||||
#include "Cartridge.hpp"
|
#include "Cartridge.hpp"
|
||||||
|
|
||||||
namespace Atari2600 {
|
namespace Atari2600 {
|
||||||
|
namespace Cartridge {
|
||||||
|
|
||||||
class CartridgeAtari16k: public Cartridge<CartridgeAtari16k> {
|
class Atari16k: public BusExtender {
|
||||||
public:
|
public:
|
||||||
CartridgeAtari16k(const std::vector<uint8_t> &rom) :
|
Atari16k(uint8_t *rom_base, std::size_t rom_size) :
|
||||||
Cartridge(rom) {
|
BusExtender(rom_base, rom_size),
|
||||||
rom_ptr_ = rom_.data();
|
rom_ptr_(rom_base) {}
|
||||||
}
|
|
||||||
|
|
||||||
void perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value) {
|
void perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value) {
|
||||||
address &= 0x1fff;
|
address &= 0x1fff;
|
||||||
if(!(address & 0x1000)) return;
|
if(!(address & 0x1000)) return;
|
||||||
|
|
||||||
if(address >= 0x1ff6 && address <= 0x1ff9) rom_ptr_ = rom_.data() + (address - 0x1ff6) * 4096;
|
if(address >= 0x1ff6 && address <= 0x1ff9) rom_ptr_ = rom_base_ + (address - 0x1ff6) * 4096;
|
||||||
|
|
||||||
if(isReadOperation(operation)) {
|
if(isReadOperation(operation)) {
|
||||||
*value = rom_ptr_[address & 4095];
|
*value = rom_ptr_[address & 4095];
|
||||||
@@ -35,18 +35,17 @@ class CartridgeAtari16k: public Cartridge<CartridgeAtari16k> {
|
|||||||
uint8_t *rom_ptr_;
|
uint8_t *rom_ptr_;
|
||||||
};
|
};
|
||||||
|
|
||||||
class CartridgeAtari16kSuperChip: public Cartridge<CartridgeAtari16kSuperChip> {
|
class Atari16kSuperChip: public BusExtender {
|
||||||
public:
|
public:
|
||||||
CartridgeAtari16kSuperChip(const std::vector<uint8_t> &rom) :
|
Atari16kSuperChip(uint8_t *rom_base, std::size_t rom_size) :
|
||||||
Cartridge(rom) {
|
BusExtender(rom_base, rom_size),
|
||||||
rom_ptr_ = rom_.data();
|
rom_ptr_(rom_base) {}
|
||||||
}
|
|
||||||
|
|
||||||
void perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value) {
|
void perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value) {
|
||||||
address &= 0x1fff;
|
address &= 0x1fff;
|
||||||
if(!(address & 0x1000)) return;
|
if(!(address & 0x1000)) return;
|
||||||
|
|
||||||
if(address >= 0x1ff6 && address <= 0x1ff9) rom_ptr_ = rom_.data() + (address - 0x1ff6) * 4096;
|
if(address >= 0x1ff6 && address <= 0x1ff9) rom_ptr_ = rom_base_ + (address - 0x1ff6) * 4096;
|
||||||
|
|
||||||
if(isReadOperation(operation)) {
|
if(isReadOperation(operation)) {
|
||||||
*value = rom_ptr_[address & 4095];
|
*value = rom_ptr_[address & 4095];
|
||||||
@@ -61,6 +60,7 @@ class CartridgeAtari16kSuperChip: public Cartridge<CartridgeAtari16kSuperChip> {
|
|||||||
uint8_t ram_[128];
|
uint8_t ram_[128];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif /* Atari2600_CartridgeAtari16k_hpp */
|
#endif /* Atari2600_CartridgeAtari16k_hpp */
|
||||||
@@ -12,19 +12,17 @@
|
|||||||
#include "Cartridge.hpp"
|
#include "Cartridge.hpp"
|
||||||
|
|
||||||
namespace Atari2600 {
|
namespace Atari2600 {
|
||||||
|
namespace Cartridge {
|
||||||
|
|
||||||
class CartridgeAtari32k: public Cartridge<CartridgeAtari32k> {
|
class Atari32k: public BusExtender {
|
||||||
public:
|
public:
|
||||||
CartridgeAtari32k(const std::vector<uint8_t> &rom) :
|
Atari32k(uint8_t *rom_base, std::size_t rom_size) : BusExtender(rom_base, rom_size), rom_ptr_(rom_base) {}
|
||||||
Cartridge(rom) {
|
|
||||||
rom_ptr_ = rom_.data();
|
|
||||||
}
|
|
||||||
|
|
||||||
void perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value) {
|
void perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value) {
|
||||||
address &= 0x1fff;
|
address &= 0x1fff;
|
||||||
if(!(address & 0x1000)) return;
|
if(!(address & 0x1000)) return;
|
||||||
|
|
||||||
if(address >= 0x1ff4 && address <= 0x1ffb) rom_ptr_ = rom_.data() + (address - 0x1ff4) * 4096;
|
if(address >= 0x1ff4 && address <= 0x1ffb) rom_ptr_ = rom_base_ + (address - 0x1ff4) * 4096;
|
||||||
|
|
||||||
if(isReadOperation(operation)) {
|
if(isReadOperation(operation)) {
|
||||||
*value = rom_ptr_[address & 4095];
|
*value = rom_ptr_[address & 4095];
|
||||||
@@ -35,18 +33,15 @@ class CartridgeAtari32k: public Cartridge<CartridgeAtari32k> {
|
|||||||
uint8_t *rom_ptr_;
|
uint8_t *rom_ptr_;
|
||||||
};
|
};
|
||||||
|
|
||||||
class CartridgeAtari32kSuperChip: public Cartridge<CartridgeAtari32kSuperChip> {
|
class Atari32kSuperChip: public BusExtender {
|
||||||
public:
|
public:
|
||||||
CartridgeAtari32kSuperChip(const std::vector<uint8_t> &rom) :
|
Atari32kSuperChip(uint8_t *rom_base, std::size_t rom_size) : BusExtender(rom_base, rom_size), rom_ptr_(rom_base) {}
|
||||||
Cartridge(rom) {
|
|
||||||
rom_ptr_ = rom_.data();
|
|
||||||
}
|
|
||||||
|
|
||||||
void perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value) {
|
void perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value) {
|
||||||
address &= 0x1fff;
|
address &= 0x1fff;
|
||||||
if(!(address & 0x1000)) return;
|
if(!(address & 0x1000)) return;
|
||||||
|
|
||||||
if(address >= 0x1ff4 && address <= 0x1ffb) rom_ptr_ = rom_.data() + (address - 0x1ff4) * 4096;
|
if(address >= 0x1ff4 && address <= 0x1ffb) rom_ptr_ = rom_base_ + (address - 0x1ff4) * 4096;
|
||||||
|
|
||||||
if(isReadOperation(operation)) {
|
if(isReadOperation(operation)) {
|
||||||
*value = rom_ptr_[address & 4095];
|
*value = rom_ptr_[address & 4095];
|
||||||
@@ -61,6 +56,7 @@ class CartridgeAtari32kSuperChip: public Cartridge<CartridgeAtari32kSuperChip> {
|
|||||||
uint8_t ram_[128];
|
uint8_t ram_[128];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif /* Atari2600_CartridgeAtari32k_hpp */
|
#endif /* Atari2600_CartridgeAtari32k_hpp */
|
||||||
@@ -12,20 +12,18 @@
|
|||||||
#include "Cartridge.hpp"
|
#include "Cartridge.hpp"
|
||||||
|
|
||||||
namespace Atari2600 {
|
namespace Atari2600 {
|
||||||
|
namespace Cartridge {
|
||||||
|
|
||||||
class CartridgeAtari8k: public Cartridge<CartridgeAtari8k> {
|
class Atari8k: public BusExtender {
|
||||||
public:
|
public:
|
||||||
CartridgeAtari8k(const std::vector<uint8_t> &rom) :
|
Atari8k(uint8_t *rom_base, std::size_t rom_size) : BusExtender(rom_base, rom_size), rom_ptr_(rom_base) {}
|
||||||
Cartridge(rom) {
|
|
||||||
rom_ptr_ = rom_.data();
|
|
||||||
}
|
|
||||||
|
|
||||||
void perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value) {
|
void perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value) {
|
||||||
address &= 0x1fff;
|
address &= 0x1fff;
|
||||||
if(!(address & 0x1000)) return;
|
if(!(address & 0x1000)) return;
|
||||||
|
|
||||||
if(address == 0x1ff8) rom_ptr_ = rom_.data();
|
if(address == 0x1ff8) rom_ptr_ = rom_base_;
|
||||||
else if(address == 0x1ff9) rom_ptr_ = rom_.data() + 4096;
|
else if(address == 0x1ff9) rom_ptr_ = rom_base_ + 4096;
|
||||||
|
|
||||||
if(isReadOperation(operation)) {
|
if(isReadOperation(operation)) {
|
||||||
*value = rom_ptr_[address & 4095];
|
*value = rom_ptr_[address & 4095];
|
||||||
@@ -36,19 +34,16 @@ class CartridgeAtari8k: public Cartridge<CartridgeAtari8k> {
|
|||||||
uint8_t *rom_ptr_;
|
uint8_t *rom_ptr_;
|
||||||
};
|
};
|
||||||
|
|
||||||
class CartridgeAtari8kSuperChip: public Cartridge<CartridgeAtari8kSuperChip> {
|
class Atari8kSuperChip: public BusExtender {
|
||||||
public:
|
public:
|
||||||
CartridgeAtari8kSuperChip(const std::vector<uint8_t> &rom) :
|
Atari8kSuperChip(uint8_t *rom_base, std::size_t rom_size) : BusExtender(rom_base, rom_size), rom_ptr_(rom_base) {}
|
||||||
Cartridge(rom) {
|
|
||||||
rom_ptr_ = rom_.data();
|
|
||||||
}
|
|
||||||
|
|
||||||
void perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value) {
|
void perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value) {
|
||||||
address &= 0x1fff;
|
address &= 0x1fff;
|
||||||
if(!(address & 0x1000)) return;
|
if(!(address & 0x1000)) return;
|
||||||
|
|
||||||
if(address == 0x1ff8) rom_ptr_ = rom_.data();
|
if(address == 0x1ff8) rom_ptr_ = rom_base_;
|
||||||
if(address == 0x1ff9) rom_ptr_ = rom_.data() + 4096;
|
if(address == 0x1ff9) rom_ptr_ = rom_base_ + 4096;
|
||||||
|
|
||||||
if(isReadOperation(operation)) {
|
if(isReadOperation(operation)) {
|
||||||
*value = rom_ptr_[address & 4095];
|
*value = rom_ptr_[address & 4095];
|
||||||
@@ -63,6 +58,7 @@ class CartridgeAtari8kSuperChip: public Cartridge<CartridgeAtari8kSuperChip> {
|
|||||||
uint8_t ram_[128];
|
uint8_t ram_[128];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif /* Atari2600_CartridgeAtari8k_hpp */
|
#endif /* Atari2600_CartridgeAtari8k_hpp */
|
||||||
@@ -12,19 +12,17 @@
|
|||||||
#include "Cartridge.hpp"
|
#include "Cartridge.hpp"
|
||||||
|
|
||||||
namespace Atari2600 {
|
namespace Atari2600 {
|
||||||
|
namespace Cartridge {
|
||||||
|
|
||||||
class CartridgeCBSRAMPlus: public Cartridge<CartridgeCBSRAMPlus> {
|
class CBSRAMPlus: public BusExtender {
|
||||||
public:
|
public:
|
||||||
CartridgeCBSRAMPlus(const std::vector<uint8_t> &rom) :
|
CBSRAMPlus(uint8_t *rom_base, std::size_t rom_size) : BusExtender(rom_base, rom_size), rom_ptr_(rom_base) {}
|
||||||
Cartridge(rom) {
|
|
||||||
rom_ptr_ = rom_.data();
|
|
||||||
}
|
|
||||||
|
|
||||||
void perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value) {
|
void perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value) {
|
||||||
address &= 0x1fff;
|
address &= 0x1fff;
|
||||||
if(!(address & 0x1000)) return;
|
if(!(address & 0x1000)) return;
|
||||||
|
|
||||||
if(address >= 0x1ff8 && address <= 0x1ffa) rom_ptr_ = rom_.data() + (address - 0x1ff8) * 4096;
|
if(address >= 0x1ff8 && address <= 0x1ffa) rom_ptr_ = rom_base_ + (address - 0x1ff8) * 4096;
|
||||||
|
|
||||||
if(isReadOperation(operation)) {
|
if(isReadOperation(operation)) {
|
||||||
*value = rom_ptr_[address & 4095];
|
*value = rom_ptr_[address & 4095];
|
||||||
@@ -39,6 +37,7 @@ class CartridgeCBSRAMPlus: public Cartridge<CartridgeCBSRAMPlus> {
|
|||||||
uint8_t ram_[256];
|
uint8_t ram_[256];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif /* Atari2600_CartridgeCBSRAMPlus_hpp */
|
#endif /* Atari2600_CartridgeCBSRAMPlus_hpp */
|
||||||
@@ -13,18 +13,34 @@
|
|||||||
#include "../Bus.hpp"
|
#include "../Bus.hpp"
|
||||||
|
|
||||||
namespace Atari2600 {
|
namespace Atari2600 {
|
||||||
|
namespace Cartridge {
|
||||||
|
|
||||||
|
class BusExtender: public CPU::MOS6502::BusHandler {
|
||||||
|
public:
|
||||||
|
BusExtender(uint8_t *rom_base, std::size_t rom_size) : rom_base_(rom_base), rom_size_(rom_size) {}
|
||||||
|
|
||||||
|
void advance_cycles(int cycles) {}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
uint8_t *rom_base_;
|
||||||
|
std::size_t rom_size_;
|
||||||
|
};
|
||||||
|
|
||||||
template<class T> class Cartridge:
|
template<class T> class Cartridge:
|
||||||
public CPU::MOS6502::Processor<Cartridge<T>>,
|
public CPU::MOS6502::BusHandler,
|
||||||
public Bus {
|
public Bus {
|
||||||
|
|
||||||
public:
|
public:
|
||||||
Cartridge(const std::vector<uint8_t> &rom) :
|
Cartridge(const std::vector<uint8_t> &rom) :
|
||||||
rom_(rom) {}
|
m6502_(*this),
|
||||||
|
rom_(rom),
|
||||||
|
bus_extender_(rom_.data(), rom.size()) {
|
||||||
|
// The above works because bus_extender_ is declared after rom_ in the instance storage list;
|
||||||
|
// consider doing something less fragile.
|
||||||
|
}
|
||||||
|
|
||||||
void run_for(const Cycles cycles) { CPU::MOS6502::Processor<Cartridge<T>>::run_for(cycles); }
|
void run_for(const Cycles cycles) { m6502_.run_for(cycles); }
|
||||||
void set_reset_line(bool state) { CPU::MOS6502::Processor<Cartridge<T>>::set_reset_line(state); }
|
void set_reset_line(bool state) { m6502_.set_reset_line(state); }
|
||||||
void advance_cycles(int cycles) {}
|
|
||||||
|
|
||||||
// to satisfy CPU::MOS6502::Processor
|
// to satisfy CPU::MOS6502::Processor
|
||||||
Cycles perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value) {
|
Cycles perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value) {
|
||||||
@@ -41,11 +57,11 @@ template<class T> class Cartridge:
|
|||||||
cycles_since_speaker_update_ += Cycles(cycles_run_for);
|
cycles_since_speaker_update_ += Cycles(cycles_run_for);
|
||||||
cycles_since_video_update_ += Cycles(cycles_run_for);
|
cycles_since_video_update_ += Cycles(cycles_run_for);
|
||||||
cycles_since_6532_update_ += Cycles(cycles_run_for / 3);
|
cycles_since_6532_update_ += Cycles(cycles_run_for / 3);
|
||||||
static_cast<T *>(this)->advance_cycles(cycles_run_for / 3);
|
bus_extender_.advance_cycles(cycles_run_for / 3);
|
||||||
|
|
||||||
if(operation != CPU::MOS6502::BusOperation::Ready) {
|
if(operation != CPU::MOS6502::BusOperation::Ready) {
|
||||||
// give the cartridge a chance to respond to the bus access
|
// give the cartridge a chance to respond to the bus access
|
||||||
static_cast<T *>(this)->perform_bus_operation(operation, address, value);
|
bus_extender_.perform_bus_operation(operation, address, value);
|
||||||
|
|
||||||
// check for a RIOT RAM access
|
// check for a RIOT RAM access
|
||||||
if((address&0x1280) == 0x80) {
|
if((address&0x1280) == 0x80) {
|
||||||
@@ -91,7 +107,7 @@ template<class T> class Cartridge:
|
|||||||
case 0x00: update_video(); tia_->set_sync(*value & 0x02); break;
|
case 0x00: update_video(); tia_->set_sync(*value & 0x02); break;
|
||||||
case 0x01: update_video(); tia_->set_blank(*value & 0x02); break;
|
case 0x01: update_video(); tia_->set_blank(*value & 0x02); break;
|
||||||
|
|
||||||
case 0x02: CPU::MOS6502::Processor<Cartridge<T>>::set_ready_line(true); break;
|
case 0x02: m6502_.set_ready_line(true); break;
|
||||||
case 0x03: update_video(); tia_->reset_horizontal_counter(); break;
|
case 0x03: update_video(); tia_->reset_horizontal_counter(); break;
|
||||||
// TODO: audio will now be out of synchronisation — fix
|
// TODO: audio will now be out of synchronisation — fix
|
||||||
|
|
||||||
@@ -132,11 +148,11 @@ template<class T> class Cartridge:
|
|||||||
case 0x2c: update_video(); tia_->clear_collision_flags(); break;
|
case 0x2c: update_video(); tia_->clear_collision_flags(); break;
|
||||||
|
|
||||||
case 0x15:
|
case 0x15:
|
||||||
case 0x16: update_audio(); speaker_->set_control(decodedAddress - 0x15, *value); break;
|
case 0x16: update_audio(); tia_sound_.set_control(decodedAddress - 0x15, *value); break;
|
||||||
case 0x17:
|
case 0x17:
|
||||||
case 0x18: update_audio(); speaker_->set_divider(decodedAddress - 0x17, *value); break;
|
case 0x18: update_audio(); tia_sound_.set_divider(decodedAddress - 0x17, *value); break;
|
||||||
case 0x19:
|
case 0x19:
|
||||||
case 0x1a: update_audio(); speaker_->set_volume(decodedAddress - 0x19, *value); break;
|
case 0x1a: update_audio(); tia_sound_.set_volume(decodedAddress - 0x19, *value); break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -156,7 +172,7 @@ template<class T> class Cartridge:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!tia_->get_cycles_until_horizontal_blank(cycles_since_video_update_)) CPU::MOS6502::Processor<Cartridge<T>>::set_ready_line(false);
|
if(!tia_->get_cycles_until_horizontal_blank(cycles_since_video_update_)) m6502_.set_ready_line(false);
|
||||||
|
|
||||||
return Cycles(cycles_run_for / 3);
|
return Cycles(cycles_run_for / 3);
|
||||||
}
|
}
|
||||||
@@ -164,13 +180,18 @@ template<class T> class Cartridge:
|
|||||||
void flush() {
|
void flush() {
|
||||||
update_audio();
|
update_audio();
|
||||||
update_video();
|
update_video();
|
||||||
speaker_->flush();
|
audio_queue_.perform();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
CPU::MOS6502::Processor<Cartridge<T>, true> m6502_;
|
||||||
std::vector<uint8_t> rom_;
|
std::vector<uint8_t> rom_;
|
||||||
|
|
||||||
|
private:
|
||||||
|
T bus_extender_;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif /* Atari2600_Cartridge_hpp */
|
#endif /* Atari2600_Cartridge_hpp */
|
||||||
|
|||||||
@@ -9,12 +9,14 @@
|
|||||||
#ifndef Atari2600_CartridgeCommaVid_hpp
|
#ifndef Atari2600_CartridgeCommaVid_hpp
|
||||||
#define Atari2600_CartridgeCommaVid_hpp
|
#define Atari2600_CartridgeCommaVid_hpp
|
||||||
|
|
||||||
namespace Atari2600 {
|
#include "Cartridge.hpp"
|
||||||
|
|
||||||
class CartridgeCommaVid: public Cartridge<CartridgeCommaVid> {
|
namespace Atari2600 {
|
||||||
|
namespace Cartridge {
|
||||||
|
|
||||||
|
class CommaVid: public BusExtender {
|
||||||
public:
|
public:
|
||||||
CartridgeCommaVid(const std::vector<uint8_t> &rom) :
|
CommaVid(uint8_t *rom_base, std::size_t rom_size) : BusExtender(rom_base, rom_size) {}
|
||||||
Cartridge(rom) {}
|
|
||||||
|
|
||||||
void perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value) {
|
void perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value) {
|
||||||
if(!(address & 0x1000)) return;
|
if(!(address & 0x1000)) return;
|
||||||
@@ -30,13 +32,14 @@ class CartridgeCommaVid: public Cartridge<CartridgeCommaVid> {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(isReadOperation(operation)) *value = rom_[address & 2047];
|
if(isReadOperation(operation)) *value = rom_base_[address & 2047];
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
uint8_t ram_[1024];
|
uint8_t ram_[1024];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif /* Atari2600_CartridgeCommaVid_hpp */
|
#endif /* Atari2600_CartridgeCommaVid_hpp */
|
||||||
@@ -12,12 +12,13 @@
|
|||||||
#include "Cartridge.hpp"
|
#include "Cartridge.hpp"
|
||||||
|
|
||||||
namespace Atari2600 {
|
namespace Atari2600 {
|
||||||
|
namespace Cartridge {
|
||||||
|
|
||||||
class CartridgeMNetwork: public Cartridge<CartridgeMNetwork> {
|
class MNetwork: public BusExtender {
|
||||||
public:
|
public:
|
||||||
CartridgeMNetwork(const std::vector<uint8_t> &rom) :
|
MNetwork(uint8_t *rom_base, std::size_t rom_size) :
|
||||||
Cartridge(rom) {
|
BusExtender(rom_base, rom_size) {
|
||||||
rom_ptr_[0] = rom_.data() + rom_.size() - 4096;
|
rom_ptr_[0] = rom_base + rom_size_ - 4096;
|
||||||
rom_ptr_[1] = rom_ptr_[0] + 2048;
|
rom_ptr_[1] = rom_ptr_[0] + 2048;
|
||||||
high_ram_ptr_ = high_ram_;
|
high_ram_ptr_ = high_ram_;
|
||||||
}
|
}
|
||||||
@@ -27,7 +28,7 @@ class CartridgeMNetwork: public Cartridge<CartridgeMNetwork> {
|
|||||||
if(!(address & 0x1000)) return;
|
if(!(address & 0x1000)) return;
|
||||||
|
|
||||||
if(address >= 0x1fe0 && address <= 0x1fe6) {
|
if(address >= 0x1fe0 && address <= 0x1fe6) {
|
||||||
rom_ptr_[0] = rom_.data() + (address - 0x1fe0) * 2048;
|
rom_ptr_[0] = rom_base_ + (address - 0x1fe0) * 2048;
|
||||||
} else if(address == 0x1fe7) {
|
} else if(address == 0x1fe7) {
|
||||||
rom_ptr_[0] = nullptr;
|
rom_ptr_[0] = nullptr;
|
||||||
} else if(address >= 0x1ff8 && address <= 0x1ffb) {
|
} else if(address >= 0x1ff8 && address <= 0x1ffb) {
|
||||||
@@ -54,7 +55,6 @@ class CartridgeMNetwork: public Cartridge<CartridgeMNetwork> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
@@ -63,6 +63,7 @@ class CartridgeMNetwork: public Cartridge<CartridgeMNetwork> {
|
|||||||
uint8_t low_ram_[1024], high_ram_[1024];
|
uint8_t low_ram_[1024], high_ram_[1024];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif /* Atari2600_CartridgeMNetwork_hpp */
|
#endif /* Atari2600_CartridgeMNetwork_hpp */
|
||||||
@@ -12,13 +12,14 @@
|
|||||||
#include "Cartridge.hpp"
|
#include "Cartridge.hpp"
|
||||||
|
|
||||||
namespace Atari2600 {
|
namespace Atari2600 {
|
||||||
|
namespace Cartridge {
|
||||||
|
|
||||||
class CartridgeMegaBoy: public Cartridge<CartridgeMegaBoy> {
|
class MegaBoy: public BusExtender {
|
||||||
public:
|
public:
|
||||||
CartridgeMegaBoy(const std::vector<uint8_t> &rom) :
|
MegaBoy(uint8_t *rom_base, std::size_t rom_size) :
|
||||||
Cartridge(rom),
|
BusExtender(rom_base, rom_size),
|
||||||
|
rom_ptr_(rom_base),
|
||||||
current_page_(0) {
|
current_page_(0) {
|
||||||
rom_ptr_ = rom_.data();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value) {
|
void perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value) {
|
||||||
@@ -27,7 +28,7 @@ class CartridgeMegaBoy: public Cartridge<CartridgeMegaBoy> {
|
|||||||
|
|
||||||
if(address == 0x1ff0) {
|
if(address == 0x1ff0) {
|
||||||
current_page_ = (current_page_ + 1) & 15;
|
current_page_ = (current_page_ + 1) & 15;
|
||||||
rom_ptr_ = rom_.data() + current_page_ * 4096;
|
rom_ptr_ = rom_base_ + current_page_ * 4096;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(isReadOperation(operation)) {
|
if(isReadOperation(operation)) {
|
||||||
@@ -40,6 +41,7 @@ class CartridgeMegaBoy: public Cartridge<CartridgeMegaBoy> {
|
|||||||
uint8_t current_page_;
|
uint8_t current_page_;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif /* CartridgeMegaBoy_h */
|
#endif /* CartridgeMegaBoy_h */
|
||||||
@@ -12,12 +12,13 @@
|
|||||||
#include "Cartridge.hpp"
|
#include "Cartridge.hpp"
|
||||||
|
|
||||||
namespace Atari2600 {
|
namespace Atari2600 {
|
||||||
|
namespace Cartridge {
|
||||||
|
|
||||||
class CartridgeParkerBros: public Cartridge<CartridgeParkerBros> {
|
class ParkerBros: public BusExtender {
|
||||||
public:
|
public:
|
||||||
CartridgeParkerBros(const std::vector<uint8_t> &rom) :
|
ParkerBros(uint8_t *rom_base, std::size_t rom_size) :
|
||||||
Cartridge(rom) {
|
BusExtender(rom_base, rom_size) {
|
||||||
rom_ptr_[0] = rom_.data() + 4096;
|
rom_ptr_[0] = rom_base + 4096;
|
||||||
rom_ptr_[1] = rom_ptr_[0] + 1024;
|
rom_ptr_[1] = rom_ptr_[0] + 1024;
|
||||||
rom_ptr_[2] = rom_ptr_[1] + 1024;
|
rom_ptr_[2] = rom_ptr_[1] + 1024;
|
||||||
rom_ptr_[3] = rom_ptr_[2] + 1024;
|
rom_ptr_[3] = rom_ptr_[2] + 1024;
|
||||||
@@ -29,7 +30,7 @@ class CartridgeParkerBros: public Cartridge<CartridgeParkerBros> {
|
|||||||
|
|
||||||
if(address >= 0x1fe0 && address < 0x1ff8) {
|
if(address >= 0x1fe0 && address < 0x1ff8) {
|
||||||
int slot = (address >> 3)&3;
|
int slot = (address >> 3)&3;
|
||||||
rom_ptr_[slot] = rom_.data() + ((address & 7) * 1024);
|
rom_ptr_[slot] = rom_base_ + ((address & 7) * 1024);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(isReadOperation(operation)) {
|
if(isReadOperation(operation)) {
|
||||||
@@ -41,6 +42,7 @@ class CartridgeParkerBros: public Cartridge<CartridgeParkerBros> {
|
|||||||
uint8_t *rom_ptr_[4];
|
uint8_t *rom_ptr_[4];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif /* Atari2600_CartridgeParkerBros_hpp */
|
#endif /* Atari2600_CartridgeParkerBros_hpp */
|
||||||
@@ -10,17 +10,13 @@
|
|||||||
#define Atari2600_CartridgePitfall2_hpp
|
#define Atari2600_CartridgePitfall2_hpp
|
||||||
|
|
||||||
namespace Atari2600 {
|
namespace Atari2600 {
|
||||||
|
namespace Cartridge {
|
||||||
|
|
||||||
class CartridgePitfall2: public Cartridge<CartridgePitfall2> {
|
class Pitfall2: public BusExtender {
|
||||||
public:
|
public:
|
||||||
CartridgePitfall2(const std::vector<uint8_t> &rom) :
|
Pitfall2(uint8_t *rom_base, std::size_t rom_size) :
|
||||||
Cartridge(rom),
|
BusExtender(rom_base, rom_size),
|
||||||
random_number_generator_(0),
|
rom_ptr_(rom_base) {}
|
||||||
featcher_address_{0, 0, 0, 0, 0, 0, 0, 0},
|
|
||||||
mask_{0, 0, 0, 0, 0, 0, 0, 0},
|
|
||||||
cycles_since_audio_update_(0) {
|
|
||||||
rom_ptr_ = rom_.data();
|
|
||||||
}
|
|
||||||
|
|
||||||
void advance_cycles(int cycles) {
|
void advance_cycles(int cycles) {
|
||||||
cycles_since_audio_update_ += cycles;
|
cycles_since_audio_update_ += cycles;
|
||||||
@@ -32,14 +28,14 @@ class CartridgePitfall2: public Cartridge<CartridgePitfall2> {
|
|||||||
|
|
||||||
switch(address) {
|
switch(address) {
|
||||||
|
|
||||||
#pragma mark - Reads
|
// MARK: - Reads
|
||||||
|
|
||||||
// The random number generator
|
// The random number generator
|
||||||
case 0x1000: case 0x1001: case 0x1002: case 0x1003: case 0x1004:
|
case 0x1000: case 0x1001: case 0x1002: case 0x1003: case 0x1004:
|
||||||
if(isReadOperation(operation)) {
|
if(isReadOperation(operation)) {
|
||||||
*value = random_number_generator_;
|
*value = random_number_generator_;
|
||||||
}
|
}
|
||||||
random_number_generator_ = (uint8_t)(
|
random_number_generator_ = static_cast<uint8_t>(
|
||||||
(random_number_generator_ << 1) |
|
(random_number_generator_ << 1) |
|
||||||
(~( (random_number_generator_ >> 7) ^
|
(~( (random_number_generator_ >> 7) ^
|
||||||
(random_number_generator_ >> 5) ^
|
(random_number_generator_ >> 5) ^
|
||||||
@@ -53,14 +49,14 @@ class CartridgePitfall2: public Cartridge<CartridgePitfall2> {
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case 0x1008: case 0x1009: case 0x100a: case 0x100b: case 0x100c: case 0x100d: case 0x100e: case 0x100f:
|
case 0x1008: case 0x1009: case 0x100a: case 0x100b: case 0x100c: case 0x100d: case 0x100e: case 0x100f:
|
||||||
*value = rom_[8192 + address_for_counter(address & 7)];
|
*value = rom_base_[8192 + address_for_counter(address & 7)];
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 0x1010: case 0x1011: case 0x1012: case 0x1013: case 0x1014: case 0x1015: case 0x1016: case 0x1017:
|
case 0x1010: case 0x1011: case 0x1012: case 0x1013: case 0x1014: case 0x1015: case 0x1016: case 0x1017:
|
||||||
*value = rom_[8192 + address_for_counter(address & 7)] & mask_[address & 7];
|
*value = rom_base_[8192 + address_for_counter(address & 7)] & mask_[address & 7];
|
||||||
break;
|
break;
|
||||||
|
|
||||||
#pragma mark - Writes
|
// MARK: - Writes
|
||||||
|
|
||||||
case 0x1040: case 0x1041: case 0x1042: case 0x1043: case 0x1044: case 0x1045: case 0x1046: case 0x1047:
|
case 0x1040: case 0x1041: case 0x1042: case 0x1043: case 0x1044: case 0x1045: case 0x1046: case 0x1047:
|
||||||
top_[address & 7] = *value;
|
top_[address & 7] = *value;
|
||||||
@@ -73,18 +69,18 @@ class CartridgePitfall2: public Cartridge<CartridgePitfall2> {
|
|||||||
mask_[address & 7] = 0x00;
|
mask_[address & 7] = 0x00;
|
||||||
break;
|
break;
|
||||||
case 0x1058: case 0x1059: case 0x105a: case 0x105b: case 0x105c: case 0x105d: case 0x105e: case 0x105f:
|
case 0x1058: case 0x1059: case 0x105a: case 0x105b: case 0x105c: case 0x105d: case 0x105e: case 0x105f:
|
||||||
featcher_address_[address & 7] = (featcher_address_[address & 7] & 0x00ff) | (uint16_t)(*value << 8);
|
featcher_address_[address & 7] = (featcher_address_[address & 7] & 0x00ff) | static_cast<uint16_t>(*value << 8);
|
||||||
break;
|
break;
|
||||||
case 0x1070: case 0x1071: case 0x1072: case 0x1073: case 0x1074: case 0x1075: case 0x1076: case 0x1077:
|
case 0x1070: case 0x1071: case 0x1072: case 0x1073: case 0x1074: case 0x1075: case 0x1076: case 0x1077:
|
||||||
random_number_generator_ = 0;
|
random_number_generator_ = 0;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
#pragma mark - Paging
|
// MARK: - Paging
|
||||||
|
|
||||||
case 0x1ff8: rom_ptr_ = rom_.data(); break;
|
case 0x1ff8: rom_ptr_ = rom_base_; break;
|
||||||
case 0x1ff9: rom_ptr_ = rom_.data() + 4096; break;
|
case 0x1ff9: rom_ptr_ = rom_base_ + 4096; break;
|
||||||
|
|
||||||
#pragma mark - Business as usual
|
// MARK: - Business as usual
|
||||||
|
|
||||||
default:
|
default:
|
||||||
if(isReadOperation(operation)) {
|
if(isReadOperation(operation)) {
|
||||||
@@ -119,15 +115,16 @@ class CartridgePitfall2: public Cartridge<CartridgePitfall2> {
|
|||||||
return level_table[table_position];
|
return level_table[table_position];
|
||||||
}
|
}
|
||||||
|
|
||||||
uint16_t featcher_address_[8];
|
uint16_t featcher_address_[8] = {0, 0, 0, 0, 0, 0, 0, 0};
|
||||||
uint8_t top_[8], bottom_[8], mask_[8];
|
uint8_t top_[8], bottom_[8], mask_[8] = {0, 0, 0, 0, 0, 0, 0, 0};
|
||||||
uint8_t music_mode_[3];
|
uint8_t music_mode_[3];
|
||||||
uint8_t random_number_generator_;
|
uint8_t random_number_generator_ = 0;
|
||||||
uint8_t *rom_ptr_;
|
uint8_t *rom_ptr_;
|
||||||
uint8_t audio_channel_[3];
|
uint8_t audio_channel_[3];
|
||||||
Cycles cycles_since_audio_update_;
|
Cycles cycles_since_audio_update_ = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif /* Atari2600_CartridgePitfall2_hpp */
|
#endif /* Atari2600_CartridgePitfall2_hpp */
|
||||||
@@ -12,19 +12,20 @@
|
|||||||
#include "Cartridge.hpp"
|
#include "Cartridge.hpp"
|
||||||
|
|
||||||
namespace Atari2600 {
|
namespace Atari2600 {
|
||||||
|
namespace Cartridge {
|
||||||
|
|
||||||
class CartridgeTigervision: public Cartridge<CartridgeTigervision> {
|
class Tigervision: public BusExtender {
|
||||||
public:
|
public:
|
||||||
CartridgeTigervision(const std::vector<uint8_t> &rom) :
|
Tigervision(uint8_t *rom_base, std::size_t rom_size) :
|
||||||
Cartridge(rom) {
|
BusExtender(rom_base, rom_size) {
|
||||||
rom_ptr_[0] = rom_.data() + rom_.size() - 4096;
|
rom_ptr_[0] = rom_base + rom_size - 4096;
|
||||||
rom_ptr_[1] = rom_ptr_[0] + 2048;
|
rom_ptr_[1] = rom_ptr_[0] + 2048;
|
||||||
}
|
}
|
||||||
|
|
||||||
void perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value) {
|
void perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value) {
|
||||||
if((address&0x1fff) == 0x3f) {
|
if((address&0x1fff) == 0x3f) {
|
||||||
int offset = ((*value) * 2048) & (rom_.size() - 1);
|
int offset = ((*value) * 2048) & (rom_size_ - 1);
|
||||||
rom_ptr_[0] = rom_.data() + offset;
|
rom_ptr_[0] = rom_base_ + offset;
|
||||||
return;
|
return;
|
||||||
} else if((address&0x1000) && isReadOperation(operation)) {
|
} else if((address&0x1000) && isReadOperation(operation)) {
|
||||||
*value = rom_ptr_[(address >> 11)&1][address & 2047];
|
*value = rom_ptr_[(address >> 11)&1][address & 2047];
|
||||||
@@ -35,6 +36,7 @@ class CartridgeTigervision: public Cartridge<CartridgeTigervision> {
|
|||||||
uint8_t *rom_ptr_[2];
|
uint8_t *rom_ptr_[2];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif /* Atari2600_CartridgeTigervision_hpp */
|
#endif /* Atari2600_CartridgeTigervision_hpp */
|
||||||
@@ -12,19 +12,20 @@
|
|||||||
#include "Cartridge.hpp"
|
#include "Cartridge.hpp"
|
||||||
|
|
||||||
namespace Atari2600 {
|
namespace Atari2600 {
|
||||||
|
namespace Cartridge {
|
||||||
|
|
||||||
class CartridgeUnpaged: public Cartridge<CartridgeUnpaged> {
|
class Unpaged: public BusExtender {
|
||||||
public:
|
public:
|
||||||
CartridgeUnpaged(const std::vector<uint8_t> &rom) :
|
Unpaged(uint8_t *rom_base, std::size_t rom_size) : BusExtender(rom_base, rom_size) {}
|
||||||
Cartridge(rom) {}
|
|
||||||
|
|
||||||
void perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value) {
|
void perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value) {
|
||||||
if(isReadOperation(operation) && (address & 0x1000)) {
|
if(isReadOperation(operation) && (address & 0x1000)) {
|
||||||
*value = rom_[address & (rom_.size() - 1)];
|
*value = rom_base_[address & (rom_size_ - 1)];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif /* Atari2600_CartridgeUnpaged_hpp */
|
#endif /* Atari2600_CartridgeUnpaged_hpp */
|
||||||
@@ -9,6 +9,8 @@
|
|||||||
#ifndef Atari2600_PIA_h
|
#ifndef Atari2600_PIA_h
|
||||||
#define Atari2600_PIA_h
|
#define Atari2600_PIA_h
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
#include "../../Components/6532/6532.hpp"
|
#include "../../Components/6532/6532.hpp"
|
||||||
|
|
||||||
namespace Atari2600 {
|
namespace Atari2600 {
|
||||||
|
|||||||
@@ -7,7 +7,9 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
#include "TIA.hpp"
|
#include "TIA.hpp"
|
||||||
|
|
||||||
#include <cassert>
|
#include <cassert>
|
||||||
|
#include <cstring>
|
||||||
|
|
||||||
using namespace Atari2600;
|
using namespace Atari2600;
|
||||||
namespace {
|
namespace {
|
||||||
@@ -20,36 +22,27 @@ namespace {
|
|||||||
uint8_t reverse_table[256];
|
uint8_t reverse_table[256];
|
||||||
}
|
}
|
||||||
|
|
||||||
TIA::TIA(bool create_crt) :
|
TIA::TIA(bool create_crt) {
|
||||||
horizontal_counter_(0),
|
|
||||||
pixels_start_location_(0),
|
|
||||||
output_mode_(0),
|
|
||||||
pixel_target_(nullptr),
|
|
||||||
background_{0, 0},
|
|
||||||
background_half_mask_(0),
|
|
||||||
horizontal_blank_extend_(false),
|
|
||||||
collision_flags_(0)
|
|
||||||
{
|
|
||||||
if(create_crt) {
|
if(create_crt) {
|
||||||
crt_.reset(new Outputs::CRT::CRT(cycles_per_line * 2 - 1, 1, Outputs::CRT::DisplayType::NTSC60, 1));
|
crt_.reset(new Outputs::CRT::CRT(cycles_per_line * 2 - 1, 1, Outputs::CRT::DisplayType::NTSC60, 1));
|
||||||
crt_->set_output_device(Outputs::CRT::Television);
|
crt_->set_output_device(Outputs::CRT::OutputDevice::Television);
|
||||||
set_output_mode(OutputMode::NTSC);
|
set_output_mode(OutputMode::NTSC);
|
||||||
}
|
}
|
||||||
|
|
||||||
for(int c = 0; c < 256; c++) {
|
for(int c = 0; c < 256; c++) {
|
||||||
reverse_table[c] = (uint8_t)(
|
reverse_table[c] = static_cast<uint8_t>(
|
||||||
((c & 0x01) << 7) | ((c & 0x02) << 5) | ((c & 0x04) << 3) | ((c & 0x08) << 1) |
|
((c & 0x01) << 7) | ((c & 0x02) << 5) | ((c & 0x04) << 3) | ((c & 0x08) << 1) |
|
||||||
((c & 0x10) >> 1) | ((c & 0x20) >> 3) | ((c & 0x40) >> 5) | ((c & 0x80) >> 7)
|
((c & 0x10) >> 1) | ((c & 0x20) >> 3) | ((c & 0x40) >> 5) | ((c & 0x80) >> 7)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
for(int c = 0; c < 64; c++) {
|
for(int c = 0; c < 64; c++) {
|
||||||
bool has_playfield = c & (int)(CollisionType::Playfield);
|
bool has_playfield = c & static_cast<int>(CollisionType::Playfield);
|
||||||
bool has_ball = c & (int)(CollisionType::Ball);
|
bool has_ball = c & static_cast<int>(CollisionType::Ball);
|
||||||
bool has_player0 = c & (int)(CollisionType::Player0);
|
bool has_player0 = c & static_cast<int>(CollisionType::Player0);
|
||||||
bool has_player1 = c & (int)(CollisionType::Player1);
|
bool has_player1 = c & static_cast<int>(CollisionType::Player1);
|
||||||
bool has_missile0 = c & (int)(CollisionType::Missile0);
|
bool has_missile0 = c & static_cast<int>(CollisionType::Missile0);
|
||||||
bool has_missile1 = c & (int)(CollisionType::Missile1);
|
bool has_missile1 = c & static_cast<int>(CollisionType::Missile1);
|
||||||
|
|
||||||
uint8_t collision_registers[8];
|
uint8_t collision_registers[8];
|
||||||
collision_registers[0] = ((has_missile0 && has_player1) ? 0x80 : 0x00) | ((has_missile0 && has_player0) ? 0x40 : 0x00);
|
collision_registers[0] = ((has_missile0 && has_player1) ? 0x80 : 0x00) | ((has_missile0 && has_player0) ? 0x40 : 0x00);
|
||||||
@@ -71,51 +64,51 @@ TIA::TIA(bool create_crt) :
|
|||||||
(collision_registers[7] << 8);
|
(collision_registers[7] << 8);
|
||||||
|
|
||||||
// all priority modes show the background if nothing else is present
|
// all priority modes show the background if nothing else is present
|
||||||
colour_mask_by_mode_collision_flags_[(int)ColourMode::Standard][c] =
|
colour_mask_by_mode_collision_flags_[static_cast<int>(ColourMode::Standard)][c] =
|
||||||
colour_mask_by_mode_collision_flags_[(int)ColourMode::ScoreLeft][c] =
|
colour_mask_by_mode_collision_flags_[static_cast<int>(ColourMode::ScoreLeft)][c] =
|
||||||
colour_mask_by_mode_collision_flags_[(int)ColourMode::ScoreRight][c] =
|
colour_mask_by_mode_collision_flags_[static_cast<int>(ColourMode::ScoreRight)][c] =
|
||||||
colour_mask_by_mode_collision_flags_[(int)ColourMode::OnTop][c] = (uint8_t)ColourIndex::Background;
|
colour_mask_by_mode_collision_flags_[static_cast<int>(ColourMode::OnTop)][c] = static_cast<uint8_t>(ColourIndex::Background);
|
||||||
|
|
||||||
// test 1 for standard priority: if there is a playfield or ball pixel, plot that colour
|
// test 1 for standard priority: if there is a playfield or ball pixel, plot that colour
|
||||||
if(has_playfield || has_ball) {
|
if(has_playfield || has_ball) {
|
||||||
colour_mask_by_mode_collision_flags_[(int)ColourMode::Standard][c] = (uint8_t)ColourIndex::PlayfieldBall;
|
colour_mask_by_mode_collision_flags_[static_cast<int>(ColourMode::Standard)][c] = static_cast<uint8_t>(ColourIndex::PlayfieldBall);
|
||||||
}
|
}
|
||||||
|
|
||||||
// test 1 for score mode: if there is a ball pixel, plot that colour
|
// test 1 for score mode: if there is a ball pixel, plot that colour
|
||||||
if(has_ball) {
|
if(has_ball) {
|
||||||
colour_mask_by_mode_collision_flags_[(int)ColourMode::ScoreLeft][c] =
|
colour_mask_by_mode_collision_flags_[static_cast<int>(ColourMode::ScoreLeft)][c] =
|
||||||
colour_mask_by_mode_collision_flags_[(int)ColourMode::ScoreRight][c] = (uint8_t)ColourIndex::PlayfieldBall;
|
colour_mask_by_mode_collision_flags_[static_cast<int>(ColourMode::ScoreRight)][c] = static_cast<uint8_t>(ColourIndex::PlayfieldBall);
|
||||||
}
|
}
|
||||||
|
|
||||||
// test 1 for on-top mode, test 2 for everbody else: if there is a player 1 or missile 1 pixel, plot that colour
|
// test 1 for on-top mode, test 2 for everbody else: if there is a player 1 or missile 1 pixel, plot that colour
|
||||||
if(has_player1 || has_missile1) {
|
if(has_player1 || has_missile1) {
|
||||||
colour_mask_by_mode_collision_flags_[(int)ColourMode::Standard][c] =
|
colour_mask_by_mode_collision_flags_[static_cast<int>(ColourMode::Standard)][c] =
|
||||||
colour_mask_by_mode_collision_flags_[(int)ColourMode::ScoreLeft][c] =
|
colour_mask_by_mode_collision_flags_[static_cast<int>(ColourMode::ScoreLeft)][c] =
|
||||||
colour_mask_by_mode_collision_flags_[(int)ColourMode::ScoreRight][c] =
|
colour_mask_by_mode_collision_flags_[static_cast<int>(ColourMode::ScoreRight)][c] =
|
||||||
colour_mask_by_mode_collision_flags_[(int)ColourMode::OnTop][c] = (uint8_t)ColourIndex::PlayerMissile1;
|
colour_mask_by_mode_collision_flags_[static_cast<int>(ColourMode::OnTop)][c] = static_cast<uint8_t>(ColourIndex::PlayerMissile1);
|
||||||
}
|
}
|
||||||
|
|
||||||
// in the right-hand side of score mode, the playfield has the same priority as player 1
|
// in the right-hand side of score mode, the playfield has the same priority as player 1
|
||||||
if(has_playfield) {
|
if(has_playfield) {
|
||||||
colour_mask_by_mode_collision_flags_[(int)ColourMode::ScoreRight][c] = (uint8_t)ColourIndex::PlayerMissile1;
|
colour_mask_by_mode_collision_flags_[static_cast<int>(ColourMode::ScoreRight)][c] = static_cast<uint8_t>(ColourIndex::PlayerMissile1);
|
||||||
}
|
}
|
||||||
|
|
||||||
// next test for everybody: if there is a player 0 or missile 0 pixel, plot that colour instead
|
// next test for everybody: if there is a player 0 or missile 0 pixel, plot that colour instead
|
||||||
if(has_player0 || has_missile0) {
|
if(has_player0 || has_missile0) {
|
||||||
colour_mask_by_mode_collision_flags_[(int)ColourMode::Standard][c] =
|
colour_mask_by_mode_collision_flags_[static_cast<int>(ColourMode::Standard)][c] =
|
||||||
colour_mask_by_mode_collision_flags_[(int)ColourMode::ScoreLeft][c] =
|
colour_mask_by_mode_collision_flags_[static_cast<int>(ColourMode::ScoreLeft)][c] =
|
||||||
colour_mask_by_mode_collision_flags_[(int)ColourMode::ScoreRight][c] =
|
colour_mask_by_mode_collision_flags_[static_cast<int>(ColourMode::ScoreRight)][c] =
|
||||||
colour_mask_by_mode_collision_flags_[(int)ColourMode::OnTop][c] = (uint8_t)ColourIndex::PlayerMissile0;
|
colour_mask_by_mode_collision_flags_[static_cast<int>(ColourMode::OnTop)][c] = static_cast<uint8_t>(ColourIndex::PlayerMissile0);
|
||||||
}
|
}
|
||||||
|
|
||||||
// if this is the left-hand side of score mode, the playfield has the same priority as player 0
|
// if this is the left-hand side of score mode, the playfield has the same priority as player 0
|
||||||
if(has_playfield) {
|
if(has_playfield) {
|
||||||
colour_mask_by_mode_collision_flags_[(int)ColourMode::ScoreLeft][c] = (uint8_t)ColourIndex::PlayerMissile0;
|
colour_mask_by_mode_collision_flags_[static_cast<int>(ColourMode::ScoreLeft)][c] = static_cast<uint8_t>(ColourIndex::PlayerMissile0);
|
||||||
}
|
}
|
||||||
|
|
||||||
// a final test for 'on top' priority mode: if the playfield or ball are visible, prefer that colour to all others
|
// a final test for 'on top' priority mode: if the playfield or ball are visible, prefer that colour to all others
|
||||||
if(has_playfield || has_ball) {
|
if(has_playfield || has_ball) {
|
||||||
colour_mask_by_mode_collision_flags_[(int)ColourMode::OnTop][c] = (uint8_t)ColourIndex::PlayfieldBall;
|
colour_mask_by_mode_collision_flags_[static_cast<int>(ColourMode::OnTop)][c] = static_cast<uint8_t>(ColourIndex::PlayfieldBall);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -162,7 +155,7 @@ void TIA::set_output_mode(Atari2600::TIA::OutputMode output_mode) {
|
|||||||
// cycles_per_line * 2 cycles of information from one sync edge to the next
|
// cycles_per_line * 2 cycles of information from one sync edge to the next
|
||||||
crt_->set_new_display_type(cycles_per_line * 2 - 1, display_type);
|
crt_->set_new_display_type(cycles_per_line * 2 - 1, display_type);
|
||||||
|
|
||||||
/* speaker_->set_input_rate((float)(get_clock_rate() / 38.0));*/
|
/* speaker_->set_input_rate(static_cast<float>(get_clock_rate() / 38.0));*/
|
||||||
}
|
}
|
||||||
|
|
||||||
void TIA::run_for(const Cycles cycles) {
|
void TIA::run_for(const Cycles cycles) {
|
||||||
@@ -203,23 +196,23 @@ int TIA::get_cycles_until_horizontal_blank(const Cycles from_offset) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void TIA::set_background_colour(uint8_t colour) {
|
void TIA::set_background_colour(uint8_t colour) {
|
||||||
colour_palette_[(int)ColourIndex::Background] = colour;
|
colour_palette_[static_cast<int>(ColourIndex::Background)] = colour;
|
||||||
}
|
}
|
||||||
|
|
||||||
void TIA::set_playfield(uint16_t offset, uint8_t value) {
|
void TIA::set_playfield(uint16_t offset, uint8_t value) {
|
||||||
assert(offset >= 0 && offset < 3);
|
assert(offset >= 0 && offset < 3);
|
||||||
switch(offset) {
|
switch(offset) {
|
||||||
case 0:
|
case 0:
|
||||||
background_[1] = (background_[1] & 0x0ffff) | ((uint32_t)reverse_table[value & 0xf0] << 16);
|
background_[1] = (background_[1] & 0x0ffff) | (static_cast<uint32_t>(reverse_table[value & 0xf0]) << 16);
|
||||||
background_[0] = (background_[0] & 0xffff0) | (uint32_t)(value >> 4);
|
background_[0] = (background_[0] & 0xffff0) | static_cast<uint32_t>(value >> 4);
|
||||||
break;
|
break;
|
||||||
case 1:
|
case 1:
|
||||||
background_[1] = (background_[1] & 0xf00ff) | ((uint32_t)value << 8);
|
background_[1] = (background_[1] & 0xf00ff) | (static_cast<uint32_t>(value) << 8);
|
||||||
background_[0] = (background_[0] & 0xff00f) | ((uint32_t)reverse_table[value] << 4);
|
background_[0] = (background_[0] & 0xff00f) | (static_cast<uint32_t>(reverse_table[value]) << 4);
|
||||||
break;
|
break;
|
||||||
case 2:
|
case 2:
|
||||||
background_[1] = (background_[1] & 0xfff00) | reverse_table[value];
|
background_[1] = (background_[1] & 0xfff00) | reverse_table[value];
|
||||||
background_[0] = (background_[0] & 0x00fff) | ((uint32_t)value << 12);
|
background_[0] = (background_[0] & 0x00fff) | (static_cast<uint32_t>(value) << 12);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -243,7 +236,7 @@ void TIA::set_playfield_control_and_ball_size(uint8_t value) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void TIA::set_playfield_ball_colour(uint8_t colour) {
|
void TIA::set_playfield_ball_colour(uint8_t colour) {
|
||||||
colour_palette_[(int)ColourIndex::PlayfieldBall] = colour;
|
colour_palette_[static_cast<int>(ColourIndex::PlayfieldBall)] = colour;
|
||||||
}
|
}
|
||||||
|
|
||||||
void TIA::set_player_number_and_size(int player, uint8_t value) {
|
void TIA::set_player_number_and_size(int player, uint8_t value) {
|
||||||
@@ -305,7 +298,7 @@ void TIA::set_player_motion(int player, uint8_t motion) {
|
|||||||
|
|
||||||
void TIA::set_player_missile_colour(int player, uint8_t colour) {
|
void TIA::set_player_missile_colour(int player, uint8_t colour) {
|
||||||
assert(player >= 0 && player < 2);
|
assert(player >= 0 && player < 2);
|
||||||
colour_palette_[(int)ColourIndex::PlayerMissile0 + player] = colour;
|
colour_palette_[static_cast<int>(ColourIndex::PlayerMissile0) + player] = colour;
|
||||||
}
|
}
|
||||||
|
|
||||||
void TIA::set_missile_enable(int missile, bool enabled) {
|
void TIA::set_missile_enable(int missile, bool enabled) {
|
||||||
@@ -360,7 +353,7 @@ void TIA::clear_motion() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
uint8_t TIA::get_collision_flags(int offset) {
|
uint8_t TIA::get_collision_flags(int offset) {
|
||||||
return (uint8_t)((collision_flags_ >> (offset << 1)) << 6) & 0xc0;
|
return static_cast<uint8_t>((collision_flags_ >> (offset << 1)) << 6) & 0xc0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void TIA::clear_collision_flags() {
|
void TIA::clear_collision_flags() {
|
||||||
@@ -388,7 +381,7 @@ void TIA::output_for_cycles(int number_of_cycles) {
|
|||||||
|
|
||||||
if(!output_cursor) {
|
if(!output_cursor) {
|
||||||
if(line_end_function_) line_end_function_(collision_buffer_);
|
if(line_end_function_) line_end_function_(collision_buffer_);
|
||||||
memset(collision_buffer_, 0, sizeof(collision_buffer_));
|
std::memset(collision_buffer_, 0, sizeof(collision_buffer_));
|
||||||
|
|
||||||
ball_.motion_time %= 228;
|
ball_.motion_time %= 228;
|
||||||
player_[0].motion_time %= 228;
|
player_[0].motion_time %= 228;
|
||||||
@@ -401,22 +394,22 @@ void TIA::output_for_cycles(int number_of_cycles) {
|
|||||||
int latent_start = output_cursor + 4;
|
int latent_start = output_cursor + 4;
|
||||||
int latent_end = horizontal_counter_ + 4;
|
int latent_end = horizontal_counter_ + 4;
|
||||||
draw_playfield(latent_start, latent_end);
|
draw_playfield(latent_start, latent_end);
|
||||||
draw_object<Player>(player_[0], (uint8_t)CollisionType::Player0, output_cursor, horizontal_counter_);
|
draw_object<Player>(player_[0], static_cast<uint8_t>(CollisionType::Player0), output_cursor, horizontal_counter_);
|
||||||
draw_object<Player>(player_[1], (uint8_t)CollisionType::Player1, output_cursor, horizontal_counter_);
|
draw_object<Player>(player_[1], static_cast<uint8_t>(CollisionType::Player1), output_cursor, horizontal_counter_);
|
||||||
draw_missile(missile_[0], player_[0], (uint8_t)CollisionType::Missile0, output_cursor, horizontal_counter_);
|
draw_missile(missile_[0], player_[0], static_cast<uint8_t>(CollisionType::Missile0), output_cursor, horizontal_counter_);
|
||||||
draw_missile(missile_[1], player_[1], (uint8_t)CollisionType::Missile1, output_cursor, horizontal_counter_);
|
draw_missile(missile_[1], player_[1], static_cast<uint8_t>(CollisionType::Missile1), output_cursor, horizontal_counter_);
|
||||||
draw_object<Ball>(ball_, (uint8_t)CollisionType::Ball, output_cursor, horizontal_counter_);
|
draw_object<Ball>(ball_, static_cast<uint8_t>(CollisionType::Ball), output_cursor, horizontal_counter_);
|
||||||
|
|
||||||
// convert to television signals
|
// convert to television signals
|
||||||
|
|
||||||
#define Period(function, target) \
|
#define Period(function, target) \
|
||||||
if(output_cursor < target) { \
|
if(output_cursor < target) { \
|
||||||
if(horizontal_counter_ <= target) { \
|
if(horizontal_counter_ <= target) { \
|
||||||
if(crt_) crt_->function((unsigned int)((horizontal_counter_ - output_cursor) * 2)); \
|
if(crt_) crt_->function(static_cast<unsigned int>((horizontal_counter_ - output_cursor) * 2)); \
|
||||||
horizontal_counter_ %= cycles_per_line; \
|
horizontal_counter_ %= cycles_per_line; \
|
||||||
return; \
|
return; \
|
||||||
} else { \
|
} else { \
|
||||||
if(crt_) crt_->function((unsigned int)((target - output_cursor) * 2)); \
|
if(crt_) crt_->function(static_cast<unsigned int>((target - output_cursor) * 2)); \
|
||||||
output_cursor = target; \
|
output_cursor = target; \
|
||||||
} \
|
} \
|
||||||
}
|
}
|
||||||
@@ -442,12 +435,12 @@ void TIA::output_for_cycles(int number_of_cycles) {
|
|||||||
if(output_mode_ & blank_flag) {
|
if(output_mode_ & blank_flag) {
|
||||||
if(pixel_target_) {
|
if(pixel_target_) {
|
||||||
output_pixels(pixels_start_location_, output_cursor);
|
output_pixels(pixels_start_location_, output_cursor);
|
||||||
if(crt_) crt_->output_data((unsigned int)(output_cursor - pixels_start_location_) * 2, 2);
|
if(crt_) crt_->output_data(static_cast<unsigned int>(output_cursor - pixels_start_location_) * 2, 2);
|
||||||
pixel_target_ = nullptr;
|
pixel_target_ = nullptr;
|
||||||
pixels_start_location_ = 0;
|
pixels_start_location_ = 0;
|
||||||
}
|
}
|
||||||
int duration = std::min(228, horizontal_counter_) - output_cursor;
|
int duration = std::min(228, horizontal_counter_) - output_cursor;
|
||||||
if(crt_) crt_->output_blank((unsigned int)(duration * 2));
|
if(crt_) crt_->output_blank(static_cast<unsigned int>(duration * 2));
|
||||||
} else {
|
} else {
|
||||||
if(!pixels_start_location_ && crt_) {
|
if(!pixels_start_location_ && crt_) {
|
||||||
pixels_start_location_ = output_cursor;
|
pixels_start_location_ = output_cursor;
|
||||||
@@ -464,7 +457,7 @@ void TIA::output_for_cycles(int number_of_cycles) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if(horizontal_counter_ == cycles_per_line && crt_) {
|
if(horizontal_counter_ == cycles_per_line && crt_) {
|
||||||
crt_->output_data((unsigned int)(output_cursor - pixels_start_location_) * 2, 2);
|
crt_->output_data(static_cast<unsigned int>(output_cursor - pixels_start_location_) * 2, 2);
|
||||||
pixel_target_ = nullptr;
|
pixel_target_ = nullptr;
|
||||||
pixels_start_location_ = 0;
|
pixels_start_location_ = 0;
|
||||||
}
|
}
|
||||||
@@ -490,18 +483,18 @@ void TIA::output_pixels(int start, int end) {
|
|||||||
if(playfield_priority_ == PlayfieldPriority::Score) {
|
if(playfield_priority_ == PlayfieldPriority::Score) {
|
||||||
while(start < end && start < first_pixel_cycle + 80) {
|
while(start < end && start < first_pixel_cycle + 80) {
|
||||||
uint8_t buffer_value = collision_buffer_[start - first_pixel_cycle];
|
uint8_t buffer_value = collision_buffer_[start - first_pixel_cycle];
|
||||||
pixel_target_[target_position] = colour_palette_[colour_mask_by_mode_collision_flags_[(int)ColourMode::ScoreLeft][buffer_value]];
|
pixel_target_[target_position] = colour_palette_[colour_mask_by_mode_collision_flags_[static_cast<int>(ColourMode::ScoreLeft)][buffer_value]];
|
||||||
start++;
|
start++;
|
||||||
target_position++;
|
target_position++;
|
||||||
}
|
}
|
||||||
while(start < end) {
|
while(start < end) {
|
||||||
uint8_t buffer_value = collision_buffer_[start - first_pixel_cycle];
|
uint8_t buffer_value = collision_buffer_[start - first_pixel_cycle];
|
||||||
pixel_target_[target_position] = colour_palette_[colour_mask_by_mode_collision_flags_[(int)ColourMode::ScoreRight][buffer_value]];
|
pixel_target_[target_position] = colour_palette_[colour_mask_by_mode_collision_flags_[static_cast<int>(ColourMode::ScoreRight)][buffer_value]];
|
||||||
start++;
|
start++;
|
||||||
target_position++;
|
target_position++;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
int table_index = (int)((playfield_priority_ == PlayfieldPriority::Standard) ? ColourMode::Standard : ColourMode::OnTop);
|
int table_index = static_cast<int>((playfield_priority_ == PlayfieldPriority::Standard) ? ColourMode::Standard : ColourMode::OnTop);
|
||||||
while(start < end) {
|
while(start < end) {
|
||||||
uint8_t buffer_value = collision_buffer_[start - first_pixel_cycle];
|
uint8_t buffer_value = collision_buffer_[start - first_pixel_cycle];
|
||||||
pixel_target_[target_position] = colour_palette_[colour_mask_by_mode_collision_flags_[table_index][buffer_value]];
|
pixel_target_[target_position] = colour_palette_[colour_mask_by_mode_collision_flags_[table_index][buffer_value]];
|
||||||
@@ -538,7 +531,7 @@ void TIA::output_line() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#pragma mark - Playfield output
|
// MARK: - Playfield output
|
||||||
|
|
||||||
void TIA::draw_playfield(int start, int end) {
|
void TIA::draw_playfield(int start, int end) {
|
||||||
// don't do anything if this window ends too early
|
// don't do anything if this window ends too early
|
||||||
@@ -553,12 +546,12 @@ void TIA::draw_playfield(int start, int end) {
|
|||||||
while(aligned_position < end) {
|
while(aligned_position < end) {
|
||||||
int offset = (aligned_position - first_pixel_cycle) >> 2;
|
int offset = (aligned_position - first_pixel_cycle) >> 2;
|
||||||
uint32_t value = ((background_[(offset/20)&background_half_mask_] >> (offset%20))&1) * 0x01010101;
|
uint32_t value = ((background_[(offset/20)&background_half_mask_] >> (offset%20))&1) * 0x01010101;
|
||||||
*(uint32_t *)&collision_buffer_[aligned_position - first_pixel_cycle] |= value;
|
*reinterpret_cast<uint32_t *>(&collision_buffer_[aligned_position - first_pixel_cycle]) |= value;
|
||||||
aligned_position += 4;
|
aligned_position += 4;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#pragma mark - Motion
|
// MARK: - Motion
|
||||||
|
|
||||||
template<class T> void TIA::perform_motion_step(T &object) {
|
template<class T> void TIA::perform_motion_step(T &object) {
|
||||||
if((object.motion_step ^ (object.motion ^ 8)) == 0xf) {
|
if((object.motion_step ^ (object.motion ^ 8)) == 0xf) {
|
||||||
@@ -666,7 +659,7 @@ template<class T> void TIA::draw_object_visible(T &object, const uint8_t collisi
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#pragma mark - Missile drawing
|
// MARK: - Missile drawing
|
||||||
|
|
||||||
void TIA::draw_missile(Missile &missile, Player &player, const uint8_t collision_identity, int start, int end) {
|
void TIA::draw_missile(Missile &missile, Player &player, const uint8_t collision_identity, int start, int end) {
|
||||||
if(!missile.locked_to_player || player.latched_pixel4_time < 0) {
|
if(!missile.locked_to_player || player.latched_pixel4_time < 0) {
|
||||||
|
|||||||
@@ -73,18 +73,18 @@ class TIA {
|
|||||||
uint8_t get_collision_flags(int offset);
|
uint8_t get_collision_flags(int offset);
|
||||||
void clear_collision_flags();
|
void clear_collision_flags();
|
||||||
|
|
||||||
virtual std::shared_ptr<Outputs::CRT::CRT> get_crt() { return crt_; }
|
Outputs::CRT::CRT *get_crt() { return crt_.get(); }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
TIA(bool create_crt);
|
TIA(bool create_crt);
|
||||||
std::shared_ptr<Outputs::CRT::CRT> crt_;
|
std::unique_ptr<Outputs::CRT::CRT> crt_;
|
||||||
std::function<void(uint8_t *output_buffer)> line_end_function_;
|
std::function<void(uint8_t *output_buffer)> line_end_function_;
|
||||||
|
|
||||||
// the master counter; counts from 0 to 228 with all visible pixels being in the final 160
|
// the master counter; counts from 0 to 228 with all visible pixels being in the final 160
|
||||||
int horizontal_counter_;
|
int horizontal_counter_ = 0;
|
||||||
|
|
||||||
// contains flags to indicate whether sync or blank are currently active
|
// contains flags to indicate whether sync or blank are currently active
|
||||||
int output_mode_;
|
int output_mode_ = 0;
|
||||||
|
|
||||||
// keeps track of the target pixel buffer for this line and when it was acquired, and a corresponding collision buffer
|
// keeps track of the target pixel buffer for this line and when it was acquired, and a corresponding collision buffer
|
||||||
alignas(alignof(uint32_t)) uint8_t collision_buffer_[160];
|
alignas(alignof(uint32_t)) uint8_t collision_buffer_[160];
|
||||||
@@ -97,7 +97,7 @@ class TIA {
|
|||||||
Missile1 = (1 << 5)
|
Missile1 = (1 << 5)
|
||||||
};
|
};
|
||||||
|
|
||||||
int collision_flags_;
|
int collision_flags_ = 0;
|
||||||
int collision_flags_by_buffer_vaules_[64];
|
int collision_flags_by_buffer_vaules_[64];
|
||||||
|
|
||||||
// colour mapping tables
|
// colour mapping tables
|
||||||
@@ -118,13 +118,14 @@ class TIA {
|
|||||||
uint8_t colour_palette_[4];
|
uint8_t colour_palette_[4];
|
||||||
|
|
||||||
// playfield state
|
// playfield state
|
||||||
int background_half_mask_;
|
int background_half_mask_ = 0;
|
||||||
enum class PlayfieldPriority {
|
enum class PlayfieldPriority {
|
||||||
Standard,
|
Standard,
|
||||||
Score,
|
Score,
|
||||||
OnTop
|
OnTop
|
||||||
} playfield_priority_;
|
} playfield_priority_;
|
||||||
uint32_t background_[2]; // contains two 20-bit bitfields representing the background state;
|
uint32_t background_[2] = {0, 0};
|
||||||
|
// contains two 20-bit bitfields representing the background state;
|
||||||
// at index 0 is the left-hand side of the playfield with bit 0 being
|
// at index 0 is the left-hand side of the playfield with bit 0 being
|
||||||
// the first bit to display, bit 1 the second, etc. Index 1 contains
|
// the first bit to display, bit 1 the second, etc. Index 1 contains
|
||||||
// a mirror image of index 0. If the playfield is being displayed in
|
// a mirror image of index 0. If the playfield is being displayed in
|
||||||
@@ -135,42 +136,27 @@ class TIA {
|
|||||||
// objects
|
// objects
|
||||||
template<class T> struct Object {
|
template<class T> struct Object {
|
||||||
// the two programmer-set values
|
// the two programmer-set values
|
||||||
int position;
|
int position = 0;
|
||||||
int motion;
|
int motion = 0;
|
||||||
|
|
||||||
// motion_step_ is the current motion counter value; motion_time_ is the next time it will fire
|
// motion_step_ is the current motion counter value; motion_time_ is the next time it will fire
|
||||||
int motion_step;
|
int motion_step = 0;
|
||||||
int motion_time;
|
int motion_time = 0;
|
||||||
|
|
||||||
// indicates whether this object is currently undergoing motion
|
// indicates whether this object is currently undergoing motion
|
||||||
bool is_moving;
|
bool is_moving = false;
|
||||||
|
|
||||||
Object() : position(0), motion(0), motion_step(0), motion_time(0), is_moving(false) {};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// player state
|
// player state
|
||||||
struct Player: public Object<Player> {
|
struct Player: public Object<Player> {
|
||||||
Player() :
|
int adder = 4;
|
||||||
adder(4),
|
int copy_flags = 0; // a bit field, corresponding to the first few values of NUSIZ
|
||||||
copy_flags(0),
|
uint8_t graphic[2] = {0, 0}; // the player graphic; 1 = new, 0 = current
|
||||||
graphic{0, 0},
|
int reverse_mask = false; // 7 for a reflected player, 0 for normal
|
||||||
reverse_mask(false),
|
int graphic_index = 0;
|
||||||
graphic_index(0),
|
|
||||||
pixel_position(32),
|
|
||||||
pixel_counter(0),
|
|
||||||
latched_pixel4_time(-1),
|
|
||||||
copy_index_(0),
|
|
||||||
queue_read_pointer_(0),
|
|
||||||
queue_write_pointer_(0) {}
|
|
||||||
|
|
||||||
int adder;
|
int pixel_position = 32, pixel_counter = 0;
|
||||||
int copy_flags; // a bit field, corresponding to the first few values of NUSIZ
|
int latched_pixel4_time = -1;
|
||||||
uint8_t graphic[2]; // the player graphic; 1 = new, 0 = current
|
|
||||||
int reverse_mask; // 7 for a reflected player, 0 for normal
|
|
||||||
int graphic_index;
|
|
||||||
|
|
||||||
int pixel_position, pixel_counter;
|
|
||||||
int latched_pixel4_time;
|
|
||||||
const bool enqueues = true;
|
const bool enqueues = true;
|
||||||
|
|
||||||
inline void skip_pixels(const int count, int from_horizontal_counter) {
|
inline void skip_pixels(const int count, int from_horizontal_counter) {
|
||||||
@@ -219,15 +205,14 @@ class TIA {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
int copy_index_;
|
int copy_index_ = 0;
|
||||||
struct QueuedPixels {
|
struct QueuedPixels {
|
||||||
int start, end;
|
int start = 0, end = 0;
|
||||||
int pixel_position;
|
int pixel_position = 0;
|
||||||
int adder;
|
int adder = 0;
|
||||||
int reverse_mask;
|
int reverse_mask = false;
|
||||||
QueuedPixels() : start(0), end(0), pixel_position(0), adder(0), reverse_mask(false) {}
|
|
||||||
} queue_[4];
|
} queue_[4];
|
||||||
int queue_read_pointer_, queue_write_pointer_;
|
int queue_read_pointer_ = 0, queue_write_pointer_ = 0;
|
||||||
|
|
||||||
inline void output_pixels(uint8_t *const target, const int count, const uint8_t collision_identity, int output_pixel_position, int output_adder, int output_reverse_mask) {
|
inline void output_pixels(uint8_t *const target, const int count, const uint8_t collision_identity, int output_pixel_position, int output_adder, int output_reverse_mask) {
|
||||||
if(output_pixel_position == 32 || !graphic[graphic_index]) return;
|
if(output_pixel_position == 32 || !graphic[graphic_index]) return;
|
||||||
@@ -244,8 +229,8 @@ class TIA {
|
|||||||
|
|
||||||
// common actor for things that appear as a horizontal run of pixels
|
// common actor for things that appear as a horizontal run of pixels
|
||||||
struct HorizontalRun: public Object<HorizontalRun> {
|
struct HorizontalRun: public Object<HorizontalRun> {
|
||||||
int pixel_position;
|
int pixel_position = 0;
|
||||||
int size;
|
int size = 1;
|
||||||
const bool enqueues = false;
|
const bool enqueues = false;
|
||||||
|
|
||||||
inline void skip_pixels(const int count, int from_horizontal_counter) {
|
inline void skip_pixels(const int count, int from_horizontal_counter) {
|
||||||
@@ -268,16 +253,13 @@ class TIA {
|
|||||||
|
|
||||||
void dequeue_pixels(uint8_t *const target, const uint8_t collision_identity, const int time_now) {}
|
void dequeue_pixels(uint8_t *const target, const uint8_t collision_identity, const int time_now) {}
|
||||||
void enqueue_pixels(const int start, const int end, int from_horizontal_counter) {}
|
void enqueue_pixels(const int start, const int end, int from_horizontal_counter) {}
|
||||||
|
|
||||||
HorizontalRun() : pixel_position(0), size(1) {}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
// missile state
|
// missile state
|
||||||
struct Missile: public HorizontalRun {
|
struct Missile: public HorizontalRun {
|
||||||
bool enabled;
|
bool enabled = false;
|
||||||
bool locked_to_player;
|
bool locked_to_player = false;
|
||||||
int copy_flags;
|
int copy_flags = 0;
|
||||||
|
|
||||||
inline void output_pixels(uint8_t *const target, const int count, const uint8_t collision_identity, int from_horizontal_counter) {
|
inline void output_pixels(uint8_t *const target, const int count, const uint8_t collision_identity, int from_horizontal_counter) {
|
||||||
if(!pixel_position) return;
|
if(!pixel_position) return;
|
||||||
@@ -287,14 +269,12 @@ class TIA {
|
|||||||
skip_pixels(count, from_horizontal_counter);
|
skip_pixels(count, from_horizontal_counter);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Missile() : enabled(false), copy_flags(0) {}
|
|
||||||
} missile_[2];
|
} missile_[2];
|
||||||
|
|
||||||
// ball state
|
// ball state
|
||||||
struct Ball: public HorizontalRun {
|
struct Ball: public HorizontalRun {
|
||||||
bool enabled[2];
|
bool enabled[2] = {false, false};
|
||||||
int enabled_index;
|
int enabled_index = 0;
|
||||||
const int copy_flags = 0;
|
const int copy_flags = 0;
|
||||||
|
|
||||||
inline void output_pixels(uint8_t *const target, const int count, const uint8_t collision_identity, int from_horizontal_counter) {
|
inline void output_pixels(uint8_t *const target, const int count, const uint8_t collision_identity, int from_horizontal_counter) {
|
||||||
@@ -305,12 +285,10 @@ class TIA {
|
|||||||
skip_pixels(count, from_horizontal_counter);
|
skip_pixels(count, from_horizontal_counter);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ball() : enabled{false, false}, enabled_index(0) {}
|
|
||||||
} ball_;
|
} ball_;
|
||||||
|
|
||||||
// motion
|
// motion
|
||||||
bool horizontal_blank_extend_;
|
bool horizontal_blank_extend_ = false;
|
||||||
template<class T> void perform_border_motion(T &object, int start, int end);
|
template<class T> void perform_border_motion(T &object, int start, int end);
|
||||||
template<class T> void perform_motion_step(T &object);
|
template<class T> void perform_motion_step(T &object);
|
||||||
|
|
||||||
@@ -323,8 +301,8 @@ class TIA {
|
|||||||
inline void output_for_cycles(int number_of_cycles);
|
inline void output_for_cycles(int number_of_cycles);
|
||||||
inline void output_line();
|
inline void output_line();
|
||||||
|
|
||||||
int pixels_start_location_;
|
int pixels_start_location_ = 0;
|
||||||
uint8_t *pixel_target_;
|
uint8_t *pixel_target_ = nullptr;
|
||||||
inline void output_pixels(int start, int end);
|
inline void output_pixels(int start, int end);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -6,31 +6,32 @@
|
|||||||
// Copyright © 2016 Thomas Harte. All rights reserved.
|
// Copyright © 2016 Thomas Harte. All rights reserved.
|
||||||
//
|
//
|
||||||
|
|
||||||
#include "Speaker.hpp"
|
#include "TIASound.hpp"
|
||||||
|
|
||||||
using namespace Atari2600;
|
using namespace Atari2600;
|
||||||
|
|
||||||
Atari2600::Speaker::Speaker() :
|
Atari2600::TIASound::TIASound(Concurrency::DeferringAsyncTaskQueue &audio_queue) :
|
||||||
|
audio_queue_(audio_queue),
|
||||||
poly4_counter_{0x00f, 0x00f},
|
poly4_counter_{0x00f, 0x00f},
|
||||||
poly5_counter_{0x01f, 0x01f},
|
poly5_counter_{0x01f, 0x01f},
|
||||||
poly9_counter_{0x1ff, 0x1ff}
|
poly9_counter_{0x1ff, 0x1ff}
|
||||||
{}
|
{}
|
||||||
|
|
||||||
void Atari2600::Speaker::set_volume(int channel, uint8_t volume) {
|
void Atari2600::TIASound::set_volume(int channel, uint8_t volume) {
|
||||||
enqueue([=]() {
|
audio_queue_.defer([=]() {
|
||||||
volume_[channel] = volume & 0xf;
|
volume_[channel] = volume & 0xf;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void Atari2600::Speaker::set_divider(int channel, uint8_t divider) {
|
void Atari2600::TIASound::set_divider(int channel, uint8_t divider) {
|
||||||
enqueue([=]() {
|
audio_queue_.defer([=]() {
|
||||||
divider_[channel] = divider & 0x1f;
|
divider_[channel] = divider & 0x1f;
|
||||||
divider_counter_[channel] = 0;
|
divider_counter_[channel] = 0;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void Atari2600::Speaker::set_control(int channel, uint8_t control) {
|
void Atari2600::TIASound::set_control(int channel, uint8_t control) {
|
||||||
enqueue([=]() {
|
audio_queue_.defer([=]() {
|
||||||
control_[channel] = control & 0xf;
|
control_[channel] = control & 0xf;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -39,7 +40,7 @@ void Atari2600::Speaker::set_control(int channel, uint8_t control) {
|
|||||||
#define advance_poly5(c) poly5_counter_[channel] = (poly5_counter_[channel] >> 1) | (((poly5_counter_[channel] << 4) ^ (poly5_counter_[channel] << 2))&0x010)
|
#define advance_poly5(c) poly5_counter_[channel] = (poly5_counter_[channel] >> 1) | (((poly5_counter_[channel] << 4) ^ (poly5_counter_[channel] << 2))&0x010)
|
||||||
#define advance_poly9(c) poly9_counter_[channel] = (poly9_counter_[channel] >> 1) | (((poly9_counter_[channel] << 4) ^ (poly9_counter_[channel] << 8))&0x100)
|
#define advance_poly9(c) poly9_counter_[channel] = (poly9_counter_[channel] >> 1) | (((poly9_counter_[channel] << 4) ^ (poly9_counter_[channel] << 8))&0x100)
|
||||||
|
|
||||||
void Atari2600::Speaker::get_samples(unsigned int number_of_samples, int16_t *target) {
|
void Atari2600::TIASound::get_samples(std::size_t number_of_samples, int16_t *target) {
|
||||||
for(unsigned int c = 0; c < number_of_samples; c++) {
|
for(unsigned int c = 0; c < number_of_samples; c++) {
|
||||||
target[c] = 0;
|
target[c] = 0;
|
||||||
for(int channel = 0; channel < 2; channel++) {
|
for(int channel = 0; channel < 2; channel++) {
|
||||||
@@ -1,15 +1,16 @@
|
|||||||
//
|
//
|
||||||
// Speaker.hpp
|
// TIASound.hpp
|
||||||
// Clock Signal
|
// Clock Signal
|
||||||
//
|
//
|
||||||
// Created by Thomas Harte on 03/12/2016.
|
// Created by Thomas Harte on 03/12/2016.
|
||||||
// Copyright © 2016 Thomas Harte. All rights reserved.
|
// Copyright © 2016 Thomas Harte. All rights reserved.
|
||||||
//
|
//
|
||||||
|
|
||||||
#ifndef Atari2600_Speaker_hpp
|
#ifndef Atari2600_TIASound_hpp
|
||||||
#define Atari2600_Speaker_hpp
|
#define Atari2600_TIASound_hpp
|
||||||
|
|
||||||
#include "../../Outputs/Speaker.hpp"
|
#include "../../Outputs/Speaker/Implementation/SampleSource.hpp"
|
||||||
|
#include "../../Concurrency/AsyncTaskQueue.hpp"
|
||||||
|
|
||||||
namespace Atari2600 {
|
namespace Atari2600 {
|
||||||
|
|
||||||
@@ -17,17 +18,19 @@ namespace Atari2600 {
|
|||||||
// will give greater resolution to changes in audio state. 1, 2 and 19 are the only divisors of 38.
|
// will give greater resolution to changes in audio state. 1, 2 and 19 are the only divisors of 38.
|
||||||
const int CPUTicksPerAudioTick = 2;
|
const int CPUTicksPerAudioTick = 2;
|
||||||
|
|
||||||
class Speaker: public ::Outputs::Filter<Speaker> {
|
class TIASound: public Outputs::Speaker::SampleSource {
|
||||||
public:
|
public:
|
||||||
Speaker();
|
TIASound(Concurrency::DeferringAsyncTaskQueue &audio_queue);
|
||||||
|
|
||||||
void set_volume(int channel, uint8_t volume);
|
void set_volume(int channel, uint8_t volume);
|
||||||
void set_divider(int channel, uint8_t divider);
|
void set_divider(int channel, uint8_t divider);
|
||||||
void set_control(int channel, uint8_t control);
|
void set_control(int channel, uint8_t control);
|
||||||
|
|
||||||
void get_samples(unsigned int number_of_samples, int16_t *target);
|
void get_samples(std::size_t number_of_samples, int16_t *target);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
Concurrency::DeferringAsyncTaskQueue &audio_queue_;
|
||||||
|
|
||||||
uint8_t volume_[2];
|
uint8_t volume_[2];
|
||||||
uint8_t divider_[2];
|
uint8_t divider_[2];
|
||||||
uint8_t control_[2];
|
uint8_t control_[2];
|
||||||
@@ -10,8 +10,9 @@
|
|||||||
#define CRTMachine_hpp
|
#define CRTMachine_hpp
|
||||||
|
|
||||||
#include "../Outputs/CRT/CRT.hpp"
|
#include "../Outputs/CRT/CRT.hpp"
|
||||||
#include "../Outputs/Speaker.hpp"
|
#include "../Outputs/Speaker/Speaker.hpp"
|
||||||
#include "../ClockReceiver/ClockReceiver.hpp"
|
#include "../ClockReceiver/ClockReceiver.hpp"
|
||||||
|
#include "ROMMachine.hpp"
|
||||||
|
|
||||||
namespace CRTMachine {
|
namespace CRTMachine {
|
||||||
|
|
||||||
@@ -20,10 +21,8 @@ namespace CRTMachine {
|
|||||||
that optionally provide a speaker, and that nominate a clock rate and can announce to a delegate
|
that optionally provide a speaker, and that nominate a clock rate and can announce to a delegate
|
||||||
should that clock rate change.
|
should that clock rate change.
|
||||||
*/
|
*/
|
||||||
class Machine {
|
class Machine: public ROMMachine::Machine {
|
||||||
public:
|
public:
|
||||||
Machine() : clock_is_unlimited_(false), delegate_(nullptr) {}
|
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
Causes the machine to set up its CRT and, if it has one, speaker. The caller guarantees
|
Causes the machine to set up its CRT and, if it has one, speaker. The caller guarantees
|
||||||
that an OpenGL context is bound.
|
that an OpenGL context is bound.
|
||||||
@@ -37,10 +36,10 @@ class Machine {
|
|||||||
virtual void close_output() = 0;
|
virtual void close_output() = 0;
|
||||||
|
|
||||||
/// @returns The CRT this machine is drawing to. Should not be @c nullptr.
|
/// @returns The CRT this machine is drawing to. Should not be @c nullptr.
|
||||||
virtual std::shared_ptr<Outputs::CRT::CRT> get_crt() = 0;
|
virtual Outputs::CRT::CRT *get_crt() = 0;
|
||||||
|
|
||||||
/// @returns The speaker that receives this machine's output, or @c nullptr if this machine is mute.
|
/// @returns The speaker that receives this machine's output, or @c nullptr if this machine is mute.
|
||||||
virtual std::shared_ptr<Outputs::Speaker> get_speaker() = 0;
|
virtual Outputs::Speaker::Speaker *get_speaker() = 0;
|
||||||
|
|
||||||
/// Runs the machine for @c cycles.
|
/// Runs the machine for @c cycles.
|
||||||
virtual void run_for(const Cycles cycles) = 0;
|
virtual void run_for(const Cycles cycles) = 0;
|
||||||
@@ -74,9 +73,9 @@ class Machine {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Delegate *delegate_;
|
Delegate *delegate_ = nullptr;
|
||||||
double clock_rate_;
|
double clock_rate_ = 1.0;
|
||||||
bool clock_is_unlimited_;
|
bool clock_is_unlimited_ = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,159 +9,43 @@
|
|||||||
#ifndef Commodore1540_hpp
|
#ifndef Commodore1540_hpp
|
||||||
#define Commodore1540_hpp
|
#define Commodore1540_hpp
|
||||||
|
|
||||||
#include "../../../Processors/6502/6502.hpp"
|
|
||||||
#include "../../../Components/6522/6522.hpp"
|
|
||||||
|
|
||||||
#include "../SerialBus.hpp"
|
#include "../SerialBus.hpp"
|
||||||
|
#include "../../ROMMachine.hpp"
|
||||||
#include "../../../Storage/Disk/Disk.hpp"
|
#include "../../../Storage/Disk/Disk.hpp"
|
||||||
#include "../../../Storage/Disk/DiskController.hpp"
|
#include "Implementation/C1540Base.hpp"
|
||||||
|
|
||||||
namespace Commodore {
|
namespace Commodore {
|
||||||
namespace C1540 {
|
namespace C1540 {
|
||||||
|
|
||||||
/*!
|
|
||||||
An implementation of the serial-port VIA in a Commodore 1540 — the VIA that facilitates all
|
|
||||||
IEC bus communications.
|
|
||||||
|
|
||||||
It is wired up such that Port B contains:
|
|
||||||
Bit 0: data input; 1 if the line is low, 0 if it is high;
|
|
||||||
Bit 1: data output; 1 if the line should be low, 0 if it should be high;
|
|
||||||
Bit 2: clock input; 1 if the line is low, 0 if it is high;
|
|
||||||
Bit 3: clock output; 1 if the line is low, 0 if it is high;
|
|
||||||
Bit 4: attention acknowledge output; exclusive ORd with the attention input and ORd onto the data output;
|
|
||||||
Bits 5/6: device select input; the 1540 will act as device 8 + [value of bits]
|
|
||||||
Bit 7: attention input; 1 if the line is low, 0 if it is high
|
|
||||||
|
|
||||||
The attention input is also connected to CA1, similarly inverted — the CA1 wire will be high when the bus is low and vice versa.
|
|
||||||
*/
|
|
||||||
class SerialPortVIA: public MOS::MOS6522<SerialPortVIA>, public MOS::MOS6522IRQDelegate {
|
|
||||||
public:
|
|
||||||
using MOS6522IRQDelegate::set_interrupt_status;
|
|
||||||
|
|
||||||
SerialPortVIA();
|
|
||||||
|
|
||||||
uint8_t get_port_input(Port);
|
|
||||||
|
|
||||||
void set_port_output(Port, uint8_t value, uint8_t mask);
|
|
||||||
void set_serial_line_state(::Commodore::Serial::Line, bool);
|
|
||||||
|
|
||||||
void set_serial_port(const std::shared_ptr<::Commodore::Serial::Port> &);
|
|
||||||
|
|
||||||
private:
|
|
||||||
uint8_t port_b_;
|
|
||||||
std::weak_ptr<::Commodore::Serial::Port> serial_port_;
|
|
||||||
bool attention_acknowledge_level_, attention_level_input_, data_level_output_;
|
|
||||||
|
|
||||||
void update_data_line();
|
|
||||||
};
|
|
||||||
|
|
||||||
/*!
|
|
||||||
An implementation of the drive VIA in a Commodore 1540 — the VIA that is used to interface with the disk.
|
|
||||||
|
|
||||||
It is wired up such that Port B contains:
|
|
||||||
Bits 0/1: head step direction
|
|
||||||
Bit 2: motor control
|
|
||||||
Bit 3: LED control (TODO)
|
|
||||||
Bit 4: write protect photocell status (TODO)
|
|
||||||
Bits 5/6: read/write density
|
|
||||||
Bit 7: 0 if sync marks are currently being detected, 1 otherwise.
|
|
||||||
|
|
||||||
... and Port A contains the byte most recently read from the disk or the byte next to write to the disk, depending on data direction.
|
|
||||||
|
|
||||||
It is implied that CA2 might be used to set processor overflow, CA1 a strobe for data input, and one of the CBs being definitive on
|
|
||||||
whether the disk head is being told to read or write, but it's unclear and I've yet to investigate. So, TODO.
|
|
||||||
*/
|
|
||||||
class DriveVIA: public MOS::MOS6522<DriveVIA>, public MOS::MOS6522IRQDelegate {
|
|
||||||
public:
|
|
||||||
class Delegate {
|
|
||||||
public:
|
|
||||||
virtual void drive_via_did_step_head(void *driveVIA, int direction) = 0;
|
|
||||||
virtual void drive_via_did_set_data_density(void *driveVIA, int density) = 0;
|
|
||||||
};
|
|
||||||
void set_delegate(Delegate *);
|
|
||||||
|
|
||||||
using MOS6522IRQDelegate::set_interrupt_status;
|
|
||||||
|
|
||||||
DriveVIA();
|
|
||||||
|
|
||||||
uint8_t get_port_input(Port port);
|
|
||||||
|
|
||||||
void set_sync_detected(bool);
|
|
||||||
void set_data_input(uint8_t);
|
|
||||||
bool get_should_set_overflow();
|
|
||||||
bool get_motor_enabled();
|
|
||||||
|
|
||||||
void set_control_line_output(Port, Line, bool value);
|
|
||||||
|
|
||||||
void set_port_output(Port, uint8_t value, uint8_t direction_mask);
|
|
||||||
|
|
||||||
private:
|
|
||||||
uint8_t port_b_, port_a_;
|
|
||||||
bool should_set_overflow_;
|
|
||||||
bool drive_motor_;
|
|
||||||
uint8_t previous_port_b_output_;
|
|
||||||
Delegate *delegate_;
|
|
||||||
};
|
|
||||||
|
|
||||||
/*!
|
|
||||||
An implementation of the C1540's serial port; this connects incoming line levels to the serial-port VIA.
|
|
||||||
*/
|
|
||||||
class SerialPort : public ::Commodore::Serial::Port {
|
|
||||||
public:
|
|
||||||
void set_input(::Commodore::Serial::Line, ::Commodore::Serial::LineLevel);
|
|
||||||
void set_serial_port_via(const std::shared_ptr<SerialPortVIA> &);
|
|
||||||
|
|
||||||
private:
|
|
||||||
std::weak_ptr<SerialPortVIA> serial_port_VIA_;
|
|
||||||
};
|
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
Provides an emulation of the C1540.
|
Provides an emulation of the C1540.
|
||||||
*/
|
*/
|
||||||
class Machine:
|
class Machine: public MachineBase, public ROMMachine::Machine {
|
||||||
public CPU::MOS6502::Processor<Machine>,
|
|
||||||
public MOS::MOS6522IRQDelegate::Delegate,
|
|
||||||
public DriveVIA::Delegate,
|
|
||||||
public Storage::Disk::Controller {
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
Machine();
|
enum Personality {
|
||||||
|
C1540,
|
||||||
|
C1541
|
||||||
|
};
|
||||||
|
Machine(Personality p);
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
Sets the ROM image to use for this drive; it is assumed that the buffer provided will be at least 16 kb in size.
|
Sets the source for this drive's ROM image.
|
||||||
*/
|
*/
|
||||||
void set_rom(const std::vector<uint8_t> &rom);
|
bool set_rom_fetcher(const std::function<std::vector<std::unique_ptr<std::vector<uint8_t>>>(const std::string &machine, const std::vector<std::string> &names)> &roms_with_names);
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
Sets the serial bus to which this drive should attach itself.
|
Sets the serial bus to which this drive should attach itself.
|
||||||
*/
|
*/
|
||||||
void set_serial_bus(std::shared_ptr<::Commodore::Serial::Bus> serial_bus);
|
void set_serial_bus(std::shared_ptr<::Commodore::Serial::Bus> serial_bus);
|
||||||
|
|
||||||
|
/// Advances time.
|
||||||
void run_for(const Cycles cycles);
|
void run_for(const Cycles cycles);
|
||||||
|
|
||||||
|
/// Inserts @c disk into the drive.
|
||||||
void set_disk(std::shared_ptr<Storage::Disk::Disk> disk);
|
void set_disk(std::shared_ptr<Storage::Disk::Disk> disk);
|
||||||
|
|
||||||
// to satisfy CPU::MOS6502::Processor
|
|
||||||
Cycles perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value);
|
|
||||||
|
|
||||||
// to satisfy MOS::MOS6522::Delegate
|
|
||||||
virtual void mos6522_did_change_interrupt_status(void *mos6522);
|
|
||||||
|
|
||||||
// to satisfy DriveVIA::Delegate
|
|
||||||
void drive_via_did_step_head(void *driveVIA, int direction);
|
|
||||||
void drive_via_did_set_data_density(void *driveVIA, int density);
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
uint8_t ram_[0x800];
|
Personality personality_;
|
||||||
uint8_t rom_[0x4000];
|
|
||||||
|
|
||||||
std::shared_ptr<SerialPortVIA> serial_port_VIA_;
|
|
||||||
std::shared_ptr<SerialPort> serial_port_;
|
|
||||||
DriveVIA drive_VIA_;
|
|
||||||
|
|
||||||
int shift_register_, bit_window_offset_;
|
|
||||||
virtual void process_input_bit(int value, unsigned int cycles_since_index_hole);
|
|
||||||
virtual void process_index_hole();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,35 +6,47 @@
|
|||||||
// Copyright © 2016 Thomas Harte. All rights reserved.
|
// Copyright © 2016 Thomas Harte. All rights reserved.
|
||||||
//
|
//
|
||||||
|
|
||||||
#include "C1540.hpp"
|
#include "../C1540.hpp"
|
||||||
|
|
||||||
|
#include <cassert>
|
||||||
|
#include <cstring>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include "../../../Storage/Disk/Encodings/CommodoreGCR.hpp"
|
|
||||||
|
#include "../../../../Storage/Disk/Encodings/CommodoreGCR.hpp"
|
||||||
|
|
||||||
using namespace Commodore::C1540;
|
using namespace Commodore::C1540;
|
||||||
|
|
||||||
Machine::Machine() :
|
MachineBase::MachineBase() :
|
||||||
shift_register_(0),
|
Storage::Disk::Controller(1000000),
|
||||||
Storage::Disk::Controller(1000000, 4, 300),
|
m6502_(*this),
|
||||||
|
drive_(new Storage::Disk::Drive(1000000, 300, 2)),
|
||||||
|
serial_port_VIA_port_handler_(new SerialPortVIA(serial_port_VIA_)),
|
||||||
serial_port_(new SerialPort),
|
serial_port_(new SerialPort),
|
||||||
serial_port_VIA_(new SerialPortVIA) {
|
drive_VIA_(drive_VIA_port_handler_),
|
||||||
|
serial_port_VIA_(*serial_port_VIA_port_handler_) {
|
||||||
// attach the serial port to its VIA and vice versa
|
// attach the serial port to its VIA and vice versa
|
||||||
serial_port_->set_serial_port_via(serial_port_VIA_);
|
serial_port_->set_serial_port_via(serial_port_VIA_port_handler_);
|
||||||
serial_port_VIA_->set_serial_port(serial_port_);
|
serial_port_VIA_port_handler_->set_serial_port(serial_port_);
|
||||||
|
|
||||||
// set this instance as the delegate to receive interrupt requests from both VIAs
|
// set this instance as the delegate to receive interrupt requests from both VIAs
|
||||||
serial_port_VIA_->set_interrupt_delegate(this);
|
serial_port_VIA_port_handler_->set_interrupt_delegate(this);
|
||||||
drive_VIA_.set_interrupt_delegate(this);
|
drive_VIA_port_handler_.set_interrupt_delegate(this);
|
||||||
drive_VIA_.set_delegate(this);
|
drive_VIA_port_handler_.set_delegate(this);
|
||||||
|
|
||||||
// set a bit rate
|
// set a bit rate
|
||||||
set_expected_bit_length(Storage::Encodings::CommodoreGCR::length_of_a_bit_in_time_zone(3));
|
set_expected_bit_length(Storage::Encodings::CommodoreGCR::length_of_a_bit_in_time_zone(3));
|
||||||
|
|
||||||
|
// attach the only drive there is
|
||||||
|
set_drive(drive_);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Machine::Machine(Commodore::C1540::Machine::Personality personality) : personality_(personality) {}
|
||||||
|
|
||||||
void Machine::set_serial_bus(std::shared_ptr<::Commodore::Serial::Bus> serial_bus) {
|
void Machine::set_serial_bus(std::shared_ptr<::Commodore::Serial::Bus> serial_bus) {
|
||||||
Commodore::Serial::AttachPortAndBus(serial_port_, serial_bus);
|
Commodore::Serial::AttachPortAndBus(serial_port_, serial_bus);
|
||||||
}
|
}
|
||||||
|
|
||||||
Cycles Machine::perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value) {
|
Cycles MachineBase::perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value) {
|
||||||
/*
|
/*
|
||||||
Memory map (given that I'm unsure yet on any potential mirroring):
|
Memory map (given that I'm unsure yet on any potential mirroring):
|
||||||
|
|
||||||
@@ -49,13 +61,14 @@ Cycles Machine::perform_bus_operation(CPU::MOS6502::BusOperation operation, uint
|
|||||||
else
|
else
|
||||||
ram_[address] = *value;
|
ram_[address] = *value;
|
||||||
} else if(address >= 0xc000) {
|
} else if(address >= 0xc000) {
|
||||||
if(isReadOperation(operation))
|
if(isReadOperation(operation)) {
|
||||||
*value = rom_[address & 0x3fff];
|
*value = rom_[address & 0x3fff];
|
||||||
|
}
|
||||||
} else if(address >= 0x1800 && address <= 0x180f) {
|
} else if(address >= 0x1800 && address <= 0x180f) {
|
||||||
if(isReadOperation(operation))
|
if(isReadOperation(operation))
|
||||||
*value = serial_port_VIA_->get_register(address);
|
*value = serial_port_VIA_.get_register(address);
|
||||||
else
|
else
|
||||||
serial_port_VIA_->set_register(address, *value);
|
serial_port_VIA_.set_register(address, *value);
|
||||||
} else if(address >= 0x1c00 && address <= 0x1c0f) {
|
} else if(address >= 0x1c00 && address <= 0x1c0f) {
|
||||||
if(isReadOperation(operation))
|
if(isReadOperation(operation))
|
||||||
*value = drive_VIA_.get_register(address);
|
*value = drive_VIA_.get_register(address);
|
||||||
@@ -63,81 +76,89 @@ Cycles Machine::perform_bus_operation(CPU::MOS6502::BusOperation operation, uint
|
|||||||
drive_VIA_.set_register(address, *value);
|
drive_VIA_.set_register(address, *value);
|
||||||
}
|
}
|
||||||
|
|
||||||
serial_port_VIA_->run_for(Cycles(1));
|
serial_port_VIA_.run_for(Cycles(1));
|
||||||
drive_VIA_.run_for(Cycles(1));
|
drive_VIA_.run_for(Cycles(1));
|
||||||
|
|
||||||
return Cycles(1);
|
return Cycles(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Machine::set_rom(const std::vector<uint8_t> &rom) {
|
bool Machine::set_rom_fetcher(const std::function<std::vector<std::unique_ptr<std::vector<uint8_t>>>(const std::string &machine, const std::vector<std::string> &names)> &roms_with_names) {
|
||||||
memcpy(rom_, rom.data(), std::min(sizeof(rom_), rom.size()));
|
std::string rom_name;
|
||||||
|
switch(personality_) {
|
||||||
|
case Personality::C1540: rom_name = "1540.bin"; break;
|
||||||
|
case Personality::C1541: rom_name = "1541.bin"; break;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto roms = roms_with_names("Commodore1540", {rom_name});
|
||||||
|
if(!roms[0]) return false;
|
||||||
|
std::memcpy(rom_, roms[0]->data(), std::min(sizeof(rom_), roms[0]->size()));
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Machine::set_disk(std::shared_ptr<Storage::Disk::Disk> disk) {
|
void Machine::set_disk(std::shared_ptr<Storage::Disk::Disk> disk) {
|
||||||
std::shared_ptr<Storage::Disk::Drive> drive(new Storage::Disk::Drive);
|
drive_->set_disk(disk);
|
||||||
drive->set_disk(disk);
|
|
||||||
set_drive(drive);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Machine::run_for(const Cycles cycles) {
|
void Machine::run_for(const Cycles cycles) {
|
||||||
CPU::MOS6502::Processor<Machine>::run_for(cycles);
|
m6502_.run_for(cycles);
|
||||||
set_motor_on(drive_VIA_.get_motor_enabled());
|
|
||||||
if(drive_VIA_.get_motor_enabled()) // TODO: motor speed up/down
|
bool drive_motor = drive_VIA_port_handler_.get_motor_enabled();
|
||||||
|
drive_->set_motor_on(drive_motor);
|
||||||
|
if(drive_motor)
|
||||||
Storage::Disk::Controller::run_for(cycles);
|
Storage::Disk::Controller::run_for(cycles);
|
||||||
}
|
}
|
||||||
|
|
||||||
#pragma mark - 6522 delegate
|
// MARK: - 6522 delegate
|
||||||
|
|
||||||
void Machine::mos6522_did_change_interrupt_status(void *mos6522) {
|
void MachineBase::mos6522_did_change_interrupt_status(void *mos6522) {
|
||||||
// both VIAs are connected to the IRQ line
|
// both VIAs are connected to the IRQ line
|
||||||
set_irq_line(serial_port_VIA_->get_interrupt_line() || drive_VIA_.get_interrupt_line());
|
m6502_.set_irq_line(serial_port_VIA_.get_interrupt_line() || drive_VIA_.get_interrupt_line());
|
||||||
}
|
}
|
||||||
|
|
||||||
#pragma mark - Disk drive
|
// MARK: - Disk drive
|
||||||
|
|
||||||
void Machine::process_input_bit(int value, unsigned int cycles_since_index_hole) {
|
void MachineBase::process_input_bit(int value) {
|
||||||
shift_register_ = (shift_register_ << 1) | value;
|
shift_register_ = (shift_register_ << 1) | value;
|
||||||
if((shift_register_ & 0x3ff) == 0x3ff) {
|
if((shift_register_ & 0x3ff) == 0x3ff) {
|
||||||
drive_VIA_.set_sync_detected(true);
|
drive_VIA_port_handler_.set_sync_detected(true);
|
||||||
bit_window_offset_ = -1; // i.e. this bit isn't the first within a data window, but the next might be
|
bit_window_offset_ = -1; // i.e. this bit isn't the first within a data window, but the next might be
|
||||||
} else {
|
} else {
|
||||||
drive_VIA_.set_sync_detected(false);
|
drive_VIA_port_handler_.set_sync_detected(false);
|
||||||
}
|
}
|
||||||
bit_window_offset_++;
|
bit_window_offset_++;
|
||||||
if(bit_window_offset_ == 8) {
|
if(bit_window_offset_ == 8) {
|
||||||
drive_VIA_.set_data_input((uint8_t)shift_register_);
|
drive_VIA_port_handler_.set_data_input(static_cast<uint8_t>(shift_register_));
|
||||||
bit_window_offset_ = 0;
|
bit_window_offset_ = 0;
|
||||||
if(drive_VIA_.get_should_set_overflow()) {
|
if(drive_VIA_port_handler_.get_should_set_overflow()) {
|
||||||
set_overflow_line(true);
|
m6502_.set_overflow_line(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else set_overflow_line(false);
|
else m6502_.set_overflow_line(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
// the 1540 does not recognise index holes
|
// the 1540 does not recognise index holes
|
||||||
void Machine::process_index_hole() {}
|
void MachineBase::process_index_hole() {}
|
||||||
|
|
||||||
#pragma mak - Drive VIA delegate
|
// MARK: - Drive VIA delegate
|
||||||
|
|
||||||
void Machine::drive_via_did_step_head(void *driveVIA, int direction) {
|
void MachineBase::drive_via_did_step_head(void *driveVIA, int direction) {
|
||||||
step(direction);
|
drive_->step(direction);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Machine::drive_via_did_set_data_density(void *driveVIA, int density) {
|
void MachineBase::drive_via_did_set_data_density(void *driveVIA, int density) {
|
||||||
set_expected_bit_length(Storage::Encodings::CommodoreGCR::length_of_a_bit_in_time_zone((unsigned int)density));
|
set_expected_bit_length(Storage::Encodings::CommodoreGCR::length_of_a_bit_in_time_zone(static_cast<unsigned int>(density)));
|
||||||
}
|
}
|
||||||
|
|
||||||
#pragma mark - SerialPortVIA
|
// MARK: - SerialPortVIA
|
||||||
|
|
||||||
SerialPortVIA::SerialPortVIA() :
|
SerialPortVIA::SerialPortVIA(MOS::MOS6522::MOS6522<SerialPortVIA> &via) : via_(via) {}
|
||||||
port_b_(0x00), attention_acknowledge_level_(false), attention_level_input_(true), data_level_output_(false) {}
|
|
||||||
|
|
||||||
uint8_t SerialPortVIA::get_port_input(Port port) {
|
uint8_t SerialPortVIA::get_port_input(MOS::MOS6522::Port port) {
|
||||||
if(port) return port_b_;
|
if(port) return port_b_;
|
||||||
return 0xff;
|
return 0xff;
|
||||||
}
|
}
|
||||||
|
|
||||||
void SerialPortVIA::set_port_output(Port port, uint8_t value, uint8_t mask) {
|
void SerialPortVIA::set_port_output(MOS::MOS6522::Port port, uint8_t value, uint8_t mask) {
|
||||||
if(port) {
|
if(port) {
|
||||||
std::shared_ptr<::Commodore::Serial::Port> serialPort = serial_port_.lock();
|
std::shared_ptr<::Commodore::Serial::Port> serialPort = serial_port_.lock();
|
||||||
if(serialPort) {
|
if(serialPort) {
|
||||||
@@ -151,6 +172,8 @@ void SerialPortVIA::set_port_output(Port port, uint8_t value, uint8_t mask) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void SerialPortVIA::set_serial_line_state(::Commodore::Serial::Line line, bool value) {
|
void SerialPortVIA::set_serial_line_state(::Commodore::Serial::Line line, bool value) {
|
||||||
|
// printf("[C1540] %s is %s\n", StringForLine(line), value ? "high" : "low");
|
||||||
|
|
||||||
switch(line) {
|
switch(line) {
|
||||||
default: break;
|
default: break;
|
||||||
case ::Commodore::Serial::Line::Data: port_b_ = (port_b_ & ~0x01) | (value ? 0x00 : 0x01); break;
|
case ::Commodore::Serial::Line::Data: port_b_ = (port_b_ & ~0x01) | (value ? 0x00 : 0x01); break;
|
||||||
@@ -158,7 +181,7 @@ void SerialPortVIA::set_serial_line_state(::Commodore::Serial::Line line, bool v
|
|||||||
case ::Commodore::Serial::Line::Attention:
|
case ::Commodore::Serial::Line::Attention:
|
||||||
attention_level_input_ = !value;
|
attention_level_input_ = !value;
|
||||||
port_b_ = (port_b_ & ~0x80) | (value ? 0x00 : 0x80);
|
port_b_ = (port_b_ & ~0x80) | (value ? 0x00 : 0x80);
|
||||||
set_control_line_input(Port::A, Line::One, !value);
|
via_.set_control_line_input(MOS::MOS6522::Port::A, MOS::MOS6522::Line::One, !value);
|
||||||
update_data_line();
|
update_data_line();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -177,7 +200,7 @@ void SerialPortVIA::update_data_line() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#pragma mark - DriveVIA
|
// MARK: - DriveVIA
|
||||||
|
|
||||||
void DriveVIA::set_delegate(Delegate *delegate) {
|
void DriveVIA::set_delegate(Delegate *delegate) {
|
||||||
delegate_ = delegate;
|
delegate_ = delegate;
|
||||||
@@ -186,7 +209,7 @@ void DriveVIA::set_delegate(Delegate *delegate) {
|
|||||||
// write protect tab uncovered
|
// write protect tab uncovered
|
||||||
DriveVIA::DriveVIA() : port_b_(0xff), port_a_(0xff), delegate_(nullptr) {}
|
DriveVIA::DriveVIA() : port_b_(0xff), port_a_(0xff), delegate_(nullptr) {}
|
||||||
|
|
||||||
uint8_t DriveVIA::get_port_input(Port port) {
|
uint8_t DriveVIA::get_port_input(MOS::MOS6522::Port port) {
|
||||||
return port ? port_b_ : port_a_;
|
return port ? port_b_ : port_a_;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -206,14 +229,15 @@ bool DriveVIA::get_motor_enabled() {
|
|||||||
return drive_motor_;
|
return drive_motor_;
|
||||||
}
|
}
|
||||||
|
|
||||||
void DriveVIA::set_control_line_output(Port port, Line line, bool value) {
|
void DriveVIA::set_control_line_output(MOS::MOS6522::Port port, MOS::MOS6522::Line line, bool value) {
|
||||||
if(port == Port::A && line == Line::Two) {
|
if(port == MOS::MOS6522::Port::A && line == MOS::MOS6522::Line::Two) {
|
||||||
should_set_overflow_ = value;
|
should_set_overflow_ = value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void DriveVIA::set_port_output(Port port, uint8_t value, uint8_t direction_mask) {
|
void DriveVIA::set_port_output(MOS::MOS6522::Port port, uint8_t value, uint8_t direction_mask) {
|
||||||
if(port) {
|
if(port) {
|
||||||
|
if(previous_port_b_output_ != value) {
|
||||||
// record drive motor state
|
// record drive motor state
|
||||||
drive_motor_ = !!(value&4);
|
drive_motor_ = !!(value&4);
|
||||||
|
|
||||||
@@ -235,8 +259,9 @@ void DriveVIA::set_port_output(Port port, uint8_t value, uint8_t direction_mask)
|
|||||||
previous_port_b_output_ = value;
|
previous_port_b_output_ = value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#pragma mark - SerialPort
|
// MARK: - SerialPort
|
||||||
|
|
||||||
void SerialPort::set_input(::Commodore::Serial::Line line, ::Commodore::Serial::LineLevel level) {
|
void SerialPort::set_input(::Commodore::Serial::Line line, ::Commodore::Serial::LineLevel level) {
|
||||||
std::shared_ptr<SerialPortVIA> serialPortVIA = serial_port_VIA_.lock();
|
std::shared_ptr<SerialPortVIA> serialPortVIA = serial_port_VIA_.lock();
|
||||||
160
Machines/Commodore/1540/Implementation/C1540Base.hpp
Normal file
160
Machines/Commodore/1540/Implementation/C1540Base.hpp
Normal file
@@ -0,0 +1,160 @@
|
|||||||
|
//
|
||||||
|
// C1540Base.hpp
|
||||||
|
// Clock Signal
|
||||||
|
//
|
||||||
|
// Created by Thomas Harte on 04/09/2017.
|
||||||
|
// Copyright © 2017 Thomas Harte. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef C1540Base_hpp
|
||||||
|
#define C1540Base_hpp
|
||||||
|
|
||||||
|
#include "../../../../Processors/6502/6502.hpp"
|
||||||
|
#include "../../../../Components/6522/6522.hpp"
|
||||||
|
|
||||||
|
#include "../../SerialBus.hpp"
|
||||||
|
|
||||||
|
#include "../../../../Storage/Disk/Disk.hpp"
|
||||||
|
|
||||||
|
#include "../../../../Storage/Disk/Controller/DiskController.hpp"
|
||||||
|
|
||||||
|
namespace Commodore {
|
||||||
|
namespace C1540 {
|
||||||
|
|
||||||
|
/*!
|
||||||
|
An implementation of the serial-port VIA in a Commodore 1540 — the VIA that facilitates all
|
||||||
|
IEC bus communications.
|
||||||
|
|
||||||
|
It is wired up such that Port B contains:
|
||||||
|
Bit 0: data input; 1 if the line is low, 0 if it is high;
|
||||||
|
Bit 1: data output; 1 if the line should be low, 0 if it should be high;
|
||||||
|
Bit 2: clock input; 1 if the line is low, 0 if it is high;
|
||||||
|
Bit 3: clock output; 1 if the line is low, 0 if it is high;
|
||||||
|
Bit 4: attention acknowledge output; exclusive ORd with the attention input and ORd onto the data output;
|
||||||
|
Bits 5/6: device select input; the 1540 will act as device 8 + [value of bits]
|
||||||
|
Bit 7: attention input; 1 if the line is low, 0 if it is high
|
||||||
|
|
||||||
|
The attention input is also connected to CA1, similarly inverted — the CA1 wire will be high when the bus is low and vice versa.
|
||||||
|
*/
|
||||||
|
class SerialPortVIA: public MOS::MOS6522::IRQDelegatePortHandler {
|
||||||
|
public:
|
||||||
|
SerialPortVIA(MOS::MOS6522::MOS6522<SerialPortVIA> &via);
|
||||||
|
|
||||||
|
uint8_t get_port_input(MOS::MOS6522::Port);
|
||||||
|
|
||||||
|
void set_port_output(MOS::MOS6522::Port, uint8_t value, uint8_t mask);
|
||||||
|
void set_serial_line_state(::Commodore::Serial::Line, bool);
|
||||||
|
|
||||||
|
void set_serial_port(const std::shared_ptr<::Commodore::Serial::Port> &);
|
||||||
|
|
||||||
|
private:
|
||||||
|
MOS::MOS6522::MOS6522<SerialPortVIA> &via_;
|
||||||
|
uint8_t port_b_ = 0x0;
|
||||||
|
std::weak_ptr<::Commodore::Serial::Port> serial_port_;
|
||||||
|
bool attention_acknowledge_level_ = false;
|
||||||
|
bool attention_level_input_ = true;
|
||||||
|
bool data_level_output_ = false;
|
||||||
|
|
||||||
|
void update_data_line();
|
||||||
|
};
|
||||||
|
|
||||||
|
/*!
|
||||||
|
An implementation of the drive VIA in a Commodore 1540 — the VIA that is used to interface with the disk.
|
||||||
|
|
||||||
|
It is wired up such that Port B contains:
|
||||||
|
Bits 0/1: head step direction
|
||||||
|
Bit 2: motor control
|
||||||
|
Bit 3: LED control (TODO)
|
||||||
|
Bit 4: write protect photocell status (TODO)
|
||||||
|
Bits 5/6: read/write density
|
||||||
|
Bit 7: 0 if sync marks are currently being detected, 1 otherwise.
|
||||||
|
|
||||||
|
... and Port A contains the byte most recently read from the disk or the byte next to write to the disk, depending on data direction.
|
||||||
|
|
||||||
|
It is implied that CA2 might be used to set processor overflow, CA1 a strobe for data input, and one of the CBs being definitive on
|
||||||
|
whether the disk head is being told to read or write, but it's unclear and I've yet to investigate. So, TODO.
|
||||||
|
*/
|
||||||
|
class DriveVIA: public MOS::MOS6522::IRQDelegatePortHandler {
|
||||||
|
public:
|
||||||
|
class Delegate {
|
||||||
|
public:
|
||||||
|
virtual void drive_via_did_step_head(void *driveVIA, int direction) = 0;
|
||||||
|
virtual void drive_via_did_set_data_density(void *driveVIA, int density) = 0;
|
||||||
|
};
|
||||||
|
void set_delegate(Delegate *);
|
||||||
|
|
||||||
|
DriveVIA();
|
||||||
|
|
||||||
|
uint8_t get_port_input(MOS::MOS6522::Port port);
|
||||||
|
|
||||||
|
void set_sync_detected(bool);
|
||||||
|
void set_data_input(uint8_t);
|
||||||
|
bool get_should_set_overflow();
|
||||||
|
bool get_motor_enabled();
|
||||||
|
|
||||||
|
void set_control_line_output(MOS::MOS6522::Port, MOS::MOS6522::Line, bool value);
|
||||||
|
|
||||||
|
void set_port_output(MOS::MOS6522::Port, uint8_t value, uint8_t direction_mask);
|
||||||
|
|
||||||
|
private:
|
||||||
|
uint8_t port_b_, port_a_;
|
||||||
|
bool should_set_overflow_;
|
||||||
|
bool drive_motor_;
|
||||||
|
uint8_t previous_port_b_output_;
|
||||||
|
Delegate *delegate_;
|
||||||
|
};
|
||||||
|
|
||||||
|
/*!
|
||||||
|
An implementation of the C1540's serial port; this connects incoming line levels to the serial-port VIA.
|
||||||
|
*/
|
||||||
|
class SerialPort : public ::Commodore::Serial::Port {
|
||||||
|
public:
|
||||||
|
void set_input(::Commodore::Serial::Line, ::Commodore::Serial::LineLevel);
|
||||||
|
void set_serial_port_via(const std::shared_ptr<SerialPortVIA> &);
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::weak_ptr<SerialPortVIA> serial_port_VIA_;
|
||||||
|
};
|
||||||
|
|
||||||
|
class MachineBase:
|
||||||
|
public CPU::MOS6502::BusHandler,
|
||||||
|
public MOS::MOS6522::IRQDelegatePortHandler::Delegate,
|
||||||
|
public DriveVIA::Delegate,
|
||||||
|
public Storage::Disk::Controller {
|
||||||
|
|
||||||
|
public:
|
||||||
|
MachineBase();
|
||||||
|
|
||||||
|
// to satisfy CPU::MOS6502::Processor
|
||||||
|
Cycles perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value);
|
||||||
|
|
||||||
|
// to satisfy MOS::MOS6522::Delegate
|
||||||
|
virtual void mos6522_did_change_interrupt_status(void *mos6522);
|
||||||
|
|
||||||
|
// to satisfy DriveVIA::Delegate
|
||||||
|
void drive_via_did_step_head(void *driveVIA, int direction);
|
||||||
|
void drive_via_did_set_data_density(void *driveVIA, int density);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
CPU::MOS6502::Processor<MachineBase, false> m6502_;
|
||||||
|
std::shared_ptr<Storage::Disk::Drive> drive_;
|
||||||
|
|
||||||
|
uint8_t ram_[0x800];
|
||||||
|
uint8_t rom_[0x4000];
|
||||||
|
|
||||||
|
std::shared_ptr<SerialPortVIA> serial_port_VIA_port_handler_;
|
||||||
|
std::shared_ptr<SerialPort> serial_port_;
|
||||||
|
DriveVIA drive_VIA_port_handler_;
|
||||||
|
|
||||||
|
MOS::MOS6522::MOS6522<DriveVIA> drive_VIA_;
|
||||||
|
MOS::MOS6522::MOS6522<SerialPortVIA> serial_port_VIA_;
|
||||||
|
|
||||||
|
int shift_register_ = 0, bit_window_offset_;
|
||||||
|
virtual void process_input_bit(int value);
|
||||||
|
virtual void process_index_hole();
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /* C1540Base_hpp */
|
||||||
@@ -8,6 +8,8 @@
|
|||||||
|
|
||||||
#include "SerialBus.hpp"
|
#include "SerialBus.hpp"
|
||||||
|
|
||||||
|
#include <cstdio>
|
||||||
|
|
||||||
using namespace Commodore::Serial;
|
using namespace Commodore::Serial;
|
||||||
|
|
||||||
const char *::Commodore::Serial::StringForLine(Line line) {
|
const char *::Commodore::Serial::StringForLine(Line line) {
|
||||||
@@ -17,6 +19,7 @@ const char *::Commodore::Serial::StringForLine(Line line) {
|
|||||||
case Clock: return "Clock";
|
case Clock: return "Clock";
|
||||||
case Data: return "Data";
|
case Data: return "Data";
|
||||||
case Reset: return "Reset";
|
case Reset: return "Reset";
|
||||||
|
default: return nullptr;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -27,12 +30,12 @@ void ::Commodore::Serial::AttachPortAndBus(std::shared_ptr<Port> port, std::shar
|
|||||||
|
|
||||||
void Bus::add_port(std::shared_ptr<Port> port) {
|
void Bus::add_port(std::shared_ptr<Port> port) {
|
||||||
ports_.push_back(port);
|
ports_.push_back(port);
|
||||||
for(int line = (int)ServiceRequest; line <= (int)Reset; line++) {
|
for(int line = static_cast<int>(ServiceRequest); line <= static_cast<int>(Reset); line++) {
|
||||||
// the addition of a new device may change the line output...
|
// the addition of a new device may change the line output...
|
||||||
set_line_output_did_change((Line)line);
|
set_line_output_did_change(static_cast<Line>(line));
|
||||||
|
|
||||||
// ... but the new device will need to be told the current state regardless
|
// ... but the new device will need to be told the current state regardless
|
||||||
port->set_input((Line)line, line_levels_[line]);
|
port->set_input(static_cast<Line>(line), line_levels_[line]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -46,6 +49,8 @@ void Bus::set_line_output_did_change(Line line) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// printf("[Bus] %s is %s\n", StringForLine(line), new_line_level ? "high" : "low");
|
||||||
|
|
||||||
// post an update only if one occurred
|
// post an update only if one occurred
|
||||||
if(new_line_level != line_levels_[line]) {
|
if(new_line_level != line_levels_[line]) {
|
||||||
line_levels_[line] = new_line_level;
|
line_levels_[line] = new_line_level;
|
||||||
@@ -59,7 +64,7 @@ void Bus::set_line_output_did_change(Line line) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#pragma mark - The debug port
|
// MARK: - The debug port
|
||||||
|
|
||||||
void DebugPort::set_input(Line line, LineLevel value) {
|
void DebugPort::set_input(Line line, LineLevel value) {
|
||||||
input_levels_[line] = value;
|
input_levels_[line] = value;
|
||||||
|
|||||||
@@ -9,6 +9,8 @@
|
|||||||
#ifndef SerialBus_hpp
|
#ifndef SerialBus_hpp
|
||||||
#define SerialBus_hpp
|
#define SerialBus_hpp
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <memory>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
namespace Commodore {
|
namespace Commodore {
|
||||||
|
|||||||
@@ -1,25 +0,0 @@
|
|||||||
//
|
|
||||||
// CharacterMapper.hpp
|
|
||||||
// Clock Signal
|
|
||||||
//
|
|
||||||
// Created by Thomas Harte on 03/08/2017.
|
|
||||||
// Copyright © 2017 Thomas Harte. All rights reserved.
|
|
||||||
//
|
|
||||||
|
|
||||||
#ifndef Machines_Commodore_Vic20_CharacterMapper_hpp
|
|
||||||
#define Machines_Commodore_Vic20_CharacterMapper_hpp
|
|
||||||
|
|
||||||
#include "../../Typer.hpp"
|
|
||||||
|
|
||||||
namespace Commodore {
|
|
||||||
namespace Vic20 {
|
|
||||||
|
|
||||||
class CharacterMapper: public ::Utility::CharacterMapper {
|
|
||||||
public:
|
|
||||||
uint16_t *sequence_for_character(char character);
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif /* CharacterMapper_hpp */
|
|
||||||
@@ -1,20 +1,80 @@
|
|||||||
//
|
//
|
||||||
// CharacterMapper.cpp
|
// Keyboard.cpp
|
||||||
// Clock Signal
|
// Clock Signal
|
||||||
//
|
//
|
||||||
// Created by Thomas Harte on 03/08/2017.
|
// Created by Thomas Harte on 10/10/2017.
|
||||||
// Copyright © 2017 Thomas Harte. All rights reserved.
|
// Copyright © 2017 Thomas Harte. All rights reserved.
|
||||||
//
|
//
|
||||||
|
|
||||||
#include "CharacterMapper.hpp"
|
#include "Keyboard.hpp"
|
||||||
#include "Vic20.hpp"
|
|
||||||
|
|
||||||
using namespace Commodore::Vic20;
|
using namespace Commodore::Vic20;
|
||||||
|
|
||||||
|
uint16_t KeyboardMapper::mapped_key_for_key(Inputs::Keyboard::Key key) {
|
||||||
|
#define BIND(source, dest) case Inputs::Keyboard::Key::source: return Commodore::Vic20::dest
|
||||||
|
switch(key) {
|
||||||
|
default: break;
|
||||||
|
|
||||||
|
BIND(k0, Key0); BIND(k1, Key1); BIND(k2, Key2); BIND(k3, Key3); BIND(k4, Key4);
|
||||||
|
BIND(k5, Key5); BIND(k6, Key6); BIND(k7, Key7); BIND(k8, Key8); BIND(k9, Key9);
|
||||||
|
BIND(Q, KeyQ); BIND(W, KeyW); BIND(E, KeyE); BIND(R, KeyR); BIND(T, KeyT);
|
||||||
|
BIND(Y, KeyY); BIND(U, KeyU); BIND(I, KeyI); BIND(O, KeyO); BIND(P, KeyP);
|
||||||
|
BIND(A, KeyA); BIND(S, KeyS); BIND(D, KeyD); BIND(F, KeyF); BIND(G, KeyG);
|
||||||
|
BIND(H, KeyH); BIND(J, KeyJ); BIND(K, KeyK); BIND(L, KeyL);
|
||||||
|
BIND(Z, KeyZ); BIND(X, KeyX); BIND(C, KeyC); BIND(V, KeyV);
|
||||||
|
BIND(B, KeyB); BIND(N, KeyN); BIND(M, KeyM);
|
||||||
|
|
||||||
|
BIND(BackTick, KeyLeft);
|
||||||
|
BIND(Hyphen, KeyPlus);
|
||||||
|
BIND(Equals, KeyDash);
|
||||||
|
BIND(F11, KeyGBP);
|
||||||
|
BIND(F12, KeyHome);
|
||||||
|
|
||||||
|
BIND(Tab, KeyControl);
|
||||||
|
BIND(OpenSquareBracket, KeyAt);
|
||||||
|
BIND(CloseSquareBracket, KeyAsterisk);
|
||||||
|
|
||||||
|
BIND(BackSlash, KeyRestore);
|
||||||
|
BIND(Hash, KeyUp);
|
||||||
|
BIND(F10, KeyUp);
|
||||||
|
|
||||||
|
BIND(Semicolon, KeyColon);
|
||||||
|
BIND(Quote, KeySemicolon);
|
||||||
|
BIND(F9, KeyEquals);
|
||||||
|
|
||||||
|
BIND(LeftMeta, KeyCBM);
|
||||||
|
BIND(LeftOption, KeyCBM);
|
||||||
|
BIND(RightOption, KeyCBM);
|
||||||
|
BIND(RightMeta, KeyCBM);
|
||||||
|
|
||||||
|
BIND(LeftShift, KeyLShift);
|
||||||
|
BIND(RightShift, KeyRShift);
|
||||||
|
|
||||||
|
BIND(Comma, KeyComma);
|
||||||
|
BIND(FullStop, KeyFullStop);
|
||||||
|
BIND(ForwardSlash, KeySlash);
|
||||||
|
|
||||||
|
BIND(Right, KeyRight);
|
||||||
|
BIND(Down, KeyDown);
|
||||||
|
|
||||||
|
BIND(Enter, KeyReturn);
|
||||||
|
BIND(Space, KeySpace);
|
||||||
|
BIND(BackSpace, KeyDelete);
|
||||||
|
|
||||||
|
BIND(Escape, KeyRunStop);
|
||||||
|
BIND(F1, KeyF1);
|
||||||
|
BIND(F3, KeyF3);
|
||||||
|
BIND(F5, KeyF5);
|
||||||
|
BIND(F7, KeyF7);
|
||||||
|
}
|
||||||
|
#undef BIND
|
||||||
|
return KeyboardMachine::Machine::KeyNotMapped;
|
||||||
|
}
|
||||||
|
|
||||||
uint16_t *CharacterMapper::sequence_for_character(char character) {
|
uint16_t *CharacterMapper::sequence_for_character(char character) {
|
||||||
#define KEYS(...) {__VA_ARGS__, EndSequence}
|
#define KEYS(...) {__VA_ARGS__, KeyboardMachine::Machine::KeyEndSequence}
|
||||||
#define SHIFT(...) {KeyLShift, __VA_ARGS__, EndSequence}
|
#define SHIFT(...) {KeyLShift, __VA_ARGS__, KeyboardMachine::Machine::KeyEndSequence}
|
||||||
#define X {NotMapped}
|
#define X {KeyboardMachine::Machine::KeyNotMapped}
|
||||||
static KeySequence key_sequences[] = {
|
static KeySequence key_sequences[] = {
|
||||||
/* NUL */ X, /* SOH */ X,
|
/* NUL */ X, /* SOH */ X,
|
||||||
/* STX */ X, /* ETX */ X,
|
/* STX */ X, /* ETX */ X,
|
||||||
52
Machines/Commodore/Vic-20/Keyboard.hpp
Normal file
52
Machines/Commodore/Vic-20/Keyboard.hpp
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
//
|
||||||
|
// Keyboard.hpp
|
||||||
|
// Clock Signal
|
||||||
|
//
|
||||||
|
// Created by Thomas Harte on 10/10/2017.
|
||||||
|
// Copyright © 2017 Thomas Harte. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef Machines_Commodore_Vic20_Keyboard_hpp
|
||||||
|
#define Machines_Commodore_Vic20_Keyboard_hpp
|
||||||
|
|
||||||
|
#include "../../KeyboardMachine.hpp"
|
||||||
|
#include "../../Utility/Typer.hpp"
|
||||||
|
|
||||||
|
namespace Commodore {
|
||||||
|
namespace Vic20 {
|
||||||
|
|
||||||
|
enum Key: uint16_t {
|
||||||
|
#define key(line, mask) (((mask) << 3) | (line))
|
||||||
|
Key2 = key(7, 0x01), Key4 = key(7, 0x02), Key6 = key(7, 0x04), Key8 = key(7, 0x08),
|
||||||
|
Key0 = key(7, 0x10), KeyDash = key(7, 0x20), KeyHome = key(7, 0x40), KeyF7 = key(7, 0x80),
|
||||||
|
KeyQ = key(6, 0x01), KeyE = key(6, 0x02), KeyT = key(6, 0x04), KeyU = key(6, 0x08),
|
||||||
|
KeyO = key(6, 0x10), KeyAt = key(6, 0x20), KeyUp = key(6, 0x40), KeyF5 = key(6, 0x80),
|
||||||
|
KeyCBM = key(5, 0x01), KeyS = key(5, 0x02), KeyF = key(5, 0x04), KeyH = key(5, 0x08),
|
||||||
|
KeyK = key(5, 0x10), KeyColon = key(5, 0x20), KeyEquals = key(5, 0x40), KeyF3 = key(5, 0x80),
|
||||||
|
KeySpace = key(4, 0x01), KeyZ = key(4, 0x02), KeyC = key(4, 0x04), KeyB = key(4, 0x08),
|
||||||
|
KeyM = key(4, 0x10), KeyFullStop = key(4, 0x20), KeyRShift = key(4, 0x40), KeyF1 = key(4, 0x80),
|
||||||
|
KeyRunStop = key(3, 0x01), KeyLShift = key(3, 0x02), KeyX = key(3, 0x04), KeyV = key(3, 0x08),
|
||||||
|
KeyN = key(3, 0x10), KeyComma = key(3, 0x20), KeySlash = key(3, 0x40), KeyDown = key(3, 0x80),
|
||||||
|
KeyControl = key(2, 0x01), KeyA = key(2, 0x02), KeyD = key(2, 0x04), KeyG = key(2, 0x08),
|
||||||
|
KeyJ = key(2, 0x10), KeyL = key(2, 0x20), KeySemicolon = key(2, 0x40), KeyRight = key(2, 0x80),
|
||||||
|
KeyLeft = key(1, 0x01), KeyW = key(1, 0x02), KeyR = key(1, 0x04), KeyY = key(1, 0x08),
|
||||||
|
KeyI = key(1, 0x10), KeyP = key(1, 0x20), KeyAsterisk = key(1, 0x40), KeyReturn = key(1, 0x80),
|
||||||
|
Key1 = key(0, 0x01), Key3 = key(0, 0x02), Key5 = key(0, 0x04), Key7 = key(0, 0x08),
|
||||||
|
Key9 = key(0, 0x10), KeyPlus = key(0, 0x20), KeyGBP = key(0, 0x40), KeyDelete = key(0, 0x80),
|
||||||
|
|
||||||
|
KeyRestore = 0xfffd
|
||||||
|
#undef key
|
||||||
|
};
|
||||||
|
|
||||||
|
struct KeyboardMapper: public KeyboardMachine::Machine::KeyboardMapper {
|
||||||
|
uint16_t mapped_key_for_key(Inputs::Keyboard::Key key);
|
||||||
|
};
|
||||||
|
|
||||||
|
struct CharacterMapper: public ::Utility::CharacterMapper {
|
||||||
|
uint16_t *sequence_for_character(char character);
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /* Keyboard_hpp */
|
||||||
@@ -8,53 +8,453 @@
|
|||||||
|
|
||||||
#include "Vic20.hpp"
|
#include "Vic20.hpp"
|
||||||
|
|
||||||
#include <algorithm>
|
#include "Keyboard.hpp"
|
||||||
#include "../../../Storage/Tape/Formats/TapePRG.hpp"
|
|
||||||
|
#include "../../../Processors/6502/6502.hpp"
|
||||||
|
#include "../../../Components/6560/6560.hpp"
|
||||||
|
#include "../../../Components/6522/6522.hpp"
|
||||||
|
|
||||||
|
#include "../../../ClockReceiver/ForceInline.hpp"
|
||||||
|
|
||||||
#include "../../../Storage/Tape/Parsers/Commodore.hpp"
|
#include "../../../Storage/Tape/Parsers/Commodore.hpp"
|
||||||
#include "../../../StaticAnalyser/StaticAnalyser.hpp"
|
|
||||||
#include "CharacterMapper.hpp"
|
|
||||||
|
|
||||||
using namespace Commodore::Vic20;
|
#include "../SerialBus.hpp"
|
||||||
|
#include "../1540/C1540.hpp"
|
||||||
|
|
||||||
Machine::Machine() :
|
#include "../../../Storage/Tape/Tape.hpp"
|
||||||
rom_(nullptr),
|
#include "../../../Storage/Disk/Disk.hpp"
|
||||||
is_running_at_zero_cost_(false),
|
|
||||||
tape_(new Storage::Tape::BinaryTapePlayer(1022727)),
|
#include "../../../Configurable/StandardOptions.hpp"
|
||||||
user_port_via_(new UserPortVIA),
|
|
||||||
keyboard_via_(new KeyboardVIA),
|
#include <algorithm>
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
namespace Commodore {
|
||||||
|
namespace Vic20 {
|
||||||
|
|
||||||
|
enum ROMSlot {
|
||||||
|
Kernel = 0,
|
||||||
|
BASIC,
|
||||||
|
Characters,
|
||||||
|
Drive
|
||||||
|
};
|
||||||
|
|
||||||
|
std::vector<std::unique_ptr<Configurable::Option>> get_options() {
|
||||||
|
return Configurable::standard_options(Configurable::QuickLoadTape);
|
||||||
|
}
|
||||||
|
|
||||||
|
enum JoystickInput {
|
||||||
|
Up = 0x04,
|
||||||
|
Down = 0x08,
|
||||||
|
Left = 0x10,
|
||||||
|
Right = 0x80,
|
||||||
|
Fire = 0x20
|
||||||
|
};
|
||||||
|
|
||||||
|
enum ROM {
|
||||||
|
CharactersDanish = 0,
|
||||||
|
CharactersEnglish,
|
||||||
|
CharactersJapanese,
|
||||||
|
CharactersSwedish,
|
||||||
|
KernelDanish,
|
||||||
|
KernelJapanese,
|
||||||
|
KernelNTSC,
|
||||||
|
KernelPAL,
|
||||||
|
KernelSwedish
|
||||||
|
};
|
||||||
|
|
||||||
|
/*!
|
||||||
|
Models the user-port VIA, which is the Vic's connection point for controlling its tape recorder —
|
||||||
|
sensing the presence or absence of a tape and controlling the tape motor — and reading the current
|
||||||
|
state from its serial port. Most of the joystick input is also exposed here.
|
||||||
|
*/
|
||||||
|
class UserPortVIA: public MOS::MOS6522::IRQDelegatePortHandler {
|
||||||
|
public:
|
||||||
|
UserPortVIA() : port_a_(0xbf) {}
|
||||||
|
|
||||||
|
/// Reports the current input to the 6522 port @c port.
|
||||||
|
uint8_t get_port_input(MOS::MOS6522::Port port) {
|
||||||
|
// Port A provides information about the presence or absence of a tape, and parts of
|
||||||
|
// the joystick and serial port state, both of which have been statefully collected
|
||||||
|
// into port_a_.
|
||||||
|
if(!port) {
|
||||||
|
return port_a_ | (tape_->has_tape() ? 0x00 : 0x40);
|
||||||
|
}
|
||||||
|
return 0xff;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Receives announcements of control line output change from the 6522.
|
||||||
|
void set_control_line_output(MOS::MOS6522::Port port, MOS::MOS6522::Line line, bool value) {
|
||||||
|
// The CA2 output is used to control the tape motor.
|
||||||
|
if(port == MOS::MOS6522::Port::A && line == MOS::MOS6522::Line::Two) {
|
||||||
|
tape_->set_motor_control(!value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Receives announcements of changes in the serial bus connected to the serial port and propagates them into Port A.
|
||||||
|
void set_serial_line_state(::Commodore::Serial::Line line, bool value) {
|
||||||
|
switch(line) {
|
||||||
|
default: break;
|
||||||
|
case ::Commodore::Serial::Line::Data: port_a_ = (port_a_ & ~0x02) | (value ? 0x02 : 0x00); break;
|
||||||
|
case ::Commodore::Serial::Line::Clock: port_a_ = (port_a_ & ~0x01) | (value ? 0x01 : 0x00); break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Allows the current joystick input to be set.
|
||||||
|
void set_joystick_state(JoystickInput input, bool value) {
|
||||||
|
if(input != JoystickInput::Right) {
|
||||||
|
port_a_ = (port_a_ & ~input) | (value ? 0 : input);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Receives announcements from the 6522 of user-port output, which might affect what's currently being presented onto the serial bus.
|
||||||
|
void set_port_output(MOS::MOS6522::Port port, uint8_t value, uint8_t mask) {
|
||||||
|
// Line 7 of port A is inverted and output as serial ATN.
|
||||||
|
if(!port) {
|
||||||
|
std::shared_ptr<::Commodore::Serial::Port> serialPort = serial_port_.lock();
|
||||||
|
if(serialPort) serialPort->set_output(::Commodore::Serial::Line::Attention, (::Commodore::Serial::LineLevel)!(value&0x80));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets @serial_port as this VIA's connection to the serial bus.
|
||||||
|
void set_serial_port(std::shared_ptr<::Commodore::Serial::Port> serial_port) {
|
||||||
|
serial_port_ = serial_port;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets @tape as the tape player connected to this VIA.
|
||||||
|
void set_tape(std::shared_ptr<Storage::Tape::BinaryTapePlayer> tape) {
|
||||||
|
tape_ = tape;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
uint8_t port_a_;
|
||||||
|
std::weak_ptr<::Commodore::Serial::Port> serial_port_;
|
||||||
|
std::shared_ptr<Storage::Tape::BinaryTapePlayer> tape_;
|
||||||
|
};
|
||||||
|
|
||||||
|
/*!
|
||||||
|
Models the keyboard VIA, which is used by the Vic for reading its keyboard, to output to its serial port,
|
||||||
|
and for the small portion of joystick input not connected to the user-port VIA.
|
||||||
|
*/
|
||||||
|
class KeyboardVIA: public MOS::MOS6522::IRQDelegatePortHandler {
|
||||||
|
public:
|
||||||
|
KeyboardVIA() : port_b_(0xff) {
|
||||||
|
clear_all_keys();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets whether @c key @c is_pressed.
|
||||||
|
void set_key_state(uint16_t key, bool is_pressed) {
|
||||||
|
if(is_pressed)
|
||||||
|
columns_[key & 7] &= ~(key >> 3);
|
||||||
|
else
|
||||||
|
columns_[key & 7] |= (key >> 3);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets all keys as unpressed.
|
||||||
|
void clear_all_keys() {
|
||||||
|
memset(columns_, 0xff, sizeof(columns_));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Called by the 6522 to get input. Reads the keyboard on Port A, returns a small amount of joystick state on Port B.
|
||||||
|
uint8_t get_port_input(MOS::MOS6522::Port port) {
|
||||||
|
if(!port) {
|
||||||
|
uint8_t result = 0xff;
|
||||||
|
for(int c = 0; c < 8; c++) {
|
||||||
|
if(!(activation_mask_&(1 << c)))
|
||||||
|
result &= columns_[c];
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
return port_b_;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Called by the 6522 to set output. The value of Port B selects which part of the keyboard to read.
|
||||||
|
void set_port_output(MOS::MOS6522::Port port, uint8_t value, uint8_t mask) {
|
||||||
|
if(port) activation_mask_ = (value & mask) | (~mask);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Called by the 6522 to set control line output. Which affects the serial port.
|
||||||
|
void set_control_line_output(MOS::MOS6522::Port port, MOS::MOS6522::Line line, bool value) {
|
||||||
|
if(line == MOS::MOS6522::Line::Two) {
|
||||||
|
std::shared_ptr<::Commodore::Serial::Port> serialPort = serial_port_.lock();
|
||||||
|
if(serialPort) {
|
||||||
|
// CB2 is inverted to become serial data; CA2 is inverted to become serial clock
|
||||||
|
if(port == MOS::MOS6522::Port::A)
|
||||||
|
serialPort->set_output(::Commodore::Serial::Line::Clock, (::Commodore::Serial::LineLevel)!value);
|
||||||
|
else
|
||||||
|
serialPort->set_output(::Commodore::Serial::Line::Data, (::Commodore::Serial::LineLevel)!value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets whether the joystick input @c input is pressed.
|
||||||
|
void set_joystick_state(JoystickInput input, bool value) {
|
||||||
|
if(input == JoystickInput::Right) {
|
||||||
|
port_b_ = (port_b_ & ~input) | (value ? 0 : input);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets the serial port to which this VIA is connected.
|
||||||
|
void set_serial_port(std::shared_ptr<::Commodore::Serial::Port> serialPort) {
|
||||||
|
serial_port_ = serialPort;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
uint8_t port_b_;
|
||||||
|
uint8_t columns_[8];
|
||||||
|
uint8_t activation_mask_;
|
||||||
|
std::weak_ptr<::Commodore::Serial::Port> serial_port_;
|
||||||
|
};
|
||||||
|
|
||||||
|
/*!
|
||||||
|
Models the Vic's serial port, providing the receipticle for input.
|
||||||
|
*/
|
||||||
|
class SerialPort : public ::Commodore::Serial::Port {
|
||||||
|
public:
|
||||||
|
/// Receives an input change from the base serial port class, and communicates it to the user-port VIA.
|
||||||
|
void set_input(::Commodore::Serial::Line line, ::Commodore::Serial::LineLevel level) {
|
||||||
|
std::shared_ptr<UserPortVIA> userPortVIA = user_port_via_.lock();
|
||||||
|
if(userPortVIA) userPortVIA->set_serial_line_state(line, (bool)level);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets the user-port VIA with which this serial port communicates.
|
||||||
|
void set_user_port_via(std::shared_ptr<UserPortVIA> userPortVIA) {
|
||||||
|
user_port_via_ = userPortVIA;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::weak_ptr<UserPortVIA> user_port_via_;
|
||||||
|
};
|
||||||
|
|
||||||
|
/*!
|
||||||
|
Provides the bus over which the Vic 6560 fetches memory in a Vic-20.
|
||||||
|
*/
|
||||||
|
class Vic6560: public MOS::MOS6560<Vic6560> {
|
||||||
|
public:
|
||||||
|
/// Performs a read on behalf of the 6560; in practice uses @c video_memory_map and @c colour_memory to find data.
|
||||||
|
inline void perform_read(uint16_t address, uint8_t *pixel_data, uint8_t *colour_data) {
|
||||||
|
*pixel_data = video_memory_map[address >> 10] ? video_memory_map[address >> 10][address & 0x3ff] : 0xff; // TODO
|
||||||
|
*colour_data = colour_memory[address & 0x03ff];
|
||||||
|
}
|
||||||
|
|
||||||
|
// It is assumed that these pointers have been filled in by the machine.
|
||||||
|
uint8_t *video_memory_map[16]; // Segments video memory into 1kb portions.
|
||||||
|
uint8_t *colour_memory; // Colour memory must be contiguous.
|
||||||
|
};
|
||||||
|
|
||||||
|
/*!
|
||||||
|
Interfaces a joystick to the two VIAs.
|
||||||
|
*/
|
||||||
|
class Joystick: public Inputs::Joystick {
|
||||||
|
public:
|
||||||
|
Joystick(UserPortVIA &user_port_via_port_handler, KeyboardVIA &keyboard_via_port_handler) :
|
||||||
|
user_port_via_port_handler_(user_port_via_port_handler),
|
||||||
|
keyboard_via_port_handler_(keyboard_via_port_handler) {}
|
||||||
|
|
||||||
|
void set_digital_input(DigitalInput digital_input, bool is_active) override {
|
||||||
|
JoystickInput mapped_input;
|
||||||
|
switch (digital_input) {
|
||||||
|
default: return;
|
||||||
|
case DigitalInput::Up: mapped_input = Up; break;
|
||||||
|
case DigitalInput::Down: mapped_input = Down; break;
|
||||||
|
case DigitalInput::Left: mapped_input = Left; break;
|
||||||
|
case DigitalInput::Right: mapped_input = Right; break;
|
||||||
|
case DigitalInput::Fire: mapped_input = Fire; break;
|
||||||
|
}
|
||||||
|
|
||||||
|
user_port_via_port_handler_.set_joystick_state(mapped_input, is_active);
|
||||||
|
keyboard_via_port_handler_.set_joystick_state(mapped_input, is_active);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
UserPortVIA &user_port_via_port_handler_;
|
||||||
|
KeyboardVIA &keyboard_via_port_handler_;
|
||||||
|
};
|
||||||
|
|
||||||
|
class ConcreteMachine:
|
||||||
|
public CPU::MOS6502::BusHandler,
|
||||||
|
public MOS::MOS6522::IRQDelegatePortHandler::Delegate,
|
||||||
|
public Utility::TypeRecipient,
|
||||||
|
public Storage::Tape::BinaryTapePlayer::Delegate,
|
||||||
|
public Machine {
|
||||||
|
public:
|
||||||
|
ConcreteMachine() :
|
||||||
|
m6502_(*this),
|
||||||
|
user_port_via_port_handler_(new UserPortVIA),
|
||||||
|
keyboard_via_port_handler_(new KeyboardVIA),
|
||||||
serial_port_(new SerialPort),
|
serial_port_(new SerialPort),
|
||||||
serial_bus_(new ::Commodore::Serial::Bus) {
|
serial_bus_(new ::Commodore::Serial::Bus),
|
||||||
|
user_port_via_(*user_port_via_port_handler_),
|
||||||
|
keyboard_via_(*keyboard_via_port_handler_),
|
||||||
|
tape_(new Storage::Tape::BinaryTapePlayer(1022727)) {
|
||||||
// communicate the tape to the user-port VIA
|
// communicate the tape to the user-port VIA
|
||||||
user_port_via_->set_tape(tape_);
|
user_port_via_port_handler_->set_tape(tape_);
|
||||||
|
|
||||||
// wire up the serial bus and serial port
|
// wire up the serial bus and serial port
|
||||||
Commodore::Serial::AttachPortAndBus(serial_port_, serial_bus_);
|
Commodore::Serial::AttachPortAndBus(serial_port_, serial_bus_);
|
||||||
|
|
||||||
// wire up 6522s and serial port
|
// wire up 6522s and serial port
|
||||||
user_port_via_->set_serial_port(serial_port_);
|
user_port_via_port_handler_->set_serial_port(serial_port_);
|
||||||
keyboard_via_->set_serial_port(serial_port_);
|
keyboard_via_port_handler_->set_serial_port(serial_port_);
|
||||||
serial_port_->set_user_port_via(user_port_via_);
|
serial_port_->set_user_port_via(user_port_via_port_handler_);
|
||||||
|
|
||||||
// wire up the 6522s, tape and machine
|
// wire up the 6522s, tape and machine
|
||||||
user_port_via_->set_interrupt_delegate(this);
|
user_port_via_port_handler_->set_interrupt_delegate(this);
|
||||||
keyboard_via_->set_interrupt_delegate(this);
|
keyboard_via_port_handler_->set_interrupt_delegate(this);
|
||||||
tape_->set_delegate(this);
|
tape_->set_delegate(this);
|
||||||
|
|
||||||
// establish the memory maps
|
// install a joystick
|
||||||
set_memory_size(MemorySize::Default);
|
joysticks_.emplace_back(new Joystick(*user_port_via_port_handler_, *keyboard_via_port_handler_));
|
||||||
|
}
|
||||||
// set the NTSC clock rate
|
|
||||||
set_region(NTSC);
|
~ConcreteMachine() {
|
||||||
// _debugPort.reset(new ::Commodore::Serial::DebugPort);
|
delete[] rom_;
|
||||||
// _debugPort->set_serial_bus(serial_bus_);
|
}
|
||||||
// serial_bus_->add_port(_debugPort);
|
|
||||||
|
void set_rom(ROMSlot slot, const std::vector<uint8_t> &data) {
|
||||||
|
}
|
||||||
|
|
||||||
|
// Obtains the system ROMs.
|
||||||
|
bool set_rom_fetcher(const std::function<std::vector<std::unique_ptr<std::vector<uint8_t>>>(const std::string &machine, const std::vector<std::string> &names)> &roms_with_names) override {
|
||||||
|
auto roms = roms_with_names(
|
||||||
|
"Vic20",
|
||||||
|
{
|
||||||
|
"characters-danish.bin",
|
||||||
|
"characters-english.bin",
|
||||||
|
"characters-japanese.bin",
|
||||||
|
"characters-swedish.bin",
|
||||||
|
"kernel-danish.bin",
|
||||||
|
"kernel-japanese.bin",
|
||||||
|
"kernel-ntsc.bin",
|
||||||
|
"kernel-pal.bin",
|
||||||
|
"kernel-swedish.bin",
|
||||||
|
"basic.bin"
|
||||||
|
});
|
||||||
|
|
||||||
|
for(std::size_t index = 0; index < roms.size(); ++index) {
|
||||||
|
auto &data = roms[index];
|
||||||
|
if(!data) return false;
|
||||||
|
if(index < 9) roms_[index] = std::move(*data); else basic_rom_ = std::move(*data);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Characters ROMs should be 4kb.
|
||||||
|
for(std::size_t index = 0; index < 4; ++index) roms_[index].resize(4096);
|
||||||
|
// Kernel ROMs and the BASIC ROM should be 8kb.
|
||||||
|
for(std::size_t index = 4; index < roms.size(); ++index) roms_[index].resize(8192);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void configure_as_target(const StaticAnalyser::Target &target) override final {
|
||||||
|
if(target.loading_command.length()) {
|
||||||
|
type_string(target.loading_command);
|
||||||
|
}
|
||||||
|
|
||||||
|
switch(target.vic20.memory_model) {
|
||||||
|
case StaticAnalyser::Vic20MemoryModel::Unexpanded:
|
||||||
|
set_memory_size(Default);
|
||||||
|
break;
|
||||||
|
case StaticAnalyser::Vic20MemoryModel::EightKB:
|
||||||
|
set_memory_size(ThreeKB);
|
||||||
|
break;
|
||||||
|
case StaticAnalyser::Vic20MemoryModel::ThirtyTwoKB:
|
||||||
|
set_memory_size(ThirtyTwoKB);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(target.media.disks.size()) {
|
||||||
|
// construct the 1540
|
||||||
|
c1540_.reset(new ::Commodore::C1540::Machine(Commodore::C1540::Machine::C1540));
|
||||||
|
|
||||||
|
// attach it to the serial bus
|
||||||
|
c1540_->set_serial_bus(serial_bus_);
|
||||||
|
|
||||||
|
// give it a means to obtain its ROM
|
||||||
|
c1540_->set_rom_fetcher(rom_fetcher_);
|
||||||
|
}
|
||||||
|
|
||||||
|
insert_media(target.media);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool insert_media(const StaticAnalyser::Media &media) override final {
|
||||||
|
if(!media.tapes.empty()) {
|
||||||
|
tape_->set_tape(media.tapes.front());
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!media.disks.empty() && c1540_) {
|
||||||
|
c1540_->set_disk(media.disks.front());
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!media.cartridges.empty()) {
|
||||||
|
rom_address_ = 0xa000;
|
||||||
|
std::vector<uint8_t> rom_image = media.cartridges.front()->get_segments().front().data;
|
||||||
|
rom_length_ = static_cast<uint16_t>(rom_image.size());
|
||||||
|
|
||||||
|
rom_ = new uint8_t[0x2000];
|
||||||
|
std::memcpy(rom_, rom_image.data(), rom_image.size());
|
||||||
|
write_to_map(processor_read_memory_map_, rom_, rom_address_, 0x2000);
|
||||||
|
}
|
||||||
|
|
||||||
|
return !media.tapes.empty() || (!media.disks.empty() && c1540_ != nullptr) || !media.cartridges.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
void set_key_state(uint16_t key, bool is_pressed) override final {
|
||||||
|
if(key != KeyRestore)
|
||||||
|
keyboard_via_port_handler_->set_key_state(key, is_pressed);
|
||||||
|
else
|
||||||
|
user_port_via_.set_control_line_input(MOS::MOS6522::Port::A, MOS::MOS6522::Line::One, !is_pressed);
|
||||||
|
}
|
||||||
|
|
||||||
|
void clear_all_keys() override final {
|
||||||
|
keyboard_via_port_handler_->clear_all_keys();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::unique_ptr<Inputs::Joystick>> &get_joysticks() override {
|
||||||
|
return joysticks_;
|
||||||
|
}
|
||||||
|
|
||||||
|
void set_memory_size(MemorySize size) override final {
|
||||||
|
memory_size_ = size;
|
||||||
|
needs_configuration_ = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void set_region(Region region) override final {
|
||||||
|
region_ = region;
|
||||||
|
needs_configuration_ = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void set_ntsc_6560() {
|
||||||
|
set_clock_rate(1022727);
|
||||||
|
if(mos6560_) {
|
||||||
|
mos6560_->set_output_mode(MOS::MOS6560<Commodore::Vic20::Vic6560>::OutputMode::NTSC);
|
||||||
|
mos6560_->set_clock_rate(1022727);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void set_pal_6560() {
|
||||||
|
set_clock_rate(1108404);
|
||||||
|
if(mos6560_) {
|
||||||
|
mos6560_->set_output_mode(MOS::MOS6560<Commodore::Vic20::Vic6560>::OutputMode::PAL);
|
||||||
|
mos6560_->set_clock_rate(1108404);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void configure_memory() {
|
||||||
|
// Determine PAL/NTSC
|
||||||
|
if(region_ == American || region_ == Japanese) {
|
||||||
|
// NTSC
|
||||||
|
set_ntsc_6560();
|
||||||
|
} else {
|
||||||
|
// PAL
|
||||||
|
set_pal_6560();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Machine::set_memory_size(MemorySize size) {
|
|
||||||
memset(processor_read_memory_map_, 0, sizeof(processor_read_memory_map_));
|
memset(processor_read_memory_map_, 0, sizeof(processor_read_memory_map_));
|
||||||
memset(processor_write_memory_map_, 0, sizeof(processor_write_memory_map_));
|
memset(processor_write_memory_map_, 0, sizeof(processor_write_memory_map_));
|
||||||
|
memset(mos6560_->video_memory_map, 0, sizeof(mos6560_->video_memory_map));
|
||||||
|
|
||||||
switch(size) {
|
switch(memory_size_) {
|
||||||
default: break;
|
default: break;
|
||||||
case ThreeKB:
|
case ThreeKB:
|
||||||
write_to_map(processor_read_memory_map_, expansion_ram_, 0x0000, 0x1000);
|
write_to_map(processor_read_memory_map_, expansion_ram_, 0x0000, 0x1000);
|
||||||
@@ -70,35 +470,58 @@ void Machine::set_memory_size(MemorySize size) {
|
|||||||
write_to_map(processor_read_memory_map_, user_basic_memory_, 0x0000, sizeof(user_basic_memory_));
|
write_to_map(processor_read_memory_map_, user_basic_memory_, 0x0000, sizeof(user_basic_memory_));
|
||||||
write_to_map(processor_read_memory_map_, screen_memory_, 0x1000, sizeof(screen_memory_));
|
write_to_map(processor_read_memory_map_, screen_memory_, 0x1000, sizeof(screen_memory_));
|
||||||
write_to_map(processor_read_memory_map_, colour_memory_, 0x9400, sizeof(colour_memory_));
|
write_to_map(processor_read_memory_map_, colour_memory_, 0x9400, sizeof(colour_memory_));
|
||||||
write_to_map(processor_read_memory_map_, character_rom_, 0x8000, sizeof(character_rom_));
|
|
||||||
write_to_map(processor_read_memory_map_, basic_rom_, 0xc000, sizeof(basic_rom_));
|
|
||||||
write_to_map(processor_read_memory_map_, kernel_rom_, 0xe000, sizeof(kernel_rom_));
|
|
||||||
|
|
||||||
write_to_map(processor_write_memory_map_, user_basic_memory_, 0x0000, sizeof(user_basic_memory_));
|
write_to_map(processor_write_memory_map_, user_basic_memory_, 0x0000, sizeof(user_basic_memory_));
|
||||||
write_to_map(processor_write_memory_map_, screen_memory_, 0x1000, sizeof(screen_memory_));
|
write_to_map(processor_write_memory_map_, screen_memory_, 0x1000, sizeof(screen_memory_));
|
||||||
write_to_map(processor_write_memory_map_, colour_memory_, 0x9400, sizeof(colour_memory_));
|
write_to_map(processor_write_memory_map_, colour_memory_, 0x9400, sizeof(colour_memory_));
|
||||||
|
|
||||||
|
write_to_map(mos6560_->video_memory_map, user_basic_memory_, 0x2000, sizeof(user_basic_memory_));
|
||||||
|
write_to_map(mos6560_->video_memory_map, screen_memory_, 0x3000, sizeof(screen_memory_));
|
||||||
|
mos6560_->colour_memory = colour_memory_;
|
||||||
|
|
||||||
|
write_to_map(processor_read_memory_map_, basic_rom_.data(), 0xc000, static_cast<uint16_t>(basic_rom_.size()));
|
||||||
|
|
||||||
|
ROM character_rom;
|
||||||
|
ROM kernel_rom;
|
||||||
|
switch(region_) {
|
||||||
|
default:
|
||||||
|
character_rom = CharactersEnglish;
|
||||||
|
kernel_rom = KernelPAL;
|
||||||
|
break;
|
||||||
|
case American:
|
||||||
|
character_rom = CharactersEnglish;
|
||||||
|
kernel_rom = KernelNTSC;
|
||||||
|
break;
|
||||||
|
case Danish:
|
||||||
|
character_rom = CharactersDanish;
|
||||||
|
kernel_rom = KernelDanish;
|
||||||
|
break;
|
||||||
|
case Japanese:
|
||||||
|
character_rom = CharactersJapanese;
|
||||||
|
kernel_rom = KernelJapanese;
|
||||||
|
break;
|
||||||
|
case Swedish:
|
||||||
|
character_rom = CharactersSwedish;
|
||||||
|
kernel_rom = KernelSwedish;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
write_to_map(processor_read_memory_map_, roms_[character_rom].data(), 0x8000, static_cast<uint16_t>(roms_[character_rom].size()));
|
||||||
|
write_to_map(mos6560_->video_memory_map, roms_[character_rom].data(), 0x0000, static_cast<uint16_t>(roms_[character_rom].size()));
|
||||||
|
write_to_map(processor_read_memory_map_, roms_[kernel_rom].data(), 0xe000, static_cast<uint16_t>(roms_[kernel_rom].size()));
|
||||||
|
|
||||||
// install the inserted ROM if there is one
|
// install the inserted ROM if there is one
|
||||||
if(rom_) {
|
if(rom_) {
|
||||||
write_to_map(processor_read_memory_map_, rom_, rom_address_, rom_length_);
|
write_to_map(processor_read_memory_map_, rom_, rom_address_, rom_length_);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Machine::write_to_map(uint8_t **map, uint8_t *area, uint16_t address, uint16_t length) {
|
void set_use_fast_tape_hack(bool activate) {
|
||||||
address >>= 10;
|
use_fast_tape_hack_ = activate;
|
||||||
length >>= 10;
|
|
||||||
while(length--) {
|
|
||||||
map[address] = area;
|
|
||||||
area += 0x400;
|
|
||||||
address++;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Machine::~Machine() {
|
// to satisfy CPU::MOS6502::Processor
|
||||||
delete[] rom_;
|
forceinline Cycles perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value) {
|
||||||
}
|
|
||||||
|
|
||||||
Cycles Machine::perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value) {
|
|
||||||
// run the phase-1 part of this cycle, in which the VIC accesses memory
|
// run the phase-1 part of this cycle, in which the VIC accesses memory
|
||||||
if(!is_running_at_zero_cost_) mos6560_->run_for(Cycles(1));
|
if(!is_running_at_zero_cost_) mos6560_->run_for(Cycles(1));
|
||||||
|
|
||||||
@@ -107,8 +530,8 @@ Cycles Machine::perform_bus_operation(CPU::MOS6502::BusOperation operation, uint
|
|||||||
uint8_t result = processor_read_memory_map_[address >> 10] ? processor_read_memory_map_[address >> 10][address & 0x3ff] : 0xff;
|
uint8_t result = processor_read_memory_map_[address >> 10] ? processor_read_memory_map_[address >> 10][address & 0x3ff] : 0xff;
|
||||||
if((address&0xfc00) == 0x9000) {
|
if((address&0xfc00) == 0x9000) {
|
||||||
if((address&0xff00) == 0x9000) result &= mos6560_->get_register(address);
|
if((address&0xff00) == 0x9000) result &= mos6560_->get_register(address);
|
||||||
if((address&0xfc10) == 0x9010) result &= user_port_via_->get_register(address);
|
if((address&0xfc10) == 0x9010) result &= user_port_via_.get_register(address);
|
||||||
if((address&0xfc20) == 0x9020) result &= keyboard_via_->get_register(address);
|
if((address&0xfc20) == 0x9020) result &= keyboard_via_.get_register(address);
|
||||||
}
|
}
|
||||||
*value = result;
|
*value = result;
|
||||||
|
|
||||||
@@ -124,7 +547,7 @@ Cycles Machine::perform_bus_operation(CPU::MOS6502::BusOperation operation, uint
|
|||||||
std::unique_ptr<Storage::Tape::Commodore::Header> header = parser.get_next_header(tape_->get_tape());
|
std::unique_ptr<Storage::Tape::Commodore::Header> header = parser.get_next_header(tape_->get_tape());
|
||||||
|
|
||||||
// serialise to wherever b2:b3 points
|
// serialise to wherever b2:b3 points
|
||||||
uint16_t tape_buffer_pointer = (uint16_t)user_basic_memory_[0xb2] | (uint16_t)(user_basic_memory_[0xb3] << 8);
|
uint16_t tape_buffer_pointer = static_cast<uint16_t>(user_basic_memory_[0xb2]) | static_cast<uint16_t>(user_basic_memory_[0xb3] << 8);
|
||||||
if(header) {
|
if(header) {
|
||||||
header->serialise(&user_basic_memory_[tape_buffer_pointer], 0x8000 - tape_buffer_pointer);
|
header->serialise(&user_basic_memory_[tape_buffer_pointer], 0x8000 - tape_buffer_pointer);
|
||||||
} else {
|
} else {
|
||||||
@@ -138,17 +561,17 @@ Cycles Machine::perform_bus_operation(CPU::MOS6502::BusOperation operation, uint
|
|||||||
|
|
||||||
*value = 0x0c; // i.e. NOP abs
|
*value = 0x0c; // i.e. NOP abs
|
||||||
} else if(address == 0xf90b) {
|
} else if(address == 0xf90b) {
|
||||||
uint8_t x = (uint8_t)get_value_of_register(CPU::MOS6502::Register::X);
|
uint8_t x = static_cast<uint8_t>(m6502_.get_value_of_register(CPU::MOS6502::Register::X));
|
||||||
if(x == 0xe) {
|
if(x == 0xe) {
|
||||||
Storage::Tape::Commodore::Parser parser;
|
Storage::Tape::Commodore::Parser parser;
|
||||||
std::unique_ptr<Storage::Tape::Commodore::Data> data = parser.get_next_data(tape_->get_tape());
|
std::unique_ptr<Storage::Tape::Commodore::Data> data = parser.get_next_data(tape_->get_tape());
|
||||||
uint16_t start_address, end_address;
|
uint16_t start_address, end_address;
|
||||||
start_address = (uint16_t)(user_basic_memory_[0xc1] | (user_basic_memory_[0xc2] << 8));
|
start_address = static_cast<uint16_t>(user_basic_memory_[0xc1] | (user_basic_memory_[0xc2] << 8));
|
||||||
end_address = (uint16_t)(user_basic_memory_[0xae] | (user_basic_memory_[0xaf] << 8));
|
end_address = static_cast<uint16_t>(user_basic_memory_[0xae] | (user_basic_memory_[0xaf] << 8));
|
||||||
|
|
||||||
// perform a via-processor_write_memory_map_ memcpy
|
// perform a via-processor_write_memory_map_ memcpy
|
||||||
uint8_t *data_ptr = data->data.data();
|
uint8_t *data_ptr = data->data.data();
|
||||||
size_t data_left = data->data.size();
|
std::size_t data_left = data->data.size();
|
||||||
while(data_left && start_address != end_address) {
|
while(data_left && start_address != end_address) {
|
||||||
uint8_t *page = processor_write_memory_map_[start_address >> 10];
|
uint8_t *page = processor_write_memory_map_[start_address >> 10];
|
||||||
if(page) page[start_address & 0x3ff] = *data_ptr;
|
if(page) page[start_address & 0x3ff] = *data_ptr;
|
||||||
@@ -159,13 +582,13 @@ Cycles Machine::perform_bus_operation(CPU::MOS6502::BusOperation operation, uint
|
|||||||
|
|
||||||
// set tape status, carry and flag
|
// set tape status, carry and flag
|
||||||
user_basic_memory_[0x90] |= 0x40;
|
user_basic_memory_[0x90] |= 0x40;
|
||||||
uint8_t flags = (uint8_t)get_value_of_register(CPU::MOS6502::Register::Flags);
|
uint8_t flags = static_cast<uint8_t>(m6502_.get_value_of_register(CPU::MOS6502::Register::Flags));
|
||||||
flags &= ~(uint8_t)(CPU::MOS6502::Flag::Carry | CPU::MOS6502::Flag::Interrupt);
|
flags &= ~static_cast<uint8_t>((CPU::MOS6502::Flag::Carry | CPU::MOS6502::Flag::Interrupt));
|
||||||
set_value_of_register(CPU::MOS6502::Register::Flags, flags);
|
m6502_.set_value_of_register(CPU::MOS6502::Register::Flags, flags);
|
||||||
|
|
||||||
// to ensure that execution proceeds to 0xfccf, pretend a NOP was here and
|
// to ensure that execution proceeds to 0xfccf, pretend a NOP was here and
|
||||||
// ensure that the PC leaps to 0xfccf
|
// ensure that the PC leaps to 0xfccf
|
||||||
set_value_of_register(CPU::MOS6502::Register::ProgramCounter, 0xfccf);
|
m6502_.set_value_of_register(CPU::MOS6502::Register::ProgramCounter, 0xfccf);
|
||||||
*value = 0xea; // i.e. NOP implied
|
*value = 0xea; // i.e. NOP implied
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -175,13 +598,13 @@ Cycles Machine::perform_bus_operation(CPU::MOS6502::BusOperation operation, uint
|
|||||||
if(ram) ram[address & 0x3ff] = *value;
|
if(ram) ram[address & 0x3ff] = *value;
|
||||||
if((address&0xfc00) == 0x9000) {
|
if((address&0xfc00) == 0x9000) {
|
||||||
if((address&0xff00) == 0x9000) mos6560_->set_register(address, *value);
|
if((address&0xff00) == 0x9000) mos6560_->set_register(address, *value);
|
||||||
if((address&0xfc10) == 0x9010) user_port_via_->set_register(address, *value);
|
if((address&0xfc10) == 0x9010) user_port_via_.set_register(address, *value);
|
||||||
if((address&0xfc20) == 0x9020) keyboard_via_->set_register(address, *value);
|
if((address&0xfc20) == 0x9020) keyboard_via_.set_register(address, *value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
user_port_via_->run_for(Cycles(1));
|
user_port_via_.run_for(Cycles(1));
|
||||||
keyboard_via_->run_for(Cycles(1));
|
keyboard_via_.run_for(Cycles(1));
|
||||||
if(typer_ && operation == CPU::MOS6502::BusOperation::ReadOpcode && address == 0xEB1E) {
|
if(typer_ && operation == CPU::MOS6502::BusOperation::ReadOpcode && address == 0xEB1E) {
|
||||||
if(!typer_->type_next_character()) {
|
if(!typer_->type_next_character()) {
|
||||||
clear_all_keys();
|
clear_all_keys();
|
||||||
@@ -194,251 +617,142 @@ Cycles Machine::perform_bus_operation(CPU::MOS6502::BusOperation operation, uint
|
|||||||
return Cycles(1);
|
return Cycles(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
#pragma mark - 6522 delegate
|
forceinline void flush() {
|
||||||
|
mos6560_->flush();
|
||||||
void Machine::mos6522_did_change_interrupt_status(void *mos6522) {
|
|
||||||
set_nmi_line(user_port_via_->get_interrupt_line());
|
|
||||||
set_irq_line(keyboard_via_->get_interrupt_line());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#pragma mark - Setup
|
void run_for(const Cycles cycles) override final {
|
||||||
|
if(needs_configuration_) {
|
||||||
void Machine::set_region(Commodore::Vic20::Region region) {
|
needs_configuration_ = false;
|
||||||
region_ = region;
|
configure_memory();
|
||||||
switch(region) {
|
|
||||||
case PAL:
|
|
||||||
set_clock_rate(1108404);
|
|
||||||
if(mos6560_) {
|
|
||||||
mos6560_->set_output_mode(MOS::MOS6560<Commodore::Vic20::Vic6560>::OutputMode::PAL);
|
|
||||||
mos6560_->set_clock_rate(1108404);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case NTSC:
|
|
||||||
set_clock_rate(1022727);
|
|
||||||
if(mos6560_) {
|
|
||||||
mos6560_->set_output_mode(MOS::MOS6560<Commodore::Vic20::Vic6560>::OutputMode::NTSC);
|
|
||||||
mos6560_->set_clock_rate(1022727);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
m6502_.run_for(cycles);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Machine::setup_output(float aspect_ratio) {
|
void setup_output(float aspect_ratio) override final {
|
||||||
mos6560_.reset(new Vic6560());
|
mos6560_.reset(new Vic6560());
|
||||||
mos6560_->get_speaker()->set_high_frequency_cut_off(1600); // There is a 1.6Khz low-pass filter in the Vic-20.
|
mos6560_->set_high_frequency_cutoff(1600); // There is a 1.6Khz low-pass filter in the Vic-20.
|
||||||
set_region(region_);
|
// Make a guess: PAL. Without setting a clock rate the 6560 isn't fully set up so contractually something must be set.
|
||||||
|
set_pal_6560();
|
||||||
memset(mos6560_->video_memory_map, 0, sizeof(mos6560_->video_memory_map));
|
|
||||||
write_to_map(mos6560_->video_memory_map, character_rom_, 0x0000, sizeof(character_rom_));
|
|
||||||
write_to_map(mos6560_->video_memory_map, user_basic_memory_, 0x2000, sizeof(user_basic_memory_));
|
|
||||||
write_to_map(mos6560_->video_memory_map, screen_memory_, 0x3000, sizeof(screen_memory_));
|
|
||||||
mos6560_->colour_memory = colour_memory_;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Machine::close_output() {
|
void close_output() override final {
|
||||||
mos6560_ = nullptr;
|
mos6560_ = nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Machine::set_rom(ROMSlot slot, size_t length, const uint8_t *data) {
|
Outputs::CRT::CRT *get_crt() override final {
|
||||||
uint8_t *target = nullptr;
|
return mos6560_->get_crt();
|
||||||
size_t max_length = 0x2000;
|
|
||||||
switch(slot) {
|
|
||||||
case Kernel: target = kernel_rom_; break;
|
|
||||||
case Characters: target = character_rom_; max_length = 0x1000; break;
|
|
||||||
case BASIC: target = basic_rom_; break;
|
|
||||||
case Drive:
|
|
||||||
drive_rom_.resize(length);
|
|
||||||
memcpy(drive_rom_.data(), data, length);
|
|
||||||
install_disk_rom();
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if(target) {
|
Outputs::Speaker::Speaker *get_speaker() override final {
|
||||||
size_t length_to_copy = std::min(max_length, length);
|
return mos6560_->get_speaker();
|
||||||
memcpy(target, data, length_to_copy);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#pragma mar - Tape
|
void mos6522_did_change_interrupt_status(void *mos6522) override final {
|
||||||
|
m6502_.set_nmi_line(user_port_via_.get_interrupt_line());
|
||||||
void Machine::configure_as_target(const StaticAnalyser::Target &target) {
|
m6502_.set_irq_line(keyboard_via_.get_interrupt_line());
|
||||||
if(target.tapes.size()) {
|
|
||||||
tape_->set_tape(target.tapes.front());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if(target.disks.size()) {
|
void type_string(const std::string &string) override final {
|
||||||
// construct the 1540
|
|
||||||
c1540_.reset(new ::Commodore::C1540::Machine);
|
|
||||||
|
|
||||||
// attach it to the serial bus
|
|
||||||
c1540_->set_serial_bus(serial_bus_);
|
|
||||||
|
|
||||||
// hand it the disk
|
|
||||||
c1540_->set_disk(target.disks.front());
|
|
||||||
|
|
||||||
// install the ROM if it was previously set
|
|
||||||
install_disk_rom();
|
|
||||||
}
|
|
||||||
|
|
||||||
if(target.cartridges.size()) {
|
|
||||||
rom_address_ = 0xa000;
|
|
||||||
std::vector<uint8_t> rom_image = target.cartridges.front()->get_segments().front().data;
|
|
||||||
rom_length_ = (uint16_t)(rom_image.size());
|
|
||||||
|
|
||||||
rom_ = new uint8_t[0x2000];
|
|
||||||
memcpy(rom_, rom_image.data(), rom_image.size());
|
|
||||||
write_to_map(processor_read_memory_map_, rom_, rom_address_, 0x2000);
|
|
||||||
}
|
|
||||||
|
|
||||||
if(target.loadingCommand.length()) {
|
|
||||||
set_typer_for_string(target.loadingCommand.c_str());
|
|
||||||
}
|
|
||||||
|
|
||||||
switch(target.vic20.memory_model) {
|
|
||||||
case StaticAnalyser::Vic20MemoryModel::Unexpanded:
|
|
||||||
set_memory_size(Default);
|
|
||||||
break;
|
|
||||||
case StaticAnalyser::Vic20MemoryModel::EightKB:
|
|
||||||
set_memory_size(ThreeKB);
|
|
||||||
break;
|
|
||||||
case StaticAnalyser::Vic20MemoryModel::ThirtyTwoKB:
|
|
||||||
set_memory_size(ThirtyTwoKB);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void Machine::set_typer_for_string(const char *string) {
|
|
||||||
std::unique_ptr<CharacterMapper> mapper(new CharacterMapper());
|
std::unique_ptr<CharacterMapper> mapper(new CharacterMapper());
|
||||||
Utility::TypeRecipient::set_typer_for_string(string, std::move(mapper));
|
Utility::TypeRecipient::add_typer(string, std::move(mapper));
|
||||||
}
|
}
|
||||||
|
|
||||||
void Machine::tape_did_change_input(Storage::Tape::BinaryTapePlayer *tape) {
|
void tape_did_change_input(Storage::Tape::BinaryTapePlayer *tape) override final {
|
||||||
keyboard_via_->set_control_line_input(KeyboardVIA::Port::A, KeyboardVIA::Line::One, !tape->get_input());
|
keyboard_via_.set_control_line_input(MOS::MOS6522::Port::A, MOS::MOS6522::Line::One, !tape->get_input());
|
||||||
}
|
}
|
||||||
|
|
||||||
#pragma mark - Disc
|
KeyboardMapper &get_keyboard_mapper() override {
|
||||||
|
return keyboard_mapper_;
|
||||||
|
}
|
||||||
|
|
||||||
void Machine::install_disk_rom() {
|
// MARK: - Configuration options.
|
||||||
if(!drive_rom_.empty() && c1540_) {
|
std::vector<std::unique_ptr<Configurable::Option>> get_options() override {
|
||||||
c1540_->set_rom(drive_rom_);
|
return Commodore::Vic20::get_options();
|
||||||
c1540_->run_for(Cycles(2000000));
|
}
|
||||||
drive_rom_.clear();
|
|
||||||
|
void set_selections(const Configurable::SelectionSet &selections_by_option) override {
|
||||||
|
bool quickload;
|
||||||
|
if(Configurable::get_quick_load_tape(selections_by_option, quickload)) {
|
||||||
|
set_use_fast_tape_hack(quickload);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#pragma mark - UserPortVIA
|
Configurable::SelectionSet get_accurate_selections() override {
|
||||||
|
Configurable::SelectionSet selection_set;
|
||||||
uint8_t UserPortVIA::get_port_input(Port port) {
|
Configurable::append_quick_load_tape_selection(selection_set, false);
|
||||||
if(!port) {
|
return selection_set;
|
||||||
return port_a_ | (tape_->has_tape() ? 0x00 : 0x40);
|
|
||||||
}
|
|
||||||
return 0xff;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void UserPortVIA::set_control_line_output(Port port, Line line, bool value) {
|
Configurable::SelectionSet get_user_friendly_selections() override {
|
||||||
if(port == Port::A && line == Line::Two) {
|
Configurable::SelectionSet selection_set;
|
||||||
tape_->set_motor_control(!value);
|
Configurable::append_quick_load_tape_selection(selection_set, true);
|
||||||
|
return selection_set;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
CPU::MOS6502::Processor<ConcreteMachine, false> m6502_;
|
||||||
|
|
||||||
|
std::vector<uint8_t> roms_[9];
|
||||||
|
|
||||||
|
std::vector<uint8_t> character_rom_;
|
||||||
|
std::vector<uint8_t> basic_rom_;
|
||||||
|
std::vector<uint8_t> kernel_rom_;
|
||||||
|
uint8_t expansion_ram_[0x8000];
|
||||||
|
|
||||||
|
uint8_t *rom_ = nullptr;
|
||||||
|
uint16_t rom_address_, rom_length_;
|
||||||
|
|
||||||
|
uint8_t user_basic_memory_[0x0400];
|
||||||
|
uint8_t screen_memory_[0x1000];
|
||||||
|
uint8_t colour_memory_[0x0400];
|
||||||
|
|
||||||
|
std::function<std::vector<std::unique_ptr<std::vector<uint8_t>>>(const std::string &machine, const std::vector<std::string> &names)> rom_fetcher_;
|
||||||
|
|
||||||
|
uint8_t *processor_read_memory_map_[64];
|
||||||
|
uint8_t *processor_write_memory_map_[64];
|
||||||
|
void write_to_map(uint8_t **map, uint8_t *area, uint16_t address, uint16_t length) {
|
||||||
|
address >>= 10;
|
||||||
|
length >>= 10;
|
||||||
|
while(length--) {
|
||||||
|
map[address] = area;
|
||||||
|
area += 0x400;
|
||||||
|
address++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void UserPortVIA::set_serial_line_state(::Commodore::Serial::Line line, bool value) {
|
Region region_ = European;
|
||||||
switch(line) {
|
MemorySize memory_size_ = MemorySize::Default;
|
||||||
default: break;
|
bool needs_configuration_ = true;
|
||||||
case ::Commodore::Serial::Line::Data: port_a_ = (port_a_ & ~0x02) | (value ? 0x02 : 0x00); break;
|
|
||||||
case ::Commodore::Serial::Line::Clock: port_a_ = (port_a_ & ~0x01) | (value ? 0x01 : 0x00); break;
|
Commodore::Vic20::KeyboardMapper keyboard_mapper_;
|
||||||
|
std::vector<std::unique_ptr<Inputs::Joystick>> joysticks_;
|
||||||
|
|
||||||
|
std::unique_ptr<Vic6560> mos6560_;
|
||||||
|
std::shared_ptr<UserPortVIA> user_port_via_port_handler_;
|
||||||
|
std::shared_ptr<KeyboardVIA> keyboard_via_port_handler_;
|
||||||
|
std::shared_ptr<SerialPort> serial_port_;
|
||||||
|
std::shared_ptr<::Commodore::Serial::Bus> serial_bus_;
|
||||||
|
|
||||||
|
MOS::MOS6522::MOS6522<UserPortVIA> user_port_via_;
|
||||||
|
MOS::MOS6522::MOS6522<KeyboardVIA> keyboard_via_;
|
||||||
|
|
||||||
|
// Tape
|
||||||
|
std::shared_ptr<Storage::Tape::BinaryTapePlayer> tape_;
|
||||||
|
bool use_fast_tape_hack_;
|
||||||
|
bool is_running_at_zero_cost_ = false;
|
||||||
|
|
||||||
|
// Disk
|
||||||
|
std::shared_ptr<::Commodore::C1540::Machine> c1540_;
|
||||||
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void UserPortVIA::set_joystick_state(JoystickInput input, bool value) {
|
using namespace Commodore::Vic20;
|
||||||
if(input != JoystickInput::Right) {
|
|
||||||
port_a_ = (port_a_ & ~input) | (value ? 0 : input);
|
Machine *Machine::Vic20() {
|
||||||
}
|
return new Vic20::ConcreteMachine;
|
||||||
}
|
}
|
||||||
|
|
||||||
void UserPortVIA::set_port_output(Port port, uint8_t value, uint8_t mask) {
|
Machine::~Machine() {}
|
||||||
// Line 7 of port A is inverted and output as serial ATN
|
|
||||||
if(!port) {
|
|
||||||
std::shared_ptr<::Commodore::Serial::Port> serialPort = serial_port_.lock();
|
|
||||||
if(serialPort)
|
|
||||||
serialPort->set_output(::Commodore::Serial::Line::Attention, (::Commodore::Serial::LineLevel)!(value&0x80));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
UserPortVIA::UserPortVIA() : port_a_(0xbf) {}
|
|
||||||
|
|
||||||
void UserPortVIA::set_serial_port(std::shared_ptr<::Commodore::Serial::Port> serialPort) {
|
|
||||||
serial_port_ = serialPort;
|
|
||||||
}
|
|
||||||
|
|
||||||
void UserPortVIA::set_tape(std::shared_ptr<Storage::Tape::BinaryTapePlayer> tape) {
|
|
||||||
tape_ = tape;
|
|
||||||
}
|
|
||||||
|
|
||||||
#pragma mark - KeyboardVIA
|
|
||||||
|
|
||||||
KeyboardVIA::KeyboardVIA() : port_b_(0xff) {
|
|
||||||
clear_all_keys();
|
|
||||||
}
|
|
||||||
|
|
||||||
void KeyboardVIA::set_key_state(uint16_t key, bool isPressed) {
|
|
||||||
if(isPressed)
|
|
||||||
columns_[key & 7] &= ~(key >> 3);
|
|
||||||
else
|
|
||||||
columns_[key & 7] |= (key >> 3);
|
|
||||||
}
|
|
||||||
|
|
||||||
void KeyboardVIA::clear_all_keys() {
|
|
||||||
memset(columns_, 0xff, sizeof(columns_));
|
|
||||||
}
|
|
||||||
|
|
||||||
uint8_t KeyboardVIA::get_port_input(Port port) {
|
|
||||||
if(!port) {
|
|
||||||
uint8_t result = 0xff;
|
|
||||||
for(int c = 0; c < 8; c++) {
|
|
||||||
if(!(activation_mask_&(1 << c)))
|
|
||||||
result &= columns_[c];
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
return port_b_;
|
|
||||||
}
|
|
||||||
|
|
||||||
void KeyboardVIA::set_port_output(Port port, uint8_t value, uint8_t mask) {
|
|
||||||
if(port)
|
|
||||||
activation_mask_ = (value & mask) | (~mask);
|
|
||||||
}
|
|
||||||
|
|
||||||
void KeyboardVIA::set_control_line_output(Port port, Line line, bool value) {
|
|
||||||
if(line == Line::Two) {
|
|
||||||
std::shared_ptr<::Commodore::Serial::Port> serialPort = serial_port_.lock();
|
|
||||||
if(serialPort) {
|
|
||||||
// CB2 is inverted to become serial data; CA2 is inverted to become serial clock
|
|
||||||
if(port == Port::A)
|
|
||||||
serialPort->set_output(::Commodore::Serial::Line::Clock, (::Commodore::Serial::LineLevel)!value);
|
|
||||||
else
|
|
||||||
serialPort->set_output(::Commodore::Serial::Line::Data, (::Commodore::Serial::LineLevel)!value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void KeyboardVIA::set_joystick_state(JoystickInput input, bool value) {
|
|
||||||
if(input == JoystickInput::Right) {
|
|
||||||
port_b_ = (port_b_ & ~input) | (value ? 0 : input);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void KeyboardVIA::set_serial_port(std::shared_ptr<::Commodore::Serial::Port> serialPort) {
|
|
||||||
serial_port_ = serialPort;
|
|
||||||
}
|
|
||||||
|
|
||||||
#pragma mark - SerialPort
|
|
||||||
|
|
||||||
void SerialPort::set_input(::Commodore::Serial::Line line, ::Commodore::Serial::LineLevel level) {
|
|
||||||
std::shared_ptr<UserPortVIA> userPortVIA = user_port_via_.lock();
|
|
||||||
if(userPortVIA) userPortVIA->set_serial_line_state(line, (bool)level);
|
|
||||||
}
|
|
||||||
|
|
||||||
void SerialPort::set_user_port_via(std::shared_ptr<UserPortVIA> userPortVIA) {
|
|
||||||
user_port_via_ = userPortVIA;
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -9,30 +9,15 @@
|
|||||||
#ifndef Vic20_hpp
|
#ifndef Vic20_hpp
|
||||||
#define Vic20_hpp
|
#define Vic20_hpp
|
||||||
|
|
||||||
|
#include "../../../Configurable/Configurable.hpp"
|
||||||
#include "../../ConfigurationTarget.hpp"
|
#include "../../ConfigurationTarget.hpp"
|
||||||
#include "../../CRTMachine.hpp"
|
#include "../../CRTMachine.hpp"
|
||||||
#include "../../Typer.hpp"
|
#include "../../KeyboardMachine.hpp"
|
||||||
|
#include "../../JoystickMachine.hpp"
|
||||||
#include "../../../Processors/6502/6502.hpp"
|
|
||||||
#include "../../../Components/6560/6560.hpp"
|
|
||||||
#include "../../../Components/6522/6522.hpp"
|
|
||||||
|
|
||||||
#include "../SerialBus.hpp"
|
|
||||||
#include "../1540/C1540.hpp"
|
|
||||||
|
|
||||||
#include "../../../Storage/Tape/Tape.hpp"
|
|
||||||
#include "../../../Storage/Disk/Disk.hpp"
|
|
||||||
|
|
||||||
namespace Commodore {
|
namespace Commodore {
|
||||||
namespace Vic20 {
|
namespace Vic20 {
|
||||||
|
|
||||||
enum ROMSlot {
|
|
||||||
Kernel,
|
|
||||||
BASIC,
|
|
||||||
Characters,
|
|
||||||
Drive
|
|
||||||
};
|
|
||||||
|
|
||||||
enum MemorySize {
|
enum MemorySize {
|
||||||
Default,
|
Default,
|
||||||
ThreeKB,
|
ThreeKB,
|
||||||
@@ -40,186 +25,33 @@ enum MemorySize {
|
|||||||
};
|
};
|
||||||
|
|
||||||
enum Region {
|
enum Region {
|
||||||
NTSC,
|
American,
|
||||||
PAL
|
Danish,
|
||||||
|
Japanese,
|
||||||
|
European,
|
||||||
|
Swedish
|
||||||
};
|
};
|
||||||
|
|
||||||
#define key(line, mask) (((mask) << 3) | (line))
|
/// @returns The options available for a Vic-20.
|
||||||
|
std::vector<std::unique_ptr<Configurable::Option>> get_options();
|
||||||
enum Key: uint16_t {
|
|
||||||
Key2 = key(7, 0x01), Key4 = key(7, 0x02), Key6 = key(7, 0x04), Key8 = key(7, 0x08),
|
|
||||||
Key0 = key(7, 0x10), KeyDash = key(7, 0x20), KeyHome = key(7, 0x40), KeyF7 = key(7, 0x80),
|
|
||||||
KeyQ = key(6, 0x01), KeyE = key(6, 0x02), KeyT = key(6, 0x04), KeyU = key(6, 0x08),
|
|
||||||
KeyO = key(6, 0x10), KeyAt = key(6, 0x20), KeyUp = key(6, 0x40), KeyF5 = key(6, 0x80),
|
|
||||||
KeyCBM = key(5, 0x01), KeyS = key(5, 0x02), KeyF = key(5, 0x04), KeyH = key(5, 0x08),
|
|
||||||
KeyK = key(5, 0x10), KeyColon = key(5, 0x20), KeyEquals = key(5, 0x40), KeyF3 = key(5, 0x80),
|
|
||||||
KeySpace = key(4, 0x01), KeyZ = key(4, 0x02), KeyC = key(4, 0x04), KeyB = key(4, 0x08),
|
|
||||||
KeyM = key(4, 0x10), KeyFullStop = key(4, 0x20), KeyRShift = key(4, 0x40), KeyF1 = key(4, 0x80),
|
|
||||||
KeyRunStop = key(3, 0x01), KeyLShift = key(3, 0x02), KeyX = key(3, 0x04), KeyV = key(3, 0x08),
|
|
||||||
KeyN = key(3, 0x10), KeyComma = key(3, 0x20), KeySlash = key(3, 0x40), KeyDown = key(3, 0x80),
|
|
||||||
KeyControl = key(2, 0x01), KeyA = key(2, 0x02), KeyD = key(2, 0x04), KeyG = key(2, 0x08),
|
|
||||||
KeyJ = key(2, 0x10), KeyL = key(2, 0x20), KeySemicolon = key(2, 0x40), KeyRight = key(2, 0x80),
|
|
||||||
KeyLeft = key(1, 0x01), KeyW = key(1, 0x02), KeyR = key(1, 0x04), KeyY = key(1, 0x08),
|
|
||||||
KeyI = key(1, 0x10), KeyP = key(1, 0x20), KeyAsterisk = key(1, 0x40), KeyReturn = key(1, 0x80),
|
|
||||||
Key1 = key(0, 0x01), Key3 = key(0, 0x02), Key5 = key(0, 0x04), Key7 = key(0, 0x08),
|
|
||||||
Key9 = key(0, 0x10), KeyPlus = key(0, 0x20), KeyGBP = key(0, 0x40), KeyDelete = key(0, 0x80),
|
|
||||||
};
|
|
||||||
|
|
||||||
enum JoystickInput {
|
|
||||||
Up = 0x04,
|
|
||||||
Down = 0x08,
|
|
||||||
Left = 0x10,
|
|
||||||
Right = 0x80,
|
|
||||||
Fire = 0x20
|
|
||||||
};
|
|
||||||
|
|
||||||
class UserPortVIA: public MOS::MOS6522<UserPortVIA>, public MOS::MOS6522IRQDelegate {
|
|
||||||
public:
|
|
||||||
UserPortVIA();
|
|
||||||
using MOS6522IRQDelegate::set_interrupt_status;
|
|
||||||
|
|
||||||
uint8_t get_port_input(Port port);
|
|
||||||
void set_control_line_output(Port port, Line line, bool value);
|
|
||||||
void set_serial_line_state(::Commodore::Serial::Line line, bool value);
|
|
||||||
void set_joystick_state(JoystickInput input, bool value);
|
|
||||||
void set_port_output(Port port, uint8_t value, uint8_t mask);
|
|
||||||
|
|
||||||
void set_serial_port(std::shared_ptr<::Commodore::Serial::Port> serialPort);
|
|
||||||
void set_tape(std::shared_ptr<Storage::Tape::BinaryTapePlayer> tape);
|
|
||||||
|
|
||||||
private:
|
|
||||||
uint8_t port_a_;
|
|
||||||
std::weak_ptr<::Commodore::Serial::Port> serial_port_;
|
|
||||||
std::shared_ptr<Storage::Tape::BinaryTapePlayer> tape_;
|
|
||||||
};
|
|
||||||
|
|
||||||
class KeyboardVIA: public MOS::MOS6522<KeyboardVIA>, public MOS::MOS6522IRQDelegate {
|
|
||||||
public:
|
|
||||||
KeyboardVIA();
|
|
||||||
using MOS6522IRQDelegate::set_interrupt_status;
|
|
||||||
|
|
||||||
void set_key_state(uint16_t key, bool isPressed);
|
|
||||||
void clear_all_keys();
|
|
||||||
|
|
||||||
// to satisfy MOS::MOS6522
|
|
||||||
uint8_t get_port_input(Port port);
|
|
||||||
|
|
||||||
void set_port_output(Port port, uint8_t value, uint8_t mask);
|
|
||||||
void set_control_line_output(Port port, Line line, bool value);
|
|
||||||
|
|
||||||
void set_joystick_state(JoystickInput input, bool value);
|
|
||||||
|
|
||||||
void set_serial_port(std::shared_ptr<::Commodore::Serial::Port> serialPort);
|
|
||||||
|
|
||||||
private:
|
|
||||||
uint8_t port_b_;
|
|
||||||
uint8_t columns_[8];
|
|
||||||
uint8_t activation_mask_;
|
|
||||||
std::weak_ptr<::Commodore::Serial::Port> serial_port_;
|
|
||||||
};
|
|
||||||
|
|
||||||
class SerialPort : public ::Commodore::Serial::Port {
|
|
||||||
public:
|
|
||||||
void set_input(::Commodore::Serial::Line line, ::Commodore::Serial::LineLevel level);
|
|
||||||
void set_user_port_via(std::shared_ptr<UserPortVIA> userPortVIA);
|
|
||||||
|
|
||||||
private:
|
|
||||||
std::weak_ptr<UserPortVIA> user_port_via_;
|
|
||||||
};
|
|
||||||
|
|
||||||
class Vic6560: public MOS::MOS6560<Vic6560> {
|
|
||||||
public:
|
|
||||||
inline void perform_read(uint16_t address, uint8_t *pixel_data, uint8_t *colour_data) {
|
|
||||||
*pixel_data = video_memory_map[address >> 10] ? video_memory_map[address >> 10][address & 0x3ff] : 0xff; // TODO
|
|
||||||
*colour_data = colour_memory[address & 0x03ff];
|
|
||||||
}
|
|
||||||
|
|
||||||
uint8_t *video_memory_map[16];
|
|
||||||
uint8_t *colour_memory;
|
|
||||||
};
|
|
||||||
|
|
||||||
class Machine:
|
class Machine:
|
||||||
public CPU::MOS6502::Processor<Machine>,
|
|
||||||
public CRTMachine::Machine,
|
public CRTMachine::Machine,
|
||||||
public MOS::MOS6522IRQDelegate::Delegate,
|
public ConfigurationTarget::Machine,
|
||||||
public Utility::TypeRecipient,
|
public KeyboardMachine::Machine,
|
||||||
public Storage::Tape::BinaryTapePlayer::Delegate,
|
public JoystickMachine::Machine,
|
||||||
public ConfigurationTarget::Machine {
|
public Configurable::Device {
|
||||||
|
|
||||||
public:
|
public:
|
||||||
Machine();
|
virtual ~Machine();
|
||||||
~Machine();
|
|
||||||
|
|
||||||
void set_rom(ROMSlot slot, size_t length, const uint8_t *data);
|
/// Creates and returns a Vic-20.
|
||||||
void configure_as_target(const StaticAnalyser::Target &target);
|
static Machine *Vic20();
|
||||||
|
|
||||||
void set_key_state(uint16_t key, bool isPressed) { keyboard_via_->set_key_state(key, isPressed); }
|
/// Sets the memory size of this Vic-20.
|
||||||
void clear_all_keys() { keyboard_via_->clear_all_keys(); }
|
virtual void set_memory_size(MemorySize size) = 0;
|
||||||
void set_joystick_state(JoystickInput input, bool isPressed) {
|
|
||||||
user_port_via_->set_joystick_state(input, isPressed);
|
|
||||||
keyboard_via_->set_joystick_state(input, isPressed);
|
|
||||||
}
|
|
||||||
|
|
||||||
void set_memory_size(MemorySize size);
|
/// Sets the region of this Vic-20.
|
||||||
void set_region(Region region);
|
virtual void set_region(Region region) = 0;
|
||||||
|
|
||||||
inline void set_use_fast_tape_hack(bool activate) { use_fast_tape_hack_ = activate; }
|
|
||||||
|
|
||||||
// to satisfy CPU::MOS6502::Processor
|
|
||||||
Cycles perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value);
|
|
||||||
void flush() { mos6560_->flush(); }
|
|
||||||
|
|
||||||
// to satisfy CRTMachine::Machine
|
|
||||||
virtual void setup_output(float aspect_ratio);
|
|
||||||
virtual void close_output();
|
|
||||||
virtual std::shared_ptr<Outputs::CRT::CRT> get_crt() { return mos6560_->get_crt(); }
|
|
||||||
virtual std::shared_ptr<Outputs::Speaker> get_speaker() { return mos6560_->get_speaker(); }
|
|
||||||
virtual void run_for(const Cycles cycles) { CPU::MOS6502::Processor<Machine>::run_for(cycles); }
|
|
||||||
|
|
||||||
// to satisfy MOS::MOS6522::Delegate
|
|
||||||
virtual void mos6522_did_change_interrupt_status(void *mos6522);
|
|
||||||
|
|
||||||
// for Utility::TypeRecipient
|
|
||||||
uint16_t *sequence_for_character(Utility::Typer *typer, char character);
|
|
||||||
void set_typer_for_string(const char *string);
|
|
||||||
|
|
||||||
// for Tape::Delegate
|
|
||||||
virtual void tape_did_change_input(Storage::Tape::BinaryTapePlayer *tape);
|
|
||||||
|
|
||||||
private:
|
|
||||||
uint8_t character_rom_[0x1000];
|
|
||||||
uint8_t basic_rom_[0x2000];
|
|
||||||
uint8_t kernel_rom_[0x2000];
|
|
||||||
uint8_t expansion_ram_[0x8000];
|
|
||||||
|
|
||||||
uint8_t *rom_;
|
|
||||||
uint16_t rom_address_, rom_length_;
|
|
||||||
|
|
||||||
uint8_t user_basic_memory_[0x0400];
|
|
||||||
uint8_t screen_memory_[0x1000];
|
|
||||||
uint8_t colour_memory_[0x0400];
|
|
||||||
std::vector<uint8_t> drive_rom_;
|
|
||||||
|
|
||||||
uint8_t *processor_read_memory_map_[64];
|
|
||||||
uint8_t *processor_write_memory_map_[64];
|
|
||||||
void write_to_map(uint8_t **map, uint8_t *area, uint16_t address, uint16_t length);
|
|
||||||
|
|
||||||
Region region_;
|
|
||||||
|
|
||||||
std::unique_ptr<Vic6560> mos6560_;
|
|
||||||
std::shared_ptr<UserPortVIA> user_port_via_;
|
|
||||||
std::shared_ptr<KeyboardVIA> keyboard_via_;
|
|
||||||
std::shared_ptr<SerialPort> serial_port_;
|
|
||||||
std::shared_ptr<::Commodore::Serial::Bus> serial_bus_;
|
|
||||||
|
|
||||||
// Tape
|
|
||||||
std::shared_ptr<Storage::Tape::BinaryTapePlayer> tape_;
|
|
||||||
bool use_fast_tape_hack_;
|
|
||||||
bool is_running_at_zero_cost_;
|
|
||||||
|
|
||||||
// Disk
|
|
||||||
std::shared_ptr<::Commodore::C1540::Machine> c1540_;
|
|
||||||
void install_disk_rom();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,16 +10,27 @@
|
|||||||
#define ConfigurationTarget_hpp
|
#define ConfigurationTarget_hpp
|
||||||
|
|
||||||
#include "../StaticAnalyser/StaticAnalyser.hpp"
|
#include "../StaticAnalyser/StaticAnalyser.hpp"
|
||||||
|
#include "../Configurable/Configurable.hpp"
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
|
||||||
namespace ConfigurationTarget {
|
namespace ConfigurationTarget {
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
A ConfigurationTarget::Machine is anything that can accept a StaticAnalyser::Target
|
A ConfigurationTarget::Machine is anything that can accept a StaticAnalyser::Target
|
||||||
and configure itself appropriately.
|
and configure itself appropriately, or accept a list of media subsequently to insert.
|
||||||
*/
|
*/
|
||||||
class Machine {
|
class Machine {
|
||||||
public:
|
public:
|
||||||
|
/// Instructs the machine to configure itself as described by @c target and insert the included media.
|
||||||
virtual void configure_as_target(const StaticAnalyser::Target &target) = 0;
|
virtual void configure_as_target(const StaticAnalyser::Target &target) = 0;
|
||||||
|
|
||||||
|
/*!
|
||||||
|
Requests that the machine insert @c media as a modification to current state
|
||||||
|
|
||||||
|
@returns @c true if any media was inserted; @c false otherwise.
|
||||||
|
*/
|
||||||
|
virtual bool insert_media(const StaticAnalyser::Media &media) = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,23 +0,0 @@
|
|||||||
//
|
|
||||||
// CharacterMapper.hpp
|
|
||||||
// Clock Signal
|
|
||||||
//
|
|
||||||
// Created by Thomas Harte on 03/08/2017.
|
|
||||||
// Copyright © 2017 Thomas Harte. All rights reserved.
|
|
||||||
//
|
|
||||||
|
|
||||||
#ifndef Machines_Electron_CharacterMapper_hpp
|
|
||||||
#define Machines_Electron_CharacterMapper_hpp
|
|
||||||
|
|
||||||
#include "../Typer.hpp"
|
|
||||||
|
|
||||||
namespace Electron {
|
|
||||||
|
|
||||||
class CharacterMapper: public ::Utility::CharacterMapper {
|
|
||||||
public:
|
|
||||||
uint16_t *sequence_for_character(char character);
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif /* Machines_Electron_CharacterMapper_hpp */
|
|
||||||
@@ -8,114 +8,56 @@
|
|||||||
|
|
||||||
#include "Electron.hpp"
|
#include "Electron.hpp"
|
||||||
|
|
||||||
#include "CharacterMapper.hpp"
|
#include "../../ClockReceiver/ClockReceiver.hpp"
|
||||||
|
#include "../../ClockReceiver/ForceInline.hpp"
|
||||||
|
#include "../../Configurable/StandardOptions.hpp"
|
||||||
|
#include "../../Outputs/Speaker/Implementation/LowpassSpeaker.hpp"
|
||||||
|
#include "../../Processors/6502/6502.hpp"
|
||||||
|
#include "../../Storage/Tape/Tape.hpp"
|
||||||
|
|
||||||
using namespace Electron;
|
#include "../Utility/Typer.hpp"
|
||||||
|
|
||||||
#pragma mark - Lifecycle
|
#include "Interrupts.hpp"
|
||||||
|
#include "Keyboard.hpp"
|
||||||
|
#include "Plus3.hpp"
|
||||||
|
#include "SoundGenerator.hpp"
|
||||||
|
#include "Tape.hpp"
|
||||||
|
#include "Video.hpp"
|
||||||
|
|
||||||
Machine::Machine() :
|
namespace Electron {
|
||||||
interrupt_control_(0),
|
|
||||||
interrupt_status_(Interrupt::PowerOnReset | Interrupt::TransmitDataEmpty | 0x80),
|
std::vector<std::unique_ptr<Configurable::Option>> get_options() {
|
||||||
cycles_since_audio_update_(0),
|
return Configurable::standard_options(
|
||||||
use_fast_tape_hack_(false),
|
static_cast<Configurable::StandardOptions>(Configurable::DisplayRGBComposite | Configurable::QuickLoadTape)
|
||||||
cycles_until_display_interrupt_(0) {
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
class ConcreteMachine:
|
||||||
|
public Machine,
|
||||||
|
public CPU::MOS6502::BusHandler,
|
||||||
|
public Tape::Delegate,
|
||||||
|
public Utility::TypeRecipient {
|
||||||
|
public:
|
||||||
|
ConcreteMachine() :
|
||||||
|
m6502_(*this),
|
||||||
|
sound_generator_(audio_queue_),
|
||||||
|
speaker_(sound_generator_) {
|
||||||
memset(key_states_, 0, sizeof(key_states_));
|
memset(key_states_, 0, sizeof(key_states_));
|
||||||
for(int c = 0; c < 16; c++)
|
for(int c = 0; c < 16; c++)
|
||||||
memset(roms_[c], 0xff, 16384);
|
memset(roms_[c], 0xff, 16384);
|
||||||
|
|
||||||
tape_.set_delegate(this);
|
tape_.set_delegate(this);
|
||||||
set_clock_rate(2000000);
|
set_clock_rate(2000000);
|
||||||
|
|
||||||
|
speaker_.set_input_rate(2000000 / SoundGenerator::clock_rate_divider);
|
||||||
}
|
}
|
||||||
|
|
||||||
#pragma mark - Output
|
void set_rom(ROMSlot slot, const std::vector<uint8_t> &data, bool is_writeable) override final {
|
||||||
|
|
||||||
void Machine::setup_output(float aspect_ratio) {
|
|
||||||
video_output_.reset(new VideoOutput(ram_));
|
|
||||||
|
|
||||||
// The maximum output frequency is 62500Hz and all other permitted output frequencies are integral divisions of that;
|
|
||||||
// however setting the speaker on or off can happen on any 2Mhz cycle, and probably (?) takes effect immediately. So
|
|
||||||
// run the speaker at a 2000000Hz input rate, at least for the time being.
|
|
||||||
speaker_.reset(new Speaker);
|
|
||||||
speaker_->set_input_rate(2000000 / Speaker::clock_rate_divider);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Machine::close_output() {
|
|
||||||
video_output_.reset();
|
|
||||||
}
|
|
||||||
|
|
||||||
std::shared_ptr<Outputs::CRT::CRT> Machine::get_crt() {
|
|
||||||
return video_output_->get_crt();
|
|
||||||
}
|
|
||||||
|
|
||||||
std::shared_ptr<Outputs::Speaker> Machine::get_speaker() {
|
|
||||||
return speaker_;
|
|
||||||
}
|
|
||||||
|
|
||||||
#pragma mark - The keyboard
|
|
||||||
|
|
||||||
void Machine::clear_all_keys() {
|
|
||||||
memset(key_states_, 0, sizeof(key_states_));
|
|
||||||
if(is_holding_shift_) set_key_state(KeyShift, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Machine::set_key_state(uint16_t key, bool isPressed) {
|
|
||||||
if(key == KeyBreak) {
|
|
||||||
set_reset_line(isPressed);
|
|
||||||
} else {
|
|
||||||
if(isPressed)
|
|
||||||
key_states_[key >> 4] |= key&0xf;
|
|
||||||
else
|
|
||||||
key_states_[key >> 4] &= ~(key&0xf);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#pragma mark - Machine configuration
|
|
||||||
|
|
||||||
void Machine::configure_as_target(const StaticAnalyser::Target &target) {
|
|
||||||
if(target.tapes.size()) {
|
|
||||||
tape_.set_tape(target.tapes.front());
|
|
||||||
}
|
|
||||||
|
|
||||||
if(target.disks.size()) {
|
|
||||||
plus3_.reset(new Plus3);
|
|
||||||
|
|
||||||
if(target.acorn.has_dfs) {
|
|
||||||
set_rom(ROMSlot0, dfs_, true);
|
|
||||||
}
|
|
||||||
if(target.acorn.has_adfs) {
|
|
||||||
set_rom(ROMSlot4, adfs_, true);
|
|
||||||
set_rom(ROMSlot5, std::vector<uint8_t>(adfs_.begin() + 16384, adfs_.end()), true);
|
|
||||||
}
|
|
||||||
|
|
||||||
plus3_->set_disk(target.disks.front(), 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
ROMSlot slot = ROMSlot12;
|
|
||||||
for(std::shared_ptr<Storage::Cartridge::Cartridge> cartridge : target.cartridges) {
|
|
||||||
set_rom(slot, cartridge->get_segments().front().data, false);
|
|
||||||
slot = (ROMSlot)(((int)slot + 1)&15);
|
|
||||||
}
|
|
||||||
|
|
||||||
if(target.loadingCommand.length()) {
|
|
||||||
set_typer_for_string(target.loadingCommand.c_str());
|
|
||||||
}
|
|
||||||
|
|
||||||
if(target.acorn.should_shift_restart) {
|
|
||||||
shift_restart_counter_ = 1000000;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void Machine::set_typer_for_string(const char *string) {
|
|
||||||
std::unique_ptr<CharacterMapper> mapper(new CharacterMapper());
|
|
||||||
Utility::TypeRecipient::set_typer_for_string(string, std::move(mapper));
|
|
||||||
}
|
|
||||||
|
|
||||||
void Machine::set_rom(ROMSlot slot, std::vector<uint8_t> data, bool is_writeable) {
|
|
||||||
uint8_t *target = nullptr;
|
uint8_t *target = nullptr;
|
||||||
switch(slot) {
|
switch(slot) {
|
||||||
case ROMSlotDFS: dfs_ = data; return;
|
case ROMSlotDFS: dfs_ = data; return;
|
||||||
case ROMSlotADFS: adfs_ = data; return;
|
case ROMSlotADFS1: adfs1_ = data; return;
|
||||||
|
case ROMSlotADFS2: adfs2_ = data; return;
|
||||||
|
|
||||||
case ROMSlotOS: target = os_; break;
|
case ROMSlotOS: target = os_; break;
|
||||||
default:
|
default:
|
||||||
@@ -124,12 +66,96 @@ void Machine::set_rom(ROMSlot slot, std::vector<uint8_t> data, bool is_writeable
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
memcpy(target, &data[0], std::min((size_t)16384, data.size()));
|
std::memcpy(target, &data[0], std::min(static_cast<std::size_t>(16384), data.size()));
|
||||||
}
|
}
|
||||||
|
|
||||||
#pragma mark - The bus
|
// Obtains the system ROMs.
|
||||||
|
bool set_rom_fetcher(const std::function<std::vector<std::unique_ptr<std::vector<uint8_t>>>(const std::string &machine, const std::vector<std::string> &names)> &roms_with_names) override {
|
||||||
|
auto roms = roms_with_names(
|
||||||
|
"Electron",
|
||||||
|
{
|
||||||
|
"DFS-1770-2.20.rom",
|
||||||
|
"ADFS-E00_1.rom", "ADFS-E00_2.rom",
|
||||||
|
"basic.rom", "os.rom"
|
||||||
|
});
|
||||||
|
ROMSlot slots[] = {
|
||||||
|
ROMSlotDFS,
|
||||||
|
ROMSlotADFS1, ROMSlotADFS2,
|
||||||
|
ROMSlotBASIC, ROMSlotOS
|
||||||
|
};
|
||||||
|
|
||||||
Cycles Machine::perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value) {
|
for(std::size_t index = 0; index < roms.size(); ++index) {
|
||||||
|
auto &data = roms[index];
|
||||||
|
if(!data) return false;
|
||||||
|
set_rom(slots[index], *data, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void set_key_state(uint16_t key, bool isPressed) override final {
|
||||||
|
if(key == KeyBreak) {
|
||||||
|
m6502_.set_reset_line(isPressed);
|
||||||
|
} else {
|
||||||
|
if(isPressed)
|
||||||
|
key_states_[key >> 4] |= key&0xf;
|
||||||
|
else
|
||||||
|
key_states_[key >> 4] &= ~(key&0xf);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void clear_all_keys() override final {
|
||||||
|
memset(key_states_, 0, sizeof(key_states_));
|
||||||
|
if(is_holding_shift_) set_key_state(KeyShift, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
void set_use_fast_tape_hack(bool activate) {
|
||||||
|
use_fast_tape_hack_ = activate;
|
||||||
|
}
|
||||||
|
|
||||||
|
void configure_as_target(const StaticAnalyser::Target &target) override final {
|
||||||
|
if(target.loading_command.length()) {
|
||||||
|
type_string(target.loading_command);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(target.acorn.should_shift_restart) {
|
||||||
|
shift_restart_counter_ = 1000000;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(target.acorn.has_dfs || target.acorn.has_adfs) {
|
||||||
|
plus3_.reset(new Plus3);
|
||||||
|
|
||||||
|
if(target.acorn.has_dfs) {
|
||||||
|
set_rom(ROMSlot0, dfs_, true);
|
||||||
|
}
|
||||||
|
if(target.acorn.has_adfs) {
|
||||||
|
set_rom(ROMSlot4, adfs1_, true);
|
||||||
|
set_rom(ROMSlot5, adfs2_, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
insert_media(target.media);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool insert_media(const StaticAnalyser::Media &media) override final {
|
||||||
|
if(!media.tapes.empty()) {
|
||||||
|
tape_.set_tape(media.tapes.front());
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!media.disks.empty() && plus3_) {
|
||||||
|
plus3_->set_disk(media.disks.front(), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
ROMSlot slot = ROMSlot12;
|
||||||
|
for(std::shared_ptr<Storage::Cartridge::Cartridge> cartridge : media.cartridges) {
|
||||||
|
set_rom(slot, cartridge->get_segments().front().data, false);
|
||||||
|
slot = static_cast<ROMSlot>((static_cast<int>(slot) + 1)&15);
|
||||||
|
}
|
||||||
|
|
||||||
|
return !media.tapes.empty() || !media.disks.empty() || !media.cartridges.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
forceinline Cycles perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value) {
|
||||||
unsigned int cycles = 1;
|
unsigned int cycles = 1;
|
||||||
|
|
||||||
if(address < 0x8000) {
|
if(address < 0x8000) {
|
||||||
@@ -160,7 +186,7 @@ Cycles Machine::perform_bus_operation(CPU::MOS6502::BusOperation operation, uint
|
|||||||
bool new_speaker_is_enabled = (*value & 6) == 2;
|
bool new_speaker_is_enabled = (*value & 6) == 2;
|
||||||
if(new_speaker_is_enabled != speaker_is_enabled_) {
|
if(new_speaker_is_enabled != speaker_is_enabled_) {
|
||||||
update_audio();
|
update_audio();
|
||||||
speaker_->set_is_enabled(new_speaker_is_enabled);
|
sound_generator_.set_is_enabled(new_speaker_is_enabled);
|
||||||
speaker_is_enabled_ = new_speaker_is_enabled;
|
speaker_is_enabled_ = new_speaker_is_enabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -221,7 +247,7 @@ Cycles Machine::perform_bus_operation(CPU::MOS6502::BusOperation operation, uint
|
|||||||
case 0xfe06:
|
case 0xfe06:
|
||||||
if(!isReadOperation(operation)) {
|
if(!isReadOperation(operation)) {
|
||||||
update_audio();
|
update_audio();
|
||||||
speaker_->set_divider(*value);
|
sound_generator_.set_divider(*value);
|
||||||
tape_.set_counter(*value);
|
tape_.set_counter(*value);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@@ -269,7 +295,7 @@ Cycles Machine::perform_bus_operation(CPU::MOS6502::BusOperation operation, uint
|
|||||||
// allow the PC read to return an RTS.
|
// allow the PC read to return an RTS.
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
uint8_t service_call = (uint8_t)get_value_of_register(CPU::MOS6502::Register::X);
|
uint8_t service_call = static_cast<uint8_t>(m6502_.get_value_of_register(CPU::MOS6502::Register::X));
|
||||||
if(address == 0xf0a8) {
|
if(address == 0xf0a8) {
|
||||||
if(!ram_[0x247] && service_call == 14) {
|
if(!ram_[0x247] && service_call == 14) {
|
||||||
tape_.set_delegate(nullptr);
|
tape_.set_delegate(nullptr);
|
||||||
@@ -291,8 +317,8 @@ Cycles Machine::perform_bus_operation(CPU::MOS6502::BusOperation operation, uint
|
|||||||
interrupt_status_ |= tape_.get_interrupt_status();
|
interrupt_status_ |= tape_.get_interrupt_status();
|
||||||
|
|
||||||
fast_load_is_in_data_ = true;
|
fast_load_is_in_data_ = true;
|
||||||
set_value_of_register(CPU::MOS6502::Register::A, 0);
|
m6502_.set_value_of_register(CPU::MOS6502::Register::A, 0);
|
||||||
set_value_of_register(CPU::MOS6502::Register::Y, tape_.get_data_register());
|
m6502_.set_value_of_register(CPU::MOS6502::Register::Y, tape_.get_data_register());
|
||||||
*value = 0x60; // 0x60 is RTS
|
*value = 0x60; // 0x60 is RTS
|
||||||
}
|
}
|
||||||
else *value = os_[address & 16383];
|
else *value = os_[address & 16383];
|
||||||
@@ -322,10 +348,10 @@ Cycles Machine::perform_bus_operation(CPU::MOS6502::BusOperation operation, uint
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
cycles_since_display_update_ += Cycles((int)cycles);
|
cycles_since_display_update_ += Cycles(static_cast<int>(cycles));
|
||||||
cycles_since_audio_update_ += Cycles((int)cycles);
|
cycles_since_audio_update_ += Cycles(static_cast<int>(cycles));
|
||||||
if(cycles_since_audio_update_ > Cycles(16384)) update_audio();
|
if(cycles_since_audio_update_ > Cycles(16384)) update_audio();
|
||||||
tape_.run_for(Cycles((int)cycles));
|
tape_.run_for(Cycles(static_cast<int>(cycles)));
|
||||||
|
|
||||||
cycles_until_display_interrupt_ -= cycles;
|
cycles_until_display_interrupt_ -= cycles;
|
||||||
if(cycles_until_display_interrupt_ < 0) {
|
if(cycles_until_display_interrupt_ < 0) {
|
||||||
@@ -334,81 +360,189 @@ Cycles Machine::perform_bus_operation(CPU::MOS6502::BusOperation operation, uint
|
|||||||
queue_next_display_interrupt();
|
queue_next_display_interrupt();
|
||||||
}
|
}
|
||||||
|
|
||||||
if(typer_) typer_->run_for(Cycles((int)cycles));
|
if(typer_) typer_->run_for(Cycles(static_cast<int>(cycles)));
|
||||||
if(plus3_) plus3_->run_for(Cycles(4*(int)cycles));
|
if(plus3_) plus3_->run_for(Cycles(4*static_cast<int>(cycles)));
|
||||||
if(shift_restart_counter_) {
|
if(shift_restart_counter_) {
|
||||||
shift_restart_counter_ -= cycles;
|
shift_restart_counter_ -= cycles;
|
||||||
if(shift_restart_counter_ <= 0) {
|
if(shift_restart_counter_ <= 0) {
|
||||||
shift_restart_counter_ = 0;
|
shift_restart_counter_ = 0;
|
||||||
set_power_on(true);
|
m6502_.set_power_on(true);
|
||||||
set_key_state(KeyShift, true);
|
set_key_state(KeyShift, true);
|
||||||
is_holding_shift_ = true;
|
is_holding_shift_ = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return Cycles((int)cycles);
|
return Cycles(static_cast<int>(cycles));
|
||||||
}
|
}
|
||||||
|
|
||||||
void Machine::flush() {
|
forceinline void flush() {
|
||||||
update_display();
|
update_display();
|
||||||
update_audio();
|
update_audio();
|
||||||
speaker_->flush();
|
audio_queue_.perform();
|
||||||
}
|
}
|
||||||
|
|
||||||
#pragma mark - Deferred scheduling
|
void setup_output(float aspect_ratio) override final {
|
||||||
|
video_output_.reset(new VideoOutput(ram_));
|
||||||
|
}
|
||||||
|
|
||||||
inline void Machine::update_display() {
|
void close_output() override final {
|
||||||
|
video_output_.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
Outputs::CRT::CRT *get_crt() override final {
|
||||||
|
return video_output_->get_crt();
|
||||||
|
}
|
||||||
|
|
||||||
|
Outputs::Speaker::Speaker *get_speaker() override final {
|
||||||
|
return &speaker_;
|
||||||
|
}
|
||||||
|
|
||||||
|
void run_for(const Cycles cycles) override final {
|
||||||
|
m6502_.run_for(cycles);
|
||||||
|
}
|
||||||
|
|
||||||
|
void tape_did_change_interrupt_status(Tape *tape) override final {
|
||||||
|
interrupt_status_ = (interrupt_status_ & ~(Interrupt::TransmitDataEmpty | Interrupt::ReceiveDataFull | Interrupt::HighToneDetect)) | tape_.get_interrupt_status();
|
||||||
|
evaluate_interrupts();
|
||||||
|
}
|
||||||
|
|
||||||
|
HalfCycles get_typer_delay() override final {
|
||||||
|
return m6502_.get_is_resetting() ? Cycles(625*25*128) : Cycles(0); // wait one second if resetting
|
||||||
|
}
|
||||||
|
|
||||||
|
HalfCycles get_typer_frequency() override final {
|
||||||
|
return Cycles(625*128*2); // accept a new character every two frames
|
||||||
|
}
|
||||||
|
|
||||||
|
void type_string(const std::string &string) override final {
|
||||||
|
std::unique_ptr<CharacterMapper> mapper(new CharacterMapper());
|
||||||
|
Utility::TypeRecipient::add_typer(string, std::move(mapper));
|
||||||
|
}
|
||||||
|
|
||||||
|
KeyboardMapper &get_keyboard_mapper() override {
|
||||||
|
return keyboard_mapper_;
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Configuration options.
|
||||||
|
std::vector<std::unique_ptr<Configurable::Option>> get_options() override {
|
||||||
|
return Electron::get_options();
|
||||||
|
}
|
||||||
|
|
||||||
|
void set_selections(const Configurable::SelectionSet &selections_by_option) override {
|
||||||
|
bool quickload;
|
||||||
|
if(Configurable::get_quick_load_tape(selections_by_option, quickload)) {
|
||||||
|
set_use_fast_tape_hack(quickload);
|
||||||
|
}
|
||||||
|
|
||||||
|
Configurable::Display display;
|
||||||
|
if(Configurable::get_display(selections_by_option, display)) {
|
||||||
|
get_crt()->set_output_device((display == Configurable::Display::RGB) ? Outputs::CRT::OutputDevice::Monitor : Outputs::CRT::OutputDevice::Television);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Configurable::SelectionSet get_accurate_selections() override {
|
||||||
|
Configurable::SelectionSet selection_set;
|
||||||
|
Configurable::append_quick_load_tape_selection(selection_set, false);
|
||||||
|
Configurable::append_display_selection(selection_set, Configurable::Display::Composite);
|
||||||
|
return selection_set;
|
||||||
|
}
|
||||||
|
|
||||||
|
Configurable::SelectionSet get_user_friendly_selections() override {
|
||||||
|
Configurable::SelectionSet selection_set;
|
||||||
|
Configurable::append_quick_load_tape_selection(selection_set, true);
|
||||||
|
Configurable::append_display_selection(selection_set, Configurable::Display::RGB);
|
||||||
|
return selection_set;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
// MARK: - Work deferral updates.
|
||||||
|
inline void update_display() {
|
||||||
if(cycles_since_display_update_ > 0) {
|
if(cycles_since_display_update_ > 0) {
|
||||||
video_output_->run_for(cycles_since_display_update_.flush());
|
video_output_->run_for(cycles_since_display_update_.flush());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
inline void Machine::queue_next_display_interrupt() {
|
inline void queue_next_display_interrupt() {
|
||||||
VideoOutput::Interrupt next_interrupt = video_output_->get_next_interrupt();
|
VideoOutput::Interrupt next_interrupt = video_output_->get_next_interrupt();
|
||||||
cycles_until_display_interrupt_ = next_interrupt.cycles;
|
cycles_until_display_interrupt_ = next_interrupt.cycles;
|
||||||
next_display_interrupt_ = next_interrupt.interrupt;
|
next_display_interrupt_ = next_interrupt.interrupt;
|
||||||
}
|
}
|
||||||
|
|
||||||
inline void Machine::update_audio() {
|
inline void update_audio() {
|
||||||
if(cycles_since_audio_update_ > 0) {
|
speaker_.run_for(audio_queue_, cycles_since_audio_update_.divide(Cycles(SoundGenerator::clock_rate_divider)));
|
||||||
speaker_->run_for(cycles_since_audio_update_.divide(Cycles(Speaker::clock_rate_divider)));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#pragma mark - Interrupts
|
inline void signal_interrupt(Interrupt interrupt) {
|
||||||
|
|
||||||
inline void Machine::signal_interrupt(Electron::Interrupt interrupt) {
|
|
||||||
interrupt_status_ |= interrupt;
|
interrupt_status_ |= interrupt;
|
||||||
evaluate_interrupts();
|
evaluate_interrupts();
|
||||||
}
|
}
|
||||||
|
|
||||||
inline void Machine::clear_interrupt(Electron::Interrupt interrupt) {
|
inline void clear_interrupt(Interrupt interrupt) {
|
||||||
interrupt_status_ &= ~interrupt;
|
interrupt_status_ &= ~interrupt;
|
||||||
evaluate_interrupts();
|
evaluate_interrupts();
|
||||||
}
|
}
|
||||||
|
|
||||||
inline void Machine::evaluate_interrupts() {
|
inline void evaluate_interrupts() {
|
||||||
if(interrupt_status_ & interrupt_control_) {
|
if(interrupt_status_ & interrupt_control_) {
|
||||||
interrupt_status_ |= 1;
|
interrupt_status_ |= 1;
|
||||||
} else {
|
} else {
|
||||||
interrupt_status_ &= ~1;
|
interrupt_status_ &= ~1;
|
||||||
}
|
}
|
||||||
set_irq_line(interrupt_status_ & 1);
|
m6502_.set_irq_line(interrupt_status_ & 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
#pragma mark - Tape::Delegate
|
CPU::MOS6502::Processor<ConcreteMachine, false> m6502_;
|
||||||
|
|
||||||
|
// Things that directly constitute the memory map.
|
||||||
|
uint8_t roms_[16][16384];
|
||||||
|
bool rom_write_masks_[16] = {false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false};
|
||||||
|
uint8_t os_[16384], ram_[32768];
|
||||||
|
std::vector<uint8_t> dfs_, adfs1_, adfs2_;
|
||||||
|
|
||||||
|
// Paging
|
||||||
|
ROMSlot active_rom_ = ROMSlot::ROMSlot0;
|
||||||
|
bool keyboard_is_active_ = false;
|
||||||
|
bool basic_is_active_ = false;
|
||||||
|
|
||||||
|
// Interrupt and keyboard state
|
||||||
|
uint8_t interrupt_status_ = Interrupt::PowerOnReset | Interrupt::TransmitDataEmpty | 0x80;
|
||||||
|
uint8_t interrupt_control_ = 0;
|
||||||
|
uint8_t key_states_[14];
|
||||||
|
Electron::KeyboardMapper keyboard_mapper_;
|
||||||
|
|
||||||
|
// Counters related to simultaneous subsystems
|
||||||
|
Cycles cycles_since_display_update_ = 0;
|
||||||
|
Cycles cycles_since_audio_update_ = 0;
|
||||||
|
int cycles_until_display_interrupt_ = 0;
|
||||||
|
Interrupt next_display_interrupt_ = Interrupt::RealTimeClock;
|
||||||
|
VideoOutput::Range video_access_range_ = {0, 0xffff};
|
||||||
|
|
||||||
|
// Tape
|
||||||
|
Tape tape_;
|
||||||
|
bool use_fast_tape_hack_ = false;
|
||||||
|
bool fast_load_is_in_data_ = false;
|
||||||
|
|
||||||
|
// Disk
|
||||||
|
std::unique_ptr<Plus3> plus3_;
|
||||||
|
bool is_holding_shift_ = false;
|
||||||
|
int shift_restart_counter_ = 0;
|
||||||
|
|
||||||
|
// Outputs
|
||||||
|
std::unique_ptr<VideoOutput> video_output_;
|
||||||
|
|
||||||
|
Concurrency::DeferringAsyncTaskQueue audio_queue_;
|
||||||
|
SoundGenerator sound_generator_;
|
||||||
|
Outputs::Speaker::LowpassSpeaker<SoundGenerator> speaker_;
|
||||||
|
|
||||||
|
bool speaker_is_enabled_ = false;
|
||||||
|
};
|
||||||
|
|
||||||
void Machine::tape_did_change_interrupt_status(Tape *tape) {
|
|
||||||
interrupt_status_ = (interrupt_status_ & ~(Interrupt::TransmitDataEmpty | Interrupt::ReceiveDataFull | Interrupt::HighToneDetect)) | tape_.get_interrupt_status();
|
|
||||||
evaluate_interrupts();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#pragma mark - Typer timing
|
using namespace Electron;
|
||||||
|
|
||||||
HalfCycles Electron::Machine::get_typer_delay() {
|
Machine *Machine::Electron() {
|
||||||
return get_is_resetting() ? Cycles(625*25*128) : Cycles(0); // wait one second if resetting
|
return new Electron::ConcreteMachine;
|
||||||
}
|
}
|
||||||
|
|
||||||
HalfCycles Electron::Machine::get_typer_frequency() {
|
Machine::~Machine() {}
|
||||||
return Cycles(625*128*2); // accept a new character every two frames
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -9,19 +9,10 @@
|
|||||||
#ifndef Electron_hpp
|
#ifndef Electron_hpp
|
||||||
#define Electron_hpp
|
#define Electron_hpp
|
||||||
|
|
||||||
#include "../../Processors/6502/6502.hpp"
|
#include "../../Configurable/Configurable.hpp"
|
||||||
#include "../../Storage/Tape/Tape.hpp"
|
|
||||||
#include "../../ClockReceiver/ClockReceiver.hpp"
|
|
||||||
|
|
||||||
#include "../ConfigurationTarget.hpp"
|
#include "../ConfigurationTarget.hpp"
|
||||||
#include "../CRTMachine.hpp"
|
#include "../CRTMachine.hpp"
|
||||||
#include "../Typer.hpp"
|
#include "../KeyboardMachine.hpp"
|
||||||
|
|
||||||
#include "Interrupts.hpp"
|
|
||||||
#include "Plus3.hpp"
|
|
||||||
#include "Speaker.hpp"
|
|
||||||
#include "Tape.hpp"
|
|
||||||
#include "Video.hpp"
|
|
||||||
|
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
@@ -38,27 +29,12 @@ enum ROMSlot: uint8_t {
|
|||||||
|
|
||||||
ROMSlot12, ROMSlot13, ROMSlot14, ROMSlot15,
|
ROMSlot12, ROMSlot13, ROMSlot14, ROMSlot15,
|
||||||
|
|
||||||
ROMSlotOS, ROMSlotDFS, ROMSlotADFS
|
ROMSlotOS, ROMSlotDFS,
|
||||||
|
ROMSlotADFS1, ROMSlotADFS2
|
||||||
};
|
};
|
||||||
|
|
||||||
enum Key: uint16_t {
|
/// @returns The options available for an Electron.
|
||||||
KeySpace = 0x0000 | 0x08, KeyCopy = 0x0000 | 0x02, KeyRight = 0x0000 | 0x01,
|
std::vector<std::unique_ptr<Configurable::Option>> get_options();
|
||||||
KeyDelete = 0x0010 | 0x08, KeyReturn = 0x0010 | 0x04, KeyDown = 0x0010 | 0x02, KeyLeft = 0x0010 | 0x01,
|
|
||||||
KeyColon = 0x0020 | 0x04, KeyUp = 0x0020 | 0x02, KeyMinus = 0x0020 | 0x01,
|
|
||||||
KeySlash = 0x0030 | 0x08, KeySemiColon = 0x0030 | 0x04, KeyP = 0x0030 | 0x02, Key0 = 0x0030 | 0x01,
|
|
||||||
KeyFullStop = 0x0040 | 0x08, KeyL = 0x0040 | 0x04, KeyO = 0x0040 | 0x02, Key9 = 0x0040 | 0x01,
|
|
||||||
KeyComma = 0x0050 | 0x08, KeyK = 0x0050 | 0x04, KeyI = 0x0050 | 0x02, Key8 = 0x0050 | 0x01,
|
|
||||||
KeyM = 0x0060 | 0x08, KeyJ = 0x0060 | 0x04, KeyU = 0x0060 | 0x02, Key7 = 0x0060 | 0x01,
|
|
||||||
KeyN = 0x0070 | 0x08, KeyH = 0x0070 | 0x04, KeyY = 0x0070 | 0x02, Key6 = 0x0070 | 0x01,
|
|
||||||
KeyB = 0x0080 | 0x08, KeyG = 0x0080 | 0x04, KeyT = 0x0080 | 0x02, Key5 = 0x0080 | 0x01,
|
|
||||||
KeyV = 0x0090 | 0x08, KeyF = 0x0090 | 0x04, KeyR = 0x0090 | 0x02, Key4 = 0x0090 | 0x01,
|
|
||||||
KeyC = 0x00a0 | 0x08, KeyD = 0x00a0 | 0x04, KeyE = 0x00a0 | 0x02, Key3 = 0x00a0 | 0x01,
|
|
||||||
KeyX = 0x00b0 | 0x08, KeyS = 0x00b0 | 0x04, KeyW = 0x00b0 | 0x02, Key2 = 0x00b0 | 0x01,
|
|
||||||
KeyZ = 0x00c0 | 0x08, KeyA = 0x00c0 | 0x04, KeyQ = 0x00c0 | 0x02, Key1 = 0x00c0 | 0x01,
|
|
||||||
KeyShift = 0x00d0 | 0x08, KeyControl = 0x00d0 | 0x04, KeyFunc = 0x00d0 | 0x02, KeyEscape = 0x00d0 | 0x01,
|
|
||||||
|
|
||||||
KeyBreak = 0xfffd,
|
|
||||||
};
|
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
@abstract Represents an Acorn Electron.
|
@abstract Represents an Acorn Electron.
|
||||||
@@ -67,88 +43,21 @@ enum Key: uint16_t {
|
|||||||
Acorn Electron.
|
Acorn Electron.
|
||||||
*/
|
*/
|
||||||
class Machine:
|
class Machine:
|
||||||
public CPU::MOS6502::Processor<Machine>,
|
|
||||||
public CRTMachine::Machine,
|
public CRTMachine::Machine,
|
||||||
public Tape::Delegate,
|
public ConfigurationTarget::Machine,
|
||||||
public Utility::TypeRecipient,
|
public KeyboardMachine::Machine,
|
||||||
public ConfigurationTarget::Machine {
|
public Configurable::Device {
|
||||||
|
|
||||||
public:
|
public:
|
||||||
Machine();
|
virtual ~Machine();
|
||||||
|
|
||||||
void set_rom(ROMSlot slot, std::vector<uint8_t> data, bool is_writeable);
|
/// Creates and returns an Electron.
|
||||||
|
static Machine *Electron();
|
||||||
|
|
||||||
void set_key_state(uint16_t key, bool isPressed);
|
/*!
|
||||||
void clear_all_keys();
|
Sets the contents of @c slot to @c data. If @c is_writeable is @c true then writing to the slot
|
||||||
|
is enabled — it acts as if it were sideways RAM. Otherwise the slot is modelled as containing ROM.
|
||||||
inline void set_use_fast_tape_hack(bool activate) { use_fast_tape_hack_ = activate; }
|
*/
|
||||||
|
virtual void set_rom(ROMSlot slot, const std::vector<uint8_t> &data, bool is_writeable) = 0;
|
||||||
// to satisfy ConfigurationTarget::Machine
|
|
||||||
void configure_as_target(const StaticAnalyser::Target &target);
|
|
||||||
|
|
||||||
// to satisfy CPU::MOS6502::Processor
|
|
||||||
Cycles perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value);
|
|
||||||
void flush();
|
|
||||||
|
|
||||||
// to satisfy CRTMachine::Machine
|
|
||||||
virtual void setup_output(float aspect_ratio);
|
|
||||||
virtual void close_output();
|
|
||||||
virtual std::shared_ptr<Outputs::CRT::CRT> get_crt();
|
|
||||||
virtual std::shared_ptr<Outputs::Speaker> get_speaker();
|
|
||||||
virtual void run_for(const Cycles cycles) { CPU::MOS6502::Processor<Machine>::run_for(cycles); }
|
|
||||||
|
|
||||||
// to satisfy Tape::Delegate
|
|
||||||
virtual void tape_did_change_interrupt_status(Tape *tape);
|
|
||||||
|
|
||||||
// for Utility::TypeRecipient
|
|
||||||
virtual HalfCycles get_typer_delay();
|
|
||||||
virtual HalfCycles get_typer_frequency();
|
|
||||||
virtual void set_typer_for_string(const char *string);
|
|
||||||
|
|
||||||
private:
|
|
||||||
inline void update_display();
|
|
||||||
inline void queue_next_display_interrupt();
|
|
||||||
inline void update_audio();
|
|
||||||
|
|
||||||
inline void signal_interrupt(Interrupt interrupt);
|
|
||||||
inline void clear_interrupt(Interrupt interrupt);
|
|
||||||
inline void evaluate_interrupts();
|
|
||||||
|
|
||||||
// Things that directly constitute the memory map.
|
|
||||||
uint8_t roms_[16][16384];
|
|
||||||
bool rom_write_masks_[16];
|
|
||||||
uint8_t os_[16384], ram_[32768];
|
|
||||||
std::vector<uint8_t> dfs_, adfs_;
|
|
||||||
|
|
||||||
// Paging
|
|
||||||
ROMSlot active_rom_;
|
|
||||||
bool keyboard_is_active_, basic_is_active_;
|
|
||||||
|
|
||||||
// Interrupt and keyboard state
|
|
||||||
uint8_t interrupt_status_, interrupt_control_;
|
|
||||||
uint8_t key_states_[14];
|
|
||||||
|
|
||||||
// Counters related to simultaneous subsystems
|
|
||||||
Cycles cycles_since_display_update_;
|
|
||||||
Cycles cycles_since_audio_update_;
|
|
||||||
int cycles_until_display_interrupt_;
|
|
||||||
Interrupt next_display_interrupt_;
|
|
||||||
VideoOutput::Range video_access_range_;
|
|
||||||
|
|
||||||
// Tape
|
|
||||||
Tape tape_;
|
|
||||||
bool use_fast_tape_hack_;
|
|
||||||
bool fast_load_is_in_data_;
|
|
||||||
|
|
||||||
// Disk
|
|
||||||
std::unique_ptr<Plus3> plus3_;
|
|
||||||
bool is_holding_shift_;
|
|
||||||
int shift_restart_counter_;
|
|
||||||
|
|
||||||
// Outputs
|
|
||||||
std::unique_ptr<VideoOutput> video_output_;
|
|
||||||
std::shared_ptr<Speaker> speaker_;
|
|
||||||
bool speaker_is_enabled_;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,21 +1,65 @@
|
|||||||
//
|
//
|
||||||
// CharacterMapper.cpp
|
// Keyboard.cpp
|
||||||
// Clock Signal
|
// Clock Signal
|
||||||
//
|
//
|
||||||
// Created by Thomas Harte on 03/08/2017.
|
// Created by Thomas Harte on 10/10/2017.
|
||||||
// Copyright © 2017 Thomas Harte. All rights reserved.
|
// Copyright © 2017 Thomas Harte. All rights reserved.
|
||||||
//
|
//
|
||||||
|
|
||||||
#include "CharacterMapper.hpp"
|
#include "Keyboard.hpp"
|
||||||
#include "Electron.hpp"
|
|
||||||
|
|
||||||
using namespace Electron;
|
using namespace Electron;
|
||||||
|
|
||||||
|
uint16_t KeyboardMapper::mapped_key_for_key(Inputs::Keyboard::Key key) {
|
||||||
|
#define BIND(source, dest) case Inputs::Keyboard::Key::source: return Electron::Key::dest
|
||||||
|
switch(key) {
|
||||||
|
default: return KeyCopy;
|
||||||
|
|
||||||
|
BIND(k0, Key0); BIND(k1, Key1); BIND(k2, Key2); BIND(k3, Key3); BIND(k4, Key4);
|
||||||
|
BIND(k5, Key5); BIND(k6, Key6); BIND(k7, Key7); BIND(k8, Key8); BIND(k9, Key9);
|
||||||
|
BIND(Q, KeyQ); BIND(W, KeyW); BIND(E, KeyE); BIND(R, KeyR); BIND(T, KeyT);
|
||||||
|
BIND(Y, KeyY); BIND(U, KeyU); BIND(I, KeyI); BIND(O, KeyO); BIND(P, KeyP);
|
||||||
|
BIND(A, KeyA); BIND(S, KeyS); BIND(D, KeyD); BIND(F, KeyF); BIND(G, KeyG);
|
||||||
|
BIND(H, KeyH); BIND(J, KeyJ); BIND(K, KeyK); BIND(L, KeyL);
|
||||||
|
BIND(Z, KeyZ); BIND(X, KeyX); BIND(C, KeyC); BIND(V, KeyV);
|
||||||
|
BIND(B, KeyB); BIND(N, KeyN); BIND(M, KeyM);
|
||||||
|
|
||||||
|
BIND(Comma, KeyComma);
|
||||||
|
BIND(FullStop, KeyFullStop);
|
||||||
|
BIND(ForwardSlash, KeySlash);
|
||||||
|
BIND(Semicolon, KeySemiColon);
|
||||||
|
BIND(Quote, KeyColon);
|
||||||
|
|
||||||
|
BIND(Escape, KeyEscape);
|
||||||
|
BIND(Equals, KeyBreak);
|
||||||
|
BIND(F12, KeyBreak);
|
||||||
|
|
||||||
|
BIND(Left, KeyLeft); BIND(Right, KeyRight); BIND(Up, KeyUp); BIND(Down, KeyDown);
|
||||||
|
|
||||||
|
BIND(Tab, KeyFunc); BIND(LeftOption, KeyFunc); BIND(RightOption, KeyFunc);
|
||||||
|
BIND(LeftMeta, KeyFunc); BIND(RightMeta, KeyFunc);
|
||||||
|
BIND(CapsLock, KeyControl); BIND(LeftControl, KeyControl); BIND(RightControl, KeyControl);
|
||||||
|
BIND(LeftShift, KeyShift); BIND(RightShift, KeyShift);
|
||||||
|
|
||||||
|
BIND(Hyphen, KeyMinus);
|
||||||
|
BIND(Delete, KeyDelete); BIND(BackSpace, KeyDelete);
|
||||||
|
BIND(Enter, KeyReturn); BIND(KeyPadEnter, KeyReturn);
|
||||||
|
|
||||||
|
BIND(KeyPad0, Key0); BIND(KeyPad1, Key1); BIND(KeyPad2, Key2); BIND(KeyPad3, Key3); BIND(KeyPad4, Key4);
|
||||||
|
BIND(KeyPad5, Key5); BIND(KeyPad6, Key6); BIND(KeyPad7, Key7); BIND(KeyPad8, Key8); BIND(KeyPad9, Key9);
|
||||||
|
|
||||||
|
BIND(KeyPadMinus, KeyMinus); BIND(KeyPadPlus, KeyColon);
|
||||||
|
|
||||||
|
BIND(Space, KeySpace);
|
||||||
|
}
|
||||||
|
#undef BIND
|
||||||
|
}
|
||||||
|
|
||||||
uint16_t *CharacterMapper::sequence_for_character(char character) {
|
uint16_t *CharacterMapper::sequence_for_character(char character) {
|
||||||
#define KEYS(...) {__VA_ARGS__, EndSequence}
|
#define KEYS(...) {__VA_ARGS__, KeyboardMachine::Machine::KeyEndSequence}
|
||||||
#define SHIFT(...) {KeyShift, __VA_ARGS__, EndSequence}
|
#define SHIFT(...) {KeyShift, __VA_ARGS__, KeyboardMachine::Machine::KeyEndSequence}
|
||||||
#define CTRL(...) {KeyControl, __VA_ARGS__, EndSequence}
|
#define CTRL(...) {KeyControl, __VA_ARGS__, KeyboardMachine::Machine::KeyEndSequence}
|
||||||
#define X {NotMapped}
|
#define X {KeyboardMachine::Machine::KeyNotMapped}
|
||||||
static KeySequence key_sequences[] = {
|
static KeySequence key_sequences[] = {
|
||||||
/* NUL */ X, /* SOH */ X,
|
/* NUL */ X, /* SOH */ X,
|
||||||
/* STX */ X, /* ETX */ X,
|
/* STX */ X, /* ETX */ X,
|
||||||
46
Machines/Electron/Keyboard.hpp
Normal file
46
Machines/Electron/Keyboard.hpp
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
//
|
||||||
|
// Keyboard.hpp
|
||||||
|
// Clock Signal
|
||||||
|
//
|
||||||
|
// Created by Thomas Harte on 10/10/2017.
|
||||||
|
// Copyright © 2017 Thomas Harte. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef Machines_Electron_Keyboard_hpp
|
||||||
|
#define Machines_Electron_Keyboard_hpp
|
||||||
|
|
||||||
|
#include "../KeyboardMachine.hpp"
|
||||||
|
#include "../Utility/Typer.hpp"
|
||||||
|
|
||||||
|
namespace Electron {
|
||||||
|
|
||||||
|
enum Key: uint16_t {
|
||||||
|
KeySpace = 0x0000 | 0x08, KeyCopy = 0x0000 | 0x02, KeyRight = 0x0000 | 0x01,
|
||||||
|
KeyDelete = 0x0010 | 0x08, KeyReturn = 0x0010 | 0x04, KeyDown = 0x0010 | 0x02, KeyLeft = 0x0010 | 0x01,
|
||||||
|
KeyColon = 0x0020 | 0x04, KeyUp = 0x0020 | 0x02, KeyMinus = 0x0020 | 0x01,
|
||||||
|
KeySlash = 0x0030 | 0x08, KeySemiColon = 0x0030 | 0x04, KeyP = 0x0030 | 0x02, Key0 = 0x0030 | 0x01,
|
||||||
|
KeyFullStop = 0x0040 | 0x08, KeyL = 0x0040 | 0x04, KeyO = 0x0040 | 0x02, Key9 = 0x0040 | 0x01,
|
||||||
|
KeyComma = 0x0050 | 0x08, KeyK = 0x0050 | 0x04, KeyI = 0x0050 | 0x02, Key8 = 0x0050 | 0x01,
|
||||||
|
KeyM = 0x0060 | 0x08, KeyJ = 0x0060 | 0x04, KeyU = 0x0060 | 0x02, Key7 = 0x0060 | 0x01,
|
||||||
|
KeyN = 0x0070 | 0x08, KeyH = 0x0070 | 0x04, KeyY = 0x0070 | 0x02, Key6 = 0x0070 | 0x01,
|
||||||
|
KeyB = 0x0080 | 0x08, KeyG = 0x0080 | 0x04, KeyT = 0x0080 | 0x02, Key5 = 0x0080 | 0x01,
|
||||||
|
KeyV = 0x0090 | 0x08, KeyF = 0x0090 | 0x04, KeyR = 0x0090 | 0x02, Key4 = 0x0090 | 0x01,
|
||||||
|
KeyC = 0x00a0 | 0x08, KeyD = 0x00a0 | 0x04, KeyE = 0x00a0 | 0x02, Key3 = 0x00a0 | 0x01,
|
||||||
|
KeyX = 0x00b0 | 0x08, KeyS = 0x00b0 | 0x04, KeyW = 0x00b0 | 0x02, Key2 = 0x00b0 | 0x01,
|
||||||
|
KeyZ = 0x00c0 | 0x08, KeyA = 0x00c0 | 0x04, KeyQ = 0x00c0 | 0x02, Key1 = 0x00c0 | 0x01,
|
||||||
|
KeyShift = 0x00d0 | 0x08, KeyControl = 0x00d0 | 0x04, KeyFunc = 0x00d0 | 0x02, KeyEscape = 0x00d0 | 0x01,
|
||||||
|
|
||||||
|
KeyBreak = 0xfffd,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct KeyboardMapper: public KeyboardMachine::Machine::KeyboardMapper {
|
||||||
|
uint16_t mapped_key_for_key(Inputs::Keyboard::Key key);
|
||||||
|
};
|
||||||
|
|
||||||
|
struct CharacterMapper: public ::Utility::CharacterMapper {
|
||||||
|
uint16_t *sequence_for_character(char character);
|
||||||
|
};
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif /* KeyboardMapper_hpp */
|
||||||
@@ -10,13 +10,13 @@
|
|||||||
|
|
||||||
using namespace Electron;
|
using namespace Electron;
|
||||||
|
|
||||||
Plus3::Plus3() : WD1770(P1770), last_control_(0) {
|
Plus3::Plus3() : WD1770(P1770) {
|
||||||
set_control_register(last_control_, 0xff);
|
set_control_register(last_control_, 0xff);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Plus3::set_disk(std::shared_ptr<Storage::Disk::Disk> disk, int drive) {
|
void Plus3::set_disk(std::shared_ptr<Storage::Disk::Disk> disk, int drive) {
|
||||||
if(!drives_[drive]) {
|
if(!drives_[drive]) {
|
||||||
drives_[drive].reset(new Storage::Disk::Drive);
|
drives_[drive].reset(new Storage::Disk::Drive(8000000, 300, 2));
|
||||||
if(drive == selected_drive_) set_drive(drives_[drive]);
|
if(drive == selected_drive_) set_drive(drives_[drive]);
|
||||||
}
|
}
|
||||||
drives_[drive]->set_disk(disk);
|
drives_[drive]->set_disk(disk);
|
||||||
@@ -42,9 +42,14 @@ void Plus3::set_control_register(uint8_t control, uint8_t changes) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if(changes & 0x04) {
|
if(changes & 0x04) {
|
||||||
invalidate_track();
|
|
||||||
if(drives_[0]) drives_[0]->set_head((control & 0x04) ? 1 : 0);
|
if(drives_[0]) drives_[0]->set_head((control & 0x04) ? 1 : 0);
|
||||||
if(drives_[1]) drives_[1]->set_head((control & 0x04) ? 1 : 0);
|
if(drives_[1]) drives_[1]->set_head((control & 0x04) ? 1 : 0);
|
||||||
}
|
}
|
||||||
if(changes & 0x08) set_is_double_density(!(control & 0x08));
|
if(changes & 0x08) set_is_double_density(!(control & 0x08));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Plus3::set_motor_on(bool on) {
|
||||||
|
// TODO: this status should transfer if the selected drive changes. But the same goes for
|
||||||
|
// writing state, so plenty of work to do in general here.
|
||||||
|
get_drive().set_motor_on(on);
|
||||||
|
}
|
||||||
|
|||||||
@@ -23,8 +23,10 @@ class Plus3 : public WD::WD1770 {
|
|||||||
private:
|
private:
|
||||||
void set_control_register(uint8_t control, uint8_t changes);
|
void set_control_register(uint8_t control, uint8_t changes);
|
||||||
std::shared_ptr<Storage::Disk::Drive> drives_[2];
|
std::shared_ptr<Storage::Disk::Drive> drives_[2];
|
||||||
int selected_drive_;
|
int selected_drive_ = 0;
|
||||||
uint8_t last_control_;
|
uint8_t last_control_ = 0;
|
||||||
|
|
||||||
|
void set_motor_on(bool on);
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
45
Machines/Electron/SoundGenerator.cpp
Normal file
45
Machines/Electron/SoundGenerator.cpp
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
//
|
||||||
|
// Speaker.cpp
|
||||||
|
// Clock Signal
|
||||||
|
//
|
||||||
|
// Created by Thomas Harte on 03/12/2016.
|
||||||
|
// Copyright © 2016 Thomas Harte. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
#include "SoundGenerator.hpp"
|
||||||
|
|
||||||
|
#include <cstring>
|
||||||
|
|
||||||
|
using namespace Electron;
|
||||||
|
|
||||||
|
SoundGenerator::SoundGenerator(Concurrency::DeferringAsyncTaskQueue &audio_queue) :
|
||||||
|
audio_queue_(audio_queue) {}
|
||||||
|
|
||||||
|
void SoundGenerator::get_samples(std::size_t number_of_samples, int16_t *target) {
|
||||||
|
if(is_enabled_) {
|
||||||
|
while(number_of_samples--) {
|
||||||
|
*target = static_cast<int16_t>((counter_ / (divider_+1)) * 8192);
|
||||||
|
target++;
|
||||||
|
counter_ = (counter_ + 1) % ((divider_+1) * 2);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
std::memset(target, 0, sizeof(int16_t) * number_of_samples);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SoundGenerator::skip_samples(std::size_t number_of_samples) {
|
||||||
|
counter_ = (counter_ + number_of_samples) % ((divider_+1) * 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SoundGenerator::set_divider(uint8_t divider) {
|
||||||
|
audio_queue_.defer([=]() {
|
||||||
|
divider_ = divider * 32 / clock_rate_divider;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void SoundGenerator::set_is_enabled(bool is_enabled) {
|
||||||
|
audio_queue_.defer([=]() {
|
||||||
|
is_enabled_ = is_enabled;
|
||||||
|
counter_ = 0;
|
||||||
|
});
|
||||||
|
}
|
||||||
39
Machines/Electron/SoundGenerator.hpp
Normal file
39
Machines/Electron/SoundGenerator.hpp
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
//
|
||||||
|
// SoundGenerator.hpp
|
||||||
|
// Clock Signal
|
||||||
|
//
|
||||||
|
// Created by Thomas Harte on 03/12/2016.
|
||||||
|
// Copyright © 2016 Thomas Harte. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef Electron_SoundGenerator_hpp
|
||||||
|
#define Electron_SoundGenerator_hpp
|
||||||
|
|
||||||
|
#include "../../Outputs/Speaker/Implementation/SampleSource.hpp"
|
||||||
|
#include "../../Concurrency/AsyncTaskQueue.hpp"
|
||||||
|
|
||||||
|
namespace Electron {
|
||||||
|
|
||||||
|
class SoundGenerator: public ::Outputs::Speaker::SampleSource {
|
||||||
|
public:
|
||||||
|
SoundGenerator(Concurrency::DeferringAsyncTaskQueue &audio_queue);
|
||||||
|
|
||||||
|
void set_divider(uint8_t divider);
|
||||||
|
|
||||||
|
void set_is_enabled(bool is_enabled);
|
||||||
|
|
||||||
|
void get_samples(std::size_t number_of_samples, int16_t *target);
|
||||||
|
void skip_samples(std::size_t number_of_samples);
|
||||||
|
|
||||||
|
static const unsigned int clock_rate_divider = 8;
|
||||||
|
|
||||||
|
private:
|
||||||
|
Concurrency::DeferringAsyncTaskQueue &audio_queue_;
|
||||||
|
unsigned int counter_ = 0;
|
||||||
|
unsigned int divider_ = 0;
|
||||||
|
bool is_enabled_ = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /* Speaker_hpp */
|
||||||
@@ -1,40 +0,0 @@
|
|||||||
//
|
|
||||||
// Speaker.cpp
|
|
||||||
// Clock Signal
|
|
||||||
//
|
|
||||||
// Created by Thomas Harte on 03/12/2016.
|
|
||||||
// Copyright © 2016 Thomas Harte. All rights reserved.
|
|
||||||
//
|
|
||||||
|
|
||||||
#include "Speaker.hpp"
|
|
||||||
|
|
||||||
using namespace Electron;
|
|
||||||
|
|
||||||
void Speaker::get_samples(unsigned int number_of_samples, int16_t *target) {
|
|
||||||
if(is_enabled_) {
|
|
||||||
while(number_of_samples--) {
|
|
||||||
*target = (int16_t)((counter_ / (divider_+1)) * 8192);
|
|
||||||
target++;
|
|
||||||
counter_ = (counter_ + 1) % ((divider_+1) * 2);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
memset(target, 0, sizeof(int16_t) * number_of_samples);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void Speaker::skip_samples(unsigned int number_of_samples) {
|
|
||||||
counter_ = (counter_ + number_of_samples) % ((divider_+1) * 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Speaker::set_divider(uint8_t divider) {
|
|
||||||
enqueue([=]() {
|
|
||||||
divider_ = divider * 32 / clock_rate_divider;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
void Speaker::set_is_enabled(bool is_enabled) {
|
|
||||||
enqueue([=]() {
|
|
||||||
is_enabled_ = is_enabled;
|
|
||||||
counter_ = 0;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
//
|
|
||||||
// Speaker.hpp
|
|
||||||
// Clock Signal
|
|
||||||
//
|
|
||||||
// Created by Thomas Harte on 03/12/2016.
|
|
||||||
// Copyright © 2016 Thomas Harte. All rights reserved.
|
|
||||||
//
|
|
||||||
|
|
||||||
#ifndef Electron_Speaker_hpp
|
|
||||||
#define Electron_Speaker_hpp
|
|
||||||
|
|
||||||
#include "../../Outputs/Speaker.hpp"
|
|
||||||
|
|
||||||
namespace Electron {
|
|
||||||
|
|
||||||
class Speaker: public ::Outputs::Filter<Speaker> {
|
|
||||||
public:
|
|
||||||
void set_divider(uint8_t divider);
|
|
||||||
|
|
||||||
void set_is_enabled(bool is_enabled);
|
|
||||||
|
|
||||||
void get_samples(unsigned int number_of_samples, int16_t *target);
|
|
||||||
void skip_samples(unsigned int number_of_samples);
|
|
||||||
|
|
||||||
static const unsigned int clock_rate_divider = 8;
|
|
||||||
|
|
||||||
private:
|
|
||||||
unsigned int counter_;
|
|
||||||
unsigned int divider_;
|
|
||||||
bool is_enabled_;
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif /* Speaker_hpp */
|
|
||||||
@@ -10,19 +10,12 @@
|
|||||||
|
|
||||||
using namespace Electron;
|
using namespace Electron;
|
||||||
|
|
||||||
Tape::Tape() :
|
Tape::Tape() : TapePlayer(2000000) {
|
||||||
TapePlayer(2000000),
|
|
||||||
is_running_(false),
|
|
||||||
data_register_(0),
|
|
||||||
delegate_(nullptr),
|
|
||||||
output_({.bits_remaining_until_empty = 0, .cycles_into_pulse = 0}),
|
|
||||||
last_posted_interrupt_status_(0),
|
|
||||||
interrupt_status_(0) {
|
|
||||||
shifter_.set_delegate(this);
|
shifter_.set_delegate(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Tape::push_tape_bit(uint16_t bit) {
|
void Tape::push_tape_bit(uint16_t bit) {
|
||||||
data_register_ = (uint16_t)((data_register_ >> 1) | (bit << 10));
|
data_register_ = static_cast<uint16_t>((data_register_ >> 1) | (bit << 10));
|
||||||
|
|
||||||
if(input_.minimum_bits_until_full) input_.minimum_bits_until_full--;
|
if(input_.minimum_bits_until_full) input_.minimum_bits_until_full--;
|
||||||
if(input_.minimum_bits_until_full == 8) interrupt_status_ &= ~Interrupt::ReceiveDataFull;
|
if(input_.minimum_bits_until_full == 8) interrupt_status_ &= ~Interrupt::ReceiveDataFull;
|
||||||
@@ -64,12 +57,12 @@ void Tape::set_counter(uint8_t value) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void Tape::set_data_register(uint8_t value) {
|
void Tape::set_data_register(uint8_t value) {
|
||||||
data_register_ = (uint16_t)((value << 2) | 1);
|
data_register_ = static_cast<uint16_t>((value << 2) | 1);
|
||||||
output_.bits_remaining_until_empty = 9;
|
output_.bits_remaining_until_empty = 9;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint8_t Tape::get_data_register() {
|
uint8_t Tape::get_data_register() {
|
||||||
return (uint8_t)(data_register_ >> 2);
|
return static_cast<uint8_t>(data_register_ >> 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Tape::process_input_pulse(const Storage::Tape::Tape::Pulse &pulse) {
|
void Tape::process_input_pulse(const Storage::Tape::Tape::Pulse &pulse) {
|
||||||
@@ -77,7 +70,7 @@ void Tape::process_input_pulse(const Storage::Tape::Tape::Pulse &pulse) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void Tape::acorn_shifter_output_bit(int value) {
|
void Tape::acorn_shifter_output_bit(int value) {
|
||||||
push_tape_bit((uint16_t)value);
|
push_tape_bit(static_cast<uint16_t>(value));
|
||||||
}
|
}
|
||||||
|
|
||||||
void Tape::run_for(const Cycles cycles) {
|
void Tape::run_for(const Cycles cycles) {
|
||||||
@@ -87,7 +80,7 @@ void Tape::run_for(const Cycles cycles) {
|
|||||||
TapePlayer::run_for(cycles);
|
TapePlayer::run_for(cycles);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
output_.cycles_into_pulse += (unsigned int)cycles.as_int();
|
output_.cycles_into_pulse += static_cast<unsigned int>(cycles.as_int());
|
||||||
while(output_.cycles_into_pulse > 1664) { // 1664 = the closest you can get to 1200 baud if you're looking for something
|
while(output_.cycles_into_pulse > 1664) { // 1664 = the closest you can get to 1200 baud if you're looking for something
|
||||||
output_.cycles_into_pulse -= 1664; // that divides the 125,000Hz clock that the sound divider runs off.
|
output_.cycles_into_pulse -= 1664; // that divides the 125,000Hz clock that the sound divider runs off.
|
||||||
push_tape_bit(1);
|
push_tape_bit(1);
|
||||||
|
|||||||
@@ -52,22 +52,23 @@ class Tape:
|
|||||||
inline void get_next_tape_pulse();
|
inline void get_next_tape_pulse();
|
||||||
|
|
||||||
struct {
|
struct {
|
||||||
int minimum_bits_until_full;
|
int minimum_bits_until_full = 0;
|
||||||
} input_;
|
} input_;
|
||||||
struct {
|
struct {
|
||||||
unsigned int cycles_into_pulse;
|
unsigned int cycles_into_pulse = 0;
|
||||||
unsigned int bits_remaining_until_empty;
|
unsigned int bits_remaining_until_empty = 0;
|
||||||
} output_;
|
} output_;
|
||||||
|
|
||||||
bool is_running_;
|
bool is_running_ = false;
|
||||||
bool is_enabled_;
|
bool is_enabled_ = false;
|
||||||
bool is_in_input_mode_;
|
bool is_in_input_mode_ = false;
|
||||||
|
|
||||||
inline void evaluate_interrupts();
|
inline void evaluate_interrupts();
|
||||||
uint16_t data_register_;
|
uint16_t data_register_ = 0;
|
||||||
|
|
||||||
uint8_t interrupt_status_, last_posted_interrupt_status_;
|
uint8_t interrupt_status_ = 0;
|
||||||
Delegate *delegate_;
|
uint8_t last_posted_interrupt_status_ = 0;
|
||||||
|
Delegate *delegate_ = nullptr;
|
||||||
|
|
||||||
::Storage::Tape::Acorn::Shifter shifter_;
|
::Storage::Tape::Acorn::Shifter shifter_;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -8,6 +8,8 @@
|
|||||||
|
|
||||||
#include "Video.hpp"
|
#include "Video.hpp"
|
||||||
|
|
||||||
|
#include <cstring>
|
||||||
|
|
||||||
using namespace Electron;
|
using namespace Electron;
|
||||||
|
|
||||||
#define graphics_line(v) ((((v) >> 7) - first_graphics_line + field_divider_line) % field_divider_line)
|
#define graphics_line(v) ((((v) >> 7) - first_graphics_line + field_divider_line) % field_divider_line)
|
||||||
@@ -32,17 +34,18 @@ namespace {
|
|||||||
static const int real_time_clock_interrupt_2 = 56704;
|
static const int real_time_clock_interrupt_2 = 56704;
|
||||||
static const int display_end_interrupt_1 = (first_graphics_line + display_end_interrupt_line)*cycles_per_line;
|
static const int display_end_interrupt_1 = (first_graphics_line + display_end_interrupt_line)*cycles_per_line;
|
||||||
static const int display_end_interrupt_2 = (first_graphics_line + field_divider_line + display_end_interrupt_line)*cycles_per_line;
|
static const int display_end_interrupt_2 = (first_graphics_line + field_divider_line + display_end_interrupt_line)*cycles_per_line;
|
||||||
|
|
||||||
|
struct FourBPPBookender: public Outputs::CRT::TextureBuilder::Bookender {
|
||||||
|
void add_bookends(uint8_t *const left_value, uint8_t *const right_value, uint8_t *left_bookend, uint8_t *right_bookend) {
|
||||||
|
*left_bookend = static_cast<uint8_t>(((*left_value) & 0x0f) | (((*left_value) & 0x0f) << 4));
|
||||||
|
*right_bookend = static_cast<uint8_t>(((*right_value) & 0xf0) | (((*right_value) & 0xf0) >> 4));
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
#pragma mark - Lifecycle
|
// MARK: - Lifecycle
|
||||||
|
|
||||||
VideoOutput::VideoOutput(uint8_t *memory) :
|
VideoOutput::VideoOutput(uint8_t *memory) : ram_(memory) {
|
||||||
ram_(memory),
|
|
||||||
current_pixel_line_(-1),
|
|
||||||
output_position_(0),
|
|
||||||
screen_mode_(6),
|
|
||||||
screen_map_pointer_(0),
|
|
||||||
cycles_into_draw_action_(0) {
|
|
||||||
memset(palette_, 0xf, sizeof(palette_));
|
memset(palette_, 0xf, sizeof(palette_));
|
||||||
setup_screen_map();
|
setup_screen_map();
|
||||||
setup_base_address();
|
setup_base_address();
|
||||||
@@ -55,17 +58,19 @@ VideoOutput::VideoOutput(uint8_t *memory) :
|
|||||||
"texValue >>= 4 - (int(icoordinate.x * 8) & 4);"
|
"texValue >>= 4 - (int(icoordinate.x * 8) & 4);"
|
||||||
"return vec3( uvec3(texValue) & uvec3(4u, 2u, 1u));"
|
"return vec3( uvec3(texValue) & uvec3(4u, 2u, 1u));"
|
||||||
"}");
|
"}");
|
||||||
|
std::unique_ptr<Outputs::CRT::TextureBuilder::Bookender> bookender(new FourBPPBookender);
|
||||||
|
crt_->set_bookender(std::move(bookender));
|
||||||
// TODO: as implied below, I've introduced a clock's latency into the graphics pipeline somehow. Investigate.
|
// TODO: as implied below, I've introduced a clock's latency into the graphics pipeline somehow. Investigate.
|
||||||
crt_->set_visible_area(crt_->get_rect_for_area(first_graphics_line - 3, 256, (first_graphics_cycle+1) * crt_cycles_multiplier, 80 * crt_cycles_multiplier, 4.0f / 3.0f));
|
crt_->set_visible_area(crt_->get_rect_for_area(first_graphics_line - 3, 256, (first_graphics_cycle+1) * crt_cycles_multiplier, 80 * crt_cycles_multiplier, 4.0f / 3.0f));
|
||||||
}
|
}
|
||||||
|
|
||||||
#pragma mark - CRT getter
|
// MARK: - CRT getter
|
||||||
|
|
||||||
std::shared_ptr<Outputs::CRT::CRT> VideoOutput::get_crt() {
|
Outputs::CRT::CRT *VideoOutput::get_crt() {
|
||||||
return crt_;
|
return crt_.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
#pragma mark - Display update methods
|
// MARK: - Display update methods
|
||||||
|
|
||||||
void VideoOutput::start_pixel_line() {
|
void VideoOutput::start_pixel_line() {
|
||||||
current_pixel_line_ = (current_pixel_line_+1)&255;
|
current_pixel_line_ = (current_pixel_line_+1)&255;
|
||||||
@@ -92,7 +97,7 @@ void VideoOutput::start_pixel_line() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void VideoOutput::end_pixel_line() {
|
void VideoOutput::end_pixel_line() {
|
||||||
if(current_output_target_) crt_->output_data((unsigned int)((current_output_target_ - initial_output_target_) * current_output_divider_), current_output_divider_);
|
if(current_output_target_) crt_->output_data(static_cast<unsigned int>((current_output_target_ - initial_output_target_) * current_output_divider_), current_output_divider_);
|
||||||
current_character_row_++;
|
current_character_row_++;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -110,9 +115,9 @@ void VideoOutput::output_pixels(unsigned int number_of_cycles) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if(!initial_output_target_ || divider != current_output_divider_) {
|
if(!initial_output_target_ || divider != current_output_divider_) {
|
||||||
if(current_output_target_) crt_->output_data((unsigned int)((current_output_target_ - initial_output_target_) * current_output_divider_), current_output_divider_);
|
if(current_output_target_) crt_->output_data(static_cast<unsigned int>((current_output_target_ - initial_output_target_) * current_output_divider_), current_output_divider_);
|
||||||
current_output_divider_ = divider;
|
current_output_divider_ = divider;
|
||||||
initial_output_target_ = current_output_target_ = crt_->allocate_write_area(640 / current_output_divider_);
|
initial_output_target_ = current_output_target_ = crt_->allocate_write_area(640 / current_output_divider_, 4);
|
||||||
}
|
}
|
||||||
|
|
||||||
#define get_pixel() \
|
#define get_pixel() \
|
||||||
@@ -127,7 +132,7 @@ void VideoOutput::output_pixels(unsigned int number_of_cycles) {
|
|||||||
if(initial_output_target_) {
|
if(initial_output_target_) {
|
||||||
while(number_of_cycles--) {
|
while(number_of_cycles--) {
|
||||||
get_pixel();
|
get_pixel();
|
||||||
*(uint32_t *)current_output_target_ = palette_tables_.eighty1bpp[last_pixel_byte_];
|
*reinterpret_cast<uint32_t *>(current_output_target_) = palette_tables_.eighty1bpp[last_pixel_byte_];
|
||||||
current_output_target_ += 4;
|
current_output_target_ += 4;
|
||||||
current_pixel_column_++;
|
current_pixel_column_++;
|
||||||
}
|
}
|
||||||
@@ -138,7 +143,7 @@ void VideoOutput::output_pixels(unsigned int number_of_cycles) {
|
|||||||
if(initial_output_target_) {
|
if(initial_output_target_) {
|
||||||
while(number_of_cycles--) {
|
while(number_of_cycles--) {
|
||||||
get_pixel();
|
get_pixel();
|
||||||
*(uint16_t *)current_output_target_ = palette_tables_.eighty2bpp[last_pixel_byte_];
|
*reinterpret_cast<uint16_t *>(current_output_target_) = palette_tables_.eighty2bpp[last_pixel_byte_];
|
||||||
current_output_target_ += 2;
|
current_output_target_ += 2;
|
||||||
current_pixel_column_++;
|
current_pixel_column_++;
|
||||||
}
|
}
|
||||||
@@ -160,7 +165,7 @@ void VideoOutput::output_pixels(unsigned int number_of_cycles) {
|
|||||||
if(initial_output_target_) {
|
if(initial_output_target_) {
|
||||||
if(current_pixel_column_&1) {
|
if(current_pixel_column_&1) {
|
||||||
last_pixel_byte_ <<= 4;
|
last_pixel_byte_ <<= 4;
|
||||||
*(uint16_t *)current_output_target_ = palette_tables_.forty1bpp[last_pixel_byte_];
|
*reinterpret_cast<uint16_t *>(current_output_target_) = palette_tables_.forty1bpp[last_pixel_byte_];
|
||||||
current_output_target_ += 2;
|
current_output_target_ += 2;
|
||||||
|
|
||||||
number_of_cycles--;
|
number_of_cycles--;
|
||||||
@@ -168,11 +173,11 @@ void VideoOutput::output_pixels(unsigned int number_of_cycles) {
|
|||||||
}
|
}
|
||||||
while(number_of_cycles > 1) {
|
while(number_of_cycles > 1) {
|
||||||
get_pixel();
|
get_pixel();
|
||||||
*(uint16_t *)current_output_target_ = palette_tables_.forty1bpp[last_pixel_byte_];
|
*reinterpret_cast<uint16_t *>(current_output_target_) = palette_tables_.forty1bpp[last_pixel_byte_];
|
||||||
current_output_target_ += 2;
|
current_output_target_ += 2;
|
||||||
|
|
||||||
last_pixel_byte_ <<= 4;
|
last_pixel_byte_ <<= 4;
|
||||||
*(uint16_t *)current_output_target_ = palette_tables_.forty1bpp[last_pixel_byte_];
|
*reinterpret_cast<uint16_t *>(current_output_target_) = palette_tables_.forty1bpp[last_pixel_byte_];
|
||||||
current_output_target_ += 2;
|
current_output_target_ += 2;
|
||||||
|
|
||||||
number_of_cycles -= 2;
|
number_of_cycles -= 2;
|
||||||
@@ -180,7 +185,7 @@ void VideoOutput::output_pixels(unsigned int number_of_cycles) {
|
|||||||
}
|
}
|
||||||
if(number_of_cycles) {
|
if(number_of_cycles) {
|
||||||
get_pixel();
|
get_pixel();
|
||||||
*(uint16_t *)current_output_target_ = palette_tables_.forty1bpp[last_pixel_byte_];
|
*reinterpret_cast<uint16_t *>(current_output_target_) = palette_tables_.forty1bpp[last_pixel_byte_];
|
||||||
current_output_target_ += 2;
|
current_output_target_ += 2;
|
||||||
current_pixel_column_++;
|
current_pixel_column_++;
|
||||||
}
|
}
|
||||||
@@ -229,15 +234,15 @@ void VideoOutput::run_for(const Cycles cycles) {
|
|||||||
while(number_of_cycles) {
|
while(number_of_cycles) {
|
||||||
int draw_action_length = screen_map_[screen_map_pointer_].length;
|
int draw_action_length = screen_map_[screen_map_pointer_].length;
|
||||||
int time_left_in_action = std::min(number_of_cycles, draw_action_length - cycles_into_draw_action_);
|
int time_left_in_action = std::min(number_of_cycles, draw_action_length - cycles_into_draw_action_);
|
||||||
if(screen_map_[screen_map_pointer_].type == DrawAction::Pixels) output_pixels((unsigned int)time_left_in_action);
|
if(screen_map_[screen_map_pointer_].type == DrawAction::Pixels) output_pixels(static_cast<unsigned int>(time_left_in_action));
|
||||||
|
|
||||||
number_of_cycles -= time_left_in_action;
|
number_of_cycles -= time_left_in_action;
|
||||||
cycles_into_draw_action_ += time_left_in_action;
|
cycles_into_draw_action_ += time_left_in_action;
|
||||||
if(cycles_into_draw_action_ == draw_action_length) {
|
if(cycles_into_draw_action_ == draw_action_length) {
|
||||||
switch(screen_map_[screen_map_pointer_].type) {
|
switch(screen_map_[screen_map_pointer_].type) {
|
||||||
case DrawAction::Sync: crt_->output_sync((unsigned int)(draw_action_length * crt_cycles_multiplier)); break;
|
case DrawAction::Sync: crt_->output_sync(static_cast<unsigned int>(draw_action_length * crt_cycles_multiplier)); break;
|
||||||
case DrawAction::ColourBurst: crt_->output_default_colour_burst((unsigned int)(draw_action_length * crt_cycles_multiplier)); break;
|
case DrawAction::ColourBurst: crt_->output_default_colour_burst(static_cast<unsigned int>(draw_action_length * crt_cycles_multiplier)); break;
|
||||||
case DrawAction::Blank: crt_->output_blank((unsigned int)(draw_action_length * crt_cycles_multiplier)); break;
|
case DrawAction::Blank: crt_->output_blank(static_cast<unsigned int>(draw_action_length * crt_cycles_multiplier)); break;
|
||||||
case DrawAction::Pixels: end_pixel_line(); break;
|
case DrawAction::Pixels: end_pixel_line(); break;
|
||||||
}
|
}
|
||||||
screen_map_pointer_ = (screen_map_pointer_ + 1) % screen_map_.size();
|
screen_map_pointer_ = (screen_map_pointer_ + 1) % screen_map_.size();
|
||||||
@@ -247,16 +252,16 @@ void VideoOutput::run_for(const Cycles cycles) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#pragma mark - Register hub
|
// MARK: - Register hub
|
||||||
|
|
||||||
void VideoOutput::set_register(int address, uint8_t value) {
|
void VideoOutput::set_register(int address, uint8_t value) {
|
||||||
switch(address & 0xf) {
|
switch(address & 0xf) {
|
||||||
case 0x02:
|
case 0x02:
|
||||||
start_screen_address_ = (start_screen_address_ & 0xfe00) | (uint16_t)((value & 0xe0) << 1);
|
start_screen_address_ = (start_screen_address_ & 0xfe00) | static_cast<uint16_t>((value & 0xe0) << 1);
|
||||||
if(!start_screen_address_) start_screen_address_ |= 0x8000;
|
if(!start_screen_address_) start_screen_address_ |= 0x8000;
|
||||||
break;
|
break;
|
||||||
case 0x03:
|
case 0x03:
|
||||||
start_screen_address_ = (start_screen_address_ & 0x01ff) | (uint16_t)((value & 0x3f) << 9);
|
start_screen_address_ = (start_screen_address_ & 0x01ff) | static_cast<uint16_t>((value & 0x3f) << 9);
|
||||||
if(!start_screen_address_) start_screen_address_ |= 0x8000;
|
if(!start_screen_address_) start_screen_address_ |= 0x8000;
|
||||||
break;
|
break;
|
||||||
case 0x07: {
|
case 0x07: {
|
||||||
@@ -298,17 +303,17 @@ void VideoOutput::set_register(int address, uint8_t value) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// regenerate all palette tables for now
|
// regenerate all palette tables for now
|
||||||
#define pack(a, b) (uint8_t)((a << 4) | (b))
|
#define pack(a, b) static_cast<uint8_t>((a << 4) | (b))
|
||||||
for(int byte = 0; byte < 256; byte++) {
|
for(int byte = 0; byte < 256; byte++) {
|
||||||
uint8_t *target = (uint8_t *)&palette_tables_.forty1bpp[byte];
|
uint8_t *target = reinterpret_cast<uint8_t *>(&palette_tables_.forty1bpp[byte]);
|
||||||
target[0] = pack(palette_[(byte&0x80) >> 4], palette_[(byte&0x40) >> 3]);
|
target[0] = pack(palette_[(byte&0x80) >> 4], palette_[(byte&0x40) >> 3]);
|
||||||
target[1] = pack(palette_[(byte&0x20) >> 2], palette_[(byte&0x10) >> 1]);
|
target[1] = pack(palette_[(byte&0x20) >> 2], palette_[(byte&0x10) >> 1]);
|
||||||
|
|
||||||
target = (uint8_t *)&palette_tables_.eighty2bpp[byte];
|
target = reinterpret_cast<uint8_t *>(&palette_tables_.eighty2bpp[byte]);
|
||||||
target[0] = pack(palette_[((byte&0x80) >> 4) | ((byte&0x08) >> 2)], palette_[((byte&0x40) >> 3) | ((byte&0x04) >> 1)]);
|
target[0] = pack(palette_[((byte&0x80) >> 4) | ((byte&0x08) >> 2)], palette_[((byte&0x40) >> 3) | ((byte&0x04) >> 1)]);
|
||||||
target[1] = pack(palette_[((byte&0x20) >> 2) | ((byte&0x02) >> 0)], palette_[((byte&0x10) >> 1) | ((byte&0x01) << 1)]);
|
target[1] = pack(palette_[((byte&0x20) >> 2) | ((byte&0x02) >> 0)], palette_[((byte&0x10) >> 1) | ((byte&0x01) << 1)]);
|
||||||
|
|
||||||
target = (uint8_t *)&palette_tables_.eighty1bpp[byte];
|
target = reinterpret_cast<uint8_t *>(&palette_tables_.eighty1bpp[byte]);
|
||||||
target[0] = pack(palette_[(byte&0x80) >> 4], palette_[(byte&0x40) >> 3]);
|
target[0] = pack(palette_[(byte&0x80) >> 4], palette_[(byte&0x40) >> 3]);
|
||||||
target[1] = pack(palette_[(byte&0x20) >> 2], palette_[(byte&0x10) >> 1]);
|
target[1] = pack(palette_[(byte&0x20) >> 2], palette_[(byte&0x10) >> 1]);
|
||||||
target[2] = pack(palette_[(byte&0x08) >> 0], palette_[(byte&0x04) << 1]);
|
target[2] = pack(palette_[(byte&0x08) >> 0], palette_[(byte&0x04) << 1]);
|
||||||
@@ -333,7 +338,7 @@ void VideoOutput::setup_base_address() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#pragma mark - Interrupts
|
// MARK: - Interrupts
|
||||||
|
|
||||||
VideoOutput::Interrupt VideoOutput::get_next_interrupt() {
|
VideoOutput::Interrupt VideoOutput::get_next_interrupt() {
|
||||||
VideoOutput::Interrupt interrupt;
|
VideoOutput::Interrupt interrupt;
|
||||||
@@ -367,24 +372,43 @@ VideoOutput::Interrupt VideoOutput::get_next_interrupt() {
|
|||||||
return interrupt;
|
return interrupt;
|
||||||
}
|
}
|
||||||
|
|
||||||
#pragma mark - RAM timing and access information
|
// MARK: - RAM timing and access information
|
||||||
|
|
||||||
unsigned int VideoOutput::get_cycles_until_next_ram_availability(int from_time) {
|
unsigned int VideoOutput::get_cycles_until_next_ram_availability(int from_time) {
|
||||||
unsigned int result = 0;
|
unsigned int result = 0;
|
||||||
int position = output_position_ + from_time;
|
int position = (output_position_ + from_time) % cycles_per_frame;
|
||||||
|
|
||||||
|
// Apply the standard cost of aligning to the available 1Mhz of RAM bandwidth.
|
||||||
result += 1 + (position&1);
|
result += 1 + (position&1);
|
||||||
|
|
||||||
|
// In Modes 0–3 there is also a complete block on any access while pixels are being fetched.
|
||||||
if(screen_mode_ < 4) {
|
if(screen_mode_ < 4) {
|
||||||
const int current_column = graphics_column(position + (position&1));
|
const int current_column = graphics_column(position + (position&1));
|
||||||
int current_line = graphics_line(position);
|
int current_line = graphics_line(position);
|
||||||
if(current_column < 80 && current_line < 256) {
|
if(current_column < 80 && current_line < 256) {
|
||||||
|
// Mode 3 is a further special case: in 'every ten line block', the final two aren't painted,
|
||||||
|
// so the CPU is allowed access. But the offset of the ten-line blocks depends on when the
|
||||||
|
// user switched into Mode 3, so that needs to be calculated relative to current output.
|
||||||
if(screen_mode_ == 3) {
|
if(screen_mode_ == 3) {
|
||||||
|
// Get the line the display was on.
|
||||||
int output_position_line = graphics_line(output_position_);
|
int output_position_line = graphics_line(output_position_);
|
||||||
int implied_row = current_character_row_ + (current_line - output_position_line) % 10;
|
|
||||||
if(implied_row < 8)
|
int implied_row;
|
||||||
result += (unsigned int)(80 - current_column);
|
if(current_line >= output_position_line) {
|
||||||
|
// Get the number of lines since then if still in the same frame.
|
||||||
|
int lines_since_output_position = current_line - output_position_line;
|
||||||
|
|
||||||
|
// Therefore get the character row at the proposed time, modulo 10.
|
||||||
|
implied_row = (current_character_row_ + lines_since_output_position) % 10;
|
||||||
|
} else {
|
||||||
|
// If the frame has rolled over, the implied row is just related to the current line.
|
||||||
|
implied_row = current_line % 10;
|
||||||
}
|
}
|
||||||
else result += (unsigned int)(80 - current_column);
|
|
||||||
|
// Mode 3 ends after 250 lines, not the usual 256.
|
||||||
|
if(implied_row < 8 && current_line < 250) result += static_cast<unsigned int>(80 - current_column);
|
||||||
|
}
|
||||||
|
else result += static_cast<unsigned int>(80 - current_column);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
@@ -402,7 +426,7 @@ VideoOutput::Range VideoOutput::get_memory_access_range() {
|
|||||||
return range;
|
return range;
|
||||||
}
|
}
|
||||||
|
|
||||||
#pragma mark - The screen map
|
// MARK: - The screen map
|
||||||
|
|
||||||
void VideoOutput::setup_screen_map() {
|
void VideoOutput::setup_screen_map() {
|
||||||
/*
|
/*
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ class VideoOutput {
|
|||||||
VideoOutput(uint8_t *memory);
|
VideoOutput(uint8_t *memory);
|
||||||
|
|
||||||
/// @returns the CRT to which output is being painted.
|
/// @returns the CRT to which output is being painted.
|
||||||
std::shared_ptr<Outputs::CRT::CRT> get_crt();
|
Outputs::CRT::CRT *get_crt();
|
||||||
|
|
||||||
/// Produces the next @c cycles of video output.
|
/// Produces the next @c cycles of video output.
|
||||||
void run_for(const Cycles cycles);
|
void run_for(const Cycles cycles);
|
||||||
@@ -80,12 +80,13 @@ class VideoOutput {
|
|||||||
inline void output_pixels(unsigned int number_of_cycles);
|
inline void output_pixels(unsigned int number_of_cycles);
|
||||||
inline void setup_base_address();
|
inline void setup_base_address();
|
||||||
|
|
||||||
int output_position_, unused_cycles_;
|
int output_position_ = 0;
|
||||||
|
int unused_cycles_ = 0;
|
||||||
|
|
||||||
uint8_t palette_[16];
|
uint8_t palette_[16];
|
||||||
uint8_t screen_mode_;
|
uint8_t screen_mode_ = 6;
|
||||||
uint16_t screen_mode_base_address_;
|
uint16_t screen_mode_base_address_ = 0;
|
||||||
uint16_t start_screen_address_;
|
uint16_t start_screen_address_ = 0;
|
||||||
|
|
||||||
uint8_t *ram_;
|
uint8_t *ram_;
|
||||||
struct {
|
struct {
|
||||||
@@ -97,16 +98,20 @@ class VideoOutput {
|
|||||||
} palette_tables_;
|
} palette_tables_;
|
||||||
|
|
||||||
// Display generation.
|
// Display generation.
|
||||||
uint16_t start_line_address_, current_screen_address_;
|
uint16_t start_line_address_ = 0;
|
||||||
int current_pixel_line_, current_pixel_column_, current_character_row_;
|
uint16_t current_screen_address_ = 0;
|
||||||
uint8_t last_pixel_byte_;
|
int current_pixel_line_ = -1;
|
||||||
bool is_blank_line_;
|
int current_pixel_column_ = 0;
|
||||||
|
int current_character_row_ = 0;
|
||||||
|
uint8_t last_pixel_byte_ = 0;
|
||||||
|
bool is_blank_line_ = false;
|
||||||
|
|
||||||
// CRT output
|
// CRT output
|
||||||
uint8_t *current_output_target_, *initial_output_target_;
|
uint8_t *current_output_target_ = nullptr;
|
||||||
unsigned int current_output_divider_;
|
uint8_t *initial_output_target_ = nullptr;
|
||||||
|
unsigned int current_output_divider_ = 1;
|
||||||
|
|
||||||
std::shared_ptr<Outputs::CRT::CRT> crt_;
|
std::unique_ptr<Outputs::CRT::CRT> crt_;
|
||||||
|
|
||||||
struct DrawAction {
|
struct DrawAction {
|
||||||
enum Type {
|
enum Type {
|
||||||
@@ -119,8 +124,8 @@ class VideoOutput {
|
|||||||
void setup_screen_map();
|
void setup_screen_map();
|
||||||
void emplace_blank_line();
|
void emplace_blank_line();
|
||||||
void emplace_pixel_line();
|
void emplace_pixel_line();
|
||||||
size_t screen_map_pointer_;
|
std::size_t screen_map_pointer_ = 0;
|
||||||
int cycles_into_draw_action_;
|
int cycles_into_draw_action_ = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
24
Machines/JoystickMachine.hpp
Normal file
24
Machines/JoystickMachine.hpp
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
//
|
||||||
|
// JoystickMachine.hpp
|
||||||
|
// Clock Signal
|
||||||
|
//
|
||||||
|
// Created by Thomas Harte on 14/10/2017.
|
||||||
|
// Copyright © 2017 Thomas Harte. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef JoystickMachine_hpp
|
||||||
|
#define JoystickMachine_hpp
|
||||||
|
|
||||||
|
#include "../Inputs/Joystick.hpp"
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace JoystickMachine {
|
||||||
|
|
||||||
|
class Machine {
|
||||||
|
public:
|
||||||
|
virtual std::vector<std::unique_ptr<Inputs::Joystick>> &get_joysticks() = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /* JoystickMachine_hpp */
|
||||||
32
Machines/KeyboardMachine.cpp
Normal file
32
Machines/KeyboardMachine.cpp
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
//
|
||||||
|
// KeyboardMachine.cpp
|
||||||
|
// Clock Signal
|
||||||
|
//
|
||||||
|
// Created by Thomas Harte on 10/10/2017.
|
||||||
|
// Copyright © 2017 Thomas Harte. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
#include "KeyboardMachine.hpp"
|
||||||
|
|
||||||
|
using namespace KeyboardMachine;
|
||||||
|
|
||||||
|
Machine::Machine() {
|
||||||
|
keyboard_.set_delegate(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Machine::keyboard_did_change_key(Inputs::Keyboard *keyboard, Inputs::Keyboard::Key key, bool is_pressed) {
|
||||||
|
uint16_t mapped_key = get_keyboard_mapper().mapped_key_for_key(key);
|
||||||
|
if(mapped_key != KeyNotMapped) set_key_state(mapped_key, is_pressed);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Machine::reset_all_keys(Inputs::Keyboard *keyboard) {
|
||||||
|
// TODO: unify naming.
|
||||||
|
clear_all_keys();
|
||||||
|
}
|
||||||
|
|
||||||
|
Inputs::Keyboard &Machine::get_keyboard() {
|
||||||
|
return keyboard_;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Machine::type_string(const std::string &) {
|
||||||
|
}
|
||||||
@@ -9,20 +9,69 @@
|
|||||||
#ifndef KeyboardMachine_h
|
#ifndef KeyboardMachine_h
|
||||||
#define KeyboardMachine_h
|
#define KeyboardMachine_h
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
#include "../Inputs/Keyboard.hpp"
|
||||||
|
|
||||||
namespace KeyboardMachine {
|
namespace KeyboardMachine {
|
||||||
|
|
||||||
class Machine {
|
class Machine: public Inputs::Keyboard::Delegate {
|
||||||
public:
|
public:
|
||||||
|
Machine();
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
Indicates that the key @c key has been either pressed or released, according to
|
Indicates that the key @c key has been either pressed or released, according to
|
||||||
the state of @c isPressed.
|
the state of @c isPressed.
|
||||||
*/
|
*/
|
||||||
virtual void set_key_state(uint16_t key, bool isPressed) = 0;
|
virtual void set_key_state(uint16_t key, bool is_pressed) = 0;
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
Instructs that all keys should now be treated as released.
|
Instructs that all keys should now be treated as released.
|
||||||
*/
|
*/
|
||||||
virtual void clear_all_keys() = 0;
|
virtual void clear_all_keys() = 0;
|
||||||
|
|
||||||
|
/*!
|
||||||
|
Causes the machine to attempt to type the supplied string.
|
||||||
|
|
||||||
|
This is best effort. Success or failure is permitted to be a function of machine and current state.
|
||||||
|
*/
|
||||||
|
virtual void type_string(const std::string &);
|
||||||
|
|
||||||
|
/*!
|
||||||
|
Provides a destination for keyboard input.
|
||||||
|
*/
|
||||||
|
virtual Inputs::Keyboard &get_keyboard();
|
||||||
|
|
||||||
|
/*!
|
||||||
|
A keyboard mapper attempts to provide a physical mapping between host keys and emulated keys.
|
||||||
|
See the character mapper for logical mapping.
|
||||||
|
*/
|
||||||
|
class KeyboardMapper {
|
||||||
|
public:
|
||||||
|
virtual uint16_t mapped_key_for_key(Inputs::Keyboard::Key key) = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Terminates a key sequence from the character mapper.
|
||||||
|
static const uint16_t KeyEndSequence = 0xffff;
|
||||||
|
|
||||||
|
/*!
|
||||||
|
Indicates that a key is not mapped (for the keyboard mapper) or that a
|
||||||
|
character cannot be typed (for the character mapper).
|
||||||
|
*/
|
||||||
|
static const uint16_t KeyNotMapped = 0xfffe;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
/*!
|
||||||
|
Allows individual machines to provide the mapping between host keys
|
||||||
|
as per Inputs::Keyboard and their native scheme.
|
||||||
|
*/
|
||||||
|
virtual KeyboardMapper &get_keyboard_mapper() = 0;
|
||||||
|
|
||||||
|
private:
|
||||||
|
void keyboard_did_change_key(Inputs::Keyboard *keyboard, Inputs::Keyboard::Key key, bool is_pressed) override;
|
||||||
|
void reset_all_keys(Inputs::Keyboard *keyboard) override;
|
||||||
|
Inputs::Keyboard keyboard_;
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
42
Machines/MSX/Cartridges/ASCII16kb.hpp
Normal file
42
Machines/MSX/Cartridges/ASCII16kb.hpp
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
//
|
||||||
|
// ASCII16kb.hpp
|
||||||
|
// Clock Signal
|
||||||
|
//
|
||||||
|
// Created by Thomas Harte on 04/01/2018.
|
||||||
|
// Copyright © 2018 Thomas Harte. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef ASCII16kb_hpp
|
||||||
|
#define ASCII16kb_hpp
|
||||||
|
|
||||||
|
#include "../ROMSlotHandler.hpp"
|
||||||
|
|
||||||
|
namespace MSX {
|
||||||
|
namespace Cartridge {
|
||||||
|
|
||||||
|
class ASCII16kbROMSlotHandler: public ROMSlotHandler {
|
||||||
|
public:
|
||||||
|
ASCII16kbROMSlotHandler(MSX::MemoryMap &map, int slot) :
|
||||||
|
map_(map), slot_(slot) {}
|
||||||
|
|
||||||
|
void write(uint16_t address, uint8_t value) {
|
||||||
|
switch(address >> 11) {
|
||||||
|
default: break;
|
||||||
|
case 0xc:
|
||||||
|
map_.map(slot_, value * 8192, 0x4000, 0x4000);
|
||||||
|
break;
|
||||||
|
case 0xe:
|
||||||
|
map_.map(slot_, value * 8192, 0x8000, 0x4000);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
MSX::MemoryMap &map_;
|
||||||
|
int slot_;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /* ASCII16kb_hpp */
|
||||||
48
Machines/MSX/Cartridges/ASCII8kb.hpp
Normal file
48
Machines/MSX/Cartridges/ASCII8kb.hpp
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
//
|
||||||
|
// ASCII8kb.hpp
|
||||||
|
// Clock Signal
|
||||||
|
//
|
||||||
|
// Created by Thomas Harte on 04/01/2018.
|
||||||
|
// Copyright © 2018 Thomas Harte. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef ASCII8kb_hpp
|
||||||
|
#define ASCII8kb_hpp
|
||||||
|
|
||||||
|
#include "../ROMSlotHandler.hpp"
|
||||||
|
|
||||||
|
namespace MSX {
|
||||||
|
namespace Cartridge {
|
||||||
|
|
||||||
|
class ASCII8kbROMSlotHandler: public ROMSlotHandler {
|
||||||
|
public:
|
||||||
|
ASCII8kbROMSlotHandler(MSX::MemoryMap &map, int slot) :
|
||||||
|
map_(map), slot_(slot) {}
|
||||||
|
|
||||||
|
void write(uint16_t address, uint8_t value) {
|
||||||
|
switch(address >> 11) {
|
||||||
|
default: break;
|
||||||
|
case 0xc:
|
||||||
|
map_.map(slot_, value * 8192, 0x4000, 0x2000);
|
||||||
|
break;
|
||||||
|
case 0xd:
|
||||||
|
map_.map(slot_, value * 8192, 0x6000, 0x2000);
|
||||||
|
break;
|
||||||
|
case 0xe:
|
||||||
|
map_.map(slot_, value * 8192, 0x8000, 0x2000);
|
||||||
|
break;
|
||||||
|
case 0xf:
|
||||||
|
map_.map(slot_, value * 8192, 0xa000, 0x2000);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
MSX::MemoryMap &map_;
|
||||||
|
int slot_;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /* ASCII8kb_hpp */
|
||||||
45
Machines/MSX/Cartridges/Konami.hpp
Normal file
45
Machines/MSX/Cartridges/Konami.hpp
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
//
|
||||||
|
// Konami.hpp
|
||||||
|
// Clock Signal
|
||||||
|
//
|
||||||
|
// Created by Thomas Harte on 04/01/2018.
|
||||||
|
// Copyright © 2018 Thomas Harte. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef Konami_hpp
|
||||||
|
#define Konami_hpp
|
||||||
|
|
||||||
|
#include "../ROMSlotHandler.hpp"
|
||||||
|
|
||||||
|
namespace MSX {
|
||||||
|
namespace Cartridge {
|
||||||
|
|
||||||
|
class KonamiROMSlotHandler: public ROMSlotHandler {
|
||||||
|
public:
|
||||||
|
KonamiROMSlotHandler(MSX::MemoryMap &map, int slot) :
|
||||||
|
map_(map), slot_(slot) {}
|
||||||
|
|
||||||
|
void write(uint16_t address, uint8_t value) {
|
||||||
|
switch(address >> 13) {
|
||||||
|
default: break;
|
||||||
|
case 3:
|
||||||
|
map_.map(slot_, value * 8192, 0x6000, 0x2000);
|
||||||
|
break;
|
||||||
|
case 4:
|
||||||
|
map_.map(slot_, value * 8192, 0x8000, 0x2000);
|
||||||
|
break;
|
||||||
|
case 5:
|
||||||
|
map_.map(slot_, value * 8192, 0xa000, 0x2000);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
MSX::MemoryMap &map_;
|
||||||
|
int slot_;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /* Konami_hpp */
|
||||||
67
Machines/MSX/Cartridges/KonamiWithSCC.hpp
Normal file
67
Machines/MSX/Cartridges/KonamiWithSCC.hpp
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
//
|
||||||
|
// KonamiWithSCC.hpp
|
||||||
|
// Clock Signal
|
||||||
|
//
|
||||||
|
// Created by Thomas Harte on 04/01/2018.
|
||||||
|
// Copyright © 2018 Thomas Harte. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef KonamiWithSCC_hpp
|
||||||
|
#define KonamiWithSCC_hpp
|
||||||
|
|
||||||
|
#include "../ROMSlotHandler.hpp"
|
||||||
|
#include "../../../Components/KonamiSCC/KonamiSCC.hpp"
|
||||||
|
|
||||||
|
namespace MSX {
|
||||||
|
namespace Cartridge {
|
||||||
|
|
||||||
|
class KonamiWithSCCROMSlotHandler: public ROMSlotHandler {
|
||||||
|
public:
|
||||||
|
KonamiWithSCCROMSlotHandler(MSX::MemoryMap &map, int slot, Konami::SCC &scc) :
|
||||||
|
map_(map), slot_(slot), scc_(scc) {}
|
||||||
|
|
||||||
|
void write(uint16_t address, uint8_t value) override {
|
||||||
|
switch(address >> 11) {
|
||||||
|
default: break;
|
||||||
|
case 0x0a:
|
||||||
|
map_.map(slot_, value * 8192, 0x4000, 0x2000);
|
||||||
|
break;
|
||||||
|
case 0x0e:
|
||||||
|
map_.map(slot_, value * 8192, 0x6000, 0x2000);
|
||||||
|
break;
|
||||||
|
case 0x12:
|
||||||
|
if((value&0x3f) == 0x3f) {
|
||||||
|
scc_is_visible_ = true;
|
||||||
|
map_.unmap(slot_, 0x8000, 0x2000);
|
||||||
|
} else {
|
||||||
|
scc_is_visible_ = false;
|
||||||
|
map_.map(slot_, value * 8192, 0x8000, 0x2000);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 0x13:
|
||||||
|
if(scc_is_visible_) scc_.write(address, value);
|
||||||
|
break;
|
||||||
|
case 0x16:
|
||||||
|
map_.map(slot_, value * 8192, 0xa000, 0x2000);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t read(uint16_t address) override {
|
||||||
|
if(scc_is_visible_ && address >= 0x9800 && address < 0xa000) {
|
||||||
|
return scc_.read(address);
|
||||||
|
}
|
||||||
|
return 0xff;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
MSX::MemoryMap &map_;
|
||||||
|
int slot_;
|
||||||
|
Konami::SCC &scc_;
|
||||||
|
bool scc_is_visible_ = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /* KonamiWithSCC_hpp */
|
||||||
71
Machines/MSX/DiskROM.cpp
Normal file
71
Machines/MSX/DiskROM.cpp
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
//
|
||||||
|
// DiskROM.cpp
|
||||||
|
// Clock Signal
|
||||||
|
//
|
||||||
|
// Created by Thomas Harte on 07/01/2018.
|
||||||
|
// Copyright © 2018 Thomas Harte. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
#include "DiskROM.hpp"
|
||||||
|
|
||||||
|
using namespace MSX;
|
||||||
|
|
||||||
|
DiskROM::DiskROM(const std::vector<uint8_t> &rom) :
|
||||||
|
WD1770(P1793),
|
||||||
|
rom_(rom) {
|
||||||
|
set_is_double_density(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
void DiskROM::write(uint16_t address, uint8_t value) {
|
||||||
|
switch(address) {
|
||||||
|
case 0x7ff8: case 0x7ff9: case 0x7ffa: case 0x7ffb:
|
||||||
|
set_register(address, value);
|
||||||
|
break;
|
||||||
|
case 0x7ffc:
|
||||||
|
selected_head_ = value & 1;
|
||||||
|
if(drives_[0]) drives_[0]->set_head(selected_head_);
|
||||||
|
if(drives_[1]) drives_[1]->set_head(selected_head_);
|
||||||
|
break;
|
||||||
|
case 0x7ffd: {
|
||||||
|
selected_drive_ = value & 1;
|
||||||
|
set_drive(drives_[selected_drive_]);
|
||||||
|
|
||||||
|
bool drive_motor = !!(value & 0x80);
|
||||||
|
if(drives_[0]) drives_[0]->set_motor_on(drive_motor);
|
||||||
|
if(drives_[1]) drives_[1]->set_motor_on(drive_motor);
|
||||||
|
} break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t DiskROM::read(uint16_t address) {
|
||||||
|
if(address >= 0x7ff8 && address < 0x7ffc) {
|
||||||
|
return get_register(address);
|
||||||
|
}
|
||||||
|
if(address == 0x7fff) {
|
||||||
|
return (get_data_request_line() ? 0x00 : 0x80) | (get_interrupt_request_line() ? 0x00 : 0x40);
|
||||||
|
}
|
||||||
|
return rom_[address & 0x3fff];
|
||||||
|
}
|
||||||
|
|
||||||
|
void DiskROM::run_for(HalfCycles half_cycles) {
|
||||||
|
// Input clock is going to be 7159090/2 Mhz, but the drive controller
|
||||||
|
// needs an 8Mhz clock, so scale up. 8000000/7159090 simplifies to
|
||||||
|
// 800000/715909.
|
||||||
|
controller_cycles_ += 800000 * half_cycles.as_int();
|
||||||
|
WD::WD1770::run_for(Cycles(static_cast<int>(controller_cycles_ / 715909)));
|
||||||
|
controller_cycles_ %= 715909;
|
||||||
|
}
|
||||||
|
|
||||||
|
void DiskROM::set_disk(std::shared_ptr<Storage::Disk::Disk> disk, int drive) {
|
||||||
|
if(!drives_[drive]) {
|
||||||
|
drives_[drive].reset(new Storage::Disk::Drive(8000000, 300, 2));
|
||||||
|
drives_[drive]->set_head(selected_head_);
|
||||||
|
if(drive == selected_drive_) set_drive(drives_[drive]);
|
||||||
|
}
|
||||||
|
drives_[drive]->set_disk(disk);
|
||||||
|
}
|
||||||
|
|
||||||
|
void DiskROM::set_head_load_request(bool head_load) {
|
||||||
|
// Magic!
|
||||||
|
set_head_loaded(head_load);
|
||||||
|
}
|
||||||
44
Machines/MSX/DiskROM.hpp
Normal file
44
Machines/MSX/DiskROM.hpp
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
//
|
||||||
|
// DiskROM.hpp
|
||||||
|
// Clock Signal
|
||||||
|
//
|
||||||
|
// Created by Thomas Harte on 07/01/2018.
|
||||||
|
// Copyright © 2018 Thomas Harte. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef DiskROM_hpp
|
||||||
|
#define DiskROM_hpp
|
||||||
|
|
||||||
|
#include "ROMSlotHandler.hpp"
|
||||||
|
|
||||||
|
#include "../../Components/1770/1770.hpp"
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace MSX {
|
||||||
|
|
||||||
|
class DiskROM: public ROMSlotHandler, public WD::WD1770 {
|
||||||
|
public:
|
||||||
|
DiskROM(const std::vector<uint8_t> &rom);
|
||||||
|
|
||||||
|
void write(uint16_t address, uint8_t value) override;
|
||||||
|
uint8_t read(uint16_t address) override;
|
||||||
|
void run_for(HalfCycles half_cycles) override;
|
||||||
|
|
||||||
|
void set_disk(std::shared_ptr<Storage::Disk::Disk> disk, int drive);
|
||||||
|
|
||||||
|
private:
|
||||||
|
const std::vector<uint8_t> &rom_;
|
||||||
|
|
||||||
|
long int controller_cycles_ = 0;
|
||||||
|
int selected_drive_ = 0;
|
||||||
|
int selected_head_ = 0;
|
||||||
|
std::shared_ptr<Storage::Disk::Drive> drives_[4];
|
||||||
|
|
||||||
|
void set_head_load_request(bool head_load) override;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /* DiskROM_hpp */
|
||||||
61
Machines/MSX/Keyboard.cpp
Normal file
61
Machines/MSX/Keyboard.cpp
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
//
|
||||||
|
// Keyboard.cpp
|
||||||
|
// Clock Signal
|
||||||
|
//
|
||||||
|
// Created by Thomas Harte on 29/11/2017.
|
||||||
|
// Copyright © 2017 Thomas Harte. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
#include "Keyboard.hpp"
|
||||||
|
|
||||||
|
uint16_t MSX::KeyboardMapper::mapped_key_for_key(Inputs::Keyboard::Key key) {
|
||||||
|
#define BIND(source, dest) case Inputs::Keyboard::Key::source: return MSX::Key::dest
|
||||||
|
switch(key) {
|
||||||
|
BIND(k0, Key0); BIND(k1, Key1); BIND(k2, Key2); BIND(k3, Key3); BIND(k4, Key4);
|
||||||
|
BIND(k5, Key5); BIND(k6, Key6); BIND(k7, Key7); BIND(k8, Key8); BIND(k9, Key9);
|
||||||
|
BIND(Q, KeyQ); BIND(W, KeyW); BIND(E, KeyE); BIND(R, KeyR); BIND(T, KeyT);
|
||||||
|
BIND(Y, KeyY); BIND(U, KeyU); BIND(I, KeyI); BIND(O, KeyO); BIND(P, KeyP);
|
||||||
|
BIND(A, KeyA); BIND(S, KeyS); BIND(D, KeyD); BIND(F, KeyF); BIND(G, KeyG);
|
||||||
|
BIND(H, KeyH); BIND(J, KeyJ); BIND(K, KeyK); BIND(L, KeyL);
|
||||||
|
BIND(Z, KeyZ); BIND(X, KeyX); BIND(C, KeyC); BIND(V, KeyV);
|
||||||
|
BIND(B, KeyB); BIND(N, KeyN); BIND(M, KeyM);
|
||||||
|
|
||||||
|
BIND(F1, KeyF1); BIND(F2, KeyF2); BIND(F3, KeyF3); BIND(F4, KeyF4); BIND(F5, KeyF5);
|
||||||
|
|
||||||
|
BIND(F12, KeyStop);
|
||||||
|
BIND(F10, KeyDelete); BIND(F9, KeyInsert); BIND(F8, KeyHome);
|
||||||
|
BIND(Delete, KeyDelete); BIND(Insert, KeyInsert); BIND(Home, KeyHome);
|
||||||
|
|
||||||
|
BIND(Escape, KeyEscape);
|
||||||
|
BIND(Tab, KeyTab); BIND(CapsLock, KeyCaps);
|
||||||
|
|
||||||
|
BIND(LeftControl, KeyControl); BIND(RightControl, KeyControl);
|
||||||
|
BIND(LeftShift, KeyShift); BIND(RightShift, KeyShift);
|
||||||
|
BIND(LeftMeta, KeyCode); BIND(RightMeta, KeyGraph);
|
||||||
|
BIND(LeftOption, KeyCode); BIND(RightOption, KeySelect);
|
||||||
|
|
||||||
|
BIND(Semicolon, KeySemicolon);
|
||||||
|
BIND(Quote, KeyQuote);
|
||||||
|
BIND(OpenSquareBracket, KeyLeftSquareBracket);
|
||||||
|
BIND(CloseSquareBracket, KeyRightSquareBracket);
|
||||||
|
BIND(Hyphen, KeyMinus);
|
||||||
|
BIND(Equals, KeyEquals);
|
||||||
|
BIND(Left, KeyLeft);
|
||||||
|
BIND(Right, KeyRight);
|
||||||
|
BIND(Up, KeyUp);
|
||||||
|
BIND(Down, KeyDown);
|
||||||
|
BIND(FullStop, KeyFullStop);
|
||||||
|
BIND(Comma, KeyComma);
|
||||||
|
BIND(ForwardSlash, KeyForwardSlash);
|
||||||
|
BIND(BackSlash, KeyBackSlash);
|
||||||
|
BIND(BackTick, KeyGrave);
|
||||||
|
|
||||||
|
BIND(Enter, KeyEnter);
|
||||||
|
BIND(Space, KeySpace);
|
||||||
|
BIND(BackSpace, KeyBackspace);
|
||||||
|
|
||||||
|
default: break;
|
||||||
|
}
|
||||||
|
#undef BIND
|
||||||
|
return KeyboardMachine::Machine::KeyNotMapped;
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user