mirror of
https://github.com/TomHarte/CLK.git
synced 2025-10-25 09:27:01 +00:00
Compare commits
965 Commits
2017-05-16
...
2017-08-27
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 | ||
|
|
27018ba64e | ||
|
|
e208f03636 | ||
|
|
cc9d23f23b | ||
|
|
1a831bcf9b | ||
|
|
82367a2246 | ||
|
|
f18206767f | ||
|
|
3947347d88 | ||
|
|
8a37a0ff2e | ||
|
|
468770b382 | ||
|
|
6cfc3daacb | ||
|
|
aefbafa18d | ||
|
|
d12c834f9c | ||
|
|
56de5cb2b3 | ||
|
|
709257a0c5 | ||
|
|
75a9d2bb33 | ||
|
|
7b92b235e1 | ||
|
|
c196f0018f | ||
|
|
73080d6c36 | ||
|
|
9541a2a5f0 | ||
|
|
944222eba4 | ||
|
|
9d77f33611 | ||
|
|
7d132f81f7 | ||
|
|
0972f19fc5 | ||
|
|
6553bf05b4 | ||
|
|
0816d3f5a9 | ||
|
|
55055c7847 | ||
|
|
113da93796 | ||
|
|
cddcd0fb79 | ||
|
|
a366298022 | ||
|
|
4df9307d25 | ||
|
|
d7bed958b3 | ||
|
|
9038ba622e | ||
|
|
7b8bb0297a | ||
|
|
0da02d3902 | ||
|
|
334872d374 | ||
|
|
2e5ad19fe1 | ||
|
|
a10389a22c | ||
|
|
cefec7a19f | ||
|
|
7264fbb3d2 | ||
|
|
0e083e9dc6 | ||
|
|
8a7b23dc9e | ||
|
|
b7065575f3 | ||
|
|
7ea703f150 | ||
|
|
ea64125124 | ||
|
|
1011143dbe | ||
|
|
9ace6e1f71 | ||
|
|
750f2cb883 | ||
|
|
5221837be8 | ||
|
|
1576b4500b | ||
|
|
e1e9a06712 | ||
|
|
6e36f8ffa4 | ||
|
|
b0a7208cc7 | ||
|
|
eec42aa7ae | ||
|
|
6d2e969e7d | ||
|
|
5f42022c1d | ||
|
|
11d0c37506 | ||
|
|
58bad1e2a3 | ||
|
|
27d1dc5c37 | ||
|
|
e7345c7a20 | ||
|
|
186048a88e | ||
|
|
7135259cc1 | ||
|
|
4909325e79 | ||
|
|
a4ee697ed1 | ||
|
|
0f15a2f97f | ||
|
|
89ace671a4 | ||
|
|
e7db2a2f6d | ||
|
|
8c33ac71ee | ||
|
|
69914faf02 | ||
|
|
daafebe7ac | ||
|
|
2d81acb82e | ||
|
|
82ca49c840 | ||
|
|
bfe297052d | ||
|
|
ffb1a14ace | ||
|
|
7e35e44934 | ||
|
|
0c8769e335 | ||
|
|
83c7d34df2 | ||
|
|
ad3c9842d7 | ||
|
|
44dace2eef | ||
|
|
a12671010a | ||
|
|
23c149368b | ||
|
|
09716d4716 | ||
|
|
4b7c504d22 | ||
|
|
1e4f9d4eda | ||
|
|
e4f04d0977 | ||
|
|
0f75525640 | ||
|
|
edb088526f | ||
|
|
80ebc63101 | ||
|
|
cf1403bc79 | ||
|
|
fcf63a7547 | ||
|
|
9c0b75faba | ||
|
|
1f2bfc9581 | ||
|
|
14ab03d1e0 | ||
|
|
3831fbaca2 | ||
|
|
1d8edf58dd | ||
|
|
4785e316ff | ||
|
|
44da9de5b0 | ||
|
|
4ecd093891 | ||
|
|
dd4bc87d52 | ||
|
|
570d25214e | ||
|
|
f0b7e58968 | ||
|
|
0411b51582 | ||
|
|
dea782cff9 | ||
|
|
388dd99762 | ||
|
|
026101a268 | ||
|
|
734099a956 | ||
|
|
6be5851484 | ||
|
|
994179f188 | ||
|
|
6a65c7a52a | ||
|
|
0d2d3ea17c | ||
|
|
5d374ebb18 | ||
|
|
62eadbb51a | ||
|
|
ad8c8166bc | ||
|
|
a5593bec79 | ||
|
|
a1e2646301 | ||
|
|
cf810d8357 | ||
|
|
f258d6fbb2 | ||
|
|
4961fda2a9 | ||
|
|
6a6e5ae79c | ||
|
|
02d792c003 | ||
|
|
be8e7a4144 | ||
|
|
b1dbd7833a | ||
|
|
84ab05c5ef | ||
|
|
78138261c2 | ||
|
|
a4c910f1de | ||
|
|
2eed24e859 | ||
|
|
7d1023ea98 | ||
|
|
b11d142cff | ||
|
|
021ff8674e | ||
|
|
1b86bc21ab | ||
|
|
e3d1f4fe1e | ||
|
|
a7452aebff | ||
|
|
484524d781 | ||
|
|
dbf9927caf | ||
|
|
3bdedfd749 | ||
|
|
005084af3d | ||
|
|
46278ff297 | ||
|
|
d73dcbb268 | ||
|
|
390ecec3d9 | ||
|
|
41a30c147d | ||
|
|
4709ae80cb | ||
|
|
7fbb455836 | ||
|
|
745afd217f | ||
|
|
4427e9a254 | ||
|
|
2b0dcf8573 | ||
|
|
47732ffb98 | ||
|
|
d07f3216ab | ||
|
|
56d65ba6f3 | ||
|
|
895a3cbf24 | ||
|
|
d951c8c1c2 | ||
|
|
a294963e98 | ||
|
|
68c73184b1 | ||
|
|
7f824d6494 | ||
|
|
3219212f03 | ||
|
|
d90e35e5bd | ||
|
|
73f8488150 | ||
|
|
3853966a1e | ||
|
|
d63893a437 | ||
|
|
90c74043f5 | ||
|
|
600445d90a | ||
|
|
e4b405fd3d | ||
|
|
3b7ecbdf0d | ||
|
|
01efb645cb | ||
|
|
b5ec1f42d5 | ||
|
|
c839556a27 | ||
|
|
e9972aa0dd | ||
|
|
1c9a744b01 | ||
|
|
e6d4bb29d8 | ||
|
|
6c5b562d97 | ||
|
|
a7103f9333 | ||
|
|
c12425e141 | ||
|
|
89f6de1383 | ||
|
|
77da582e88 | ||
|
|
34eaf75352 | ||
|
|
ffadb04761 | ||
|
|
29288b690e | ||
|
|
25fd3f7e50 | ||
|
|
4d60b8801c | ||
|
|
3e984e75b6 | ||
|
|
9e8645ca7a | ||
|
|
caf3ac0645 | ||
|
|
0f4343cd84 | ||
|
|
192f232d3f | ||
|
|
6e4d3b8a77 | ||
|
|
8eda24261c | ||
|
|
75c59fefab | ||
|
|
4b19cf60df | ||
|
|
2b476554e7 | ||
|
|
b3788fed41 | ||
|
|
b999c1d8aa | ||
|
|
a63aa80dc9 | ||
|
|
63f57c8c4f | ||
|
|
7e6a6365c9 | ||
|
|
3dbf26ac49 | ||
|
|
f075fea78c | ||
|
|
cc8380c286 | ||
|
|
c0f0c68f4f | ||
|
|
c2bb019381 | ||
|
|
26ce6cdab2 | ||
|
|
d9097facf1 | ||
|
|
b927500487 | ||
|
|
e71eabedf9 | ||
|
|
33ed27c3ad | ||
|
|
45cbab6751 | ||
|
|
a7b74d6164 | ||
|
|
575b1dba75 | ||
|
|
a3b16b6dfa | ||
|
|
c8f4de6f11 | ||
|
|
bbb17acf3a | ||
|
|
ad3a98387f | ||
|
|
985fbf59c2 | ||
|
|
2f2071be8a | ||
|
|
6d510e4e70 | ||
|
|
8e0736fbe6 | ||
|
|
681d1e2f8d | ||
|
|
42e70ef993 | ||
|
|
039811ce6a | ||
|
|
a54ccd1969 | ||
|
|
707821ca55 | ||
|
|
d3bf8fa53b | ||
|
|
f5e2dd410e | ||
|
|
3ca9c38777 | ||
|
|
2d2cefb0b0 | ||
|
|
2fd071e45d | ||
|
|
d7a5c3f49a | ||
|
|
819761f9fb | ||
|
|
e50adf1cc8 | ||
|
|
dcab10f53e | ||
|
|
633d8965e2 | ||
|
|
f602f9b6ec | ||
|
|
f7e66dea61 | ||
|
|
bda9441620 | ||
|
|
4d5d5041df | ||
|
|
587eb3a67c | ||
|
|
6ca07f1e28 | ||
|
|
8d39a20088 | ||
|
|
4b6370eb86 | ||
|
|
c6e340a8a2 | ||
|
|
31c7153301 | ||
|
|
7e04d00cc1 | ||
|
|
9d43784c65 | ||
|
|
eca9586a0f | ||
|
|
0267bc237f | ||
|
|
2e4577f741 | ||
|
|
f5b278d683 | ||
|
|
e6854ff8db | ||
|
|
3b292273c7 | ||
|
|
cb732e5d5f | ||
|
|
2d4e202be3 | ||
|
|
64da8e17d1 | ||
|
|
08ad35efd9 | ||
|
|
58b98267fc | ||
|
|
ace71280a0 | ||
|
|
a27946102a | ||
|
|
1d99c116e7 | ||
|
|
ee27e16fb1 | ||
|
|
6ac7132799 | ||
|
|
ca42abab70 | ||
|
|
933d69a256 | ||
|
|
3b1db14817 | ||
|
|
10a5581aea | ||
|
|
3ae699964f | ||
|
|
9d953421d8 | ||
|
|
763e3b65d1 | ||
|
|
42dd27c9b1 | ||
|
|
2b168f7383 | ||
|
|
0536f089e1 | ||
|
|
3df13cddd4 | ||
|
|
e3f677fa37 | ||
|
|
c2253c1e0f | ||
|
|
5c68b6cc21 | ||
|
|
ffaa627820 | ||
|
|
f742fd5d4a | ||
|
|
69b99fe127 | ||
|
|
5a396f6787 | ||
|
|
cb0dc7b434 | ||
|
|
e28829bd1b | ||
|
|
68ceeab610 | ||
|
|
68dca9d047 | ||
|
|
d88ca151f4 | ||
|
|
3c90218c3d | ||
|
|
afd409c883 | ||
|
|
26b6c03a2a | ||
|
|
9c04d851e4 | ||
|
|
1d6fe11906 | ||
|
|
c0f1313830 | ||
|
|
fb51fadf00 | ||
|
|
55fd9122d0 | ||
|
|
5b5720fac0 | ||
|
|
d25d7d7d40 | ||
|
|
ba4f2d8917 | ||
|
|
a2aec39633 | ||
|
|
0bf4fdc9af | ||
|
|
ed8c73eb14 | ||
|
|
3528a7f78b | ||
|
|
54bcc40192 | ||
|
|
4b5e9ffb83 | ||
|
|
a7f5f035a6 | ||
|
|
4abd62e62b | ||
|
|
1fb158b297 | ||
|
|
968d2bb8ba | ||
|
|
92a3dfe44a | ||
|
|
9ef232157b | ||
|
|
b9f4f7a530 | ||
|
|
761afad118 | ||
|
|
8848ebbd4f | ||
|
|
37950143fc | ||
|
|
25fd95044c | ||
|
|
1da24d10fd | ||
|
|
60e374dca3 | ||
|
|
7a65f91575 | ||
|
|
6f8b558724 | ||
|
|
1a88b62bf7 | ||
|
|
8361756dc4 | ||
|
|
273299028e | ||
|
|
847e49ccdf | ||
|
|
81a3899381 | ||
|
|
9257a3f6d7 | ||
|
|
728143247d | ||
|
|
6ec4e4e3d7 | ||
|
|
7922a12f02 | ||
|
|
37ccb9d3b6 | ||
|
|
3c254360ba | ||
|
|
d2a0beaa67 | ||
|
|
cda223ffc0 | ||
|
|
3ca51bedc6 | ||
|
|
36076b7ea5 | ||
|
|
e90d128a26 | ||
|
|
966b5e6372 | ||
|
|
279c369a1f | ||
|
|
d9c6b3bcf7 | ||
|
|
1c2f68f129 | ||
|
|
296c7cec05 | ||
|
|
75d67ee770 | ||
|
|
a1e9a54765 | ||
|
|
8d1dacd951 | ||
|
|
545683df6f | ||
|
|
cfbd62a5dc | ||
|
|
40339a12e1 | ||
|
|
90bf6565d0 | ||
|
|
9be9bd9106 | ||
|
|
c1527cc9e2 | ||
|
|
a1a3aab115 | ||
|
|
c77a83d86f | ||
|
|
a6e377aa57 | ||
|
|
df4732be2e | ||
|
|
9435c1e12a | ||
|
|
efdac2ce8c | ||
|
|
2912d7055b | ||
|
|
d056f2e246 | ||
|
|
55ecb0c022 | ||
|
|
13f7aa4063 | ||
|
|
915f587ef1 | ||
|
|
b7f88e8f61 | ||
|
|
677ed463f0 | ||
|
|
8a2bdb8d22 | ||
|
|
9bff787ee1 | ||
|
|
b3ae920746 | ||
|
|
b82bef95f3 | ||
|
|
e6578defcd | ||
|
|
ace8e30818 | ||
|
|
ec3aa06caf | ||
|
|
ba088e5545 | ||
|
|
8a0b0cb3d7 | ||
|
|
6369138bd1 | ||
|
|
c2a7dffa7d | ||
|
|
b0c2325adc | ||
|
|
2ff157cf7a | ||
|
|
83628b285b | ||
|
|
1ba3f262a2 | ||
|
|
97334e10af | ||
|
|
31b4f8fa31 | ||
|
|
1bbb4cb478 | ||
|
|
d46da6ac9d | ||
|
|
825b38850e | ||
|
|
8755824c64 | ||
|
|
4ea835e50b | ||
|
|
5fddbec132 | ||
|
|
b3d3fd70aa | ||
|
|
6633537fb8 | ||
|
|
4dec9716c4 | ||
|
|
313b36944f | ||
|
|
456fdda6c2 | ||
|
|
6437c43147 | ||
|
|
5928a24803 | ||
|
|
20a6bcc676 | ||
|
|
eaf313b0f6 | ||
|
|
d51b66c204 | ||
|
|
660f0e4c40 | ||
|
|
540a03f75c | ||
|
|
a6b239698c | ||
|
|
92d1dd9a4a | ||
|
|
45ec5f8eab | ||
|
|
1d01acce06 | ||
|
|
be750ee427 | ||
|
|
dddb30477b | ||
|
|
37459a3ebc | ||
|
|
449c33ee8b | ||
|
|
2b5d0877a8 | ||
|
|
6d5807ec4b | ||
|
|
64865b3f41 | ||
|
|
53f0e1896b | ||
|
|
aaa60dab12 | ||
|
|
9b72c445a7 | ||
|
|
3f609e17b3 | ||
|
|
ef03c84b21 | ||
|
|
163c0f1b44 | ||
|
|
807e1d36d5 | ||
|
|
5545cc8bab | ||
|
|
d6e60c8e3a | ||
|
|
2d8e7e9f8b | ||
|
|
5b4c5b0cbf | ||
|
|
2471ef805b | ||
|
|
2a7fc86b15 | ||
|
|
3c8bf52fb8 | ||
|
|
a026998682 | ||
|
|
06ea81fdb2 | ||
|
|
d69b1e2d11 | ||
|
|
a3e0024980 | ||
|
|
d9a2c32aca | ||
|
|
e152ed2e61 | ||
|
|
9e975a46a2 | ||
|
|
70af075012 | ||
|
|
44e5a03cf2 | ||
|
|
c8cee88e33 | ||
|
|
35296017b5 | ||
|
|
130d598ec9 | ||
|
|
0350925c1e | ||
|
|
94e3dd0d4f | ||
|
|
5d44422230 | ||
|
|
eafdd7dbd7 | ||
|
|
127b584e79 | ||
|
|
2179edc7fe | ||
|
|
9108495586 | ||
|
|
fa617eac6b | ||
|
|
b63971caf7 | ||
|
|
7327da6350 | ||
|
|
8f72fc4a44 | ||
|
|
238348c885 | ||
|
|
7b5f93510b | ||
|
|
b3861ff755 | ||
|
|
5a6b7219b9 | ||
|
|
c2bc34fd87 | ||
|
|
1d3ae31755 | ||
|
|
8ddd686049 | ||
|
|
e5188a60dc | ||
|
|
e71d13c090 | ||
|
|
ba83dfd454 | ||
|
|
2fb0aea990 | ||
|
|
51177e4e1f | ||
|
|
004d6da9fb | ||
|
|
561373e793 | ||
|
|
279f4760d7 | ||
|
|
f931cd582d | ||
|
|
ab51bc443b | ||
|
|
c8575fe6e0 | ||
|
|
4489f120f9 | ||
|
|
253f9603ed | ||
|
|
6ca712b498 | ||
|
|
b743566339 | ||
|
|
481487f084 | ||
|
|
fc8313430a | ||
|
|
648618d280 | ||
|
|
ae1a130843 | ||
|
|
33d16ae0cd | ||
|
|
f09fe30af5 | ||
|
|
33eadb5549 | ||
|
|
368bff1a82 | ||
|
|
d853841dd5 | ||
|
|
eacaafeb48 | ||
|
|
ac59dd8b1d | ||
|
|
353c854734 | ||
|
|
3e5c209039 | ||
|
|
f56d96267e | ||
|
|
ed28260aaf | ||
|
|
8ccec37a4b | ||
|
|
646622b99e | ||
|
|
a25c2fd6b5 | ||
|
|
ee1a9a4781 | ||
|
|
a0367d6669 | ||
|
|
87658e83c1 | ||
|
|
4509c3ce34 | ||
|
|
30e93979d2 | ||
|
|
d6b87053bf | ||
|
|
22389a5d2d | ||
|
|
37696532c2 | ||
|
|
54efcb7e2f | ||
|
|
bcb7c27cc4 | ||
|
|
e2575d6de4 | ||
|
|
23e989e170 | ||
|
|
28412150e6 | ||
|
|
6d941b0c1f | ||
|
|
2f90f35478 | ||
|
|
12f7e1b804 | ||
|
|
c7fa2ed11a | ||
|
|
bfbe12b94b | ||
|
|
7476c64a66 | ||
|
|
46fff8e8a2 | ||
|
|
cd646aab9e | ||
|
|
a3684545b5 | ||
|
|
2f42874fd3 | ||
|
|
84d0e9b4cd | ||
|
|
a53011f778 | ||
|
|
b842c5b8bb | ||
|
|
ab1374f801 | ||
|
|
a5359027f0 | ||
|
|
43951a36eb | ||
|
|
55df96491c | ||
|
|
0c037627fc | ||
|
|
344d267fd2 | ||
|
|
4211389ac7 | ||
|
|
c6d00ec7d1 | ||
|
|
212ae60622 | ||
|
|
a72a2e0a1a | ||
|
|
50375fb373 | ||
|
|
2d02c23574 | ||
|
|
cb105fdeb4 | ||
|
|
acfd4dde36 | ||
|
|
919fc48cc5 | ||
|
|
aec4fd066b | ||
|
|
73c7b18900 | ||
|
|
3dfe45d225 | ||
|
|
95a6b0f85c | ||
|
|
87ee8450fe | ||
|
|
f2a6bcf2a8 | ||
|
|
644ef13acd | ||
|
|
342574761f | ||
|
|
b7c978e078 | ||
|
|
f0398a6db8 | ||
|
|
f3b1ef99cc | ||
|
|
52d9ddf9e5 | ||
|
|
93f251dbcd | ||
|
|
a6810fc3ef | ||
|
|
15f6c51062 | ||
|
|
5e21c706f3 | ||
|
|
e1355d4b62 | ||
|
|
7eeac3b586 | ||
|
|
4bf13610ce | ||
|
|
0e0ce379b4 | ||
|
|
36e8a11505 | ||
|
|
45f442ea63 | ||
|
|
db743c90d8 | ||
|
|
10cc94f581 | ||
|
|
108da64562 | ||
|
|
f85b46286e | ||
|
|
184b371649 | ||
|
|
b0375bb037 | ||
|
|
48942848e7 | ||
|
|
27ac342928 | ||
|
|
25aba16ef8 | ||
|
|
a0d0f383c8 | ||
|
|
6752f165db | ||
|
|
e05076b258 | ||
|
|
fadbfdf801 | ||
|
|
cb277b8d1e | ||
|
|
234f14dbbe | ||
|
|
99ede3a9ef | ||
|
|
378233f53d | ||
|
|
f903408980 | ||
|
|
cc8f316941 | ||
|
|
b684254908 | ||
|
|
351d90ca55 | ||
|
|
23177df26a | ||
|
|
ba15371948 | ||
|
|
73dbaebbc1 | ||
|
|
8d60734737 | ||
|
|
002098d496 | ||
|
|
e3244eb68e | ||
|
|
85c6fb1430 | ||
|
|
54e4643396 | ||
|
|
85c5c4405a | ||
|
|
d668879ba6 | ||
|
|
cb140aa06e | ||
|
|
6a769d3953 | ||
|
|
3be8ffd826 | ||
|
|
bb910e14a4 | ||
|
|
69ebbe019a | ||
|
|
0d39672d32 | ||
|
|
0d1231980a | ||
|
|
82a015892b | ||
|
|
194b7f60c5 | ||
|
|
ebc7356db5 | ||
|
|
e1a2580b2a | ||
|
|
b6f51474ff | ||
|
|
0f18768091 | ||
|
|
efc7f9df37 | ||
|
|
50cd617bd9 | ||
|
|
838b818cd3 | ||
|
|
cf795562bf | ||
|
|
ac37424878 | ||
|
|
a336048c98 | ||
|
|
87496f9978 | ||
|
|
08a542a324 | ||
|
|
9b3d05e05f | ||
|
|
d8e3103a2b | ||
|
|
76a64d13a0 | ||
|
|
1e975859c2 | ||
|
|
4c5261bfa0 | ||
|
|
aed2827e7b | ||
|
|
e6e6e4e62b | ||
|
|
8b09b4180b | ||
|
|
626737b9fa | ||
|
|
22de481557 | ||
|
|
b9dbb6bcf8 | ||
|
|
a48616a138 | ||
|
|
8222aac9e3 | ||
|
|
77aa3c187e | ||
|
|
ee0283c985 | ||
|
|
302c2e94de | ||
|
|
06fe07932a | ||
|
|
6913c7a018 | ||
|
|
6b602c74b7 | ||
|
|
8116f85479 | ||
|
|
e40d553045 | ||
|
|
2c6414ce11 | ||
|
|
e5aea632ee | ||
|
|
e5b30cdfbb | ||
|
|
ba5f34f827 | ||
|
|
84d2feb2e6 | ||
|
|
d12e50eb02 | ||
|
|
c2bc9a8c62 | ||
|
|
d910a4fd38 | ||
|
|
db30f53ab0 | ||
|
|
50be3a24fe | ||
|
|
256ba4028b | ||
|
|
b07af2660d | ||
|
|
bc0d70b2f7 | ||
|
|
c6e48dfd56 | ||
|
|
c775db50ef | ||
|
|
ee4c8b5ad2 | ||
|
|
d8b76e31c3 | ||
|
|
7e10c7f9d8 | ||
|
|
c47128f433 | ||
|
|
8aab9acc10 | ||
|
|
350b36df25 | ||
|
|
dbd2944c13 | ||
|
|
4603fa6f24 | ||
|
|
60300851ea | ||
|
|
58312ea2b7 | ||
|
|
cb534d8b85 | ||
|
|
5626d35bc4 | ||
|
|
4677cebf40 | ||
|
|
7399f3d798 | ||
|
|
faeecf7665 | ||
|
|
63e0802f4e | ||
|
|
e3ee9604a5 | ||
|
|
8c66e1d99d | ||
|
|
b55579c348 | ||
|
|
ca9e8aecd6 | ||
|
|
cc4cb45e9d | ||
|
|
ebbf6e6133 | ||
|
|
cba07dec7e | ||
|
|
6f7037b2b1 | ||
|
|
ef4b2f963d | ||
|
|
97f3ff03b6 | ||
|
|
2fbc7a2869 | ||
|
|
4983718df7 | ||
|
|
23ca00fd9a | ||
|
|
3df6eba237 | ||
|
|
893f61b490 | ||
|
|
e940e02126 | ||
|
|
7e3a46c33e | ||
|
|
7f743c6fb0 | ||
|
|
73654d51dd | ||
|
|
096551ab3e | ||
|
|
c485c460f7 | ||
|
|
b0a7c58287 | ||
|
|
d2637123c4 | ||
|
|
02b7c3d1b0 | ||
|
|
8c1769f157 | ||
|
|
655809517c | ||
|
|
2190f60a89 | ||
|
|
18faebc93c | ||
|
|
0eebfdb4cc | ||
|
|
7811374b0f | ||
|
|
a2f01b4a46 | ||
|
|
f5c910beb7 | ||
|
|
4e014ca748 | ||
|
|
87095b0578 | ||
|
|
fba6ac2b4c | ||
|
|
1a811b1ab1 | ||
|
|
c26349624c | ||
|
|
b642d9f712 | ||
|
|
fd6623b5a5 | ||
|
|
0b2a3f18bc | ||
|
|
b304c3a4b9 | ||
|
|
3ceef2005b | ||
|
|
0f438f524b | ||
|
|
24c84ca6f5 | ||
|
|
7898f643ac | ||
|
|
7bd45d308a | ||
|
|
b3da16911f | ||
|
|
e52892f75b | ||
|
|
8c41a0f0ed | ||
|
|
3e9212aaff | ||
|
|
a2ec902773 | ||
|
|
1c0130fd02 | ||
|
|
3e3d6f97f4 | ||
|
|
9c3bda0111 | ||
|
|
d14902700a | ||
|
|
c95c32a9fe | ||
|
|
35e045d7a7 | ||
|
|
084e1f3d51 | ||
|
|
5b43cefb85 | ||
|
|
aab637c9e7 | ||
|
|
7d9b197383 | ||
|
|
c9dd267ec1 | ||
|
|
a5254989f8 | ||
|
|
494ce073b5 | ||
|
|
b99e4210ba | ||
|
|
d3b74cbc91 | ||
|
|
5ff73faf48 | ||
|
|
2f7f11e2e5 | ||
|
|
5119997122 | ||
|
|
b5c1773d59 | ||
|
|
dfb5057342 | ||
|
|
7bddd294c9 | ||
|
|
01f7394f7f | ||
|
|
5aa8b03349 | ||
|
|
b5ad910b81 | ||
|
|
da65bae86e | ||
|
|
a0189a6fe1 | ||
|
|
244b5ba3c2 | ||
|
|
960de7bd7b | ||
|
|
c6185baa99 | ||
|
|
4d4695032c | ||
|
|
9d29cefe75 | ||
|
|
35f535b9a3 | ||
|
|
6d22f6fcd5 | ||
|
|
8bfaa487ce | ||
|
|
0d067d2f01 | ||
|
|
d66755fd1e | ||
|
|
267b2add9a | ||
|
|
d290e3d99e | ||
|
|
a6a4c5a936 | ||
|
|
8a8f0cef20 | ||
|
|
91dc0d5f4a | ||
|
|
ed7b07c8b1 | ||
|
|
3f880fa769 | ||
|
|
d83dd17738 | ||
|
|
9ade0dcae3 | ||
|
|
a329d85697 | ||
|
|
c322410783 | ||
|
|
b67331e018 | ||
|
|
a47b339668 | ||
|
|
ad56a9215c | ||
|
|
c56a5344b9 | ||
|
|
1f62cbe21a | ||
|
|
47845f8c19 | ||
|
|
409c82ce73 | ||
|
|
dc3f5b6211 | ||
|
|
fb02b77e63 | ||
|
|
f974d54c7a | ||
|
|
68978c6e25 | ||
|
|
6e83b7d6df | ||
|
|
5a4d448cc1 | ||
|
|
743eac8c55 | ||
|
|
6b66c8f304 | ||
|
|
c976fbfcd5 | ||
|
|
ed3e38ac31 | ||
|
|
76f03900d2 | ||
|
|
035df316aa | ||
|
|
9759a04c7d | ||
|
|
c7cb47a1d8 | ||
|
|
0d2d04e17b | ||
|
|
98423c6e41 | ||
|
|
33c3fa21e3 | ||
|
|
2141d52794 | ||
|
|
16b8021401 | ||
|
|
151b09b5ca | ||
|
|
9bc2b48d9b | ||
|
|
ab8a98f1df | ||
|
|
efe354a7b1 | ||
|
|
d50d3fc837 | ||
|
|
83ee92af1a | ||
|
|
ea0ad9fd87 | ||
|
|
ff3c60c0e1 | ||
|
|
399703a471 | ||
|
|
82017c4aea | ||
|
|
bdf07c3dc9 | ||
|
|
598be24644 | ||
|
|
e4e71a1e5f | ||
|
|
fba5af280e | ||
|
|
c668ff9472 | ||
|
|
2cadc706e2 | ||
|
|
3c6f63abcc | ||
|
|
00cd7e7e9c | ||
|
|
055c860b43 | ||
|
|
454c8628c3 | ||
|
|
a23a6db4d6 | ||
|
|
6575091a78 | ||
|
|
9e25d014d2 | ||
|
|
41d5dd8679 | ||
|
|
c3ea6dc1f5 | ||
|
|
22afa509ca | ||
|
|
3fb3cc8269 | ||
|
|
e3e461d7cb | ||
|
|
c16fccb317 | ||
|
|
b9cffdf2bd | ||
|
|
f2aae72cc2 | ||
|
|
fe8db1873c | ||
|
|
c66c715ac9 | ||
|
|
5dcfd85642 | ||
|
|
c70dfe1b09 | ||
|
|
232c591655 | ||
|
|
790614b544 | ||
|
|
32c032cd97 | ||
|
|
e48ee16366 | ||
|
|
e92d936ce8 | ||
|
|
4e210c5396 | ||
|
|
3d3e60b1fc | ||
|
|
f3f0e2f1a9 | ||
|
|
08206eea56 | ||
|
|
78296246e8 | ||
|
|
85b5dd35b1 | ||
|
|
11cfaa3e3d | ||
|
|
103c863534 | ||
|
|
6688f83226 | ||
|
|
01a064dd63 | ||
|
|
7b234078ae | ||
|
|
add02a7897 | ||
|
|
19167df692 | ||
|
|
6766845e21 | ||
|
|
bc3b5f3e35 | ||
|
|
5fe23113ec | ||
|
|
c55e1c1d17 | ||
|
|
d910405648 | ||
|
|
62b432c046 | ||
|
|
eae1f78221 | ||
|
|
11d05fb3b8 | ||
|
|
58efca835f | ||
|
|
da6e520b91 | ||
|
|
a5099f69d8 | ||
|
|
9398b6c2c8 | ||
|
|
99f2060fc1 | ||
|
|
5d3ebcb35a | ||
|
|
509d011fbe | ||
|
|
17ffd604bf | ||
|
|
a3dafa9056 | ||
|
|
21d0602305 | ||
|
|
64d6ee1be5 | ||
|
|
1378ab7278 | ||
|
|
87a021ec2d | ||
|
|
189317b80c | ||
|
|
4f0775cc7c | ||
|
|
7190f927b7 | ||
|
|
d559d8b901 | ||
|
|
2562306802 | ||
|
|
50bb4f0142 | ||
|
|
df80c37adb | ||
|
|
7da51602d5 | ||
|
|
5152517887 | ||
|
|
eb8a2de5d6 | ||
|
|
f2a1a906ff | ||
|
|
0808e9b6fb | ||
|
|
b81a2cc273 |
209
ClockReceiver/ClockReceiver.hpp
Normal file
209
ClockReceiver/ClockReceiver.hpp
Normal file
@@ -0,0 +1,209 @@
|
||||
//
|
||||
// ClockReceiver.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 22/07/2017.
|
||||
// Copyright © 2017 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef ClockReceiver_hpp
|
||||
#define ClockReceiver_hpp
|
||||
|
||||
/*
|
||||
Informal pattern for all classes that run from a clock cycle:
|
||||
|
||||
Each will implement either or both of run_for(Cycles) and run_for(HalfCycles), as
|
||||
is appropriate.
|
||||
|
||||
Callers that are accumulating HalfCycles but want to talk to receivers that implement
|
||||
only run_for(Cycles) can use HalfCycle.flush_cycles if they have appropriate storage, or
|
||||
can wrap the receiver in HalfClockReceiver in order automatically to bind half-cycle
|
||||
storage to it.
|
||||
|
||||
Alignment rule:
|
||||
|
||||
run_for(Cycles) may be called only after an even number of half cycles. E.g. the following
|
||||
sequence will have undefined results:
|
||||
|
||||
run_for(HalfCycles(1))
|
||||
run_for(Cycles(1))
|
||||
|
||||
An easy way to ensure this as a caller is to pick only one of run_for(Cycles) and
|
||||
run_for(HalfCycles) to use.
|
||||
|
||||
Reasoning:
|
||||
|
||||
Users of this template may with to implement run_for(Cycles) and run_for(HalfCycles)
|
||||
where there is a need to implement at half-cycle precision but a faster execution
|
||||
path can be offered for full-cycle precision. Those users are permitted to assume
|
||||
phase in run_for(Cycles) and should do so to be compatible with callers that use
|
||||
only run_for(Cycles).
|
||||
|
||||
Corollary:
|
||||
|
||||
Starting from nothing, the first run_for(HalfCycles(1)) will do the **first** half
|
||||
of a full cycle. The second will do the second half. Etc.
|
||||
|
||||
*/
|
||||
|
||||
/*!
|
||||
Provides a class that wraps a plain int, providing most of the basic arithmetic and
|
||||
Boolean operators, but forcing callers and receivers to be explicit as to usage.
|
||||
*/
|
||||
template <class T> class WrappedInt {
|
||||
public:
|
||||
inline WrappedInt(int l) : length_(l) {}
|
||||
inline WrappedInt() : length_(0) {}
|
||||
|
||||
inline T &operator =(const T &rhs) {
|
||||
length_ = rhs.length_;
|
||||
return *this;
|
||||
}
|
||||
|
||||
inline T &operator +=(const T &rhs) {
|
||||
length_ += rhs.length_;
|
||||
return *static_cast<T *>(this);
|
||||
}
|
||||
|
||||
inline T &operator -=(const T &rhs) {
|
||||
length_ -= rhs.length_;
|
||||
return *static_cast<T *>(this);
|
||||
}
|
||||
|
||||
inline T &operator ++() {
|
||||
++ length_;
|
||||
return *static_cast<T *>(this);
|
||||
}
|
||||
|
||||
inline T &operator ++(int) {
|
||||
length_ ++;
|
||||
return *static_cast<T *>(this);
|
||||
}
|
||||
|
||||
inline T &operator --() {
|
||||
-- length_;
|
||||
return *static_cast<T *>(this);
|
||||
}
|
||||
|
||||
inline T &operator --(int) {
|
||||
length_ --;
|
||||
return *static_cast<T *>(this);
|
||||
}
|
||||
|
||||
inline T &operator %=(const T &rhs) {
|
||||
length_ %= rhs.length_;
|
||||
return *static_cast<T *>(this);
|
||||
}
|
||||
|
||||
inline T &operator &=(const T &rhs) {
|
||||
length_ &= rhs.length_;
|
||||
return *static_cast<T *>(this);
|
||||
}
|
||||
|
||||
inline T operator +(const T &rhs) const { return T(length_ + rhs.length_); }
|
||||
inline T operator -(const T &rhs) const { return T(length_ - rhs.length_); }
|
||||
|
||||
inline T operator %(const T &rhs) const { return T(length_ % rhs.length_); }
|
||||
inline T operator &(const T &rhs) const { return T(length_ & rhs.length_); }
|
||||
|
||||
inline T operator -() const { return T(- length_); }
|
||||
|
||||
inline bool operator <(const T &rhs) const { return length_ < rhs.length_; }
|
||||
inline bool operator >(const T &rhs) const { return length_ > rhs.length_; }
|
||||
inline bool operator <=(const T &rhs) const { return length_ <= rhs.length_; }
|
||||
inline bool operator >=(const T &rhs) const { return length_ >= rhs.length_; }
|
||||
inline bool operator ==(const T &rhs) const { return length_ == rhs.length_; }
|
||||
inline bool operator !=(const T &rhs) const { return length_ != rhs.length_; }
|
||||
|
||||
inline bool operator !() const { return !length_; }
|
||||
// bool operator () is not supported because it offers an implicit cast to int, which is prone silently to permit misuse
|
||||
|
||||
inline int as_int() const { return length_; }
|
||||
|
||||
/*!
|
||||
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.
|
||||
*/
|
||||
inline T divide(const T &divisor) {
|
||||
T result(length_ / divisor.length_);
|
||||
length_ %= divisor.length_;
|
||||
return result;
|
||||
}
|
||||
|
||||
/*!
|
||||
Flushes the value in @c this. The current value is returned, and the internal value
|
||||
is reset to zero.
|
||||
*/
|
||||
inline T flush() {
|
||||
T result(length_);
|
||||
length_ = 0;
|
||||
return result;
|
||||
}
|
||||
|
||||
// operator int() is deliberately not provided, to avoid accidental subtitution of
|
||||
// classes that use this template.
|
||||
|
||||
protected:
|
||||
int length_;
|
||||
};
|
||||
|
||||
/// Describes an integer number of whole cycles — pairs of clock signal transitions.
|
||||
class Cycles: public WrappedInt<Cycles> {
|
||||
public:
|
||||
inline Cycles(int l) : WrappedInt<Cycles>(l) {}
|
||||
inline Cycles() : WrappedInt<Cycles>() {}
|
||||
inline Cycles(const Cycles &cycles) : WrappedInt<Cycles>(cycles.length_) {}
|
||||
};
|
||||
|
||||
/// Describes an integer number of half cycles — single clock signal transitions.
|
||||
class HalfCycles: public WrappedInt<HalfCycles> {
|
||||
public:
|
||||
inline HalfCycles(int l) : WrappedInt<HalfCycles>(l) {}
|
||||
inline HalfCycles() : WrappedInt<HalfCycles>() {}
|
||||
|
||||
inline HalfCycles(const Cycles cycles) : WrappedInt<HalfCycles>(cycles.as_int() << 1) {}
|
||||
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.
|
||||
inline Cycles cycles() {
|
||||
return Cycles(length_ >> 1);
|
||||
}
|
||||
|
||||
///Flushes the whole cycles in @c this, subtracting that many from the total stored here.
|
||||
inline Cycles flush_cycles() {
|
||||
Cycles result(length_ >> 1);
|
||||
length_ &= 1;
|
||||
return result;
|
||||
}
|
||||
|
||||
/*!
|
||||
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.
|
||||
*/
|
||||
inline Cycles divide_cycles(const Cycles &divisor) {
|
||||
HalfCycles half_divisor = HalfCycles(divisor);
|
||||
Cycles result(length_ / half_divisor.length_);
|
||||
length_ %= half_divisor.length_;
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
/*!
|
||||
If a component implements only run_for(Cycles), an owner can wrap it in HalfClockReceiver
|
||||
automatically to gain run_for(HalfCycles).
|
||||
*/
|
||||
template <class T> class HalfClockReceiver: public T {
|
||||
public:
|
||||
using T::T;
|
||||
|
||||
using T::run_for;
|
||||
inline void run_for(const HalfCycles half_cycles) {
|
||||
half_cycles_ += half_cycles;
|
||||
T::run_for(half_cycles_.flush_cycles());
|
||||
}
|
||||
|
||||
private:
|
||||
HalfCycles half_cycles_;
|
||||
};
|
||||
|
||||
#endif /* ClockReceiver_hpp */
|
||||
26
ClockReceiver/ForceInline.hpp
Normal file
26
ClockReceiver/ForceInline.hpp
Normal file
@@ -0,0 +1,26 @@
|
||||
//
|
||||
// ForceInline.h
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 01/08/2017.
|
||||
// Copyright © 2017 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef ForceInline_hpp
|
||||
#define ForceInline_hpp
|
||||
|
||||
#ifdef DEBUG
|
||||
|
||||
#define forceinline
|
||||
|
||||
#else
|
||||
|
||||
#ifdef __GNUC__
|
||||
#define forceinline __attribute__((always_inline)) inline
|
||||
#elif _MSC_VER
|
||||
#define forceinline __forceinline
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
||||
#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 */
|
||||
@@ -25,29 +25,16 @@ WD1770::Status::Status() :
|
||||
busy(false) {}
|
||||
|
||||
WD1770::WD1770(Personality p) :
|
||||
Storage::Disk::Controller(8000000, 16, 300),
|
||||
crc_generator_(0x1021, 0xffff),
|
||||
interesting_event_mask_(Event::Command),
|
||||
Storage::Disk::MFMController(8000000, 16, 300),
|
||||
interesting_event_mask_((int)Event1770::Command),
|
||||
resume_point_(0),
|
||||
delay_time_(0),
|
||||
index_hole_count_target_(-1),
|
||||
is_awaiting_marker_value_(false),
|
||||
data_mode_(DataMode::Scanning),
|
||||
delegate_(nullptr),
|
||||
personality_(p),
|
||||
head_is_loaded_(false) {
|
||||
set_is_double_density(false);
|
||||
posit_event(Event::Command);
|
||||
}
|
||||
|
||||
void WD1770::set_is_double_density(bool is_double_density) {
|
||||
is_double_density_ = is_double_density;
|
||||
Storage::Time bit_length;
|
||||
bit_length.length = 1;
|
||||
bit_length.clock_rate = is_double_density ? 500000 : 250000;
|
||||
set_expected_bit_length(bit_length);
|
||||
|
||||
if(!is_double_density) is_awaiting_marker_value_ = false;
|
||||
posit_event((int)Event1770::Command);
|
||||
}
|
||||
|
||||
void WD1770::set_register(int address, uint8_t value) {
|
||||
@@ -60,7 +47,7 @@ void WD1770::set_register(int address, uint8_t value) {
|
||||
});
|
||||
} else {
|
||||
command_ = value;
|
||||
posit_event(Event::Command);
|
||||
posit_event((int)Event1770::Command);
|
||||
}
|
||||
}
|
||||
break;
|
||||
@@ -124,154 +111,31 @@ uint8_t WD1770::get_register(int address) {
|
||||
}
|
||||
}
|
||||
|
||||
void WD1770::run_for_cycles(unsigned int number_of_cycles) {
|
||||
Storage::Disk::Controller::run_for_cycles((int)number_of_cycles);
|
||||
void WD1770::run_for(const Cycles cycles) {
|
||||
Storage::Disk::Controller::run_for(cycles);
|
||||
|
||||
if(delay_time_) {
|
||||
unsigned int number_of_cycles = (unsigned int)cycles.as_int();
|
||||
if(delay_time_ <= number_of_cycles) {
|
||||
delay_time_ = 0;
|
||||
posit_event(Event::Timer);
|
||||
posit_event((int)Event1770::Timer);
|
||||
} else {
|
||||
delay_time_ -= number_of_cycles;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void WD1770::process_input_bit(int value, unsigned int cycles_since_index_hole) {
|
||||
if(data_mode_ == DataMode::Writing) return;
|
||||
|
||||
shift_register_ = (shift_register_ << 1) | value;
|
||||
bits_since_token_++;
|
||||
|
||||
if(data_mode_ == DataMode::Scanning) {
|
||||
Token::Type token_type = Token::Byte;
|
||||
if(!is_double_density_) {
|
||||
switch(shift_register_ & 0xffff) {
|
||||
case Storage::Encodings::MFM::FMIndexAddressMark:
|
||||
token_type = Token::Index;
|
||||
crc_generator_.reset();
|
||||
crc_generator_.add(latest_token_.byte_value = Storage::Encodings::MFM::IndexAddressByte);
|
||||
break;
|
||||
case Storage::Encodings::MFM::FMIDAddressMark:
|
||||
token_type = Token::ID;
|
||||
crc_generator_.reset();
|
||||
crc_generator_.add(latest_token_.byte_value = Storage::Encodings::MFM::IDAddressByte);
|
||||
break;
|
||||
case Storage::Encodings::MFM::FMDataAddressMark:
|
||||
token_type = Token::Data;
|
||||
crc_generator_.reset();
|
||||
crc_generator_.add(latest_token_.byte_value = Storage::Encodings::MFM::DataAddressByte);
|
||||
break;
|
||||
case Storage::Encodings::MFM::FMDeletedDataAddressMark:
|
||||
token_type = Token::DeletedData;
|
||||
crc_generator_.reset();
|
||||
crc_generator_.add(latest_token_.byte_value = Storage::Encodings::MFM::DeletedDataAddressByte);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
switch(shift_register_ & 0xffff) {
|
||||
case Storage::Encodings::MFM::MFMIndexSync:
|
||||
bits_since_token_ = 0;
|
||||
is_awaiting_marker_value_ = true;
|
||||
|
||||
token_type = Token::Sync;
|
||||
latest_token_.byte_value = Storage::Encodings::MFM::MFMIndexSyncByteValue;
|
||||
break;
|
||||
case Storage::Encodings::MFM::MFMSync:
|
||||
bits_since_token_ = 0;
|
||||
is_awaiting_marker_value_ = true;
|
||||
crc_generator_.set_value(Storage::Encodings::MFM::MFMPostSyncCRCValue);
|
||||
|
||||
token_type = Token::Sync;
|
||||
latest_token_.byte_value = Storage::Encodings::MFM::MFMSyncByteValue;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(token_type != Token::Byte) {
|
||||
latest_token_.type = token_type;
|
||||
bits_since_token_ = 0;
|
||||
posit_event(Event::Token);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if(bits_since_token_ == 16) {
|
||||
latest_token_.type = Token::Byte;
|
||||
latest_token_.byte_value = (uint8_t)(
|
||||
((shift_register_ & 0x0001) >> 0) |
|
||||
((shift_register_ & 0x0004) >> 1) |
|
||||
((shift_register_ & 0x0010) >> 2) |
|
||||
((shift_register_ & 0x0040) >> 3) |
|
||||
((shift_register_ & 0x0100) >> 4) |
|
||||
((shift_register_ & 0x0400) >> 5) |
|
||||
((shift_register_ & 0x1000) >> 6) |
|
||||
((shift_register_ & 0x4000) >> 7));
|
||||
bits_since_token_ = 0;
|
||||
|
||||
if(is_awaiting_marker_value_ && is_double_density_) {
|
||||
is_awaiting_marker_value_ = false;
|
||||
switch(latest_token_.byte_value) {
|
||||
case Storage::Encodings::MFM::IndexAddressByte:
|
||||
latest_token_.type = Token::Index;
|
||||
break;
|
||||
case Storage::Encodings::MFM::IDAddressByte:
|
||||
latest_token_.type = Token::ID;
|
||||
break;
|
||||
case Storage::Encodings::MFM::DataAddressByte:
|
||||
latest_token_.type = Token::Data;
|
||||
break;
|
||||
case Storage::Encodings::MFM::DeletedDataAddressByte:
|
||||
latest_token_.type = Token::DeletedData;
|
||||
break;
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
|
||||
crc_generator_.add(latest_token_.byte_value);
|
||||
posit_event(Event::Token);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void WD1770::process_index_hole() {
|
||||
index_hole_count_++;
|
||||
posit_event(Event::IndexHole);
|
||||
if(index_hole_count_target_ == index_hole_count_) {
|
||||
posit_event(Event::IndexHoleTarget);
|
||||
index_hole_count_target_ = -1;
|
||||
}
|
||||
|
||||
// motor power-down
|
||||
if(index_hole_count_ == 9 && !status_.busy && has_motor_on_line()) {
|
||||
set_motor_on(false);
|
||||
}
|
||||
|
||||
// head unload
|
||||
if(index_hole_count_ == 15 && !status_.busy && has_head_load_line()) {
|
||||
set_head_load_request(false);
|
||||
}
|
||||
}
|
||||
|
||||
void WD1770::process_write_completed() {
|
||||
posit_event(Event::DataWritten);
|
||||
}
|
||||
|
||||
#define WAIT_FOR_EVENT(mask) resume_point_ = __LINE__; interesting_event_mask_ = mask; return; case __LINE__:
|
||||
#define WAIT_FOR_TIME(ms) resume_point_ = __LINE__; interesting_event_mask_ = Event::Timer; delay_time_ = ms * 8000; if(delay_time_) return; case __LINE__:
|
||||
#define WAIT_FOR_BYTES(count) resume_point_ = __LINE__; interesting_event_mask_ = Event::Token; distance_into_section_ = 0; return; case __LINE__: if(latest_token_.type == Token::Byte) distance_into_section_++; if(distance_into_section_ < count) { interesting_event_mask_ = Event::Token; return; }
|
||||
#define WAIT_FOR_EVENT(mask) resume_point_ = __LINE__; interesting_event_mask_ = (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_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 BEGIN_SECTION() switch(resume_point_) { default:
|
||||
#define END_SECTION() 0; }
|
||||
|
||||
#define READ_ID() \
|
||||
if(new_event_type == Event::Token) { \
|
||||
if(!distance_into_section_ && latest_token_.type == Token::ID) {data_mode_ = DataMode::Reading; distance_into_section_++; } \
|
||||
else if(distance_into_section_ && distance_into_section_ < 7 && latest_token_.type == Token::Byte) { \
|
||||
header_[distance_into_section_ - 1] = latest_token_.byte_value; \
|
||||
if(new_event_type == (int)Event::Token) { \
|
||||
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) { \
|
||||
header_[distance_into_section_ - 1] = get_latest_token().byte_value; \
|
||||
distance_into_section_++; \
|
||||
} \
|
||||
}
|
||||
@@ -284,7 +148,7 @@ void WD1770::process_write_completed() {
|
||||
set_motor_on(true); \
|
||||
index_hole_count_ = 0; \
|
||||
index_hole_count_target_ = 6; \
|
||||
WAIT_FOR_EVENT(Event::IndexHoleTarget); \
|
||||
WAIT_FOR_EVENT(Event1770::IndexHoleTarget); \
|
||||
status_.spin_up = true;
|
||||
|
||||
// +--------+----------+-------------------------+
|
||||
@@ -304,7 +168,25 @@ void WD1770::process_write_completed() {
|
||||
// ! 4 ! Forc int ! 1 1 0 1 i3 i2 i1 i0 !
|
||||
// +--------+----------+-------------------------+
|
||||
|
||||
void WD1770::posit_event(Event new_event_type) {
|
||||
void WD1770::posit_event(int new_event_type) {
|
||||
if(new_event_type == (int)Event::IndexHole) {
|
||||
index_hole_count_++;
|
||||
if(index_hole_count_target_ == index_hole_count_) {
|
||||
posit_event((int)Event1770::IndexHoleTarget);
|
||||
index_hole_count_target_ = -1;
|
||||
}
|
||||
|
||||
// motor power-down
|
||||
if(index_hole_count_ == 9 && !status_.busy && has_motor_on_line()) {
|
||||
set_motor_on(false);
|
||||
}
|
||||
|
||||
// head unload
|
||||
if(index_hole_count_ == 15 && !status_.busy && has_head_load_line()) {
|
||||
set_head_load_request(false);
|
||||
}
|
||||
}
|
||||
|
||||
if(!(interesting_event_mask_ & (int)new_event_type)) return;
|
||||
interesting_event_mask_ &= ~new_event_type;
|
||||
|
||||
@@ -314,7 +196,7 @@ void WD1770::posit_event(Event new_event_type) {
|
||||
// Wait for a new command, branch to the appropriate handler.
|
||||
wait_for_command:
|
||||
printf("Idle...\n");
|
||||
data_mode_ = DataMode::Scanning;
|
||||
set_data_mode(DataMode::Scanning);
|
||||
index_hole_count_ = 0;
|
||||
|
||||
update_status([] (Status &status) {
|
||||
@@ -322,7 +204,7 @@ void WD1770::posit_event(Event new_event_type) {
|
||||
status.interrupt_request = true;
|
||||
});
|
||||
|
||||
WAIT_FOR_EVENT(Event::Command);
|
||||
WAIT_FOR_EVENT(Event1770::Command);
|
||||
|
||||
update_status([] (Status &status) {
|
||||
status.busy = true;
|
||||
@@ -371,7 +253,7 @@ void WD1770::posit_event(Event new_event_type) {
|
||||
}
|
||||
set_head_load_request(true);
|
||||
if(head_is_loaded_) goto test_type1_type;
|
||||
WAIT_FOR_EVENT(Event::HeadLoad);
|
||||
WAIT_FOR_EVENT(Event1770::HeadLoad);
|
||||
goto test_type1_type;
|
||||
|
||||
begin_type1_spin_up:
|
||||
@@ -403,7 +285,7 @@ void WD1770::posit_event(Event new_event_type) {
|
||||
goto verify;
|
||||
}
|
||||
step(step_direction_ ? 1 : -1);
|
||||
int time_to_wait;
|
||||
unsigned int time_to_wait;
|
||||
switch(command_ & 3) {
|
||||
default:
|
||||
case 0: time_to_wait = 6; break;
|
||||
@@ -428,7 +310,7 @@ void WD1770::posit_event(Event new_event_type) {
|
||||
distance_into_section_ = 0;
|
||||
|
||||
verify_read_data:
|
||||
WAIT_FOR_EVENT(Event::IndexHole | Event::Token);
|
||||
WAIT_FOR_EVENT((int)Event::IndexHole | (int)Event::Token);
|
||||
READ_ID();
|
||||
|
||||
if(index_hole_count_ == 6) {
|
||||
@@ -438,8 +320,8 @@ void WD1770::posit_event(Event new_event_type) {
|
||||
goto wait_for_command;
|
||||
}
|
||||
if(distance_into_section_ == 7) {
|
||||
data_mode_ = DataMode::Scanning;
|
||||
if(crc_generator_.get_value()) {
|
||||
set_data_mode(DataMode::Scanning);
|
||||
if(get_crc_generator().get_value()) {
|
||||
update_status([] (Status &status) {
|
||||
status.crc_error = true;
|
||||
});
|
||||
@@ -490,7 +372,7 @@ void WD1770::posit_event(Event new_event_type) {
|
||||
begin_type2_load_head:
|
||||
set_head_load_request(true);
|
||||
if(head_is_loaded_) goto test_type2_delay;
|
||||
WAIT_FOR_EVENT(Event::HeadLoad);
|
||||
WAIT_FOR_EVENT(Event1770::HeadLoad);
|
||||
goto test_type2_delay;
|
||||
|
||||
begin_type2_spin_up:
|
||||
@@ -512,7 +394,7 @@ void WD1770::posit_event(Event new_event_type) {
|
||||
}
|
||||
|
||||
type2_get_header:
|
||||
WAIT_FOR_EVENT(Event::IndexHole | Event::Token);
|
||||
WAIT_FOR_EVENT((int)Event::IndexHole | (int)Event::Token);
|
||||
READ_ID();
|
||||
|
||||
if(index_hole_count_ == 5) {
|
||||
@@ -524,11 +406,11 @@ void WD1770::posit_event(Event new_event_type) {
|
||||
}
|
||||
if(distance_into_section_ == 7) {
|
||||
printf("Considering %d/%d\n", header_[0], header_[2]);
|
||||
data_mode_ = DataMode::Scanning;
|
||||
set_data_mode(DataMode::Scanning);
|
||||
if( header_[0] == track_ && header_[2] == sector_ &&
|
||||
(has_motor_on_line() || !(command_&0x02) || ((command_&0x08) >> 3) == header_[1])) {
|
||||
printf("Found %d/%d\n", header_[0], header_[2]);
|
||||
if(crc_generator_.get_value()) {
|
||||
if(get_crc_generator().get_value()) {
|
||||
printf("CRC error; back to searching\n");
|
||||
update_status([] (Status &status) {
|
||||
status.crc_error = true;
|
||||
@@ -553,20 +435,20 @@ void WD1770::posit_event(Event new_event_type) {
|
||||
type2_read_data:
|
||||
WAIT_FOR_EVENT(Event::Token);
|
||||
// TODO: timeout
|
||||
if(latest_token_.type == Token::Data || latest_token_.type == Token::DeletedData) {
|
||||
if(get_latest_token().type == Token::Data || get_latest_token().type == Token::DeletedData) {
|
||||
update_status([this] (Status &status) {
|
||||
status.record_type = (latest_token_.type == Token::DeletedData);
|
||||
status.record_type = (get_latest_token().type == Token::DeletedData);
|
||||
});
|
||||
distance_into_section_ = 0;
|
||||
data_mode_ = DataMode::Reading;
|
||||
set_data_mode(DataMode::Reading);
|
||||
goto type2_read_byte;
|
||||
}
|
||||
goto type2_read_data;
|
||||
|
||||
type2_read_byte:
|
||||
WAIT_FOR_EVENT(Event::Token);
|
||||
if(latest_token_.type != Token::Byte) goto type2_read_byte;
|
||||
data_ = latest_token_.byte_value;
|
||||
if(get_latest_token().type != Token::Byte) goto type2_read_byte;
|
||||
data_ = get_latest_token().byte_value;
|
||||
update_status([] (Status &status) {
|
||||
status.lost_data |= status.data_request;
|
||||
status.data_request = true;
|
||||
@@ -580,11 +462,11 @@ void WD1770::posit_event(Event new_event_type) {
|
||||
|
||||
type2_check_crc:
|
||||
WAIT_FOR_EVENT(Event::Token);
|
||||
if(latest_token_.type != Token::Byte) goto type2_read_byte;
|
||||
header_[distance_into_section_] = latest_token_.byte_value;
|
||||
if(get_latest_token().type != Token::Byte) goto type2_read_byte;
|
||||
header_[distance_into_section_] = get_latest_token().byte_value;
|
||||
distance_into_section_++;
|
||||
if(distance_into_section_ == 2) {
|
||||
if(crc_generator_.get_value()) {
|
||||
if(get_crc_generator().get_value()) {
|
||||
printf("CRC error; terminating\n");
|
||||
update_status([this] (Status &status) {
|
||||
status.crc_error = true;
|
||||
@@ -615,24 +497,24 @@ void WD1770::posit_event(Event new_event_type) {
|
||||
goto wait_for_command;
|
||||
}
|
||||
WAIT_FOR_BYTES(1);
|
||||
if(is_double_density_) {
|
||||
if(get_is_double_density()) {
|
||||
WAIT_FOR_BYTES(11);
|
||||
}
|
||||
|
||||
data_mode_ = DataMode::Writing;
|
||||
begin_writing();
|
||||
for(int c = 0; c < (is_double_density_ ? 12 : 6); c++) {
|
||||
set_data_mode(DataMode::Writing);
|
||||
begin_writing(false);
|
||||
for(int c = 0; c < (get_is_double_density() ? 12 : 6); c++) {
|
||||
write_byte(0);
|
||||
}
|
||||
WAIT_FOR_EVENT(Event::DataWritten);
|
||||
|
||||
if(is_double_density_) {
|
||||
crc_generator_.set_value(Storage::Encodings::MFM::MFMPostSyncCRCValue);
|
||||
if(get_is_double_density()) {
|
||||
get_crc_generator().set_value(Storage::Encodings::MFM::MFMPostSyncCRCValue);
|
||||
for(int c = 0; c < 3; c++) write_raw_short(Storage::Encodings::MFM::MFMSync);
|
||||
write_byte((command_&0x01) ? Storage::Encodings::MFM::DeletedDataAddressByte : Storage::Encodings::MFM::DataAddressByte);
|
||||
} else {
|
||||
crc_generator_.reset();
|
||||
crc_generator_.add((command_&0x01) ? Storage::Encodings::MFM::DeletedDataAddressByte : Storage::Encodings::MFM::DataAddressByte);
|
||||
get_crc_generator().reset();
|
||||
get_crc_generator().add((command_&0x01) ? Storage::Encodings::MFM::DeletedDataAddressByte : Storage::Encodings::MFM::DataAddressByte);
|
||||
write_raw_short((command_&0x01) ? Storage::Encodings::MFM::FMDeletedDataAddressMark : Storage::Encodings::MFM::FMDataAddressMark);
|
||||
}
|
||||
|
||||
@@ -668,11 +550,8 @@ void WD1770::posit_event(Event new_event_type) {
|
||||
|
||||
goto type2_write_loop;
|
||||
|
||||
type2_write_crc: {
|
||||
uint16_t crc = crc_generator_.get_value();
|
||||
write_byte(crc >> 8);
|
||||
write_byte(crc & 0xff);
|
||||
}
|
||||
type2_write_crc:
|
||||
write_crc();
|
||||
write_byte(0xff);
|
||||
WAIT_FOR_EVENT(Event::DataWritten);
|
||||
end_writing();
|
||||
@@ -711,7 +590,7 @@ void WD1770::posit_event(Event new_event_type) {
|
||||
begin_type3_load_head:
|
||||
set_head_load_request(true);
|
||||
if(head_is_loaded_) goto type3_test_delay;
|
||||
WAIT_FOR_EVENT(Event::HeadLoad);
|
||||
WAIT_FOR_EVENT(Event1770::HeadLoad);
|
||||
goto type3_test_delay;
|
||||
|
||||
begin_type3_spin_up:
|
||||
@@ -732,17 +611,17 @@ void WD1770::posit_event(Event new_event_type) {
|
||||
distance_into_section_ = 0;
|
||||
|
||||
read_address_get_header:
|
||||
WAIT_FOR_EVENT(Event::IndexHole | Event::Token);
|
||||
if(new_event_type == Event::Token) {
|
||||
if(!distance_into_section_ && latest_token_.type == Token::ID) {data_mode_ = DataMode::Reading; distance_into_section_++; }
|
||||
else if(distance_into_section_ && distance_into_section_ < 7 && latest_token_.type == Token::Byte) {
|
||||
WAIT_FOR_EVENT((int)Event::IndexHole | (int)Event::Token);
|
||||
if(new_event_type == (int)Event::Token) {
|
||||
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) {
|
||||
if(status_.data_request) {
|
||||
update_status([] (Status &status) {
|
||||
status.lost_data = true;
|
||||
});
|
||||
goto wait_for_command;
|
||||
}
|
||||
header_[distance_into_section_ - 1] = data_ = latest_token_.byte_value;
|
||||
header_[distance_into_section_ - 1] = data_ = get_latest_token().byte_value;
|
||||
track_ = header_[0];
|
||||
update_status([] (Status &status) {
|
||||
status.data_request = true;
|
||||
@@ -750,7 +629,7 @@ void WD1770::posit_event(Event new_event_type) {
|
||||
distance_into_section_++;
|
||||
|
||||
if(distance_into_section_ == 7) {
|
||||
if(crc_generator_.get_value()) {
|
||||
if(get_crc_generator().get_value()) {
|
||||
update_status([] (Status &status) {
|
||||
status.crc_error = true;
|
||||
});
|
||||
@@ -773,7 +652,7 @@ void WD1770::posit_event(Event new_event_type) {
|
||||
index_hole_count_ = 0;
|
||||
|
||||
read_track_read_byte:
|
||||
WAIT_FOR_EVENT(Event::Token | Event::IndexHole);
|
||||
WAIT_FOR_EVENT((int)Event::Token | (int)Event::IndexHole);
|
||||
if(index_hole_count_) {
|
||||
goto wait_for_command;
|
||||
}
|
||||
@@ -783,7 +662,7 @@ void WD1770::posit_event(Event new_event_type) {
|
||||
});
|
||||
goto wait_for_command;
|
||||
}
|
||||
data_ = latest_token_.byte_value;
|
||||
data_ = get_latest_token().byte_value;
|
||||
update_status([] (Status &status) {
|
||||
status.data_request = true;
|
||||
});
|
||||
@@ -814,25 +693,23 @@ void WD1770::posit_event(Event new_event_type) {
|
||||
goto wait_for_command;
|
||||
}
|
||||
|
||||
WAIT_FOR_EVENT(Event::IndexHoleTarget);
|
||||
begin_writing();
|
||||
WAIT_FOR_EVENT(Event1770::IndexHoleTarget);
|
||||
begin_writing(true);
|
||||
index_hole_count_ = 0;
|
||||
|
||||
write_track_write_loop:
|
||||
if(is_double_density_) {
|
||||
if(get_is_double_density()) {
|
||||
switch(data_) {
|
||||
case 0xf5:
|
||||
write_raw_short(Storage::Encodings::MFM::MFMSync);
|
||||
crc_generator_.set_value(Storage::Encodings::MFM::MFMPostSyncCRCValue);
|
||||
get_crc_generator().set_value(Storage::Encodings::MFM::MFMPostSyncCRCValue);
|
||||
break;
|
||||
case 0xf6:
|
||||
write_raw_short(Storage::Encodings::MFM::MFMIndexSync);
|
||||
break;
|
||||
case 0xff: {
|
||||
uint16_t crc = crc_generator_.get_value();
|
||||
write_byte(crc >> 8);
|
||||
write_byte(crc & 0xff);
|
||||
} break;
|
||||
case 0xff:
|
||||
write_crc();
|
||||
break;
|
||||
default:
|
||||
write_byte(data_);
|
||||
break;
|
||||
@@ -855,17 +732,15 @@ void WD1770::posit_event(Event new_event_type) {
|
||||
(data_ & 0x01)
|
||||
)
|
||||
);
|
||||
crc_generator_.reset();
|
||||
crc_generator_.add(data_);
|
||||
get_crc_generator().reset();
|
||||
get_crc_generator().add(data_);
|
||||
break;
|
||||
case 0xfc:
|
||||
write_raw_short(Storage::Encodings::MFM::FMIndexAddressMark);
|
||||
break;
|
||||
case 0xf7: {
|
||||
uint16_t crc = crc_generator_.get_value();
|
||||
write_byte(crc >> 8);
|
||||
write_byte(crc & 0xff);
|
||||
} break;
|
||||
case 0xf7:
|
||||
write_crc();
|
||||
break;
|
||||
default:
|
||||
write_byte(data_);
|
||||
break;
|
||||
@@ -909,27 +784,5 @@ void WD1770::set_head_load_request(bool head_load) {}
|
||||
|
||||
void WD1770::set_head_loaded(bool head_loaded) {
|
||||
head_is_loaded_ = head_loaded;
|
||||
if(head_loaded) posit_event(Event::HeadLoad);
|
||||
}
|
||||
|
||||
void WD1770::write_bit(int bit) {
|
||||
if(is_double_density_) {
|
||||
Controller::write_bit(!bit && !last_bit_);
|
||||
Controller::write_bit(!!bit);
|
||||
last_bit_ = bit;
|
||||
} else {
|
||||
Controller::write_bit(true);
|
||||
Controller::write_bit(!!bit);
|
||||
}
|
||||
}
|
||||
|
||||
void WD1770::write_byte(uint8_t byte) {
|
||||
for(int c = 0; c < 8; c++) write_bit((byte << c)&0x80);
|
||||
crc_generator_.add(byte);
|
||||
}
|
||||
|
||||
void WD1770::write_raw_short(uint16_t value) {
|
||||
for(int c = 0; c < 16; c++) {
|
||||
Controller::write_bit(!!((value << c)&0x8000));
|
||||
}
|
||||
if(head_loaded) posit_event((int)Event1770::HeadLoad);
|
||||
}
|
||||
|
||||
@@ -9,26 +9,41 @@
|
||||
#ifndef _770_hpp
|
||||
#define _770_hpp
|
||||
|
||||
#include "../../Storage/Disk/DiskController.hpp"
|
||||
#include "../../NumberTheory/CRC.hpp"
|
||||
#include "../../Storage/Disk/MFMDiskController.hpp"
|
||||
|
||||
namespace WD {
|
||||
|
||||
class WD1770: public Storage::Disk::Controller {
|
||||
/*!
|
||||
Provides an emulation of various Western Digital drive controllers, including the
|
||||
WD1770, WD1772, FDC1773 and FDC1793.
|
||||
*/
|
||||
class WD1770: public Storage::Disk::MFMController {
|
||||
public:
|
||||
enum Personality {
|
||||
P1770, // implies automatic motor-on management with Type 2 commands offering a spin-up disable
|
||||
P1770, // implies automatic motor-on management, with Type 2 commands offering a spin-up disable
|
||||
P1772, // as per the 1770, with different stepping rates
|
||||
P1773, // implements the side number-testing logic of the 1793; omits spin-up/loading logic
|
||||
P1793 // implies Type 2 commands use side number testing logic; spin-up/loading is by HLD and HLT
|
||||
};
|
||||
|
||||
/*!
|
||||
Constructs an instance of the drive controller that behaves according to personality @c p.
|
||||
@param p The type of controller to emulate.
|
||||
*/
|
||||
WD1770(Personality p);
|
||||
|
||||
void set_is_double_density(bool is_double_density);
|
||||
/// Sets the value of the double-density input; when @c is_double_density is @c true, reads and writes double-density format data.
|
||||
using Storage::Disk::MFMController::set_is_double_density;
|
||||
|
||||
/// Writes @c value to the register at @c address. Only the low two bits of the address are decoded.
|
||||
void set_register(int address, uint8_t value);
|
||||
|
||||
/// Fetches the value of the register @c address. Only the low two bits of the address are decoded.
|
||||
uint8_t get_register(int address);
|
||||
|
||||
void run_for_cycles(unsigned int number_of_cycles);
|
||||
/// Runs the controller for @c number_of_cycles cycles.
|
||||
void run_for(const Cycles cycles);
|
||||
using Storage::Disk::Controller::run_for;
|
||||
|
||||
enum Flag: uint8_t {
|
||||
NotReady = 0x80,
|
||||
@@ -47,8 +62,12 @@ class WD1770: public Storage::Disk::Controller {
|
||||
Busy = 0x01
|
||||
};
|
||||
|
||||
/// @returns The current value of the IRQ line output.
|
||||
inline bool get_interrupt_request_line() { return status_.interrupt_request; }
|
||||
|
||||
/// @returns The current value of the DRQ line output.
|
||||
inline bool get_data_request_line() { return status_.data_request; }
|
||||
|
||||
class Delegate {
|
||||
public:
|
||||
virtual void wd1770_did_change_output(WD1770 *wd1770) = 0;
|
||||
@@ -87,66 +106,31 @@ class WD1770: public Storage::Disk::Controller {
|
||||
|
||||
int index_hole_count_;
|
||||
int index_hole_count_target_;
|
||||
int bits_since_token_;
|
||||
int distance_into_section_;
|
||||
bool is_awaiting_marker_value_;
|
||||
|
||||
int step_direction_;
|
||||
void update_status(std::function<void(Status &)> updater);
|
||||
|
||||
// Tokeniser
|
||||
enum DataMode {
|
||||
Scanning,
|
||||
Reading,
|
||||
Writing
|
||||
} data_mode_;
|
||||
bool is_double_density_;
|
||||
int shift_register_;
|
||||
struct Token {
|
||||
enum Type {
|
||||
Index, ID, Data, DeletedData, Sync, Byte
|
||||
} type;
|
||||
uint8_t byte_value;
|
||||
} latest_token_;
|
||||
|
||||
// Events
|
||||
enum Event: int {
|
||||
Command = (1 << 0), // Indicates receipt of a new command.
|
||||
Token = (1 << 1), // Indicates recognition of a new token in the flux stream. Interrogate latest_token_ for details.
|
||||
IndexHole = (1 << 2), // Indicates the passing of a physical index hole.
|
||||
HeadLoad = (1 << 3), // Indicates the head has been loaded (1973 only).
|
||||
DataWritten = (1 << 4), // Indicates that all queued bits have been written
|
||||
|
||||
enum Event1770: int {
|
||||
Command = (1 << 3), // Indicates receipt of a new command.
|
||||
HeadLoad = (1 << 4), // Indicates the head has been loaded (1973 only).
|
||||
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_.
|
||||
};
|
||||
void posit_event(Event type);
|
||||
void posit_event(int type);
|
||||
int interesting_event_mask_;
|
||||
int resume_point_;
|
||||
int delay_time_;
|
||||
|
||||
// Output
|
||||
int last_bit_;
|
||||
void write_bit(int bit);
|
||||
void write_byte(uint8_t byte);
|
||||
void write_raw_short(uint16_t value);
|
||||
unsigned int delay_time_;
|
||||
|
||||
// ID buffer
|
||||
uint8_t header_[6];
|
||||
|
||||
// CRC generator
|
||||
NumberTheory::CRC16 crc_generator_;
|
||||
|
||||
// 1793 head-loading logic
|
||||
bool head_is_loaded_;
|
||||
|
||||
// delegate
|
||||
Delegate *delegate_;
|
||||
|
||||
// Storage::Disk::Controller
|
||||
virtual void process_input_bit(int value, unsigned int cycles_since_index_hole);
|
||||
virtual void process_index_hole();
|
||||
virtual void process_write_completed();
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -13,6 +13,8 @@
|
||||
#include <typeinfo>
|
||||
#include <cstdio>
|
||||
|
||||
#include "../../ClockReceiver/ClockReceiver.hpp"
|
||||
|
||||
namespace MOS {
|
||||
|
||||
/*!
|
||||
@@ -250,32 +252,22 @@ template <class T> class MOS6522 {
|
||||
timer_is_running_[0] = false;\
|
||||
}
|
||||
|
||||
/*!
|
||||
Runs for a specified number of half cycles.
|
||||
/*! 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();
|
||||
|
||||
Although the original chip accepts only a phase-2 input, timer reloads are specified as occuring
|
||||
1.5 cycles after the timer hits zero. It therefore may be necessary to emulate at half-cycle precision.
|
||||
|
||||
The first emulated half-cycle will be the period between the trailing edge of a phase-2 input and the
|
||||
next rising edge. So it should align with a full system's phase-1. The next emulated half-cycle will be
|
||||
that which occurs during phase-2.
|
||||
|
||||
Callers should decide whether they are going to use @c run_for_half_cycles or @c run_for_cycles, and not
|
||||
intermingle usage.
|
||||
*/
|
||||
inline void run_for_half_cycles(unsigned int number_of_cycles) {
|
||||
if(is_phase2_) {
|
||||
phase2();
|
||||
number_of_cycles--;
|
||||
number_of_half_cycles--;
|
||||
}
|
||||
|
||||
while(number_of_cycles >= 2) {
|
||||
while(number_of_half_cycles >= 2) {
|
||||
phase1();
|
||||
phase2();
|
||||
number_of_cycles -= 2;
|
||||
number_of_half_cycles -= 2;
|
||||
}
|
||||
|
||||
if(number_of_cycles) {
|
||||
if(number_of_half_cycles) {
|
||||
phase1();
|
||||
is_phase2_ = true;
|
||||
} else {
|
||||
@@ -283,13 +275,9 @@ template <class T> class MOS6522 {
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
Runs for a specified number of cycles.
|
||||
|
||||
Callers should decide whether they are going to use @c run_for_half_cycles or @c run_for_cycles, and not
|
||||
intermingle usage.
|
||||
*/
|
||||
inline void run_for_cycles(unsigned int number_of_cycles) {
|
||||
/*! 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();
|
||||
|
||||
@@ -12,6 +12,8 @@
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
|
||||
#include "../../ClockReceiver/ClockReceiver.hpp"
|
||||
|
||||
namespace MOS {
|
||||
|
||||
/*!
|
||||
@@ -104,7 +106,9 @@ template <class T> class MOS6532 {
|
||||
return 0xff;
|
||||
}
|
||||
|
||||
inline void run_for_cycles(unsigned int number_of_cycles) {
|
||||
inline void run_for(const Cycles cycles) {
|
||||
unsigned int number_of_cycles = (unsigned int)cycles.as_int();
|
||||
|
||||
// permit counting _to_ zero; counting _through_ zero initiates the other behaviour
|
||||
if(timer_.value >= number_of_cycles) {
|
||||
timer_.value -= number_of_cycles;
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
|
||||
#include "../../Outputs/CRT/CRT.hpp"
|
||||
#include "../../Outputs/Speaker.hpp"
|
||||
#include "../../ClockReceiver/ClockReceiver.hpp"
|
||||
|
||||
namespace MOS {
|
||||
|
||||
@@ -99,10 +100,10 @@ template <class T> class MOS6560 {
|
||||
8, 88, 120, 56,
|
||||
};
|
||||
const uint8_t ntsc_chrominances[16] = {
|
||||
255, 255, 40, 104,
|
||||
64, 120, 80, 16,
|
||||
32, 32, 40, 104,
|
||||
64, 120, 80, 16,
|
||||
255, 255, 8, 72,
|
||||
32, 88, 48, 112,
|
||||
0, 0, 8, 72,
|
||||
32, 88, 48, 112,
|
||||
};
|
||||
const uint8_t *chrominances;
|
||||
Outputs::CRT::DisplayType display_type;
|
||||
@@ -149,10 +150,11 @@ template <class T> class MOS6560 {
|
||||
/*!
|
||||
Runs for cycles. Derr.
|
||||
*/
|
||||
inline void run_for_cycles(unsigned int number_of_cycles) {
|
||||
inline void run_for(const Cycles cycles) {
|
||||
// keep track of the amount of time since the speaker was updated; lazy updates are applied
|
||||
cycles_since_speaker_update_ += number_of_cycles;
|
||||
cycles_since_speaker_update_ += cycles;
|
||||
|
||||
int number_of_cycles = cycles.as_int();
|
||||
while(number_of_cycles--) {
|
||||
// keep an old copy of the vertical count because that test is a cycle later than the actual changes
|
||||
int previous_vertical_counter = vertical_counter_;
|
||||
@@ -259,7 +261,7 @@ template <class T> class MOS6560 {
|
||||
if(this_state_ != output_state_) {
|
||||
switch(output_state_) {
|
||||
case State::Sync: crt_->output_sync(cycles_in_state_ * 4); break;
|
||||
case State::ColourBurst: crt_->output_colour_burst(cycles_in_state_ * 4, (is_odd_frame_ || is_odd_line_) ? 128 : 0, 0); break;
|
||||
case State::ColourBurst: crt_->output_colour_burst(cycles_in_state_ * 4, (is_odd_frame_ || is_odd_line_) ? 128 : 0); break;
|
||||
case State::Border: output_border(cycles_in_state_ * 4); break;
|
||||
case State::Pixels: crt_->output_data(cycles_in_state_ * 4, 1); break;
|
||||
}
|
||||
@@ -323,7 +325,7 @@ template <class T> class MOS6560 {
|
||||
/*!
|
||||
Causes the 6560 to flush as much pending CRT and speaker communications as possible.
|
||||
*/
|
||||
inline void synchronise() { update_audio(); speaker_->flush(); }
|
||||
inline void flush() { update_audio(); speaker_->flush(); }
|
||||
|
||||
/*!
|
||||
Writes to a 6560 register.
|
||||
@@ -406,10 +408,9 @@ template <class T> class MOS6560 {
|
||||
std::shared_ptr<Outputs::CRT::CRT> crt_;
|
||||
|
||||
std::shared_ptr<Speaker> speaker_;
|
||||
unsigned int cycles_since_speaker_update_;
|
||||
Cycles cycles_since_speaker_update_;
|
||||
void update_audio() {
|
||||
speaker_->run_for_cycles(cycles_since_speaker_update_ >> 2);
|
||||
cycles_since_speaker_update_ &= 3;
|
||||
speaker_->run_for(Cycles(cycles_since_speaker_update_.divide(Cycles(4))));
|
||||
}
|
||||
|
||||
// register state
|
||||
|
||||
226
Components/6845/CRTC6845.hpp
Normal file
226
Components/6845/CRTC6845.hpp
Normal file
@@ -0,0 +1,226 @@
|
||||
//
|
||||
// CRTC6845.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 31/07/2017.
|
||||
// Copyright © 2017 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef CRTC6845_hpp
|
||||
#define CRTC6845_hpp
|
||||
|
||||
#include "../../ClockReceiver/ClockReceiver.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
|
||||
namespace Motorola {
|
||||
namespace CRTC {
|
||||
|
||||
struct BusState {
|
||||
bool display_enable;
|
||||
bool hsync;
|
||||
bool vsync;
|
||||
bool cursor;
|
||||
uint16_t refresh_address;
|
||||
uint16_t row_address;
|
||||
};
|
||||
|
||||
class BusHandler {
|
||||
public:
|
||||
/*!
|
||||
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 {
|
||||
HD6845S, //
|
||||
UM6845R, //
|
||||
MC6845, //
|
||||
AMS40226 //
|
||||
};
|
||||
|
||||
template <class T> class CRTC6845 {
|
||||
public:
|
||||
|
||||
CRTC6845(Personality p, T &bus_handler) noexcept :
|
||||
personality_(p), bus_handler_(bus_handler) {}
|
||||
|
||||
void select_register(uint8_t r) {
|
||||
selected_register_ = r;
|
||||
}
|
||||
|
||||
uint8_t get_status() const {
|
||||
return 0xff;
|
||||
}
|
||||
|
||||
uint8_t get_register() const {
|
||||
if(selected_register_ < 12 || selected_register_ > 17) return 0xff;
|
||||
return registers_[selected_register_];
|
||||
}
|
||||
|
||||
void set_register(uint8_t value) {
|
||||
static uint8_t masks[] = {
|
||||
0xff, 0xff, 0xff, 0xff, 0x7f, 0x1f, 0x7f, 0x7f,
|
||||
0xff, 0x1f, 0x7f, 0x1f, 0x3f, 0xff, 0x3f, 0xff
|
||||
};
|
||||
|
||||
if(selected_register_ < 16) {
|
||||
registers_[selected_register_] = value & masks[selected_register_];
|
||||
}
|
||||
}
|
||||
|
||||
void trigger_light_pen() {
|
||||
registers_[17] = bus_state_.refresh_address & 0xff;
|
||||
registers_[16] = bus_state_.refresh_address >> 8;
|
||||
}
|
||||
|
||||
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(bus_state_.hsync) {
|
||||
bus_state_.hsync = hsync_counter_ != (registers_[3] & 15);
|
||||
hsync_counter_ = (hsync_counter_ + 1) & 15;
|
||||
}
|
||||
|
||||
perform_bus_cycle_phase2();
|
||||
}
|
||||
}
|
||||
|
||||
const BusState &get_bus_state() const {
|
||||
return bus_state_;
|
||||
}
|
||||
|
||||
private:
|
||||
inline void perform_bus_cycle_phase1() {
|
||||
bus_state_.display_enable = character_is_visible_ && line_is_visible_;
|
||||
bus_handler_.perform_bus_cycle_phase1(bus_state_);
|
||||
}
|
||||
|
||||
inline void perform_bus_cycle_phase2() {
|
||||
bus_state_.display_enable = character_is_visible_ && line_is_visible_;
|
||||
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;
|
||||
if(vsync_counter_ == (registers_[3] >> 4)) {
|
||||
bus_state_.vsync = false;
|
||||
}
|
||||
}
|
||||
|
||||
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_ = (uint16_t)((registers_[12] << 8) | registers_[13]);
|
||||
bus_state_.refresh_address = line_address_;
|
||||
}
|
||||
|
||||
Personality personality_;
|
||||
T &bus_handler_;
|
||||
BusState bus_state_;
|
||||
|
||||
uint8_t registers_[18];
|
||||
int selected_register_;
|
||||
|
||||
uint8_t character_counter_;
|
||||
uint8_t line_counter_;
|
||||
|
||||
bool character_is_visible_, line_is_visible_;
|
||||
|
||||
int hsync_counter_;
|
||||
int vsync_counter_;
|
||||
bool is_in_adjustment_period_;
|
||||
|
||||
uint16_t line_address_;
|
||||
uint16_t end_of_line_address_;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* CRTC6845_hpp */
|
||||
92
Components/8255/i8255.hpp
Normal file
92
Components/8255/i8255.hpp
Normal file
@@ -0,0 +1,92 @@
|
||||
//
|
||||
// i8255.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 01/08/2017.
|
||||
// Copyright © 2017 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef i8255_hpp
|
||||
#define i8255_hpp
|
||||
|
||||
namespace Intel {
|
||||
namespace i8255 {
|
||||
|
||||
class PortHandler {
|
||||
public:
|
||||
void set_value(int port, uint8_t value) {}
|
||||
uint8_t get_value(int port) { return 0xff; }
|
||||
};
|
||||
|
||||
// TODO: Modes 1 and 2.
|
||||
template <class T> class i8255 {
|
||||
public:
|
||||
i8255(T &port_handler) : control_(0), outputs_{0, 0, 0}, port_handler_(port_handler) {}
|
||||
|
||||
/*!
|
||||
Stores the value @c value to the register at @c address. If this causes a change in 8255 output
|
||||
then the PortHandler will be informed.
|
||||
*/
|
||||
void set_register(int address, uint8_t value) {
|
||||
switch(address & 3) {
|
||||
case 0:
|
||||
if(!(control_ & 0x10)) {
|
||||
// TODO: so what would output be when switching from input to output mode?
|
||||
outputs_[0] = value; port_handler_.set_value(0, value);
|
||||
}
|
||||
break;
|
||||
case 1:
|
||||
if(!(control_ & 0x02)) {
|
||||
outputs_[1] = value; port_handler_.set_value(1, value);
|
||||
}
|
||||
break;
|
||||
case 2: outputs_[2] = value; port_handler_.set_value(2, value); break;
|
||||
case 3:
|
||||
if(value & 0x80) {
|
||||
control_ = value;
|
||||
} else {
|
||||
if(value & 1) {
|
||||
outputs_[2] |= 1 << ((value >> 1)&7);
|
||||
} else {
|
||||
outputs_[2] &= ~(1 << ((value >> 1)&7));
|
||||
}
|
||||
}
|
||||
update_outputs();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
Obtains the current value for the register at @c address. If this provides a reading
|
||||
of input then the PortHandler will be queried.
|
||||
*/
|
||||
uint8_t get_register(int address) {
|
||||
switch(address & 3) {
|
||||
case 0: return (control_ & 0x10) ? port_handler_.get_value(0) : outputs_[0];
|
||||
case 1: return (control_ & 0x02) ? port_handler_.get_value(1) : outputs_[1];
|
||||
case 2: {
|
||||
if(!(control_ & 0x09)) return outputs_[2];
|
||||
uint8_t input = port_handler_.get_value(2);
|
||||
return ((control_ & 0x01) ? (input & 0x0f) : (outputs_[2] & 0x0f)) | ((control_ & 0x08) ? (input & 0xf0) : (outputs_[2] & 0xf0));
|
||||
}
|
||||
case 3: return control_;
|
||||
}
|
||||
return 0xff;
|
||||
}
|
||||
|
||||
private:
|
||||
void update_outputs() {
|
||||
port_handler_.set_value(0, outputs_[0]);
|
||||
port_handler_.set_value(1, outputs_[1]);
|
||||
port_handler_.set_value(2, outputs_[2]);
|
||||
}
|
||||
|
||||
uint8_t control_;
|
||||
uint8_t outputs_[3];
|
||||
T &port_handler_;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* i8255_hpp */
|
||||
872
Components/8272/i8272.cpp
Normal file
872
Components/8272/i8272.cpp
Normal file
@@ -0,0 +1,872 @@
|
||||
//
|
||||
// i8272.cpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 05/08/2017.
|
||||
// Copyright © 2017 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#include "i8272.hpp"
|
||||
#include "../../Storage/Disk/Encodings/MFM.hpp"
|
||||
|
||||
#include <cstdio>
|
||||
|
||||
using namespace Intel::i8272;
|
||||
|
||||
#define SetDataRequest() (main_status_ |= 0x80)
|
||||
#define ResetDataRequest() (main_status_ &= ~0x80)
|
||||
#define DataRequest() (main_status_ & 0x80)
|
||||
|
||||
#define SetDataDirectionToProcessor() (main_status_ |= 0x40)
|
||||
#define SetDataDirectionFromProcessor() (main_status_ &= ~0x40)
|
||||
#define DataDirectionToProcessor() (main_status_ & 0x40)
|
||||
|
||||
#define SetNonDMAExecution() (main_status_ |= 0x20)
|
||||
#define ResetNonDMAExecution() (main_status_ &= ~0x20)
|
||||
|
||||
#define SetBusy() (main_status_ |= 0x10)
|
||||
#define ResetBusy() (main_status_ &= ~0x10)
|
||||
#define Busy() (main_status_ & 0x10)
|
||||
|
||||
#define SetAbnormalTermination() (status_[0] |= 0x40)
|
||||
#define SetInvalidCommand() (status_[0] |= 0x80)
|
||||
#define SetReadyChanged() (status_[0] |= 0xc0)
|
||||
#define SetSeekEnd() (status_[0] |= 0x20)
|
||||
#define SetEquipmentCheck() (status_[0] |= 0x10)
|
||||
#define SetNotReady() (status_[0] |= 0x08)
|
||||
|
||||
#define SetEndOfCylinder() (status_[1] |= 0x80)
|
||||
#define SetDataError() (status_[1] |= 0x20)
|
||||
#define SetOverrun() (status_[1] |= 0x10)
|
||||
#define SetNoData() (status_[1] |= 0x04)
|
||||
#define SetNotWriteable() (status_[1] |= 0x02)
|
||||
#define SetMissingAddressMark() (status_[1] |= 0x01)
|
||||
|
||||
#define SetControlMark() (status_[2] |= 0x40)
|
||||
#define ControlMark() (status_[2] & 0x40)
|
||||
|
||||
#define SetDataFieldDataError() (status_[2] |= 0x20)
|
||||
#define SetWrongCyinder() (status_[2] |= 0x10)
|
||||
#define SetScanEqualHit() (status_[2] |= 0x08)
|
||||
#define SetScanNotSatisfied() (status_[2] |= 0x04)
|
||||
#define SetBadCylinder() (status_[2] |= 0x02)
|
||||
#define SetMissingDataAddressMark() (status_[2] |= 0x01)
|
||||
|
||||
namespace {
|
||||
const uint8_t CommandReadData = 0x06;
|
||||
const uint8_t CommandReadDeletedData = 0x0c;
|
||||
|
||||
const uint8_t CommandWriteData = 0x05;
|
||||
const uint8_t CommandWriteDeletedData = 0x09;
|
||||
|
||||
const uint8_t CommandReadTrack = 0x02;
|
||||
const uint8_t CommandReadID = 0x0a;
|
||||
const uint8_t CommandFormatTrack = 0x0d;
|
||||
|
||||
const uint8_t CommandScanLow = 0x11;
|
||||
const uint8_t CommandScanLowOrEqual = 0x19;
|
||||
const uint8_t CommandScanHighOrEqual = 0x1d;
|
||||
|
||||
const uint8_t CommandRecalibrate = 0x07;
|
||||
const uint8_t CommandSeek = 0x0f;
|
||||
|
||||
const uint8_t CommandSenseInterruptStatus = 0x08;
|
||||
const uint8_t CommandSpecify = 0x03;
|
||||
const uint8_t CommandSenseDriveStatus = 0x04;
|
||||
}
|
||||
|
||||
i8272::i8272(BusHandler &bus_handler, Cycles clock_rate, int clock_rate_multiplier, int revolutions_per_minute) :
|
||||
Storage::Disk::MFMController(clock_rate, clock_rate_multiplier, revolutions_per_minute),
|
||||
bus_handler_(bus_handler),
|
||||
main_status_(0),
|
||||
interesting_event_mask_((int)Event8272::CommandByte),
|
||||
resume_point_(0),
|
||||
delay_time_(0),
|
||||
head_timers_running_(0),
|
||||
expects_input_(false),
|
||||
drives_seeking_(0) {
|
||||
posit_event((int)Event8272::CommandByte);
|
||||
}
|
||||
|
||||
bool i8272::is_sleeping() {
|
||||
return is_sleeping_ && Storage::Disk::MFMController::is_sleeping();
|
||||
}
|
||||
|
||||
void i8272::run_for(Cycles cycles) {
|
||||
Storage::Disk::MFMController::run_for(cycles);
|
||||
|
||||
if(is_sleeping_) return;
|
||||
|
||||
// check for an expired timer
|
||||
if(delay_time_ > 0) {
|
||||
if(cycles.as_int() >= delay_time_) {
|
||||
delay_time_ = 0;
|
||||
posit_event((int)Event8272::Timer);
|
||||
} else {
|
||||
delay_time_ -= cycles.as_int();
|
||||
}
|
||||
}
|
||||
|
||||
// update seek status of any drives presently seeking
|
||||
if(drives_seeking_) {
|
||||
int drives_left = drives_seeking_;
|
||||
for(int c = 0; c < 4; c++) {
|
||||
if(drives_[c].phase == Drive::Seeking) {
|
||||
drives_[c].step_rate_counter += cycles.as_int();
|
||||
int steps = drives_[c].step_rate_counter / (8000 * step_rate_time_);
|
||||
drives_[c].step_rate_counter %= (8000 * step_rate_time_);
|
||||
while(steps--) {
|
||||
// Perform a step.
|
||||
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);
|
||||
drives_[c].drive->step(direction);
|
||||
if(drives_[c].target_head_position >= 0) drives_[c].head_position += direction;
|
||||
|
||||
// Check for completion.
|
||||
if(drives_[c].seek_is_satisfied()) {
|
||||
drives_[c].phase = Drive::CompletedSeeking;
|
||||
drives_seeking_--;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
drives_left--;
|
||||
if(!drives_left) break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// check for any head unloads
|
||||
if(head_timers_running_) {
|
||||
int timers_left = head_timers_running_;
|
||||
for(int c = 0; c < 8; c++) {
|
||||
int drive = (c >> 1);
|
||||
int head = c&1;
|
||||
|
||||
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_--;
|
||||
} else {
|
||||
drives_[drive].head_unload_delay[head] -= cycles.as_int();
|
||||
}
|
||||
timers_left--;
|
||||
if(!timers_left) break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
is_sleeping_ = !delay_time_ && !drives_seeking_ && !head_timers_running_;
|
||||
if(is_sleeping_) update_sleep_observer();
|
||||
}
|
||||
|
||||
void i8272::set_register(int address, uint8_t value) {
|
||||
// don't consider attempted sets to the status register
|
||||
if(!address) return;
|
||||
|
||||
// if not ready for commands, do nothing
|
||||
if(!DataRequest() || DataDirectionToProcessor()) return;
|
||||
|
||||
if(expects_input_) {
|
||||
input_ = value;
|
||||
has_input_ = true;
|
||||
ResetDataRequest();
|
||||
} else {
|
||||
// accumulate latest byte in the command byte sequence
|
||||
command_.push_back(value);
|
||||
posit_event((int)Event8272::CommandByte);
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t i8272::get_register(int address) {
|
||||
if(address) {
|
||||
if(result_stack_.empty()) return 0xff;
|
||||
uint8_t result = result_stack_.back();
|
||||
result_stack_.pop_back();
|
||||
if(result_stack_.empty()) posit_event((int)Event8272::ResultEmpty);
|
||||
|
||||
return result;
|
||||
} else {
|
||||
return main_status_;
|
||||
}
|
||||
}
|
||||
|
||||
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 END_SECTION() }
|
||||
|
||||
#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_TIME(ms) resume_point_ = __LINE__; interesting_event_mask_ = (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 CONCAT(x, y) PASTE(x, y)
|
||||
|
||||
#define FIND_HEADER() \
|
||||
set_data_mode(DataMode::Scanning); \
|
||||
CONCAT(find_header, __LINE__): WAIT_FOR_EVENT((int)Event::Token | (int)Event::IndexHole); \
|
||||
if(event_type == (int)Event::IndexHole) { index_hole_limit_--; } \
|
||||
else if(get_latest_token().type == Token::ID) goto CONCAT(header_found, __LINE__); \
|
||||
\
|
||||
if(index_hole_limit_) goto CONCAT(find_header, __LINE__); \
|
||||
CONCAT(header_found, __LINE__): 0;\
|
||||
|
||||
#define FIND_DATA() \
|
||||
set_data_mode(DataMode::Scanning); \
|
||||
CONCAT(find_data, __LINE__): WAIT_FOR_EVENT((int)Event::Token | (int)Event::IndexHole); \
|
||||
if(event_type == (int)Event::Token) { \
|
||||
if(get_latest_token().type == Token::Byte || get_latest_token().type == Token::Sync) goto CONCAT(find_data, __LINE__); \
|
||||
}
|
||||
|
||||
#define READ_HEADER() \
|
||||
distance_into_section_ = 0; \
|
||||
set_data_mode(DataMode::Reading); \
|
||||
CONCAT(read_header, __LINE__): WAIT_FOR_EVENT(Event::Token); \
|
||||
header_[distance_into_section_] = get_latest_token().byte_value; \
|
||||
distance_into_section_++; \
|
||||
if(distance_into_section_ < 6) goto CONCAT(read_header, __LINE__); \
|
||||
|
||||
#define SET_DRIVE_HEAD_MFM() \
|
||||
active_drive_ = command_[1]&3; \
|
||||
active_head_ = (command_[1] >> 2)&1; \
|
||||
set_drive(drives_[active_drive_].drive); \
|
||||
drives_[active_drive_].drive->set_head((unsigned int)active_head_); \
|
||||
set_is_double_density(command_[0] & 0x40); \
|
||||
invalidate_track();
|
||||
|
||||
#define WAIT_FOR_BYTES(n) \
|
||||
distance_into_section_ = 0; \
|
||||
CONCAT(wait_bytes, __LINE__): WAIT_FOR_EVENT(Event::Token); \
|
||||
if(get_latest_token().type == Token::Byte) distance_into_section_++; \
|
||||
if(distance_into_section_ < (n)) goto CONCAT(wait_bytes, __LINE__);
|
||||
|
||||
#define LOAD_HEAD() \
|
||||
if(!drives_[active_drive_].head_is_loaded[active_head_]) { \
|
||||
drives_[active_drive_].head_is_loaded[active_head_] = true; \
|
||||
WAIT_FOR_TIME(head_load_time_); \
|
||||
} else { \
|
||||
if(drives_[active_drive_].head_unload_delay[active_head_] > 0) { \
|
||||
drives_[active_drive_].head_unload_delay[active_head_] = 0; \
|
||||
head_timers_running_--; \
|
||||
} \
|
||||
}
|
||||
|
||||
#define SCHEDULE_HEAD_UNLOAD() \
|
||||
if(drives_[active_drive_].head_is_loaded[active_head_]) {\
|
||||
if(drives_[active_drive_].head_unload_delay[active_head_] == 0) { \
|
||||
head_timers_running_++; \
|
||||
is_sleeping_ = false; \
|
||||
update_sleep_observer(); \
|
||||
} \
|
||||
drives_[active_drive_].head_unload_delay[active_head_] = MS_TO_CYCLES(head_unload_time_);\
|
||||
}
|
||||
|
||||
void i8272::posit_event(int event_type) {
|
||||
if(event_type == (int)Event::IndexHole) index_hole_count_++;
|
||||
if(!(interesting_event_mask_ & event_type)) return;
|
||||
interesting_event_mask_ &= ~event_type;
|
||||
|
||||
BEGIN_SECTION();
|
||||
|
||||
// Resets busy and non-DMA execution, clears the command buffer, sets the data mode to scanning and flows
|
||||
// into wait_for_complete_command_sequence.
|
||||
wait_for_command:
|
||||
expects_input_ = false;
|
||||
set_data_mode(Storage::Disk::MFMController::DataMode::Scanning);
|
||||
ResetBusy();
|
||||
ResetNonDMAExecution();
|
||||
command_.clear();
|
||||
|
||||
// Sets the data request bit, and waits for a byte. Then sets the busy bit. Continues accepting bytes
|
||||
// until it has a quantity that make up an entire command, then resets the data request bit and
|
||||
// branches to that command.
|
||||
wait_for_complete_command_sequence:
|
||||
SetDataRequest();
|
||||
SetDataDirectionFromProcessor();
|
||||
WAIT_FOR_EVENT(Event8272::CommandByte)
|
||||
SetBusy();
|
||||
|
||||
static const size_t required_lengths[32] = {
|
||||
0, 0, 9, 3, 2, 9, 9, 2,
|
||||
1, 9, 2, 0, 9, 6, 0, 3,
|
||||
0, 9, 0, 0, 0, 0, 0, 0,
|
||||
0, 9, 0, 0, 0, 9, 0, 0,
|
||||
};
|
||||
|
||||
if(command_.size() < required_lengths[command_[0] & 0x1f]) goto wait_for_complete_command_sequence;
|
||||
if(command_.size() == 9) {
|
||||
cylinder_ = command_[2];
|
||||
head_ = command_[3];
|
||||
sector_ = command_[4];
|
||||
size_ = command_[5];
|
||||
}
|
||||
ResetDataRequest();
|
||||
status_[0] = status_[1] = status_[2] = 0;
|
||||
|
||||
// If this is not clearly a command that's safe to carry out in parallel to a seek, end all seeks.
|
||||
switch(command_[0] & 0x1f) {
|
||||
case CommandReadData:
|
||||
case CommandReadDeletedData:
|
||||
case CommandWriteData:
|
||||
case CommandWriteDeletedData:
|
||||
case CommandReadTrack:
|
||||
case CommandReadID:
|
||||
case CommandFormatTrack:
|
||||
case CommandScanLow:
|
||||
case CommandScanLowOrEqual:
|
||||
case CommandScanHighOrEqual:
|
||||
is_access_command_ = true;
|
||||
break;
|
||||
|
||||
default:
|
||||
is_access_command_ = false;
|
||||
break;
|
||||
}
|
||||
|
||||
if(is_access_command_) {
|
||||
for(int c = 0; c < 4; c++) {
|
||||
if(drives_[c].phase == Drive::Seeking) {
|
||||
drives_[c].phase = Drive::NotSeeking;
|
||||
drives_seeking_--;
|
||||
}
|
||||
}
|
||||
// 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.
|
||||
if(!dma_mode_) SetNonDMAExecution();
|
||||
SET_DRIVE_HEAD_MFM();
|
||||
LOAD_HEAD();
|
||||
}
|
||||
|
||||
// Jump to the proper place.
|
||||
switch(command_[0] & 0x1f) {
|
||||
case CommandReadData:
|
||||
case CommandReadDeletedData:
|
||||
goto read_data;
|
||||
|
||||
case CommandWriteData:
|
||||
case CommandWriteDeletedData:
|
||||
goto write_data;
|
||||
|
||||
case CommandReadTrack: goto read_track;
|
||||
case CommandReadID: goto read_id;
|
||||
case CommandFormatTrack: goto format_track;
|
||||
|
||||
case CommandScanLow: goto scan_low;
|
||||
case CommandScanLowOrEqual: goto scan_low_or_equal;
|
||||
case CommandScanHighOrEqual: goto scan_high_or_equal;
|
||||
|
||||
case CommandRecalibrate: goto recalibrate;
|
||||
case CommandSeek: goto seek;
|
||||
|
||||
case CommandSenseInterruptStatus: goto sense_interrupt_status;
|
||||
case CommandSpecify: goto specify;
|
||||
case CommandSenseDriveStatus: goto sense_drive_status;
|
||||
|
||||
default: goto invalid;
|
||||
}
|
||||
|
||||
// Decodes drive, head and density, loads the head, loads the internal cylinder, head, sector and size registers,
|
||||
// and searches for a sector that meets those criteria. If one is found, inspects the instruction in use and
|
||||
// jumps to an appropriate handler.
|
||||
read_write_find_header:
|
||||
|
||||
// Sets a maximum index hole limit of 2 then performs a find header/read header loop, continuing either until
|
||||
// the index hole limit is breached or a sector is found with a cylinder, head, sector and size equal to the
|
||||
// values in the internal registers.
|
||||
index_hole_limit_ = 2;
|
||||
// printf("Seeking %02x %02x %02x %02x\n", cylinder_, head_, sector_, size_);
|
||||
find_next_sector:
|
||||
FIND_HEADER();
|
||||
if(!index_hole_limit_) {
|
||||
// Two index holes have passed wihout finding the header sought.
|
||||
// printf("Not found\n");
|
||||
SetNoData();
|
||||
goto abort;
|
||||
}
|
||||
index_hole_count_ = 0;
|
||||
// printf("Header\n");
|
||||
READ_HEADER();
|
||||
if(index_hole_count_) {
|
||||
// This implies an index hole was sighted within the header. Error out.
|
||||
SetEndOfCylinder();
|
||||
goto abort;
|
||||
}
|
||||
if(get_crc_generator().get_value()) {
|
||||
// This implies a CRC error in the header; mark as such but continue.
|
||||
SetDataError();
|
||||
}
|
||||
// printf("Considering %02x %02x %02x %02x [%04x]\n", header_[0], header_[1], header_[2], header_[3], get_crc_generator().get_value());
|
||||
if(header_[0] != cylinder_ || header_[1] != head_ || header_[2] != sector_ || header_[3] != size_) goto find_next_sector;
|
||||
|
||||
// Branch to whatever is supposed to happen next
|
||||
// printf("Proceeding\n");
|
||||
switch(command_[0] & 0x1f) {
|
||||
case CommandReadData:
|
||||
case CommandReadDeletedData:
|
||||
goto read_data_found_header;
|
||||
|
||||
case CommandWriteData: // write data
|
||||
case CommandWriteDeletedData: // write deleted data
|
||||
goto write_data_found_header;
|
||||
}
|
||||
|
||||
|
||||
// Performs the read data or read deleted data command.
|
||||
read_data:
|
||||
printf("Read [deleted] data [%02x %02x %02x %02x ... %02x %02x]\n", command_[2], command_[3], command_[4], command_[5], command_[6], command_[8]);
|
||||
read_next_data:
|
||||
goto read_write_find_header;
|
||||
|
||||
// Finds the next data block and sets data mode to reading, setting an error flag if the on-disk deleted
|
||||
// flag doesn't match the sort the command was looking for.
|
||||
read_data_found_header:
|
||||
FIND_DATA();
|
||||
if(event_type == (int)Event::Token) {
|
||||
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.
|
||||
SetMissingAddressMark();
|
||||
SetMissingDataAddressMark();
|
||||
goto abort; // TODO: or read_next_data?
|
||||
} else {
|
||||
if((get_latest_token().type == Token::Data) != ((command_[0] & 0x1f) == CommandReadData)) {
|
||||
if(!(command_[0]&0x20)) {
|
||||
// SK is not set; set the error flag but read this sector before finishing.
|
||||
SetControlMark();
|
||||
} else {
|
||||
// SK is set; skip this sector.
|
||||
goto read_next_data;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// An index hole appeared before the data mark.
|
||||
SetEndOfCylinder();
|
||||
goto abort; // TODO: or read_next_data?
|
||||
}
|
||||
|
||||
distance_into_section_ = 0;
|
||||
set_data_mode(Reading);
|
||||
|
||||
// Waits for the next token, then supplies it to the CPU by: (i) setting data request and direction; and (ii) resetting
|
||||
// data request once the byte has been taken. Continues until all bytes have been read.
|
||||
//
|
||||
// TODO: consider DTL.
|
||||
read_data_get_byte:
|
||||
WAIT_FOR_EVENT((int)Event::Token | (int)Event::IndexHole);
|
||||
if(event_type == (int)Event::Token) {
|
||||
result_stack_.push_back(get_latest_token().byte_value);
|
||||
distance_into_section_++;
|
||||
SetDataRequest();
|
||||
SetDataDirectionToProcessor();
|
||||
WAIT_FOR_EVENT((int)Event8272::ResultEmpty | (int)Event::Token | (int)Event::IndexHole);
|
||||
}
|
||||
switch(event_type) {
|
||||
case (int)Event8272::ResultEmpty: // The caller read the byte in time; proceed as normal.
|
||||
ResetDataRequest();
|
||||
if(distance_into_section_ < (128 << size_)) goto read_data_get_byte;
|
||||
break;
|
||||
case (int)Event::Token: // The caller hasn't read the old byte yet and a new one has arrived
|
||||
SetOverrun();
|
||||
goto abort;
|
||||
break;
|
||||
case (int)Event::IndexHole:
|
||||
SetEndOfCylinder();
|
||||
goto abort;
|
||||
break;
|
||||
}
|
||||
|
||||
// read CRC, without transferring it, then check it
|
||||
WAIT_FOR_EVENT(Event::Token);
|
||||
WAIT_FOR_EVENT(Event::Token);
|
||||
if(get_crc_generator().get_value()) {
|
||||
// This implies a CRC error in the sector; mark as such and temrinate.
|
||||
SetDataError();
|
||||
SetDataFieldDataError();
|
||||
goto abort;
|
||||
}
|
||||
|
||||
// check whether that's it: either the final requested sector has been read, or because
|
||||
// a sector that was [/wasn't] marked as deleted when it shouldn't [/should] have been
|
||||
if(sector_ != command_[6] && !ControlMark()) {
|
||||
sector_++;
|
||||
goto read_next_data;
|
||||
}
|
||||
|
||||
// For a final result phase, post the standard ST0, ST1, ST2, C, H, R, N
|
||||
goto post_st012chrn;
|
||||
|
||||
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]);
|
||||
|
||||
if(drives_[active_drive_].drive->get_is_read_only()) {
|
||||
SetNotWriteable();
|
||||
goto abort;
|
||||
}
|
||||
|
||||
write_next_data:
|
||||
goto read_write_find_header;
|
||||
|
||||
write_data_found_header:
|
||||
WAIT_FOR_BYTES(get_is_double_density() ? 22 : 11);
|
||||
begin_writing(true);
|
||||
|
||||
write_id_data_joiner((command_[0] & 0x1f) == CommandWriteDeletedData, true);
|
||||
|
||||
SetDataDirectionFromProcessor();
|
||||
SetDataRequest();
|
||||
expects_input_ = true;
|
||||
distance_into_section_ = 0;
|
||||
|
||||
write_loop:
|
||||
WAIT_FOR_EVENT(Event::DataWritten);
|
||||
if(!has_input_) {
|
||||
SetOverrun();
|
||||
end_writing();
|
||||
goto abort;
|
||||
}
|
||||
write_byte(input_);
|
||||
has_input_ = false;
|
||||
distance_into_section_++;
|
||||
if(distance_into_section_ < (128 << size_)) {
|
||||
SetDataRequest();
|
||||
goto write_loop;
|
||||
}
|
||||
|
||||
printf("Wrote %d bytes\n", distance_into_section_);
|
||||
write_crc();
|
||||
expects_input_ = false;
|
||||
WAIT_FOR_EVENT(Event::DataWritten);
|
||||
end_writing();
|
||||
|
||||
if(sector_ != command_[6]) {
|
||||
sector_++;
|
||||
goto write_next_data;
|
||||
}
|
||||
|
||||
goto post_st012chrn;
|
||||
|
||||
// Performs the read ID command.
|
||||
read_id:
|
||||
// Establishes the drive and head being addressed, and whether in double density mode.
|
||||
printf("Read ID [%02x %02x]\n", command_[0], command_[1]);
|
||||
|
||||
// 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.
|
||||
index_hole_limit_ = 2;
|
||||
read_id_find_next_sector:
|
||||
FIND_HEADER();
|
||||
if(!index_hole_limit_) {
|
||||
SetMissingAddressMark();
|
||||
goto abort;
|
||||
}
|
||||
READ_HEADER();
|
||||
|
||||
// Sets internal registers from the discovered header and posts the standard ST0, ST1, ST2, C, H, R, N.
|
||||
cylinder_ = header_[0];
|
||||
head_ = header_[1];
|
||||
sector_ = header_[2];
|
||||
size_ = header_[3];
|
||||
|
||||
goto post_st012chrn;
|
||||
|
||||
// Performs read track.
|
||||
read_track:
|
||||
printf("Read track [%02x %02x %02x %02x]\n", command_[2], command_[3], command_[4], command_[5]);
|
||||
|
||||
// Wait for the index hole.
|
||||
WAIT_FOR_EVENT(Event::IndexHole);
|
||||
|
||||
sector_ = 0;
|
||||
index_hole_limit_ = 2;
|
||||
|
||||
// While not index hole again, stream all sector contents until EOT sectors have been read.
|
||||
read_track_next_sector:
|
||||
FIND_HEADER();
|
||||
if(!index_hole_limit_) {
|
||||
if(!sector_) {
|
||||
SetMissingAddressMark();
|
||||
goto abort;
|
||||
} else {
|
||||
goto post_st012chrn;
|
||||
}
|
||||
}
|
||||
READ_HEADER();
|
||||
|
||||
FIND_DATA();
|
||||
distance_into_section_ = 0;
|
||||
SetDataDirectionToProcessor();
|
||||
read_track_get_byte:
|
||||
WAIT_FOR_EVENT(Event::Token);
|
||||
result_stack_.push_back(get_latest_token().byte_value);
|
||||
distance_into_section_++;
|
||||
SetDataRequest();
|
||||
// TODO: other possible exit conditions; find a way to merge with the read_data version of this.
|
||||
WAIT_FOR_EVENT((int)Event8272::ResultEmpty);
|
||||
ResetDataRequest();
|
||||
if(distance_into_section_ < (128 << header_[2])) goto read_track_get_byte;
|
||||
|
||||
sector_++;
|
||||
if(sector_ < command_[6]) goto read_track_next_sector;
|
||||
|
||||
goto post_st012chrn;
|
||||
|
||||
// Performs format [/write] track.
|
||||
format_track:
|
||||
printf("Format track\n");
|
||||
if(drives_[active_drive_].drive->get_is_read_only()) {
|
||||
SetNotWriteable();
|
||||
goto abort;
|
||||
}
|
||||
|
||||
// Wait for the index hole.
|
||||
WAIT_FOR_EVENT(Event::IndexHole);
|
||||
index_hole_count_ = 0;
|
||||
begin_writing(true);
|
||||
|
||||
// Write start-of-track.
|
||||
write_start_of_track();
|
||||
WAIT_FOR_EVENT(Event::DataWritten);
|
||||
sector_ = 0;
|
||||
|
||||
format_track_write_sector:
|
||||
write_id_joiner();
|
||||
|
||||
// Write the sector header, obtaining its contents
|
||||
// from the processor.
|
||||
SetDataDirectionFromProcessor();
|
||||
SetDataRequest();
|
||||
expects_input_ = true;
|
||||
distance_into_section_ = 0;
|
||||
format_track_write_header:
|
||||
WAIT_FOR_EVENT((int)Event::DataWritten | (int)Event::IndexHole);
|
||||
switch(event_type) {
|
||||
case (int)Event::IndexHole:
|
||||
SetOverrun();
|
||||
end_writing();
|
||||
goto abort;
|
||||
break;
|
||||
case (int)Event::DataWritten:
|
||||
header_[distance_into_section_] = input_;
|
||||
write_byte(input_);
|
||||
has_input_ = false;
|
||||
distance_into_section_++;
|
||||
if(distance_into_section_ < 4) {
|
||||
SetDataRequest();
|
||||
goto format_track_write_header;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
printf("W: %02x %02x %02x %02x, %04x\n", header_[0], header_[1], header_[2], header_[3], get_crc_generator().get_value());
|
||||
write_crc();
|
||||
|
||||
// Write the sector body.
|
||||
write_id_data_joiner(false, false);
|
||||
write_n_bytes(128 << command_[2], command_[5]);
|
||||
write_crc();
|
||||
|
||||
// Write the prescribed gap.
|
||||
write_n_bytes(command_[4], get_is_double_density() ? 0x4e : 0xff);
|
||||
|
||||
// Consider repeating.
|
||||
sector_++;
|
||||
if(sector_ < command_[3] && !index_hole_count_)
|
||||
goto format_track_write_sector;
|
||||
|
||||
// Otherwise, pad out to the index hole.
|
||||
format_track_pad:
|
||||
write_byte(get_is_double_density() ? 0x4e : 0xff);
|
||||
WAIT_FOR_EVENT((int)Event::DataWritten | (int)Event::IndexHole);
|
||||
if(event_type != (int)Event::IndexHole) goto format_track_pad;
|
||||
|
||||
end_writing();
|
||||
|
||||
cylinder_ = header_[0];
|
||||
head_ = header_[1];
|
||||
sector_ = header_[2] + 1;
|
||||
size_ = header_[3];
|
||||
|
||||
goto post_st012chrn;
|
||||
|
||||
scan_low:
|
||||
printf("Scan low unimplemented!!\n");
|
||||
goto wait_for_command;
|
||||
|
||||
scan_low_or_equal:
|
||||
printf("Scan low or equal unimplemented!!\n");
|
||||
goto wait_for_command;
|
||||
|
||||
scan_high_or_equal:
|
||||
printf("Scan high or equal unimplemented!!\n");
|
||||
goto wait_for_command;
|
||||
|
||||
// Performs both recalibrate and seek commands. These commands occur asynchronously, so the actual work
|
||||
// occurs in ::run_for; this merely establishes that seeking should be ongoing.
|
||||
recalibrate:
|
||||
seek:
|
||||
{
|
||||
int drive = command_[1]&3;
|
||||
|
||||
// Increment the seeking count if this drive wasn't already seeking.
|
||||
if(drives_[drive].phase != Drive::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
|
||||
// in could damage your drive motor).
|
||||
drives_[drive].phase = Drive::Seeking;
|
||||
drives_[drive].step_rate_counter = 8000 * step_rate_time_;
|
||||
drives_[drive].steps_taken = 0;
|
||||
drives_[drive].seek_failed = false;
|
||||
main_status_ |= 1 << (command_[1]&3);
|
||||
|
||||
// If this is a seek, set the processor-supplied target location; otherwise it is a recalibrate,
|
||||
// which means resetting the current state now but aiming to hit '-1' (which the stepping code
|
||||
// up in run_for understands to mean 'keep going until track 0 is active').
|
||||
if(command_.size() > 2) {
|
||||
drives_[drive].target_head_position = command_[2];
|
||||
printf("Seek to %02x\n", command_[2]);
|
||||
} else {
|
||||
drives_[drive].target_head_position = -1;
|
||||
drives_[drive].head_position = 0;
|
||||
printf("Recalibrate\n");
|
||||
}
|
||||
|
||||
// Check whether any steps are even needed; if not then mark as completed already.
|
||||
if(drives_[drive].seek_is_satisfied()) {
|
||||
drives_[drive].phase = Drive::CompletedSeeking;
|
||||
drives_seeking_--;
|
||||
}
|
||||
}
|
||||
goto wait_for_command;
|
||||
|
||||
// Performs sense interrupt status.
|
||||
sense_interrupt_status:
|
||||
printf("Sense interrupt status\n");
|
||||
{
|
||||
// Find the first drive that is in the CompletedSeeking state.
|
||||
int found_drive = -1;
|
||||
for(int c = 0; c < 4; c++) {
|
||||
if(drives_[c].phase == Drive::CompletedSeeking) {
|
||||
found_drive = c;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// If a drive was found, return its results. Otherwise return a single 0x80.
|
||||
if(found_drive != -1) {
|
||||
drives_[found_drive].phase = Drive::NotSeeking;
|
||||
status_[0] = (uint8_t)found_drive;
|
||||
main_status_ &= ~(1 << found_drive);
|
||||
SetSeekEnd();
|
||||
|
||||
result_stack_.push_back(drives_[found_drive].head_position);
|
||||
result_stack_.push_back(status_[0]);
|
||||
} else {
|
||||
result_stack_.push_back(0x80);
|
||||
}
|
||||
}
|
||||
goto post_result;
|
||||
|
||||
// Performs specify.
|
||||
specify:
|
||||
// Just store the values, and terminate the command.
|
||||
printf("Specify\n");
|
||||
step_rate_time_ = 16 - (command_[1] >> 4); // i.e. 1 to 16ms
|
||||
head_unload_time_ = (command_[1] & 0x0f) << 4; // i.e. 16 to 240ms
|
||||
head_load_time_ = command_[2] & ~1; // i.e. 2 to 254 ms in increments of 2ms
|
||||
|
||||
if(!head_unload_time_) head_unload_time_ = 16;
|
||||
if(!head_load_time_) head_load_time_ = 2;
|
||||
dma_mode_ = !(command_[2] & 1);
|
||||
goto wait_for_command;
|
||||
|
||||
sense_drive_status:
|
||||
printf("Sense drive status\n");
|
||||
{
|
||||
int drive = command_[1] & 3;
|
||||
result_stack_.push_back(
|
||||
(command_[1] & 7) | // drive and head number
|
||||
0x08 | // single sided
|
||||
(drives_[drive].drive->get_is_track_zero() ? 0x10 : 0x00) |
|
||||
(drives_[drive].drive->get_is_ready() ? 0x20 : 0x00) |
|
||||
(drives_[drive].drive->get_is_read_only() ? 0x40 : 0x00)
|
||||
);
|
||||
}
|
||||
goto post_result;
|
||||
|
||||
// Performs any invalid command.
|
||||
invalid:
|
||||
// A no-op, but posts ST0 (but which ST0?)
|
||||
result_stack_.push_back(0x80);
|
||||
goto post_result;
|
||||
|
||||
// Sets abnormal termination of the current command and proceeds to an ST0, ST1, ST2, C, H, R, N result phase.
|
||||
abort:
|
||||
SetAbnormalTermination();
|
||||
goto post_st012chrn;
|
||||
|
||||
// Posts ST0, ST1, ST2, C, H, R and N as a result phase.
|
||||
post_st012chrn:
|
||||
SCHEDULE_HEAD_UNLOAD();
|
||||
|
||||
result_stack_.push_back(size_);
|
||||
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;
|
||||
|
||||
// Posts whatever is in result_stack_ as a result phase. Be aware that it is a stack — the
|
||||
// last thing in it will be returned first.
|
||||
post_result:
|
||||
printf("Result to %02x, main %02x: ", command_[0] & 0x1f, main_status_);
|
||||
for(size_t c = 0; c < result_stack_.size(); c++) {
|
||||
printf("%02x ", result_stack_[result_stack_.size() - 1 - c]);
|
||||
}
|
||||
printf("\n");
|
||||
|
||||
// Set ready to send data to the processor, no longer in non-DMA execution phase.
|
||||
ResetNonDMAExecution();
|
||||
SetDataRequest();
|
||||
SetDataDirectionToProcessor();
|
||||
|
||||
// The actual stuff of unwinding result_stack_ is handled by ::get_register; wait
|
||||
// until the processor has read all result bytes.
|
||||
WAIT_FOR_EVENT(Event8272::ResultEmpty);
|
||||
|
||||
// Reset data direction and end the command.
|
||||
goto wait_for_command;
|
||||
|
||||
END_SECTION()
|
||||
}
|
||||
|
||||
bool i8272::Drive::seek_is_satisfied() {
|
||||
return (target_head_position == head_position) ||
|
||||
(target_head_position == -1 && drive->get_is_track_zero());
|
||||
}
|
||||
|
||||
void i8272::set_dma_acknowledge(bool dack) {
|
||||
}
|
||||
|
||||
void i8272::set_terminal_count(bool tc) {
|
||||
}
|
||||
|
||||
void i8272::set_data_input(uint8_t value) {
|
||||
}
|
||||
|
||||
uint8_t i8272::get_data_output() {
|
||||
return 0xff;
|
||||
}
|
||||
140
Components/8272/i8272.hpp
Normal file
140
Components/8272/i8272.hpp
Normal file
@@ -0,0 +1,140 @@
|
||||
//
|
||||
// i8272.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 05/08/2017.
|
||||
// Copyright © 2017 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef i8272_hpp
|
||||
#define i8272_hpp
|
||||
|
||||
#include "../../Storage/Disk/MFMDiskController.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
namespace Intel {
|
||||
namespace i8272 {
|
||||
|
||||
class BusHandler {
|
||||
public:
|
||||
virtual void set_dma_data_request(bool drq) {}
|
||||
virtual void set_interrupt(bool irq) {}
|
||||
};
|
||||
|
||||
class i8272: public Storage::Disk::MFMController {
|
||||
public:
|
||||
i8272(BusHandler &bus_handler, Cycles clock_rate, int clock_rate_multiplier, int revolutions_per_minute);
|
||||
|
||||
void run_for(Cycles);
|
||||
|
||||
void set_data_input(uint8_t value);
|
||||
uint8_t get_data_output();
|
||||
|
||||
void set_register(int address, uint8_t value);
|
||||
uint8_t get_register(int address);
|
||||
|
||||
void set_dma_acknowledge(bool dack);
|
||||
void set_terminal_count(bool tc);
|
||||
|
||||
void set_disk(std::shared_ptr<Storage::Disk::Disk> disk, int drive);
|
||||
|
||||
bool is_sleeping();
|
||||
|
||||
private:
|
||||
// The bus handler, for interrupt and DMA-driven usage.
|
||||
BusHandler &bus_handler_;
|
||||
std::unique_ptr<BusHandler> allocated_bus_handler_;
|
||||
|
||||
// Status registers.
|
||||
uint8_t main_status_;
|
||||
uint8_t status_[3];
|
||||
|
||||
// A buffer for accumulating the incoming command, and one for accumulating the result.
|
||||
std::vector<uint8_t> command_;
|
||||
std::vector<uint8_t> result_stack_;
|
||||
uint8_t input_;
|
||||
bool has_input_;
|
||||
bool expects_input_;
|
||||
|
||||
// Event stream: the 8272-specific events, plus the current event state.
|
||||
enum class Event8272: int {
|
||||
CommandByte = (1 << 3),
|
||||
Timer = (1 << 4),
|
||||
ResultEmpty = (1 << 5),
|
||||
};
|
||||
void posit_event(int type);
|
||||
int interesting_event_mask_;
|
||||
int resume_point_;
|
||||
bool is_access_command_;
|
||||
|
||||
// The counter used for ::Timer events.
|
||||
int delay_time_;
|
||||
|
||||
// The connected drives.
|
||||
struct Drive {
|
||||
uint8_t head_position;
|
||||
|
||||
// Seeking: persistent state.
|
||||
enum Phase {
|
||||
NotSeeking,
|
||||
Seeking,
|
||||
CompletedSeeking
|
||||
} phase;
|
||||
bool did_seek;
|
||||
bool seek_failed;
|
||||
|
||||
// Seeking: transient state.
|
||||
int step_rate_counter;
|
||||
int steps_taken;
|
||||
int target_head_position; // 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.
|
||||
int head_unload_delay[2];
|
||||
bool head_is_loaded[2];
|
||||
|
||||
// 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},
|
||||
head_unload_delay{0, 0} {};
|
||||
} drives_[4];
|
||||
int drives_seeking_;
|
||||
|
||||
// User-supplied parameters; as per the specify command.
|
||||
int step_rate_time_;
|
||||
int head_unload_time_;
|
||||
int head_load_time_;
|
||||
bool dma_mode_;
|
||||
|
||||
// A count of head unload timers currently running.
|
||||
int head_timers_running_;
|
||||
|
||||
// Transient storage and counters used while reading the disk.
|
||||
uint8_t header_[6];
|
||||
int distance_into_section_;
|
||||
int index_hole_count_, index_hole_limit_;
|
||||
|
||||
// Keeps track of the drive and head in use during commands.
|
||||
int active_drive_;
|
||||
int active_head_;
|
||||
|
||||
// Internal registers.
|
||||
uint8_t cylinder_, head_, sector_, size_;
|
||||
|
||||
// Master switch on not performing any work.
|
||||
bool is_sleeping_;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* i8272_hpp */
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
#include "AY38910.hpp"
|
||||
|
||||
using namespace GI;
|
||||
using namespace GI::AY38910;
|
||||
|
||||
AY38910::AY38910() :
|
||||
selected_register_(0),
|
||||
@@ -16,7 +16,8 @@ AY38910::AY38910() :
|
||||
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} {
|
||||
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
|
||||
@@ -76,7 +77,7 @@ void AY38910::set_clock_rate(double clock_rate) {
|
||||
}
|
||||
|
||||
void AY38910::get_samples(unsigned int number_of_samples, int16_t *target) {
|
||||
int c = 0;
|
||||
unsigned int c = 0;
|
||||
while((master_divider_&7) && c < number_of_samples) {
|
||||
target[c] = output_volume_;
|
||||
master_divider_++;
|
||||
@@ -166,11 +167,14 @@ void AY38910::evaluate_output_volume() {
|
||||
);
|
||||
}
|
||||
|
||||
#pragma mark - Register manipulation
|
||||
|
||||
void AY38910::select_register(uint8_t r) {
|
||||
selected_register_ = r & 0xf;
|
||||
selected_register_ = r;
|
||||
}
|
||||
|
||||
void AY38910::set_register_value(uint8_t value) {
|
||||
if(selected_register_ > 15) return;
|
||||
registers_[selected_register_] = value;
|
||||
if(selected_register_ < 14) {
|
||||
int selected_register = selected_register_;
|
||||
@@ -185,23 +189,19 @@ void AY38910::set_register_value(uint8_t value) {
|
||||
tone_periods_[channel] = (tone_periods_[channel] & 0xff) | (uint16_t)((value&0xf) << 8);
|
||||
else
|
||||
tone_periods_[channel] = (tone_periods_[channel] & ~0xff) | value;
|
||||
tone_counters_[channel] = tone_periods_[channel];
|
||||
}
|
||||
break;
|
||||
|
||||
case 6:
|
||||
noise_period_ = value & 0x1f;
|
||||
noise_counter_ = noise_period_;
|
||||
break;
|
||||
|
||||
case 11:
|
||||
envelope_period_ = (envelope_period_ & ~0xff) | value;
|
||||
envelope_divider_ = envelope_period_;
|
||||
break;
|
||||
|
||||
case 12:
|
||||
envelope_period_ = (envelope_period_ & 0xff) | (int)(value << 8);
|
||||
envelope_divider_ = envelope_period_;
|
||||
break;
|
||||
|
||||
case 13:
|
||||
@@ -212,53 +212,75 @@ void AY38910::set_register_value(uint8_t value) {
|
||||
output_registers_[selected_register] = masked_value;
|
||||
evaluate_output_volume();
|
||||
});
|
||||
} else {
|
||||
if(port_handler_) port_handler_->set_port_output(selected_register_ == 15, value);
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t AY38910::get_register_value() {
|
||||
// This table ensures that bits that aren't defined within the AY are returned as 1s
|
||||
// when read. I can't find documentation on this and don't have a machine to test, so
|
||||
// this is provisionally a guess. TODO: investigate.
|
||||
// This table ensures that bits that aren't defined within the AY are returned as 0s
|
||||
// when read, conforming to CPC-sourced unit tests.
|
||||
const uint8_t register_masks[16] = {
|
||||
0x00, 0xf0, 0x00, 0xf0, 0x00, 0xf0, 0xe0, 0x00,
|
||||
0xe0, 0xe0, 0xe0, 0x00, 0x00, 0xf0, 0x00, 0x00
|
||||
0xff, 0x0f, 0xff, 0x0f, 0xff, 0x0f, 0x1f, 0xff,
|
||||
0x1f, 0x1f, 0x1f, 0xff, 0xff, 0x0f, 0xff, 0xff
|
||||
};
|
||||
|
||||
return registers_[selected_register_] | register_masks[selected_register_];
|
||||
if(selected_register_ > 15) return 0xff;
|
||||
switch(selected_register_) {
|
||||
default: return registers_[selected_register_] & register_masks[selected_register_];
|
||||
case 14: return (registers_[0x7] & 0x40) ? registers_[14] : port_inputs_[0];
|
||||
case 15: return (registers_[0x7] & 0x80) ? registers_[15] : port_inputs_[1];
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - Port handling
|
||||
|
||||
uint8_t AY38910::get_port_output(bool port_b) {
|
||||
return registers_[port_b ? 15 : 14];
|
||||
}
|
||||
|
||||
#pragma mark - Bus handling
|
||||
|
||||
void AY38910::set_port_handler(PortHandler *handler) {
|
||||
port_handler_ = handler;
|
||||
}
|
||||
|
||||
void AY38910::set_data_input(uint8_t r) {
|
||||
data_input_ = r;
|
||||
update_bus();
|
||||
}
|
||||
|
||||
uint8_t AY38910::get_data_output() {
|
||||
if(control_state_ == Read && selected_register_ >= 14) {
|
||||
if(port_handler_) {
|
||||
return port_handler_->get_port_input(selected_register_ == 15);
|
||||
} else {
|
||||
return 0xff;
|
||||
}
|
||||
}
|
||||
return data_output_;
|
||||
}
|
||||
|
||||
void AY38910::set_control_lines(ControlLines control_lines) {
|
||||
ControlState new_state;
|
||||
switch((int)control_lines) {
|
||||
default: new_state = Inactive; break;
|
||||
default: control_state_ = Inactive; break;
|
||||
|
||||
case (int)(BCDIR | BC2 | BC1):
|
||||
case BCDIR:
|
||||
case BC1: new_state = LatchAddress; break;
|
||||
case (int)(BDIR | BC2 | BC1):
|
||||
case BDIR:
|
||||
case BC1: control_state_ = LatchAddress; break;
|
||||
|
||||
case (int)(BC2 | BC1): new_state = Read; break;
|
||||
case (int)(BCDIR | BC2): new_state = Write; break;
|
||||
case (int)(BC2 | BC1): control_state_ = Read; break;
|
||||
case (int)(BDIR | BC2): control_state_ = Write; break;
|
||||
}
|
||||
|
||||
if(new_state != control_state_) {
|
||||
control_state_ = new_state;
|
||||
switch(new_state) {
|
||||
default: break;
|
||||
case LatchAddress: select_register(data_input_); break;
|
||||
case Write: set_register_value(data_input_); break;
|
||||
case Read: data_output_ = get_register_value(); break;
|
||||
}
|
||||
update_bus();
|
||||
}
|
||||
|
||||
void AY38910::update_bus() {
|
||||
switch(control_state_) {
|
||||
default: break;
|
||||
case LatchAddress: select_register(data_input_); break;
|
||||
case Write: set_register_value(data_input_); break;
|
||||
case Read: data_output_ = get_register_value(); break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,6 +12,43 @@
|
||||
#include "../../Outputs/Speaker.hpp"
|
||||
|
||||
namespace GI {
|
||||
namespace AY38910 {
|
||||
|
||||
/*!
|
||||
A port handler provides all input for an AY's two 8-bit ports, and may optionally receive
|
||||
active notification of changes in output.
|
||||
|
||||
Machines with an AY without ports or with nothing wired to them need not supply a port handler.
|
||||
Machines that use the AY ports as output but for which polling for changes is acceptable can
|
||||
instead use AY38910.get_port_output.
|
||||
*/
|
||||
class PortHandler {
|
||||
public:
|
||||
/*!
|
||||
Requests the current input on an AY port.
|
||||
|
||||
@param port_b @c true if the input being queried is Port B. @c false if it is Port A.
|
||||
*/
|
||||
virtual uint8_t get_port_input(bool port_b) {
|
||||
return 0xff;
|
||||
}
|
||||
|
||||
/*!
|
||||
Requests the current input on an AY port.
|
||||
|
||||
@param port_b @c true if the input being queried is Port B. @c false if it is Port A.
|
||||
*/
|
||||
virtual void set_port_output(bool port_b, uint8_t value) {}
|
||||
};
|
||||
|
||||
/*!
|
||||
Names the control lines used as input to the AY, which uses CP1600 bus semantics.
|
||||
*/
|
||||
enum ControlLines {
|
||||
BC1 = (1 << 0),
|
||||
BC2 = (1 << 1),
|
||||
BDIR = (1 << 2)
|
||||
};
|
||||
|
||||
/*!
|
||||
Provides emulation of an AY-3-8910 / YM2149, which is a three-channel sound chip with a
|
||||
@@ -26,19 +63,13 @@ class AY38910: public ::Outputs::Filter<AY38910> {
|
||||
/// Sets the clock rate at which this AY38910 will be run.
|
||||
void set_clock_rate(double clock_rate);
|
||||
|
||||
enum ControlLines {
|
||||
BC1 = (1 << 0),
|
||||
BC2 = (1 << 1),
|
||||
BCDIR = (1 << 2)
|
||||
};
|
||||
|
||||
/// Sets the value the AY would read from its data lines if it were not outputting.
|
||||
void set_data_input(uint8_t r);
|
||||
|
||||
/// Gets the value that would appear on the data lines if only the AY is outputting.
|
||||
uint8_t get_data_output();
|
||||
|
||||
/// Sets the
|
||||
/// Sets the current control line state, as a bit field.
|
||||
void set_control_lines(ControlLines control_lines);
|
||||
|
||||
/*!
|
||||
@@ -47,12 +78,20 @@ class AY38910: public ::Outputs::Filter<AY38910> {
|
||||
*/
|
||||
uint8_t get_port_output(bool port_b);
|
||||
|
||||
/*!
|
||||
Sets the port handler, which will receive a call every time the AY either wants to sample
|
||||
input or else declare new output. As a convenience, current port output can be obtained
|
||||
without installing a port handler via get_port_output.
|
||||
*/
|
||||
void set_port_handler(PortHandler *);
|
||||
|
||||
// to satisfy ::Outputs::Speaker (included via ::Outputs::Filter; not for public consumption
|
||||
void get_samples(unsigned int number_of_samples, int16_t *target);
|
||||
|
||||
private:
|
||||
int selected_register_;
|
||||
uint8_t registers_[16], output_registers_[16];
|
||||
uint8_t port_inputs_[2];
|
||||
|
||||
int master_divider_;
|
||||
|
||||
@@ -88,8 +127,12 @@ class AY38910: public ::Outputs::Filter<AY38910> {
|
||||
|
||||
int16_t output_volume_;
|
||||
inline void evaluate_output_volume();
|
||||
|
||||
inline void update_bus();
|
||||
PortHandler *port_handler_;
|
||||
};
|
||||
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* AY_3_8910_hpp */
|
||||
|
||||
@@ -46,6 +46,7 @@ AsyncTaskQueue::AsyncTaskQueue()
|
||||
AsyncTaskQueue::~AsyncTaskQueue() {
|
||||
#ifdef __APPLE__
|
||||
dispatch_release(serial_dispatch_queue_);
|
||||
serial_dispatch_queue_ = nullptr;
|
||||
#else
|
||||
should_destruct_ = true;
|
||||
enqueue([](){});
|
||||
|
||||
@@ -22,11 +22,10 @@ namespace Concurrency {
|
||||
|
||||
/*!
|
||||
An async task queue allows a caller to enqueue void(void) functions. Those functions are guaranteed
|
||||
to be performed serially and asynchronously from the caller. A caller may also request to synchronise,
|
||||
to be performed serially and asynchronously from the caller. A caller may also request to flush,
|
||||
causing it to block until all previously-enqueued functions are complete.
|
||||
*/
|
||||
class AsyncTaskQueue {
|
||||
|
||||
public:
|
||||
AsyncTaskQueue();
|
||||
~AsyncTaskQueue();
|
||||
|
||||
1045
Machines/AmstradCPC/AmstradCPC.cpp
Normal file
1045
Machines/AmstradCPC/AmstradCPC.cpp
Normal file
File diff suppressed because it is too large
Load Diff
66
Machines/AmstradCPC/AmstradCPC.hpp
Normal file
66
Machines/AmstradCPC/AmstradCPC.hpp
Normal file
@@ -0,0 +1,66 @@
|
||||
//
|
||||
// AmstradCPC.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 30/07/2017.
|
||||
// Copyright © 2017 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef AmstradCPC_hpp
|
||||
#define AmstradCPC_hpp
|
||||
|
||||
#include "../ConfigurationTarget.hpp"
|
||||
#include "../CRTMachine.hpp"
|
||||
#include "../KeyboardMachine.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
|
||||
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.
|
||||
*/
|
||||
class Machine:
|
||||
public CRTMachine::Machine,
|
||||
public ConfigurationTarget::Machine,
|
||||
public KeyboardMachine::Machine {
|
||||
public:
|
||||
virtual ~Machine();
|
||||
|
||||
/// Creates and returns an Amstrad CPC.
|
||||
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;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif /* AmstradCPC_hpp */
|
||||
89
Machines/AmstradCPC/CharacterMapper.cpp
Normal file
89
Machines/AmstradCPC/CharacterMapper.cpp
Normal file
@@ -0,0 +1,89 @@
|
||||
//
|
||||
// CharacterMapper.cpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 11/08/2017.
|
||||
// Copyright © 2017 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#include "CharacterMapper.hpp"
|
||||
#include "AmstradCPC.hpp"
|
||||
|
||||
using namespace AmstradCPC;
|
||||
|
||||
uint16_t *CharacterMapper::sequence_for_character(char character) {
|
||||
#define KEYS(...) {__VA_ARGS__, EndSequence}
|
||||
#define SHIFT(...) {KeyShift, __VA_ARGS__, EndSequence}
|
||||
#define X {NotMapped}
|
||||
static KeySequence key_sequences[] = {
|
||||
/* NUL */ X, /* SOH */ X,
|
||||
/* STX */ X, /* ETX */ X,
|
||||
/* EOT */ X, /* ENQ */ X,
|
||||
/* ACK */ X, /* BEL */ X,
|
||||
/* BS */ KEYS(KeyDelete), /* HT */ X,
|
||||
/* LF */ KEYS(KeyReturn), /* VT */ X,
|
||||
/* FF */ X, /* CR */ X,
|
||||
/* SO */ X, /* SI */ X,
|
||||
/* DLE */ X, /* DC1 */ X,
|
||||
/* DC2 */ X, /* DC3 */ X,
|
||||
/* DC4 */ X, /* NAK */ X,
|
||||
/* SYN */ X, /* ETB */ X,
|
||||
/* CAN */ X, /* EM */ X,
|
||||
/* SUB */ X, /* ESC */ X,
|
||||
/* FS */ X, /* GS */ X,
|
||||
/* RS */ X, /* US */ X,
|
||||
/* space */ KEYS(KeySpace), /* ! */ SHIFT(Key1),
|
||||
/* " */ SHIFT(Key2), /* # */ SHIFT(Key3),
|
||||
/* $ */ SHIFT(Key4), /* % */ SHIFT(Key5),
|
||||
/* & */ SHIFT(Key6), /* ' */ SHIFT(Key7),
|
||||
/* ( */ SHIFT(Key8), /* ) */ SHIFT(Key9),
|
||||
/* * */ SHIFT(KeyColon), /* + */ SHIFT(KeySemicolon),
|
||||
/* , */ KEYS(KeyComma), /* - */ KEYS(KeyMinus),
|
||||
/* . */ KEYS(KeyFullStop), /* / */ KEYS(KeyForwardSlash),
|
||||
/* 0 */ KEYS(Key0), /* 1 */ KEYS(Key1),
|
||||
/* 2 */ KEYS(Key2), /* 3 */ KEYS(Key3),
|
||||
/* 4 */ KEYS(Key4), /* 5 */ KEYS(Key5),
|
||||
/* 6 */ KEYS(Key6), /* 7 */ KEYS(Key7),
|
||||
/* 8 */ KEYS(Key8), /* 9 */ KEYS(Key9),
|
||||
/* : */ KEYS(KeyColon), /* ; */ KEYS(KeySemicolon),
|
||||
/* < */ SHIFT(KeyComma), /* = */ SHIFT(KeyMinus),
|
||||
/* > */ SHIFT(KeyFullStop), /* ? */ SHIFT(KeyForwardSlash),
|
||||
/* @ */ SHIFT(KeyAt), /* A */ SHIFT(KeyA),
|
||||
/* B */ SHIFT(KeyB), /* C */ SHIFT(KeyC),
|
||||
/* D */ SHIFT(KeyD), /* E */ SHIFT(KeyE),
|
||||
/* F */ SHIFT(KeyF), /* G */ SHIFT(KeyG),
|
||||
/* H */ SHIFT(KeyH), /* I */ SHIFT(KeyI),
|
||||
/* J */ SHIFT(KeyJ), /* K */ SHIFT(KeyK),
|
||||
/* L */ SHIFT(KeyL), /* M */ SHIFT(KeyM),
|
||||
/* N */ SHIFT(KeyN), /* O */ SHIFT(KeyO),
|
||||
/* P */ SHIFT(KeyP), /* Q */ SHIFT(KeyQ),
|
||||
/* R */ SHIFT(KeyR), /* S */ SHIFT(KeyS),
|
||||
/* T */ SHIFT(KeyT), /* U */ SHIFT(KeyU),
|
||||
/* V */ SHIFT(KeyV), /* W */ SHIFT(KeyW),
|
||||
/* X */ SHIFT(KeyX), /* Y */ SHIFT(KeyY),
|
||||
/* Z */ SHIFT(KeyZ), /* [ */ KEYS(KeyLeftSquareBracket),
|
||||
/* \ */ KEYS(KeyBackSlash), /* ] */ KEYS(KeyRightSquareBracket),
|
||||
/* ^ */ SHIFT(KeyCaret), /* _ */ SHIFT(Key0),
|
||||
/* ` */ X, /* a */ KEYS(KeyA),
|
||||
/* b */ KEYS(KeyB), /* c */ KEYS(KeyC),
|
||||
/* d */ KEYS(KeyD), /* e */ KEYS(KeyE),
|
||||
/* f */ KEYS(KeyF), /* g */ KEYS(KeyG),
|
||||
/* h */ KEYS(KeyH), /* i */ KEYS(KeyI),
|
||||
/* j */ KEYS(KeyJ), /* k */ KEYS(KeyK),
|
||||
/* l */ KEYS(KeyL), /* m */ KEYS(KeyM),
|
||||
/* n */ KEYS(KeyN), /* o */ KEYS(KeyO),
|
||||
/* p */ KEYS(KeyP), /* q */ KEYS(KeyQ),
|
||||
/* r */ KEYS(KeyR), /* s */ KEYS(KeyS),
|
||||
/* t */ KEYS(KeyT), /* u */ KEYS(KeyU),
|
||||
/* v */ KEYS(KeyV), /* w */ KEYS(KeyW),
|
||||
/* x */ KEYS(KeyX), /* y */ KEYS(KeyY),
|
||||
/* z */ KEYS(KeyZ), /* { */ X,
|
||||
/* | */ SHIFT(KeyAt), /* } */ X,
|
||||
/* ~ */ X
|
||||
};
|
||||
#undef KEYS
|
||||
#undef SHIFT
|
||||
#undef X
|
||||
|
||||
return table_lookup_sequence_for_character(key_sequences, sizeof(key_sequences), character);
|
||||
}
|
||||
23
Machines/AmstradCPC/CharacterMapper.hpp
Normal file
23
Machines/AmstradCPC/CharacterMapper.hpp
Normal file
@@ -0,0 +1,23 @@
|
||||
//
|
||||
// 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 */
|
||||
@@ -10,148 +10,200 @@
|
||||
#include <algorithm>
|
||||
#include <stdio.h>
|
||||
|
||||
#include "Cartridges/CartridgeAtari8k.hpp"
|
||||
#include "Cartridges/CartridgeAtari16k.hpp"
|
||||
#include "Cartridges/CartridgeAtari32k.hpp"
|
||||
#include "Cartridges/CartridgeActivisionStack.hpp"
|
||||
#include "Cartridges/CartridgeCBSRAMPlus.hpp"
|
||||
#include "Cartridges/CartridgeCommaVid.hpp"
|
||||
#include "Cartridges/CartridgeMegaBoy.hpp"
|
||||
#include "Cartridges/CartridgeMNetwork.hpp"
|
||||
#include "Cartridges/CartridgeParkerBros.hpp"
|
||||
#include "Cartridges/CartridgePitfall2.hpp"
|
||||
#include "Cartridges/CartridgeTigervision.hpp"
|
||||
#include "Cartridges/CartridgeUnpaged.hpp"
|
||||
#include "Cartridges/Atari8k.hpp"
|
||||
#include "Cartridges/Atari16k.hpp"
|
||||
#include "Cartridges/Atari32k.hpp"
|
||||
#include "Cartridges/ActivisionStack.hpp"
|
||||
#include "Cartridges/CBSRAMPlus.hpp"
|
||||
#include "Cartridges/CommaVid.hpp"
|
||||
#include "Cartridges/MegaBoy.hpp"
|
||||
#include "Cartridges/MNetwork.hpp"
|
||||
#include "Cartridges/ParkerBros.hpp"
|
||||
#include "Cartridges/Pitfall2.hpp"
|
||||
#include "Cartridges/Tigervision.hpp"
|
||||
#include "Cartridges/Unpaged.hpp"
|
||||
|
||||
using namespace Atari2600;
|
||||
namespace {
|
||||
static const double NTSC_clock_rate = 1194720;
|
||||
static const double PAL_clock_rate = 1182298;
|
||||
}
|
||||
|
||||
Machine::Machine() :
|
||||
frame_record_pointer_(0),
|
||||
is_ntsc_(true) {
|
||||
set_clock_rate(NTSC_clock_rate);
|
||||
}
|
||||
namespace Atari2600 {
|
||||
|
||||
void Machine::setup_output(float aspect_ratio) {
|
||||
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();
|
||||
}
|
||||
|
||||
void Machine::set_digital_input(Atari2600DigitalInput input, bool state) {
|
||||
switch (input) {
|
||||
case Atari2600DigitalInputJoy1Up: bus_->mos6532_.update_port_input(0, 0x10, state); break;
|
||||
case Atari2600DigitalInputJoy1Down: bus_->mos6532_.update_port_input(0, 0x20, state); break;
|
||||
case Atari2600DigitalInputJoy1Left: bus_->mos6532_.update_port_input(0, 0x40, state); break;
|
||||
case Atari2600DigitalInputJoy1Right: bus_->mos6532_.update_port_input(0, 0x80, state); break;
|
||||
|
||||
case Atari2600DigitalInputJoy2Up: bus_->mos6532_.update_port_input(0, 0x01, state); break;
|
||||
case Atari2600DigitalInputJoy2Down: bus_->mos6532_.update_port_input(0, 0x02, state); break;
|
||||
case Atari2600DigitalInputJoy2Left: bus_->mos6532_.update_port_input(0, 0x04, state); break;
|
||||
case Atari2600DigitalInputJoy2Right: bus_->mos6532_.update_port_input(0, 0x08, state); break;
|
||||
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
|
||||
void Machine::set_switch_is_enabled(Atari2600Switch input, bool state) {
|
||||
switch(input) {
|
||||
case Atari2600SwitchReset: bus_->mos6532_.update_port_input(1, 0x01, state); break;
|
||||
case Atari2600SwitchSelect: bus_->mos6532_.update_port_input(1, 0x02, state); break;
|
||||
case Atari2600SwitchColour: bus_->mos6532_.update_port_input(1, 0x08, state); break;
|
||||
case Atari2600SwitchLeftPlayerDifficulty: bus_->mos6532_.update_port_input(1, 0x40, state); break;
|
||||
case Atari2600SwitchRightPlayerDifficulty: bus_->mos6532_.update_port_input(1, 0x80, state); break;
|
||||
}
|
||||
}
|
||||
|
||||
void Machine::configure_as_target(const StaticAnalyser::Target &target) {
|
||||
const std::vector<uint8_t> &rom = target.cartridges.front()->get_segments().front().data;
|
||||
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
|
||||
|
||||
void Machine::crt_did_end_batch_of_frames(Outputs::CRT::CRT *crt, unsigned int number_of_frames, unsigned int number_of_unexpected_vertical_syncs) {
|
||||
const 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_unexpected_vertical_syncs = number_of_unexpected_vertical_syncs;
|
||||
frame_record_pointer_ ++;
|
||||
|
||||
if(frame_record_pointer_ >= 6) {
|
||||
unsigned int total_number_of_frames = 0;
|
||||
unsigned int total_number_of_unexpected_vertical_syncs = 0;
|
||||
for(size_t c = 0; c < number_of_frame_records; c++) {
|
||||
total_number_of_frames += frame_records_[c].number_of_frames;
|
||||
total_number_of_unexpected_vertical_syncs += frame_records_[c].number_of_unexpected_vertical_syncs;
|
||||
class ConcreteMachine:
|
||||
public Machine,
|
||||
public Outputs::CRT::Delegate {
|
||||
public:
|
||||
ConcreteMachine() :
|
||||
frame_record_pointer_(0),
|
||||
is_ntsc_(true) {
|
||||
set_clock_rate(NTSC_clock_rate);
|
||||
}
|
||||
|
||||
if(total_number_of_unexpected_vertical_syncs >= total_number_of_frames >> 1) {
|
||||
for(size_t c = 0; c < number_of_frame_records; c++) {
|
||||
frame_records_[c].number_of_frames = 0;
|
||||
frame_records_[c].number_of_unexpected_vertical_syncs = 0;
|
||||
}
|
||||
is_ntsc_ ^= true;
|
||||
|
||||
double clock_rate;
|
||||
if(is_ntsc_) {
|
||||
clock_rate = NTSC_clock_rate;
|
||||
bus_->tia_->set_output_mode(TIA::OutputMode::NTSC);
|
||||
} else {
|
||||
clock_rate = PAL_clock_rate;
|
||||
bus_->tia_->set_output_mode(TIA::OutputMode::PAL);
|
||||
}
|
||||
|
||||
bus_->speaker_->set_input_rate((float)(clock_rate / (double)CPUTicksPerAudioTick));
|
||||
bus_->speaker_->set_high_frequency_cut_off((float)(clock_rate / ((double)CPUTicksPerAudioTick * 2.0)));
|
||||
set_clock_rate(clock_rate);
|
||||
~ConcreteMachine() {
|
||||
close_output();
|
||||
}
|
||||
}
|
||||
|
||||
void configure_as_target(const StaticAnalyser::Target &target) {
|
||||
const std::vector<uint8_t> &rom = target.media.cartridges.front()->get_segments().front().data;
|
||||
switch(target.atari.paging_model) {
|
||||
case StaticAnalyser::Atari2600PagingModel::ActivisionStack: bus_.reset(new Cartridge::Cartridge<Cartridge::ActivisionStack>(rom)); break;
|
||||
case StaticAnalyser::Atari2600PagingModel::CBSRamPlus: bus_.reset(new Cartridge::Cartridge<Cartridge::CBSRAMPlus>(rom)); 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 StaticAnalyser::Atari2600PagingModel::Atari8k:
|
||||
if(target.atari.uses_superchip) {
|
||||
bus_.reset(new Cartridge::Cartridge<Cartridge::Atari8kSuperChip>(rom));
|
||||
} else {
|
||||
bus_.reset(new Cartridge::Cartridge<Cartridge::Atari8k>(rom));
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
bool insert_media(const StaticAnalyser::Media &media) {
|
||||
return false;
|
||||
}
|
||||
|
||||
void set_digital_input(Atari2600DigitalInput input, bool state) {
|
||||
switch (input) {
|
||||
case Atari2600DigitalInputJoy1Up: bus_->mos6532_.update_port_input(0, 0x10, state); break;
|
||||
case Atari2600DigitalInputJoy1Down: bus_->mos6532_.update_port_input(0, 0x20, state); break;
|
||||
case Atari2600DigitalInputJoy1Left: bus_->mos6532_.update_port_input(0, 0x40, state); break;
|
||||
case Atari2600DigitalInputJoy1Right: bus_->mos6532_.update_port_input(0, 0x80, state); break;
|
||||
|
||||
case Atari2600DigitalInputJoy2Up: bus_->mos6532_.update_port_input(0, 0x01, state); break;
|
||||
case Atari2600DigitalInputJoy2Down: bus_->mos6532_.update_port_input(0, 0x02, state); break;
|
||||
case Atari2600DigitalInputJoy2Left: bus_->mos6532_.update_port_input(0, 0x04, state); break;
|
||||
case Atari2600DigitalInputJoy2Right: bus_->mos6532_.update_port_input(0, 0x08, state); break;
|
||||
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void set_switch_is_enabled(Atari2600Switch input, bool state) {
|
||||
switch(input) {
|
||||
case Atari2600SwitchReset: bus_->mos6532_.update_port_input(1, 0x01, state); break;
|
||||
case Atari2600SwitchSelect: bus_->mos6532_.update_port_input(1, 0x02, state); break;
|
||||
case Atari2600SwitchColour: bus_->mos6532_.update_port_input(1, 0x08, state); break;
|
||||
case Atari2600SwitchLeftPlayerDifficulty: bus_->mos6532_.update_port_input(1, 0x40, state); break;
|
||||
case Atari2600SwitchRightPlayerDifficulty: bus_->mos6532_.update_port_input(1, 0x80, state); break;
|
||||
}
|
||||
}
|
||||
|
||||
void set_reset_switch(bool state) {
|
||||
bus_->set_reset_line(state);
|
||||
}
|
||||
|
||||
// to satisfy CRTMachine::Machine
|
||||
void setup_output(float aspect_ratio) {
|
||||
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 close_output() {
|
||||
bus_.reset();
|
||||
}
|
||||
|
||||
std::shared_ptr<Outputs::CRT::CRT> get_crt() {
|
||||
return bus_->tia_->get_crt();
|
||||
}
|
||||
|
||||
std::shared_ptr<Outputs::Speaker> get_speaker() {
|
||||
return bus_->speaker_;
|
||||
}
|
||||
|
||||
void run_for(const Cycles cycles) {
|
||||
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) {
|
||||
const 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_unexpected_vertical_syncs = number_of_unexpected_vertical_syncs;
|
||||
frame_record_pointer_ ++;
|
||||
|
||||
if(frame_record_pointer_ >= 6) {
|
||||
unsigned int total_number_of_frames = 0;
|
||||
unsigned int total_number_of_unexpected_vertical_syncs = 0;
|
||||
for(size_t c = 0; c < number_of_frame_records; c++) {
|
||||
total_number_of_frames += frame_records_[c].number_of_frames;
|
||||
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) {
|
||||
for(size_t c = 0; c < number_of_frame_records; c++) {
|
||||
frame_records_[c].number_of_frames = 0;
|
||||
frame_records_[c].number_of_unexpected_vertical_syncs = 0;
|
||||
}
|
||||
is_ntsc_ ^= true;
|
||||
|
||||
double clock_rate;
|
||||
if(is_ntsc_) {
|
||||
clock_rate = NTSC_clock_rate;
|
||||
bus_->tia_->set_output_mode(TIA::OutputMode::NTSC);
|
||||
} else {
|
||||
clock_rate = PAL_clock_rate;
|
||||
bus_->tia_->set_output_mode(TIA::OutputMode::PAL);
|
||||
}
|
||||
|
||||
bus_->speaker_->set_input_rate((float)(clock_rate / (double)CPUTicksPerAudioTick));
|
||||
bus_->speaker_->set_high_frequency_cut_off((float)(clock_rate / ((double)CPUTicksPerAudioTick * 2.0)));
|
||||
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_;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
using namespace Atari2600;
|
||||
|
||||
Machine *Machine::Atari2600() {
|
||||
return new Atari2600::ConcreteMachine;
|
||||
}
|
||||
|
||||
Machine::~Machine() {}
|
||||
|
||||
@@ -9,59 +9,33 @@
|
||||
#ifndef Atari2600_cpp
|
||||
#define Atari2600_cpp
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include "../../Processors/6502/CPU6502.hpp"
|
||||
#include "../CRTMachine.hpp"
|
||||
#include "Bus.hpp"
|
||||
#include "PIA.hpp"
|
||||
#include "Speaker.hpp"
|
||||
#include "TIA.hpp"
|
||||
|
||||
#include "../ConfigurationTarget.hpp"
|
||||
#include "../CRTMachine.hpp"
|
||||
|
||||
#include "Atari2600Inputs.h"
|
||||
|
||||
namespace Atari2600 {
|
||||
|
||||
/*!
|
||||
Models an Atari 2600.
|
||||
*/
|
||||
class Machine:
|
||||
public CRTMachine::Machine,
|
||||
public ConfigurationTarget::Machine,
|
||||
public Outputs::CRT::Delegate {
|
||||
|
||||
public ConfigurationTarget::Machine {
|
||||
public:
|
||||
Machine();
|
||||
~Machine();
|
||||
virtual ~Machine();
|
||||
|
||||
void configure_as_target(const StaticAnalyser::Target &target);
|
||||
void switch_region();
|
||||
/// Creates and returns an Atari 2600 on the heap.
|
||||
static Machine *Atari2600();
|
||||
|
||||
void set_digital_input(Atari2600DigitalInput input, bool state);
|
||||
void set_switch_is_enabled(Atari2600Switch input, bool state);
|
||||
void set_reset_line(bool state) { bus_->set_reset_line(state); }
|
||||
/// Sets @c input to @c state.
|
||||
virtual void set_digital_input(Atari2600DigitalInput input, bool state) = 0;
|
||||
|
||||
// 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 bus_->tia_->get_crt(); }
|
||||
virtual std::shared_ptr<Outputs::Speaker> get_speaker() { return bus_->speaker_; }
|
||||
virtual void run_for_cycles(int number_of_cycles) { bus_->run_for_cycles(number_of_cycles); }
|
||||
/// Sets the switch @c input to @c state.
|
||||
virtual void set_switch_is_enabled(Atari2600Switch input, bool state) = 0;
|
||||
|
||||
// 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_;
|
||||
// Presses or releases the reset button.
|
||||
virtual void set_reset_switch(bool state) = 0;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -14,17 +14,17 @@
|
||||
#include "Speaker.hpp"
|
||||
#include "TIA.hpp"
|
||||
|
||||
#include "../../ClockReceiver/ClockReceiver.hpp"
|
||||
|
||||
namespace Atari2600 {
|
||||
|
||||
class Bus {
|
||||
public:
|
||||
Bus() :
|
||||
tia_input_value_{0xff, 0xff},
|
||||
cycles_since_speaker_update_(0),
|
||||
cycles_since_video_update_(0),
|
||||
cycles_since_6532_update_(0) {}
|
||||
cycles_since_speaker_update_(0) {}
|
||||
|
||||
virtual void run_for_cycles(int number_of_cycles) = 0;
|
||||
virtual void run_for(const Cycles cycles) = 0;
|
||||
virtual void set_reset_line(bool state) = 0;
|
||||
|
||||
// the RIOT, TIA and speaker
|
||||
@@ -37,25 +37,21 @@ class Bus {
|
||||
|
||||
protected:
|
||||
// speaker backlog accumlation counter
|
||||
unsigned int cycles_since_speaker_update_;
|
||||
Cycles cycles_since_speaker_update_;
|
||||
inline void update_audio() {
|
||||
unsigned int audio_cycles = cycles_since_speaker_update_ / (CPUTicksPerAudioTick * 3);
|
||||
cycles_since_speaker_update_ %= (CPUTicksPerAudioTick * 3);
|
||||
speaker_->run_for_cycles(audio_cycles);
|
||||
speaker_->run_for(cycles_since_speaker_update_.divide(Cycles(CPUTicksPerAudioTick * 3)));
|
||||
}
|
||||
|
||||
// video backlog accumulation counter
|
||||
unsigned int cycles_since_video_update_;
|
||||
Cycles cycles_since_video_update_;
|
||||
inline void update_video() {
|
||||
tia_->run_for_cycles((int)cycles_since_video_update_);
|
||||
cycles_since_video_update_ = 0;
|
||||
tia_->run_for(cycles_since_video_update_.flush());
|
||||
}
|
||||
|
||||
// RIOT backlog accumulation counter
|
||||
unsigned int cycles_since_6532_update_;
|
||||
Cycles cycles_since_6532_update_;
|
||||
inline void update_6532() {
|
||||
mos6532_.run_for_cycles(cycles_since_6532_update_);
|
||||
cycles_since_6532_update_ = 0;
|
||||
mos6532_.run_for(cycles_since_6532_update_.flush());
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -6,30 +6,30 @@
|
||||
// Copyright © 2017 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef Atari2600_CartridgeActivisionStack_hpp
|
||||
#define Atari2600_CartridgeActivisionStack_hpp
|
||||
#ifndef Atari2600_ActivisionStack_hpp
|
||||
#define Atari2600_ActivisionStack_hpp
|
||||
|
||||
namespace Atari2600 {
|
||||
namespace Cartridge {
|
||||
|
||||
class CartridgeActivisionStack: public Cartridge<CartridgeActivisionStack> {
|
||||
class ActivisionStack: public BusExtender {
|
||||
public:
|
||||
CartridgeActivisionStack(const std::vector<uint8_t> &rom) :
|
||||
Cartridge(rom),
|
||||
last_opcode_(0x00) {
|
||||
rom_ptr_ = rom_.data();
|
||||
}
|
||||
ActivisionStack(uint8_t *rom_base, size_t rom_size) :
|
||||
BusExtender(rom_base, rom_size),
|
||||
rom_ptr_(rom_base),
|
||||
last_opcode_(0x00) {}
|
||||
|
||||
void perform_bus_operation(CPU6502::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;
|
||||
|
||||
// This is a bit of a hack; a real cartridge can't see either the sync or read lines, and can't see
|
||||
// address line 13. Instead it looks for a pattern in recent address accesses that would imply an
|
||||
// RST or JSR.
|
||||
if(operation == CPU6502::BusOperation::ReadOpcode && (last_opcode_ == 0x20 || last_opcode_ == 0x60)) {
|
||||
if(operation == CPU::MOS6502::BusOperation::ReadOpcode && (last_opcode_ == 0x20 || last_opcode_ == 0x60)) {
|
||||
if(address & 0x2000) {
|
||||
rom_ptr_ = rom_.data();
|
||||
rom_ptr_ = rom_base_;
|
||||
} else {
|
||||
rom_ptr_ = rom_.data() + 4096;
|
||||
rom_ptr_ = rom_base_ + 4096;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,7 +37,7 @@ class CartridgeActivisionStack: public Cartridge<CartridgeActivisionStack> {
|
||||
*value = rom_ptr_[address & 4095];
|
||||
}
|
||||
|
||||
if(operation == CPU6502::BusOperation::ReadOpcode) last_opcode_ = *value;
|
||||
if(operation == CPU::MOS6502::BusOperation::ReadOpcode) last_opcode_ = *value;
|
||||
}
|
||||
|
||||
private:
|
||||
@@ -45,6 +45,7 @@ class CartridgeActivisionStack: public Cartridge<CartridgeActivisionStack> {
|
||||
uint8_t last_opcode_;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* Atari2600_CartridgeActivisionStack_hpp */
|
||||
@@ -12,19 +12,19 @@
|
||||
#include "Cartridge.hpp"
|
||||
|
||||
namespace Atari2600 {
|
||||
namespace Cartridge {
|
||||
|
||||
class CartridgeAtari16k: public Cartridge<CartridgeAtari16k> {
|
||||
class Atari16k: public BusExtender {
|
||||
public:
|
||||
CartridgeAtari16k(const std::vector<uint8_t> &rom) :
|
||||
Cartridge(rom) {
|
||||
rom_ptr_ = rom_.data();
|
||||
}
|
||||
Atari16k(uint8_t *rom_base, size_t rom_size) :
|
||||
BusExtender(rom_base, rom_size),
|
||||
rom_ptr_(rom_base) {}
|
||||
|
||||
void perform_bus_operation(CPU6502::BusOperation operation, uint16_t address, uint8_t *value) {
|
||||
void perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value) {
|
||||
address &= 0x1fff;
|
||||
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)) {
|
||||
*value = rom_ptr_[address & 4095];
|
||||
@@ -35,18 +35,17 @@ class CartridgeAtari16k: public Cartridge<CartridgeAtari16k> {
|
||||
uint8_t *rom_ptr_;
|
||||
};
|
||||
|
||||
class CartridgeAtari16kSuperChip: public Cartridge<CartridgeAtari16kSuperChip> {
|
||||
class Atari16kSuperChip: public BusExtender {
|
||||
public:
|
||||
CartridgeAtari16kSuperChip(const std::vector<uint8_t> &rom) :
|
||||
Cartridge(rom) {
|
||||
rom_ptr_ = rom_.data();
|
||||
}
|
||||
Atari16kSuperChip(uint8_t *rom_base, size_t rom_size) :
|
||||
BusExtender(rom_base, rom_size),
|
||||
rom_ptr_(rom_base) {}
|
||||
|
||||
void perform_bus_operation(CPU6502::BusOperation operation, uint16_t address, uint8_t *value) {
|
||||
void perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value) {
|
||||
address &= 0x1fff;
|
||||
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)) {
|
||||
*value = rom_ptr_[address & 4095];
|
||||
@@ -61,6 +60,7 @@ class CartridgeAtari16kSuperChip: public Cartridge<CartridgeAtari16kSuperChip> {
|
||||
uint8_t ram_[128];
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* Atari2600_CartridgeAtari16k_hpp */
|
||||
@@ -12,19 +12,17 @@
|
||||
#include "Cartridge.hpp"
|
||||
|
||||
namespace Atari2600 {
|
||||
namespace Cartridge {
|
||||
|
||||
class CartridgeAtari32k: public Cartridge<CartridgeAtari32k> {
|
||||
class Atari32k: public BusExtender {
|
||||
public:
|
||||
CartridgeAtari32k(const std::vector<uint8_t> &rom) :
|
||||
Cartridge(rom) {
|
||||
rom_ptr_ = rom_.data();
|
||||
}
|
||||
Atari32k(uint8_t *rom_base, size_t rom_size) : BusExtender(rom_base, rom_size), rom_ptr_(rom_base) {}
|
||||
|
||||
void perform_bus_operation(CPU6502::BusOperation operation, uint16_t address, uint8_t *value) {
|
||||
void perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value) {
|
||||
address &= 0x1fff;
|
||||
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)) {
|
||||
*value = rom_ptr_[address & 4095];
|
||||
@@ -35,18 +33,15 @@ class CartridgeAtari32k: public Cartridge<CartridgeAtari32k> {
|
||||
uint8_t *rom_ptr_;
|
||||
};
|
||||
|
||||
class CartridgeAtari32kSuperChip: public Cartridge<CartridgeAtari32kSuperChip> {
|
||||
class Atari32kSuperChip: public BusExtender {
|
||||
public:
|
||||
CartridgeAtari32kSuperChip(const std::vector<uint8_t> &rom) :
|
||||
Cartridge(rom) {
|
||||
rom_ptr_ = rom_.data();
|
||||
}
|
||||
Atari32kSuperChip(uint8_t *rom_base, size_t rom_size) : BusExtender(rom_base, rom_size), rom_ptr_(rom_base) {}
|
||||
|
||||
void perform_bus_operation(CPU6502::BusOperation operation, uint16_t address, uint8_t *value) {
|
||||
void perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value) {
|
||||
address &= 0x1fff;
|
||||
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)) {
|
||||
*value = rom_ptr_[address & 4095];
|
||||
@@ -61,6 +56,7 @@ class CartridgeAtari32kSuperChip: public Cartridge<CartridgeAtari32kSuperChip> {
|
||||
uint8_t ram_[128];
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* Atari2600_CartridgeAtari32k_hpp */
|
||||
@@ -12,20 +12,18 @@
|
||||
#include "Cartridge.hpp"
|
||||
|
||||
namespace Atari2600 {
|
||||
namespace Cartridge {
|
||||
|
||||
class CartridgeAtari8k: public Cartridge<CartridgeAtari8k> {
|
||||
class Atari8k: public BusExtender {
|
||||
public:
|
||||
CartridgeAtari8k(const std::vector<uint8_t> &rom) :
|
||||
Cartridge(rom) {
|
||||
rom_ptr_ = rom_.data();
|
||||
}
|
||||
Atari8k(uint8_t *rom_base, size_t rom_size) : BusExtender(rom_base, rom_size), rom_ptr_(rom_base) {}
|
||||
|
||||
void perform_bus_operation(CPU6502::BusOperation operation, uint16_t address, uint8_t *value) {
|
||||
void perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value) {
|
||||
address &= 0x1fff;
|
||||
if(!(address & 0x1000)) return;
|
||||
|
||||
if(address == 0x1ff8) rom_ptr_ = rom_.data();
|
||||
else if(address == 0x1ff9) rom_ptr_ = rom_.data() + 4096;
|
||||
if(address == 0x1ff8) rom_ptr_ = rom_base_;
|
||||
else if(address == 0x1ff9) rom_ptr_ = rom_base_ + 4096;
|
||||
|
||||
if(isReadOperation(operation)) {
|
||||
*value = rom_ptr_[address & 4095];
|
||||
@@ -36,19 +34,16 @@ class CartridgeAtari8k: public Cartridge<CartridgeAtari8k> {
|
||||
uint8_t *rom_ptr_;
|
||||
};
|
||||
|
||||
class CartridgeAtari8kSuperChip: public Cartridge<CartridgeAtari8kSuperChip> {
|
||||
class Atari8kSuperChip: public BusExtender {
|
||||
public:
|
||||
CartridgeAtari8kSuperChip(const std::vector<uint8_t> &rom) :
|
||||
Cartridge(rom) {
|
||||
rom_ptr_ = rom_.data();
|
||||
}
|
||||
Atari8kSuperChip(uint8_t *rom_base, size_t rom_size) : BusExtender(rom_base, rom_size), rom_ptr_(rom_base) {}
|
||||
|
||||
void perform_bus_operation(CPU6502::BusOperation operation, uint16_t address, uint8_t *value) {
|
||||
void perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value) {
|
||||
address &= 0x1fff;
|
||||
if(!(address & 0x1000)) return;
|
||||
|
||||
if(address == 0x1ff8) rom_ptr_ = rom_.data();
|
||||
if(address == 0x1ff9) rom_ptr_ = rom_.data() + 4096;
|
||||
if(address == 0x1ff8) rom_ptr_ = rom_base_;
|
||||
if(address == 0x1ff9) rom_ptr_ = rom_base_ + 4096;
|
||||
|
||||
if(isReadOperation(operation)) {
|
||||
*value = rom_ptr_[address & 4095];
|
||||
@@ -63,6 +58,7 @@ class CartridgeAtari8kSuperChip: public Cartridge<CartridgeAtari8kSuperChip> {
|
||||
uint8_t ram_[128];
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* Atari2600_CartridgeAtari8k_hpp */
|
||||
@@ -12,19 +12,17 @@
|
||||
#include "Cartridge.hpp"
|
||||
|
||||
namespace Atari2600 {
|
||||
namespace Cartridge {
|
||||
|
||||
class CartridgeCBSRAMPlus: public Cartridge<CartridgeCBSRAMPlus> {
|
||||
class CBSRAMPlus: public BusExtender {
|
||||
public:
|
||||
CartridgeCBSRAMPlus(const std::vector<uint8_t> &rom) :
|
||||
Cartridge(rom) {
|
||||
rom_ptr_ = rom_.data();
|
||||
}
|
||||
CBSRAMPlus(uint8_t *rom_base, size_t rom_size) : BusExtender(rom_base, rom_size), rom_ptr_(rom_base) {}
|
||||
|
||||
void perform_bus_operation(CPU6502::BusOperation operation, uint16_t address, uint8_t *value) {
|
||||
void perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value) {
|
||||
address &= 0x1fff;
|
||||
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)) {
|
||||
*value = rom_ptr_[address & 4095];
|
||||
@@ -39,6 +37,7 @@ class CartridgeCBSRAMPlus: public Cartridge<CartridgeCBSRAMPlus> {
|
||||
uint8_t ram_[256];
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* Atari2600_CartridgeCBSRAMPlus_hpp */
|
||||
@@ -9,43 +9,59 @@
|
||||
#ifndef Atari2600_Cartridge_hpp
|
||||
#define Atari2600_Cartridge_hpp
|
||||
|
||||
#include "../../../Processors/6502/CPU6502.hpp"
|
||||
#include "../../../Processors/6502/6502.hpp"
|
||||
#include "../Bus.hpp"
|
||||
|
||||
namespace Atari2600 {
|
||||
namespace Cartridge {
|
||||
|
||||
class BusExtender: public CPU::MOS6502::BusHandler {
|
||||
public:
|
||||
BusExtender(uint8_t *rom_base, size_t rom_size) : rom_base_(rom_base), rom_size_(rom_size) {}
|
||||
|
||||
void advance_cycles(int cycles) {}
|
||||
|
||||
protected:
|
||||
uint8_t *rom_base_;
|
||||
size_t rom_size_;
|
||||
};
|
||||
|
||||
template<class T> class Cartridge:
|
||||
public CPU6502::Processor<Cartridge<T>>,
|
||||
public CPU::MOS6502::BusHandler,
|
||||
public Bus {
|
||||
|
||||
public:
|
||||
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_cycles(int number_of_cycles) { CPU6502::Processor<Cartridge<T>>::run_for_cycles(number_of_cycles); }
|
||||
void set_reset_line(bool state) { CPU6502::Processor<Cartridge<T>>::set_reset_line(state); }
|
||||
void advance_cycles(unsigned int cycles) {}
|
||||
void run_for(const Cycles cycles) { m6502_.run_for(cycles); }
|
||||
void set_reset_line(bool state) { m6502_.set_reset_line(state); }
|
||||
|
||||
// to satisfy CPU6502::Processor
|
||||
unsigned int perform_bus_operation(CPU6502::BusOperation operation, uint16_t address, uint8_t *value) {
|
||||
// to satisfy CPU::MOS6502::Processor
|
||||
Cycles perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value) {
|
||||
uint8_t returnValue = 0xff;
|
||||
unsigned int cycles_run_for = 3;
|
||||
int cycles_run_for = 3;
|
||||
|
||||
// this occurs as a feedback loop — the 2600 requests ready, then performs the cycles_run_for
|
||||
// leap to the end of ready only once ready is signalled — because on a 6502 ready doesn't take
|
||||
// effect until the next read; therefore it isn't safe to assume that signalling ready immediately
|
||||
// skips to the end of the line.
|
||||
if(operation == CPU6502::BusOperation::Ready)
|
||||
cycles_run_for = (unsigned int)tia_->get_cycles_until_horizontal_blank(cycles_since_video_update_);
|
||||
if(operation == CPU::MOS6502::BusOperation::Ready)
|
||||
cycles_run_for = tia_->get_cycles_until_horizontal_blank(cycles_since_video_update_);
|
||||
|
||||
cycles_since_speaker_update_ += cycles_run_for;
|
||||
cycles_since_video_update_ += cycles_run_for;
|
||||
cycles_since_6532_update_ += (cycles_run_for / 3);
|
||||
static_cast<T *>(this)->advance_cycles(cycles_run_for / 3);
|
||||
cycles_since_speaker_update_ += Cycles(cycles_run_for);
|
||||
cycles_since_video_update_ += Cycles(cycles_run_for);
|
||||
cycles_since_6532_update_ += Cycles(cycles_run_for / 3);
|
||||
bus_extender_.advance_cycles(cycles_run_for / 3);
|
||||
|
||||
if(operation != CPU6502::BusOperation::Ready) {
|
||||
if(operation != CPU::MOS6502::BusOperation::Ready) {
|
||||
// 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
|
||||
if((address&0x1280) == 0x80) {
|
||||
@@ -91,7 +107,7 @@ template<class T> class Cartridge:
|
||||
case 0x00: update_video(); tia_->set_sync(*value & 0x02); break;
|
||||
case 0x01: update_video(); tia_->set_blank(*value & 0x02); break;
|
||||
|
||||
case 0x02: CPU6502::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;
|
||||
// TODO: audio will now be out of synchronisation — fix
|
||||
|
||||
@@ -156,21 +172,26 @@ template<class T> class Cartridge:
|
||||
}
|
||||
}
|
||||
|
||||
if(!tia_->get_cycles_until_horizontal_blank(cycles_since_video_update_)) CPU6502::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_run_for / 3;
|
||||
return Cycles(cycles_run_for / 3);
|
||||
}
|
||||
|
||||
void synchronise() {
|
||||
void flush() {
|
||||
update_audio();
|
||||
update_video();
|
||||
speaker_->flush();
|
||||
}
|
||||
|
||||
protected:
|
||||
CPU::MOS6502::Processor<Cartridge<T>, true> m6502_;
|
||||
std::vector<uint8_t> rom_;
|
||||
|
||||
private:
|
||||
T bus_extender_;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* Atari2600_Cartridge_hpp */
|
||||
|
||||
@@ -9,14 +9,16 @@
|
||||
#ifndef Atari2600_CartridgeCommaVid_hpp
|
||||
#define Atari2600_CartridgeCommaVid_hpp
|
||||
|
||||
#include "Cartridge.hpp"
|
||||
|
||||
namespace Atari2600 {
|
||||
namespace Cartridge {
|
||||
|
||||
class CartridgeCommaVid: public Cartridge<CartridgeCommaVid> {
|
||||
class CommaVid: public BusExtender {
|
||||
public:
|
||||
CartridgeCommaVid(const std::vector<uint8_t> &rom) :
|
||||
Cartridge(rom) {}
|
||||
CommaVid(uint8_t *rom_base, size_t rom_size) : BusExtender(rom_base, rom_size) {}
|
||||
|
||||
void perform_bus_operation(CPU6502::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;
|
||||
address &= 0x1fff;
|
||||
|
||||
@@ -30,13 +32,14 @@ class CartridgeCommaVid: public Cartridge<CartridgeCommaVid> {
|
||||
return;
|
||||
}
|
||||
|
||||
if(isReadOperation(operation)) *value = rom_[address & 2047];
|
||||
if(isReadOperation(operation)) *value = rom_base_[address & 2047];
|
||||
}
|
||||
|
||||
private:
|
||||
uint8_t ram_[1024];
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* Atari2600_CartridgeCommaVid_hpp */
|
||||
@@ -12,22 +12,23 @@
|
||||
#include "Cartridge.hpp"
|
||||
|
||||
namespace Atari2600 {
|
||||
namespace Cartridge {
|
||||
|
||||
class CartridgeMNetwork: public Cartridge<CartridgeMNetwork> {
|
||||
class MNetwork: public BusExtender {
|
||||
public:
|
||||
CartridgeMNetwork(const std::vector<uint8_t> &rom) :
|
||||
Cartridge(rom) {
|
||||
rom_ptr_[0] = rom_.data() + rom_.size() - 4096;
|
||||
MNetwork(uint8_t *rom_base, size_t rom_size) :
|
||||
BusExtender(rom_base, rom_size) {
|
||||
rom_ptr_[0] = rom_base + rom_size_ - 4096;
|
||||
rom_ptr_[1] = rom_ptr_[0] + 2048;
|
||||
high_ram_ptr_ = high_ram_;
|
||||
}
|
||||
|
||||
void perform_bus_operation(CPU6502::BusOperation operation, uint16_t address, uint8_t *value) {
|
||||
void perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value) {
|
||||
address &= 0x1fff;
|
||||
if(!(address & 0x1000)) return;
|
||||
|
||||
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) {
|
||||
rom_ptr_[0] = nullptr;
|
||||
} else if(address >= 0x1ff8 && address <= 0x1ffb) {
|
||||
@@ -54,7 +55,6 @@ class CartridgeMNetwork: public Cartridge<CartridgeMNetwork> {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private:
|
||||
@@ -63,6 +63,7 @@ class CartridgeMNetwork: public Cartridge<CartridgeMNetwork> {
|
||||
uint8_t low_ram_[1024], high_ram_[1024];
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* Atari2600_CartridgeMNetwork_hpp */
|
||||
@@ -12,22 +12,23 @@
|
||||
#include "Cartridge.hpp"
|
||||
|
||||
namespace Atari2600 {
|
||||
namespace Cartridge {
|
||||
|
||||
class CartridgeMegaBoy: public Cartridge<CartridgeMegaBoy> {
|
||||
class MegaBoy: public BusExtender {
|
||||
public:
|
||||
CartridgeMegaBoy(const std::vector<uint8_t> &rom) :
|
||||
Cartridge(rom),
|
||||
MegaBoy(uint8_t *rom_base, size_t rom_size) :
|
||||
BusExtender(rom_base, rom_size),
|
||||
rom_ptr_(rom_base),
|
||||
current_page_(0) {
|
||||
rom_ptr_ = rom_.data();
|
||||
}
|
||||
|
||||
void perform_bus_operation(CPU6502::BusOperation operation, uint16_t address, uint8_t *value) {
|
||||
void perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value) {
|
||||
address &= 0x1fff;
|
||||
if(!(address & 0x1000)) return;
|
||||
|
||||
if(address == 0x1ff0) {
|
||||
current_page_ = (current_page_ + 1) & 15;
|
||||
rom_ptr_ = rom_.data() + current_page_ * 4096;
|
||||
rom_ptr_ = rom_base_ + current_page_ * 4096;
|
||||
}
|
||||
|
||||
if(isReadOperation(operation)) {
|
||||
@@ -40,6 +41,7 @@ class CartridgeMegaBoy: public Cartridge<CartridgeMegaBoy> {
|
||||
uint8_t current_page_;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* CartridgeMegaBoy_h */
|
||||
@@ -12,24 +12,25 @@
|
||||
#include "Cartridge.hpp"
|
||||
|
||||
namespace Atari2600 {
|
||||
namespace Cartridge {
|
||||
|
||||
class CartridgeParkerBros: public Cartridge<CartridgeParkerBros> {
|
||||
class ParkerBros: public BusExtender {
|
||||
public:
|
||||
CartridgeParkerBros(const std::vector<uint8_t> &rom) :
|
||||
Cartridge(rom) {
|
||||
rom_ptr_[0] = rom_.data() + 4096;
|
||||
ParkerBros(uint8_t *rom_base, size_t rom_size) :
|
||||
BusExtender(rom_base, rom_size) {
|
||||
rom_ptr_[0] = rom_base + 4096;
|
||||
rom_ptr_[1] = rom_ptr_[0] + 1024;
|
||||
rom_ptr_[2] = rom_ptr_[1] + 1024;
|
||||
rom_ptr_[3] = rom_ptr_[2] + 1024;
|
||||
}
|
||||
|
||||
void perform_bus_operation(CPU6502::BusOperation operation, uint16_t address, uint8_t *value) {
|
||||
void perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value) {
|
||||
address &= 0x1fff;
|
||||
if(!(address & 0x1000)) return;
|
||||
|
||||
if(address >= 0x1fe0 && address < 0x1ff8) {
|
||||
int slot = (address >> 3)&3;
|
||||
rom_ptr_[slot] = rom_.data() + ((address & 7) * 1024);
|
||||
rom_ptr_[slot] = rom_base_ + ((address & 7) * 1024);
|
||||
}
|
||||
|
||||
if(isReadOperation(operation)) {
|
||||
@@ -41,6 +42,7 @@ class CartridgeParkerBros: public Cartridge<CartridgeParkerBros> {
|
||||
uint8_t *rom_ptr_[4];
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* Atari2600_CartridgeParkerBros_hpp */
|
||||
@@ -10,23 +10,23 @@
|
||||
#define Atari2600_CartridgePitfall2_hpp
|
||||
|
||||
namespace Atari2600 {
|
||||
namespace Cartridge {
|
||||
|
||||
class CartridgePitfall2: public Cartridge<CartridgePitfall2> {
|
||||
class Pitfall2: public BusExtender {
|
||||
public:
|
||||
CartridgePitfall2(const std::vector<uint8_t> &rom) :
|
||||
Cartridge(rom),
|
||||
Pitfall2(uint8_t *rom_base, size_t rom_size) :
|
||||
BusExtender(rom_base, rom_size),
|
||||
rom_ptr_(rom_base),
|
||||
random_number_generator_(0),
|
||||
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();
|
||||
}
|
||||
cycles_since_audio_update_(0) {}
|
||||
|
||||
void advance_cycles(unsigned int cycles) {
|
||||
void advance_cycles(int cycles) {
|
||||
cycles_since_audio_update_ += cycles;
|
||||
}
|
||||
|
||||
void perform_bus_operation(CPU6502::BusOperation operation, uint16_t address, uint8_t *value) {
|
||||
void perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value) {
|
||||
address &= 0x1fff;
|
||||
if(!(address & 0x1000)) return;
|
||||
|
||||
@@ -53,11 +53,11 @@ class CartridgePitfall2: public Cartridge<CartridgePitfall2> {
|
||||
break;
|
||||
|
||||
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;
|
||||
|
||||
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;
|
||||
|
||||
#pragma mark - Writes
|
||||
@@ -81,8 +81,8 @@ class CartridgePitfall2: public Cartridge<CartridgePitfall2> {
|
||||
|
||||
#pragma mark - Paging
|
||||
|
||||
case 0x1ff8: rom_ptr_ = rom_.data(); break;
|
||||
case 0x1ff9: rom_ptr_ = rom_.data() + 4096; break;
|
||||
case 0x1ff8: rom_ptr_ = rom_base_; break;
|
||||
case 0x1ff9: rom_ptr_ = rom_base_ + 4096; break;
|
||||
|
||||
#pragma mark - Business as usual
|
||||
|
||||
@@ -105,8 +105,7 @@ class CartridgePitfall2: public Cartridge<CartridgePitfall2> {
|
||||
|
||||
inline uint8_t update_audio() {
|
||||
const unsigned int clock_divisor = 57;
|
||||
unsigned int cycles_to_run_for = cycles_since_audio_update_ / clock_divisor;
|
||||
cycles_since_audio_update_ %= clock_divisor;
|
||||
int cycles_to_run_for = cycles_since_audio_update_.divide(clock_divisor).as_int();
|
||||
|
||||
int table_position = 0;
|
||||
for(int c = 0; c < 3; c++) {
|
||||
@@ -126,9 +125,10 @@ class CartridgePitfall2: public Cartridge<CartridgePitfall2> {
|
||||
uint8_t random_number_generator_;
|
||||
uint8_t *rom_ptr_;
|
||||
uint8_t audio_channel_[3];
|
||||
unsigned int cycles_since_audio_update_;
|
||||
Cycles cycles_since_audio_update_;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* Atari2600_CartridgePitfall2_hpp */
|
||||
@@ -12,19 +12,20 @@
|
||||
#include "Cartridge.hpp"
|
||||
|
||||
namespace Atari2600 {
|
||||
namespace Cartridge {
|
||||
|
||||
class CartridgeTigervision: public Cartridge<CartridgeTigervision> {
|
||||
class Tigervision: public BusExtender {
|
||||
public:
|
||||
CartridgeTigervision(const std::vector<uint8_t> &rom) :
|
||||
Cartridge(rom) {
|
||||
rom_ptr_[0] = rom_.data() + rom_.size() - 4096;
|
||||
Tigervision(uint8_t *rom_base, size_t rom_size) :
|
||||
BusExtender(rom_base, rom_size) {
|
||||
rom_ptr_[0] = rom_base + rom_size - 4096;
|
||||
rom_ptr_[1] = rom_ptr_[0] + 2048;
|
||||
}
|
||||
|
||||
void perform_bus_operation(CPU6502::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) {
|
||||
int offset = ((*value) * 2048) & (rom_.size() - 1);
|
||||
rom_ptr_[0] = rom_.data() + offset;
|
||||
int offset = ((*value) * 2048) & (rom_size_ - 1);
|
||||
rom_ptr_[0] = rom_base_ + offset;
|
||||
return;
|
||||
} else if((address&0x1000) && isReadOperation(operation)) {
|
||||
*value = rom_ptr_[(address >> 11)&1][address & 2047];
|
||||
@@ -35,6 +36,7 @@ class CartridgeTigervision: public Cartridge<CartridgeTigervision> {
|
||||
uint8_t *rom_ptr_[2];
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* Atari2600_CartridgeTigervision_hpp */
|
||||
@@ -12,19 +12,20 @@
|
||||
#include "Cartridge.hpp"
|
||||
|
||||
namespace Atari2600 {
|
||||
namespace Cartridge {
|
||||
|
||||
class CartridgeUnpaged: public Cartridge<CartridgeUnpaged> {
|
||||
class Unpaged: public BusExtender {
|
||||
public:
|
||||
CartridgeUnpaged(const std::vector<uint8_t> &rom) :
|
||||
Cartridge(rom) {}
|
||||
Unpaged(uint8_t *rom_base, size_t rom_size) : BusExtender(rom_base, rom_size) {}
|
||||
|
||||
void perform_bus_operation(CPU6502::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)) {
|
||||
*value = rom_[address & (rom_.size() - 1)];
|
||||
*value = rom_base_[address & (rom_size_ - 1)];
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* Atari2600_CartridgeUnpaged_hpp */
|
||||
@@ -165,13 +165,14 @@ void TIA::set_output_mode(Atari2600::TIA::OutputMode output_mode) {
|
||||
/* speaker_->set_input_rate((float)(get_clock_rate() / 38.0));*/
|
||||
}
|
||||
|
||||
void TIA::run_for_cycles(int number_of_cycles)
|
||||
{
|
||||
void TIA::run_for(const Cycles cycles) {
|
||||
int number_of_cycles = cycles.as_int();
|
||||
|
||||
// if part way through a line, definitely perform a partial, at most up to the end of the line
|
||||
if(horizontal_counter_) {
|
||||
int cycles = std::min(number_of_cycles, cycles_per_line - horizontal_counter_);
|
||||
output_for_cycles(cycles);
|
||||
number_of_cycles -= cycles;
|
||||
int output_cycles = std::min(number_of_cycles, cycles_per_line - horizontal_counter_);
|
||||
output_for_cycles(output_cycles);
|
||||
number_of_cycles -= output_cycles;
|
||||
}
|
||||
|
||||
// output full lines for as long as possible
|
||||
@@ -197,8 +198,8 @@ void TIA::set_blank(bool blank) {
|
||||
void TIA::reset_horizontal_counter() {
|
||||
}
|
||||
|
||||
int TIA::get_cycles_until_horizontal_blank(unsigned int from_offset) {
|
||||
return (cycles_per_line - (horizontal_counter_ + (int)from_offset) % cycles_per_line) % cycles_per_line;
|
||||
int TIA::get_cycles_until_horizontal_blank(const Cycles from_offset) {
|
||||
return (cycles_per_line - (horizontal_counter_ + from_offset.as_int()) % cycles_per_line) % cycles_per_line;
|
||||
}
|
||||
|
||||
void TIA::set_background_colour(uint8_t colour) {
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
#define TIA_hpp
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
#include "../CRTMachine.hpp"
|
||||
|
||||
namespace Atari2600 {
|
||||
@@ -27,10 +28,9 @@ class TIA {
|
||||
};
|
||||
|
||||
/*!
|
||||
Advances the TIA by @c number_of_cycles cycles. Any queued setters take effect in the
|
||||
first cycle performed.
|
||||
Advances the TIA by @c cycles. Any queued setters take effect in the first cycle performed.
|
||||
*/
|
||||
void run_for_cycles(int number_of_cycles);
|
||||
void run_for(const Cycles cycles);
|
||||
void set_output_mode(OutputMode output_mode);
|
||||
|
||||
void set_sync(bool sync);
|
||||
@@ -41,7 +41,7 @@ class TIA {
|
||||
@returns the number of cycles between (current TIA time) + from_offset to the current or
|
||||
next horizontal blanking period. Returns numbers in the range [0, 227].
|
||||
*/
|
||||
int get_cycles_until_horizontal_blank(unsigned int from_offset);
|
||||
int get_cycles_until_horizontal_blank(const Cycles from_offset);
|
||||
|
||||
void set_background_colour(uint8_t colour);
|
||||
|
||||
@@ -229,14 +229,14 @@ class TIA {
|
||||
} queue_[4];
|
||||
int queue_read_pointer_, queue_write_pointer_;
|
||||
|
||||
inline void output_pixels(uint8_t *const target, const int count, const uint8_t collision_identity, int pixel_position, int adder, int reverse_mask) {
|
||||
if(pixel_position == 32 || !graphic[graphic_index]) return;
|
||||
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;
|
||||
int output_cursor = 0;
|
||||
while(pixel_position < 32 && output_cursor < count) {
|
||||
int shift = (pixel_position >> 2) ^ reverse_mask;
|
||||
while(output_pixel_position < 32 && output_cursor < count) {
|
||||
int shift = (output_pixel_position >> 2) ^ output_reverse_mask;
|
||||
target[output_cursor] |= ((graphic[graphic_index] >> shift)&1) * collision_identity;
|
||||
output_cursor++;
|
||||
pixel_position += adder;
|
||||
output_pixel_position += output_adder;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
|
||||
#include "../Outputs/CRT/CRT.hpp"
|
||||
#include "../Outputs/Speaker.hpp"
|
||||
#include "../ClockReceiver/ClockReceiver.hpp"
|
||||
|
||||
namespace CRTMachine {
|
||||
|
||||
@@ -21,15 +22,28 @@ namespace CRTMachine {
|
||||
*/
|
||||
class Machine {
|
||||
public:
|
||||
Machine() : clock_is_unlimited_(false) {}
|
||||
Machine() : clock_is_unlimited_(false), delegate_(nullptr) {}
|
||||
|
||||
/*!
|
||||
Causes the machine to set up its CRT and, if it has one, speaker. The caller guarantees
|
||||
that an OpenGL context is bound.
|
||||
*/
|
||||
virtual void setup_output(float aspect_ratio) = 0;
|
||||
|
||||
/*!
|
||||
Gives the machine a chance to release all owned resources. The caller guarantees that the
|
||||
OpenGL context is bound.
|
||||
*/
|
||||
virtual void close_output() = 0;
|
||||
|
||||
/// @returns The CRT this machine is drawing to. Should not be @c nullptr.
|
||||
virtual std::shared_ptr<Outputs::CRT::CRT> get_crt() = 0;
|
||||
|
||||
/// @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 void run_for_cycles(int number_of_cycles) = 0;
|
||||
/// Runs the machine for @c cycles.
|
||||
virtual void run_for(const Cycles cycles) = 0;
|
||||
|
||||
// TODO: sever the clock-rate stuff.
|
||||
double get_clock_rate() {
|
||||
@@ -43,7 +57,7 @@ class Machine {
|
||||
virtual void machine_did_change_clock_rate(Machine *machine) = 0;
|
||||
virtual void machine_did_change_clock_is_unlimited(Machine *machine) = 0;
|
||||
};
|
||||
void set_delegate(Delegate *delegate) { this->delegate_ = delegate; }
|
||||
void set_delegate(Delegate *delegate) { delegate_ = delegate; }
|
||||
|
||||
protected:
|
||||
void set_clock_rate(double clock_rate) {
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
using namespace Commodore::C1540;
|
||||
|
||||
Machine::Machine() :
|
||||
m6502_(*this),
|
||||
shift_register_(0),
|
||||
Storage::Disk::Controller(1000000, 4, 300),
|
||||
serial_port_(new SerialPort),
|
||||
@@ -34,7 +35,7 @@ void Machine::set_serial_bus(std::shared_ptr<::Commodore::Serial::Bus> serial_bu
|
||||
Commodore::Serial::AttachPortAndBus(serial_port_, serial_bus);
|
||||
}
|
||||
|
||||
unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uint16_t address, uint8_t *value) {
|
||||
Cycles Machine::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):
|
||||
|
||||
@@ -63,14 +64,14 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin
|
||||
drive_VIA_.set_register(address, *value);
|
||||
}
|
||||
|
||||
serial_port_VIA_->run_for_cycles(1);
|
||||
drive_VIA_.run_for_cycles(1);
|
||||
serial_port_VIA_->run_for(Cycles(1));
|
||||
drive_VIA_.run_for(Cycles(1));
|
||||
|
||||
return 1;
|
||||
return Cycles(1);
|
||||
}
|
||||
|
||||
void Machine::set_rom(const uint8_t *rom) {
|
||||
memcpy(rom_, rom, sizeof(rom_));
|
||||
void Machine::set_rom(const std::vector<uint8_t> &rom) {
|
||||
memcpy(rom_, rom.data(), std::min(sizeof(rom_), rom.size()));
|
||||
}
|
||||
|
||||
void Machine::set_disk(std::shared_ptr<Storage::Disk::Disk> disk) {
|
||||
@@ -79,18 +80,18 @@ void Machine::set_disk(std::shared_ptr<Storage::Disk::Disk> disk) {
|
||||
set_drive(drive);
|
||||
}
|
||||
|
||||
void Machine::run_for_cycles(int number_of_cycles) {
|
||||
CPU6502::Processor<Machine>::run_for_cycles(number_of_cycles);
|
||||
void Machine::run_for(const Cycles cycles) {
|
||||
m6502_.run_for(cycles);
|
||||
set_motor_on(drive_VIA_.get_motor_enabled());
|
||||
if(drive_VIA_.get_motor_enabled()) // TODO: motor speed up/down
|
||||
Storage::Disk::Controller::run_for_cycles(number_of_cycles);
|
||||
Storage::Disk::Controller::run_for(cycles);
|
||||
}
|
||||
|
||||
#pragma mark - 6522 delegate
|
||||
|
||||
void Machine::mos6522_did_change_interrupt_status(void *mos6522) {
|
||||
// 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
|
||||
@@ -108,10 +109,10 @@ void Machine::process_input_bit(int value, unsigned int cycles_since_index_hole)
|
||||
drive_VIA_.set_data_input((uint8_t)shift_register_);
|
||||
bit_window_offset_ = 0;
|
||||
if(drive_VIA_.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
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
#ifndef Commodore1540_hpp
|
||||
#define Commodore1540_hpp
|
||||
|
||||
#include "../../../Processors/6502/CPU6502.hpp"
|
||||
#include "../../../Processors/6502/6502.hpp"
|
||||
#include "../../../Components/6522/6522.hpp"
|
||||
|
||||
#include "../SerialBus.hpp"
|
||||
@@ -120,7 +120,7 @@ class SerialPort : public ::Commodore::Serial::Port {
|
||||
Provides an emulation of the C1540.
|
||||
*/
|
||||
class Machine:
|
||||
public CPU6502::Processor<Machine>,
|
||||
public CPU::MOS6502::BusHandler,
|
||||
public MOS::MOS6522IRQDelegate::Delegate,
|
||||
public DriveVIA::Delegate,
|
||||
public Storage::Disk::Controller {
|
||||
@@ -131,18 +131,18 @@ class Machine:
|
||||
/*!
|
||||
Sets the ROM image to use for this drive; it is assumed that the buffer provided will be at least 16 kb in size.
|
||||
*/
|
||||
void set_rom(const uint8_t *rom);
|
||||
void set_rom(const std::vector<uint8_t> &rom);
|
||||
|
||||
/*!
|
||||
Sets the serial bus to which this drive should attach itself.
|
||||
*/
|
||||
void set_serial_bus(std::shared_ptr<::Commodore::Serial::Bus> serial_bus);
|
||||
|
||||
void run_for_cycles(int number_of_cycles);
|
||||
void run_for(const Cycles cycles);
|
||||
void set_disk(std::shared_ptr<Storage::Disk::Disk> disk);
|
||||
|
||||
// to satisfy CPU6502::Processor
|
||||
unsigned int perform_bus_operation(CPU6502::BusOperation operation, uint16_t address, uint8_t *value);
|
||||
// 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);
|
||||
@@ -152,6 +152,8 @@ class Machine:
|
||||
void drive_via_did_set_data_density(void *driveVIA, int density);
|
||||
|
||||
private:
|
||||
CPU::MOS6502::Processor<Machine, false> m6502_;
|
||||
|
||||
uint8_t ram_[0x800];
|
||||
uint8_t rom_[0x4000];
|
||||
|
||||
|
||||
@@ -1,18 +1,21 @@
|
||||
//
|
||||
// Typer.cpp
|
||||
// CharacterMapper.cpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 05/11/2016.
|
||||
// Copyright © 2016 Thomas Harte. All rights reserved.
|
||||
// Created by Thomas Harte on 03/08/2017.
|
||||
// Copyright © 2017 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#include "CharacterMapper.hpp"
|
||||
#include "Vic20.hpp"
|
||||
|
||||
uint16_t *Commodore::Vic20::Machine::sequence_for_character(Utility::Typer *typer, char character) {
|
||||
#define KEYS(...) {__VA_ARGS__, TerminateSequence}
|
||||
#define SHIFT(...) {KeyLShift, __VA_ARGS__, TerminateSequence}
|
||||
using namespace Commodore::Vic20;
|
||||
|
||||
uint16_t *CharacterMapper::sequence_for_character(char character) {
|
||||
#define KEYS(...) {__VA_ARGS__, EndSequence}
|
||||
#define SHIFT(...) {KeyLShift, __VA_ARGS__, EndSequence}
|
||||
#define X {NotMapped}
|
||||
static Key key_sequences[][3] = {
|
||||
static KeySequence key_sequences[] = {
|
||||
/* NUL */ X, /* SOH */ X,
|
||||
/* STX */ X, /* ETX */ X,
|
||||
/* EOT */ X, /* ENQ */ X,
|
||||
@@ -80,7 +83,5 @@ uint16_t *Commodore::Vic20::Machine::sequence_for_character(Utility::Typer *type
|
||||
#undef SHIFT
|
||||
#undef X
|
||||
|
||||
if(character > sizeof(key_sequences) / sizeof(*key_sequences)) return nullptr;
|
||||
if(key_sequences[character][0] == NotMapped) return nullptr;
|
||||
return (uint16_t *)key_sequences[character];
|
||||
return table_lookup_sequence_for_character(key_sequences, sizeof(key_sequences), character);
|
||||
}
|
||||
25
Machines/Commodore/Vic-20/CharacterMapper.hpp
Normal file
25
Machines/Commodore/Vic-20/CharacterMapper.hpp
Normal file
@@ -0,0 +1,25 @@
|
||||
//
|
||||
// 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 */
|
||||
File diff suppressed because it is too large
Load Diff
@@ -13,15 +13,7 @@
|
||||
#include "../../CRTMachine.hpp"
|
||||
#include "../../Typer.hpp"
|
||||
|
||||
#include "../../../Processors/6502/CPU6502.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"
|
||||
#include <cstdint>
|
||||
|
||||
namespace Commodore {
|
||||
namespace Vic20 {
|
||||
@@ -44,9 +36,8 @@ enum Region {
|
||||
PAL
|
||||
};
|
||||
|
||||
#define key(line, mask) (((mask) << 3) | (line))
|
||||
|
||||
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),
|
||||
@@ -63,8 +54,7 @@ enum Key: uint16_t {
|
||||
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),
|
||||
|
||||
TerminateSequence = 0xffff, NotMapped = 0xfffe
|
||||
#undef key
|
||||
};
|
||||
|
||||
enum JoystickInput {
|
||||
@@ -75,152 +65,31 @@ enum JoystickInput {
|
||||
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:
|
||||
public CPU6502::Processor<Machine>,
|
||||
public ConfigurationTarget::Machine,
|
||||
public CRTMachine::Machine,
|
||||
public MOS::MOS6522IRQDelegate::Delegate,
|
||||
public Utility::TypeRecipient,
|
||||
public Storage::Tape::BinaryTapePlayer::Delegate,
|
||||
public ConfigurationTarget::Machine {
|
||||
|
||||
public KeyboardMachine::Machine {
|
||||
public:
|
||||
Machine();
|
||||
~Machine();
|
||||
virtual ~Machine();
|
||||
|
||||
void set_rom(ROMSlot slot, size_t length, const uint8_t *data);
|
||||
void configure_as_target(const StaticAnalyser::Target &target);
|
||||
/// Creates and returns a Vic-20.
|
||||
static Machine *Vic20();
|
||||
|
||||
void set_key_state(uint16_t key, bool isPressed) { keyboard_via_->set_key_state(key, isPressed); }
|
||||
void clear_all_keys() { keyboard_via_->clear_all_keys(); }
|
||||
void set_joystick_state(JoystickInput input, bool isPressed) {
|
||||
user_port_via_->set_joystick_state(input, isPressed);
|
||||
keyboard_via_->set_joystick_state(input, isPressed);
|
||||
}
|
||||
/// Sets the contents of the rom in @c slot to the buffer @c data of length @c length.
|
||||
virtual void set_rom(ROMSlot slot, size_t length, const uint8_t *data) = 0;
|
||||
// TODO: take a std::vector<uint8_t> to collapse length and data.
|
||||
|
||||
void set_memory_size(MemorySize size);
|
||||
void set_region(Region region);
|
||||
/// Sets the state of joystick input @c input.
|
||||
virtual void set_joystick_state(JoystickInput input, bool isPressed) = 0;
|
||||
|
||||
inline void set_use_fast_tape_hack(bool activate) { use_fast_tape_hack_ = activate; }
|
||||
/// Sets the memory size of this Vic-20.
|
||||
virtual void set_memory_size(MemorySize size) = 0;
|
||||
|
||||
// to satisfy CPU6502::Processor
|
||||
unsigned int perform_bus_operation(CPU6502::BusOperation operation, uint16_t address, uint8_t *value);
|
||||
void synchronise() { mos6560_->synchronise(); }
|
||||
/// Sets the region of this Vic-20.
|
||||
virtual void set_region(Region region) = 0;
|
||||
|
||||
// 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_cycles(int number_of_cycles) { CPU6502::Processor<Machine>::run_for_cycles(number_of_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);
|
||||
|
||||
// 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::unique_ptr<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();
|
||||
/// Enables or disables turbo-speed tape loading.
|
||||
virtual void set_use_fast_tape_hack(bool activate) = 0;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -15,11 +15,19 @@ namespace ConfigurationTarget {
|
||||
|
||||
/*!
|
||||
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 {
|
||||
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;
|
||||
|
||||
/*!
|
||||
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,27 +1,22 @@
|
||||
//
|
||||
// Typer.cpp
|
||||
// CharacterMapper.cpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 05/11/2016.
|
||||
// Copyright © 2016 Thomas Harte. All rights reserved.
|
||||
// Created by Thomas Harte on 03/08/2017.
|
||||
// Copyright © 2017 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#include "CharacterMapper.hpp"
|
||||
#include "Electron.hpp"
|
||||
|
||||
int Electron::Machine::get_typer_delay() {
|
||||
return get_is_resetting() ? 625*25*128 : 0; // wait one second if resetting
|
||||
}
|
||||
using namespace Electron;
|
||||
|
||||
int Electron::Machine::get_typer_frequency() {
|
||||
return 625*128*2; // accept a new character every two frames
|
||||
}
|
||||
|
||||
uint16_t *Electron::Machine::sequence_for_character(Utility::Typer *typer, char character) {
|
||||
#define KEYS(...) {__VA_ARGS__, TerminateSequence}
|
||||
#define SHIFT(...) {KeyShift, __VA_ARGS__, TerminateSequence}
|
||||
#define CTRL(...) {KeyControl, __VA_ARGS__, TerminateSequence}
|
||||
uint16_t *CharacterMapper::sequence_for_character(char character) {
|
||||
#define KEYS(...) {__VA_ARGS__, EndSequence}
|
||||
#define SHIFT(...) {KeyShift, __VA_ARGS__, EndSequence}
|
||||
#define CTRL(...) {KeyControl, __VA_ARGS__, EndSequence}
|
||||
#define X {NotMapped}
|
||||
static Key key_sequences[][3] = {
|
||||
static KeySequence key_sequences[] = {
|
||||
/* NUL */ X, /* SOH */ X,
|
||||
/* STX */ X, /* ETX */ X,
|
||||
/* EOT */ X, /* ENQ */ X,
|
||||
@@ -91,7 +86,5 @@ uint16_t *Electron::Machine::sequence_for_character(Utility::Typer *typer, char
|
||||
#undef SHIFT
|
||||
#undef X
|
||||
|
||||
if(character > sizeof(key_sequences) / sizeof(*key_sequences)) return nullptr;
|
||||
if(key_sequences[character][0] == NotMapped) return nullptr;
|
||||
return (uint16_t *)key_sequences[character];
|
||||
return table_lookup_sequence_for_character(key_sequences, sizeof(key_sequences), character);
|
||||
}
|
||||
23
Machines/Electron/CharacterMapper.hpp
Normal file
23
Machines/Electron/CharacterMapper.hpp
Normal file
@@ -0,0 +1,23 @@
|
||||
//
|
||||
// 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,394 +8,474 @@
|
||||
|
||||
#include "Electron.hpp"
|
||||
|
||||
#include "../../Processors/6502/6502.hpp"
|
||||
#include "../../Storage/Tape/Tape.hpp"
|
||||
#include "../../ClockReceiver/ClockReceiver.hpp"
|
||||
#include "../../ClockReceiver/ForceInline.hpp"
|
||||
|
||||
#include "../Typer.hpp"
|
||||
|
||||
#include "CharacterMapper.hpp"
|
||||
#include "Interrupts.hpp"
|
||||
#include "Plus3.hpp"
|
||||
#include "Speaker.hpp"
|
||||
#include "Tape.hpp"
|
||||
#include "Video.hpp"
|
||||
|
||||
namespace Electron {
|
||||
|
||||
class ConcreteMachine:
|
||||
public Machine,
|
||||
public CPU::MOS6502::BusHandler,
|
||||
public Tape::Delegate,
|
||||
public Utility::TypeRecipient {
|
||||
public:
|
||||
ConcreteMachine() :
|
||||
m6502_(*this),
|
||||
interrupt_control_(0),
|
||||
interrupt_status_(Interrupt::PowerOnReset | Interrupt::TransmitDataEmpty | 0x80),
|
||||
cycles_since_audio_update_(0),
|
||||
use_fast_tape_hack_(false),
|
||||
cycles_until_display_interrupt_(0) {
|
||||
memset(key_states_, 0, sizeof(key_states_));
|
||||
for(int c = 0; c < 16; c++)
|
||||
memset(roms_[c], 0xff, 16384);
|
||||
|
||||
tape_.set_delegate(this);
|
||||
set_clock_rate(2000000);
|
||||
}
|
||||
|
||||
void set_rom(ROMSlot slot, std::vector<uint8_t> data, bool is_writeable) override final {
|
||||
uint8_t *target = nullptr;
|
||||
switch(slot) {
|
||||
case ROMSlotDFS: dfs_ = data; return;
|
||||
case ROMSlotADFS: adfs_ = data; return;
|
||||
|
||||
case ROMSlotOS: target = os_; break;
|
||||
default:
|
||||
target = roms_[slot];
|
||||
rom_write_masks_[slot] = is_writeable;
|
||||
break;
|
||||
}
|
||||
|
||||
memcpy(target, &data[0], std::min((size_t)16384, data.size()));
|
||||
}
|
||||
|
||||
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) override final {
|
||||
use_fast_tape_hack_ = activate;
|
||||
}
|
||||
|
||||
void configure_as_target(const StaticAnalyser::Target &target) override final {
|
||||
if(target.loadingCommand.length()) {
|
||||
set_typer_for_string(target.loadingCommand.c_str());
|
||||
}
|
||||
|
||||
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, adfs_, true);
|
||||
set_rom(ROMSlot5, std::vector<uint8_t>(adfs_.begin() + 16384, adfs_.end()), 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 = (ROMSlot)(((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;
|
||||
|
||||
if(address < 0x8000) {
|
||||
if(isReadOperation(operation)) {
|
||||
*value = ram_[address];
|
||||
} else {
|
||||
if(address >= video_access_range_.low_address && address <= video_access_range_.high_address) update_display();
|
||||
ram_[address] = *value;
|
||||
}
|
||||
|
||||
// for the entire frame, RAM is accessible only on odd cycles; in modes below 4
|
||||
// it's also accessible only outside of the pixel regions
|
||||
cycles += video_output_->get_cycles_until_next_ram_availability(cycles_since_display_update_.as_int() + 1);
|
||||
} else {
|
||||
switch(address & 0xff0f) {
|
||||
case 0xfe00:
|
||||
if(isReadOperation(operation)) {
|
||||
*value = interrupt_status_;
|
||||
interrupt_status_ &= ~PowerOnReset;
|
||||
} else {
|
||||
interrupt_control_ = (*value) & ~1;
|
||||
evaluate_interrupts();
|
||||
}
|
||||
break;
|
||||
case 0xfe07:
|
||||
if(!isReadOperation(operation)) {
|
||||
// update speaker mode
|
||||
bool new_speaker_is_enabled = (*value & 6) == 2;
|
||||
if(new_speaker_is_enabled != speaker_is_enabled_) {
|
||||
update_audio();
|
||||
speaker_->set_is_enabled(new_speaker_is_enabled);
|
||||
speaker_is_enabled_ = new_speaker_is_enabled;
|
||||
}
|
||||
|
||||
tape_.set_is_enabled((*value & 6) != 6);
|
||||
tape_.set_is_in_input_mode((*value & 6) == 0);
|
||||
tape_.set_is_running(((*value)&0x40) ? true : false);
|
||||
|
||||
// TODO: caps lock LED
|
||||
}
|
||||
|
||||
// deliberate fallthrough
|
||||
case 0xfe02: case 0xfe03:
|
||||
case 0xfe08: case 0xfe09: case 0xfe0a: case 0xfe0b:
|
||||
case 0xfe0c: case 0xfe0d: case 0xfe0e: case 0xfe0f:
|
||||
if(!isReadOperation(operation)) {
|
||||
update_display();
|
||||
video_output_->set_register(address, *value);
|
||||
video_access_range_ = video_output_->get_memory_access_range();
|
||||
queue_next_display_interrupt();
|
||||
}
|
||||
break;
|
||||
case 0xfe04:
|
||||
if(isReadOperation(operation)) {
|
||||
*value = tape_.get_data_register();
|
||||
tape_.clear_interrupts(Interrupt::ReceiveDataFull);
|
||||
} else {
|
||||
tape_.set_data_register(*value);
|
||||
tape_.clear_interrupts(Interrupt::TransmitDataEmpty);
|
||||
}
|
||||
break;
|
||||
case 0xfe05:
|
||||
if(!isReadOperation(operation)) {
|
||||
const uint8_t interruptDisable = (*value)&0xf0;
|
||||
if( interruptDisable ) {
|
||||
if( interruptDisable&0x10 ) interrupt_status_ &= ~Interrupt::DisplayEnd;
|
||||
if( interruptDisable&0x20 ) interrupt_status_ &= ~Interrupt::RealTimeClock;
|
||||
if( interruptDisable&0x40 ) interrupt_status_ &= ~Interrupt::HighToneDetect;
|
||||
evaluate_interrupts();
|
||||
|
||||
// TODO: NMI
|
||||
}
|
||||
|
||||
// latch the paged ROM in case external hardware is being emulated
|
||||
active_rom_ = (Electron::ROMSlot)(*value & 0xf);
|
||||
|
||||
// apply the ULA's test
|
||||
if(*value & 0x08) {
|
||||
if(*value & 0x04) {
|
||||
keyboard_is_active_ = false;
|
||||
basic_is_active_ = false;
|
||||
} else {
|
||||
keyboard_is_active_ = !(*value & 0x02);
|
||||
basic_is_active_ = !keyboard_is_active_;
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 0xfe06:
|
||||
if(!isReadOperation(operation)) {
|
||||
update_audio();
|
||||
speaker_->set_divider(*value);
|
||||
tape_.set_counter(*value);
|
||||
}
|
||||
break;
|
||||
|
||||
case 0xfc04: case 0xfc05: case 0xfc06: case 0xfc07:
|
||||
if(plus3_ && (address&0x00f0) == 0x00c0) {
|
||||
if(is_holding_shift_ && address == 0xfcc4) {
|
||||
is_holding_shift_ = false;
|
||||
set_key_state(KeyShift, false);
|
||||
}
|
||||
if(isReadOperation(operation))
|
||||
*value = plus3_->get_register(address);
|
||||
else
|
||||
plus3_->set_register(address, *value);
|
||||
}
|
||||
break;
|
||||
case 0xfc00:
|
||||
if(plus3_ && (address&0x00f0) == 0x00c0) {
|
||||
if(!isReadOperation(operation)) {
|
||||
plus3_->set_control_register(*value);
|
||||
} else *value = 1;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
if(address >= 0xc000) {
|
||||
if(isReadOperation(operation)) {
|
||||
if(
|
||||
use_fast_tape_hack_ &&
|
||||
tape_.has_tape() &&
|
||||
(operation == CPU::MOS6502::BusOperation::ReadOpcode) &&
|
||||
(
|
||||
(address == 0xf4e5) || (address == 0xf4e6) || // double NOPs at 0xf4e5, 0xf6de, 0xf6fa and 0xfa51
|
||||
(address == 0xf6de) || (address == 0xf6df) || // act to disable the normal branch into tape-handling
|
||||
(address == 0xf6fa) || (address == 0xf6fb) || // code, forcing the OS along the serially-accessed ROM
|
||||
(address == 0xfa51) || (address == 0xfa52) || // pathway.
|
||||
|
||||
(address == 0xf0a8) // 0xf0a8 is from where a service call would normally be
|
||||
// dispatched; we can check whether it would be call 14
|
||||
// (i.e. read byte) and, if so, whether the OS was about to
|
||||
// issue a read byte call to a ROM despite being the tape
|
||||
// FS being selected. If so then this is a get byte that
|
||||
// we should service synthetically. Put the byte into Y
|
||||
// and set A to zero to report that action was taken, then
|
||||
// allow the PC read to return an RTS.
|
||||
)
|
||||
) {
|
||||
uint8_t service_call = (uint8_t)m6502_.get_value_of_register(CPU::MOS6502::Register::X);
|
||||
if(address == 0xf0a8) {
|
||||
if(!ram_[0x247] && service_call == 14) {
|
||||
tape_.set_delegate(nullptr);
|
||||
|
||||
// TODO: handle tape wrap around.
|
||||
|
||||
int cycles_left_while_plausibly_in_data = 50;
|
||||
tape_.clear_interrupts(Interrupt::ReceiveDataFull);
|
||||
while(!tape_.get_tape()->is_at_end()) {
|
||||
tape_.run_for_input_pulse();
|
||||
cycles_left_while_plausibly_in_data--;
|
||||
if(!cycles_left_while_plausibly_in_data) fast_load_is_in_data_ = false;
|
||||
if( (tape_.get_interrupt_status() & Interrupt::ReceiveDataFull) &&
|
||||
(fast_load_is_in_data_ || tape_.get_data_register() == 0x2a)
|
||||
) break;
|
||||
}
|
||||
tape_.set_delegate(this);
|
||||
tape_.clear_interrupts(Interrupt::ReceiveDataFull);
|
||||
interrupt_status_ |= tape_.get_interrupt_status();
|
||||
|
||||
fast_load_is_in_data_ = true;
|
||||
m6502_.set_value_of_register(CPU::MOS6502::Register::A, 0);
|
||||
m6502_.set_value_of_register(CPU::MOS6502::Register::Y, tape_.get_data_register());
|
||||
*value = 0x60; // 0x60 is RTS
|
||||
}
|
||||
else *value = os_[address & 16383];
|
||||
}
|
||||
else *value = 0xea;
|
||||
} else {
|
||||
*value = os_[address & 16383];
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if(isReadOperation(operation)) {
|
||||
*value = roms_[active_rom_][address & 16383];
|
||||
if(keyboard_is_active_) {
|
||||
*value &= 0xf0;
|
||||
for(int address_line = 0; address_line < 14; address_line++) {
|
||||
if(!(address&(1 << address_line))) *value |= key_states_[address_line];
|
||||
}
|
||||
}
|
||||
if(basic_is_active_) {
|
||||
*value &= roms_[ROMSlotBASIC][address & 16383];
|
||||
}
|
||||
} else if(rom_write_masks_[active_rom_]) {
|
||||
roms_[active_rom_][address & 16383] = *value;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
cycles_since_display_update_ += Cycles((int)cycles);
|
||||
cycles_since_audio_update_ += Cycles((int)cycles);
|
||||
if(cycles_since_audio_update_ > Cycles(16384)) update_audio();
|
||||
tape_.run_for(Cycles((int)cycles));
|
||||
|
||||
cycles_until_display_interrupt_ -= cycles;
|
||||
if(cycles_until_display_interrupt_ < 0) {
|
||||
signal_interrupt(next_display_interrupt_);
|
||||
update_display();
|
||||
queue_next_display_interrupt();
|
||||
}
|
||||
|
||||
if(typer_) typer_->run_for(Cycles((int)cycles));
|
||||
if(plus3_) plus3_->run_for(Cycles(4*(int)cycles));
|
||||
if(shift_restart_counter_) {
|
||||
shift_restart_counter_ -= cycles;
|
||||
if(shift_restart_counter_ <= 0) {
|
||||
shift_restart_counter_ = 0;
|
||||
m6502_.set_power_on(true);
|
||||
set_key_state(KeyShift, true);
|
||||
is_holding_shift_ = true;
|
||||
}
|
||||
}
|
||||
|
||||
return Cycles((int)cycles);
|
||||
}
|
||||
|
||||
forceinline void flush() {
|
||||
update_display();
|
||||
update_audio();
|
||||
speaker_->flush();
|
||||
}
|
||||
|
||||
void setup_output(float aspect_ratio) override final {
|
||||
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 close_output() override final {
|
||||
video_output_.reset();
|
||||
}
|
||||
|
||||
std::shared_ptr<Outputs::CRT::CRT> get_crt() override final {
|
||||
return video_output_->get_crt();
|
||||
}
|
||||
|
||||
std::shared_ptr<Outputs::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 set_typer_for_string(const char *string) override final {
|
||||
std::unique_ptr<CharacterMapper> mapper(new CharacterMapper());
|
||||
Utility::TypeRecipient::set_typer_for_string(string, std::move(mapper));
|
||||
}
|
||||
|
||||
private:
|
||||
inline void update_display() {
|
||||
if(cycles_since_display_update_ > 0) {
|
||||
video_output_->run_for(cycles_since_display_update_.flush());
|
||||
}
|
||||
}
|
||||
|
||||
inline void queue_next_display_interrupt() {
|
||||
VideoOutput::Interrupt next_interrupt = video_output_->get_next_interrupt();
|
||||
cycles_until_display_interrupt_ = next_interrupt.cycles;
|
||||
next_display_interrupt_ = next_interrupt.interrupt;
|
||||
}
|
||||
|
||||
inline void update_audio() {
|
||||
if(cycles_since_audio_update_ > 0) {
|
||||
speaker_->run_for(cycles_since_audio_update_.divide(Cycles(Speaker::clock_rate_divider)));
|
||||
}
|
||||
}
|
||||
|
||||
inline void signal_interrupt(Interrupt interrupt) {
|
||||
interrupt_status_ |= interrupt;
|
||||
evaluate_interrupts();
|
||||
}
|
||||
|
||||
inline void clear_interrupt(Interrupt interrupt) {
|
||||
interrupt_status_ &= ~interrupt;
|
||||
evaluate_interrupts();
|
||||
}
|
||||
|
||||
inline void evaluate_interrupts() {
|
||||
if(interrupt_status_ & interrupt_control_) {
|
||||
interrupt_status_ |= 1;
|
||||
} else {
|
||||
interrupt_status_ &= ~1;
|
||||
}
|
||||
m6502_.set_irq_line(interrupt_status_ & 1);
|
||||
}
|
||||
|
||||
CPU::MOS6502::Processor<ConcreteMachine, false> m6502_;
|
||||
|
||||
// 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_;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
using namespace Electron;
|
||||
|
||||
#pragma mark - Lifecycle
|
||||
|
||||
Machine::Machine() :
|
||||
interrupt_control_(0),
|
||||
interrupt_status_(Interrupt::PowerOnReset | Interrupt::TransmitDataEmpty | 0x80),
|
||||
cycles_since_display_update_(0),
|
||||
cycles_since_audio_update_(0),
|
||||
use_fast_tape_hack_(false),
|
||||
cycles_until_display_interrupt_(0) {
|
||||
memset(key_states_, 0, sizeof(key_states_));
|
||||
for(int c = 0; c < 16; c++)
|
||||
memset(roms_[c], 0xff, 16384);
|
||||
|
||||
tape_.set_delegate(this);
|
||||
set_clock_rate(2000000);
|
||||
Machine *Machine::Electron() {
|
||||
return new Electron::ConcreteMachine;
|
||||
}
|
||||
|
||||
#pragma mark - Output
|
||||
|
||||
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()) { // TODO: and automatic loading option enabled
|
||||
set_typer_for_string(target.loadingCommand.c_str());
|
||||
}
|
||||
|
||||
if(target.acorn.should_shift_restart) {
|
||||
shift_restart_counter_ = 1000000;
|
||||
}
|
||||
}
|
||||
|
||||
void Machine::set_rom(ROMSlot slot, std::vector<uint8_t> data, bool is_writeable) {
|
||||
uint8_t *target = nullptr;
|
||||
switch(slot) {
|
||||
case ROMSlotDFS: dfs_ = data; return;
|
||||
case ROMSlotADFS: adfs_ = data; return;
|
||||
|
||||
case ROMSlotOS: target = os_; break;
|
||||
default:
|
||||
target = roms_[slot];
|
||||
rom_write_masks_[slot] = is_writeable;
|
||||
break;
|
||||
}
|
||||
|
||||
memcpy(target, &data[0], std::min((size_t)16384, data.size()));
|
||||
}
|
||||
|
||||
#pragma mark - The bus
|
||||
|
||||
unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uint16_t address, uint8_t *value) {
|
||||
unsigned int cycles = 1;
|
||||
|
||||
if(address < 0x8000) {
|
||||
if(isReadOperation(operation)) {
|
||||
*value = ram_[address];
|
||||
} else {
|
||||
if(address >= video_access_range_.low_address && address <= video_access_range_.high_address) update_display();
|
||||
ram_[address] = *value;
|
||||
}
|
||||
|
||||
// for the entire frame, RAM is accessible only on odd cycles; in modes below 4
|
||||
// it's also accessible only outside of the pixel regions
|
||||
cycles += video_output_->get_cycles_until_next_ram_availability((int)(cycles_since_display_update_ + 1));
|
||||
} else {
|
||||
switch(address & 0xff0f) {
|
||||
case 0xfe00:
|
||||
if(isReadOperation(operation)) {
|
||||
*value = interrupt_status_;
|
||||
interrupt_status_ &= ~PowerOnReset;
|
||||
} else {
|
||||
interrupt_control_ = (*value) & ~1;
|
||||
evaluate_interrupts();
|
||||
}
|
||||
break;
|
||||
case 0xfe07:
|
||||
if(!isReadOperation(operation)) {
|
||||
// update speaker mode
|
||||
bool new_speaker_is_enabled = (*value & 6) == 2;
|
||||
if(new_speaker_is_enabled != speaker_is_enabled_) {
|
||||
update_audio();
|
||||
speaker_->set_is_enabled(new_speaker_is_enabled);
|
||||
speaker_is_enabled_ = new_speaker_is_enabled;
|
||||
}
|
||||
|
||||
tape_.set_is_enabled((*value & 6) != 6);
|
||||
tape_.set_is_in_input_mode((*value & 6) == 0);
|
||||
tape_.set_is_running(((*value)&0x40) ? true : false);
|
||||
|
||||
// TODO: caps lock LED
|
||||
}
|
||||
|
||||
// deliberate fallthrough
|
||||
case 0xfe02: case 0xfe03:
|
||||
case 0xfe08: case 0xfe09: case 0xfe0a: case 0xfe0b:
|
||||
case 0xfe0c: case 0xfe0d: case 0xfe0e: case 0xfe0f:
|
||||
if(!isReadOperation(operation)) {
|
||||
update_display();
|
||||
video_output_->set_register(address, *value);
|
||||
video_access_range_ = video_output_->get_memory_access_range();
|
||||
queue_next_display_interrupt();
|
||||
}
|
||||
break;
|
||||
case 0xfe04:
|
||||
if(isReadOperation(operation)) {
|
||||
*value = tape_.get_data_register();
|
||||
tape_.clear_interrupts(Interrupt::ReceiveDataFull);
|
||||
} else {
|
||||
tape_.set_data_register(*value);
|
||||
tape_.clear_interrupts(Interrupt::TransmitDataEmpty);
|
||||
}
|
||||
break;
|
||||
case 0xfe05:
|
||||
if(!isReadOperation(operation)) {
|
||||
const uint8_t interruptDisable = (*value)&0xf0;
|
||||
if( interruptDisable ) {
|
||||
if( interruptDisable&0x10 ) interrupt_status_ &= ~Interrupt::DisplayEnd;
|
||||
if( interruptDisable&0x20 ) interrupt_status_ &= ~Interrupt::RealTimeClock;
|
||||
if( interruptDisable&0x40 ) interrupt_status_ &= ~Interrupt::HighToneDetect;
|
||||
evaluate_interrupts();
|
||||
|
||||
// TODO: NMI
|
||||
}
|
||||
|
||||
// latch the paged ROM in case external hardware is being emulated
|
||||
active_rom_ = (Electron::ROMSlot)(*value & 0xf);
|
||||
|
||||
// apply the ULA's test
|
||||
if(*value & 0x08) {
|
||||
if(*value & 0x04) {
|
||||
keyboard_is_active_ = false;
|
||||
basic_is_active_ = false;
|
||||
} else {
|
||||
keyboard_is_active_ = !(*value & 0x02);
|
||||
basic_is_active_ = !keyboard_is_active_;
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 0xfe06:
|
||||
if(!isReadOperation(operation)) {
|
||||
update_audio();
|
||||
speaker_->set_divider(*value);
|
||||
tape_.set_counter(*value);
|
||||
}
|
||||
break;
|
||||
|
||||
case 0xfc04: case 0xfc05: case 0xfc06: case 0xfc07:
|
||||
if(plus3_ && (address&0x00f0) == 0x00c0) {
|
||||
if(is_holding_shift_ && address == 0xfcc4) {
|
||||
is_holding_shift_ = false;
|
||||
set_key_state(KeyShift, false);
|
||||
}
|
||||
if(isReadOperation(operation))
|
||||
*value = plus3_->get_register(address);
|
||||
else
|
||||
plus3_->set_register(address, *value);
|
||||
}
|
||||
break;
|
||||
case 0xfc00:
|
||||
if(plus3_ && (address&0x00f0) == 0x00c0) {
|
||||
if(!isReadOperation(operation)) {
|
||||
plus3_->set_control_register(*value);
|
||||
} else *value = 1;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
if(address >= 0xc000) {
|
||||
if(isReadOperation(operation)) {
|
||||
if(
|
||||
use_fast_tape_hack_ &&
|
||||
tape_.has_tape() &&
|
||||
(operation == CPU6502::BusOperation::ReadOpcode) &&
|
||||
(
|
||||
(address == 0xf4e5) || (address == 0xf4e6) || // double NOPs at 0xf4e5, 0xf6de, 0xf6fa and 0xfa51
|
||||
(address == 0xf6de) || (address == 0xf6df) || // act to disable the normal branch into tape-handling
|
||||
(address == 0xf6fa) || (address == 0xf6fb) || // code, forcing the OS along the serially-accessed ROM
|
||||
(address == 0xfa51) || (address == 0xfa52) || // pathway.
|
||||
|
||||
(address == 0xf0a8) // 0xf0a8 is from where a service call would normally be
|
||||
// dispatched; we can check whether it would be call 14
|
||||
// (i.e. read byte) and, if so, whether the OS was about to
|
||||
// issue a read byte call to a ROM despite being the tape
|
||||
// FS being selected. If so then this is a get byte that
|
||||
// we should service synthetically. Put the byte into Y
|
||||
// and set A to zero to report that action was taken, then
|
||||
// allow the PC read to return an RTS.
|
||||
)
|
||||
) {
|
||||
uint8_t service_call = (uint8_t)get_value_of_register(CPU6502::Register::X);
|
||||
if(address == 0xf0a8) {
|
||||
if(!ram_[0x247] && service_call == 14) {
|
||||
tape_.set_delegate(nullptr);
|
||||
|
||||
// TODO: handle tape wrap around.
|
||||
|
||||
int cycles_left_while_plausibly_in_data = 50;
|
||||
tape_.clear_interrupts(Interrupt::ReceiveDataFull);
|
||||
while(!tape_.get_tape()->is_at_end()) {
|
||||
tape_.run_for_input_pulse();
|
||||
cycles_left_while_plausibly_in_data--;
|
||||
if(!cycles_left_while_plausibly_in_data) fast_load_is_in_data_ = false;
|
||||
if( (tape_.get_interrupt_status() & Interrupt::ReceiveDataFull) &&
|
||||
(fast_load_is_in_data_ || tape_.get_data_register() == 0x2a)
|
||||
) break;
|
||||
}
|
||||
tape_.set_delegate(this);
|
||||
tape_.clear_interrupts(Interrupt::ReceiveDataFull);
|
||||
interrupt_status_ |= tape_.get_interrupt_status();
|
||||
|
||||
fast_load_is_in_data_ = true;
|
||||
set_value_of_register(CPU6502::Register::A, 0);
|
||||
set_value_of_register(CPU6502::Register::Y, tape_.get_data_register());
|
||||
*value = 0x60; // 0x60 is RTS
|
||||
}
|
||||
else *value = os_[address & 16383];
|
||||
}
|
||||
else *value = 0xea;
|
||||
} else {
|
||||
*value = os_[address & 16383];
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if(isReadOperation(operation)) {
|
||||
*value = roms_[active_rom_][address & 16383];
|
||||
if(keyboard_is_active_) {
|
||||
*value &= 0xf0;
|
||||
for(int address_line = 0; address_line < 14; address_line++) {
|
||||
if(!(address&(1 << address_line))) *value |= key_states_[address_line];
|
||||
}
|
||||
}
|
||||
if(basic_is_active_) {
|
||||
*value &= roms_[ROMSlotBASIC][address & 16383];
|
||||
}
|
||||
} else if(rom_write_masks_[active_rom_]) {
|
||||
roms_[active_rom_][address & 16383] = *value;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
cycles_since_display_update_ += cycles;
|
||||
cycles_since_audio_update_ += cycles;
|
||||
if(cycles_since_audio_update_ > 16384) update_audio();
|
||||
tape_.run_for_cycles(cycles);
|
||||
|
||||
cycles_until_display_interrupt_ -= cycles;
|
||||
if(cycles_until_display_interrupt_ < 0) {
|
||||
signal_interrupt(next_display_interrupt_);
|
||||
update_display();
|
||||
queue_next_display_interrupt();
|
||||
}
|
||||
|
||||
if(typer_) typer_->update((int)cycles);
|
||||
if(plus3_) plus3_->run_for_cycles(4*cycles);
|
||||
if(shift_restart_counter_) {
|
||||
shift_restart_counter_ -= cycles;
|
||||
if(shift_restart_counter_ <= 0) {
|
||||
shift_restart_counter_ = 0;
|
||||
set_power_on(true);
|
||||
set_key_state(KeyShift, true);
|
||||
is_holding_shift_ = true;
|
||||
}
|
||||
}
|
||||
|
||||
return cycles;
|
||||
}
|
||||
|
||||
void Machine::synchronise() {
|
||||
update_display();
|
||||
update_audio();
|
||||
speaker_->flush();
|
||||
}
|
||||
|
||||
#pragma mark - Deferred scheduling
|
||||
|
||||
inline void Machine::update_display() {
|
||||
if(cycles_since_display_update_) {
|
||||
video_output_->run_for_cycles((int)cycles_since_display_update_);
|
||||
cycles_since_display_update_ = 0;
|
||||
}
|
||||
}
|
||||
|
||||
inline void Machine::queue_next_display_interrupt() {
|
||||
VideoOutput::Interrupt next_interrupt = video_output_->get_next_interrupt();
|
||||
cycles_until_display_interrupt_ = next_interrupt.cycles;
|
||||
next_display_interrupt_ = next_interrupt.interrupt;
|
||||
}
|
||||
|
||||
inline void Machine::update_audio() {
|
||||
if(cycles_since_audio_update_) {
|
||||
unsigned int difference = cycles_since_audio_update_ / Speaker::clock_rate_divider;
|
||||
cycles_since_audio_update_ %= Speaker::clock_rate_divider;
|
||||
speaker_->run_for_cycles(difference);
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - Interrupts
|
||||
|
||||
inline void Machine::signal_interrupt(Electron::Interrupt interrupt) {
|
||||
interrupt_status_ |= interrupt;
|
||||
evaluate_interrupts();
|
||||
}
|
||||
|
||||
inline void Machine::clear_interrupt(Electron::Interrupt interrupt) {
|
||||
interrupt_status_ &= ~interrupt;
|
||||
evaluate_interrupts();
|
||||
}
|
||||
|
||||
inline void Machine::evaluate_interrupts() {
|
||||
if(interrupt_status_ & interrupt_control_) {
|
||||
interrupt_status_ |= 1;
|
||||
} else {
|
||||
interrupt_status_ &= ~1;
|
||||
}
|
||||
set_irq_line(interrupt_status_ & 1);
|
||||
}
|
||||
|
||||
#pragma mark - Tape::Delegate
|
||||
|
||||
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();
|
||||
}
|
||||
Machine::~Machine() {}
|
||||
|
||||
@@ -9,18 +9,9 @@
|
||||
#ifndef Electron_hpp
|
||||
#define Electron_hpp
|
||||
|
||||
#include "../../Processors/6502/CPU6502.hpp"
|
||||
#include "../../Storage/Tape/Tape.hpp"
|
||||
|
||||
#include "../ConfigurationTarget.hpp"
|
||||
#include "../CRTMachine.hpp"
|
||||
#include "../Typer.hpp"
|
||||
|
||||
#include "Interrupts.hpp"
|
||||
#include "Plus3.hpp"
|
||||
#include "Speaker.hpp"
|
||||
#include "Tape.hpp"
|
||||
#include "Video.hpp"
|
||||
#include "../KeyboardMachine.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
@@ -57,8 +48,6 @@ enum Key: uint16_t {
|
||||
KeyShift = 0x00d0 | 0x08, KeyControl = 0x00d0 | 0x04, KeyFunc = 0x00d0 | 0x02, KeyEscape = 0x00d0 | 0x01,
|
||||
|
||||
KeyBreak = 0xfffd,
|
||||
|
||||
TerminateSequence = 0xffff, NotMapped = 0xfffe,
|
||||
};
|
||||
|
||||
/*!
|
||||
@@ -68,88 +57,23 @@ enum Key: uint16_t {
|
||||
Acorn Electron.
|
||||
*/
|
||||
class Machine:
|
||||
public CPU6502::Processor<Machine>,
|
||||
public CRTMachine::Machine,
|
||||
public Tape::Delegate,
|
||||
public Utility::TypeRecipient,
|
||||
public ConfigurationTarget::Machine {
|
||||
|
||||
public ConfigurationTarget::Machine,
|
||||
public KeyboardMachine::Machine {
|
||||
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.
|
||||
*/
|
||||
virtual void set_rom(ROMSlot slot, std::vector<uint8_t> data, bool is_writeable) = 0;
|
||||
|
||||
inline void set_use_fast_tape_hack(bool activate) { use_fast_tape_hack_ = activate; }
|
||||
|
||||
// to satisfy ConfigurationTarget::Machine
|
||||
void configure_as_target(const StaticAnalyser::Target &target);
|
||||
|
||||
// to satisfy CPU6502::Processor
|
||||
unsigned int perform_bus_operation(CPU6502::BusOperation operation, uint16_t address, uint8_t *value);
|
||||
void synchronise();
|
||||
|
||||
// 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_cycles(int number_of_cycles) { CPU6502::Processor<Machine>::run_for_cycles(number_of_cycles); }
|
||||
|
||||
// to satisfy Tape::Delegate
|
||||
virtual void tape_did_change_interrupt_status(Tape *tape);
|
||||
|
||||
// for Utility::TypeRecipient
|
||||
virtual int get_typer_delay();
|
||||
virtual int get_typer_frequency();
|
||||
uint16_t *sequence_for_character(Utility::Typer *typer, char character);
|
||||
|
||||
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
|
||||
unsigned int cycles_since_display_update_;
|
||||
unsigned int 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_;
|
||||
/// Enables or disables turbo-speed tape loading.
|
||||
virtual void set_use_fast_tape_hack(bool activate) = 0;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -17,7 +17,9 @@ Tape::Tape() :
|
||||
delegate_(nullptr),
|
||||
output_({.bits_remaining_until_empty = 0, .cycles_into_pulse = 0}),
|
||||
last_posted_interrupt_status_(0),
|
||||
interrupt_status_(0) {}
|
||||
interrupt_status_(0) {
|
||||
shifter_.set_delegate(this);
|
||||
}
|
||||
|
||||
void Tape::push_tape_bit(uint16_t bit) {
|
||||
data_register_ = (uint16_t)((data_register_ >> 1) | (bit << 10));
|
||||
@@ -70,38 +72,22 @@ uint8_t Tape::get_data_register() {
|
||||
return (uint8_t)(data_register_ >> 2);
|
||||
}
|
||||
|
||||
void Tape::process_input_pulse(Storage::Tape::Tape::Pulse pulse) {
|
||||
crossings_[0] = crossings_[1];
|
||||
crossings_[1] = crossings_[2];
|
||||
crossings_[2] = crossings_[3];
|
||||
|
||||
crossings_[3] = Tape::Unrecognised;
|
||||
if(pulse.type != Storage::Tape::Tape::Pulse::Zero) {
|
||||
float pulse_length = (float)pulse.length.length / (float)pulse.length.clock_rate;
|
||||
if(pulse_length >= 0.35 / 2400.0 && pulse_length < 0.7 / 2400.0) crossings_[3] = Tape::Short;
|
||||
if(pulse_length >= 0.35 / 1200.0 && pulse_length < 0.7 / 1200.0) crossings_[3] = Tape::Long;
|
||||
}
|
||||
|
||||
if(crossings_[0] == Tape::Long && crossings_[1] == Tape::Long) {
|
||||
push_tape_bit(0);
|
||||
crossings_[0] = crossings_[1] = Tape::Recognised;
|
||||
} else {
|
||||
if(crossings_[0] == Tape::Short && crossings_[1] == Tape::Short && crossings_[2] == Tape::Short && crossings_[3] == Tape::Short) {
|
||||
push_tape_bit(1);
|
||||
crossings_[0] = crossings_[1] =
|
||||
crossings_[2] = crossings_[3] = Tape::Recognised;
|
||||
}
|
||||
}
|
||||
void Tape::process_input_pulse(const Storage::Tape::Tape::Pulse &pulse) {
|
||||
shifter_.process_pulse(pulse);
|
||||
}
|
||||
|
||||
void Tape::run_for_cycles(unsigned int number_of_cycles) {
|
||||
void Tape::acorn_shifter_output_bit(int value) {
|
||||
push_tape_bit((uint16_t)value);
|
||||
}
|
||||
|
||||
void Tape::run_for(const Cycles cycles) {
|
||||
if(is_enabled_) {
|
||||
if(is_in_input_mode_) {
|
||||
if(is_running_) {
|
||||
TapePlayer::run_for_cycles((int)number_of_cycles);
|
||||
TapePlayer::run_for(cycles);
|
||||
}
|
||||
} else {
|
||||
output_.cycles_into_pulse += number_of_cycles;
|
||||
output_.cycles_into_pulse += (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
|
||||
output_.cycles_into_pulse -= 1664; // that divides the 125,000Hz clock that the sound divider runs off.
|
||||
push_tape_bit(1);
|
||||
|
||||
@@ -9,18 +9,23 @@
|
||||
#ifndef Electron_Tape_h
|
||||
#define Electron_Tape_h
|
||||
|
||||
#include "../../Storage/Tape/Tape.hpp"
|
||||
#include "Interrupts.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
#include "../../ClockReceiver/ClockReceiver.hpp"
|
||||
#include "../../Storage/Tape/Tape.hpp"
|
||||
#include "../../Storage/Tape/Parsers/Acorn.hpp"
|
||||
#include "Interrupts.hpp"
|
||||
|
||||
namespace Electron {
|
||||
|
||||
class Tape: public Storage::Tape::TapePlayer {
|
||||
class Tape:
|
||||
public Storage::Tape::TapePlayer,
|
||||
public Storage::Tape::Acorn::Shifter::Delegate {
|
||||
public:
|
||||
Tape();
|
||||
|
||||
void run_for_cycles(unsigned int number_of_cycles);
|
||||
void run_for(const Cycles cycles);
|
||||
using Storage::Tape::TapePlayer::run_for;
|
||||
|
||||
uint8_t get_data_register();
|
||||
void set_data_register(uint8_t value);
|
||||
@@ -39,8 +44,10 @@ class Tape: public Storage::Tape::TapePlayer {
|
||||
inline void set_is_enabled(bool is_enabled) { is_enabled_ = is_enabled; }
|
||||
void set_is_in_input_mode(bool is_in_input_mode);
|
||||
|
||||
void acorn_shifter_output_bit(int value);
|
||||
|
||||
private:
|
||||
void process_input_pulse(Storage::Tape::Tape::Pulse pulse);
|
||||
void process_input_pulse(const Storage::Tape::Tape::Pulse &pulse);
|
||||
inline void push_tape_bit(uint16_t bit);
|
||||
inline void get_next_tape_pulse();
|
||||
|
||||
@@ -62,9 +69,7 @@ class Tape: public Storage::Tape::TapePlayer {
|
||||
uint8_t interrupt_status_, last_posted_interrupt_status_;
|
||||
Delegate *delegate_;
|
||||
|
||||
enum {
|
||||
Long, Short, Unrecognised, Recognised
|
||||
} crossings_[4];
|
||||
::Storage::Tape::Acorn::Shifter shifter_;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -223,7 +223,8 @@ void VideoOutput::output_pixels(unsigned int number_of_cycles) {
|
||||
}
|
||||
}
|
||||
|
||||
void VideoOutput::run_for_cycles(int number_of_cycles) {
|
||||
void VideoOutput::run_for(const Cycles cycles) {
|
||||
int number_of_cycles = cycles.as_int();
|
||||
output_position_ = (output_position_ + number_of_cycles) % cycles_per_frame;
|
||||
while(number_of_cycles) {
|
||||
int draw_action_length = screen_map_[screen_map_pointer_].length;
|
||||
@@ -425,9 +426,9 @@ void VideoOutput::setup_screen_map() {
|
||||
screen_map_.emplace_back(DrawAction::Blank, cycles_per_line >> 1);
|
||||
screen_map_.emplace_back(DrawAction::Sync, (cycles_per_line * 5) >> 1);
|
||||
}
|
||||
for(int c = 0; c < first_graphics_line - 3; c++) emplace_blank_line();
|
||||
for(int c = 0; c < 256; c++) emplace_pixel_line();
|
||||
for(int c = 256 + first_graphics_line; c < 312; c++) emplace_blank_line();
|
||||
for(int l = 0; l < first_graphics_line - 3; l++) emplace_blank_line();
|
||||
for(int l = 0; l < 256; l++) emplace_pixel_line();
|
||||
for(int l = 256 + first_graphics_line; l < 312; l++) emplace_blank_line();
|
||||
if(c&1) emplace_blank_line();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
#define Machines_Electron_Video_hpp
|
||||
|
||||
#include "../../Outputs/CRT/CRT.hpp"
|
||||
#include "../../ClockReceiver/ClockReceiver.hpp"
|
||||
#include "Interrupts.hpp"
|
||||
|
||||
namespace Electron {
|
||||
@@ -32,8 +33,8 @@ class VideoOutput {
|
||||
/// @returns the CRT to which output is being painted.
|
||||
std::shared_ptr<Outputs::CRT::CRT> get_crt();
|
||||
|
||||
/// Produces the next @c number_of_cycles cycles of video output.
|
||||
void run_for_cycles(int number_of_cycles);
|
||||
/// Produces the next @c cycles of video output.
|
||||
void run_for(const Cycles cycles);
|
||||
|
||||
/*!
|
||||
Writes @c value to the register at @c address. May mutate the results of @c get_next_interrupt,
|
||||
@@ -53,14 +54,14 @@ class VideoOutput {
|
||||
/*!
|
||||
@returns the next interrupt that should be generated as a result of the video hardware.
|
||||
The time until signalling returned is the number of cycles after the final one triggered
|
||||
by the most recent call to @c run_for_cycles.
|
||||
by the most recent call to @c run_for.
|
||||
|
||||
This result may be mutated by calls to @c set_register.
|
||||
*/
|
||||
Interrupt get_next_interrupt();
|
||||
|
||||
/*!
|
||||
@returns the number of cycles after (final cycle of last run_for_cycles batch + @c from_time)
|
||||
@returns the number of cycles after (final cycle of last run_for batch + @c from_time)
|
||||
before the video circuits will allow the CPU to access RAM.
|
||||
*/
|
||||
unsigned int get_cycles_until_next_ram_availability(int from_time);
|
||||
|
||||
@@ -13,7 +13,15 @@ namespace KeyboardMachine {
|
||||
|
||||
class Machine {
|
||||
public:
|
||||
/*!
|
||||
Indicates that the key @c key has been either pressed or released, according to
|
||||
the state of @c isPressed.
|
||||
*/
|
||||
virtual void set_key_state(uint16_t key, bool isPressed) = 0;
|
||||
|
||||
/*!
|
||||
Instructs that all keys should now be treated as released.
|
||||
*/
|
||||
virtual void clear_all_keys() = 0;
|
||||
};
|
||||
|
||||
|
||||
@@ -22,3 +22,7 @@ void Memory::Fuzz(uint8_t *buffer, size_t size) {
|
||||
buffer[c] = (uint8_t)(std::rand() >> shift);
|
||||
}
|
||||
}
|
||||
|
||||
void Memory::Fuzz(std::vector<uint8_t> &buffer) {
|
||||
Fuzz(buffer.data(), buffer.size());
|
||||
}
|
||||
|
||||
@@ -11,11 +11,16 @@
|
||||
|
||||
#include <cstdint>
|
||||
#include <cstddef>
|
||||
#include <vector>
|
||||
|
||||
namespace Memory {
|
||||
|
||||
/// Stores @c size random bytes from @c buffer onwards.
|
||||
void Fuzz(uint8_t *buffer, size_t size);
|
||||
|
||||
// Replaces all existing vector contents with random bytes.
|
||||
void Fuzz(std::vector<uint8_t> &buffer);
|
||||
|
||||
}
|
||||
|
||||
#endif /* MemoryFuzzer_hpp */
|
||||
|
||||
@@ -1,10 +1,21 @@
|
||||
//
|
||||
// CharacterMapper.cpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 03/08/2017.
|
||||
// Copyright © 2017 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#include "CharacterMapper.hpp"
|
||||
#include "Oric.hpp"
|
||||
|
||||
uint16_t *Oric::Machine::sequence_for_character(Utility::Typer *typer, char character) {
|
||||
#define KEYS(...) {__VA_ARGS__, TerminateSequence}
|
||||
#define SHIFT(...) {KeyLeftShift, __VA_ARGS__, TerminateSequence}
|
||||
using namespace Oric;
|
||||
|
||||
uint16_t *CharacterMapper::sequence_for_character(char character) {
|
||||
#define KEYS(...) {__VA_ARGS__, EndSequence}
|
||||
#define SHIFT(...) {KeyLeftShift, __VA_ARGS__, EndSequence}
|
||||
#define X {NotMapped}
|
||||
static Key key_sequences[][3] = {
|
||||
static KeySequence key_sequences[] = {
|
||||
/* NUL */ X, /* SOH */ X,
|
||||
/* STX */ X, /* ETX */ X,
|
||||
/* EOT */ X, /* ENQ */ X,
|
||||
@@ -73,7 +84,5 @@ uint16_t *Oric::Machine::sequence_for_character(Utility::Typer *typer, char char
|
||||
#undef SHIFT
|
||||
#undef X
|
||||
|
||||
if(character > sizeof(key_sequences) / sizeof(*key_sequences)) return nullptr;
|
||||
if(key_sequences[character][0] == NotMapped) return nullptr;
|
||||
return (uint16_t *)key_sequences[character];
|
||||
return table_lookup_sequence_for_character(key_sequences, sizeof(key_sequences), character);
|
||||
}
|
||||
23
Machines/Oric/CharacterMapper.hpp
Normal file
23
Machines/Oric/CharacterMapper.hpp
Normal file
@@ -0,0 +1,23 @@
|
||||
//
|
||||
// CharacterMapper.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 03/08/2017.
|
||||
// Copyright © 2017 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef Machines_Oric_CharacterMapper_hpp
|
||||
#define Machines_Oric_CharacterMapper_hpp
|
||||
|
||||
#include "../Typer.hpp"
|
||||
|
||||
namespace Oric {
|
||||
|
||||
class CharacterMapper: public ::Utility::CharacterMapper {
|
||||
public:
|
||||
uint16_t *sequence_for_character(char character);
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif /* Machines_Oric_CharacterMapper_hpp */
|
||||
@@ -104,12 +104,12 @@ void Microdisc::set_head_load_request(bool head_load) {
|
||||
}
|
||||
}
|
||||
|
||||
void Microdisc::run_for_cycles(unsigned int number_of_cycles) {
|
||||
void Microdisc::run_for(const Cycles cycles) {
|
||||
if(head_load_request_counter_ < head_load_request_counter_target) {
|
||||
head_load_request_counter_ += number_of_cycles;
|
||||
head_load_request_counter_ += cycles.as_int();
|
||||
if(head_load_request_counter_ >= head_load_request_counter_target) set_head_loaded(true);
|
||||
}
|
||||
WD::WD1770::run_for_cycles(number_of_cycles);
|
||||
WD::WD1770::run_for(cycles);
|
||||
}
|
||||
|
||||
bool Microdisc::get_drive_is_ready() {
|
||||
|
||||
@@ -24,7 +24,8 @@ class Microdisc: public WD::WD1770 {
|
||||
|
||||
bool get_interrupt_request_line();
|
||||
|
||||
void run_for_cycles(unsigned int number_of_cycles);
|
||||
void run_for(const Cycles cycles);
|
||||
using WD::WD1770::run_for;
|
||||
|
||||
enum PagingFlags {
|
||||
BASICDisable = (1 << 0),
|
||||
|
||||
@@ -7,283 +7,381 @@
|
||||
//
|
||||
|
||||
#include "Oric.hpp"
|
||||
|
||||
#include "Video.hpp"
|
||||
#include "Microdisc.hpp"
|
||||
#include "CharacterMapper.hpp"
|
||||
|
||||
#include "../MemoryFuzzer.hpp"
|
||||
#include "../Typer.hpp"
|
||||
|
||||
#include "../../Processors/6502/6502.hpp"
|
||||
#include "../../Components/6522/6522.hpp"
|
||||
#include "../../Components/AY38910/AY38910.hpp"
|
||||
|
||||
#include "../../Storage/Tape/Tape.hpp"
|
||||
#include "../../Storage/Tape/Parsers/Oric.hpp"
|
||||
|
||||
#include "../../ClockReceiver/ForceInline.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
namespace Oric {
|
||||
|
||||
class ConcreteMachine:
|
||||
public CPU::MOS6502::BusHandler,
|
||||
public MOS::MOS6522IRQDelegate::Delegate,
|
||||
public Utility::TypeRecipient,
|
||||
public Storage::Tape::BinaryTapePlayer::Delegate,
|
||||
public Microdisc::Delegate,
|
||||
public Machine {
|
||||
|
||||
public:
|
||||
ConcreteMachine() :
|
||||
m6502_(*this),
|
||||
use_fast_tape_hack_(false),
|
||||
typer_delay_(2500000),
|
||||
keyboard_read_count_(0),
|
||||
keyboard_(new Keyboard),
|
||||
ram_top_(0xbfff),
|
||||
paged_rom_(rom_),
|
||||
microdisc_is_enabled_(false) {
|
||||
set_clock_rate(1000000);
|
||||
via_.set_interrupt_delegate(this);
|
||||
via_.keyboard = keyboard_;
|
||||
clear_all_keys();
|
||||
via_.tape->set_delegate(this);
|
||||
Memory::Fuzz(ram_, sizeof(ram_));
|
||||
}
|
||||
|
||||
void set_rom(ROM rom, const std::vector<uint8_t> &data) override final {
|
||||
switch(rom) {
|
||||
case BASIC11: basic11_rom_ = std::move(data); break;
|
||||
case BASIC10: basic10_rom_ = std::move(data); break;
|
||||
case Microdisc: microdisc_rom_ = std::move(data); break;
|
||||
case Colour:
|
||||
colour_rom_ = std::move(data);
|
||||
if(video_output_) video_output_->set_colour_rom(colour_rom_);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void set_key_state(uint16_t key, bool isPressed) override final {
|
||||
if(key == KeyNMI) {
|
||||
m6502_.set_nmi_line(isPressed);
|
||||
} else {
|
||||
if(isPressed)
|
||||
keyboard_->rows[key >> 8] |= (key & 0xff);
|
||||
else
|
||||
keyboard_->rows[key >> 8] &= ~(key & 0xff);
|
||||
}
|
||||
}
|
||||
|
||||
void clear_all_keys() override final {
|
||||
memset(keyboard_->rows, 0, sizeof(keyboard_->rows));
|
||||
}
|
||||
|
||||
void set_use_fast_tape_hack(bool activate) override final {
|
||||
use_fast_tape_hack_ = activate;
|
||||
}
|
||||
|
||||
void set_output_device(Outputs::CRT::OutputDevice output_device) override final {
|
||||
video_output_->set_output_device(output_device);
|
||||
}
|
||||
|
||||
// to satisfy ConfigurationTarget::Machine
|
||||
void configure_as_target(const StaticAnalyser::Target &target) override final {
|
||||
if(target.oric.has_microdisc) {
|
||||
microdisc_is_enabled_ = true;
|
||||
microdisc_did_change_paging_flags(µdisc_);
|
||||
microdisc_.set_delegate(this);
|
||||
}
|
||||
|
||||
if(target.loadingCommand.length()) {
|
||||
set_typer_for_string(target.loadingCommand.c_str());
|
||||
}
|
||||
|
||||
if(target.oric.use_atmos_rom) {
|
||||
memcpy(rom_, basic11_rom_.data(), std::min(basic11_rom_.size(), sizeof(rom_)));
|
||||
|
||||
is_using_basic11_ = true;
|
||||
tape_get_byte_address_ = 0xe6c9;
|
||||
scan_keyboard_address_ = 0xf495;
|
||||
tape_speed_address_ = 0x024d;
|
||||
} else {
|
||||
memcpy(rom_, basic10_rom_.data(), std::min(basic10_rom_.size(), sizeof(rom_)));
|
||||
|
||||
is_using_basic11_ = false;
|
||||
tape_get_byte_address_ = 0xe630;
|
||||
scan_keyboard_address_ = 0xf43c;
|
||||
tape_speed_address_ = 0x67;
|
||||
}
|
||||
|
||||
insert_media(target.media);
|
||||
}
|
||||
|
||||
bool insert_media(const StaticAnalyser::Media &media) override final {
|
||||
if(media.tapes.size()) {
|
||||
via_.tape->set_tape(media.tapes.front());
|
||||
}
|
||||
|
||||
int drive_index = 0;
|
||||
for(auto disk : media.disks) {
|
||||
if(drive_index < 4) microdisc_.set_disk(disk, drive_index);
|
||||
drive_index++;
|
||||
}
|
||||
|
||||
return !media.tapes.empty() || (!media.disks.empty() && microdisc_is_enabled_);
|
||||
}
|
||||
|
||||
// to satisfy CPU::MOS6502::BusHandler
|
||||
forceinline Cycles perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value) {
|
||||
if(address > ram_top_) {
|
||||
if(isReadOperation(operation)) *value = paged_rom_[address - ram_top_ - 1];
|
||||
|
||||
// 024D = 0 => fast; otherwise slow
|
||||
// E6C9 = read byte: return byte in A
|
||||
if(address == tape_get_byte_address_ && paged_rom_ == rom_ && use_fast_tape_hack_ && operation == CPU::MOS6502::BusOperation::ReadOpcode && via_.tape->has_tape() && !via_.tape->get_tape()->is_at_end()) {
|
||||
uint8_t next_byte = via_.tape->get_next_byte(!ram_[tape_speed_address_]);
|
||||
m6502_.set_value_of_register(CPU::MOS6502::A, next_byte);
|
||||
m6502_.set_value_of_register(CPU::MOS6502::Flags, next_byte ? 0 : CPU::MOS6502::Flag::Zero);
|
||||
*value = 0x60; // i.e. RTS
|
||||
}
|
||||
} else {
|
||||
if((address & 0xff00) == 0x0300) {
|
||||
if(microdisc_is_enabled_ && address >= 0x0310) {
|
||||
switch(address) {
|
||||
case 0x0310: case 0x0311: case 0x0312: case 0x0313:
|
||||
if(isReadOperation(operation)) *value = microdisc_.get_register(address);
|
||||
else microdisc_.set_register(address, *value);
|
||||
break;
|
||||
case 0x314: case 0x315: case 0x316: case 0x317:
|
||||
if(isReadOperation(operation)) *value = microdisc_.get_interrupt_request_register();
|
||||
else microdisc_.set_control_register(*value);
|
||||
break;
|
||||
case 0x318: case 0x319: case 0x31a: case 0x31b:
|
||||
if(isReadOperation(operation)) *value = microdisc_.get_data_request_register();
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
if(isReadOperation(operation)) *value = via_.get_register(address);
|
||||
else via_.set_register(address, *value);
|
||||
}
|
||||
} else {
|
||||
if(isReadOperation(operation))
|
||||
*value = ram_[address];
|
||||
else {
|
||||
if(address >= 0x9800 && address <= 0xc000) { update_video(); typer_delay_ = 0; }
|
||||
ram_[address] = *value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(typer_ && address == scan_keyboard_address_ && operation == CPU::MOS6502::BusOperation::ReadOpcode) {
|
||||
// the Oric 1 misses any key pressed on the very first entry into the read keyboard routine, so don't
|
||||
// do anything until at least the second, regardless of machine
|
||||
if(!keyboard_read_count_) keyboard_read_count_++;
|
||||
else if(!typer_->type_next_character()) {
|
||||
clear_all_keys();
|
||||
typer_.reset();
|
||||
}
|
||||
}
|
||||
|
||||
via_.run_for(Cycles(1));
|
||||
if(microdisc_is_enabled_) microdisc_.run_for(Cycles(8));
|
||||
cycles_since_video_update_++;
|
||||
return Cycles(1);
|
||||
}
|
||||
|
||||
forceinline void flush() {
|
||||
update_video();
|
||||
via_.flush();
|
||||
}
|
||||
|
||||
// to satisfy CRTMachine::Machine
|
||||
void setup_output(float aspect_ratio) override final {
|
||||
via_.ay8910.reset(new GI::AY38910::AY38910());
|
||||
via_.ay8910->set_clock_rate(1000000);
|
||||
video_output_.reset(new VideoOutput(ram_));
|
||||
if(!colour_rom_.empty()) video_output_->set_colour_rom(colour_rom_);
|
||||
}
|
||||
|
||||
void close_output() override final {
|
||||
video_output_.reset();
|
||||
via_.ay8910.reset();
|
||||
}
|
||||
|
||||
std::shared_ptr<Outputs::CRT::CRT> get_crt() override final {
|
||||
return video_output_->get_crt();
|
||||
}
|
||||
|
||||
std::shared_ptr<Outputs::Speaker> get_speaker() override final {
|
||||
return via_.ay8910;
|
||||
}
|
||||
|
||||
void run_for(const Cycles cycles) override final {
|
||||
m6502_.run_for(cycles);
|
||||
}
|
||||
|
||||
// to satisfy MOS::MOS6522IRQDelegate::Delegate
|
||||
void mos6522_did_change_interrupt_status(void *mos6522) override final {
|
||||
set_interrupt_line();
|
||||
}
|
||||
|
||||
// to satisfy Storage::Tape::BinaryTapePlayer::Delegate
|
||||
void tape_did_change_input(Storage::Tape::BinaryTapePlayer *tape_player) override final {
|
||||
// set CB1
|
||||
via_.set_control_line_input(VIA::Port::B, VIA::Line::One, !tape_player->get_input());
|
||||
}
|
||||
|
||||
// for Utility::TypeRecipient::Delegate
|
||||
void set_typer_for_string(const char *string) override final {
|
||||
std::unique_ptr<CharacterMapper> mapper(new CharacterMapper);
|
||||
Utility::TypeRecipient::set_typer_for_string(string, std::move(mapper));
|
||||
}
|
||||
|
||||
// for Microdisc::Delegate
|
||||
void microdisc_did_change_paging_flags(class Microdisc *microdisc) override final {
|
||||
int flags = microdisc->get_paging_flags();
|
||||
if(!(flags&Microdisc::PagingFlags::BASICDisable)) {
|
||||
ram_top_ = 0xbfff;
|
||||
paged_rom_ = rom_;
|
||||
} else {
|
||||
if(flags&Microdisc::PagingFlags::MicrodscDisable) {
|
||||
ram_top_ = 0xffff;
|
||||
} else {
|
||||
ram_top_ = 0xdfff;
|
||||
paged_rom_ = microdisc_rom_.data();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void wd1770_did_change_output(WD::WD1770 *wd1770) override final {
|
||||
set_interrupt_line();
|
||||
}
|
||||
|
||||
private:
|
||||
CPU::MOS6502::Processor<ConcreteMachine, false> m6502_;
|
||||
|
||||
// RAM and ROM
|
||||
std::vector<uint8_t> basic11_rom_, basic10_rom_, microdisc_rom_, colour_rom_;
|
||||
uint8_t ram_[65536], rom_[16384];
|
||||
Cycles cycles_since_video_update_;
|
||||
inline void update_video() {
|
||||
video_output_->run_for(cycles_since_video_update_.flush());
|
||||
}
|
||||
|
||||
// ROM bookkeeping
|
||||
bool is_using_basic11_;
|
||||
uint16_t tape_get_byte_address_, scan_keyboard_address_, tape_speed_address_;
|
||||
int keyboard_read_count_;
|
||||
|
||||
// Outputs
|
||||
std::unique_ptr<VideoOutput> video_output_;
|
||||
|
||||
// Keyboard
|
||||
class Keyboard {
|
||||
public:
|
||||
uint8_t row;
|
||||
uint8_t rows[8];
|
||||
};
|
||||
int typer_delay_;
|
||||
|
||||
// The tape
|
||||
class TapePlayer: public Storage::Tape::BinaryTapePlayer {
|
||||
public:
|
||||
TapePlayer() : Storage::Tape::BinaryTapePlayer(1000000) {}
|
||||
|
||||
inline uint8_t get_next_byte(bool fast) {
|
||||
return (uint8_t)parser_.get_next_byte(get_tape(), fast);
|
||||
}
|
||||
|
||||
private:
|
||||
Storage::Tape::Oric::Parser parser_;
|
||||
};
|
||||
bool use_fast_tape_hack_;
|
||||
|
||||
// VIA (which owns the tape and the AY)
|
||||
class VIA: public MOS::MOS6522<VIA>, public MOS::MOS6522IRQDelegate {
|
||||
public:
|
||||
VIA() :
|
||||
MOS::MOS6522<VIA>(),
|
||||
tape(new TapePlayer) {}
|
||||
|
||||
using MOS6522IRQDelegate::set_interrupt_status;
|
||||
|
||||
void set_control_line_output(Port port, Line line, bool value) {
|
||||
if(line) {
|
||||
if(port) ay_bdir_ = value; else ay_bc1_ = value;
|
||||
update_ay();
|
||||
}
|
||||
}
|
||||
|
||||
void set_port_output(Port port, uint8_t value, uint8_t direction_mask) {
|
||||
if(port) {
|
||||
keyboard->row = value;
|
||||
tape->set_motor_control(value & 0x40);
|
||||
} else {
|
||||
ay8910->set_data_input(value);
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t get_port_input(Port port) {
|
||||
if(port) {
|
||||
uint8_t column = ay8910->get_port_output(false) ^ 0xff;
|
||||
return (keyboard->rows[keyboard->row & 7] & column) ? 0x08 : 0x00;
|
||||
} else {
|
||||
return ay8910->get_data_output();
|
||||
}
|
||||
}
|
||||
|
||||
inline void run_for(const Cycles cycles) {
|
||||
cycles_since_ay_update_ += cycles;
|
||||
MOS::MOS6522<VIA>::run_for(cycles);
|
||||
tape->run_for(cycles);
|
||||
}
|
||||
|
||||
void flush() {
|
||||
ay8910->run_for(cycles_since_ay_update_.flush());
|
||||
ay8910->flush();
|
||||
}
|
||||
|
||||
std::shared_ptr<GI::AY38910::AY38910> ay8910;
|
||||
std::unique_ptr<TapePlayer> tape;
|
||||
std::shared_ptr<Keyboard> keyboard;
|
||||
|
||||
private:
|
||||
void update_ay() {
|
||||
ay8910->run_for(cycles_since_ay_update_.flush());
|
||||
ay8910->set_control_lines( (GI::AY38910::ControlLines)((ay_bdir_ ? GI::AY38910::BDIR : 0) | (ay_bc1_ ? GI::AY38910::BC1 : 0) | GI::AY38910::BC2));
|
||||
}
|
||||
bool ay_bdir_, ay_bc1_;
|
||||
Cycles cycles_since_ay_update_;
|
||||
};
|
||||
VIA via_;
|
||||
std::shared_ptr<Keyboard> keyboard_;
|
||||
|
||||
// the Microdisc, if in use
|
||||
class Microdisc microdisc_;
|
||||
bool microdisc_is_enabled_;
|
||||
uint16_t ram_top_;
|
||||
uint8_t *paged_rom_;
|
||||
|
||||
inline void set_interrupt_line() {
|
||||
m6502_.set_irq_line(
|
||||
via_.get_interrupt_line() ||
|
||||
(microdisc_is_enabled_ && microdisc_.get_interrupt_request_line()));
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
using namespace Oric;
|
||||
|
||||
Machine::Machine() :
|
||||
cycles_since_video_update_(0),
|
||||
use_fast_tape_hack_(false),
|
||||
typer_delay_(2500000),
|
||||
keyboard_read_count_(0),
|
||||
keyboard_(new Keyboard),
|
||||
ram_top_(0xbfff),
|
||||
paged_rom_(rom_),
|
||||
microdisc_is_enabled_(false) {
|
||||
set_clock_rate(1000000);
|
||||
via_.set_interrupt_delegate(this);
|
||||
via_.keyboard = keyboard_;
|
||||
clear_all_keys();
|
||||
via_.tape->set_delegate(this);
|
||||
Memory::Fuzz(ram_, sizeof(ram_));
|
||||
Machine *Machine::Oric() {
|
||||
return new ConcreteMachine;
|
||||
}
|
||||
|
||||
void Machine::configure_as_target(const StaticAnalyser::Target &target) {
|
||||
if(target.tapes.size()) {
|
||||
via_.tape->set_tape(target.tapes.front());
|
||||
}
|
||||
|
||||
if(target.loadingCommand.length()) { // TODO: and automatic loading option enabled
|
||||
set_typer_for_string(target.loadingCommand.c_str());
|
||||
}
|
||||
|
||||
if(target.oric.has_microdisc) {
|
||||
microdisc_is_enabled_ = true;
|
||||
microdisc_did_change_paging_flags(µdisc_);
|
||||
microdisc_.set_delegate(this);
|
||||
}
|
||||
|
||||
int drive_index = 0;
|
||||
for(auto disk : target.disks) {
|
||||
if(drive_index < 4) microdisc_.set_disk(disk, drive_index);
|
||||
drive_index++;
|
||||
}
|
||||
|
||||
if(target.oric.use_atmos_rom) {
|
||||
memcpy(rom_, basic11_rom_.data(), std::min(basic11_rom_.size(), sizeof(rom_)));
|
||||
|
||||
is_using_basic11_ = true;
|
||||
tape_get_byte_address_ = 0xe6c9;
|
||||
scan_keyboard_address_ = 0xf495;
|
||||
tape_speed_address_ = 0x024d;
|
||||
} else {
|
||||
memcpy(rom_, basic10_rom_.data(), std::min(basic10_rom_.size(), sizeof(rom_)));
|
||||
|
||||
is_using_basic11_ = false;
|
||||
tape_get_byte_address_ = 0xe630;
|
||||
scan_keyboard_address_ = 0xf43c;
|
||||
tape_speed_address_ = 0x67;
|
||||
}
|
||||
}
|
||||
|
||||
void Machine::set_rom(ROM rom, const std::vector<uint8_t> &data) {
|
||||
switch(rom) {
|
||||
case BASIC11: basic11_rom_ = std::move(data); break;
|
||||
case BASIC10: basic10_rom_ = std::move(data); break;
|
||||
case Microdisc: microdisc_rom_ = std::move(data); break;
|
||||
case Colour:
|
||||
colour_rom_ = std::move(data);
|
||||
if(video_output_) video_output_->set_colour_rom(colour_rom_);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uint16_t address, uint8_t *value) {
|
||||
if(address > ram_top_) {
|
||||
if(isReadOperation(operation)) *value = paged_rom_[address - ram_top_ - 1];
|
||||
|
||||
// 024D = 0 => fast; otherwise slow
|
||||
// E6C9 = read byte: return byte in A
|
||||
if(address == tape_get_byte_address_ && paged_rom_ == rom_ && use_fast_tape_hack_ && operation == CPU6502::BusOperation::ReadOpcode && via_.tape->has_tape() && !via_.tape->get_tape()->is_at_end()) {
|
||||
uint8_t next_byte = via_.tape->get_next_byte(!ram_[tape_speed_address_]);
|
||||
set_value_of_register(CPU6502::A, next_byte);
|
||||
set_value_of_register(CPU6502::Flags, next_byte ? 0 : CPU6502::Flag::Zero);
|
||||
*value = 0x60; // i.e. RTS
|
||||
}
|
||||
} else {
|
||||
if((address & 0xff00) == 0x0300) {
|
||||
if(microdisc_is_enabled_ && address >= 0x0310) {
|
||||
switch(address) {
|
||||
case 0x0310: case 0x0311: case 0x0312: case 0x0313:
|
||||
if(isReadOperation(operation)) *value = microdisc_.get_register(address);
|
||||
else microdisc_.set_register(address, *value);
|
||||
break;
|
||||
case 0x314: case 0x315: case 0x316: case 0x317:
|
||||
if(isReadOperation(operation)) *value = microdisc_.get_interrupt_request_register();
|
||||
else microdisc_.set_control_register(*value);
|
||||
break;
|
||||
case 0x318: case 0x319: case 0x31a: case 0x31b:
|
||||
if(isReadOperation(operation)) *value = microdisc_.get_data_request_register();
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
if(isReadOperation(operation)) *value = via_.get_register(address);
|
||||
else via_.set_register(address, *value);
|
||||
}
|
||||
} else {
|
||||
if(isReadOperation(operation))
|
||||
*value = ram_[address];
|
||||
else {
|
||||
if(address >= 0x9800 && address <= 0xc000) { update_video(); typer_delay_ = 0; }
|
||||
ram_[address] = *value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(typer_ && address == scan_keyboard_address_ && operation == CPU6502::BusOperation::ReadOpcode) {
|
||||
// the Oric 1 misses any key pressed on the very first entry into the read keyboard routine, so don't
|
||||
// do anything until at least the second, regardless of machine
|
||||
if(!keyboard_read_count_) keyboard_read_count_++;
|
||||
else if(!typer_->type_next_character()) {
|
||||
clear_all_keys();
|
||||
typer_.reset();
|
||||
}
|
||||
}
|
||||
|
||||
via_.run_for_cycles(1);
|
||||
if(microdisc_is_enabled_) microdisc_.run_for_cycles(8);
|
||||
cycles_since_video_update_++;
|
||||
return 1;
|
||||
}
|
||||
|
||||
void Machine::synchronise() {
|
||||
update_video();
|
||||
via_.synchronise();
|
||||
}
|
||||
|
||||
void Machine::update_video() {
|
||||
video_output_->run_for_cycles(cycles_since_video_update_);
|
||||
cycles_since_video_update_ = 0;
|
||||
}
|
||||
|
||||
void Machine::setup_output(float aspect_ratio) {
|
||||
via_.ay8910.reset(new GI::AY38910());
|
||||
via_.ay8910->set_clock_rate(1000000);
|
||||
video_output_.reset(new VideoOutput(ram_));
|
||||
if(!colour_rom_.empty()) video_output_->set_colour_rom(colour_rom_);
|
||||
}
|
||||
|
||||
void Machine::close_output() {
|
||||
video_output_.reset();
|
||||
via_.ay8910.reset();
|
||||
}
|
||||
|
||||
void Machine::mos6522_did_change_interrupt_status(void *mos6522) {
|
||||
set_interrupt_line();
|
||||
}
|
||||
|
||||
void Machine::set_key_state(uint16_t key, bool isPressed) {
|
||||
if(key == KeyNMI) {
|
||||
set_nmi_line(isPressed);
|
||||
} else {
|
||||
if(isPressed)
|
||||
keyboard_->rows[key >> 8] |= (key & 0xff);
|
||||
else
|
||||
keyboard_->rows[key >> 8] &= ~(key & 0xff);
|
||||
}
|
||||
}
|
||||
|
||||
void Machine::clear_all_keys() {
|
||||
memset(keyboard_->rows, 0, sizeof(keyboard_->rows));
|
||||
}
|
||||
|
||||
void Machine::set_use_fast_tape_hack(bool activate) {
|
||||
use_fast_tape_hack_ = activate;
|
||||
}
|
||||
|
||||
void Machine::set_output_device(Outputs::CRT::OutputDevice output_device) {
|
||||
video_output_->set_output_device(output_device);
|
||||
}
|
||||
|
||||
void Machine::tape_did_change_input(Storage::Tape::BinaryTapePlayer *tape_player) {
|
||||
// set CB1
|
||||
via_.set_control_line_input(VIA::Port::B, VIA::Line::One, tape_player->get_input());
|
||||
}
|
||||
|
||||
std::shared_ptr<Outputs::CRT::CRT> Machine::get_crt() {
|
||||
return video_output_->get_crt();
|
||||
}
|
||||
|
||||
std::shared_ptr<Outputs::Speaker> Machine::get_speaker() {
|
||||
return via_.ay8910;
|
||||
}
|
||||
|
||||
void Machine::run_for_cycles(int number_of_cycles) {
|
||||
CPU6502::Processor<Machine>::run_for_cycles(number_of_cycles);
|
||||
}
|
||||
|
||||
#pragma mark - The 6522
|
||||
|
||||
Machine::VIA::VIA() :
|
||||
MOS::MOS6522<Machine::VIA>(),
|
||||
cycles_since_ay_update_(0),
|
||||
tape(new TapePlayer) {}
|
||||
|
||||
void Machine::VIA::set_control_line_output(Port port, Line line, bool value) {
|
||||
if(line) {
|
||||
if(port) ay_bdir_ = value; else ay_bc1_ = value;
|
||||
update_ay();
|
||||
}
|
||||
}
|
||||
|
||||
void Machine::VIA::set_port_output(Port port, uint8_t value, uint8_t direction_mask) {
|
||||
if(port) {
|
||||
keyboard->row = value;
|
||||
tape->set_motor_control(value & 0x40);
|
||||
} else {
|
||||
ay8910->set_data_input(value);
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t Machine::VIA::get_port_input(Port port) {
|
||||
if(port) {
|
||||
uint8_t column = ay8910->get_port_output(false) ^ 0xff;
|
||||
return (keyboard->rows[keyboard->row & 7] & column) ? 0x08 : 0x00;
|
||||
} else {
|
||||
return ay8910->get_data_output();
|
||||
}
|
||||
}
|
||||
|
||||
void Machine::VIA::synchronise() {
|
||||
ay8910->run_for_cycles(cycles_since_ay_update_);
|
||||
ay8910->flush();
|
||||
cycles_since_ay_update_ = 0;
|
||||
}
|
||||
|
||||
void Machine::VIA::run_for_cycles(unsigned int number_of_cycles) {
|
||||
cycles_since_ay_update_ += number_of_cycles;
|
||||
MOS::MOS6522<VIA>::run_for_cycles(number_of_cycles);
|
||||
tape->run_for_cycles((int)number_of_cycles);
|
||||
}
|
||||
|
||||
void Machine::VIA::update_ay() {
|
||||
ay8910->run_for_cycles(cycles_since_ay_update_);
|
||||
cycles_since_ay_update_ = 0;
|
||||
ay8910->set_control_lines( (GI::AY38910::ControlLines)((ay_bdir_ ? GI::AY38910::BCDIR : 0) | (ay_bc1_ ? GI::AY38910::BC1 : 0) | GI::AY38910::BC2));
|
||||
}
|
||||
|
||||
#pragma mark - TapePlayer
|
||||
|
||||
Machine::TapePlayer::TapePlayer() :
|
||||
Storage::Tape::BinaryTapePlayer(1000000) {}
|
||||
|
||||
uint8_t Machine::TapePlayer::get_next_byte(bool fast) {
|
||||
return (uint8_t)parser_.get_next_byte(get_tape(), fast);
|
||||
}
|
||||
|
||||
#pragma mark - Microdisc
|
||||
|
||||
void Machine::microdisc_did_change_paging_flags(class Microdisc *microdisc) {
|
||||
int flags = microdisc->get_paging_flags();
|
||||
if(!(flags&Microdisc::PagingFlags::BASICDisable)) {
|
||||
ram_top_ = 0xbfff;
|
||||
paged_rom_ = rom_;
|
||||
} else {
|
||||
if(flags&Microdisc::PagingFlags::MicrodscDisable) {
|
||||
ram_top_ = 0xffff;
|
||||
} else {
|
||||
ram_top_ = 0xdfff;
|
||||
paged_rom_ = microdisc_rom_.data();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Machine::wd1770_did_change_output(WD::WD1770 *wd1770) {
|
||||
set_interrupt_line();
|
||||
}
|
||||
|
||||
void Machine::set_interrupt_line() {
|
||||
set_irq_line(
|
||||
via_.get_interrupt_line() ||
|
||||
(microdisc_is_enabled_ && microdisc_.get_interrupt_request_line()));
|
||||
}
|
||||
Machine::~Machine() {}
|
||||
|
||||
@@ -11,24 +11,17 @@
|
||||
|
||||
#include "../ConfigurationTarget.hpp"
|
||||
#include "../CRTMachine.hpp"
|
||||
#include "../Typer.hpp"
|
||||
|
||||
#include "../../Processors/6502/CPU6502.hpp"
|
||||
#include "../../Components/6522/6522.hpp"
|
||||
#include "../../Components/AY38910/AY38910.hpp"
|
||||
#include "../../Storage/Tape/Parsers/Oric.hpp"
|
||||
|
||||
#include "Video.hpp"
|
||||
#include "Microdisc.hpp"
|
||||
|
||||
#include "../../Storage/Tape/Tape.hpp"
|
||||
#include "../KeyboardMachine.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
|
||||
namespace Oric {
|
||||
|
||||
enum ROM {
|
||||
BASIC10, BASIC11, Microdisc, Colour
|
||||
};
|
||||
|
||||
enum Key: uint16_t {
|
||||
Key3 = 0x0000 | 0x80, KeyX = 0x0000 | 0x40, Key1 = 0x0000 | 0x20,
|
||||
KeyV = 0x0000 | 0x08, Key5 = 0x0000 | 0x04, KeyN = 0x0000 | 0x02, Key7 = 0x0000 | 0x01,
|
||||
@@ -48,126 +41,29 @@ enum Key: uint16_t {
|
||||
KeyForwardSlash = 0x0700 | 0x08, Key0 = 0x0700 | 0x04, KeyL = 0x0700 | 0x02, Key8 = 0x0700 | 0x01,
|
||||
|
||||
KeyNMI = 0xfffd,
|
||||
|
||||
TerminateSequence = 0xffff, NotMapped = 0xfffe
|
||||
};
|
||||
|
||||
enum ROM {
|
||||
BASIC10, BASIC11, Microdisc, Colour
|
||||
};
|
||||
|
||||
/*!
|
||||
Models an Oric 1/Atmos with or without a Microdisc.
|
||||
*/
|
||||
class Machine:
|
||||
public CPU6502::Processor<Machine>,
|
||||
public CRTMachine::Machine,
|
||||
public ConfigurationTarget::Machine,
|
||||
public MOS::MOS6522IRQDelegate::Delegate,
|
||||
public Utility::TypeRecipient,
|
||||
public Storage::Tape::BinaryTapePlayer::Delegate,
|
||||
public Microdisc::Delegate {
|
||||
|
||||
public KeyboardMachine::Machine {
|
||||
public:
|
||||
Machine();
|
||||
virtual ~Machine();
|
||||
|
||||
void set_rom(ROM rom, const std::vector<uint8_t> &data);
|
||||
void set_key_state(uint16_t key, bool isPressed);
|
||||
void clear_all_keys();
|
||||
/// Creates and returns an Oric.
|
||||
static Machine *Oric();
|
||||
|
||||
void set_use_fast_tape_hack(bool activate);
|
||||
void set_output_device(Outputs::CRT::OutputDevice output_device);
|
||||
/// Sets the contents of @c rom to @c data. Assumed to be a setup step; has no effect once a machine is running.
|
||||
virtual void set_rom(ROM rom, const std::vector<uint8_t> &data) = 0;
|
||||
|
||||
// to satisfy ConfigurationTarget::Machine
|
||||
void configure_as_target(const StaticAnalyser::Target &target);
|
||||
/// Enables or disables turbo-speed tape loading.
|
||||
virtual void set_use_fast_tape_hack(bool activate) = 0;
|
||||
|
||||
// to satisfy CPU6502::Processor
|
||||
unsigned int perform_bus_operation(CPU6502::BusOperation operation, uint16_t address, uint8_t *value);
|
||||
void synchronise();
|
||||
|
||||
// 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_cycles(int number_of_cycles);
|
||||
|
||||
// to satisfy MOS::MOS6522IRQDelegate::Delegate
|
||||
void mos6522_did_change_interrupt_status(void *mos6522);
|
||||
|
||||
// to satisfy Storage::Tape::BinaryTapePlayer::Delegate
|
||||
void tape_did_change_input(Storage::Tape::BinaryTapePlayer *tape_player);
|
||||
|
||||
// for Utility::TypeRecipient::Delegate
|
||||
uint16_t *sequence_for_character(Utility::Typer *typer, char character);
|
||||
|
||||
// for Microdisc::Delegate
|
||||
void microdisc_did_change_paging_flags(class Microdisc *microdisc);
|
||||
void wd1770_did_change_output(WD::WD1770 *wd1770);
|
||||
|
||||
private:
|
||||
// RAM and ROM
|
||||
std::vector<uint8_t> basic11_rom_, basic10_rom_, microdisc_rom_, colour_rom_;
|
||||
uint8_t ram_[65536], rom_[16384];
|
||||
int cycles_since_video_update_;
|
||||
inline void update_video();
|
||||
|
||||
// ROM bookkeeping
|
||||
bool is_using_basic11_;
|
||||
uint16_t tape_get_byte_address_, scan_keyboard_address_, tape_speed_address_;
|
||||
int keyboard_read_count_;
|
||||
|
||||
// Outputs
|
||||
std::unique_ptr<VideoOutput> video_output_;
|
||||
|
||||
// Keyboard
|
||||
class Keyboard {
|
||||
public:
|
||||
uint8_t row;
|
||||
uint8_t rows[8];
|
||||
};
|
||||
int typer_delay_;
|
||||
|
||||
// The tape
|
||||
class TapePlayer: public Storage::Tape::BinaryTapePlayer {
|
||||
public:
|
||||
TapePlayer();
|
||||
uint8_t get_next_byte(bool fast);
|
||||
|
||||
private:
|
||||
Storage::Tape::Oric::Parser parser_;
|
||||
};
|
||||
bool use_fast_tape_hack_;
|
||||
|
||||
// VIA (which owns the tape and the AY)
|
||||
class VIA: public MOS::MOS6522<VIA>, public MOS::MOS6522IRQDelegate {
|
||||
public:
|
||||
VIA();
|
||||
using MOS6522IRQDelegate::set_interrupt_status;
|
||||
|
||||
void set_control_line_output(Port port, Line line, bool value);
|
||||
void set_port_output(Port port, uint8_t value, uint8_t direction_mask);
|
||||
uint8_t get_port_input(Port port);
|
||||
inline void run_for_cycles(unsigned int number_of_cycles);
|
||||
|
||||
std::shared_ptr<GI::AY38910> ay8910;
|
||||
std::unique_ptr<TapePlayer> tape;
|
||||
std::shared_ptr<Keyboard> keyboard;
|
||||
|
||||
void synchronise();
|
||||
|
||||
private:
|
||||
void update_ay();
|
||||
bool ay_bdir_, ay_bc1_;
|
||||
unsigned int cycles_since_ay_update_;
|
||||
};
|
||||
VIA via_;
|
||||
std::shared_ptr<Keyboard> keyboard_;
|
||||
|
||||
// the Microdisc, if in use
|
||||
class Microdisc microdisc_;
|
||||
bool microdisc_is_enabled_;
|
||||
uint16_t ram_top_;
|
||||
uint8_t *paged_rom_;
|
||||
|
||||
inline void set_interrupt_line();
|
||||
/// Sets the type of display the Oric is connected to.
|
||||
virtual void set_output_device(Outputs::CRT::OutputDevice output_device) = 0;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -74,13 +74,14 @@ std::shared_ptr<Outputs::CRT::CRT> VideoOutput::get_crt() {
|
||||
return crt_;
|
||||
}
|
||||
|
||||
void VideoOutput::run_for_cycles(int number_of_cycles) {
|
||||
void VideoOutput::run_for(const Cycles cycles) {
|
||||
// Vertical: 0–39: pixels; otherwise blank; 48–53 sync, 54–56 colour burst
|
||||
// Horizontal: 0–223: pixels; otherwise blank; 256–259 sync
|
||||
|
||||
#define clamp(action) \
|
||||
if(cycles_run_for <= number_of_cycles) { action; } else cycles_run_for = number_of_cycles;
|
||||
|
||||
int number_of_cycles = cycles.as_int();
|
||||
while(number_of_cycles) {
|
||||
int h_counter = counter_ & 63;
|
||||
int cycles_run_for = 0;
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
#define Machines_Oric_Video_hpp
|
||||
|
||||
#include "../../Outputs/CRT/CRT.hpp"
|
||||
#include "../../ClockReceiver/ClockReceiver.hpp"
|
||||
|
||||
namespace Oric {
|
||||
|
||||
@@ -17,7 +18,7 @@ class VideoOutput {
|
||||
public:
|
||||
VideoOutput(uint8_t *memory);
|
||||
std::shared_ptr<Outputs::CRT::CRT> get_crt();
|
||||
void run_for_cycles(int number_of_cycles);
|
||||
void run_for(const Cycles cycles);
|
||||
void set_colour_rom(const std::vector<uint8_t> &rom);
|
||||
void set_output_device(Outputs::CRT::OutputDevice output_device);
|
||||
|
||||
|
||||
@@ -11,14 +11,19 @@
|
||||
|
||||
using namespace Utility;
|
||||
|
||||
Typer::Typer(const char *string, int delay, int frequency, Delegate *delegate) :
|
||||
counter_(-delay), frequency_(frequency), string_pointer_(0), delegate_(delegate), phase_(0) {
|
||||
Typer::Typer(const char *string, HalfCycles delay, HalfCycles frequency, std::unique_ptr<CharacterMapper> character_mapper, Delegate *delegate) :
|
||||
counter_(-delay),
|
||||
frequency_(frequency),
|
||||
string_pointer_(0),
|
||||
delegate_(delegate),
|
||||
phase_(0),
|
||||
character_mapper_(std::move(character_mapper)) {
|
||||
size_t string_size = strlen(string) + 3;
|
||||
string_ = (char *)malloc(string_size);
|
||||
snprintf(string_, strlen(string) + 3, "%c%s%c", Typer::BeginString, string, Typer::EndString);
|
||||
snprintf(string_, string_size, "%c%s%c", Typer::BeginString, string, Typer::EndString);
|
||||
}
|
||||
|
||||
void Typer::update(int duration) {
|
||||
void Typer::run_for(const HalfCycles duration) {
|
||||
if(string_) {
|
||||
if(counter_ < 0 && counter_ + duration >= 0) {
|
||||
if(!type_next_character()) {
|
||||
@@ -36,10 +41,26 @@ void Typer::update(int duration) {
|
||||
}
|
||||
}
|
||||
|
||||
bool Typer::try_type_next_character() {
|
||||
uint16_t *sequence = character_mapper_->sequence_for_character(string_[string_pointer_]);
|
||||
|
||||
if(!sequence || sequence[0] == CharacterMapper::NotMapped) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if(!phase_) delegate_->clear_all_keys();
|
||||
else {
|
||||
delegate_->set_key_state(sequence[phase_ - 1], true);
|
||||
return sequence[phase_] != CharacterMapper::EndSequence;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Typer::type_next_character() {
|
||||
if(string_ == nullptr) return false;
|
||||
|
||||
if(delegate_->typer_set_next_character(this, string_[string_pointer_], phase_)) {
|
||||
if(!try_type_next_character()) {
|
||||
phase_ = 0;
|
||||
if(!string_[string_pointer_]) {
|
||||
free(string_);
|
||||
@@ -59,21 +80,11 @@ Typer::~Typer() {
|
||||
free(string_);
|
||||
}
|
||||
|
||||
#pragma mark - Delegate
|
||||
#pragma mark - Character mapper
|
||||
|
||||
bool Typer::Delegate::typer_set_next_character(Utility::Typer *typer, char character, int phase) {
|
||||
uint16_t *sequence = sequence_for_character(typer, character);
|
||||
if(!sequence) return true;
|
||||
|
||||
if(!phase) clear_all_keys();
|
||||
else {
|
||||
set_key_state(sequence[phase - 1], true);
|
||||
return sequence[phase] == Typer::Delegate::EndSequence;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
uint16_t *Typer::Delegate::sequence_for_character(Typer *typer, char character) {
|
||||
return nullptr;
|
||||
uint16_t *CharacterMapper::table_lookup_sequence_for_character(KeySequence *sequences, size_t length, char character) {
|
||||
size_t ucharacter = (size_t)((unsigned char)character);
|
||||
if(ucharacter > (length / sizeof(KeySequence))) return nullptr;
|
||||
if(sequences[ucharacter][0] == NotMapped) return nullptr;
|
||||
return sequences[ucharacter];
|
||||
}
|
||||
|
||||
@@ -11,53 +11,117 @@
|
||||
|
||||
#include <memory>
|
||||
#include "KeyboardMachine.hpp"
|
||||
#include "../ClockReceiver/ClockReceiver.hpp"
|
||||
|
||||
namespace Utility {
|
||||
|
||||
/*!
|
||||
An interface that provides a mapping from logical characters to the sequence of keys
|
||||
necessary to type that character on a given machine.
|
||||
*/
|
||||
class CharacterMapper {
|
||||
public:
|
||||
/// @returns The EndSequence-terminated sequence of keys that would cause @c character to be typed.
|
||||
virtual uint16_t *sequence_for_character(char character) = 0;
|
||||
|
||||
/// Terminates a key sequence.
|
||||
static const uint16_t EndSequence = 0xffff;
|
||||
|
||||
/*!
|
||||
If returned as the first entry in a key sequence, indicates that the requested character
|
||||
cannot be mapped.
|
||||
*/
|
||||
static const uint16_t NotMapped = 0xfffe;
|
||||
|
||||
protected:
|
||||
typedef uint16_t KeySequence[16];
|
||||
|
||||
/*!
|
||||
Provided in the base class as a convenience: given the lookup table of key sequences @c sequences,
|
||||
with @c length entries, returns the sequence for character @c character if it exists; otherwise
|
||||
returns @c nullptr.
|
||||
*/
|
||||
uint16_t *table_lookup_sequence_for_character(KeySequence *sequences, size_t length, char character);
|
||||
};
|
||||
|
||||
/*!
|
||||
Provides a stateful mechanism for typing a sequence of characters. Each character is mapped to a key sequence
|
||||
by a character mapper. That key sequence is then replayed to a delegate.
|
||||
|
||||
Being given a delay and frequency at construction, the run_for interface can be used to produce time-based
|
||||
typing. Alternatively, an owner may decline to use run_for and simply call type_next_character each time a
|
||||
fresh key transition is ready to be consumed.
|
||||
*/
|
||||
class Typer {
|
||||
public:
|
||||
class Delegate: public KeyboardMachine::Machine {
|
||||
public:
|
||||
virtual bool typer_set_next_character(Typer *typer, char character, int phase);
|
||||
virtual void typer_reset(Typer *typer) = 0;
|
||||
|
||||
virtual uint16_t *sequence_for_character(Typer *typer, char character);
|
||||
|
||||
const uint16_t EndSequence = 0xffff;
|
||||
};
|
||||
|
||||
Typer(const char *string, int delay, int frequency, Delegate *delegate);
|
||||
Typer(const char *string, HalfCycles delay, HalfCycles frequency, std::unique_ptr<CharacterMapper> character_mapper, Delegate *delegate);
|
||||
~Typer();
|
||||
void update(int duration);
|
||||
|
||||
void run_for(const HalfCycles duration);
|
||||
bool type_next_character();
|
||||
|
||||
bool is_completed();
|
||||
|
||||
const char BeginString = 0x02; // i.e. ASCII start of text
|
||||
const char EndString = 0x03; // i.e. ASCII end of text
|
||||
|
||||
private:
|
||||
char *string_;
|
||||
int frequency_;
|
||||
int counter_;
|
||||
int phase_;
|
||||
Delegate *delegate_;
|
||||
size_t string_pointer_;
|
||||
|
||||
HalfCycles frequency_;
|
||||
HalfCycles counter_;
|
||||
int phase_;
|
||||
|
||||
Delegate *delegate_;
|
||||
std::unique_ptr<CharacterMapper> character_mapper_;
|
||||
|
||||
bool try_type_next_character();
|
||||
};
|
||||
|
||||
/*!
|
||||
Provides a default base class for type recipients: classes that want to attach a single typer at a time and
|
||||
which may or may not want to nominate an initial delay and typing frequency.
|
||||
*/
|
||||
class TypeRecipient: public Typer::Delegate {
|
||||
public:
|
||||
void set_typer_for_string(const char *string) {
|
||||
typer_.reset(new Typer(string, get_typer_delay(), get_typer_frequency(), this));
|
||||
/// Attaches a typer to this class that will type @c string using @c character_mapper as a source.
|
||||
void set_typer_for_string(const char *string, std::unique_ptr<CharacterMapper> character_mapper) {
|
||||
typer_.reset(new Typer(string, get_typer_delay(), get_typer_frequency(), std::move(character_mapper), this));
|
||||
}
|
||||
|
||||
/*!
|
||||
Provided as a hook for subclasses to implement so that external callers can install a typer
|
||||
without needing inside knowledge as to where the character mapper comes from.
|
||||
*/
|
||||
virtual void set_typer_for_string(const char *string) = 0;
|
||||
|
||||
/*!
|
||||
Provided in order to conform to that part of the Typer::Delegate interface that goes above and
|
||||
beyond KeyboardMachine::Machine; responds to the end of typing by clearing all keys.
|
||||
*/
|
||||
void typer_reset(Typer *typer) {
|
||||
clear_all_keys();
|
||||
typer_.reset();
|
||||
|
||||
// It's unsafe to deallocate typer right now, since it is the caller, but also it has a small
|
||||
// memory footprint and it's desireable not to imply that the subclass need call it any more.
|
||||
// So shuffle it off into a siding.
|
||||
previous_typer_ = std::move(typer_);
|
||||
typer_ = nullptr;
|
||||
}
|
||||
|
||||
protected:
|
||||
virtual int get_typer_delay() { return 0; }
|
||||
virtual int get_typer_frequency() { return 0; }
|
||||
virtual HalfCycles get_typer_delay() { return HalfCycles(0); }
|
||||
virtual HalfCycles get_typer_frequency() { return HalfCycles(0); }
|
||||
std::unique_ptr<Typer> typer_;
|
||||
|
||||
private:
|
||||
std::unique_ptr<Typer> previous_typer_;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
159
Machines/ZX8081/CharacterMapper.cpp
Normal file
159
Machines/ZX8081/CharacterMapper.cpp
Normal file
@@ -0,0 +1,159 @@
|
||||
//
|
||||
// CharacterMapper.cpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 03/08/2017.
|
||||
// Copyright © 2017 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#include "CharacterMapper.hpp"
|
||||
#include "ZX8081.hpp"
|
||||
|
||||
using namespace ZX8081;
|
||||
|
||||
CharacterMapper::CharacterMapper(bool is_zx81) : is_zx81_(is_zx81) {}
|
||||
|
||||
uint16_t *CharacterMapper::sequence_for_character(char character) {
|
||||
#define KEYS(...) {__VA_ARGS__, EndSequence}
|
||||
#define SHIFT(...) {KeyShift, __VA_ARGS__, EndSequence}
|
||||
#define X {NotMapped}
|
||||
static KeySequence zx81_key_sequences[] = {
|
||||
/* NUL */ X, /* SOH */ X,
|
||||
/* STX */ X, /* ETX */ X,
|
||||
/* EOT */ X, /* ENQ */ X,
|
||||
/* ACK */ X, /* BEL */ X,
|
||||
/* BS */ SHIFT(Key0), /* HT */ X,
|
||||
/* LF */ KEYS(KeyEnter), /* VT */ X,
|
||||
/* FF */ X, /* CR */ X,
|
||||
/* SO */ X, /* SI */ X,
|
||||
/* DLE */ X, /* DC1 */ X,
|
||||
/* DC2 */ X, /* DC3 */ X,
|
||||
/* DC4 */ X, /* NAK */ X,
|
||||
/* SYN */ X, /* ETB */ X,
|
||||
/* CAN */ X, /* EM */ X,
|
||||
/* SUB */ X, /* ESC */ X,
|
||||
/* FS */ X, /* GS */ X,
|
||||
/* RS */ X, /* US */ X,
|
||||
/* space */ KEYS(KeySpace), /* ! */ X,
|
||||
/* " */ SHIFT(KeyP), /* # */ X,
|
||||
/* $ */ SHIFT(KeyU), /* % */ X,
|
||||
/* & */ X, /* ' */ X,
|
||||
/* ( */ SHIFT(KeyI), /* ) */ SHIFT(KeyO),
|
||||
/* * */ SHIFT(KeyB), /* + */ SHIFT(KeyK),
|
||||
/* , */ SHIFT(KeyDot), /* - */ SHIFT(KeyJ),
|
||||
/* . */ KEYS(KeyDot), /* / */ SHIFT(KeyV),
|
||||
/* 0 */ KEYS(Key0), /* 1 */ KEYS(Key1),
|
||||
/* 2 */ KEYS(Key2), /* 3 */ KEYS(Key3),
|
||||
/* 4 */ KEYS(Key4), /* 5 */ KEYS(Key5),
|
||||
/* 6 */ KEYS(Key6), /* 7 */ KEYS(Key7),
|
||||
/* 8 */ KEYS(Key8), /* 9 */ KEYS(Key9),
|
||||
/* : */ SHIFT(KeyZ), /* ; */ SHIFT(KeyX),
|
||||
/* < */ SHIFT(KeyN), /* = */ SHIFT(KeyL),
|
||||
/* > */ SHIFT(KeyM), /* ? */ SHIFT(KeyC),
|
||||
/* @ */ X, /* A */ KEYS(KeyA),
|
||||
/* B */ KEYS(KeyB), /* C */ KEYS(KeyC),
|
||||
/* D */ KEYS(KeyD), /* E */ KEYS(KeyE),
|
||||
/* F */ KEYS(KeyF), /* G */ KEYS(KeyG),
|
||||
/* H */ KEYS(KeyH), /* I */ KEYS(KeyI),
|
||||
/* J */ KEYS(KeyJ), /* K */ KEYS(KeyK),
|
||||
/* L */ KEYS(KeyL), /* M */ KEYS(KeyM),
|
||||
/* N */ KEYS(KeyN), /* O */ KEYS(KeyO),
|
||||
/* P */ KEYS(KeyP), /* Q */ KEYS(KeyQ),
|
||||
/* R */ KEYS(KeyR), /* S */ KEYS(KeyS),
|
||||
/* T */ KEYS(KeyT), /* U */ KEYS(KeyU),
|
||||
/* V */ KEYS(KeyV), /* W */ KEYS(KeyW),
|
||||
/* X */ KEYS(KeyX), /* Y */ KEYS(KeyY),
|
||||
/* Z */ KEYS(KeyZ), /* [ */ X,
|
||||
/* \ */ X, /* ] */ X,
|
||||
/* ^ */ X, /* _ */ X,
|
||||
/* ` */ X, /* a */ KEYS(KeyA),
|
||||
/* b */ KEYS(KeyB), /* c */ KEYS(KeyC),
|
||||
/* d */ KEYS(KeyD), /* e */ KEYS(KeyE),
|
||||
/* f */ KEYS(KeyF), /* g */ KEYS(KeyG),
|
||||
/* h */ KEYS(KeyH), /* i */ KEYS(KeyI),
|
||||
/* j */ KEYS(KeyJ), /* k */ KEYS(KeyK),
|
||||
/* l */ KEYS(KeyL), /* m */ KEYS(KeyM),
|
||||
/* n */ KEYS(KeyN), /* o */ KEYS(KeyO),
|
||||
/* p */ KEYS(KeyP), /* q */ KEYS(KeyQ),
|
||||
/* r */ KEYS(KeyR), /* s */ KEYS(KeyS),
|
||||
/* t */ KEYS(KeyT), /* u */ KEYS(KeyU),
|
||||
/* v */ KEYS(KeyV), /* w */ KEYS(KeyW),
|
||||
/* x */ KEYS(KeyX), /* y */ KEYS(KeyY),
|
||||
/* z */ KEYS(KeyZ), /* { */ X,
|
||||
/* | */ X, /* } */ X,
|
||||
};
|
||||
|
||||
static KeySequence zx80_key_sequences[] = {
|
||||
/* NUL */ X, /* SOH */ X,
|
||||
/* STX */ X, /* ETX */ X,
|
||||
/* EOT */ X, /* ENQ */ X,
|
||||
/* ACK */ X, /* BEL */ X,
|
||||
/* BS */ SHIFT(Key0), /* HT */ X,
|
||||
/* LF */ KEYS(KeyEnter), /* VT */ X,
|
||||
/* FF */ X, /* CR */ X,
|
||||
/* SO */ X, /* SI */ X,
|
||||
/* DLE */ X, /* DC1 */ X,
|
||||
/* DC2 */ X, /* DC3 */ X,
|
||||
/* DC4 */ X, /* NAK */ X,
|
||||
/* SYN */ X, /* ETB */ X,
|
||||
/* CAN */ X, /* EM */ X,
|
||||
/* SUB */ X, /* ESC */ X,
|
||||
/* FS */ X, /* GS */ X,
|
||||
/* RS */ X, /* US */ X,
|
||||
/* space */ KEYS(KeySpace), /* ! */ X,
|
||||
/* " */ SHIFT(KeyY), /* # */ X,
|
||||
/* $ */ SHIFT(KeyU), /* % */ X,
|
||||
/* & */ X, /* ' */ X,
|
||||
/* ( */ SHIFT(KeyI), /* ) */ SHIFT(KeyO),
|
||||
/* * */ SHIFT(KeyP), /* + */ SHIFT(KeyK),
|
||||
/* , */ SHIFT(KeyDot), /* - */ SHIFT(KeyJ),
|
||||
/* . */ KEYS(KeyDot), /* / */ SHIFT(KeyV),
|
||||
/* 0 */ KEYS(Key0), /* 1 */ KEYS(Key1),
|
||||
/* 2 */ KEYS(Key2), /* 3 */ KEYS(Key3),
|
||||
/* 4 */ KEYS(Key4), /* 5 */ KEYS(Key5),
|
||||
/* 6 */ KEYS(Key6), /* 7 */ KEYS(Key7),
|
||||
/* 8 */ KEYS(Key8), /* 9 */ KEYS(Key9),
|
||||
/* : */ SHIFT(KeyZ), /* ; */ SHIFT(KeyX),
|
||||
/* < */ SHIFT(KeyN), /* = */ SHIFT(KeyL),
|
||||
/* > */ SHIFT(KeyM), /* ? */ SHIFT(KeyC),
|
||||
/* @ */ X, /* A */ KEYS(KeyA),
|
||||
/* B */ KEYS(KeyB), /* C */ KEYS(KeyC),
|
||||
/* D */ KEYS(KeyD), /* E */ KEYS(KeyE),
|
||||
/* F */ KEYS(KeyF), /* G */ KEYS(KeyG),
|
||||
/* H */ KEYS(KeyH), /* I */ KEYS(KeyI),
|
||||
/* J */ KEYS(KeyJ), /* K */ KEYS(KeyK),
|
||||
/* L */ KEYS(KeyL), /* M */ KEYS(KeyM),
|
||||
/* N */ KEYS(KeyN), /* O */ KEYS(KeyO),
|
||||
/* P */ KEYS(KeyP), /* Q */ KEYS(KeyQ),
|
||||
/* R */ KEYS(KeyR), /* S */ KEYS(KeyS),
|
||||
/* T */ KEYS(KeyT), /* U */ KEYS(KeyU),
|
||||
/* V */ KEYS(KeyV), /* W */ KEYS(KeyW),
|
||||
/* X */ KEYS(KeyX), /* Y */ KEYS(KeyY),
|
||||
/* Z */ KEYS(KeyZ), /* [ */ X,
|
||||
/* \ */ X, /* ] */ X,
|
||||
/* ^ */ X, /* _ */ X,
|
||||
/* ` */ X, /* a */ KEYS(KeyA),
|
||||
/* b */ KEYS(KeyB), /* c */ KEYS(KeyC),
|
||||
/* d */ KEYS(KeyD), /* e */ KEYS(KeyE),
|
||||
/* f */ KEYS(KeyF), /* g */ KEYS(KeyG),
|
||||
/* h */ KEYS(KeyH), /* i */ KEYS(KeyI),
|
||||
/* j */ KEYS(KeyJ), /* k */ KEYS(KeyK),
|
||||
/* l */ KEYS(KeyL), /* m */ KEYS(KeyM),
|
||||
/* n */ KEYS(KeyN), /* o */ KEYS(KeyO),
|
||||
/* p */ KEYS(KeyP), /* q */ KEYS(KeyQ),
|
||||
/* r */ KEYS(KeyR), /* s */ KEYS(KeyS),
|
||||
/* t */ KEYS(KeyT), /* u */ KEYS(KeyU),
|
||||
/* v */ KEYS(KeyV), /* w */ KEYS(KeyW),
|
||||
/* x */ KEYS(KeyX), /* y */ KEYS(KeyY),
|
||||
/* z */ KEYS(KeyZ), /* { */ X,
|
||||
/* | */ X, /* } */ X,
|
||||
};
|
||||
#undef KEYS
|
||||
#undef SHIFT
|
||||
#undef X
|
||||
|
||||
if(is_zx81_)
|
||||
return table_lookup_sequence_for_character(zx81_key_sequences, sizeof(zx81_key_sequences), character);
|
||||
else
|
||||
return table_lookup_sequence_for_character(zx80_key_sequences, sizeof(zx80_key_sequences), character);
|
||||
}
|
||||
27
Machines/ZX8081/CharacterMapper.hpp
Normal file
27
Machines/ZX8081/CharacterMapper.hpp
Normal file
@@ -0,0 +1,27 @@
|
||||
//
|
||||
// CharacterMapper.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 03/08/2017.
|
||||
// Copyright © 2017 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef Machines_ZX8081_CharacterMapper_hpp
|
||||
#define Machines_ZX8081_CharacterMapper_hpp
|
||||
|
||||
#include "../Typer.hpp"
|
||||
|
||||
namespace ZX8081 {
|
||||
|
||||
class CharacterMapper: public ::Utility::CharacterMapper {
|
||||
public:
|
||||
CharacterMapper(bool is_zx81);
|
||||
uint16_t *sequence_for_character(char character);
|
||||
|
||||
private:
|
||||
bool is_zx81_;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif /* CharacterMapper_hpp */
|
||||
109
Machines/ZX8081/Video.cpp
Normal file
109
Machines/ZX8081/Video.cpp
Normal file
@@ -0,0 +1,109 @@
|
||||
//
|
||||
// Video.cpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 06/06/2017.
|
||||
// Copyright © 2017 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#include "Video.hpp"
|
||||
|
||||
using namespace ZX8081;
|
||||
|
||||
Video::Video() :
|
||||
crt_(new Outputs::CRT::CRT(207 * 2, 1, Outputs::CRT::DisplayType::PAL50, 1)),
|
||||
line_data_(nullptr),
|
||||
line_data_pointer_(nullptr),
|
||||
cycles_since_update_(0),
|
||||
sync_(false) {
|
||||
|
||||
// Set a composite sampling function that assumes 8bpp input grayscale.
|
||||
// TODO: lessen this to 1bpp.
|
||||
crt_->set_composite_sampling_function(
|
||||
"float composite_sample(usampler2D sampler, vec2 coordinate, vec2 icoordinate, float phase, float amplitude)"
|
||||
"{"
|
||||
"return float(texture(texID, coordinate).r) / 255.0;"
|
||||
"}");
|
||||
|
||||
// Show only the centre 80% of the TV frame.
|
||||
crt_->set_visible_area(Outputs::CRT::Rect(0.1f, 0.1f, 0.8f, 0.8f));
|
||||
}
|
||||
|
||||
void Video::run_for(const HalfCycles half_cycles) {
|
||||
// Just keep a running total of the amount of time that remains owed to the CRT.
|
||||
cycles_since_update_ += (unsigned int)half_cycles.as_int();
|
||||
}
|
||||
|
||||
void Video::flush() {
|
||||
flush(sync_);
|
||||
}
|
||||
|
||||
void Video::flush(bool next_sync) {
|
||||
if(sync_) {
|
||||
// If in sync, that takes priority. Output the proper amount of sync.
|
||||
crt_->output_sync(cycles_since_update_);
|
||||
} else {
|
||||
// If not presently in sync, then...
|
||||
|
||||
if(line_data_) {
|
||||
// If there is output data queued, output it either if it's being interrupted by
|
||||
// sync, or if we're past its end anyway. Otherwise let it be.
|
||||
unsigned int data_length = (unsigned int)(line_data_pointer_ - line_data_);
|
||||
if(data_length < cycles_since_update_ || next_sync) {
|
||||
unsigned int output_length = std::min(data_length, cycles_since_update_);
|
||||
crt_->output_data(output_length, 1);
|
||||
line_data_pointer_ = line_data_ = nullptr;
|
||||
cycles_since_update_ -= output_length;
|
||||
} else return;
|
||||
}
|
||||
|
||||
// Any pending pixels being dealt with, pad with the white level.
|
||||
uint8_t *colour_pointer = (uint8_t *)crt_->allocate_write_area(1);
|
||||
if(colour_pointer) *colour_pointer = 0xff;
|
||||
crt_->output_level(cycles_since_update_);
|
||||
}
|
||||
|
||||
cycles_since_update_ = 0;
|
||||
}
|
||||
|
||||
void Video::set_sync(bool sync) {
|
||||
// Do nothing if sync hasn't changed.
|
||||
if(sync_ == sync) return;
|
||||
|
||||
// Complete whatever was being drawn, and update sync.
|
||||
flush(sync);
|
||||
sync_ = sync;
|
||||
}
|
||||
|
||||
void Video::output_byte(uint8_t byte) {
|
||||
// Complete whatever was going on.
|
||||
if(sync_) return;
|
||||
flush();
|
||||
|
||||
// Grab a buffer if one isn't already available.
|
||||
if(!line_data_) {
|
||||
line_data_pointer_ = line_data_ = crt_->allocate_write_area(320);
|
||||
}
|
||||
|
||||
// If a buffer was obtained, serialise the new pixels.
|
||||
if(line_data_) {
|
||||
// If the buffer is full, output it now and obtain a new one
|
||||
if(line_data_pointer_ - line_data_ == 320) {
|
||||
crt_->output_data(320, 1);
|
||||
cycles_since_update_ -= 320;
|
||||
line_data_pointer_ = line_data_ = crt_->allocate_write_area(320);
|
||||
if(!line_data_) return;
|
||||
}
|
||||
|
||||
uint8_t mask = 0x80;
|
||||
for(int c = 0; c < 8; c++) {
|
||||
line_data_pointer_[c] = (byte & mask) ? 0xff : 0x00;
|
||||
mask >>= 1;
|
||||
}
|
||||
line_data_pointer_ += 8;
|
||||
}
|
||||
}
|
||||
|
||||
std::shared_ptr<Outputs::CRT::CRT> Video::get_crt() {
|
||||
return crt_;
|
||||
}
|
||||
55
Machines/ZX8081/Video.hpp
Normal file
55
Machines/ZX8081/Video.hpp
Normal file
@@ -0,0 +1,55 @@
|
||||
//
|
||||
// Video.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 06/06/2017.
|
||||
// Copyright © 2017 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef Machines_ZX8081_Video_hpp
|
||||
#define Machines_ZX8081_Video_hpp
|
||||
|
||||
#include "../../Outputs/CRT/CRT.hpp"
|
||||
#include "../../ClockReceiver/ClockReceiver.hpp"
|
||||
|
||||
namespace ZX8081 {
|
||||
|
||||
/*!
|
||||
Packages a ZX80/81-style video feed into a CRT-compatible waveform.
|
||||
|
||||
While sync is active, this feed will output the sync level.
|
||||
|
||||
While sync is inactive, this feed will output the white level unless it is supplied
|
||||
with a byte to output. When a byte is supplied for output, it will be interpreted as
|
||||
a 1-bit graphic and output over the next 4 cycles, picking between the white level
|
||||
and the black level.
|
||||
*/
|
||||
class Video {
|
||||
public:
|
||||
/// Constructs an instance of the video feed; a CRT is also created.
|
||||
Video();
|
||||
/// @returns The CRT this video feed is feeding.
|
||||
std::shared_ptr<Outputs::CRT::CRT> get_crt();
|
||||
|
||||
/// Advances time by @c cycles.
|
||||
void run_for(const HalfCycles);
|
||||
/// Forces output to catch up to the current output position.
|
||||
void flush();
|
||||
|
||||
/// Sets the current sync output.
|
||||
void set_sync(bool sync);
|
||||
/// Causes @c byte to be serialised into pixels and output over the next four cycles.
|
||||
void output_byte(uint8_t byte);
|
||||
|
||||
private:
|
||||
bool sync_;
|
||||
uint8_t *line_data_, *line_data_pointer_;
|
||||
unsigned int cycles_since_update_;
|
||||
std::shared_ptr<Outputs::CRT::CRT> crt_;
|
||||
|
||||
void flush(bool next_sync);
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif /* Video_hpp */
|
||||
400
Machines/ZX8081/ZX8081.cpp
Normal file
400
Machines/ZX8081/ZX8081.cpp
Normal file
@@ -0,0 +1,400 @@
|
||||
//
|
||||
// ZX8081.cpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 04/06/2017.
|
||||
// Copyright © 2017 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#include "ZX8081.hpp"
|
||||
|
||||
#include "../../Processors/Z80/Z80.hpp"
|
||||
#include "../../Storage/Tape/Tape.hpp"
|
||||
#include "../../Storage/Tape/Parsers/ZX8081.hpp"
|
||||
|
||||
#include "../../ClockReceiver/ForceInline.hpp"
|
||||
|
||||
#include "../MemoryFuzzer.hpp"
|
||||
#include "../Typer.hpp"
|
||||
|
||||
#include "CharacterMapper.hpp"
|
||||
#include "Video.hpp"
|
||||
|
||||
#include <memory>
|
||||
|
||||
namespace {
|
||||
// The clock rate is 3.25Mhz.
|
||||
const unsigned int ZX8081ClockRate = 3250000;
|
||||
}
|
||||
|
||||
namespace ZX8081 {
|
||||
|
||||
template<bool is_zx81> class ConcreteMachine:
|
||||
public Utility::TypeRecipient,
|
||||
public CPU::Z80::BusHandler,
|
||||
public Machine {
|
||||
public:
|
||||
ConcreteMachine() :
|
||||
z80_(*this),
|
||||
vsync_(false),
|
||||
hsync_(false),
|
||||
nmi_is_enabled_(false),
|
||||
tape_player_(ZX8081ClockRate),
|
||||
use_fast_tape_hack_(false),
|
||||
tape_advance_delay_(0),
|
||||
has_latched_video_byte_(false) {
|
||||
set_clock_rate(ZX8081ClockRate);
|
||||
clear_all_keys();
|
||||
}
|
||||
|
||||
forceinline HalfCycles perform_machine_cycle(const CPU::Z80::PartialMachineCycle &cycle) {
|
||||
HalfCycles previous_counter = horizontal_counter_;
|
||||
horizontal_counter_ += cycle.length;
|
||||
|
||||
if(previous_counter < vsync_start_ && horizontal_counter_ >= vsync_start_) {
|
||||
video_->run_for(vsync_start_ - previous_counter);
|
||||
set_hsync(true);
|
||||
line_counter_ = (line_counter_ + 1) & 7;
|
||||
if(nmi_is_enabled_) {
|
||||
z80_.set_non_maskable_interrupt_line(true);
|
||||
}
|
||||
video_->run_for(horizontal_counter_ - vsync_start_);
|
||||
} else if(previous_counter < vsync_end_ && horizontal_counter_ >= vsync_end_) {
|
||||
video_->run_for(vsync_end_ - previous_counter);
|
||||
set_hsync(false);
|
||||
if(nmi_is_enabled_) {
|
||||
z80_.set_non_maskable_interrupt_line(false);
|
||||
z80_.set_wait_line(false);
|
||||
}
|
||||
video_->run_for(horizontal_counter_ - vsync_end_);
|
||||
} else {
|
||||
video_->run_for(cycle.length);
|
||||
}
|
||||
|
||||
if(is_zx81_) horizontal_counter_ %= HalfCycles(Cycles(207));
|
||||
if(!tape_advance_delay_) {
|
||||
tape_player_.run_for(cycle.length);
|
||||
} else {
|
||||
tape_advance_delay_ = std::max(tape_advance_delay_ - cycle.length, HalfCycles(0));
|
||||
}
|
||||
|
||||
if(nmi_is_enabled_ && !z80_.get_halt_line() && z80_.get_non_maskable_interrupt_line()) {
|
||||
z80_.set_wait_line(true);
|
||||
}
|
||||
|
||||
if(!cycle.is_terminal()) {
|
||||
return Cycles(0);
|
||||
}
|
||||
|
||||
uint16_t address = cycle.address ? *cycle.address : 0;
|
||||
bool is_opcode_read = false;
|
||||
switch(cycle.operation) {
|
||||
case CPU::Z80::PartialMachineCycle::Output:
|
||||
if(!(address & 2)) nmi_is_enabled_ = false;
|
||||
if(!(address & 1)) nmi_is_enabled_ = is_zx81_;
|
||||
if(!nmi_is_enabled_) {
|
||||
// Line counter reset is held low while vsync is active; simulate that lazily by performing
|
||||
// an instant reset upon the transition from active to inactive.
|
||||
if(vsync_) line_counter_ = 0;
|
||||
set_vsync(false);
|
||||
}
|
||||
break;
|
||||
|
||||
case CPU::Z80::PartialMachineCycle::Input: {
|
||||
uint8_t value = 0xff;
|
||||
if(!(address&1)) {
|
||||
if(!nmi_is_enabled_) set_vsync(true);
|
||||
|
||||
uint16_t mask = 0x100;
|
||||
for(int c = 0; c < 8; c++) {
|
||||
if(!(address & mask)) value &= key_states_[c];
|
||||
mask <<= 1;
|
||||
}
|
||||
|
||||
value &= ~(tape_player_.get_input() ? 0x00 : 0x80);
|
||||
}
|
||||
*cycle.value = value;
|
||||
} break;
|
||||
|
||||
case CPU::Z80::PartialMachineCycle::Interrupt:
|
||||
// resetting event is M1 and IOREQ both simultaneously having leading edges;
|
||||
// that happens 2 cycles before the end of INTACK. So the timer was reset and
|
||||
// now has advanced twice.
|
||||
horizontal_counter_ = HalfCycles(2);
|
||||
|
||||
*cycle.value = 0xff;
|
||||
break;
|
||||
|
||||
case CPU::Z80::PartialMachineCycle::Refresh:
|
||||
// The ZX80 and 81 signal an interrupt while refresh is active and bit 6 of the refresh
|
||||
// address is low. The Z80 signals a refresh, providing the refresh address during the
|
||||
// final two cycles of an opcode fetch. Therefore communicate a transient signalling
|
||||
// of the IRQ line if necessary.
|
||||
if(!(address & 0x40)) {
|
||||
z80_.set_interrupt_line(true, Cycles(-2));
|
||||
z80_.set_interrupt_line(false);
|
||||
}
|
||||
if(has_latched_video_byte_) {
|
||||
size_t char_address = (size_t)((address & 0xfe00) | ((latched_video_byte_ & 0x3f) << 3) | line_counter_);
|
||||
uint8_t mask = (latched_video_byte_ & 0x80) ? 0x00 : 0xff;
|
||||
if(char_address < ram_base_) {
|
||||
latched_video_byte_ = rom_[char_address & rom_mask_] ^ mask;
|
||||
} else {
|
||||
latched_video_byte_ = ram_[address & ram_mask_] ^ mask;
|
||||
}
|
||||
|
||||
video_->output_byte(latched_video_byte_);
|
||||
has_latched_video_byte_ = false;
|
||||
}
|
||||
break;
|
||||
|
||||
case CPU::Z80::PartialMachineCycle::ReadOpcode:
|
||||
// Check for use of the fast tape hack.
|
||||
if(use_fast_tape_hack_ && address == tape_trap_address_ && tape_player_.has_tape()) {
|
||||
uint64_t prior_offset = tape_player_.get_tape()->get_offset();
|
||||
int next_byte = parser_.get_next_byte(tape_player_.get_tape());
|
||||
if(next_byte != -1) {
|
||||
uint16_t hl = z80_.get_value_of_register(CPU::Z80::Register::HL);
|
||||
ram_[hl & ram_mask_] = (uint8_t)next_byte;
|
||||
*cycle.value = 0x00;
|
||||
z80_.set_value_of_register(CPU::Z80::Register::ProgramCounter, tape_return_address_ - 1);
|
||||
|
||||
// Assume that having read one byte quickly, we're probably going to be asked to read
|
||||
// another shortly. Therefore, temporarily disable the tape motor for 1000 cycles in order
|
||||
// to avoid fighting with real time. This is a stop-gap fix.
|
||||
tape_advance_delay_ = 1000;
|
||||
return 0;
|
||||
} else {
|
||||
tape_player_.get_tape()->set_offset(prior_offset);
|
||||
}
|
||||
}
|
||||
|
||||
// Check for automatic tape control.
|
||||
if(use_automatic_tape_motor_control_) {
|
||||
tape_player_.set_motor_control((address >= automatic_tape_motor_start_address_) && (address < automatic_tape_motor_end_address_));
|
||||
}
|
||||
is_opcode_read = true;
|
||||
|
||||
case CPU::Z80::PartialMachineCycle::Read:
|
||||
if(address < ram_base_) {
|
||||
*cycle.value = rom_[address & rom_mask_];
|
||||
} else {
|
||||
uint8_t value = ram_[address & ram_mask_];
|
||||
|
||||
// If this is an M1 cycle reading from above the 32kb mark and HALT is not
|
||||
// currently active, latch for video output and return a NOP. Otherwise,
|
||||
// just return the value as read.
|
||||
if(is_opcode_read && address&0x8000 && !(value & 0x40) && !z80_.get_halt_line()) {
|
||||
latched_video_byte_ = value;
|
||||
has_latched_video_byte_ = true;
|
||||
*cycle.value = 0;
|
||||
} else *cycle.value = value;
|
||||
}
|
||||
break;
|
||||
|
||||
case CPU::Z80::PartialMachineCycle::Write:
|
||||
if(address >= ram_base_) {
|
||||
ram_[address & ram_mask_] = *cycle.value;
|
||||
}
|
||||
break;
|
||||
|
||||
default: break;
|
||||
}
|
||||
|
||||
if(typer_) typer_->run_for(cycle.length);
|
||||
|
||||
return HalfCycles(0);
|
||||
}
|
||||
|
||||
forceinline void flush() {
|
||||
video_->flush();
|
||||
}
|
||||
|
||||
void setup_output(float aspect_ratio) override final {
|
||||
video_.reset(new Video);
|
||||
}
|
||||
|
||||
void close_output() override final {
|
||||
video_.reset();
|
||||
}
|
||||
|
||||
std::shared_ptr<Outputs::CRT::CRT> get_crt() override final {
|
||||
return video_->get_crt();
|
||||
}
|
||||
|
||||
std::shared_ptr<Outputs::Speaker> get_speaker() override final {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void run_for(const Cycles cycles) override final {
|
||||
z80_.run_for(cycles);
|
||||
}
|
||||
|
||||
void configure_as_target(const StaticAnalyser::Target &target) override final {
|
||||
is_zx81_ = target.zx8081.isZX81;
|
||||
if(is_zx81_) {
|
||||
rom_ = zx81_rom_;
|
||||
tape_trap_address_ = 0x37c;
|
||||
tape_return_address_ = 0x380;
|
||||
vsync_start_ = HalfCycles(32);
|
||||
vsync_end_ = HalfCycles(64);
|
||||
automatic_tape_motor_start_address_ = 0x0340;
|
||||
automatic_tape_motor_end_address_ = 0x03c3;
|
||||
} else {
|
||||
rom_ = zx80_rom_;
|
||||
tape_trap_address_ = 0x220;
|
||||
tape_return_address_ = 0x248;
|
||||
vsync_start_ = HalfCycles(26);
|
||||
vsync_end_ = HalfCycles(66);
|
||||
automatic_tape_motor_start_address_ = 0x0206;
|
||||
automatic_tape_motor_end_address_ = 0x024d;
|
||||
}
|
||||
rom_mask_ = (uint16_t)(rom_.size() - 1);
|
||||
|
||||
switch(target.zx8081.memory_model) {
|
||||
case StaticAnalyser::ZX8081MemoryModel::Unexpanded:
|
||||
ram_.resize(1024);
|
||||
ram_base_ = 16384;
|
||||
ram_mask_ = 1023;
|
||||
break;
|
||||
case StaticAnalyser::ZX8081MemoryModel::SixteenKB:
|
||||
ram_.resize(16384);
|
||||
ram_base_ = 16384;
|
||||
ram_mask_ = 16383;
|
||||
break;
|
||||
case StaticAnalyser::ZX8081MemoryModel::SixtyFourKB:
|
||||
ram_.resize(65536);
|
||||
ram_base_ = 8192;
|
||||
ram_mask_ = 65535;
|
||||
break;
|
||||
}
|
||||
Memory::Fuzz(ram_);
|
||||
|
||||
if(target.loadingCommand.length()) {
|
||||
set_typer_for_string(target.loadingCommand.c_str());
|
||||
}
|
||||
|
||||
insert_media(target.media);
|
||||
}
|
||||
|
||||
bool insert_media(const StaticAnalyser::Media &media) override final {
|
||||
if(!media.tapes.empty()) {
|
||||
tape_player_.set_tape(media.tapes.front());
|
||||
}
|
||||
|
||||
return !media.tapes.empty();
|
||||
}
|
||||
|
||||
void set_typer_for_string(const char *string) override final {
|
||||
std::unique_ptr<CharacterMapper> mapper(new CharacterMapper(is_zx81_));
|
||||
Utility::TypeRecipient::set_typer_for_string(string, std::move(mapper));
|
||||
}
|
||||
|
||||
void set_rom(ROMType type, std::vector<uint8_t> data) override final {
|
||||
switch(type) {
|
||||
case ZX80: zx80_rom_ = data; break;
|
||||
case ZX81: zx81_rom_ = data; break;
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - Keyboard
|
||||
|
||||
void set_key_state(uint16_t key, bool isPressed) override final {
|
||||
if(isPressed)
|
||||
key_states_[key >> 8] &= (uint8_t)(~key);
|
||||
else
|
||||
key_states_[key >> 8] |= (uint8_t)key;
|
||||
}
|
||||
|
||||
void clear_all_keys() override final {
|
||||
memset(key_states_, 0xff, 8);
|
||||
}
|
||||
|
||||
#pragma mark - Tape control
|
||||
|
||||
void set_use_fast_tape_hack(bool activate) override final {
|
||||
use_fast_tape_hack_ = activate;
|
||||
}
|
||||
|
||||
void set_use_automatic_tape_motor_control(bool enabled) override final {
|
||||
use_automatic_tape_motor_control_ = enabled;
|
||||
if(!enabled) {
|
||||
tape_player_.set_motor_control(false);
|
||||
}
|
||||
}
|
||||
void set_tape_is_playing(bool is_playing) override final {
|
||||
tape_player_.set_motor_control(is_playing);
|
||||
}
|
||||
|
||||
#pragma mark - Typer timing
|
||||
|
||||
HalfCycles get_typer_delay() override final { return Cycles(7000000); }
|
||||
HalfCycles get_typer_frequency() override final { return Cycles(390000); }
|
||||
|
||||
private:
|
||||
CPU::Z80::Processor<ConcreteMachine, false, is_zx81> z80_;
|
||||
|
||||
std::shared_ptr<Video> video_;
|
||||
std::vector<uint8_t> zx81_rom_, zx80_rom_;
|
||||
|
||||
uint16_t tape_trap_address_, tape_return_address_;
|
||||
uint16_t automatic_tape_motor_start_address_, automatic_tape_motor_end_address_;
|
||||
|
||||
std::vector<uint8_t> ram_;
|
||||
uint16_t ram_mask_, ram_base_;
|
||||
|
||||
std::vector<uint8_t> rom_;
|
||||
uint16_t rom_mask_;
|
||||
|
||||
bool vsync_, hsync_;
|
||||
int line_counter_;
|
||||
|
||||
uint8_t key_states_[8];
|
||||
|
||||
HalfClockReceiver<Storage::Tape::BinaryTapePlayer> tape_player_;
|
||||
Storage::Tape::ZX8081::Parser parser_;
|
||||
|
||||
bool is_zx81_;
|
||||
bool nmi_is_enabled_;
|
||||
|
||||
HalfCycles vsync_start_, vsync_end_;
|
||||
HalfCycles horizontal_counter_;
|
||||
|
||||
uint8_t latched_video_byte_;
|
||||
bool has_latched_video_byte_;
|
||||
|
||||
bool use_fast_tape_hack_;
|
||||
bool use_automatic_tape_motor_control_;
|
||||
HalfCycles tape_advance_delay_;
|
||||
|
||||
#pragma mark - Video
|
||||
|
||||
inline void set_vsync(bool sync) {
|
||||
vsync_ = sync;
|
||||
update_sync();
|
||||
}
|
||||
|
||||
inline void set_hsync(bool sync) {
|
||||
hsync_ = sync;
|
||||
update_sync();
|
||||
}
|
||||
|
||||
inline void update_sync() {
|
||||
video_->set_sync(vsync_ || hsync_);
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
using namespace ZX8081;
|
||||
|
||||
// See header; constructs and returns an instance of the ZX80 or 81.
|
||||
Machine *Machine::ZX8081(const StaticAnalyser::Target &target_hint) {
|
||||
// Instantiate the correct type of machine.
|
||||
if(target_hint.zx8081.isZX81)
|
||||
return new ZX8081::ConcreteMachine<true>();
|
||||
else
|
||||
return new ZX8081::ConcreteMachine<false>();
|
||||
}
|
||||
|
||||
Machine::~Machine() {}
|
||||
53
Machines/ZX8081/ZX8081.hpp
Normal file
53
Machines/ZX8081/ZX8081.hpp
Normal file
@@ -0,0 +1,53 @@
|
||||
//
|
||||
// ZX8081.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 04/06/2017.
|
||||
// Copyright © 2017 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef ZX8081_hpp
|
||||
#define ZX8081_hpp
|
||||
|
||||
#include "../ConfigurationTarget.hpp"
|
||||
#include "../CRTMachine.hpp"
|
||||
#include "../KeyboardMachine.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
|
||||
namespace ZX8081 {
|
||||
|
||||
enum ROMType: uint8_t {
|
||||
ZX80, ZX81
|
||||
};
|
||||
|
||||
enum Key: uint16_t {
|
||||
KeyShift = 0x0000 | 0x01, KeyZ = 0x0000 | 0x02, KeyX = 0x0000 | 0x04, KeyC = 0x0000 | 0x08, KeyV = 0x0000 | 0x10,
|
||||
KeyA = 0x0100 | 0x01, KeyS = 0x0100 | 0x02, KeyD = 0x0100 | 0x04, KeyF = 0x0100 | 0x08, KeyG = 0x0100 | 0x10,
|
||||
KeyQ = 0x0200 | 0x01, KeyW = 0x0200 | 0x02, KeyE = 0x0200 | 0x04, KeyR = 0x0200 | 0x08, KeyT = 0x0200 | 0x10,
|
||||
Key1 = 0x0300 | 0x01, Key2 = 0x0300 | 0x02, Key3 = 0x0300 | 0x04, Key4 = 0x0300 | 0x08, Key5 = 0x0300 | 0x10,
|
||||
Key0 = 0x0400 | 0x01, Key9 = 0x0400 | 0x02, Key8 = 0x0400 | 0x04, Key7 = 0x0400 | 0x08, Key6 = 0x0400 | 0x10,
|
||||
KeyP = 0x0500 | 0x01, KeyO = 0x0500 | 0x02, KeyI = 0x0500 | 0x04, KeyU = 0x0500 | 0x08, KeyY = 0x0500 | 0x10,
|
||||
KeyEnter = 0x0600 | 0x01, KeyL = 0x0600 | 0x02, KeyK = 0x0600 | 0x04, KeyJ = 0x0600 | 0x08, KeyH = 0x0600 | 0x10,
|
||||
KeySpace = 0x0700 | 0x01, KeyDot = 0x0700 | 0x02, KeyM = 0x0700 | 0x04, KeyN = 0x0700 | 0x08, KeyB = 0x0700 | 0x10,
|
||||
};
|
||||
|
||||
class Machine:
|
||||
public CRTMachine::Machine,
|
||||
public ConfigurationTarget::Machine,
|
||||
public KeyboardMachine::Machine {
|
||||
public:
|
||||
static Machine *ZX8081(const StaticAnalyser::Target &target_hint);
|
||||
virtual ~Machine();
|
||||
|
||||
virtual void set_rom(ROMType type, std::vector<uint8_t> data) = 0;
|
||||
|
||||
virtual void set_use_fast_tape_hack(bool activate) = 0;
|
||||
virtual void set_tape_is_playing(bool is_playing) = 0;
|
||||
virtual void set_use_automatic_tape_motor_control(bool enabled) = 0;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif /* ZX8081_hpp */
|
||||
@@ -13,8 +13,13 @@
|
||||
|
||||
namespace NumberTheory {
|
||||
|
||||
/*! Provides a class capable of accumulating a CRC16 from source data. */
|
||||
class CRC16 {
|
||||
public:
|
||||
/*!
|
||||
Instantiates a CRC16 that will compute the CRC16 specified by the supplied
|
||||
@c polynomial and @c reset_value.
|
||||
*/
|
||||
CRC16(uint16_t polynomial, uint16_t reset_value) :
|
||||
reset_value_(reset_value), value_(reset_value) {
|
||||
for(int c = 0; c < 256; c++) {
|
||||
@@ -27,11 +32,18 @@ class CRC16 {
|
||||
}
|
||||
}
|
||||
|
||||
/// Resets the CRC to the reset value.
|
||||
inline void reset() { value_ = reset_value_; }
|
||||
|
||||
/// Updates the CRC to include @c byte.
|
||||
inline void add(uint8_t byte) {
|
||||
value_ = (uint16_t)((value_ << 8) ^ xor_table[(value_ >> 8) ^ byte]);
|
||||
}
|
||||
|
||||
/// @returns The current value of the CRC.
|
||||
inline uint16_t get_value() const { return value_; }
|
||||
|
||||
/// Sets the current value of the CRC.
|
||||
inline void set_value(uint16_t value) { value_ = value; }
|
||||
|
||||
private:
|
||||
|
||||
@@ -9,15 +9,19 @@
|
||||
#ifndef Factors_hpp
|
||||
#define Factors_hpp
|
||||
|
||||
#include <numeric>
|
||||
#include <utility>
|
||||
|
||||
namespace NumberTheory {
|
||||
/*!
|
||||
@returns The greatest common divisor of @c a and @c b as computed by Euclid's algorithm.
|
||||
@returns The greatest common divisor of @c a and @c b.
|
||||
*/
|
||||
template<class T> T greatest_common_divisor(T a, T b) {
|
||||
#if __cplusplus > 201402L
|
||||
return std::gcd(a, b);
|
||||
#else
|
||||
if(a < b) {
|
||||
T swap = b;
|
||||
b = a;
|
||||
a = swap;
|
||||
std::swap(a, b);
|
||||
}
|
||||
|
||||
while(1) {
|
||||
@@ -28,11 +32,12 @@ namespace NumberTheory {
|
||||
a = b;
|
||||
b = remainder;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
/*!
|
||||
@returns The least common multiple of @c a and @c b computed indirectly via Euclid's greatest
|
||||
common divisor algorithm.
|
||||
@returns The least common multiple of @c a and @c b computed indirectly via the greatest
|
||||
common divisor.
|
||||
*/
|
||||
template<class T> T least_common_multiple(T a, T b) {
|
||||
if(a == b) return a;
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -38,6 +38,11 @@
|
||||
BlueprintName = "Clock SignalTests"
|
||||
ReferencedContainer = "container:Clock Signal.xcodeproj">
|
||||
</BuildableReference>
|
||||
<SkippedTests>
|
||||
<Test
|
||||
Identifier = "ZexallTests">
|
||||
</Test>
|
||||
</SkippedTests>
|
||||
</TestableReference>
|
||||
<TestableReference
|
||||
skipped = "YES">
|
||||
|
||||
48
OSBindings/Mac/Clock Signal/Base.lproj/AmstradCPCOptions.xib
Normal file
48
OSBindings/Mac/Clock Signal/Base.lproj/AmstradCPCOptions.xib
Normal file
@@ -0,0 +1,48 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="12121" systemVersion="16F73" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
|
||||
<dependencies>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="12121"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<objects>
|
||||
<customObject id="-2" userLabel="File's Owner" customClass="MachineDocument" customModule="Clock_Signal" customModuleProvider="target">
|
||||
<connections>
|
||||
<outlet property="optionsPanel" destination="ZW7-Bw-4RP" id="JpE-wG-zRR"/>
|
||||
</connections>
|
||||
</customObject>
|
||||
<customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
|
||||
<customObject id="-3" userLabel="Application" customClass="NSObject"/>
|
||||
<window title="Options" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" hidesOnDeactivate="YES" oneShot="NO" releasedWhenClosed="NO" showsToolbarButton="NO" visibleAtLaunch="NO" frameAutosaveName="" animationBehavior="default" id="ZW7-Bw-4RP" customClass="MachinePanel" customModule="Clock_Signal" customModuleProvider="target">
|
||||
<windowStyleMask key="styleMask" titled="YES" closable="YES" utility="YES" nonactivatingPanel="YES" HUD="YES"/>
|
||||
<windowPositionMask key="initialPositionMask" leftStrut="YES" rightStrut="YES" topStrut="YES" bottomStrut="YES"/>
|
||||
<rect key="contentRect" x="83" y="102" width="200" height="54"/>
|
||||
<rect key="screenRect" x="0.0" y="0.0" width="1366" height="768"/>
|
||||
<view key="contentView" id="tpZ-0B-QQu">
|
||||
<rect key="frame" x="0.0" y="0.0" width="200" height="54"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<button translatesAutoresizingMaskIntoConstraints="NO" id="e1J-pw-zGw">
|
||||
<rect key="frame" x="18" y="18" width="164" height="18"/>
|
||||
<buttonCell key="cell" type="check" title="Load Quickly" bezelStyle="regularSquare" imagePosition="left" alignment="left" state="on" inset="2" id="tD6-UB-ESB">
|
||||
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
|
||||
<font key="font" metaFont="system"/>
|
||||
</buttonCell>
|
||||
<connections>
|
||||
<action selector="setFastLoading:" target="ZW7-Bw-4RP" id="JmG-Ks-jSh"/>
|
||||
</connections>
|
||||
</button>
|
||||
</subviews>
|
||||
<constraints>
|
||||
<constraint firstItem="e1J-pw-zGw" firstAttribute="leading" secondItem="tpZ-0B-QQu" secondAttribute="leading" constant="20" id="HSD-3d-Bl7"/>
|
||||
<constraint firstAttribute="trailing" secondItem="e1J-pw-zGw" secondAttribute="trailing" constant="20" id="Q9M-FH-92N"/>
|
||||
<constraint firstAttribute="bottom" secondItem="e1J-pw-zGw" secondAttribute="bottom" constant="20" id="sdh-oJ-ZIQ"/>
|
||||
<constraint firstItem="e1J-pw-zGw" firstAttribute="top" secondItem="tpZ-0B-QQu" secondAttribute="top" constant="20" id="ul9-lf-Y3u"/>
|
||||
</constraints>
|
||||
</view>
|
||||
<connections>
|
||||
<outlet property="fastLoadingButton" destination="e1J-pw-zGw" id="jj7-OZ-mOH"/>
|
||||
</connections>
|
||||
<point key="canvasLocation" x="175" y="30"/>
|
||||
</window>
|
||||
</objects>
|
||||
</document>
|
||||
76
OSBindings/Mac/Clock Signal/Base.lproj/ZX8081Options.xib
Normal file
76
OSBindings/Mac/Clock Signal/Base.lproj/ZX8081Options.xib
Normal file
@@ -0,0 +1,76 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="12121" systemVersion="16F73" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
|
||||
<dependencies>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="12121"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<objects>
|
||||
<customObject id="-2" userLabel="File's Owner" customClass="MachineDocument" customModule="Clock_Signal" customModuleProvider="target">
|
||||
<connections>
|
||||
<outlet property="optionsPanel" destination="ota-g7-hOL" id="zeO-di-9i3"/>
|
||||
</connections>
|
||||
</customObject>
|
||||
<customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
|
||||
<customObject id="-3" userLabel="Application" customClass="NSObject"/>
|
||||
<window title="Options" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" hidesOnDeactivate="YES" oneShot="NO" releasedWhenClosed="NO" showsToolbarButton="NO" visibleAtLaunch="NO" frameAutosaveName="" animationBehavior="default" id="ota-g7-hOL" customClass="ZX8081OptionsPanel" customModule="Clock_Signal" customModuleProvider="target">
|
||||
<windowStyleMask key="styleMask" titled="YES" closable="YES" utility="YES" nonactivatingPanel="YES" HUD="YES"/>
|
||||
<windowPositionMask key="initialPositionMask" leftStrut="YES" rightStrut="YES" topStrut="YES" bottomStrut="YES"/>
|
||||
<rect key="contentRect" x="83" y="102" width="261" height="100"/>
|
||||
<rect key="screenRect" x="0.0" y="0.0" width="1366" height="768"/>
|
||||
<view key="contentView" id="7Pv-WL-2Rq">
|
||||
<rect key="frame" x="0.0" y="0.0" width="261" height="100"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<button translatesAutoresizingMaskIntoConstraints="NO" id="sBT-cU-h7s">
|
||||
<rect key="frame" x="18" y="64" width="225" height="18"/>
|
||||
<buttonCell key="cell" type="check" title="Load Tapes Quickly" bezelStyle="regularSquare" imagePosition="left" alignment="left" state="on" inset="2" id="w0l-ha-esm">
|
||||
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
|
||||
<font key="font" metaFont="system"/>
|
||||
</buttonCell>
|
||||
<connections>
|
||||
<action selector="setFastLoading:" target="ota-g7-hOL" id="me0-h2-Ga5"/>
|
||||
</connections>
|
||||
</button>
|
||||
<button translatesAutoresizingMaskIntoConstraints="NO" id="qSb-72-6Os">
|
||||
<rect key="frame" x="18" y="44" width="225" height="18"/>
|
||||
<buttonCell key="cell" type="check" title="Control Tape Motor Automatically" bezelStyle="regularSquare" imagePosition="left" state="on" inset="2" id="CzC-YT-lgA">
|
||||
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
|
||||
<font key="font" metaFont="system"/>
|
||||
</buttonCell>
|
||||
<connections>
|
||||
<action selector="setAutomaticTapeMotorConrol:" target="ota-g7-hOL" id="bpF-1P-tga"/>
|
||||
</connections>
|
||||
</button>
|
||||
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="tkN-gI-RmT">
|
||||
<rect key="frame" x="20" y="19" width="221" height="19"/>
|
||||
<buttonCell key="cell" type="roundRect" title="Play Tape" bezelStyle="roundedRect" alignment="center" enabled="NO" state="on" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="cTq-f9-Gzx">
|
||||
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
|
||||
<font key="font" metaFont="cellTitle"/>
|
||||
</buttonCell>
|
||||
<connections>
|
||||
<action selector="playOrPauseTape:" target="ota-g7-hOL" id="O0K-pL-nOr"/>
|
||||
</connections>
|
||||
</button>
|
||||
</subviews>
|
||||
<constraints>
|
||||
<constraint firstItem="qSb-72-6Os" firstAttribute="leading" secondItem="7Pv-WL-2Rq" secondAttribute="leading" constant="20" id="05p-Jn-ueX"/>
|
||||
<constraint firstAttribute="trailing" secondItem="sBT-cU-h7s" secondAttribute="trailing" constant="20" id="79b-2A-2c7"/>
|
||||
<constraint firstItem="sBT-cU-h7s" firstAttribute="top" secondItem="7Pv-WL-2Rq" secondAttribute="top" constant="20" id="E5m-wo-X92"/>
|
||||
<constraint firstItem="qSb-72-6Os" firstAttribute="top" secondItem="sBT-cU-h7s" secondAttribute="bottom" constant="6" id="WxD-kP-vwf"/>
|
||||
<constraint firstAttribute="bottom" secondItem="tkN-gI-RmT" secondAttribute="bottom" constant="20" id="Xnu-On-nOA"/>
|
||||
<constraint firstItem="tkN-gI-RmT" firstAttribute="leading" secondItem="7Pv-WL-2Rq" secondAttribute="leading" constant="20" id="fHf-K0-PsU"/>
|
||||
<constraint firstItem="tkN-gI-RmT" firstAttribute="top" secondItem="qSb-72-6Os" secondAttribute="bottom" constant="8" id="gLh-vE-Cqk"/>
|
||||
<constraint firstAttribute="trailing" secondItem="qSb-72-6Os" secondAttribute="trailing" constant="20" id="mQz-p8-aYf"/>
|
||||
<constraint firstItem="sBT-cU-h7s" firstAttribute="leading" secondItem="7Pv-WL-2Rq" secondAttribute="leading" constant="20" id="nDy-Xc-Ug9"/>
|
||||
<constraint firstAttribute="trailing" secondItem="tkN-gI-RmT" secondAttribute="trailing" constant="20" id="vgD-A3-m6T"/>
|
||||
</constraints>
|
||||
</view>
|
||||
<connections>
|
||||
<outlet property="automaticTapeMotorControlButton" destination="qSb-72-6Os" id="bB6-FP-TKM"/>
|
||||
<outlet property="fastLoadingButton" destination="sBT-cU-h7s" id="uWa-EB-mbd"/>
|
||||
<outlet property="playOrPauseTapeButton" destination="tkN-gI-RmT" id="UnJ-nb-3mv"/>
|
||||
</connections>
|
||||
<point key="canvasLocation" x="28.5" y="15"/>
|
||||
</window>
|
||||
</objects>
|
||||
</document>
|
||||
@@ -10,6 +10,7 @@
|
||||
#import "CSElectron.h"
|
||||
#import "CSOric.h"
|
||||
#import "CSVic20.h"
|
||||
#import "CSZX8081.h"
|
||||
|
||||
#import "CSStaticAnalyser.h"
|
||||
|
||||
|
||||
@@ -164,6 +164,13 @@ class MachineDocument:
|
||||
}
|
||||
}
|
||||
|
||||
final func openGLView(_ view: CSOpenGLView, didReceiveFileAt URL: URL) {
|
||||
let mediaSet = CSMediaSet(fileAt: URL)
|
||||
if let mediaSet = mediaSet {
|
||||
mediaSet.apply(to: self.machine)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: NSDocument overrides
|
||||
override func data(ofType typeName: String) throws -> Data {
|
||||
throw NSError(domain: NSOSStatusErrorDomain, code: unimpErr, userInfo: nil)
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
//
|
||||
// ZX8081OptionsPanel.swift
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 08/07/2017.
|
||||
// Copyright © 2017 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
class ZX8081OptionsPanel: MachinePanel {
|
||||
var zx8081: CSZX8081! {
|
||||
get {
|
||||
return self.machine as! CSZX8081
|
||||
}
|
||||
}
|
||||
|
||||
@IBOutlet var automaticTapeMotorControlButton: NSButton!
|
||||
var automaticTapeMotorControlDefaultsKey: String {
|
||||
get { return prefixedUserDefaultsKey("automaticTapeMotorControl") }
|
||||
}
|
||||
@IBAction func setAutomaticTapeMotorConrol(_ sender: NSButton!) {
|
||||
let isEnabled = sender.state == NSOnState
|
||||
UserDefaults.standard.set(isEnabled, forKey: self.automaticTapeMotorControlDefaultsKey)
|
||||
self.playOrPauseTapeButton.isEnabled = !isEnabled
|
||||
self.zx8081.useAutomaticTapeMotorControl = isEnabled
|
||||
}
|
||||
|
||||
@IBOutlet var playOrPauseTapeButton: NSButton!
|
||||
@IBAction func playOrPauseTape(_ sender: NSButton!) {
|
||||
self.zx8081.tapeIsPlaying = !self.zx8081.tapeIsPlaying
|
||||
self.playOrPauseTapeButton.title = self.zx8081.tapeIsPlaying
|
||||
? NSLocalizedString("Stop Tape", comment: "Text for a button that will stop tape playback")
|
||||
: NSLocalizedString("Play Tape", comment: "Text for a button that will start tape playback")
|
||||
}
|
||||
|
||||
// MARK: option restoration
|
||||
override func establishStoredOptions() {
|
||||
super.establishStoredOptions()
|
||||
|
||||
let standardUserDefaults = UserDefaults.standard
|
||||
standardUserDefaults.register(defaults: [
|
||||
self.automaticTapeMotorControlDefaultsKey: true
|
||||
])
|
||||
|
||||
let automaticTapeMotorControlIsEnabled = standardUserDefaults.bool(forKey: self.automaticTapeMotorControlDefaultsKey)
|
||||
self.automaticTapeMotorControlButton.state = automaticTapeMotorControlIsEnabled ? NSOnState : NSOffState
|
||||
self.playOrPauseTapeButton.isEnabled = !automaticTapeMotorControlIsEnabled
|
||||
self.zx8081.useAutomaticTapeMotorControl = automaticTapeMotorControlIsEnabled
|
||||
}
|
||||
}
|
||||
@@ -104,7 +104,7 @@
|
||||
<key>CFBundleTypeName</key>
|
||||
<string>Commodore Disk</string>
|
||||
<key>CFBundleTypeRole</key>
|
||||
<string>Viewer</string>
|
||||
<string>Editor</string>
|
||||
<key>LSTypeIsPackage</key>
|
||||
<integer>0</integer>
|
||||
<key>NSDocumentClass</key>
|
||||
@@ -120,7 +120,7 @@
|
||||
<key>CFBundleTypeName</key>
|
||||
<string>Commodore 1540/1 Disk</string>
|
||||
<key>CFBundleTypeRole</key>
|
||||
<string>Viewer</string>
|
||||
<string>Editor</string>
|
||||
<key>LSTypeIsPackage</key>
|
||||
<integer>0</integer>
|
||||
<key>NSDocumentClass</key>
|
||||
@@ -140,7 +140,9 @@
|
||||
<key>CFBundleTypeName</key>
|
||||
<string>Electron/BBC Disk Image</string>
|
||||
<key>CFBundleTypeRole</key>
|
||||
<string>Viewer</string>
|
||||
<string>Editor</string>
|
||||
<key>LSTypeIsPackage</key>
|
||||
<integer>0</integer>
|
||||
<key>NSDocumentClass</key>
|
||||
<string>$(PRODUCT_MODULE_NAME).MachineDocument</string>
|
||||
</dict>
|
||||
@@ -154,7 +156,108 @@
|
||||
<key>CFBundleTypeName</key>
|
||||
<string>Disk Image</string>
|
||||
<key>CFBundleTypeRole</key>
|
||||
<string>Editor</string>
|
||||
<key>LSTypeIsPackage</key>
|
||||
<integer>0</integer>
|
||||
<key>NSDocumentClass</key>
|
||||
<string>$(PRODUCT_MODULE_NAME).MachineDocument</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>CFBundleTypeExtensions</key>
|
||||
<array>
|
||||
<string>o</string>
|
||||
<string>80</string>
|
||||
</array>
|
||||
<key>CFBundleTypeIconFile</key>
|
||||
<string>cassette</string>
|
||||
<key>CFBundleTypeName</key>
|
||||
<string>ZX80 Tape Image</string>
|
||||
<key>CFBundleTypeRole</key>
|
||||
<string>Viewer</string>
|
||||
<key>LSTypeIsPackage</key>
|
||||
<integer>0</integer>
|
||||
<key>NSDocumentClass</key>
|
||||
<string>$(PRODUCT_MODULE_NAME).MachineDocument</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>CFBundleTypeExtensions</key>
|
||||
<array>
|
||||
<string>p</string>
|
||||
<string>81</string>
|
||||
<string>p81</string>
|
||||
</array>
|
||||
<key>CFBundleTypeIconFile</key>
|
||||
<string>cassette</string>
|
||||
<key>CFBundleTypeName</key>
|
||||
<string>ZX81 Tape Image</string>
|
||||
<key>CFBundleTypeRole</key>
|
||||
<string>Viewer</string>
|
||||
<key>LSTypeIsPackage</key>
|
||||
<integer>0</integer>
|
||||
<key>NSDocumentClass</key>
|
||||
<string>$(PRODUCT_MODULE_NAME).MachineDocument</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>CFBundleTypeExtensions</key>
|
||||
<array>
|
||||
<string>csw</string>
|
||||
</array>
|
||||
<key>CFBundleTypeIconFile</key>
|
||||
<string>cassette</string>
|
||||
<key>CFBundleTypeName</key>
|
||||
<string>Tape Image</string>
|
||||
<key>CFBundleTypeRole</key>
|
||||
<string>Viewer</string>
|
||||
<key>LSTypeIsPackage</key>
|
||||
<integer>0</integer>
|
||||
<key>NSDocumentClass</key>
|
||||
<string>$(PRODUCT_MODULE_NAME).MachineDocument</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>CFBundleTypeExtensions</key>
|
||||
<array>
|
||||
<string>tzx</string>
|
||||
</array>
|
||||
<key>CFBundleTypeIconFile</key>
|
||||
<string>cassette</string>
|
||||
<key>CFBundleTypeName</key>
|
||||
<string>Tape Image</string>
|
||||
<key>CFBundleTypeRole</key>
|
||||
<string>Viewer</string>
|
||||
<key>LSTypeIsPackage</key>
|
||||
<integer>0</integer>
|
||||
<key>NSDocumentClass</key>
|
||||
<string>$(PRODUCT_MODULE_NAME).MachineDocument</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>CFBundleTypeExtensions</key>
|
||||
<array>
|
||||
<string>cdt</string>
|
||||
</array>
|
||||
<key>CFBundleTypeIconFile</key>
|
||||
<string>cassette</string>
|
||||
<key>CFBundleTypeName</key>
|
||||
<string>Amstrad CPC Tape Image</string>
|
||||
<key>CFBundleTypeRole</key>
|
||||
<string>Viewer</string>
|
||||
<key>LSTypeIsPackage</key>
|
||||
<integer>0</integer>
|
||||
<key>NSDocumentClass</key>
|
||||
<string>$(PRODUCT_MODULE_NAME).MachineDocument</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>CFBundleTypeExtensions</key>
|
||||
<array>
|
||||
<string>hfe</string>
|
||||
</array>
|
||||
<key>CFBundleTypeIconFile</key>
|
||||
<string>floppy35</string>
|
||||
<key>CFBundleTypeName</key>
|
||||
<string>HxC Disk Image</string>
|
||||
<key>CFBundleTypeRole</key>
|
||||
<string>Viewer</string>
|
||||
<key>LSTypeIsPackage</key>
|
||||
<integer>0</integer>
|
||||
<key>NSDocumentClass</key>
|
||||
<string>$(PRODUCT_MODULE_NAME).MachineDocument</string>
|
||||
</dict>
|
||||
|
||||
@@ -11,7 +11,5 @@
|
||||
|
||||
@interface CSMachine (Subclassing)
|
||||
|
||||
- (CRTMachine::Machine * const)machine;
|
||||
- (void)setupOutputWithAspectRatio:(float)aspectRatio;
|
||||
|
||||
@end
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
|
||||
@interface CSMachine(Target)
|
||||
|
||||
- (void)applyTarget:(StaticAnalyser::Target)target;
|
||||
- (void)applyTarget:(const StaticAnalyser::Target &)target;
|
||||
- (void)applyMedia:(const StaticAnalyser::Media &)media;
|
||||
|
||||
@end
|
||||
|
||||
@@ -18,6 +18,15 @@
|
||||
|
||||
@interface CSMachine : NSObject
|
||||
|
||||
- (instancetype)init NS_UNAVAILABLE;
|
||||
/*!
|
||||
Initialises an instance of CSMachine.
|
||||
|
||||
@param machine The pointer to an instance of @c CRTMachine::Machine* . C++ type is omitted because
|
||||
this header is visible to Swift, and the designated initialiser cannot be placed into a category.
|
||||
*/
|
||||
- (instancetype)initWithMachine:(void *)machine NS_DESIGNATED_INITIALIZER;
|
||||
|
||||
- (void)runForNumberOfCycles:(int)numberOfCycles;
|
||||
|
||||
- (float)idealSamplingRateFromRange:(NSRange)range;
|
||||
|
||||
@@ -21,8 +21,8 @@
|
||||
|
||||
struct SpeakerDelegate: public Outputs::Speaker::Delegate {
|
||||
__weak CSMachine *machine;
|
||||
void speaker_did_complete_samples(Outputs::Speaker *speaker, const int16_t *buffer, int buffer_size) {
|
||||
[machine speaker:speaker didCompleteSamples:buffer length:buffer_size];
|
||||
void speaker_did_complete_samples(Outputs::Speaker *speaker, const std::vector<int16_t> &buffer) {
|
||||
[machine speaker:speaker didCompleteSamples:buffer.data() length:(int)buffer.size()];
|
||||
}
|
||||
};
|
||||
|
||||
@@ -39,15 +39,17 @@ struct MachineDelegate: CRTMachine::Machine::Delegate {
|
||||
@implementation CSMachine {
|
||||
SpeakerDelegate _speakerDelegate;
|
||||
MachineDelegate _machineDelegate;
|
||||
CRTMachine::Machine *_machine;
|
||||
}
|
||||
|
||||
- (instancetype)init {
|
||||
- (instancetype)initWithMachine:(void *)machine {
|
||||
self = [super init];
|
||||
if(self)
|
||||
{
|
||||
if(self) {
|
||||
_machine = (CRTMachine::Machine *)machine;
|
||||
_machineDelegate.machine = self;
|
||||
self.machine->set_delegate(&_machineDelegate);
|
||||
_speakerDelegate.machine = self;
|
||||
|
||||
_machine->set_delegate(&_machineDelegate);
|
||||
}
|
||||
return self;
|
||||
}
|
||||
@@ -67,14 +69,14 @@ struct MachineDelegate: CRTMachine::Machine::Delegate {
|
||||
- (void)dealloc {
|
||||
[_view performWithGLContext:^{
|
||||
@synchronized(self) {
|
||||
self.machine->close_output();
|
||||
_machine->close_output();
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
- (float)idealSamplingRateFromRange:(NSRange)range {
|
||||
@synchronized(self) {
|
||||
std::shared_ptr<Outputs::Speaker> speaker = self.machine->get_speaker();
|
||||
std::shared_ptr<Outputs::Speaker> speaker = _machine->get_speaker();
|
||||
if(speaker)
|
||||
{
|
||||
return speaker->get_ideal_clock_rate_in_range((float)range.location, (float)(range.location + range.length));
|
||||
@@ -91,7 +93,7 @@ struct MachineDelegate: CRTMachine::Machine::Delegate {
|
||||
|
||||
- (BOOL)setSpeakerDelegate:(Outputs::Speaker::Delegate *)delegate sampleRate:(float)sampleRate bufferSize:(NSUInteger)bufferSize {
|
||||
@synchronized(self) {
|
||||
std::shared_ptr<Outputs::Speaker> speaker = self.machine->get_speaker();
|
||||
std::shared_ptr<Outputs::Speaker> speaker = _machine->get_speaker();
|
||||
if(speaker)
|
||||
{
|
||||
speaker->set_output_rate(sampleRate, (int)bufferSize);
|
||||
@@ -104,7 +106,7 @@ struct MachineDelegate: CRTMachine::Machine::Delegate {
|
||||
|
||||
- (void)runForNumberOfCycles:(int)numberOfCycles {
|
||||
@synchronized(self) {
|
||||
self.machine->run_for_cycles(numberOfCycles);
|
||||
_machine->run_for(Cycles(numberOfCycles));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -116,33 +118,44 @@ struct MachineDelegate: CRTMachine::Machine::Delegate {
|
||||
}
|
||||
|
||||
- (void)setupOutputWithAspectRatio:(float)aspectRatio {
|
||||
self.machine->setup_output(aspectRatio);
|
||||
_machine->setup_output(aspectRatio);
|
||||
|
||||
// Since OS X v10.6, Macs have had a gamma of 2.2.
|
||||
_machine->get_crt()->set_output_gamma(2.2f);
|
||||
}
|
||||
|
||||
- (void)drawViewForPixelSize:(CGSize)pixelSize onlyIfDirty:(BOOL)onlyIfDirty {
|
||||
self.machine->get_crt()->draw_frame((unsigned int)pixelSize.width, (unsigned int)pixelSize.height, onlyIfDirty ? true : false);
|
||||
_machine->get_crt()->draw_frame((unsigned int)pixelSize.width, (unsigned int)pixelSize.height, onlyIfDirty ? true : false);
|
||||
}
|
||||
|
||||
- (double)clockRate {
|
||||
return self.machine->get_clock_rate();
|
||||
return _machine->get_clock_rate();
|
||||
}
|
||||
|
||||
- (BOOL)clockIsUnlimited {
|
||||
return self.machine->get_clock_is_unlimited() ? YES : NO;
|
||||
return _machine->get_clock_is_unlimited() ? YES : NO;
|
||||
}
|
||||
|
||||
- (void)paste:(NSString *)paste {
|
||||
Utility::TypeRecipient *typeRecipient = dynamic_cast<Utility::TypeRecipient *>(self.machine);
|
||||
Utility::TypeRecipient *typeRecipient = dynamic_cast<Utility::TypeRecipient *>(_machine);
|
||||
if(typeRecipient)
|
||||
typeRecipient->set_typer_for_string([paste UTF8String]);
|
||||
}
|
||||
|
||||
- (void)applyTarget:(StaticAnalyser::Target)target {
|
||||
- (void)applyTarget:(const StaticAnalyser::Target &)target {
|
||||
@synchronized(self) {
|
||||
ConfigurationTarget::Machine *const configurationTarget =
|
||||
dynamic_cast<ConfigurationTarget::Machine *>(self.machine);
|
||||
dynamic_cast<ConfigurationTarget::Machine *>(_machine);
|
||||
if(configurationTarget) configurationTarget->configure_as_target(target);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)applyMedia:(const StaticAnalyser::Media &)media {
|
||||
@synchronized(self) {
|
||||
ConfigurationTarget::Machine *const configurationTarget =
|
||||
dynamic_cast<ConfigurationTarget::Machine *>(_machine);
|
||||
if(configurationTarget) configurationTarget->insert_media(media);
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -22,3 +22,10 @@
|
||||
- (void)applyToMachine:(CSMachine *)machine;
|
||||
|
||||
@end
|
||||
|
||||
@interface CSMediaSet : NSObject
|
||||
|
||||
- (instancetype)initWithFileAtURL:(NSURL *)url;
|
||||
- (void)applyToMachine:(CSMachine *)machine;
|
||||
|
||||
@end
|
||||
|
||||
@@ -14,23 +14,22 @@
|
||||
|
||||
#include "StaticAnalyser.hpp"
|
||||
|
||||
#import "CSAmstradCPC.h"
|
||||
#import "CSAtari2600.h"
|
||||
#import "CSElectron.h"
|
||||
#import "CSOric.h"
|
||||
#import "CSVic20.h"
|
||||
#import "CSZX8081+Instantiation.h"
|
||||
|
||||
#import "Clock_Signal-Swift.h"
|
||||
|
||||
@implementation CSStaticAnalyser
|
||||
{
|
||||
@implementation CSStaticAnalyser {
|
||||
StaticAnalyser::Target _target;
|
||||
}
|
||||
|
||||
- (instancetype)initWithFileAtURL:(NSURL *)url
|
||||
{
|
||||
- (instancetype)initWithFileAtURL:(NSURL *)url {
|
||||
self = [super init];
|
||||
if(self)
|
||||
{
|
||||
if(self) {
|
||||
std::list<StaticAnalyser::Target> targets = StaticAnalyser::GetTargets([url fileSystemRepresentation]);
|
||||
if(!targets.size()) return nil;
|
||||
_target = targets.front();
|
||||
@@ -41,33 +40,50 @@
|
||||
return self;
|
||||
}
|
||||
|
||||
- (NSString *)optionsPanelNibName
|
||||
{
|
||||
switch(_target.machine)
|
||||
{
|
||||
case StaticAnalyser::Target::Atari2600: return @"Atari2600Options";
|
||||
case StaticAnalyser::Target::Electron: return @"ElectronOptions";
|
||||
case StaticAnalyser::Target::Oric: return @"OricOptions";
|
||||
case StaticAnalyser::Target::Vic20: return @"Vic20Options";
|
||||
- (NSString *)optionsPanelNibName {
|
||||
switch(_target.machine) {
|
||||
case StaticAnalyser::Target::AmstradCPC: return nil;
|
||||
case StaticAnalyser::Target::Atari2600: return @"Atari2600Options";
|
||||
case StaticAnalyser::Target::Electron: return @"ElectronOptions";
|
||||
case StaticAnalyser::Target::Oric: return @"OricOptions";
|
||||
case StaticAnalyser::Target::Vic20: return @"Vic20Options";
|
||||
case StaticAnalyser::Target::ZX8081: return @"ZX8081Options";
|
||||
default: return nil;
|
||||
}
|
||||
}
|
||||
|
||||
- (CSMachine *)newMachine
|
||||
{
|
||||
switch(_target.machine)
|
||||
{
|
||||
case StaticAnalyser::Target::Atari2600: return [[CSAtari2600 alloc] init];
|
||||
case StaticAnalyser::Target::Electron: return [[CSElectron alloc] init];
|
||||
case StaticAnalyser::Target::Oric: return [[CSOric alloc] init];
|
||||
case StaticAnalyser::Target::Vic20: return [[CSVic20 alloc] init];
|
||||
- (CSMachine *)newMachine {
|
||||
switch(_target.machine) {
|
||||
case StaticAnalyser::Target::AmstradCPC: return [[CSAmstradCPC alloc] init];
|
||||
case StaticAnalyser::Target::Atari2600: return [[CSAtari2600 alloc] init];
|
||||
case StaticAnalyser::Target::Electron: return [[CSElectron alloc] init];
|
||||
case StaticAnalyser::Target::Oric: return [[CSOric alloc] init];
|
||||
case StaticAnalyser::Target::Vic20: return [[CSVic20 alloc] init];
|
||||
case StaticAnalyser::Target::ZX8081: return [[CSZX8081 alloc] initWithIntendedTarget:_target];
|
||||
default: return nil;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)applyToMachine:(CSMachine *)machine
|
||||
{
|
||||
- (void)applyToMachine:(CSMachine *)machine {
|
||||
[machine applyTarget:_target];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation CSMediaSet {
|
||||
StaticAnalyser::Media _media;
|
||||
}
|
||||
|
||||
- (instancetype)initWithFileAtURL:(NSURL *)url {
|
||||
self = [super init];
|
||||
if(self) {
|
||||
_media = StaticAnalyser::GetMedia([url fileSystemRepresentation]);
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)applyToMachine:(CSMachine *)machine {
|
||||
[machine applyMedia:_media];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
16
OSBindings/Mac/Clock Signal/Machine/Wrappers/CSAmstradCPC.h
Normal file
16
OSBindings/Mac/Clock Signal/Machine/Wrappers/CSAmstradCPC.h
Normal file
@@ -0,0 +1,16 @@
|
||||
//
|
||||
// CSAmstradCPC.h
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 30/07/2017.
|
||||
// Copyright © 2017 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#import "CSMachine.h"
|
||||
#import "CSKeyboardMachine.h"
|
||||
|
||||
@interface CSAmstradCPC : CSMachine <CSKeyboardMachine>
|
||||
|
||||
- (instancetype)init;
|
||||
|
||||
@end
|
||||
152
OSBindings/Mac/Clock Signal/Machine/Wrappers/CSAmstradCPC.mm
Normal file
152
OSBindings/Mac/Clock Signal/Machine/Wrappers/CSAmstradCPC.mm
Normal file
@@ -0,0 +1,152 @@
|
||||
//
|
||||
// CSAmstradCPC.m
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 30/07/2017.
|
||||
// Copyright © 2017 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#import "CSAmstradCPC.h"
|
||||
|
||||
#include "AmstradCPC.hpp"
|
||||
|
||||
#import "CSMachine+Subclassing.h"
|
||||
#import "NSData+StdVector.h"
|
||||
#import "NSBundle+DataResource.h"
|
||||
|
||||
@implementation CSAmstradCPC {
|
||||
std::unique_ptr<AmstradCPC::Machine> _amstradCPC;
|
||||
}
|
||||
|
||||
- (instancetype)init {
|
||||
AmstradCPC::Machine *machine = AmstradCPC::Machine::AmstradCPC();
|
||||
|
||||
self = [super initWithMachine:machine];
|
||||
if(self) {
|
||||
_amstradCPC.reset(machine);
|
||||
|
||||
NSDictionary *roms = @{
|
||||
@(AmstradCPC::ROMType::OS464) : @"os464",
|
||||
@(AmstradCPC::ROMType::OS664) : @"os664",
|
||||
@(AmstradCPC::ROMType::OS6128) : @"os6128",
|
||||
@(AmstradCPC::ROMType::BASIC464) : @"basic464",
|
||||
@(AmstradCPC::ROMType::BASIC664) : @"basic664",
|
||||
@(AmstradCPC::ROMType::BASIC6128) : @"basic6128",
|
||||
@(AmstradCPC::ROMType::AMSDOS) : @"amsdos",
|
||||
};
|
||||
|
||||
for(NSNumber *key in roms.allKeys) {
|
||||
AmstradCPC::ROMType type = (AmstradCPC::ROMType)key.integerValue;
|
||||
NSString *name = roms[key];
|
||||
NSData *data = [self rom:name];
|
||||
if(data) {
|
||||
_amstradCPC->set_rom(type, data.stdVector8);
|
||||
} else {
|
||||
NSLog(@"Amstrad CPC ROM missing: %@", name);
|
||||
}
|
||||
}
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (NSData *)rom:(NSString *)name {
|
||||
return [[NSBundle mainBundle] dataForResource:name withExtension:@"rom" subdirectory:@"ROMImages/AmstradCPC"];
|
||||
}
|
||||
|
||||
- (NSString *)userDefaultsPrefix { return @"amstradCPC"; }
|
||||
|
||||
#pragma mark - Keyboard Mapping
|
||||
|
||||
- (void)clearAllKeys {
|
||||
@synchronized(self) {
|
||||
_amstradCPC->clear_all_keys();
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setKey:(uint16_t)key isPressed:(BOOL)isPressed {
|
||||
@synchronized(self) {
|
||||
switch(key) {
|
||||
case VK_ANSI_0: _amstradCPC->set_key_state(AmstradCPC::Key::Key0, isPressed); break;
|
||||
case VK_ANSI_1: _amstradCPC->set_key_state(AmstradCPC::Key::Key1, isPressed); break;
|
||||
case VK_ANSI_2: _amstradCPC->set_key_state(AmstradCPC::Key::Key2, isPressed); break;
|
||||
case VK_ANSI_3: _amstradCPC->set_key_state(AmstradCPC::Key::Key3, isPressed); break;
|
||||
case VK_ANSI_4: _amstradCPC->set_key_state(AmstradCPC::Key::Key4, isPressed); break;
|
||||
case VK_ANSI_5: _amstradCPC->set_key_state(AmstradCPC::Key::Key5, isPressed); break;
|
||||
case VK_ANSI_6: _amstradCPC->set_key_state(AmstradCPC::Key::Key6, isPressed); break;
|
||||
case VK_ANSI_7: _amstradCPC->set_key_state(AmstradCPC::Key::Key7, isPressed); break;
|
||||
case VK_ANSI_8: _amstradCPC->set_key_state(AmstradCPC::Key::Key8, isPressed); break;
|
||||
case VK_ANSI_9: _amstradCPC->set_key_state(AmstradCPC::Key::Key9, isPressed); break;
|
||||
|
||||
case VK_ANSI_Q: _amstradCPC->set_key_state(AmstradCPC::Key::KeyQ, isPressed); break;
|
||||
case VK_ANSI_W: _amstradCPC->set_key_state(AmstradCPC::Key::KeyW, isPressed); break;
|
||||
case VK_ANSI_E: _amstradCPC->set_key_state(AmstradCPC::Key::KeyE, isPressed); break;
|
||||
case VK_ANSI_R: _amstradCPC->set_key_state(AmstradCPC::Key::KeyR, isPressed); break;
|
||||
case VK_ANSI_T: _amstradCPC->set_key_state(AmstradCPC::Key::KeyT, isPressed); break;
|
||||
case VK_ANSI_Y: _amstradCPC->set_key_state(AmstradCPC::Key::KeyY, isPressed); break;
|
||||
case VK_ANSI_U: _amstradCPC->set_key_state(AmstradCPC::Key::KeyU, isPressed); break;
|
||||
case VK_ANSI_I: _amstradCPC->set_key_state(AmstradCPC::Key::KeyI, isPressed); break;
|
||||
case VK_ANSI_O: _amstradCPC->set_key_state(AmstradCPC::Key::KeyO, isPressed); break;
|
||||
case VK_ANSI_P: _amstradCPC->set_key_state(AmstradCPC::Key::KeyP, isPressed); break;
|
||||
case VK_ANSI_A: _amstradCPC->set_key_state(AmstradCPC::Key::KeyA, isPressed); break;
|
||||
case VK_ANSI_S: _amstradCPC->set_key_state(AmstradCPC::Key::KeyS, isPressed); break;
|
||||
case VK_ANSI_D: _amstradCPC->set_key_state(AmstradCPC::Key::KeyD, isPressed); break;
|
||||
case VK_ANSI_F: _amstradCPC->set_key_state(AmstradCPC::Key::KeyF, isPressed); break;
|
||||
case VK_ANSI_G: _amstradCPC->set_key_state(AmstradCPC::Key::KeyG, isPressed); break;
|
||||
case VK_ANSI_H: _amstradCPC->set_key_state(AmstradCPC::Key::KeyH, isPressed); break;
|
||||
case VK_ANSI_J: _amstradCPC->set_key_state(AmstradCPC::Key::KeyJ, isPressed); break;
|
||||
case VK_ANSI_K: _amstradCPC->set_key_state(AmstradCPC::Key::KeyK, isPressed); break;
|
||||
case VK_ANSI_L: _amstradCPC->set_key_state(AmstradCPC::Key::KeyL, isPressed); break;
|
||||
case VK_ANSI_Z: _amstradCPC->set_key_state(AmstradCPC::Key::KeyZ, isPressed); break;
|
||||
case VK_ANSI_X: _amstradCPC->set_key_state(AmstradCPC::Key::KeyX, isPressed); break;
|
||||
case VK_ANSI_C: _amstradCPC->set_key_state(AmstradCPC::Key::KeyC, isPressed); break;
|
||||
case VK_ANSI_V: _amstradCPC->set_key_state(AmstradCPC::Key::KeyV, isPressed); break;
|
||||
case VK_ANSI_B: _amstradCPC->set_key_state(AmstradCPC::Key::KeyB, isPressed); break;
|
||||
case VK_ANSI_N: _amstradCPC->set_key_state(AmstradCPC::Key::KeyN, isPressed); break;
|
||||
case VK_ANSI_M: _amstradCPC->set_key_state(AmstradCPC::Key::KeyM, isPressed); break;
|
||||
|
||||
case VK_Space: _amstradCPC->set_key_state(AmstradCPC::Key::KeySpace, isPressed); break;
|
||||
case VK_ANSI_Grave: _amstradCPC->set_key_state(AmstradCPC::Key::KeyCopy, isPressed); break;
|
||||
case VK_Return: _amstradCPC->set_key_state(AmstradCPC::Key::KeyReturn, isPressed); break;
|
||||
case VK_ANSI_Minus: _amstradCPC->set_key_state(AmstradCPC::Key::KeyMinus, isPressed); break;
|
||||
|
||||
case VK_RightArrow: _amstradCPC->set_key_state(AmstradCPC::Key::KeyRight, isPressed); break;
|
||||
case VK_LeftArrow: _amstradCPC->set_key_state(AmstradCPC::Key::KeyLeft, isPressed); break;
|
||||
case VK_DownArrow: _amstradCPC->set_key_state(AmstradCPC::Key::KeyDown, isPressed); break;
|
||||
case VK_UpArrow: _amstradCPC->set_key_state(AmstradCPC::Key::KeyUp, isPressed); break;
|
||||
|
||||
case VK_Delete: _amstradCPC->set_key_state(AmstradCPC::Key::KeyDelete, isPressed); break;
|
||||
case VK_Escape: _amstradCPC->set_key_state(AmstradCPC::Key::KeyEscape, isPressed); break;
|
||||
|
||||
case VK_ANSI_Comma: _amstradCPC->set_key_state(AmstradCPC::Key::KeyComma, isPressed); break;
|
||||
case VK_ANSI_Period: _amstradCPC->set_key_state(AmstradCPC::Key::KeyFullStop, isPressed); break;
|
||||
|
||||
case VK_ANSI_Semicolon:
|
||||
_amstradCPC->set_key_state(AmstradCPC::Key::KeySemicolon, isPressed); break;
|
||||
case VK_ANSI_Quote: _amstradCPC->set_key_state(AmstradCPC::Key::KeyColon, isPressed); break;
|
||||
|
||||
case VK_ANSI_Slash: _amstradCPC->set_key_state(AmstradCPC::Key::KeyForwardSlash, isPressed); break;
|
||||
case VK_ANSI_Backslash: _amstradCPC->set_key_state(AmstradCPC::Key::KeyBackSlash, isPressed); break;
|
||||
|
||||
case VK_Shift: _amstradCPC->set_key_state(AmstradCPC::Key::KeyShift, isPressed); break;
|
||||
case VK_Control: _amstradCPC->set_key_state(AmstradCPC::Key::KeyControl, isPressed); break;
|
||||
|
||||
case VK_F1: _amstradCPC->set_key_state(AmstradCPC::Key::KeyF1, isPressed); break;
|
||||
case VK_F2: _amstradCPC->set_key_state(AmstradCPC::Key::KeyF2, isPressed); break;
|
||||
case VK_F3: _amstradCPC->set_key_state(AmstradCPC::Key::KeyF3, isPressed); break;
|
||||
case VK_F4: _amstradCPC->set_key_state(AmstradCPC::Key::KeyF4, isPressed); break;
|
||||
case VK_F5: _amstradCPC->set_key_state(AmstradCPC::Key::KeyF5, isPressed); break;
|
||||
case VK_F6: _amstradCPC->set_key_state(AmstradCPC::Key::KeyF6, isPressed); break;
|
||||
case VK_F7: _amstradCPC->set_key_state(AmstradCPC::Key::KeyF7, isPressed); break;
|
||||
case VK_F8: _amstradCPC->set_key_state(AmstradCPC::Key::KeyF8, isPressed); break;
|
||||
case VK_F9: _amstradCPC->set_key_state(AmstradCPC::Key::KeyF9, isPressed); break;
|
||||
case VK_F10: _amstradCPC->set_key_state(AmstradCPC::Key::KeyF0, isPressed); break;
|
||||
case VK_F12: _amstradCPC->set_key_state(AmstradCPC::Key::KeyFDot, isPressed); break;
|
||||
|
||||
default:
|
||||
// printf("%02x\n", key);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -12,6 +12,8 @@
|
||||
|
||||
@interface CSAtari2600 : CSMachine <CSJoystickMachine>
|
||||
|
||||
- (instancetype)init;
|
||||
|
||||
- (void)setResetLineEnabled:(BOOL)enabled;
|
||||
|
||||
@property (nonatomic, assign) BOOL colourButton;
|
||||
|
||||
@@ -12,7 +12,16 @@
|
||||
#import "CSMachine+Subclassing.h"
|
||||
|
||||
@implementation CSAtari2600 {
|
||||
Atari2600::Machine _atari2600;
|
||||
std::unique_ptr<Atari2600::Machine> _atari2600;
|
||||
}
|
||||
|
||||
- (instancetype)init {
|
||||
Atari2600::Machine *machine = Atari2600::Machine::Atari2600();
|
||||
self = [super initWithMachine:machine];
|
||||
if(self) {
|
||||
_atari2600.reset(machine);
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)setDirection:(CSJoystickDirection)direction onPad:(NSUInteger)pad isPressed:(BOOL)isPressed {
|
||||
@@ -25,19 +34,19 @@
|
||||
case CSJoystickDirectionRight: input = pad ? Atari2600DigitalInputJoy2Right : Atari2600DigitalInputJoy1Right; break;
|
||||
}
|
||||
@synchronized(self) {
|
||||
_atari2600.set_digital_input(input, isPressed ? true : false);
|
||||
_atari2600->set_digital_input(input, isPressed ? true : false);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setButtonAtIndex:(NSUInteger)button onPad:(NSUInteger)pad isPressed:(BOOL)isPressed {
|
||||
@synchronized(self) {
|
||||
_atari2600.set_digital_input(pad ? Atari2600DigitalInputJoy2Fire : Atari2600DigitalInputJoy1Fire, isPressed ? true : false);
|
||||
_atari2600->set_digital_input(pad ? Atari2600DigitalInputJoy2Fire : Atari2600DigitalInputJoy1Fire, isPressed ? true : false);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setResetLineEnabled:(BOOL)enabled {
|
||||
@synchronized(self) {
|
||||
_atari2600.set_reset_line(enabled ? true : false);
|
||||
_atari2600->set_reset_switch(enabled ? true : false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,40 +56,36 @@
|
||||
}
|
||||
}
|
||||
|
||||
- (CRTMachine::Machine * const)machine {
|
||||
return &_atari2600;
|
||||
}
|
||||
|
||||
#pragma mark - Switches
|
||||
|
||||
- (void)setColourButton:(BOOL)colourButton {
|
||||
_colourButton = colourButton;
|
||||
@synchronized(self) {
|
||||
_atari2600.set_switch_is_enabled(Atari2600SwitchColour, colourButton);
|
||||
_atari2600->set_switch_is_enabled(Atari2600SwitchColour, colourButton);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setLeftPlayerDifficultyButton:(BOOL)leftPlayerDifficultyButton {
|
||||
_leftPlayerDifficultyButton = leftPlayerDifficultyButton;
|
||||
@synchronized(self) {
|
||||
_atari2600.set_switch_is_enabled(Atari2600SwitchLeftPlayerDifficulty, leftPlayerDifficultyButton);
|
||||
_atari2600->set_switch_is_enabled(Atari2600SwitchLeftPlayerDifficulty, leftPlayerDifficultyButton);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setRightPlayerDifficultyButton:(BOOL)rightPlayerDifficultyButton {
|
||||
_rightPlayerDifficultyButton = rightPlayerDifficultyButton;
|
||||
@synchronized(self) {
|
||||
_atari2600.set_switch_is_enabled(Atari2600SwitchRightPlayerDifficulty, rightPlayerDifficultyButton);
|
||||
_atari2600->set_switch_is_enabled(Atari2600SwitchRightPlayerDifficulty, rightPlayerDifficultyButton);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)toggleSwitch:(Atari2600Switch)toggleSwitch {
|
||||
@synchronized(self) {
|
||||
_atari2600.set_switch_is_enabled(toggleSwitch, true);
|
||||
_atari2600->set_switch_is_enabled(toggleSwitch, true);
|
||||
}
|
||||
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
|
||||
@synchronized(self) {
|
||||
_atari2600.set_switch_is_enabled(toggleSwitch, false);
|
||||
_atari2600->set_switch_is_enabled(toggleSwitch, false);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -12,6 +12,8 @@
|
||||
|
||||
@interface CSElectron : CSMachine <CSKeyboardMachine, CSFastLoading>
|
||||
|
||||
- (instancetype)init;
|
||||
|
||||
@property (nonatomic, assign) BOOL useFastLoadingHack;
|
||||
@property (nonatomic, assign) BOOL useTelevisionOutput;
|
||||
|
||||
|
||||
@@ -16,17 +16,16 @@
|
||||
#import "NSBundle+DataResource.h"
|
||||
|
||||
@implementation CSElectron {
|
||||
Electron::Machine _electron;
|
||||
}
|
||||
|
||||
- (CRTMachine::Machine * const)machine {
|
||||
return &_electron;
|
||||
std::unique_ptr<Electron::Machine> _electron;
|
||||
}
|
||||
|
||||
- (instancetype)init {
|
||||
self = [super init];
|
||||
if(self)
|
||||
{
|
||||
Electron::Machine *machine = Electron::Machine::Electron();
|
||||
|
||||
self = [super initWithMachine:machine];
|
||||
if(self) {
|
||||
_electron.reset(machine);
|
||||
|
||||
[self setOSROM:[self rom:@"os"]];
|
||||
[self setBASICROM:[self rom:@"basic"]];
|
||||
[self setDFSROM:[self rom:@"DFS-1770-2.20"]];
|
||||
@@ -38,8 +37,7 @@
|
||||
return self;
|
||||
}
|
||||
|
||||
- (NSData *)rom:(NSString *)name
|
||||
{
|
||||
- (NSData *)rom:(NSString *)name {
|
||||
return [[NSBundle mainBundle] dataForResource:name withExtension:@"rom" subdirectory:@"ROMImages/Electron"];
|
||||
}
|
||||
|
||||
@@ -54,7 +52,7 @@
|
||||
if(rom)
|
||||
{
|
||||
@synchronized(self) {
|
||||
_electron.set_rom((Electron::ROMSlot)slot, rom.stdVector8, false);
|
||||
_electron->set_rom((Electron::ROMSlot)slot, rom.stdVector8, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -63,7 +61,7 @@
|
||||
|
||||
- (void)clearAllKeys {
|
||||
@synchronized(self) {
|
||||
_electron.clear_all_keys();
|
||||
_electron->clear_all_keys();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -71,74 +69,74 @@
|
||||
@synchronized(self) {
|
||||
switch(key)
|
||||
{
|
||||
case VK_ANSI_0: _electron.set_key_state(Electron::Key::Key0, isPressed); break;
|
||||
case VK_ANSI_1: _electron.set_key_state(Electron::Key::Key1, isPressed); break;
|
||||
case VK_ANSI_2: _electron.set_key_state(Electron::Key::Key2, isPressed); break;
|
||||
case VK_ANSI_3: _electron.set_key_state(Electron::Key::Key3, isPressed); break;
|
||||
case VK_ANSI_4: _electron.set_key_state(Electron::Key::Key4, isPressed); break;
|
||||
case VK_ANSI_5: _electron.set_key_state(Electron::Key::Key5, isPressed); break;
|
||||
case VK_ANSI_6: _electron.set_key_state(Electron::Key::Key6, isPressed); break;
|
||||
case VK_ANSI_7: _electron.set_key_state(Electron::Key::Key7, isPressed); break;
|
||||
case VK_ANSI_8: _electron.set_key_state(Electron::Key::Key8, isPressed); break;
|
||||
case VK_ANSI_9: _electron.set_key_state(Electron::Key::Key9, isPressed); break;
|
||||
case VK_ANSI_0: _electron->set_key_state(Electron::Key::Key0, isPressed); break;
|
||||
case VK_ANSI_1: _electron->set_key_state(Electron::Key::Key1, isPressed); break;
|
||||
case VK_ANSI_2: _electron->set_key_state(Electron::Key::Key2, isPressed); break;
|
||||
case VK_ANSI_3: _electron->set_key_state(Electron::Key::Key3, isPressed); break;
|
||||
case VK_ANSI_4: _electron->set_key_state(Electron::Key::Key4, isPressed); break;
|
||||
case VK_ANSI_5: _electron->set_key_state(Electron::Key::Key5, isPressed); break;
|
||||
case VK_ANSI_6: _electron->set_key_state(Electron::Key::Key6, isPressed); break;
|
||||
case VK_ANSI_7: _electron->set_key_state(Electron::Key::Key7, isPressed); break;
|
||||
case VK_ANSI_8: _electron->set_key_state(Electron::Key::Key8, isPressed); break;
|
||||
case VK_ANSI_9: _electron->set_key_state(Electron::Key::Key9, isPressed); break;
|
||||
|
||||
case VK_ANSI_Q: _electron.set_key_state(Electron::Key::KeyQ, isPressed); break;
|
||||
case VK_ANSI_W: _electron.set_key_state(Electron::Key::KeyW, isPressed); break;
|
||||
case VK_ANSI_E: _electron.set_key_state(Electron::Key::KeyE, isPressed); break;
|
||||
case VK_ANSI_R: _electron.set_key_state(Electron::Key::KeyR, isPressed); break;
|
||||
case VK_ANSI_T: _electron.set_key_state(Electron::Key::KeyT, isPressed); break;
|
||||
case VK_ANSI_Y: _electron.set_key_state(Electron::Key::KeyY, isPressed); break;
|
||||
case VK_ANSI_U: _electron.set_key_state(Electron::Key::KeyU, isPressed); break;
|
||||
case VK_ANSI_I: _electron.set_key_state(Electron::Key::KeyI, isPressed); break;
|
||||
case VK_ANSI_O: _electron.set_key_state(Electron::Key::KeyO, isPressed); break;
|
||||
case VK_ANSI_P: _electron.set_key_state(Electron::Key::KeyP, isPressed); break;
|
||||
case VK_ANSI_A: _electron.set_key_state(Electron::Key::KeyA, isPressed); break;
|
||||
case VK_ANSI_S: _electron.set_key_state(Electron::Key::KeyS, isPressed); break;
|
||||
case VK_ANSI_D: _electron.set_key_state(Electron::Key::KeyD, isPressed); break;
|
||||
case VK_ANSI_F: _electron.set_key_state(Electron::Key::KeyF, isPressed); break;
|
||||
case VK_ANSI_G: _electron.set_key_state(Electron::Key::KeyG, isPressed); break;
|
||||
case VK_ANSI_H: _electron.set_key_state(Electron::Key::KeyH, isPressed); break;
|
||||
case VK_ANSI_J: _electron.set_key_state(Electron::Key::KeyJ, isPressed); break;
|
||||
case VK_ANSI_K: _electron.set_key_state(Electron::Key::KeyK, isPressed); break;
|
||||
case VK_ANSI_L: _electron.set_key_state(Electron::Key::KeyL, isPressed); break;
|
||||
case VK_ANSI_Z: _electron.set_key_state(Electron::Key::KeyZ, isPressed); break;
|
||||
case VK_ANSI_X: _electron.set_key_state(Electron::Key::KeyX, isPressed); break;
|
||||
case VK_ANSI_C: _electron.set_key_state(Electron::Key::KeyC, isPressed); break;
|
||||
case VK_ANSI_V: _electron.set_key_state(Electron::Key::KeyV, isPressed); break;
|
||||
case VK_ANSI_B: _electron.set_key_state(Electron::Key::KeyB, isPressed); break;
|
||||
case VK_ANSI_N: _electron.set_key_state(Electron::Key::KeyN, isPressed); break;
|
||||
case VK_ANSI_M: _electron.set_key_state(Electron::Key::KeyM, isPressed); break;
|
||||
case VK_ANSI_Q: _electron->set_key_state(Electron::Key::KeyQ, isPressed); break;
|
||||
case VK_ANSI_W: _electron->set_key_state(Electron::Key::KeyW, isPressed); break;
|
||||
case VK_ANSI_E: _electron->set_key_state(Electron::Key::KeyE, isPressed); break;
|
||||
case VK_ANSI_R: _electron->set_key_state(Electron::Key::KeyR, isPressed); break;
|
||||
case VK_ANSI_T: _electron->set_key_state(Electron::Key::KeyT, isPressed); break;
|
||||
case VK_ANSI_Y: _electron->set_key_state(Electron::Key::KeyY, isPressed); break;
|
||||
case VK_ANSI_U: _electron->set_key_state(Electron::Key::KeyU, isPressed); break;
|
||||
case VK_ANSI_I: _electron->set_key_state(Electron::Key::KeyI, isPressed); break;
|
||||
case VK_ANSI_O: _electron->set_key_state(Electron::Key::KeyO, isPressed); break;
|
||||
case VK_ANSI_P: _electron->set_key_state(Electron::Key::KeyP, isPressed); break;
|
||||
case VK_ANSI_A: _electron->set_key_state(Electron::Key::KeyA, isPressed); break;
|
||||
case VK_ANSI_S: _electron->set_key_state(Electron::Key::KeyS, isPressed); break;
|
||||
case VK_ANSI_D: _electron->set_key_state(Electron::Key::KeyD, isPressed); break;
|
||||
case VK_ANSI_F: _electron->set_key_state(Electron::Key::KeyF, isPressed); break;
|
||||
case VK_ANSI_G: _electron->set_key_state(Electron::Key::KeyG, isPressed); break;
|
||||
case VK_ANSI_H: _electron->set_key_state(Electron::Key::KeyH, isPressed); break;
|
||||
case VK_ANSI_J: _electron->set_key_state(Electron::Key::KeyJ, isPressed); break;
|
||||
case VK_ANSI_K: _electron->set_key_state(Electron::Key::KeyK, isPressed); break;
|
||||
case VK_ANSI_L: _electron->set_key_state(Electron::Key::KeyL, isPressed); break;
|
||||
case VK_ANSI_Z: _electron->set_key_state(Electron::Key::KeyZ, isPressed); break;
|
||||
case VK_ANSI_X: _electron->set_key_state(Electron::Key::KeyX, isPressed); break;
|
||||
case VK_ANSI_C: _electron->set_key_state(Electron::Key::KeyC, isPressed); break;
|
||||
case VK_ANSI_V: _electron->set_key_state(Electron::Key::KeyV, isPressed); break;
|
||||
case VK_ANSI_B: _electron->set_key_state(Electron::Key::KeyB, isPressed); break;
|
||||
case VK_ANSI_N: _electron->set_key_state(Electron::Key::KeyN, isPressed); break;
|
||||
case VK_ANSI_M: _electron->set_key_state(Electron::Key::KeyM, isPressed); break;
|
||||
|
||||
case VK_Space: _electron.set_key_state(Electron::Key::KeySpace, isPressed); break;
|
||||
case VK_Space: _electron->set_key_state(Electron::Key::KeySpace, isPressed); break;
|
||||
case VK_ANSI_Grave:
|
||||
case VK_ANSI_Backslash:
|
||||
_electron.set_key_state(Electron::Key::KeyCopy, isPressed); break;
|
||||
case VK_Return: _electron.set_key_state(Electron::Key::KeyReturn, isPressed); break;
|
||||
case VK_ANSI_Minus: _electron.set_key_state(Electron::Key::KeyMinus, isPressed); break;
|
||||
_electron->set_key_state(Electron::Key::KeyCopy, isPressed); break;
|
||||
case VK_Return: _electron->set_key_state(Electron::Key::KeyReturn, isPressed); break;
|
||||
case VK_ANSI_Minus: _electron->set_key_state(Electron::Key::KeyMinus, isPressed); break;
|
||||
|
||||
case VK_RightArrow: _electron.set_key_state(Electron::Key::KeyRight, isPressed); break;
|
||||
case VK_LeftArrow: _electron.set_key_state(Electron::Key::KeyLeft, isPressed); break;
|
||||
case VK_DownArrow: _electron.set_key_state(Electron::Key::KeyDown, isPressed); break;
|
||||
case VK_UpArrow: _electron.set_key_state(Electron::Key::KeyUp, isPressed); break;
|
||||
case VK_RightArrow: _electron->set_key_state(Electron::Key::KeyRight, isPressed); break;
|
||||
case VK_LeftArrow: _electron->set_key_state(Electron::Key::KeyLeft, isPressed); break;
|
||||
case VK_DownArrow: _electron->set_key_state(Electron::Key::KeyDown, isPressed); break;
|
||||
case VK_UpArrow: _electron->set_key_state(Electron::Key::KeyUp, isPressed); break;
|
||||
|
||||
case VK_Delete: _electron.set_key_state(Electron::Key::KeyDelete, isPressed); break;
|
||||
case VK_Escape: _electron.set_key_state(Electron::Key::KeyEscape, isPressed); break;
|
||||
case VK_Delete: _electron->set_key_state(Electron::Key::KeyDelete, isPressed); break;
|
||||
case VK_Escape: _electron->set_key_state(Electron::Key::KeyEscape, isPressed); break;
|
||||
|
||||
case VK_ANSI_Comma: _electron.set_key_state(Electron::Key::KeyComma, isPressed); break;
|
||||
case VK_ANSI_Period: _electron.set_key_state(Electron::Key::KeyFullStop, isPressed); break;
|
||||
case VK_ANSI_Comma: _electron->set_key_state(Electron::Key::KeyComma, isPressed); break;
|
||||
case VK_ANSI_Period: _electron->set_key_state(Electron::Key::KeyFullStop, isPressed); break;
|
||||
|
||||
case VK_ANSI_Semicolon:
|
||||
_electron.set_key_state(Electron::Key::KeySemiColon, isPressed); break;
|
||||
case VK_ANSI_Quote: _electron.set_key_state(Electron::Key::KeyColon, isPressed); break;
|
||||
_electron->set_key_state(Electron::Key::KeySemiColon, isPressed); break;
|
||||
case VK_ANSI_Quote: _electron->set_key_state(Electron::Key::KeyColon, isPressed); break;
|
||||
|
||||
case VK_ANSI_Slash: _electron.set_key_state(Electron::Key::KeySlash, isPressed); break;
|
||||
case VK_ANSI_Slash: _electron->set_key_state(Electron::Key::KeySlash, isPressed); break;
|
||||
|
||||
case VK_Shift: _electron.set_key_state(Electron::Key::KeyShift, isPressed); break;
|
||||
case VK_Control: _electron.set_key_state(Electron::Key::KeyControl, isPressed); break;
|
||||
case VK_Shift: _electron->set_key_state(Electron::Key::KeyShift, isPressed); break;
|
||||
case VK_Control: _electron->set_key_state(Electron::Key::KeyControl, isPressed); break;
|
||||
case VK_Command:
|
||||
case VK_Option: _electron.set_key_state(Electron::Key::KeyFunc, isPressed); break;
|
||||
case VK_Option: _electron->set_key_state(Electron::Key::KeyFunc, isPressed); break;
|
||||
|
||||
case VK_F12: _electron.set_key_state(Electron::Key::KeyBreak, isPressed); break;
|
||||
case VK_F12: _electron->set_key_state(Electron::Key::KeyBreak, isPressed); break;
|
||||
|
||||
default:
|
||||
// printf("%02x\n", key);
|
||||
@@ -154,19 +152,14 @@
|
||||
- (void)setUseFastLoadingHack:(BOOL)useFastLoadingHack {
|
||||
@synchronized(self) {
|
||||
_useFastLoadingHack = useFastLoadingHack;
|
||||
_electron.set_use_fast_tape_hack(useFastLoadingHack ? true : false);
|
||||
_electron->set_use_fast_tape_hack(useFastLoadingHack ? true : false);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setUseTelevisionOutput:(BOOL)useTelevisionOutput {
|
||||
@synchronized(self) {
|
||||
_useTelevisionOutput = useTelevisionOutput;
|
||||
_electron.get_crt()->set_output_device(useTelevisionOutput ? Outputs::CRT::Television : Outputs::CRT::Monitor);
|
||||
_electron->get_crt()->set_output_device(useTelevisionOutput ? Outputs::CRT::Television : Outputs::CRT::Monitor);
|
||||
}
|
||||
}
|
||||
|
||||
//override func aspectRatio() -> NSSize {
|
||||
// return NSSize(width: 11.0, height: 10.0)
|
||||
// }
|
||||
|
||||
@end
|
||||
|
||||
@@ -12,6 +12,8 @@
|
||||
|
||||
@interface CSOric : CSMachine <CSKeyboardMachine, CSFastLoading>
|
||||
|
||||
- (instancetype)init;
|
||||
|
||||
@property (nonatomic, assign) BOOL useFastLoadingHack;
|
||||
@property (nonatomic, assign) BOOL useCompositeOutput;
|
||||
|
||||
|
||||
@@ -15,118 +15,113 @@
|
||||
#import "NSData+StdVector.h"
|
||||
#import "NSBundle+DataResource.h"
|
||||
|
||||
@implementation CSOric
|
||||
{
|
||||
Oric::Machine _oric;
|
||||
@implementation CSOric {
|
||||
std::unique_ptr<Oric::Machine> _oric;
|
||||
}
|
||||
|
||||
- (instancetype)init
|
||||
{
|
||||
self = [super init];
|
||||
if(self)
|
||||
{
|
||||
- (instancetype)init {
|
||||
Oric::Machine *machine = Oric::Machine::Oric();
|
||||
|
||||
self = [super initWithMachine:machine];
|
||||
if(self) {
|
||||
_oric.reset(machine);
|
||||
|
||||
NSData *basic10 = [self rom:@"basic10"];
|
||||
NSData *basic11 = [self rom:@"basic11"];
|
||||
NSData *colour = [self rom:@"colour"];
|
||||
NSData *microdisc = [self rom:@"microdisc"];
|
||||
|
||||
if(basic10) _oric.set_rom(Oric::BASIC10, basic10.stdVector8);
|
||||
if(basic11) _oric.set_rom(Oric::BASIC11, basic11.stdVector8);
|
||||
if(colour) _oric.set_rom(Oric::Colour, colour.stdVector8);
|
||||
if(microdisc) _oric.set_rom(Oric::Microdisc, microdisc.stdVector8);
|
||||
if(basic10) _oric->set_rom(Oric::BASIC10, basic10.stdVector8);
|
||||
if(basic11) _oric->set_rom(Oric::BASIC11, basic11.stdVector8);
|
||||
if(colour) _oric->set_rom(Oric::Colour, colour.stdVector8);
|
||||
if(microdisc) _oric->set_rom(Oric::Microdisc, microdisc.stdVector8);
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (NSData *)rom:(NSString *)name
|
||||
{
|
||||
- (NSData *)rom:(NSString *)name {
|
||||
return [[NSBundle mainBundle] dataForResource:name withExtension:@"rom" subdirectory:@"ROMImages/Oric"];
|
||||
}
|
||||
|
||||
- (CRTMachine::Machine * const)machine
|
||||
{
|
||||
return &_oric;
|
||||
}
|
||||
|
||||
#pragma mark - CSKeyboardMachine
|
||||
|
||||
- (void)setKey:(uint16_t)key isPressed:(BOOL)isPressed
|
||||
{
|
||||
- (void)setKey:(uint16_t)key isPressed:(BOOL)isPressed {
|
||||
@synchronized(self) {
|
||||
switch(key)
|
||||
{
|
||||
case VK_ANSI_0: _oric.set_key_state(Oric::Key::Key0, isPressed); break;
|
||||
case VK_ANSI_1: _oric.set_key_state(Oric::Key::Key1, isPressed); break;
|
||||
case VK_ANSI_2: _oric.set_key_state(Oric::Key::Key2, isPressed); break;
|
||||
case VK_ANSI_3: _oric.set_key_state(Oric::Key::Key3, isPressed); break;
|
||||
case VK_ANSI_4: _oric.set_key_state(Oric::Key::Key4, isPressed); break;
|
||||
case VK_ANSI_5: _oric.set_key_state(Oric::Key::Key5, isPressed); break;
|
||||
case VK_ANSI_6: _oric.set_key_state(Oric::Key::Key6, isPressed); break;
|
||||
case VK_ANSI_7: _oric.set_key_state(Oric::Key::Key7, isPressed); break;
|
||||
case VK_ANSI_8: _oric.set_key_state(Oric::Key::Key8, isPressed); break;
|
||||
case VK_ANSI_9: _oric.set_key_state(Oric::Key::Key9, isPressed); break;
|
||||
switch(key) {
|
||||
case VK_ANSI_0: _oric->set_key_state(Oric::Key::Key0, isPressed); break;
|
||||
case VK_ANSI_1: _oric->set_key_state(Oric::Key::Key1, isPressed); break;
|
||||
case VK_ANSI_2: _oric->set_key_state(Oric::Key::Key2, isPressed); break;
|
||||
case VK_ANSI_3: _oric->set_key_state(Oric::Key::Key3, isPressed); break;
|
||||
case VK_ANSI_4: _oric->set_key_state(Oric::Key::Key4, isPressed); break;
|
||||
case VK_ANSI_5: _oric->set_key_state(Oric::Key::Key5, isPressed); break;
|
||||
case VK_ANSI_6: _oric->set_key_state(Oric::Key::Key6, isPressed); break;
|
||||
case VK_ANSI_7: _oric->set_key_state(Oric::Key::Key7, isPressed); break;
|
||||
case VK_ANSI_8: _oric->set_key_state(Oric::Key::Key8, isPressed); break;
|
||||
case VK_ANSI_9: _oric->set_key_state(Oric::Key::Key9, isPressed); break;
|
||||
|
||||
case VK_ANSI_Q: _oric.set_key_state(Oric::Key::KeyQ, isPressed); break;
|
||||
case VK_ANSI_W: _oric.set_key_state(Oric::Key::KeyW, isPressed); break;
|
||||
case VK_ANSI_E: _oric.set_key_state(Oric::Key::KeyE, isPressed); break;
|
||||
case VK_ANSI_R: _oric.set_key_state(Oric::Key::KeyR, isPressed); break;
|
||||
case VK_ANSI_T: _oric.set_key_state(Oric::Key::KeyT, isPressed); break;
|
||||
case VK_ANSI_Y: _oric.set_key_state(Oric::Key::KeyY, isPressed); break;
|
||||
case VK_ANSI_U: _oric.set_key_state(Oric::Key::KeyU, isPressed); break;
|
||||
case VK_ANSI_I: _oric.set_key_state(Oric::Key::KeyI, isPressed); break;
|
||||
case VK_ANSI_O: _oric.set_key_state(Oric::Key::KeyO, isPressed); break;
|
||||
case VK_ANSI_P: _oric.set_key_state(Oric::Key::KeyP, isPressed); break;
|
||||
case VK_ANSI_A: _oric.set_key_state(Oric::Key::KeyA, isPressed); break;
|
||||
case VK_ANSI_S: _oric.set_key_state(Oric::Key::KeyS, isPressed); break;
|
||||
case VK_ANSI_D: _oric.set_key_state(Oric::Key::KeyD, isPressed); break;
|
||||
case VK_ANSI_F: _oric.set_key_state(Oric::Key::KeyF, isPressed); break;
|
||||
case VK_ANSI_G: _oric.set_key_state(Oric::Key::KeyG, isPressed); break;
|
||||
case VK_ANSI_H: _oric.set_key_state(Oric::Key::KeyH, isPressed); break;
|
||||
case VK_ANSI_J: _oric.set_key_state(Oric::Key::KeyJ, isPressed); break;
|
||||
case VK_ANSI_K: _oric.set_key_state(Oric::Key::KeyK, isPressed); break;
|
||||
case VK_ANSI_L: _oric.set_key_state(Oric::Key::KeyL, isPressed); break;
|
||||
case VK_ANSI_Z: _oric.set_key_state(Oric::Key::KeyZ, isPressed); break;
|
||||
case VK_ANSI_X: _oric.set_key_state(Oric::Key::KeyX, isPressed); break;
|
||||
case VK_ANSI_C: _oric.set_key_state(Oric::Key::KeyC, isPressed); break;
|
||||
case VK_ANSI_V: _oric.set_key_state(Oric::Key::KeyV, isPressed); break;
|
||||
case VK_ANSI_B: _oric.set_key_state(Oric::Key::KeyB, isPressed); break;
|
||||
case VK_ANSI_N: _oric.set_key_state(Oric::Key::KeyN, isPressed); break;
|
||||
case VK_ANSI_M: _oric.set_key_state(Oric::Key::KeyM, isPressed); break;
|
||||
case VK_ANSI_Q: _oric->set_key_state(Oric::Key::KeyQ, isPressed); break;
|
||||
case VK_ANSI_W: _oric->set_key_state(Oric::Key::KeyW, isPressed); break;
|
||||
case VK_ANSI_E: _oric->set_key_state(Oric::Key::KeyE, isPressed); break;
|
||||
case VK_ANSI_R: _oric->set_key_state(Oric::Key::KeyR, isPressed); break;
|
||||
case VK_ANSI_T: _oric->set_key_state(Oric::Key::KeyT, isPressed); break;
|
||||
case VK_ANSI_Y: _oric->set_key_state(Oric::Key::KeyY, isPressed); break;
|
||||
case VK_ANSI_U: _oric->set_key_state(Oric::Key::KeyU, isPressed); break;
|
||||
case VK_ANSI_I: _oric->set_key_state(Oric::Key::KeyI, isPressed); break;
|
||||
case VK_ANSI_O: _oric->set_key_state(Oric::Key::KeyO, isPressed); break;
|
||||
case VK_ANSI_P: _oric->set_key_state(Oric::Key::KeyP, isPressed); break;
|
||||
case VK_ANSI_A: _oric->set_key_state(Oric::Key::KeyA, isPressed); break;
|
||||
case VK_ANSI_S: _oric->set_key_state(Oric::Key::KeyS, isPressed); break;
|
||||
case VK_ANSI_D: _oric->set_key_state(Oric::Key::KeyD, isPressed); break;
|
||||
case VK_ANSI_F: _oric->set_key_state(Oric::Key::KeyF, isPressed); break;
|
||||
case VK_ANSI_G: _oric->set_key_state(Oric::Key::KeyG, isPressed); break;
|
||||
case VK_ANSI_H: _oric->set_key_state(Oric::Key::KeyH, isPressed); break;
|
||||
case VK_ANSI_J: _oric->set_key_state(Oric::Key::KeyJ, isPressed); break;
|
||||
case VK_ANSI_K: _oric->set_key_state(Oric::Key::KeyK, isPressed); break;
|
||||
case VK_ANSI_L: _oric->set_key_state(Oric::Key::KeyL, isPressed); break;
|
||||
case VK_ANSI_Z: _oric->set_key_state(Oric::Key::KeyZ, isPressed); break;
|
||||
case VK_ANSI_X: _oric->set_key_state(Oric::Key::KeyX, isPressed); break;
|
||||
case VK_ANSI_C: _oric->set_key_state(Oric::Key::KeyC, isPressed); break;
|
||||
case VK_ANSI_V: _oric->set_key_state(Oric::Key::KeyV, isPressed); break;
|
||||
case VK_ANSI_B: _oric->set_key_state(Oric::Key::KeyB, isPressed); break;
|
||||
case VK_ANSI_N: _oric->set_key_state(Oric::Key::KeyN, isPressed); break;
|
||||
case VK_ANSI_M: _oric->set_key_state(Oric::Key::KeyM, isPressed); break;
|
||||
|
||||
case VK_Space: _oric.set_key_state(Oric::Key::KeySpace, isPressed); break;
|
||||
case VK_Return: _oric.set_key_state(Oric::Key::KeyReturn, isPressed); break;
|
||||
case VK_ANSI_Minus: _oric.set_key_state(Oric::Key::KeyMinus, isPressed); break;
|
||||
case VK_ANSI_Equal: _oric.set_key_state(Oric::Key::KeyEquals, isPressed); break;
|
||||
case VK_Space: _oric->set_key_state(Oric::Key::KeySpace, isPressed); break;
|
||||
case VK_Return: _oric->set_key_state(Oric::Key::KeyReturn, isPressed); break;
|
||||
case VK_ANSI_Minus: _oric->set_key_state(Oric::Key::KeyMinus, isPressed); break;
|
||||
case VK_ANSI_Equal: _oric->set_key_state(Oric::Key::KeyEquals, isPressed); break;
|
||||
case VK_ANSI_Backslash:
|
||||
_oric.set_key_state(Oric::Key::KeyBackSlash, isPressed); break;
|
||||
case VK_ANSI_Slash: _oric.set_key_state(Oric::Key::KeyForwardSlash, isPressed); break;
|
||||
_oric->set_key_state(Oric::Key::KeyBackSlash, isPressed); break;
|
||||
case VK_ANSI_Slash: _oric->set_key_state(Oric::Key::KeyForwardSlash, isPressed); break;
|
||||
|
||||
case VK_ANSI_LeftBracket:
|
||||
_oric.set_key_state(Oric::Key::KeyOpenSquare, isPressed); break;
|
||||
_oric->set_key_state(Oric::Key::KeyOpenSquare, isPressed); break;
|
||||
case VK_ANSI_RightBracket:
|
||||
_oric.set_key_state(Oric::Key::KeyCloseSquare, isPressed); break;
|
||||
case VK_ANSI_Quote: _oric.set_key_state(Oric::Key::KeyQuote, isPressed); break;
|
||||
_oric->set_key_state(Oric::Key::KeyCloseSquare, isPressed); break;
|
||||
case VK_ANSI_Quote: _oric->set_key_state(Oric::Key::KeyQuote, isPressed); break;
|
||||
|
||||
case VK_RightArrow: _oric.set_key_state(Oric::Key::KeyRight, isPressed); break;
|
||||
case VK_LeftArrow: _oric.set_key_state(Oric::Key::KeyLeft, isPressed); break;
|
||||
case VK_DownArrow: _oric.set_key_state(Oric::Key::KeyDown, isPressed); break;
|
||||
case VK_UpArrow: _oric.set_key_state(Oric::Key::KeyUp, isPressed); break;
|
||||
case VK_RightArrow: _oric->set_key_state(Oric::Key::KeyRight, isPressed); break;
|
||||
case VK_LeftArrow: _oric->set_key_state(Oric::Key::KeyLeft, isPressed); break;
|
||||
case VK_DownArrow: _oric->set_key_state(Oric::Key::KeyDown, isPressed); break;
|
||||
case VK_UpArrow: _oric->set_key_state(Oric::Key::KeyUp, isPressed); break;
|
||||
|
||||
case VK_Delete: _oric.set_key_state(Oric::Key::KeyDelete, isPressed); break;
|
||||
case VK_Escape: _oric.set_key_state(Oric::Key::KeyEscape, isPressed); break;
|
||||
case VK_Delete: _oric->set_key_state(Oric::Key::KeyDelete, isPressed); break;
|
||||
case VK_Escape: _oric->set_key_state(Oric::Key::KeyEscape, isPressed); break;
|
||||
|
||||
case VK_ANSI_Comma: _oric.set_key_state(Oric::Key::KeyComma, isPressed); break;
|
||||
case VK_ANSI_Period: _oric.set_key_state(Oric::Key::KeyFullStop, isPressed); break;
|
||||
case VK_ANSI_Comma: _oric->set_key_state(Oric::Key::KeyComma, isPressed); break;
|
||||
case VK_ANSI_Period: _oric->set_key_state(Oric::Key::KeyFullStop, isPressed); break;
|
||||
|
||||
case VK_ANSI_Semicolon:
|
||||
_oric.set_key_state(Oric::Key::KeySemiColon, isPressed); break;
|
||||
case VK_ANSI_Semicolon: _oric->set_key_state(Oric::Key::KeySemiColon, isPressed); break;
|
||||
|
||||
case VK_Shift: _oric.set_key_state(Oric::Key::KeyLeftShift, isPressed); break;
|
||||
case VK_RightShift: _oric.set_key_state(Oric::Key::KeyRightShift, isPressed); break;
|
||||
case VK_Control: _oric.set_key_state(Oric::Key::KeyControl, isPressed); break;
|
||||
case VK_Shift:
|
||||
_oric->set_key_state(Oric::Key::KeyLeftShift, isPressed);
|
||||
_oric->set_key_state(Oric::Key::KeyRightShift, isPressed);
|
||||
break;
|
||||
case VK_RightShift: _oric->set_key_state(Oric::Key::KeyRightShift, isPressed); break;
|
||||
case VK_Control: _oric->set_key_state(Oric::Key::KeyControl, isPressed); break;
|
||||
|
||||
case VK_ANSI_Grave:
|
||||
case VK_F12: _oric.set_key_state(Oric::Key::KeyNMI, isPressed); break;
|
||||
case VK_F12: _oric->set_key_state(Oric::Key::KeyNMI, isPressed); break;
|
||||
|
||||
default:
|
||||
printf("%02x\n", key);
|
||||
@@ -135,9 +130,10 @@
|
||||
}
|
||||
}
|
||||
|
||||
- (void)clearAllKeys
|
||||
{
|
||||
_oric.clear_all_keys();
|
||||
- (void)clearAllKeys {
|
||||
@synchronized(self) {
|
||||
_oric->clear_all_keys();
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - Options
|
||||
@@ -145,14 +141,14 @@
|
||||
- (void)setUseFastLoadingHack:(BOOL)useFastLoadingHack {
|
||||
@synchronized(self) {
|
||||
_useFastLoadingHack = useFastLoadingHack;
|
||||
_oric.set_use_fast_tape_hack(useFastLoadingHack ? true : false);
|
||||
_oric->set_use_fast_tape_hack(useFastLoadingHack ? true : false);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setUseCompositeOutput:(BOOL)useCompositeOutput {
|
||||
@synchronized(self) {
|
||||
_useCompositeOutput = useCompositeOutput;
|
||||
_oric.set_output_device(useCompositeOutput ? Outputs::CRT::Television : Outputs::CRT::Monitor);
|
||||
_oric->set_output_device(useCompositeOutput ? Outputs::CRT::Television : Outputs::CRT::Monitor);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -28,6 +28,8 @@ typedef NS_ENUM(NSInteger, CSVic20MemorySize)
|
||||
|
||||
@interface CSVic20 : CSMachine <CSKeyboardMachine, CSFastLoading>
|
||||
|
||||
- (instancetype)init;
|
||||
|
||||
@property (nonatomic, assign) BOOL useFastLoadingHack;
|
||||
@property (nonatomic, assign) CSVic20Country country;
|
||||
@property (nonatomic, assign) CSVic20MemorySize memorySize;
|
||||
|
||||
@@ -13,22 +13,24 @@
|
||||
#include "G64.hpp"
|
||||
#include "D64.hpp"
|
||||
|
||||
#import "CSmachine+Subclassing.h"
|
||||
#import "NSBundle+DataResource.h"
|
||||
|
||||
using namespace Commodore::Vic20;
|
||||
|
||||
@implementation CSVic20 {
|
||||
Machine _vic20;
|
||||
std::unique_ptr<Machine> _vic20;
|
||||
BOOL _joystickMode;
|
||||
}
|
||||
|
||||
- (CRTMachine::Machine * const)machine { return &_vic20; }
|
||||
- (NSString *)userDefaultsPrefix { return @"vic20"; }
|
||||
|
||||
- (instancetype)init {
|
||||
self = [super init];
|
||||
if(self)
|
||||
{
|
||||
Machine *machine = Commodore::Vic20::Machine::Vic20();
|
||||
|
||||
self = [super initWithMachine:machine];
|
||||
if(self) {
|
||||
_vic20.reset(machine);
|
||||
[self setDriveROM:[[NSBundle mainBundle] dataForResource:@"1540" withExtension:@"bin" subdirectory:@"ROMImages/Commodore1540"]];
|
||||
[self setBASICROM:[self rom:@"basic"]];
|
||||
[self setCountry:CSVic20CountryEuropean];
|
||||
@@ -36,8 +38,7 @@ using namespace Commodore::Vic20;
|
||||
return self;
|
||||
}
|
||||
|
||||
- (NSData *)rom:(NSString *)name
|
||||
{
|
||||
- (NSData *)rom:(NSString *)name {
|
||||
return [[NSBundle mainBundle] dataForResource:name withExtension:@"bin" subdirectory:@"ROMImages/Vic20"];
|
||||
}
|
||||
|
||||
@@ -45,7 +46,7 @@ using namespace Commodore::Vic20;
|
||||
|
||||
- (void)setROM:(nonnull NSData *)rom slot:(ROMSlot)slot {
|
||||
@synchronized(self) {
|
||||
_vic20.set_rom(slot, rom.length, (const uint8_t *)rom.bytes);
|
||||
_vic20->set_rom(slot, rom.length, (const uint8_t *)rom.bytes);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -120,32 +121,26 @@ using namespace Commodore::Vic20;
|
||||
// KeyPlus
|
||||
// KeyGBP
|
||||
|
||||
if(key == VK_Tab && isPressed)
|
||||
{
|
||||
if(key == VK_Tab && isPressed) {
|
||||
_joystickMode ^= YES;
|
||||
}
|
||||
|
||||
@synchronized(self) {
|
||||
if(_joystickMode)
|
||||
{
|
||||
switch(key)
|
||||
{
|
||||
case VK_UpArrow: _vic20.set_joystick_state(JoystickInput::Up, isPressed); break;
|
||||
case VK_DownArrow: _vic20.set_joystick_state(JoystickInput::Down, isPressed); break;
|
||||
case VK_LeftArrow: _vic20.set_joystick_state(JoystickInput::Left, isPressed); break;
|
||||
case VK_RightArrow: _vic20.set_joystick_state(JoystickInput::Right, isPressed); break;
|
||||
case VK_ANSI_A: _vic20.set_joystick_state(JoystickInput::Fire, isPressed); break;
|
||||
if(_joystickMode) {
|
||||
switch(key) {
|
||||
case VK_UpArrow: _vic20->set_joystick_state(JoystickInput::Up, isPressed); break;
|
||||
case VK_DownArrow: _vic20->set_joystick_state(JoystickInput::Down, isPressed); break;
|
||||
case VK_LeftArrow: _vic20->set_joystick_state(JoystickInput::Left, isPressed); break;
|
||||
case VK_RightArrow: _vic20->set_joystick_state(JoystickInput::Right, isPressed); break;
|
||||
case VK_ANSI_A: _vic20->set_joystick_state(JoystickInput::Fire, isPressed); break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
switch(key)
|
||||
{
|
||||
} else {
|
||||
switch(key) {
|
||||
default: {
|
||||
NSNumber *targetKey = vicKeysByKeys[@(key)];
|
||||
if(targetKey)
|
||||
{
|
||||
_vic20.set_key_state((Key)targetKey.integerValue, isPressed);
|
||||
_vic20->set_key_state((Key)targetKey.integerValue, isPressed);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -155,8 +150,8 @@ using namespace Commodore::Vic20;
|
||||
|
||||
case VK_Shift:
|
||||
// Yuck
|
||||
_vic20.set_key_state(Key::KeyLShift, isPressed);
|
||||
_vic20.set_key_state(Key::KeyRShift, isPressed);
|
||||
_vic20->set_key_state(Key::KeyLShift, isPressed);
|
||||
_vic20->set_key_state(Key::KeyRShift, isPressed);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -165,7 +160,7 @@ using namespace Commodore::Vic20;
|
||||
|
||||
- (void)clearAllKeys {
|
||||
@synchronized(self) {
|
||||
_vic20.clear_all_keys();
|
||||
_vic20->clear_all_keys();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -174,7 +169,7 @@ using namespace Commodore::Vic20;
|
||||
- (void)setUseFastLoadingHack:(BOOL)useFastLoadingHack {
|
||||
_useFastLoadingHack = useFastLoadingHack;
|
||||
@synchronized(self) {
|
||||
_vic20.set_use_fast_tape_hack(useFastLoadingHack ? true : false);
|
||||
_vic20->set_use_fast_tape_hack(useFastLoadingHack ? true : false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -182,8 +177,7 @@ using namespace Commodore::Vic20;
|
||||
_country = country;
|
||||
NSString *charactersROM, *kernelROM;
|
||||
Commodore::Vic20::Region region;
|
||||
switch(country)
|
||||
{
|
||||
switch(country) {
|
||||
case CSVic20CountryDanish:
|
||||
region = Commodore::Vic20::Region::PAL;
|
||||
charactersROM = @"characters-danish";
|
||||
@@ -212,7 +206,7 @@ using namespace Commodore::Vic20;
|
||||
}
|
||||
|
||||
@synchronized(self) {
|
||||
_vic20.set_region(region);
|
||||
_vic20->set_region(region);
|
||||
[self setCharactersROM:[self rom:charactersROM]];
|
||||
[self setKernelROM:[self rom:kernelROM]];
|
||||
}
|
||||
@@ -222,9 +216,9 @@ using namespace Commodore::Vic20;
|
||||
_memorySize = memorySize;
|
||||
@synchronized(self) {
|
||||
switch(memorySize) {
|
||||
case CSVic20MemorySize5Kb: _vic20.set_memory_size(Commodore::Vic20::Default); break;
|
||||
case CSVic20MemorySize8Kb: _vic20.set_memory_size(Commodore::Vic20::ThreeKB); break;
|
||||
case CSVic20MemorySize32Kb: _vic20.set_memory_size(Commodore::Vic20::ThirtyTwoKB); break;
|
||||
case CSVic20MemorySize5Kb: _vic20->set_memory_size(Commodore::Vic20::Default); break;
|
||||
case CSVic20MemorySize8Kb: _vic20->set_memory_size(Commodore::Vic20::ThreeKB); break;
|
||||
case CSVic20MemorySize32Kb: _vic20->set_memory_size(Commodore::Vic20::ThirtyTwoKB); break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
//
|
||||
// CSZX8081+Instantiation.h
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 27/08/2017.
|
||||
// Copyright © 2017 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#include "StaticAnalyser.hpp"
|
||||
#import "CSZX8081.h"
|
||||
|
||||
@interface CSZX8081 (Instantiation)
|
||||
|
||||
- (instancetype)initWithIntendedTarget:(const StaticAnalyser::Target &)target;
|
||||
|
||||
@end
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user