mirror of
https://github.com/TomHarte/CLK.git
synced 2025-10-25 09:27:01 +00:00
Compare commits
645 Commits
2019-03-10
...
2019-08-01
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0c8e313fd5 | ||
|
|
f64ec11668 | ||
|
|
9bbccd89d3 | ||
|
|
1ae3799aee | ||
|
|
260843e5b1 | ||
|
|
e3f22e5787 | ||
|
|
2aa308efdd | ||
|
|
74c18d7861 | ||
|
|
c41cccd9a6 | ||
|
|
bba34b28b8 | ||
|
|
d8a41575c8 | ||
|
|
0521de668a | ||
|
|
12441bddab | ||
|
|
bc25c52683 | ||
|
|
eb3fb70ea1 | ||
|
|
2f90ed1f32 | ||
|
|
f3dd4b028d | ||
|
|
7dcad516bd | ||
|
|
9859f99513 | ||
|
|
51b7f2777d | ||
|
|
2f2478d2d3 | ||
|
|
a43ada82b2 | ||
|
|
5149f290d0 | ||
|
|
0dc6f08deb | ||
|
|
b1f04ed96d | ||
|
|
cd49b3c89b | ||
|
|
f894d43111 | ||
|
|
4033c0c754 | ||
|
|
786b26d23e | ||
|
|
d08d8ed22c | ||
|
|
b7b62aa3f6 | ||
|
|
39d7e3c62c | ||
|
|
81b57ecf7c | ||
|
|
572e8b52e1 | ||
|
|
9b634472c6 | ||
|
|
d8bc20b1ab | ||
|
|
d2bfd59953 | ||
|
|
3d20ae47ea | ||
|
|
85cf8d89bc | ||
|
|
50e954223a | ||
|
|
109d5d16bd | ||
|
|
1672dc5946 | ||
|
|
5769944918 | ||
|
|
9ef1211d53 | ||
|
|
f2ae04597f | ||
|
|
1327de1c82 | ||
|
|
827c4e172a | ||
|
|
c300bd17fa | ||
|
|
0187fd8eae | ||
|
|
0469f0240b | ||
|
|
4aca6c5ef8 | ||
|
|
d69aee4972 | ||
|
|
3da47318b1 | ||
|
|
ef036df2bc | ||
|
|
579f68cf11 | ||
|
|
90f6ca4635 | ||
|
|
374cac0107 | ||
|
|
4d361b1952 | ||
|
|
fcee7779b0 | ||
|
|
b4191b6225 | ||
|
|
dbee37ab34 | ||
|
|
a3ad0ab09b | ||
|
|
ed0c4c117b | ||
|
|
2432151bf8 | ||
|
|
2129bfc570 | ||
|
|
8de6cd3f44 | ||
|
|
9b9831f28b | ||
|
|
8a2cac0d0c | ||
|
|
e17b105574 | ||
|
|
67c5f6b7cb | ||
|
|
d452d070a1 | ||
|
|
a846c3245d | ||
|
|
4ffa3c1b49 | ||
|
|
b2a6682798 | ||
|
|
f3aac603f8 | ||
|
|
712cb473f7 | ||
|
|
3c68a5ca65 | ||
|
|
20670bab2f | ||
|
|
86d709ae01 | ||
|
|
0aba95cc9d | ||
|
|
de3c8373fd | ||
|
|
75ecd4e72d | ||
|
|
56555a4d99 | ||
|
|
cfad20bb33 | ||
|
|
fa226bb1b9 | ||
|
|
77333ff9f7 | ||
|
|
b9a34bee51 | ||
|
|
22ee51c12c | ||
|
|
ee8d853fcb | ||
|
|
19198ea665 | ||
|
|
bcbda4d855 | ||
|
|
79a624e696 | ||
|
|
c123ca1054 | ||
|
|
9f0f35033d | ||
|
|
3633285aaa | ||
|
|
cb16790330 | ||
|
|
67055d8b56 | ||
|
|
ca37fd8f4c | ||
|
|
46b98dab70 | ||
|
|
0568996264 | ||
|
|
7baad61746 | ||
|
|
1d1e0d74f8 | ||
|
|
d53d1c616f | ||
|
|
5b05a9bc61 | ||
|
|
2c39229b13 | ||
|
|
59b5dfddec | ||
|
|
b730ac5d5a | ||
|
|
4860d8a7df | ||
|
|
9f0cde3d69 | ||
|
|
c8917e677b | ||
|
|
6c2cc206a6 | ||
|
|
5a9f3cfc1e | ||
|
|
8f28b33342 | ||
|
|
cac97a9663 | ||
|
|
2ccb564a7b | ||
|
|
d1d0430fce | ||
|
|
be251d6b03 | ||
|
|
6cfaf920ee | ||
|
|
1657f8768c | ||
|
|
c4ab0bb867 | ||
|
|
886946cc8c | ||
|
|
ed4ddcfda8 | ||
|
|
7886cd63bd | ||
|
|
69b94719a1 | ||
|
|
b4a3f66773 | ||
|
|
ab14433151 | ||
|
|
5078f6fb5c | ||
|
|
fc6d62aefb | ||
|
|
f73bccfec8 | ||
|
|
96be1a3f62 | ||
|
|
52e96e3d2a | ||
|
|
33e2721eb2 | ||
|
|
4bc44666e5 | ||
|
|
3d8e4f96c8 | ||
|
|
94457d81b6 | ||
|
|
c212bf27db | ||
|
|
59b5ee65d4 | ||
|
|
60cedca97b | ||
|
|
1a9aa60bf7 | ||
|
|
6438a5ca1f | ||
|
|
3f303511bd | ||
|
|
fb352a8d40 | ||
|
|
ea7899f47d | ||
|
|
fb6da1de4a | ||
|
|
2651b15db1 | ||
|
|
6e7a733c3c | ||
|
|
245e27c893 | ||
|
|
793c2df7ee | ||
|
|
28de629c08 | ||
|
|
210bcaa56d | ||
|
|
d7329c1bdd | ||
|
|
a5f0761a43 | ||
|
|
dd963d6161 | ||
|
|
96c0253ee2 | ||
|
|
191a7a9386 | ||
|
|
387be4a0a6 | ||
|
|
b9c2c42bc0 | ||
|
|
fffe6ed2df | ||
|
|
c4cbe9476c | ||
|
|
0a67cc3dab | ||
|
|
726e07ed5b | ||
|
|
ebb6313eef | ||
|
|
11d8f765b2 | ||
|
|
514e57b3e9 | ||
|
|
d8fb6fb951 | ||
|
|
255f0d4b2a | ||
|
|
d30e7504c2 | ||
|
|
8d0cd356fd | ||
|
|
aff40bf00a | ||
|
|
eedf7358b4 | ||
|
|
26aebcc167 | ||
|
|
9d420c727e | ||
|
|
60fe84ad16 | ||
|
|
6a44c682ad | ||
|
|
60df44f0ca | ||
|
|
ac926f5070 | ||
|
|
6e9a4a48f7 | ||
|
|
a8894b308a | ||
|
|
7cc91e1bc5 | ||
|
|
9eb51f164c | ||
|
|
a1c00e9318 | ||
|
|
17666bc059 | ||
|
|
241d29ff7c | ||
|
|
c5039a4719 | ||
|
|
fd604048db | ||
|
|
6a77ed1e07 | ||
|
|
9e38815ec4 | ||
|
|
86c325c4ec | ||
|
|
bfcc6cf12c | ||
|
|
8ba8cf7c23 | ||
|
|
6c588a1510 | ||
|
|
651fd9c4a5 | ||
|
|
5d0db2198c | ||
|
|
d81053ea38 | ||
|
|
8d39c3bc98 | ||
|
|
da351a3e32 | ||
|
|
c0591090f5 | ||
|
|
538aecb46e | ||
|
|
dbdbea85c2 | ||
|
|
ba2224dd06 | ||
|
|
44e2aa9183 | ||
|
|
202bff70fe | ||
|
|
26c0cd7f7c | ||
|
|
cb76301fbe | ||
|
|
8bfa12edf1 | ||
|
|
7daa969a5a | ||
|
|
4aeb60100d | ||
|
|
e2c7aaac5a | ||
|
|
6ff661c30d | ||
|
|
79066f8628 | ||
|
|
2c813a2692 | ||
|
|
d2cb595b83 | ||
|
|
cc4abcb00a | ||
|
|
c1ca85987f | ||
|
|
ecb5a0b8cc | ||
|
|
e12e8fc616 | ||
|
|
1fbbf32cd2 | ||
|
|
31edb15369 | ||
|
|
d7883d18d4 | ||
|
|
40100773d3 | ||
|
|
4048ed3a33 | ||
|
|
11f2d3cea7 | ||
|
|
aa656a39b8 | ||
|
|
e830d23533 | ||
|
|
9a666fb8cc | ||
|
|
0e208ed432 | ||
|
|
c8b769de8a | ||
|
|
c447655047 | ||
|
|
3ec9a1d869 | ||
|
|
d326886852 | ||
|
|
faef917cbd | ||
|
|
d27ba90c07 | ||
|
|
db4ca746e3 | ||
|
|
d50fbfb506 | ||
|
|
5d283a9f1f | ||
|
|
86fdc75feb | ||
|
|
b63231523a | ||
|
|
70e296674d | ||
|
|
5089fcd2f6 | ||
|
|
df2ce8ca6f | ||
|
|
7e209353bb | ||
|
|
c2806a94e2 | ||
|
|
d428120776 | ||
|
|
8c8493bc9d | ||
|
|
6b996ae57d | ||
|
|
ccfe1b13cb | ||
|
|
0c1c10bc66 | ||
|
|
fafd1801fe | ||
|
|
bcf6f665b8 | ||
|
|
bd069490b5 | ||
|
|
79d8d27b4c | ||
|
|
624b0b6372 | ||
|
|
7976cf5b3c | ||
|
|
440f52c943 | ||
|
|
47b1218a68 | ||
|
|
91ced056d2 | ||
|
|
8dace34e63 | ||
|
|
8182b0363f | ||
|
|
c5b036fedf | ||
|
|
e26ddd0ed5 | ||
|
|
ca83431e54 | ||
|
|
68a3e5a739 | ||
|
|
b98f10cb45 | ||
|
|
9730800b6a | ||
|
|
506276a2bd | ||
|
|
00c32e4b59 | ||
|
|
df56e6fe53 | ||
|
|
756641e837 | ||
|
|
05c2854dbc | ||
|
|
5c8aacdc17 | ||
|
|
745a5ab749 | ||
|
|
fe0dc4df88 | ||
|
|
33f2664fe9 | ||
|
|
a17e47fa43 | ||
|
|
877b46d2c1 | ||
|
|
cc7226ae9f | ||
|
|
bde975a3b9 | ||
|
|
f6f9024631 | ||
|
|
39aae34323 | ||
|
|
5630141ad7 | ||
|
|
535747e3f2 | ||
|
|
59a94943aa | ||
|
|
bf4889f238 | ||
|
|
7cc5afd798 | ||
|
|
11ab021672 | ||
|
|
feafd4bdae | ||
|
|
d6150645c0 | ||
|
|
ccd2cb44a2 | ||
|
|
ec5701459c | ||
|
|
ad8b68c998 | ||
|
|
c8066b01b6 | ||
|
|
ebd59f4dd3 | ||
|
|
109953ef49 | ||
|
|
124c7bcbb0 | ||
|
|
a0321aa6ff | ||
|
|
567feaac10 | ||
|
|
15c38e2f15 | ||
|
|
3c075e9542 | ||
|
|
9230969f43 | ||
|
|
0e16c67805 | ||
|
|
697e094a4e | ||
|
|
50d37798a2 | ||
|
|
e9d0676e75 | ||
|
|
7591906777 | ||
|
|
08671ed69c | ||
|
|
511d292e73 | ||
|
|
a413ae11cb | ||
|
|
833258f3d7 | ||
|
|
b8a1553368 | ||
|
|
058fe3e986 | ||
|
|
51ee83a427 | ||
|
|
5b21da7874 | ||
|
|
bd7f00bd9c | ||
|
|
517cca251f | ||
|
|
1033abd9fe | ||
|
|
113d022741 | ||
|
|
299a7b99ae | ||
|
|
66540ff86f | ||
|
|
8557558bd8 | ||
|
|
376cf08c71 | ||
|
|
83e5e650d2 | ||
|
|
b860ba2ee3 | ||
|
|
661fe1e649 | ||
|
|
5b8375f0a0 | ||
|
|
abe55fe950 | ||
|
|
4d4ddded6d | ||
|
|
1328708a70 | ||
|
|
85298319fa | ||
|
|
881feb1bd3 | ||
|
|
3e9fa63799 | ||
|
|
da2b190288 | ||
|
|
48d837c636 | ||
|
|
983407896c | ||
|
|
5c08bb810e | ||
|
|
17635da812 | ||
|
|
6d985866ee | ||
|
|
723137c0d4 | ||
|
|
938928865d | ||
|
|
d80b0cbf90 | ||
|
|
e88ef30ce6 | ||
|
|
4197c6f149 | ||
|
|
035f07877c | ||
|
|
4632be4fe5 | ||
|
|
b3d2b4cd37 | ||
|
|
c86fe9ada9 | ||
|
|
ecf93b7822 | ||
|
|
541b75ee6e | ||
|
|
77b08febdb | ||
|
|
fcda376f33 | ||
|
|
0848fc7e03 | ||
|
|
3bb8d6717f | ||
|
|
5e2496d59c | ||
|
|
c52da9d802 | ||
|
|
1d3dde32f2 | ||
|
|
0b999ce0e4 | ||
|
|
b04bd7069d | ||
|
|
249b0fbb32 | ||
|
|
41740fb45e | ||
|
|
0ad88508f7 | ||
|
|
8293b18278 | ||
|
|
2ba0364850 | ||
|
|
8b72043f33 | ||
|
|
2e7bc0b98a | ||
|
|
f0f9722ca6 | ||
|
|
b5ef88902b | ||
|
|
8278809383 | ||
|
|
4367459cf2 | ||
|
|
254132b83d | ||
|
|
7b466e6d0a | ||
|
|
7e6d4f5a3e | ||
|
|
ce099a297a | ||
|
|
949c848815 | ||
|
|
9bf9b9ea8c | ||
|
|
d8ed8b66f3 | ||
|
|
a131d39451 | ||
|
|
b540f58457 | ||
|
|
4f5a38b5c5 | ||
|
|
cefc3af08b | ||
|
|
e6ed50383c | ||
|
|
96facc103a | ||
|
|
407bbfb379 | ||
|
|
a99ebda513 | ||
|
|
537b604fc9 | ||
|
|
98bc570bf7 | ||
|
|
181b77c490 | ||
|
|
bc9eb82e6f | ||
|
|
29fc024ecd | ||
|
|
c1695d0910 | ||
|
|
6d6a4e79c9 | ||
|
|
417a3e1540 | ||
|
|
fa8c804d47 | ||
|
|
68392ce6f5 | ||
|
|
6873f62ad8 | ||
|
|
5f385e15f6 | ||
|
|
8c5d37b6ee | ||
|
|
9c3c2192dd | ||
|
|
4f9f73ca81 | ||
|
|
2c9a1f7b16 | ||
|
|
0ea4c1ac80 | ||
|
|
a873ec97eb | ||
|
|
cc8a65780e | ||
|
|
c117deb43b | ||
|
|
ae31d45c88 | ||
|
|
a0eb20ff1f | ||
|
|
34fe9981e4 | ||
|
|
291e91375f | ||
|
|
857f74b320 | ||
|
|
1d9608efc7 | ||
|
|
93616a4903 | ||
|
|
bb07206c55 | ||
|
|
2e5c0811e7 | ||
|
|
f6ac407e4d | ||
|
|
078c3135df | ||
|
|
92568c90c8 | ||
|
|
f1879c5fbc | ||
|
|
31bb770fdd | ||
|
|
e430f2658f | ||
|
|
3060175ff5 | ||
|
|
eb4233e2fd | ||
|
|
6b4c656849 | ||
|
|
1b8fada6aa | ||
|
|
7332c64964 | ||
|
|
977f9ee831 | ||
|
|
16fb3b49a5 | ||
|
|
3da1b3bf9b | ||
|
|
bc00856c05 | ||
|
|
52e3dece81 | ||
|
|
2c1d8fa18a | ||
|
|
3e34ae67f6 | ||
|
|
d6e16d0042 | ||
|
|
8e02d29ae6 | ||
|
|
ceebecec8d | ||
|
|
05d1eda422 | ||
|
|
31f318ad43 | ||
|
|
270f46e147 | ||
|
|
c0e9c37cc7 | ||
|
|
8564945713 | ||
|
|
7bd7f3fb73 | ||
|
|
5b5bfc8445 | ||
|
|
c466b6f9e7 | ||
|
|
407643c575 | ||
|
|
d9071ee9f1 | ||
|
|
97e118abfa | ||
|
|
412f091d76 | ||
|
|
d9278e9827 | ||
|
|
ca1f669e64 | ||
|
|
0298b1b3b7 | ||
|
|
4b1324de77 | ||
|
|
8e8dce9bec | ||
|
|
f4350522bf | ||
|
|
e2abb66a11 | ||
|
|
ab5fcab9bf | ||
|
|
cf547ef569 | ||
|
|
e75b386f7d | ||
|
|
796203859f | ||
|
|
40f68b70c1 | ||
|
|
40b2fe7339 | ||
|
|
a3b6d2d16e | ||
|
|
3983f8303f | ||
|
|
7cbd5e0ef6 | ||
|
|
dab9bb6575 | ||
|
|
7df85ea695 | ||
|
|
c132bda01c | ||
|
|
4e25bcfcdc | ||
|
|
ea463549c7 | ||
|
|
723acb31b3 | ||
|
|
5725db9234 | ||
|
|
8557e563bc | ||
|
|
d2491633ce | ||
|
|
002796e5f5 | ||
|
|
fa0accf251 | ||
|
|
dcb8176d90 | ||
|
|
be32b1a198 | ||
|
|
582e4acc11 | ||
|
|
10f75acf71 | ||
|
|
b9933f512f | ||
|
|
75a7f7ab22 | ||
|
|
757be2906e | ||
|
|
e214584c76 | ||
|
|
0bb6b498ce | ||
|
|
958d44a20d | ||
|
|
bb9424d944 | ||
|
|
11bf706aa2 | ||
|
|
033b8e6b36 | ||
|
|
7c3ea7b2ea | ||
|
|
a08043ae88 | ||
|
|
7c132a3ed5 | ||
|
|
20e774be1e | ||
|
|
6d6046757d | ||
|
|
55073b0a52 | ||
|
|
44eb4e51ed | ||
|
|
3cb042a49d | ||
|
|
b78ea7d24c | ||
|
|
c66728dce2 | ||
|
|
0be9a0cb88 | ||
|
|
a90f12dab7 | ||
|
|
ef33b004f9 | ||
|
|
2cac4b0d74 | ||
|
|
a49f516265 | ||
|
|
71ac26944d | ||
|
|
2d97fc1f59 | ||
|
|
9ef7743205 | ||
|
|
ee7ae11e90 | ||
|
|
f67d7f1db5 | ||
|
|
99981751a2 | ||
|
|
ffdf02c5df | ||
|
|
27c7d00a05 | ||
|
|
64c4137e5b | ||
|
|
8c26d0c6e6 | ||
|
|
81dcfd9f85 | ||
|
|
9334557fbf | ||
|
|
b09de8efce | ||
|
|
5a50eb56dd | ||
|
|
e49b257e94 | ||
|
|
b8a0f4e831 | ||
|
|
c265ea9847 | ||
|
|
29f8dcfb40 | ||
|
|
0c05983617 | ||
|
|
0bd653708c | ||
|
|
41d800cb63 | ||
|
|
cadc0bd509 | ||
|
|
b64da2710a | ||
|
|
82b08d0e3a | ||
|
|
8f77d1831b | ||
|
|
be722143e1 | ||
|
|
d8d974e2d7 | ||
|
|
9b7ca6f271 | ||
|
|
8ce018dbab | ||
|
|
180062c58c | ||
|
|
6076b8df69 | ||
|
|
5e65ee79b1 | ||
|
|
c0861c7362 | ||
|
|
37656f14d8 | ||
|
|
dec5535e54 | ||
|
|
1f0e3b157a | ||
|
|
d802e83f49 | ||
|
|
ebcae25762 | ||
|
|
5330267d16 | ||
|
|
892476973b | ||
|
|
84f4a25bc9 | ||
|
|
1460a88bb3 | ||
|
|
62e4c23961 | ||
|
|
d25ab35d58 | ||
|
|
a223cd90a1 | ||
|
|
aef92ba29c | ||
|
|
328d297490 | ||
|
|
3d240f3f18 | ||
|
|
45f35236a7 | ||
|
|
fba210f7ce | ||
|
|
8a09e5fc16 | ||
|
|
52e33e861c | ||
|
|
75d8824e6b | ||
|
|
325af677d3 | ||
|
|
1003e70b5e | ||
|
|
d70229201d | ||
|
|
823f91605b | ||
|
|
53f75034fc | ||
|
|
78649a5b54 | ||
|
|
f48db625a0 | ||
|
|
2ba66c4457 | ||
|
|
2c78ea1a4e | ||
|
|
73f50ac44e | ||
|
|
9ce48953c1 | ||
|
|
1098cd0c6b | ||
|
|
652ebd143c | ||
|
|
8e9d7c0f40 | ||
|
|
a64948a2ba | ||
|
|
43f619a081 | ||
|
|
a07de97df4 | ||
|
|
85d25068a8 | ||
|
|
7a0319cfe5 | ||
|
|
f750671f33 | ||
|
|
7886fe677a | ||
|
|
73c027f8e3 | ||
|
|
eda88cc462 | ||
|
|
652f4ebfed | ||
|
|
06a2f59bd0 | ||
|
|
0af57806da | ||
|
|
03f365e696 | ||
|
|
49a22674ba | ||
|
|
ec494511ec | ||
|
|
af02ce9c6e | ||
|
|
56e42859ab | ||
|
|
2d153359f8 | ||
|
|
068ce23716 | ||
|
|
03be2e3652 | ||
|
|
4ef2c0bed8 | ||
|
|
bfd405613c | ||
|
|
73e1c8c780 | ||
|
|
689ba1d4a2 | ||
|
|
39b9d00550 | ||
|
|
64f99d83a4 | ||
|
|
8f1faefa1c | ||
|
|
2c5ff9ada0 | ||
|
|
a9ceef5c37 | ||
|
|
c6f977ed4b | ||
|
|
cb240cd32a | ||
|
|
bc6349f823 | ||
|
|
a93a1ae40f | ||
|
|
25254255fe | ||
|
|
b0b2798f39 | ||
|
|
7f5c637aeb | ||
|
|
42634b500c | ||
|
|
6f0eb5eccd | ||
|
|
3d83891eb0 | ||
|
|
69a2a133d5 | ||
|
|
be4b38c76a | ||
|
|
7163b1132c | ||
|
|
3ccec1c996 | ||
|
|
47359dc8f1 | ||
|
|
43532c8455 | ||
|
|
d7c3d4ce52 | ||
|
|
ed7060a105 | ||
|
|
db0da4b741 | ||
|
|
c9c16968bb | ||
|
|
87420881c8 | ||
|
|
fdc598f2e1 | ||
|
|
f679145bd1 | ||
|
|
eeb161ec51 | ||
|
|
21cb7307d0 | ||
|
|
412a1eb7ee | ||
|
|
1d801acf72 | ||
|
|
0d7bbdad54 | ||
|
|
53b3d9cf9d | ||
|
|
c3ebbfb10e | ||
|
|
58f035e31a | ||
|
|
a8f1d98d40 | ||
|
|
cf6fa98433 | ||
|
|
937b3ca81d | ||
|
|
d0c5cf0d2d | ||
|
|
4cbf2bef82 | ||
|
|
388d808536 | ||
|
|
720aba3f2d | ||
|
|
f9101de956 | ||
|
|
bb04981280 | ||
|
|
57898ed6dd | ||
|
|
33b53e7605 | ||
|
|
9e8928aad9 | ||
|
|
89c71f9119 | ||
|
|
a4f6db6719 | ||
|
|
2d8e65ea32 | ||
|
|
98aa597510 | ||
|
|
de56d48b2f | ||
|
|
4aeb9a7c56 | ||
|
|
b9b52b7c8b |
@@ -59,6 +59,11 @@ KeyboardMachine::Machine *MultiMachine::keyboard_machine() {
|
||||
}
|
||||
}
|
||||
|
||||
MouseMachine::Machine *MultiMachine::mouse_machine() {
|
||||
// TODO.
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
Configurable::Device *MultiMachine::configurable_device() {
|
||||
if(has_picked_) {
|
||||
return machines_.front()->configurable_device();
|
||||
|
||||
@@ -54,6 +54,7 @@ class MultiMachine: public ::Machine::DynamicMachine, public MultiCRTMachine::De
|
||||
Configurable::Device *configurable_device() override;
|
||||
CRTMachine::Machine *crt_machine() override;
|
||||
JoystickMachine::Machine *joystick_machine() override;
|
||||
MouseMachine::Machine *mouse_machine() override;
|
||||
KeyboardMachine::Machine *keyboard_machine() override;
|
||||
MediaTarget::Machine *media_target() override;
|
||||
void *raw_pointer() override;
|
||||
|
||||
@@ -17,6 +17,7 @@ enum class Machine {
|
||||
Atari2600,
|
||||
ColecoVision,
|
||||
Electron,
|
||||
Macintosh,
|
||||
MasterSystem,
|
||||
MSX,
|
||||
Oric,
|
||||
|
||||
@@ -290,6 +290,7 @@ Analyser::Static::TargetList Analyser::Static::MSX::GetTargets(const Media &medi
|
||||
target->region = target->media.tapes.empty() ? Target::Region::USA : Target::Region::Europe;
|
||||
|
||||
// Blindly accept disks for now.
|
||||
// TODO: how to spot an MSX disk?
|
||||
target->media.disks = media.disks;
|
||||
target->has_disk_drive = !media.disks.empty();
|
||||
|
||||
|
||||
26
Analyser/Static/Macintosh/StaticAnalyser.cpp
Normal file
26
Analyser/Static/Macintosh/StaticAnalyser.cpp
Normal file
@@ -0,0 +1,26 @@
|
||||
//
|
||||
// StaticAnalyser.cpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 02/06/2019.
|
||||
// Copyright © 2019 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#include "StaticAnalyser.hpp"
|
||||
#include "Target.hpp"
|
||||
|
||||
Analyser::Static::TargetList Analyser::Static::Macintosh::GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms) {
|
||||
// This analyser can comprehend disks only.
|
||||
if(media.disks.empty()) return {};
|
||||
|
||||
// If there is at least one disk, wave it through.
|
||||
Analyser::Static::TargetList targets;
|
||||
|
||||
using Target = Analyser::Static::Macintosh::Target;
|
||||
auto *target = new Target;
|
||||
target->machine = Analyser::Machine::Macintosh;
|
||||
target->media = media;
|
||||
targets.push_back(std::unique_ptr<Analyser::Static::Target>(target));
|
||||
|
||||
return targets;
|
||||
}
|
||||
27
Analyser/Static/Macintosh/StaticAnalyser.hpp
Normal file
27
Analyser/Static/Macintosh/StaticAnalyser.hpp
Normal file
@@ -0,0 +1,27 @@
|
||||
//
|
||||
// StaticAnalyser.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 02/06/2019.
|
||||
// Copyright © 2019 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef Analyser_Static_Macintosh_StaticAnalyser_hpp
|
||||
#define Analyser_Static_Macintosh_StaticAnalyser_hpp
|
||||
|
||||
#include "../StaticAnalyser.hpp"
|
||||
#include "../../../Storage/TargetPlatforms.hpp"
|
||||
#include <string>
|
||||
|
||||
namespace Analyser {
|
||||
namespace Static {
|
||||
namespace Macintosh {
|
||||
|
||||
TargetList GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#endif /* Analyser_Static_Macintosh_StaticAnalyser_hpp */
|
||||
31
Analyser/Static/Macintosh/Target.hpp
Normal file
31
Analyser/Static/Macintosh/Target.hpp
Normal file
@@ -0,0 +1,31 @@
|
||||
//
|
||||
// Target.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 03/06/2019.
|
||||
// Copyright © 2019 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef Analyser_Static_Macintosh_Target_h
|
||||
#define Analyser_Static_Macintosh_Target_h
|
||||
|
||||
namespace Analyser {
|
||||
namespace Static {
|
||||
namespace Macintosh {
|
||||
|
||||
struct Target: public ::Analyser::Static::Target {
|
||||
enum class Model {
|
||||
Mac128k,
|
||||
Mac512k,
|
||||
Mac512ke,
|
||||
MacPlus
|
||||
};
|
||||
|
||||
Model model = Model::Mac512ke;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* Analyser_Static_Macintosh_Target_h */
|
||||
@@ -21,6 +21,7 @@
|
||||
#include "Coleco/StaticAnalyser.hpp"
|
||||
#include "Commodore/StaticAnalyser.hpp"
|
||||
#include "DiskII/StaticAnalyser.hpp"
|
||||
#include "Macintosh/StaticAnalyser.hpp"
|
||||
#include "MSX/StaticAnalyser.hpp"
|
||||
#include "Oric/StaticAnalyser.hpp"
|
||||
#include "Sega/StaticAnalyser.hpp"
|
||||
@@ -35,6 +36,7 @@
|
||||
#include "../../Storage/Disk/DiskImage/Formats/AppleDSK.hpp"
|
||||
#include "../../Storage/Disk/DiskImage/Formats/CPCDSK.hpp"
|
||||
#include "../../Storage/Disk/DiskImage/Formats/D64.hpp"
|
||||
#include "../../Storage/Disk/DiskImage/Formats/MacintoshIMG.hpp"
|
||||
#include "../../Storage/Disk/DiskImage/Formats/G64.hpp"
|
||||
#include "../../Storage/Disk/DiskImage/Formats/DMK.hpp"
|
||||
#include "../../Storage/Disk/DiskImage/Formats/HFE.hpp"
|
||||
@@ -85,34 +87,37 @@ static Media GetMediaAndPlatforms(const std::string &file_name, TargetPlatform::
|
||||
TryInsert(list, class, platforms) \
|
||||
}
|
||||
|
||||
Format("80", result.tapes, Tape::ZX80O81P, TargetPlatform::ZX8081) // 80
|
||||
Format("81", result.tapes, Tape::ZX80O81P, TargetPlatform::ZX8081) // 81
|
||||
Format("a26", result.cartridges, Cartridge::BinaryDump, TargetPlatform::Atari2600) // A26
|
||||
Format("adf", result.disks, Disk::DiskImageHolder<Storage::Disk::AcornADF>, TargetPlatform::Acorn) // ADF
|
||||
Format("bin", result.cartridges, Cartridge::BinaryDump, TargetPlatform::AllCartridge) // BIN
|
||||
Format("cas", result.tapes, Tape::CAS, TargetPlatform::MSX) // CAS
|
||||
Format("cdt", result.tapes, Tape::TZX, TargetPlatform::AmstradCPC) // CDT
|
||||
Format("col", result.cartridges, Cartridge::BinaryDump, TargetPlatform::ColecoVision) // COL
|
||||
Format("csw", result.tapes, Tape::CSW, TargetPlatform::AllTape) // CSW
|
||||
Format("d64", result.disks, Disk::DiskImageHolder<Storage::Disk::D64>, TargetPlatform::Commodore) // D64
|
||||
Format("dmk", result.disks, Disk::DiskImageHolder<Storage::Disk::DMK>, TargetPlatform::MSX) // DMK
|
||||
Format("do", result.disks, Disk::DiskImageHolder<Storage::Disk::AppleDSK>, TargetPlatform::DiskII) // DO
|
||||
Format("dsd", result.disks, Disk::DiskImageHolder<Storage::Disk::SSD>, TargetPlatform::Acorn) // DSD
|
||||
Format("dsk", result.disks, Disk::DiskImageHolder<Storage::Disk::CPCDSK>, TargetPlatform::AmstradCPC) // DSK (Amstrad CPC)
|
||||
Format("dsk", result.disks, Disk::DiskImageHolder<Storage::Disk::AppleDSK>, TargetPlatform::DiskII) // DSK (Apple)
|
||||
Format("dsk", result.disks, Disk::DiskImageHolder<Storage::Disk::MSXDSK>, TargetPlatform::MSX) // DSK (MSX)
|
||||
Format("dsk", result.disks, Disk::DiskImageHolder<Storage::Disk::OricMFMDSK>, TargetPlatform::Oric) // DSK (Oric)
|
||||
Format("g64", result.disks, Disk::DiskImageHolder<Storage::Disk::G64>, TargetPlatform::Commodore) // G64
|
||||
Format("80", result.tapes, Tape::ZX80O81P, TargetPlatform::ZX8081) // 80
|
||||
Format("81", result.tapes, Tape::ZX80O81P, TargetPlatform::ZX8081) // 81
|
||||
Format("a26", result.cartridges, Cartridge::BinaryDump, TargetPlatform::Atari2600) // A26
|
||||
Format("adf", result.disks, Disk::DiskImageHolder<Storage::Disk::AcornADF>, TargetPlatform::Acorn) // ADF
|
||||
Format("bin", result.cartridges, Cartridge::BinaryDump, TargetPlatform::AllCartridge) // BIN (cartridge dump)
|
||||
Format("cas", result.tapes, Tape::CAS, TargetPlatform::MSX) // CAS
|
||||
Format("cdt", result.tapes, Tape::TZX, TargetPlatform::AmstradCPC) // CDT
|
||||
Format("col", result.cartridges, Cartridge::BinaryDump, TargetPlatform::ColecoVision) // COL
|
||||
Format("csw", result.tapes, Tape::CSW, TargetPlatform::AllTape) // CSW
|
||||
Format("d64", result.disks, Disk::DiskImageHolder<Storage::Disk::D64>, TargetPlatform::Commodore) // D64
|
||||
Format("dmk", result.disks, Disk::DiskImageHolder<Storage::Disk::DMK>, TargetPlatform::MSX) // DMK
|
||||
Format("do", result.disks, Disk::DiskImageHolder<Storage::Disk::AppleDSK>, TargetPlatform::DiskII) // DO
|
||||
Format("dsd", result.disks, Disk::DiskImageHolder<Storage::Disk::SSD>, TargetPlatform::Acorn) // DSD
|
||||
Format("dsk", result.disks, Disk::DiskImageHolder<Storage::Disk::CPCDSK>, TargetPlatform::AmstradCPC) // DSK (Amstrad CPC)
|
||||
Format("dsk", result.disks, Disk::DiskImageHolder<Storage::Disk::AppleDSK>, TargetPlatform::DiskII) // DSK (Apple II)
|
||||
Format("dsk", result.disks, Disk::DiskImageHolder<Storage::Disk::MacintoshIMG>, TargetPlatform::Macintosh) // DSK (Macintosh)
|
||||
Format("dsk", result.disks, Disk::DiskImageHolder<Storage::Disk::MSXDSK>, TargetPlatform::MSX) // DSK (MSX)
|
||||
Format("dsk", result.disks, Disk::DiskImageHolder<Storage::Disk::OricMFMDSK>, TargetPlatform::Oric) // DSK (Oric)
|
||||
Format("g64", result.disks, Disk::DiskImageHolder<Storage::Disk::G64>, TargetPlatform::Commodore) // G64
|
||||
Format( "hfe",
|
||||
result.disks,
|
||||
Disk::DiskImageHolder<Storage::Disk::HFE>,
|
||||
TargetPlatform::Acorn | TargetPlatform::AmstradCPC | TargetPlatform::Commodore | TargetPlatform::Oric)
|
||||
// HFE (TODO: switch to AllDisk once the MSX stops being so greedy)
|
||||
Format("nib", result.disks, Disk::DiskImageHolder<Storage::Disk::NIB>, TargetPlatform::DiskII) // NIB
|
||||
Format("o", result.tapes, Tape::ZX80O81P, TargetPlatform::ZX8081) // O
|
||||
Format("p", result.tapes, Tape::ZX80O81P, TargetPlatform::ZX8081) // P
|
||||
Format("po", result.disks, Disk::DiskImageHolder<Storage::Disk::AppleDSK>, TargetPlatform::DiskII) // PO
|
||||
Format("p81", result.tapes, Tape::ZX80O81P, TargetPlatform::ZX8081) // P81
|
||||
Format("img", result.disks, Disk::DiskImageHolder<Storage::Disk::MacintoshIMG>, TargetPlatform::Macintosh) // IMG (DiskCopy 4.2)
|
||||
Format("image", result.disks, Disk::DiskImageHolder<Storage::Disk::MacintoshIMG>, TargetPlatform::Macintosh) // IMG (DiskCopy 4.2)
|
||||
Format("nib", result.disks, Disk::DiskImageHolder<Storage::Disk::NIB>, TargetPlatform::DiskII) // NIB
|
||||
Format("o", result.tapes, Tape::ZX80O81P, TargetPlatform::ZX8081) // O
|
||||
Format("p", result.tapes, Tape::ZX80O81P, TargetPlatform::ZX8081) // P
|
||||
Format("po", result.disks, Disk::DiskImageHolder<Storage::Disk::AppleDSK>, TargetPlatform::DiskII) // PO
|
||||
Format("p81", result.tapes, Tape::ZX80O81P, TargetPlatform::ZX8081) // P81
|
||||
|
||||
// PRG
|
||||
if(extension == "prg") {
|
||||
@@ -129,16 +134,16 @@ static Media GetMediaAndPlatforms(const std::string &file_name, TargetPlatform::
|
||||
Format( "rom",
|
||||
result.cartridges,
|
||||
Cartridge::BinaryDump,
|
||||
TargetPlatform::AcornElectron | TargetPlatform::ColecoVision | TargetPlatform::MSX) // ROM
|
||||
Format("sg", result.cartridges, Cartridge::BinaryDump, TargetPlatform::Sega) // SG
|
||||
Format("sms", result.cartridges, Cartridge::BinaryDump, TargetPlatform::Sega) // SMS
|
||||
Format("ssd", result.disks, Disk::DiskImageHolder<Storage::Disk::SSD>, TargetPlatform::Acorn) // SSD
|
||||
Format("tap", result.tapes, Tape::CommodoreTAP, TargetPlatform::Commodore) // TAP (Commodore)
|
||||
Format("tap", result.tapes, Tape::OricTAP, TargetPlatform::Oric) // TAP (Oric)
|
||||
Format("tsx", result.tapes, Tape::TZX, TargetPlatform::MSX) // TSX
|
||||
Format("tzx", result.tapes, Tape::TZX, TargetPlatform::ZX8081) // TZX
|
||||
Format("uef", result.tapes, Tape::UEF, TargetPlatform::Acorn) // UEF (tape)
|
||||
Format("woz", result.disks, Disk::DiskImageHolder<Storage::Disk::WOZ>, TargetPlatform::DiskII) // WOZ
|
||||
TargetPlatform::AcornElectron | TargetPlatform::ColecoVision | TargetPlatform::MSX) // ROM
|
||||
Format("sg", result.cartridges, Cartridge::BinaryDump, TargetPlatform::Sega) // SG
|
||||
Format("sms", result.cartridges, Cartridge::BinaryDump, TargetPlatform::Sega) // SMS
|
||||
Format("ssd", result.disks, Disk::DiskImageHolder<Storage::Disk::SSD>, TargetPlatform::Acorn) // SSD
|
||||
Format("tap", result.tapes, Tape::CommodoreTAP, TargetPlatform::Commodore) // TAP (Commodore)
|
||||
Format("tap", result.tapes, Tape::OricTAP, TargetPlatform::Oric) // TAP (Oric)
|
||||
Format("tsx", result.tapes, Tape::TZX, TargetPlatform::MSX) // TSX
|
||||
Format("tzx", result.tapes, Tape::TZX, TargetPlatform::ZX8081) // TZX
|
||||
Format("uef", result.tapes, Tape::UEF, TargetPlatform::Acorn) // UEF (tape)
|
||||
Format("woz", result.disks, Disk::DiskImageHolder<Storage::Disk::WOZ>, TargetPlatform::DiskII) // WOZ
|
||||
|
||||
#undef Format
|
||||
#undef Insert
|
||||
@@ -173,9 +178,10 @@ TargetList Analyser::Static::GetTargets(const std::string &file_name) {
|
||||
if(potential_platforms & TargetPlatform::ColecoVision) Append(Coleco);
|
||||
if(potential_platforms & TargetPlatform::Commodore) Append(Commodore);
|
||||
if(potential_platforms & TargetPlatform::DiskII) Append(DiskII);
|
||||
if(potential_platforms & TargetPlatform::Sega) Append(Sega);
|
||||
if(potential_platforms & TargetPlatform::Macintosh) Append(Macintosh);
|
||||
if(potential_platforms & TargetPlatform::MSX) Append(MSX);
|
||||
if(potential_platforms & TargetPlatform::Oric) Append(Oric);
|
||||
if(potential_platforms & TargetPlatform::Sega) Append(Sega);
|
||||
if(potential_platforms & TargetPlatform::ZX8081) Append(ZX8081);
|
||||
#undef Append
|
||||
|
||||
|
||||
@@ -9,6 +9,8 @@
|
||||
#ifndef ClockReceiver_hpp
|
||||
#define ClockReceiver_hpp
|
||||
|
||||
#include "ForceInline.hpp"
|
||||
|
||||
/*
|
||||
Informal pattern for all classes that run from a clock cycle:
|
||||
|
||||
@@ -52,79 +54,92 @@
|
||||
*/
|
||||
template <class T> class WrappedInt {
|
||||
public:
|
||||
constexpr WrappedInt(int l) : length_(l) {}
|
||||
constexpr WrappedInt() : length_(0) {}
|
||||
forceinline constexpr WrappedInt(int l) noexcept : length_(l) {}
|
||||
forceinline constexpr WrappedInt() noexcept : length_(0) {}
|
||||
|
||||
T &operator =(const T &rhs) {
|
||||
forceinline T &operator =(const T &rhs) {
|
||||
length_ = rhs.length_;
|
||||
return *this;
|
||||
}
|
||||
|
||||
T &operator +=(const T &rhs) {
|
||||
forceinline T &operator +=(const T &rhs) {
|
||||
length_ += rhs.length_;
|
||||
return *static_cast<T *>(this);
|
||||
}
|
||||
|
||||
T &operator -=(const T &rhs) {
|
||||
forceinline T &operator -=(const T &rhs) {
|
||||
length_ -= rhs.length_;
|
||||
return *static_cast<T *>(this);
|
||||
}
|
||||
|
||||
T &operator ++() {
|
||||
forceinline T &operator ++() {
|
||||
++ length_;
|
||||
return *static_cast<T *>(this);
|
||||
}
|
||||
|
||||
T &operator ++(int) {
|
||||
forceinline T &operator ++(int) {
|
||||
length_ ++;
|
||||
return *static_cast<T *>(this);
|
||||
}
|
||||
|
||||
T &operator --() {
|
||||
forceinline T &operator --() {
|
||||
-- length_;
|
||||
return *static_cast<T *>(this);
|
||||
}
|
||||
|
||||
T &operator --(int) {
|
||||
forceinline T &operator --(int) {
|
||||
length_ --;
|
||||
return *static_cast<T *>(this);
|
||||
}
|
||||
|
||||
T &operator %=(const T &rhs) {
|
||||
forceinline T &operator *=(const T &rhs) {
|
||||
length_ *= rhs.length_;
|
||||
return *static_cast<T *>(this);
|
||||
}
|
||||
|
||||
forceinline T &operator /=(const T &rhs) {
|
||||
length_ /= rhs.length_;
|
||||
return *static_cast<T *>(this);
|
||||
}
|
||||
|
||||
forceinline T &operator %=(const T &rhs) {
|
||||
length_ %= rhs.length_;
|
||||
return *static_cast<T *>(this);
|
||||
}
|
||||
|
||||
T &operator &=(const T &rhs) {
|
||||
forceinline T &operator &=(const T &rhs) {
|
||||
length_ &= rhs.length_;
|
||||
return *static_cast<T *>(this);
|
||||
}
|
||||
|
||||
constexpr T operator +(const T &rhs) const { return T(length_ + rhs.length_); }
|
||||
constexpr T operator -(const T &rhs) const { return T(length_ - rhs.length_); }
|
||||
forceinline constexpr T operator +(const T &rhs) const { return T(length_ + rhs.length_); }
|
||||
forceinline constexpr T operator -(const T &rhs) const { return T(length_ - rhs.length_); }
|
||||
|
||||
constexpr T operator %(const T &rhs) const { return T(length_ % rhs.length_); }
|
||||
constexpr T operator &(const T &rhs) const { return T(length_ & rhs.length_); }
|
||||
forceinline constexpr T operator *(const T &rhs) const { return T(length_ * rhs.length_); }
|
||||
forceinline constexpr T operator /(const T &rhs) const { return T(length_ / rhs.length_); }
|
||||
|
||||
constexpr T operator -() const { return T(- length_); }
|
||||
forceinline constexpr T operator %(const T &rhs) const { return T(length_ % rhs.length_); }
|
||||
forceinline constexpr T operator &(const T &rhs) const { return T(length_ & rhs.length_); }
|
||||
|
||||
constexpr bool operator <(const T &rhs) const { return length_ < rhs.length_; }
|
||||
constexpr bool operator >(const T &rhs) const { return length_ > rhs.length_; }
|
||||
constexpr bool operator <=(const T &rhs) const { return length_ <= rhs.length_; }
|
||||
constexpr bool operator >=(const T &rhs) const { return length_ >= rhs.length_; }
|
||||
constexpr bool operator ==(const T &rhs) const { return length_ == rhs.length_; }
|
||||
constexpr bool operator !=(const T &rhs) const { return length_ != rhs.length_; }
|
||||
forceinline constexpr T operator -() const { return T(- length_); }
|
||||
|
||||
constexpr bool operator !() const { return !length_; }
|
||||
forceinline constexpr bool operator <(const T &rhs) const { return length_ < rhs.length_; }
|
||||
forceinline constexpr bool operator >(const T &rhs) const { return length_ > rhs.length_; }
|
||||
forceinline constexpr bool operator <=(const T &rhs) const { return length_ <= rhs.length_; }
|
||||
forceinline constexpr bool operator >=(const T &rhs) const { return length_ >= rhs.length_; }
|
||||
forceinline constexpr bool operator ==(const T &rhs) const { return length_ == rhs.length_; }
|
||||
forceinline constexpr bool operator !=(const T &rhs) const { return length_ != rhs.length_; }
|
||||
|
||||
forceinline constexpr bool operator !() const { return !length_; }
|
||||
// bool operator () is not supported because it offers an implicit cast to int, which is prone silently to permit misuse
|
||||
|
||||
constexpr int as_int() const { return length_; }
|
||||
forceinline constexpr 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.
|
||||
*/
|
||||
T divide(const T &divisor) {
|
||||
forceinline T divide(const T &divisor) {
|
||||
T result(length_ / divisor.length_);
|
||||
length_ %= divisor.length_;
|
||||
return result;
|
||||
@@ -134,10 +149,12 @@ template <class T> class WrappedInt {
|
||||
Flushes the value in @c this. The current value is returned, and the internal value
|
||||
is reset to zero.
|
||||
*/
|
||||
T flush() {
|
||||
T result(length_);
|
||||
length_ = 0;
|
||||
return result;
|
||||
template <typename Result> Result flush() {
|
||||
// Jiggery pokery here; switching to function overloading avoids
|
||||
// the namespace-level requirement for template specialisation.
|
||||
Result r;
|
||||
static_cast<T *>(this)->fill(r);
|
||||
return r;
|
||||
}
|
||||
|
||||
// operator int() is deliberately not provided, to avoid accidental subtitution of
|
||||
@@ -150,51 +167,59 @@ template <class T> class WrappedInt {
|
||||
/// Describes an integer number of whole cycles: pairs of clock signal transitions.
|
||||
class Cycles: public WrappedInt<Cycles> {
|
||||
public:
|
||||
constexpr Cycles(int l) : WrappedInt<Cycles>(l) {}
|
||||
constexpr Cycles() : WrappedInt<Cycles>() {}
|
||||
constexpr Cycles(const Cycles &cycles) : WrappedInt<Cycles>(cycles.length_) {}
|
||||
forceinline constexpr Cycles(int l) noexcept : WrappedInt<Cycles>(l) {}
|
||||
forceinline constexpr Cycles() noexcept : WrappedInt<Cycles>() {}
|
||||
forceinline constexpr Cycles(const Cycles &cycles) noexcept : WrappedInt<Cycles>(cycles.length_) {}
|
||||
|
||||
private:
|
||||
friend WrappedInt;
|
||||
void fill(Cycles &result) {
|
||||
result.length_ = length_;
|
||||
length_ = 0;
|
||||
}
|
||||
};
|
||||
|
||||
/// Describes an integer number of half cycles: single clock signal transitions.
|
||||
class HalfCycles: public WrappedInt<HalfCycles> {
|
||||
public:
|
||||
constexpr HalfCycles(int l) : WrappedInt<HalfCycles>(l) {}
|
||||
constexpr HalfCycles() : WrappedInt<HalfCycles>() {}
|
||||
forceinline constexpr HalfCycles(int l) noexcept : WrappedInt<HalfCycles>(l) {}
|
||||
forceinline constexpr HalfCycles() noexcept : WrappedInt<HalfCycles>() {}
|
||||
|
||||
constexpr HalfCycles(const Cycles cycles) : WrappedInt<HalfCycles>(cycles.as_int() * 2) {}
|
||||
constexpr HalfCycles(const HalfCycles &half_cycles) : WrappedInt<HalfCycles>(half_cycles.length_) {}
|
||||
forceinline constexpr HalfCycles(const Cycles &cycles) noexcept : WrappedInt<HalfCycles>(cycles.as_int() * 2) {}
|
||||
forceinline constexpr HalfCycles(const HalfCycles &half_cycles) noexcept : WrappedInt<HalfCycles>(half_cycles.length_) {}
|
||||
|
||||
/// @returns The number of whole cycles completely covered by this span of half cycles.
|
||||
constexpr Cycles cycles() const {
|
||||
forceinline constexpr Cycles cycles() const {
|
||||
return Cycles(length_ >> 1);
|
||||
}
|
||||
|
||||
/// Flushes the whole cycles in @c this, subtracting that many from the total stored here.
|
||||
Cycles flush_cycles() {
|
||||
Cycles result(length_ >> 1);
|
||||
length_ &= 1;
|
||||
return result;
|
||||
}
|
||||
|
||||
/// Flushes the half cycles in @c this, returning the number stored and setting this total to zero.
|
||||
HalfCycles flush() {
|
||||
HalfCycles result(length_);
|
||||
length_ = 0;
|
||||
return result;
|
||||
}
|
||||
|
||||
/*!
|
||||
Severs from @c this the effect of dividing by @c divisor; @c this will end up with
|
||||
the value of @c this modulo @c divisor and @c divided by @c divisor is returned.
|
||||
*/
|
||||
Cycles divide_cycles(const Cycles &divisor) {
|
||||
HalfCycles half_divisor = HalfCycles(divisor);
|
||||
Cycles result(length_ / half_divisor.length_);
|
||||
forceinline Cycles divide_cycles(const Cycles &divisor) {
|
||||
const HalfCycles half_divisor = HalfCycles(divisor);
|
||||
const Cycles result(length_ / half_divisor.length_);
|
||||
length_ %= half_divisor.length_;
|
||||
return result;
|
||||
}
|
||||
|
||||
private:
|
||||
friend WrappedInt;
|
||||
void fill(Cycles &result) {
|
||||
result = Cycles(length_ >> 1);
|
||||
length_ &= 1;
|
||||
}
|
||||
|
||||
void fill(HalfCycles &result) {
|
||||
result.length_ = length_;
|
||||
length_ = 0;
|
||||
}
|
||||
};
|
||||
|
||||
// Create a specialisation of WrappedInt::flush for converting HalfCycles to Cycles
|
||||
// without losing the fractional part.
|
||||
|
||||
/*!
|
||||
If a component implements only run_for(Cycles), an owner can wrap it in HalfClockReceiver
|
||||
automatically to gain run_for(HalfCycles).
|
||||
@@ -203,9 +228,9 @@ template <class T> class HalfClockReceiver: public T {
|
||||
public:
|
||||
using T::T;
|
||||
|
||||
inline void run_for(const HalfCycles half_cycles) {
|
||||
forceinline void run_for(const HalfCycles half_cycles) {
|
||||
half_cycles_ += half_cycles;
|
||||
T::run_for(half_cycles_.flush_cycles());
|
||||
T::run_for(half_cycles_.flush<Cycles>());
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
@@ -1,26 +1,26 @@
|
||||
//
|
||||
// ClockDeferrer.hpp
|
||||
// DeferredQueue.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 23/08/2018.
|
||||
// Copyright © 2018 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef ClockDeferrer_h
|
||||
#define ClockDeferrer_h
|
||||
#ifndef DeferredQueue_h
|
||||
#define DeferredQueue_h
|
||||
|
||||
#include <functional>
|
||||
#include <vector>
|
||||
|
||||
/*!
|
||||
A ClockDeferrer maintains a list of ordered actions and the times at which
|
||||
A DeferredQueue maintains a list of ordered actions and the times at which
|
||||
they should happen, and divides a total execution period up into the portions
|
||||
that occur between those actions, triggering each action when it is reached.
|
||||
*/
|
||||
template <typename TimeUnit> class ClockDeferrer {
|
||||
template <typename TimeUnit> class DeferredQueue {
|
||||
public:
|
||||
/// Constructs a ClockDeferrer that will call target(period) in between deferred actions.
|
||||
ClockDeferrer(std::function<void(TimeUnit)> &&target) : target_(std::move(target)) {}
|
||||
/// Constructs a DeferredQueue that will call target(period) in between deferred actions.
|
||||
DeferredQueue(std::function<void(TimeUnit)> &&target) : target_(std::move(target)) {}
|
||||
|
||||
/*!
|
||||
Schedules @c action to occur in @c delay units of time.
|
||||
@@ -79,4 +79,4 @@ template <typename TimeUnit> class ClockDeferrer {
|
||||
std::vector<DeferredAction> pending_actions_;
|
||||
};
|
||||
|
||||
#endif /* ClockDeferrer_h */
|
||||
#endif /* DeferredQueue_h */
|
||||
110
ClockReceiver/JustInTime.hpp
Normal file
110
ClockReceiver/JustInTime.hpp
Normal file
@@ -0,0 +1,110 @@
|
||||
//
|
||||
// JustInTime.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 28/07/2019.
|
||||
// Copyright © 2019 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef JustInTime_h
|
||||
#define JustInTime_h
|
||||
|
||||
#include "../Concurrency/AsyncTaskQueue.hpp"
|
||||
|
||||
/*!
|
||||
A JustInTimeActor holds (i) an embedded object with a run_for method; and (ii) an amount
|
||||
of time since run_for was last called.
|
||||
|
||||
Time can be added using the += operator. The -> operator can be used to access the
|
||||
embedded object. All time accumulated will be pushed to object before the pointer is returned.
|
||||
|
||||
Machines that accumulate HalfCycle time but supply to a Cycle-counted device may supply a
|
||||
separate @c TargetTimeScale at template declaration.
|
||||
*/
|
||||
template <class T, class LocalTimeScale, class TargetTimeScale = LocalTimeScale> class JustInTimeActor {
|
||||
public:
|
||||
/// Constructs a new JustInTimeActor using the same construction arguments as the included object.
|
||||
template<typename... Args> JustInTimeActor(Args&&... args) : object_(std::forward<Args>(args)...) {}
|
||||
|
||||
/// Adds time to the actor.
|
||||
inline void operator += (const LocalTimeScale &rhs) {
|
||||
time_since_update_ += rhs;
|
||||
is_flushed_ = false;
|
||||
}
|
||||
|
||||
/// Flushes all accumulated time and returns a pointer to the included object.
|
||||
inline T *operator->() {
|
||||
flush();
|
||||
return &object_;
|
||||
}
|
||||
|
||||
/// Returns a pointer to the included object without flushing time.
|
||||
inline T *last_valid() {
|
||||
return &object_;
|
||||
}
|
||||
|
||||
/// Flushes all accumulated time.
|
||||
inline void flush() {
|
||||
if(!is_flushed_) object_.run_for(time_since_update_.template flush<TargetTimeScale>());
|
||||
is_flushed_ = true;
|
||||
}
|
||||
|
||||
private:
|
||||
T object_;
|
||||
LocalTimeScale time_since_update_;
|
||||
bool is_flushed_ = true;
|
||||
};
|
||||
|
||||
/*!
|
||||
A AsyncJustInTimeActor acts like a JustInTimeActor but additionally contains an AsyncTaskQueue.
|
||||
Any time the amount of accumulated time crosses a threshold provided at construction time,
|
||||
the object will be updated on the AsyncTaskQueue.
|
||||
*/
|
||||
template <class T, class LocalTimeScale, class TargetTimeScale = LocalTimeScale> class AsyncJustInTimeActor {
|
||||
public:
|
||||
/// Constructs a new AsyncJustInTimeActor using the same construction arguments as the included object.
|
||||
template<typename... Args> AsyncJustInTimeActor(TargetTimeScale threshold, Args&&... args) :
|
||||
object_(std::forward<Args>(args)...),
|
||||
threshold_(threshold) {}
|
||||
|
||||
/// Adds time to the actor.
|
||||
inline void operator += (const LocalTimeScale &rhs) {
|
||||
time_since_update_ += rhs;
|
||||
if(time_since_update_ >= threshold_) {
|
||||
time_since_update_ -= threshold_;
|
||||
task_queue_.enqueue([this] () {
|
||||
object_.run_for(threshold_);
|
||||
});
|
||||
}
|
||||
is_flushed_ = false;
|
||||
}
|
||||
|
||||
/// Flushes all accumulated time and returns a pointer to the included object.
|
||||
inline T *operator->() {
|
||||
flush();
|
||||
return &object_;
|
||||
}
|
||||
|
||||
/// Returns a pointer to the included object without flushing time.
|
||||
inline T *last_valid() {
|
||||
return &object_;
|
||||
}
|
||||
|
||||
/// Flushes all accumulated time.
|
||||
inline void flush() {
|
||||
if(!is_flushed_) {
|
||||
task_queue_.flush();
|
||||
object_.run_for(time_since_update_.template flush<TargetTimeScale>());
|
||||
is_flushed_ = true;
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
T object_;
|
||||
LocalTimeScale time_since_update_;
|
||||
TargetTimeScale threshold_;
|
||||
bool is_flushed_ = true;
|
||||
Concurrency::AsyncTaskQueue task_queue_;
|
||||
};
|
||||
|
||||
#endif /* JustInTime_h */
|
||||
@@ -47,6 +47,12 @@ class PortHandler {
|
||||
|
||||
/// Sets the current logical value of the interrupt line.
|
||||
void set_interrupt_status(bool status) {}
|
||||
|
||||
/// Provides a measure of time elapsed between other calls.
|
||||
void run_for(HalfCycles duration) {}
|
||||
|
||||
/// Receives passed-on flush() calls from the 6522.
|
||||
void flush() {}
|
||||
};
|
||||
|
||||
/*!
|
||||
@@ -71,26 +77,6 @@ class IRQDelegatePortHandler: public PortHandler {
|
||||
Delegate *delegate_ = nullptr;
|
||||
};
|
||||
|
||||
class MOS6522Base: public MOS6522Storage {
|
||||
public:
|
||||
/// Sets the input value of line @c line on port @c port.
|
||||
void set_control_line_input(Port port, Line line, bool value);
|
||||
|
||||
/// Runs for a specified number of half cycles.
|
||||
void run_for(const HalfCycles half_cycles);
|
||||
|
||||
/// Runs for a specified number of cycles.
|
||||
void run_for(const Cycles cycles);
|
||||
|
||||
/// @returns @c true if the IRQ line is currently active; @c false otherwise.
|
||||
bool get_interrupt_line();
|
||||
|
||||
private:
|
||||
inline void do_phase1();
|
||||
inline void do_phase2();
|
||||
virtual void reevaluate_interrupts() = 0;
|
||||
};
|
||||
|
||||
/*!
|
||||
Implements a template for emulation of the MOS 6522 Versatile Interface Adaptor ('VIA').
|
||||
|
||||
@@ -102,7 +88,7 @@ class MOS6522Base: public MOS6522Storage {
|
||||
Consumers should derive their own curiously-recurring-template-pattern subclass,
|
||||
implementing bus communications as required.
|
||||
*/
|
||||
template <class T> class MOS6522: public MOS6522Base {
|
||||
template <class T> class MOS6522: public MOS6522Storage {
|
||||
public:
|
||||
MOS6522(T &bus_handler) noexcept : bus_handler_(bus_handler) {}
|
||||
MOS6522(const MOS6522 &) = delete;
|
||||
@@ -116,11 +102,39 @@ template <class T> class MOS6522: public MOS6522Base {
|
||||
/*! @returns the bus handler. */
|
||||
T &bus_handler();
|
||||
|
||||
/// Sets the input value of line @c line on port @c port.
|
||||
void set_control_line_input(Port port, Line line, bool value);
|
||||
|
||||
/// Runs for a specified number of half cycles.
|
||||
void run_for(const HalfCycles half_cycles);
|
||||
|
||||
/// Runs for a specified number of cycles.
|
||||
void run_for(const Cycles cycles);
|
||||
|
||||
/// @returns @c true if the IRQ line is currently active; @c false otherwise.
|
||||
bool get_interrupt_line();
|
||||
|
||||
/// Updates the port handler to the current time and then requests that it flush.
|
||||
void flush();
|
||||
|
||||
private:
|
||||
void do_phase1();
|
||||
void do_phase2();
|
||||
void shift_in();
|
||||
void shift_out();
|
||||
|
||||
T &bus_handler_;
|
||||
HalfCycles time_since_bus_handler_call_;
|
||||
|
||||
void access(int address);
|
||||
|
||||
uint8_t get_port_input(Port port, uint8_t output_mask, uint8_t output);
|
||||
inline void reevaluate_interrupts();
|
||||
|
||||
/// Sets the current intended output value for the port and line;
|
||||
/// if this affects the visible output, it will be passed to the handler.
|
||||
void set_control_line_output(Port port, Line line, LineState value);
|
||||
void evaluate_cb2_output();
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -1,116 +0,0 @@
|
||||
//
|
||||
// 6522Base.cpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 04/09/2017.
|
||||
// Copyright 2017 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#include "../6522.hpp"
|
||||
|
||||
using namespace MOS::MOS6522;
|
||||
|
||||
void MOS6522Base::set_control_line_input(Port port, Line line, bool value) {
|
||||
switch(line) {
|
||||
case Line::One:
|
||||
if( value != control_inputs_[port].line_one &&
|
||||
value == !!(registers_.peripheral_control & (port ? 0x10 : 0x01))
|
||||
) {
|
||||
registers_.interrupt_flags |= port ? InterruptFlag::CB1ActiveEdge : InterruptFlag::CA1ActiveEdge;
|
||||
reevaluate_interrupts();
|
||||
}
|
||||
control_inputs_[port].line_one = value;
|
||||
break;
|
||||
|
||||
case Line::Two:
|
||||
// TODO: output modes, but probably elsewhere?
|
||||
if( value != control_inputs_[port].line_two && // i.e. value has changed ...
|
||||
!(registers_.peripheral_control & (port ? 0x80 : 0x08)) && // ... and line is input ...
|
||||
value == !!(registers_.peripheral_control & (port ? 0x40 : 0x04)) // ... and it's either high or low, as required
|
||||
) {
|
||||
registers_.interrupt_flags |= port ? InterruptFlag::CB2ActiveEdge : InterruptFlag::CA2ActiveEdge;
|
||||
reevaluate_interrupts();
|
||||
}
|
||||
control_inputs_[port].line_two = value;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void MOS6522Base::do_phase2() {
|
||||
registers_.last_timer[0] = registers_.timer[0];
|
||||
registers_.last_timer[1] = registers_.timer[1];
|
||||
|
||||
if(registers_.timer_needs_reload) {
|
||||
registers_.timer_needs_reload = false;
|
||||
registers_.timer[0] = registers_.timer_latch[0];
|
||||
} else {
|
||||
registers_.timer[0] --;
|
||||
}
|
||||
|
||||
registers_.timer[1] --;
|
||||
if(registers_.next_timer[0] >= 0) {
|
||||
registers_.timer[0] = static_cast<uint16_t>(registers_.next_timer[0]);
|
||||
registers_.next_timer[0] = -1;
|
||||
}
|
||||
if(registers_.next_timer[1] >= 0) {
|
||||
registers_.timer[1] = static_cast<uint16_t>(registers_.next_timer[1]);
|
||||
registers_.next_timer[1] = -1;
|
||||
}
|
||||
}
|
||||
|
||||
void MOS6522Base::do_phase1() {
|
||||
// IRQ is raised on the half cycle after overflow
|
||||
if((registers_.timer[1] == 0xffff) && !registers_.last_timer[1] && timer_is_running_[1]) {
|
||||
timer_is_running_[1] = false;
|
||||
registers_.interrupt_flags |= InterruptFlag::Timer2;
|
||||
reevaluate_interrupts();
|
||||
}
|
||||
|
||||
if((registers_.timer[0] == 0xffff) && !registers_.last_timer[0] && timer_is_running_[0]) {
|
||||
registers_.interrupt_flags |= InterruptFlag::Timer1;
|
||||
reevaluate_interrupts();
|
||||
|
||||
if(registers_.auxiliary_control&0x40)
|
||||
registers_.timer_needs_reload = true;
|
||||
else
|
||||
timer_is_running_[0] = false;
|
||||
}
|
||||
}
|
||||
|
||||
/*! Runs for a specified number of half cycles. */
|
||||
void MOS6522Base::run_for(const HalfCycles half_cycles) {
|
||||
int number_of_half_cycles = half_cycles.as_int();
|
||||
|
||||
if(is_phase2_) {
|
||||
do_phase2();
|
||||
number_of_half_cycles--;
|
||||
}
|
||||
|
||||
while(number_of_half_cycles >= 2) {
|
||||
do_phase1();
|
||||
do_phase2();
|
||||
number_of_half_cycles -= 2;
|
||||
}
|
||||
|
||||
if(number_of_half_cycles) {
|
||||
do_phase1();
|
||||
is_phase2_ = true;
|
||||
} else {
|
||||
is_phase2_ = false;
|
||||
}
|
||||
}
|
||||
|
||||
/*! Runs for a specified number of cycles. */
|
||||
void MOS6522Base::run_for(const Cycles cycles) {
|
||||
int number_of_cycles = cycles.as_int();
|
||||
while(number_of_cycles--) {
|
||||
do_phase1();
|
||||
do_phase2();
|
||||
}
|
||||
}
|
||||
|
||||
/*! @returns @c true if the IRQ line is currently active; @c false otherwise. */
|
||||
bool MOS6522Base::get_interrupt_line() {
|
||||
uint8_t interrupt_status = registers_.interrupt_flags & registers_.interrupt_enable & 0x7f;
|
||||
return !!interrupt_status;
|
||||
}
|
||||
@@ -11,29 +11,58 @@
|
||||
namespace MOS {
|
||||
namespace MOS6522 {
|
||||
|
||||
template <typename T> void MOS6522<T>::set_register(int address, uint8_t value) {
|
||||
address &= 0xf;
|
||||
template <typename T> void MOS6522<T>::access(int address) {
|
||||
switch(address) {
|
||||
case 0x0:
|
||||
// In both handshake and pulse modes, CB2 goes low on any read or write of Port B.
|
||||
if(handshake_modes_[1] != HandshakeMode::None) {
|
||||
set_control_line_output(Port::B, Line::Two, LineState::Off);
|
||||
}
|
||||
break;
|
||||
|
||||
case 0xf:
|
||||
case 0x1:
|
||||
// In both handshake and pulse modes, CA2 goes low on any read or write of Port A.
|
||||
if(handshake_modes_[0] != HandshakeMode::None) {
|
||||
set_control_line_output(Port::A, Line::Two, LineState::Off);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T> void MOS6522<T>::set_register(int address, uint8_t value) {
|
||||
address &= 0xf;
|
||||
access(address);
|
||||
switch(address) {
|
||||
case 0x0: // Write Port B.
|
||||
// Store locally and communicate outwards.
|
||||
registers_.output[1] = value;
|
||||
bus_handler_.set_port_output(Port::B, value, registers_.data_direction[1]); // TODO: handshake
|
||||
|
||||
bus_handler_.run_for(time_since_bus_handler_call_.flush<HalfCycles>());
|
||||
bus_handler_.set_port_output(Port::B, value, registers_.data_direction[1]);
|
||||
|
||||
registers_.interrupt_flags &= ~(InterruptFlag::CB1ActiveEdge | ((registers_.peripheral_control&0x20) ? 0 : InterruptFlag::CB2ActiveEdge));
|
||||
reevaluate_interrupts();
|
||||
break;
|
||||
case 0xf:
|
||||
case 0x1:
|
||||
case 0x1: // Write Port A.
|
||||
registers_.output[0] = value;
|
||||
bus_handler_.set_port_output(Port::A, value, registers_.data_direction[0]); // TODO: handshake
|
||||
|
||||
bus_handler_.run_for(time_since_bus_handler_call_.flush<HalfCycles>());
|
||||
bus_handler_.set_port_output(Port::A, value, registers_.data_direction[0]);
|
||||
|
||||
if(handshake_modes_[1] != HandshakeMode::None) {
|
||||
set_control_line_output(Port::A, Line::Two, LineState::Off);
|
||||
}
|
||||
|
||||
registers_.interrupt_flags &= ~(InterruptFlag::CA1ActiveEdge | ((registers_.peripheral_control&0x02) ? 0 : InterruptFlag::CB2ActiveEdge));
|
||||
reevaluate_interrupts();
|
||||
break;
|
||||
|
||||
case 0x2:
|
||||
case 0x2: // Port B direction.
|
||||
registers_.data_direction[1] = value;
|
||||
break;
|
||||
case 0x3:
|
||||
case 0x3: // Port A direction.
|
||||
registers_.data_direction[0] = value;
|
||||
break;
|
||||
|
||||
@@ -59,31 +88,57 @@ template <typename T> void MOS6522<T>::set_register(int address, uint8_t value)
|
||||
break;
|
||||
|
||||
// Shift
|
||||
case 0xa: registers_.shift = value; break;
|
||||
case 0xa:
|
||||
registers_.shift = value;
|
||||
shift_bits_remaining_ = 8;
|
||||
registers_.interrupt_flags &= ~InterruptFlag::ShiftRegister;
|
||||
reevaluate_interrupts();
|
||||
break;
|
||||
|
||||
// Control
|
||||
case 0xb:
|
||||
registers_.auxiliary_control = value;
|
||||
evaluate_cb2_output();
|
||||
break;
|
||||
case 0xc:
|
||||
case 0xc: {
|
||||
// const auto old_peripheral_control = registers_.peripheral_control;
|
||||
registers_.peripheral_control = value;
|
||||
|
||||
// TODO: simplify below; trying to avoid improper logging of unimplemented warnings in input mode
|
||||
if(value & 0x08) {
|
||||
switch(value & 0x0e) {
|
||||
default: LOG("Unimplemented control line mode " << int((value >> 1)&7)); break;
|
||||
case 0x0c: bus_handler_.set_control_line_output(Port::A, Line::Two, false); break;
|
||||
case 0x0e: bus_handler_.set_control_line_output(Port::A, Line::Two, true); break;
|
||||
int shift = 0;
|
||||
for(int port = 0; port < 2; ++port) {
|
||||
handshake_modes_[port] = HandshakeMode::None;
|
||||
switch((value >> shift) & 0x0e) {
|
||||
default: break;
|
||||
|
||||
case 0x00: // Negative interrupt input; set Cx2 interrupt on negative Cx2 transition, clear on access to Port x register.
|
||||
case 0x02: // Independent negative interrupt input; set Cx2 interrupt on negative transition, don't clear automatically.
|
||||
case 0x04: // Positive interrupt input; set Cx2 interrupt on positive Cx2 transition, clear on access to Port x register.
|
||||
case 0x06: // Independent positive interrupt input; set Cx2 interrupt on positive transition, don't clear automatically.
|
||||
set_control_line_output(Port(port), Line::Two, LineState::Input);
|
||||
break;
|
||||
|
||||
case 0x08: // Handshake: set Cx2 to low on any read or write of Port x; set to high on an active transition of Cx1.
|
||||
handshake_modes_[port] = HandshakeMode::Handshake;
|
||||
set_control_line_output(Port(port), Line::Two, LineState::Off); // At a guess.
|
||||
break;
|
||||
|
||||
case 0x0a: // Pulse output: Cx2 is low for one cycle following a read or write of Port x.
|
||||
handshake_modes_[port] = HandshakeMode::Pulse;
|
||||
set_control_line_output(Port(port), Line::Two, LineState::On);
|
||||
break;
|
||||
|
||||
case 0x0c: // Manual output: Cx2 low.
|
||||
set_control_line_output(Port(port), Line::Two, LineState::Off);
|
||||
break;
|
||||
|
||||
case 0x0e: // Manual output: Cx2 high.
|
||||
set_control_line_output(Port(port), Line::Two, LineState::On);
|
||||
break;
|
||||
}
|
||||
|
||||
shift += 4;
|
||||
}
|
||||
if(value & 0x80) {
|
||||
switch(value & 0xe0) {
|
||||
default: LOG("Unimplemented control line mode " << int((value >> 5)&7)); break;
|
||||
case 0xc0: bus_handler_.set_control_line_output(Port::B, Line::Two, false); break;
|
||||
case 0xe0: bus_handler_.set_control_line_output(Port::B, Line::Two, true); break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
} break;
|
||||
|
||||
// Interrupt control
|
||||
case 0xd:
|
||||
@@ -102,12 +157,13 @@ template <typename T> void MOS6522<T>::set_register(int address, uint8_t value)
|
||||
|
||||
template <typename T> uint8_t MOS6522<T>::get_register(int address) {
|
||||
address &= 0xf;
|
||||
access(address);
|
||||
switch(address) {
|
||||
case 0x0:
|
||||
registers_.interrupt_flags &= ~(InterruptFlag::CB1ActiveEdge | InterruptFlag::CB2ActiveEdge);
|
||||
reevaluate_interrupts();
|
||||
return get_port_input(Port::B, registers_.data_direction[1], registers_.output[1]);
|
||||
case 0xf: // TODO: handshake, latching
|
||||
case 0xf:
|
||||
case 0x1:
|
||||
registers_.interrupt_flags &= ~(InterruptFlag::CA1ActiveEdge | InterruptFlag::CA2ActiveEdge);
|
||||
reevaluate_interrupts();
|
||||
@@ -132,7 +188,11 @@ template <typename T> uint8_t MOS6522<T>::get_register(int address) {
|
||||
return registers_.timer[1] & 0x00ff;
|
||||
case 0x9: return registers_.timer[1] >> 8;
|
||||
|
||||
case 0xa: return registers_.shift;
|
||||
case 0xa:
|
||||
shift_bits_remaining_ = 8;
|
||||
registers_.interrupt_flags &= ~InterruptFlag::ShiftRegister;
|
||||
reevaluate_interrupts();
|
||||
return registers_.shift;
|
||||
|
||||
case 0xb: return registers_.auxiliary_control;
|
||||
case 0xc: return registers_.peripheral_control;
|
||||
@@ -145,7 +205,8 @@ template <typename T> uint8_t MOS6522<T>::get_register(int address) {
|
||||
}
|
||||
|
||||
template <typename T> uint8_t MOS6522<T>::get_port_input(Port port, uint8_t output_mask, uint8_t output) {
|
||||
uint8_t input = bus_handler_.get_port_input(port);
|
||||
bus_handler_.run_for(time_since_bus_handler_call_.flush<HalfCycles>());
|
||||
const uint8_t input = bus_handler_.get_port_input(port);
|
||||
return (input & ~output_mask) | (output & output_mask);
|
||||
}
|
||||
|
||||
@@ -158,9 +219,238 @@ template <typename T> void MOS6522<T>::reevaluate_interrupts() {
|
||||
bool new_interrupt_status = get_interrupt_line();
|
||||
if(new_interrupt_status != last_posted_interrupt_status_) {
|
||||
last_posted_interrupt_status_ = new_interrupt_status;
|
||||
|
||||
bus_handler_.run_for(time_since_bus_handler_call_.flush<HalfCycles>());
|
||||
bus_handler_.set_interrupt_status(new_interrupt_status);
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T> void MOS6522<T>::set_control_line_input(Port port, Line line, bool value) {
|
||||
switch(line) {
|
||||
case Line::One:
|
||||
if(value != control_inputs_[port].lines[line]) {
|
||||
// In handshake mode, any transition on C[A/B]1 sets output high on C[A/B]2.
|
||||
if(handshake_modes_[port] == HandshakeMode::Handshake) {
|
||||
set_control_line_output(port, Line::Two, LineState::On);
|
||||
}
|
||||
|
||||
// Set the proper transition interrupt bit if enabled.
|
||||
if(value == !!(registers_.peripheral_control & (port ? 0x10 : 0x01))) {
|
||||
registers_.interrupt_flags |= port ? InterruptFlag::CB1ActiveEdge : InterruptFlag::CA1ActiveEdge;
|
||||
reevaluate_interrupts();
|
||||
}
|
||||
|
||||
// If this is a transition on CB1, consider updating the shift register.
|
||||
// TODO: and at least one full clock since the shift register was written?
|
||||
if(port == Port::B) {
|
||||
switch(shift_mode()) {
|
||||
default: break;
|
||||
case ShiftMode::InUnderCB1: if(value) shift_in(); break; // Shifts in are captured on a low-to-high transition.
|
||||
case ShiftMode::OutUnderCB1: if(!value) shift_out(); break; // Shifts out are updated on a high-to-low transition.
|
||||
}
|
||||
}
|
||||
}
|
||||
control_inputs_[port].lines[line] = value;
|
||||
break;
|
||||
|
||||
case Line::Two:
|
||||
if( value != control_inputs_[port].lines[line] && // i.e. value has changed ...
|
||||
!(registers_.peripheral_control & (port ? 0x80 : 0x08)) && // ... and line is input ...
|
||||
value == !!(registers_.peripheral_control & (port ? 0x40 : 0x04)) // ... and it's either high or low, as required
|
||||
) {
|
||||
registers_.interrupt_flags |= port ? InterruptFlag::CB2ActiveEdge : InterruptFlag::CA2ActiveEdge;
|
||||
reevaluate_interrupts();
|
||||
}
|
||||
control_inputs_[port].lines[line] = value;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T> void MOS6522<T>::do_phase2() {
|
||||
++ time_since_bus_handler_call_;
|
||||
|
||||
registers_.last_timer[0] = registers_.timer[0];
|
||||
registers_.last_timer[1] = registers_.timer[1];
|
||||
|
||||
if(registers_.timer_needs_reload) {
|
||||
registers_.timer_needs_reload = false;
|
||||
registers_.timer[0] = registers_.timer_latch[0];
|
||||
} else {
|
||||
registers_.timer[0] --;
|
||||
}
|
||||
|
||||
registers_.timer[1] --;
|
||||
if(registers_.next_timer[0] >= 0) {
|
||||
registers_.timer[0] = static_cast<uint16_t>(registers_.next_timer[0]);
|
||||
registers_.next_timer[0] = -1;
|
||||
}
|
||||
if(registers_.next_timer[1] >= 0) {
|
||||
registers_.timer[1] = static_cast<uint16_t>(registers_.next_timer[1]);
|
||||
registers_.next_timer[1] = -1;
|
||||
}
|
||||
|
||||
// In pulse modes, CA2 and CB2 go high again on the next clock edge.
|
||||
if(handshake_modes_[1] == HandshakeMode::Pulse) {
|
||||
set_control_line_output(Port::B, Line::Two, LineState::On);
|
||||
}
|
||||
if(handshake_modes_[0] == HandshakeMode::Pulse) {
|
||||
set_control_line_output(Port::A, Line::Two, LineState::On);
|
||||
}
|
||||
|
||||
// If the shift register is shifting according to the input clock, do a shift.
|
||||
switch(shift_mode()) {
|
||||
default: break;
|
||||
case ShiftMode::InUnderPhase2: shift_in(); break;
|
||||
case ShiftMode::OutUnderPhase2: shift_out(); break;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T> void MOS6522<T>::do_phase1() {
|
||||
++ time_since_bus_handler_call_;
|
||||
|
||||
// IRQ is raised on the half cycle after overflow
|
||||
if((registers_.timer[1] == 0xffff) && !registers_.last_timer[1] && timer_is_running_[1]) {
|
||||
timer_is_running_[1] = false;
|
||||
|
||||
// If the shift register is shifting according to this timer, do a shift.
|
||||
// TODO: "shift register is driven by only the low order 8 bits of timer 2"?
|
||||
switch(shift_mode()) {
|
||||
default: break;
|
||||
case ShiftMode::InUnderT2: shift_in(); break;
|
||||
case ShiftMode::OutUnderT2FreeRunning: shift_out(); break;
|
||||
case ShiftMode::OutUnderT2: shift_out(); break; // TODO: present a clock on CB1.
|
||||
}
|
||||
|
||||
registers_.interrupt_flags |= InterruptFlag::Timer2;
|
||||
reevaluate_interrupts();
|
||||
}
|
||||
|
||||
if((registers_.timer[0] == 0xffff) && !registers_.last_timer[0] && timer_is_running_[0]) {
|
||||
registers_.interrupt_flags |= InterruptFlag::Timer1;
|
||||
reevaluate_interrupts();
|
||||
|
||||
// Determine whether to reload.
|
||||
if(registers_.auxiliary_control&0x40)
|
||||
registers_.timer_needs_reload = true;
|
||||
else
|
||||
timer_is_running_[0] = false;
|
||||
|
||||
// Determine whether to toggle PB7.
|
||||
if(registers_.auxiliary_control&0x80) {
|
||||
registers_.output[1] ^= 0x80;
|
||||
bus_handler_.run_for(time_since_bus_handler_call_.flush<HalfCycles>());
|
||||
bus_handler_.set_port_output(Port::B, registers_.output[1], registers_.data_direction[1]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*! Runs for a specified number of half cycles. */
|
||||
template <typename T> void MOS6522<T>::run_for(const HalfCycles half_cycles) {
|
||||
int number_of_half_cycles = half_cycles.as_int();
|
||||
if(!number_of_half_cycles) return;
|
||||
|
||||
if(is_phase2_) {
|
||||
do_phase2();
|
||||
number_of_half_cycles--;
|
||||
}
|
||||
|
||||
while(number_of_half_cycles >= 2) {
|
||||
do_phase1();
|
||||
do_phase2();
|
||||
number_of_half_cycles -= 2;
|
||||
}
|
||||
|
||||
if(number_of_half_cycles) {
|
||||
do_phase1();
|
||||
is_phase2_ = true;
|
||||
} else {
|
||||
is_phase2_ = false;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T> void MOS6522<T>::flush() {
|
||||
bus_handler_.run_for(time_since_bus_handler_call_.flush<HalfCycles>());
|
||||
bus_handler_.flush();
|
||||
}
|
||||
|
||||
/*! Runs for a specified number of cycles. */
|
||||
template <typename T> void MOS6522<T>::run_for(const Cycles cycles) {
|
||||
int number_of_cycles = cycles.as_int();
|
||||
while(number_of_cycles--) {
|
||||
do_phase1();
|
||||
do_phase2();
|
||||
}
|
||||
}
|
||||
|
||||
/*! @returns @c true if the IRQ line is currently active; @c false otherwise. */
|
||||
template <typename T> bool MOS6522<T>::get_interrupt_line() {
|
||||
uint8_t interrupt_status = registers_.interrupt_flags & registers_.interrupt_enable & 0x7f;
|
||||
return !!interrupt_status;
|
||||
}
|
||||
|
||||
template <typename T> void MOS6522<T>::evaluate_cb2_output() {
|
||||
// CB2 is a special case, being both the line the shift register can output to,
|
||||
// and one that can be used as an input or handshaking output according to the
|
||||
// peripheral control register.
|
||||
|
||||
// My guess: other CB2 functions work only if the shift register is disabled (?).
|
||||
if(shift_mode() != ShiftMode::Disabled) {
|
||||
// Shift register is enabled, one way or the other; but announce only output.
|
||||
if(is_shifting_out()) {
|
||||
// Output mode; set the level according to the current top of the shift register.
|
||||
bus_handler_.set_control_line_output(Port::B, Line::Two, !!(registers_.shift & 0x80));
|
||||
} else {
|
||||
// Input mode.
|
||||
bus_handler_.set_control_line_output(Port::B, Line::Two, true);
|
||||
}
|
||||
} else {
|
||||
// Shift register is disabled.
|
||||
bus_handler_.set_control_line_output(Port::B, Line::Two, control_outputs_[1].lines[1] != LineState::Off);
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T> void MOS6522<T>::set_control_line_output(Port port, Line line, LineState value) {
|
||||
if(port == Port::B && line == Line::Two) {
|
||||
control_outputs_[port].lines[line] = value;
|
||||
evaluate_cb2_output();
|
||||
} else {
|
||||
// Do nothing if unchanged.
|
||||
if(value == control_outputs_[port].lines[line]) {
|
||||
return;
|
||||
}
|
||||
|
||||
control_outputs_[port].lines[line] = value;
|
||||
|
||||
if(value != LineState::Input) {
|
||||
bus_handler_.run_for(time_since_bus_handler_call_.flush<HalfCycles>());
|
||||
bus_handler_.set_control_line_output(port, line, value != LineState::Off);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T> void MOS6522<T>::shift_in() {
|
||||
registers_.shift = uint8_t((registers_.shift << 1) | (control_inputs_[1].lines[1] ? 1 : 0));
|
||||
--shift_bits_remaining_;
|
||||
if(!shift_bits_remaining_) {
|
||||
registers_.interrupt_flags |= InterruptFlag::ShiftRegister;
|
||||
reevaluate_interrupts();
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T> void MOS6522<T>::shift_out() {
|
||||
// When shifting out, the shift register rotates rather than strictly shifts.
|
||||
// TODO: is that true for all modes?
|
||||
if(shift_mode() == ShiftMode::OutUnderT2FreeRunning || shift_bits_remaining_) {
|
||||
registers_.shift = uint8_t((registers_.shift << 1) | (registers_.shift >> 7));
|
||||
evaluate_cb2_output();
|
||||
|
||||
--shift_bits_remaining_;
|
||||
if(!shift_bits_remaining_) {
|
||||
registers_.interrupt_flags |= InterruptFlag::ShiftRegister;
|
||||
reevaluate_interrupts();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ class MOS6522Storage {
|
||||
|
||||
// The registers
|
||||
struct Registers {
|
||||
// "A low reset (RES) input clears all R6522 internal registers to logic 0"
|
||||
// "A low reset (RES) input clears all R6522 internal registers to logic 0"
|
||||
uint8_t output[2] = {0, 0};
|
||||
uint8_t input[2] = {0, 0};
|
||||
uint8_t data_direction[2] = {0, 0};
|
||||
@@ -37,14 +37,27 @@ class MOS6522Storage {
|
||||
bool timer_needs_reload = false;
|
||||
} registers_;
|
||||
|
||||
// control state
|
||||
// Control state.
|
||||
struct {
|
||||
bool line_one = false;
|
||||
bool line_two = false;
|
||||
bool lines[2] = {false, false};
|
||||
} control_inputs_[2];
|
||||
|
||||
enum class LineState {
|
||||
On, Off, Input
|
||||
};
|
||||
struct {
|
||||
LineState lines[2] = {LineState::Input, LineState::Input};
|
||||
} control_outputs_[2];
|
||||
|
||||
enum class HandshakeMode {
|
||||
None,
|
||||
Handshake,
|
||||
Pulse
|
||||
} handshake_modes_[2] = { HandshakeMode::None, HandshakeMode::None };
|
||||
|
||||
bool timer_is_running_[2] = {false, false};
|
||||
bool last_posted_interrupt_status_ = false;
|
||||
int shift_bits_remaining_ = 8;
|
||||
|
||||
enum InterruptFlag: uint8_t {
|
||||
CA2ActiveEdge = 1 << 0,
|
||||
@@ -55,6 +68,23 @@ class MOS6522Storage {
|
||||
Timer2 = 1 << 5,
|
||||
Timer1 = 1 << 6,
|
||||
};
|
||||
|
||||
enum class ShiftMode {
|
||||
Disabled = 0,
|
||||
InUnderT2 = 1,
|
||||
InUnderPhase2 = 2,
|
||||
InUnderCB1 = 3,
|
||||
OutUnderT2FreeRunning = 4,
|
||||
OutUnderT2 = 5,
|
||||
OutUnderPhase2 = 6,
|
||||
OutUnderCB1 = 7
|
||||
};
|
||||
ShiftMode shift_mode() const {
|
||||
return ShiftMode((registers_.auxiliary_control >> 2) & 7);
|
||||
}
|
||||
bool is_shifting_out() const {
|
||||
return registers_.auxiliary_control & 0x10;
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
268
Components/8530/z8530.cpp
Normal file
268
Components/8530/z8530.cpp
Normal file
@@ -0,0 +1,268 @@
|
||||
//
|
||||
// 8530.cpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 07/06/2019.
|
||||
// Copyright © 2019 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#include "z8530.hpp"
|
||||
|
||||
#include "../../Outputs/Log.hpp"
|
||||
|
||||
using namespace Zilog::SCC;
|
||||
|
||||
void z8530::reset() {
|
||||
// TODO.
|
||||
}
|
||||
|
||||
bool z8530::get_interrupt_line() {
|
||||
return
|
||||
(master_interrupt_control_ & 0x8) &&
|
||||
(
|
||||
channels_[0].get_interrupt_line() ||
|
||||
channels_[1].get_interrupt_line()
|
||||
);
|
||||
}
|
||||
|
||||
std::uint8_t z8530::read(int address) {
|
||||
if(address & 2) {
|
||||
// Read data register for channel
|
||||
return 0x00;
|
||||
} else {
|
||||
// Read control register for channel.
|
||||
uint8_t result = 0;
|
||||
|
||||
switch(pointer_) {
|
||||
default:
|
||||
result = channels_[address & 1].read(address & 2, pointer_);
|
||||
break;
|
||||
|
||||
case 2: // Handled non-symmetrically between channels.
|
||||
if(address & 1) {
|
||||
LOG("[SCC] Unimplemented: register 2 status bits");
|
||||
} else {
|
||||
result = interrupt_vector_;
|
||||
|
||||
// Modify the vector if permitted.
|
||||
// if(master_interrupt_control_ & 1) {
|
||||
for(int port = 0; port < 2; ++port) {
|
||||
// TODO: the logic below assumes that DCD is the only implemented interrupt. Fix.
|
||||
if(channels_[port].get_interrupt_line()) {
|
||||
const uint8_t shift = 1 + 3*((master_interrupt_control_ & 0x10) >> 4);
|
||||
const uint8_t mask = uint8_t(~(7 << shift));
|
||||
result = uint8_t(
|
||||
(result & mask) |
|
||||
((1 | ((port == 1) ? 4 : 0)) << shift)
|
||||
);
|
||||
break;
|
||||
}
|
||||
}
|
||||
// }
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
pointer_ = 0;
|
||||
update_delegate();
|
||||
return result;
|
||||
}
|
||||
|
||||
return 0x00;
|
||||
}
|
||||
|
||||
void z8530::write(int address, std::uint8_t value) {
|
||||
if(address & 2) {
|
||||
// Write data register for channel.
|
||||
} else {
|
||||
// Write control register for channel.
|
||||
|
||||
// Most registers are per channel, but a couple are shared; sever
|
||||
// them here.
|
||||
switch(pointer_) {
|
||||
default:
|
||||
channels_[address & 1].write(address & 2, pointer_, value);
|
||||
break;
|
||||
|
||||
case 2: // Interrupt vector register; shared between both channels.
|
||||
interrupt_vector_ = value;
|
||||
LOG("[SCC] Interrupt vector set to " << PADHEX(2) << int(value));
|
||||
break;
|
||||
|
||||
case 9: // Master interrupt and reset register; also shared between both channels.
|
||||
LOG("[SCC] Master interrupt and reset register: " << PADHEX(2) << int(value));
|
||||
master_interrupt_control_ = value;
|
||||
break;
|
||||
}
|
||||
|
||||
// The pointer number resets to 0 after every access, but if it is zero
|
||||
// then crib at least the next set of pointer bits (which, similarly, are shared
|
||||
// between the two channels).
|
||||
if(pointer_) {
|
||||
pointer_ = 0;
|
||||
} else {
|
||||
// The lowest three bits are the lowest three bits of the pointer.
|
||||
pointer_ = value & 7;
|
||||
|
||||
// If the command part of the byte is a 'point high', also set the
|
||||
// top bit of the pointer.
|
||||
if(((value >> 3)&7) == 1) {
|
||||
pointer_ |= 8;
|
||||
}
|
||||
}
|
||||
}
|
||||
update_delegate();
|
||||
}
|
||||
|
||||
void z8530::set_dcd(int port, bool level) {
|
||||
channels_[port].set_dcd(level);
|
||||
update_delegate();
|
||||
}
|
||||
|
||||
// MARK: - Channel implementations
|
||||
|
||||
uint8_t z8530::Channel::read(bool data, uint8_t pointer) {
|
||||
// If this is a data read, just return it.
|
||||
if(data) {
|
||||
return data_;
|
||||
} else {
|
||||
// Otherwise, this is a control read...
|
||||
switch(pointer) {
|
||||
default:
|
||||
LOG("[SCC] Unrecognised control read from register " << int(pointer));
|
||||
return 0x00;
|
||||
|
||||
case 0:
|
||||
return dcd_ ? 0x8 : 0x0;
|
||||
|
||||
case 0xf:
|
||||
return external_interrupt_status_;
|
||||
}
|
||||
}
|
||||
|
||||
return 0x00;
|
||||
}
|
||||
|
||||
void z8530::Channel::write(bool data, uint8_t pointer, uint8_t value) {
|
||||
if(data) {
|
||||
data_ = value;
|
||||
return;
|
||||
} else {
|
||||
switch(pointer) {
|
||||
default:
|
||||
LOG("[SCC] Unrecognised control write: " << PADHEX(2) << int(value) << " to register " << int(pointer));
|
||||
break;
|
||||
|
||||
case 0x0: // Write register 0 — CRC reset and other functions.
|
||||
// Decode CRC reset instructions.
|
||||
switch(value >> 6) {
|
||||
default: /* Do nothing. */ break;
|
||||
case 1:
|
||||
LOG("[SCC] TODO: reset Rx CRC checker.");
|
||||
break;
|
||||
case 2:
|
||||
LOG("[SCC] TODO: reset Tx CRC checker.");
|
||||
break;
|
||||
case 3:
|
||||
LOG("[SCC] TODO: reset Tx underrun/EOM latch.");
|
||||
break;
|
||||
}
|
||||
|
||||
// Decode command code.
|
||||
switch((value >> 3)&7) {
|
||||
default: /* Do nothing. */ break;
|
||||
case 2:
|
||||
// LOG("[SCC] reset ext/status interrupts.");
|
||||
external_status_interrupt_ = false;
|
||||
external_interrupt_status_ = 0;
|
||||
break;
|
||||
case 3:
|
||||
LOG("[SCC] TODO: send abort (SDLC).");
|
||||
break;
|
||||
case 4:
|
||||
LOG("[SCC] TODO: enable interrupt on next Rx character.");
|
||||
break;
|
||||
case 5:
|
||||
LOG("[SCC] TODO: reset Tx interrupt pending.");
|
||||
break;
|
||||
case 6:
|
||||
LOG("[SCC] TODO: reset error.");
|
||||
break;
|
||||
case 7:
|
||||
LOG("[SCC] TODO: reset highest IUS.");
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
case 0x1: // Write register 1 — Transmit/Receive Interrupt and Data Transfer Mode Definition.
|
||||
interrupt_mask_ = value;
|
||||
break;
|
||||
|
||||
case 0x4: // Write register 4 — Transmit/Receive Miscellaneous Parameters and Modes.
|
||||
// Bits 0 and 1 select parity mode.
|
||||
if(!(value&1)) {
|
||||
parity_ = Parity::Off;
|
||||
} else {
|
||||
parity_ = (value&2) ? Parity::Even : Parity::Odd;
|
||||
}
|
||||
|
||||
// Bits 2 and 3 select stop bits.
|
||||
switch((value >> 2)&3) {
|
||||
default: stop_bits_ = StopBits::Synchronous; break;
|
||||
case 1: stop_bits_ = StopBits::OneBit; break;
|
||||
case 2: stop_bits_ = StopBits::OneAndAHalfBits; break;
|
||||
case 3: stop_bits_ = StopBits::TwoBits; break;
|
||||
}
|
||||
|
||||
// Bits 4 and 5 pick a sync mode.
|
||||
switch((value >> 4)&3) {
|
||||
default: sync_mode_ = Sync::Monosync; break;
|
||||
case 1: sync_mode_ = Sync::Bisync; break;
|
||||
case 2: sync_mode_ = Sync::SDLC; break;
|
||||
case 3: sync_mode_ = Sync::External; break;
|
||||
}
|
||||
|
||||
// Bits 6 and 7 select a clock rate multiplier, unless synchronous
|
||||
// mode is enabled (and this is ignored if sync mode is external).
|
||||
if(stop_bits_ == StopBits::Synchronous) {
|
||||
clock_rate_multiplier_ = 1;
|
||||
} else {
|
||||
switch((value >> 6)&3) {
|
||||
default: clock_rate_multiplier_ = 1; break;
|
||||
case 1: clock_rate_multiplier_ = 16; break;
|
||||
case 2: clock_rate_multiplier_ = 32; break;
|
||||
case 3: clock_rate_multiplier_ = 64; break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case 0xf: // Write register 15 — External/Status Interrupt Control.
|
||||
external_interrupt_mask_ = value;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void z8530::Channel::set_dcd(bool level) {
|
||||
if(dcd_ == level) return;
|
||||
dcd_ = level;
|
||||
|
||||
if(external_interrupt_mask_ & 0x8) {
|
||||
external_status_interrupt_ = true;
|
||||
external_interrupt_status_ |= 0x8;
|
||||
}
|
||||
}
|
||||
|
||||
bool z8530::Channel::get_interrupt_line() {
|
||||
return
|
||||
(interrupt_mask_ & 1) && external_status_interrupt_;
|
||||
// TODO: other potential causes of an interrupt.
|
||||
}
|
||||
|
||||
void z8530::update_delegate() {
|
||||
const bool interrupt_line = get_interrupt_line();
|
||||
if(interrupt_line != previous_interrupt_line_) {
|
||||
previous_interrupt_line_ = interrupt_line;
|
||||
if(delegate_) delegate_->did_change_interrupt_status(this, interrupt_line);
|
||||
}
|
||||
}
|
||||
99
Components/8530/z8530.hpp
Normal file
99
Components/8530/z8530.hpp
Normal file
@@ -0,0 +1,99 @@
|
||||
//
|
||||
// z8530.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 07/06/2019.
|
||||
// Copyright © 2019 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef z8530_hpp
|
||||
#define z8530_hpp
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace Zilog {
|
||||
namespace SCC {
|
||||
|
||||
/*!
|
||||
Models the Zilog 8530 SCC, a serial adaptor.
|
||||
*/
|
||||
class z8530 {
|
||||
public:
|
||||
/*
|
||||
**Interface for emulated machine.**
|
||||
|
||||
Notes on addressing below:
|
||||
|
||||
There's no inherent ordering of the two 'address' lines,
|
||||
A/B and C/D, but the methods below assume:
|
||||
|
||||
A/B = A0
|
||||
C/D = A1
|
||||
*/
|
||||
std::uint8_t read(int address);
|
||||
void write(int address, std::uint8_t value);
|
||||
void reset();
|
||||
bool get_interrupt_line();
|
||||
|
||||
struct Delegate {
|
||||
virtual void did_change_interrupt_status(z8530 *, bool new_status) = 0;
|
||||
};
|
||||
void set_delegate(Delegate *delegate) {
|
||||
delegate_ = delegate;
|
||||
}
|
||||
|
||||
/*
|
||||
**Interface for serial port input.**
|
||||
*/
|
||||
void set_dcd(int port, bool level);
|
||||
|
||||
private:
|
||||
class Channel {
|
||||
public:
|
||||
uint8_t read(bool data, uint8_t pointer);
|
||||
void write(bool data, uint8_t pointer, uint8_t value);
|
||||
void set_dcd(bool level);
|
||||
bool get_interrupt_line();
|
||||
|
||||
private:
|
||||
uint8_t data_ = 0xff;
|
||||
|
||||
enum class Parity {
|
||||
Even, Odd, Off
|
||||
} parity_ = Parity::Off;
|
||||
|
||||
enum class StopBits {
|
||||
Synchronous, OneBit, OneAndAHalfBits, TwoBits
|
||||
} stop_bits_ = StopBits::Synchronous;
|
||||
|
||||
enum class Sync {
|
||||
Monosync, Bisync, SDLC, External
|
||||
} sync_mode_ = Sync::Monosync;
|
||||
|
||||
int clock_rate_multiplier_ = 1;
|
||||
|
||||
uint8_t interrupt_mask_ = 0; // i.e. Write Register 0x1.
|
||||
|
||||
uint8_t external_interrupt_mask_ = 0; // i.e. Write Register 0xf.
|
||||
bool external_status_interrupt_ = false;
|
||||
uint8_t external_interrupt_status_ = 0;
|
||||
|
||||
bool dcd_ = false;
|
||||
} channels_[2];
|
||||
|
||||
uint8_t pointer_ = 0;
|
||||
|
||||
uint8_t interrupt_vector_ = 0;
|
||||
|
||||
uint8_t master_interrupt_control_ = 0;
|
||||
|
||||
bool previous_interrupt_line_ = false;
|
||||
void update_delegate();
|
||||
Delegate *delegate_ = nullptr;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#endif /* z8530_hpp */
|
||||
@@ -22,7 +22,7 @@ namespace {
|
||||
DiskII::DiskII(int clock_rate) :
|
||||
clock_rate_(clock_rate),
|
||||
inputs_(input_command),
|
||||
drives_{{static_cast<unsigned int>(clock_rate), 300, 1}, {static_cast<unsigned int>(clock_rate), 300, 1}}
|
||||
drives_{{clock_rate, 300, 1}, {clock_rate, 300, 1}}
|
||||
{
|
||||
drives_[0].set_clocking_hint_observer(this);
|
||||
drives_[1].set_clocking_hint_observer(this);
|
||||
@@ -211,7 +211,7 @@ void DiskII::set_disk(const std::shared_ptr<Storage::Disk::Disk> &disk, int driv
|
||||
drives_[drive].set_disk(disk);
|
||||
}
|
||||
|
||||
void DiskII::process_event(const Storage::Disk::Track::Event &event) {
|
||||
void DiskII::process_event(const Storage::Disk::Drive::Event &event) {
|
||||
if(event.type == Storage::Disk::Track::Event::FluxTransition) {
|
||||
inputs_ &= ~input_flux;
|
||||
flux_duration_ = 2; // Upon detection of a flux transition, the flux flag should stay set for 1us. Emulate that as two cycles.
|
||||
|
||||
@@ -98,7 +98,7 @@ class DiskII:
|
||||
void select_drive(int drive);
|
||||
|
||||
uint8_t trigger_address(int address, uint8_t value);
|
||||
void process_event(const Storage::Disk::Track::Event &event) override;
|
||||
void process_event(const Storage::Disk::Drive::Event &event) override;
|
||||
void set_component_prefers_clocking(ClockingHint::Source *component, ClockingHint::Preference preference) override;
|
||||
|
||||
const int clock_rate_ = 0;
|
||||
|
||||
376
Components/DiskII/IWM.cpp
Normal file
376
Components/DiskII/IWM.cpp
Normal file
@@ -0,0 +1,376 @@
|
||||
//
|
||||
// IWM.cpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 05/05/2019.
|
||||
// Copyright © 2019 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#include "IWM.hpp"
|
||||
|
||||
#include "../../Outputs/Log.hpp"
|
||||
|
||||
using namespace Apple;
|
||||
|
||||
namespace {
|
||||
const int CA0 = 1 << 0;
|
||||
const int CA1 = 1 << 1;
|
||||
const int CA2 = 1 << 2;
|
||||
const int LSTRB = 1 << 3;
|
||||
const int ENABLE = 1 << 4;
|
||||
const int DRIVESEL = 1 << 5; /* This means drive select, like on the original Disk II. */
|
||||
const int Q6 = 1 << 6;
|
||||
const int Q7 = 1 << 7;
|
||||
const int SEL = 1 << 8; /* This is an additional input, not available on a Disk II, with a confusingly-similar name to SELECT but a distinct purpose. */
|
||||
}
|
||||
|
||||
IWM::IWM(int clock_rate) :
|
||||
clock_rate_(clock_rate) {}
|
||||
|
||||
// MARK: - Bus accessors
|
||||
|
||||
uint8_t IWM::read(int address) {
|
||||
access(address);
|
||||
|
||||
// Per Inside Macintosh:
|
||||
//
|
||||
// "Before you can read from any of the disk registers you must set up the state of the IWM so that it
|
||||
// can pass the data through to the MC68000's address space where you'll be able to read it. To do that,
|
||||
// you must first turn off Q7 by reading or writing dBase+q7L. Then turn on Q6 by accessing dBase+q6H.
|
||||
// After that, the IWM will be able to pass data from the disk's RD/SENSE line through to you."
|
||||
//
|
||||
// My understanding:
|
||||
//
|
||||
// Q6 = 1, Q7 = 0 reads the status register. The meaning of the top 'SENSE' bit is then determined by
|
||||
// the CA0,1,2 and SEL switches as described in Inside Macintosh, summarised above as RD/SENSE.
|
||||
|
||||
if(address&1) {
|
||||
return 0xff;
|
||||
}
|
||||
|
||||
switch(state_ & (Q6 | Q7 | ENABLE)) {
|
||||
default:
|
||||
LOG("[IWM] Invalid read\n");
|
||||
return 0xff;
|
||||
|
||||
// "Read all 1s".
|
||||
// printf("Reading all 1s\n");
|
||||
// return 0xff;
|
||||
|
||||
case 0:
|
||||
case ENABLE: { /* Read data register. Zeroing afterwards is a guess. */
|
||||
const auto result = data_register_;
|
||||
|
||||
if(data_register_ & 0x80) {
|
||||
// printf("\n\nIWM:%02x\n\n", data_register_);
|
||||
// printf(".");
|
||||
data_register_ = 0;
|
||||
}
|
||||
// LOG("Reading data register: " << PADHEX(2) << int(result));
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
case Q6: case Q6|ENABLE: {
|
||||
/*
|
||||
[If A = 0], Read status register:
|
||||
|
||||
bits 0-4: same as mode register.
|
||||
bit 5: 1 = either /ENBL1 or /ENBL2 is currently low.
|
||||
bit 6: 1 = MZ (reserved for future compatibility; should always be read as 0).
|
||||
bit 7: 1 = SENSE input high; 0 = SENSE input low.
|
||||
|
||||
(/ENBL1 is low when the first drive's motor is on; /ENBL2 is low when the second drive's motor is on.
|
||||
If the 1-second timer is enabled, motors remain on for one second after being programmatically disabled.)
|
||||
*/
|
||||
|
||||
return uint8_t(
|
||||
(mode_&0x1f) |
|
||||
((state_ & ENABLE) ? 0x20 : 0x00) |
|
||||
(sense() & 0x80)
|
||||
);
|
||||
} break;
|
||||
|
||||
case Q7: case Q7|ENABLE:
|
||||
/*
|
||||
Read write-handshake register:
|
||||
|
||||
bits 0-5: reserved for future use (currently read as 1).
|
||||
bit 6: 1 = write state (0 = underrun has occurred; 1 = no underrun so far).
|
||||
bit 7: 1 = write data buffer ready for data (1 = ready; 0 = busy).
|
||||
*/
|
||||
// LOG("Reading write handshake: " << PADHEX(2) << (0x3f | write_handshake_));
|
||||
return 0x3f | write_handshake_;
|
||||
}
|
||||
|
||||
return 0xff;
|
||||
}
|
||||
|
||||
void IWM::write(int address, uint8_t input) {
|
||||
access(address);
|
||||
|
||||
switch(state_ & (Q6 | Q7 | ENABLE)) {
|
||||
default: break;
|
||||
|
||||
case Q7|Q6:
|
||||
/*
|
||||
Write mode register:
|
||||
|
||||
bit 0: 1 = latch mode (should be set in asynchronous mode).
|
||||
bit 1: 0 = synchronous handshake protocol; 1 = asynchronous.
|
||||
bit 2: 0 = 1-second on-board timer enable; 1 = timer disable.
|
||||
bit 3: 0 = slow mode; 1 = fast mode.
|
||||
bit 4: 0 = 7Mhz; 1 = 8Mhz (7 or 8 mHz clock descriptor).
|
||||
bit 5: 1 = test mode; 0 = normal operation.
|
||||
bit 6: 1 = MZ-reset.
|
||||
bit 7: reserved for future expansion.
|
||||
*/
|
||||
|
||||
mode_ = input;
|
||||
|
||||
switch(mode_ & 0x18) {
|
||||
case 0x00: bit_length_ = Cycles(24); break; // slow mode, 7Mhz
|
||||
case 0x08: bit_length_ = Cycles(12); break; // fast mode, 7Mhz
|
||||
case 0x10: bit_length_ = Cycles(32); break; // slow mode, 8Mhz
|
||||
case 0x18: bit_length_ = Cycles(16); break; // fast mode, 8Mhz
|
||||
}
|
||||
LOG("IWM mode is now " << PADHEX(2) << int(mode_));
|
||||
break;
|
||||
|
||||
case Q7|Q6|ENABLE: // Write data register.
|
||||
next_output_ = input;
|
||||
write_handshake_ &= ~0x80;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Switch access
|
||||
|
||||
void IWM::access(int address) {
|
||||
// Keep a record of switch state; bits in state_
|
||||
// should correlate with the anonymous namespace constants
|
||||
// defined at the top of this file — CA0, CA1, etc.
|
||||
address &= 0xf;
|
||||
const auto mask = 1 << (address >> 1);
|
||||
const auto old_state = state_;
|
||||
|
||||
if(address & 1) {
|
||||
state_ |= mask;
|
||||
} else {
|
||||
state_ &= ~mask;
|
||||
}
|
||||
|
||||
// React appropriately to ENABLE and DRIVESEL changes, and changes into/out of write mode.
|
||||
if(old_state != state_) {
|
||||
push_drive_state();
|
||||
|
||||
switch(mask) {
|
||||
default: break;
|
||||
|
||||
case ENABLE:
|
||||
if(address & 1) {
|
||||
if(drives_[active_drive_]) drives_[active_drive_]->set_enabled(true);
|
||||
} else {
|
||||
// If the 1-second delay is enabled, set up a timer for that.
|
||||
if(!(mode_ & 4)) {
|
||||
cycles_until_disable_ = Cycles(clock_rate_);
|
||||
} else {
|
||||
if(drives_[active_drive_]) drives_[active_drive_]->set_enabled(false);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case DRIVESEL: {
|
||||
const int new_drive = address & 1;
|
||||
if(new_drive != active_drive_) {
|
||||
if(drives_[active_drive_]) drives_[active_drive_]->set_enabled(false);
|
||||
active_drive_ = new_drive;
|
||||
if(drives_[active_drive_]) {
|
||||
drives_[active_drive_]->set_enabled(state_ & ENABLE || (cycles_until_disable_ > Cycles(0)));
|
||||
push_drive_state();
|
||||
}
|
||||
}
|
||||
} break;
|
||||
|
||||
case Q6:
|
||||
case Q7:
|
||||
select_shift_mode();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void IWM::set_select(bool enabled) {
|
||||
// Store SEL as an extra state bit.
|
||||
if(enabled) state_ |= SEL;
|
||||
else state_ &= ~SEL;
|
||||
push_drive_state();
|
||||
}
|
||||
|
||||
void IWM::push_drive_state() {
|
||||
if(drives_[active_drive_]) {
|
||||
const uint8_t drive_control_lines =
|
||||
((state_ & CA0) ? IWMDrive::CA0 : 0) |
|
||||
((state_ & CA1) ? IWMDrive::CA1 : 0) |
|
||||
((state_ & CA2) ? IWMDrive::CA2 : 0) |
|
||||
((state_ & SEL) ? IWMDrive::SEL : 0) |
|
||||
((state_ & LSTRB) ? IWMDrive::LSTRB : 0);
|
||||
drives_[active_drive_]->set_control_lines(drive_control_lines);
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Active logic
|
||||
|
||||
void IWM::run_for(const Cycles cycles) {
|
||||
// Check for a timeout of the motor-off timer.
|
||||
if(cycles_until_disable_ > Cycles(0)) {
|
||||
cycles_until_disable_ -= cycles;
|
||||
if(cycles_until_disable_ <= Cycles(0)) {
|
||||
cycles_until_disable_ = Cycles(0);
|
||||
if(drives_[active_drive_])
|
||||
drives_[active_drive_]->set_enabled(false);
|
||||
}
|
||||
}
|
||||
|
||||
// Activity otherwise depends on mode and motor state.
|
||||
int integer_cycles = cycles.as_int();
|
||||
switch(shift_mode_) {
|
||||
case ShiftMode::Reading:
|
||||
if(drive_is_rotating_[active_drive_]) {
|
||||
while(integer_cycles--) {
|
||||
drives_[active_drive_]->run_for(Cycles(1));
|
||||
++cycles_since_shift_;
|
||||
if(cycles_since_shift_ == bit_length_ + Cycles(2)) {
|
||||
propose_shift(0);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
while(cycles_since_shift_ + integer_cycles >= bit_length_ + Cycles(2)) {
|
||||
propose_shift(0);
|
||||
integer_cycles -= bit_length_.as_int() + 2 - cycles_since_shift_.as_int();
|
||||
}
|
||||
cycles_since_shift_ += Cycles(integer_cycles);
|
||||
}
|
||||
break;
|
||||
|
||||
case ShiftMode::Writing:
|
||||
if(drives_[active_drive_]->is_writing()) {
|
||||
while(cycles_since_shift_ + integer_cycles >= bit_length_) {
|
||||
const auto cycles_until_write = bit_length_ - cycles_since_shift_;
|
||||
drives_[active_drive_]->run_for(cycles_until_write);
|
||||
|
||||
// Output a flux transition if the top bit is set.
|
||||
drives_[active_drive_]->write_bit(shift_register_ & 0x80);
|
||||
shift_register_ <<= 1;
|
||||
|
||||
integer_cycles -= cycles_until_write.as_int();
|
||||
cycles_since_shift_ = Cycles(0);
|
||||
|
||||
--output_bits_remaining_;
|
||||
if(!output_bits_remaining_) {
|
||||
if(!(write_handshake_ & 0x80)) {
|
||||
write_handshake_ |= 0x80;
|
||||
shift_register_ = next_output_;
|
||||
output_bits_remaining_ = 8;
|
||||
// LOG("Next byte: " << PADHEX(2) << int(shift_register_));
|
||||
} else {
|
||||
write_handshake_ &= ~0x40;
|
||||
drives_[active_drive_]->end_writing();
|
||||
// printf("\n");
|
||||
LOG("Overrun; done.");
|
||||
select_shift_mode();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cycles_since_shift_ = integer_cycles;
|
||||
if(integer_cycles) {
|
||||
drives_[active_drive_]->run_for(cycles_since_shift_);
|
||||
}
|
||||
} else {
|
||||
drives_[active_drive_]->run_for(cycles);
|
||||
}
|
||||
break;
|
||||
|
||||
case ShiftMode::CheckingWriteProtect:
|
||||
if(integer_cycles < 8) {
|
||||
shift_register_ = (shift_register_ >> integer_cycles) | (sense() & (0xff << (8 - integer_cycles)));
|
||||
} else {
|
||||
shift_register_ = sense();
|
||||
}
|
||||
|
||||
/* Deliberate fallthrough. */
|
||||
default:
|
||||
if(drive_is_rotating_[active_drive_]) drives_[active_drive_]->run_for(cycles);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void IWM::select_shift_mode() {
|
||||
// Don't allow an ongoing write to be interrupted.
|
||||
if(shift_mode_ == ShiftMode::Writing && drives_[active_drive_] && drives_[active_drive_]->is_writing()) return;
|
||||
|
||||
const auto old_shift_mode = shift_mode_;
|
||||
|
||||
switch(state_ & (Q6|Q7)) {
|
||||
default: shift_mode_ = ShiftMode::CheckingWriteProtect; break;
|
||||
case 0: shift_mode_ = ShiftMode::Reading; break;
|
||||
case Q7:
|
||||
// "The IWM is put into the write state by a transition from the write protect sense state to the
|
||||
// write load state".
|
||||
if(shift_mode_ == ShiftMode::CheckingWriteProtect) shift_mode_ = ShiftMode::Writing;
|
||||
break;
|
||||
}
|
||||
|
||||
// If writing mode just began, set the drive into write mode and cue up the first output byte.
|
||||
if(drives_[active_drive_] && old_shift_mode != ShiftMode::Writing && shift_mode_ == ShiftMode::Writing) {
|
||||
drives_[active_drive_]->begin_writing(Storage::Time(1, clock_rate_ / bit_length_.as_int()), false);
|
||||
shift_register_ = next_output_;
|
||||
write_handshake_ |= 0x80 | 0x40;
|
||||
output_bits_remaining_ = 8;
|
||||
LOG("Seeding output with " << PADHEX(2) << shift_register_);
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t IWM::sense() {
|
||||
return drives_[active_drive_] ? (drives_[active_drive_]->read() ? 0xff : 0x00) : 0xff;
|
||||
}
|
||||
|
||||
void IWM::process_event(const Storage::Disk::Drive::Event &event) {
|
||||
if(shift_mode_ != ShiftMode::Reading) return;
|
||||
|
||||
switch(event.type) {
|
||||
case Storage::Disk::Track::Event::IndexHole: return;
|
||||
case Storage::Disk::Track::Event::FluxTransition:
|
||||
propose_shift(1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void IWM::propose_shift(uint8_t bit) {
|
||||
// TODO: synchronous mode.
|
||||
|
||||
// LOG("Shifting input");
|
||||
shift_register_ = uint8_t((shift_register_ << 1) | bit);
|
||||
if(shift_register_ & 0x80) {
|
||||
data_register_ = shift_register_;
|
||||
shift_register_ = 0;
|
||||
}
|
||||
cycles_since_shift_ = Cycles(0);
|
||||
}
|
||||
|
||||
void IWM::set_drive(int slot, IWMDrive *drive) {
|
||||
drives_[slot] = drive;
|
||||
drive->set_event_delegate(this);
|
||||
drive->set_clocking_hint_observer(this);
|
||||
}
|
||||
|
||||
void IWM::set_component_prefers_clocking(ClockingHint::Source *component, ClockingHint::Preference clocking) {
|
||||
const bool is_rotating = clocking != ClockingHint::Preference::None;
|
||||
|
||||
if(component == static_cast<ClockingHint::Source *>(drives_[0])) {
|
||||
drive_is_rotating_[0] = is_rotating;
|
||||
} else {
|
||||
drive_is_rotating_[1] = is_rotating;
|
||||
}
|
||||
}
|
||||
117
Components/DiskII/IWM.hpp
Normal file
117
Components/DiskII/IWM.hpp
Normal file
@@ -0,0 +1,117 @@
|
||||
//
|
||||
// IWM.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 05/05/2019.
|
||||
// Copyright © 2019 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef IWM_hpp
|
||||
#define IWM_hpp
|
||||
|
||||
#include "../../ClockReceiver/ClockReceiver.hpp"
|
||||
#include "../../ClockReceiver/ClockingHintSource.hpp"
|
||||
#include "../../Storage/Disk/Drive.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace Apple {
|
||||
|
||||
/*!
|
||||
Defines the drive interface used by the IWM, derived from the external pinout as
|
||||
per e.g. https://old.pinouts.ru/HD/MacExtDrive_pinout.shtml
|
||||
|
||||
These are subclassed of Storage::Disk::Drive, so accept any disk the emulator supports,
|
||||
and provide the usual read/write interface for on-disk data.
|
||||
*/
|
||||
struct IWMDrive: public Storage::Disk::Drive {
|
||||
IWMDrive(int input_clock_rate, int number_of_heads) : Storage::Disk::Drive(input_clock_rate, number_of_heads) {}
|
||||
|
||||
enum Line: int {
|
||||
CA0 = 1 << 0,
|
||||
CA1 = 1 << 1,
|
||||
CA2 = 1 << 2,
|
||||
LSTRB = 1 << 3,
|
||||
SEL = 1 << 4,
|
||||
};
|
||||
|
||||
virtual void set_enabled(bool) = 0;
|
||||
virtual void set_control_lines(int) = 0;
|
||||
virtual bool read() = 0;
|
||||
};
|
||||
|
||||
class IWM:
|
||||
public Storage::Disk::Drive::EventDelegate,
|
||||
public ClockingHint::Observer {
|
||||
public:
|
||||
IWM(int clock_rate);
|
||||
|
||||
/// Sets the current external value of the data bus.
|
||||
void write(int address, uint8_t value);
|
||||
|
||||
/*!
|
||||
Submits an access to address @c address.
|
||||
|
||||
@returns The 8-bit value loaded to the data bus by the IWM.
|
||||
*/
|
||||
uint8_t read(int address);
|
||||
|
||||
/*!
|
||||
Sets the current input of the IWM's SEL line.
|
||||
*/
|
||||
void set_select(bool enabled);
|
||||
|
||||
/// Advances the controller by @c cycles.
|
||||
void run_for(const Cycles cycles);
|
||||
|
||||
/// Connects a drive to the IWM.
|
||||
void set_drive(int slot, IWMDrive *drive);
|
||||
|
||||
private:
|
||||
// Storage::Disk::Drive::EventDelegate.
|
||||
void process_event(const Storage::Disk::Drive::Event &event) override;
|
||||
|
||||
const int clock_rate_;
|
||||
|
||||
uint8_t data_register_ = 0;
|
||||
uint8_t mode_ = 0;
|
||||
bool read_write_ready_ = true;
|
||||
bool write_overran_ = false;
|
||||
|
||||
int state_ = 0;
|
||||
|
||||
int active_drive_ = 0;
|
||||
IWMDrive *drives_[2] = {nullptr, nullptr};
|
||||
bool drive_is_rotating_[2] = {false, false};
|
||||
|
||||
void set_component_prefers_clocking(ClockingHint::Source *component, ClockingHint::Preference clocking) override;
|
||||
|
||||
Cycles cycles_until_disable_;
|
||||
uint8_t write_handshake_ = 0x80;
|
||||
|
||||
void access(int address);
|
||||
|
||||
uint8_t shift_register_ = 0;
|
||||
uint8_t next_output_ = 0;
|
||||
int output_bits_remaining_ = 0;
|
||||
|
||||
void propose_shift(uint8_t bit);
|
||||
Cycles cycles_since_shift_;
|
||||
Cycles bit_length_ = Cycles(16);
|
||||
|
||||
void push_drive_state();
|
||||
|
||||
enum class ShiftMode {
|
||||
Reading,
|
||||
Writing,
|
||||
CheckingWriteProtect
|
||||
} shift_mode_;
|
||||
|
||||
uint8_t sense();
|
||||
void select_shift_mode();
|
||||
};
|
||||
|
||||
|
||||
}
|
||||
|
||||
#endif /* IWM_hpp */
|
||||
179
Components/DiskII/MacintoshDoubleDensityDrive.cpp
Normal file
179
Components/DiskII/MacintoshDoubleDensityDrive.cpp
Normal file
@@ -0,0 +1,179 @@
|
||||
//
|
||||
// MacintoshDoubleDensityDrive.cpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 10/07/2019.
|
||||
// Copyright © 2019 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#include "MacintoshDoubleDensityDrive.hpp"
|
||||
|
||||
/*
|
||||
Sources used pervasively:
|
||||
|
||||
http://members.iinet.net.au/~kalandi/apple/AUG/1991/11%20NOV.DEC/DISK.STUFF.html
|
||||
Apple Guide to the Macintosh Family Hardware
|
||||
Inside Macintosh III
|
||||
*/
|
||||
|
||||
using namespace Apple::Macintosh;
|
||||
|
||||
DoubleDensityDrive::DoubleDensityDrive(int input_clock_rate, bool is_800k) :
|
||||
IWMDrive(input_clock_rate, is_800k ? 2 : 1), // Only 800kb drives are double sided.
|
||||
is_800k_(is_800k) {
|
||||
// Start with a valid rotation speed.
|
||||
if(is_800k) {
|
||||
Drive::set_rotation_speed(393.3807f);
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Speed Selection
|
||||
|
||||
void DoubleDensityDrive::did_step(Storage::Disk::HeadPosition to_position) {
|
||||
// printf("At track %d\n", to_position.as_int());
|
||||
// The 800kb drive automatically selects rotation speed as a function of
|
||||
// head position; the 400kb drive doesn't do so.
|
||||
if(is_800k_) {
|
||||
/*
|
||||
Numbers below cribbed from the Kryoflux forums; specifically:
|
||||
https://forum.kryoflux.com/viewtopic.php?t=1090
|
||||
|
||||
They can almost be worked out algorithmically, since the point is to
|
||||
produce an almost-constant value for speed*(number of sectors), and:
|
||||
|
||||
393.3807 * 12 = 4720.5684
|
||||
429.1723 * 11 = 4720.895421
|
||||
472.1435 * 10 = 4721.435
|
||||
524.5672 * 9 = 4721.1048
|
||||
590.1098 * 8 = 4720.8784
|
||||
|
||||
So 4721 / (number of sectors per track in zone) would give essentially
|
||||
the same results.
|
||||
*/
|
||||
const int zone = to_position.as_int() >> 4;
|
||||
switch(zone) {
|
||||
case 0: Drive::set_rotation_speed(393.3807f); break;
|
||||
case 1: Drive::set_rotation_speed(429.1723f); break;
|
||||
case 2: Drive::set_rotation_speed(472.1435f); break;
|
||||
case 3: Drive::set_rotation_speed(524.5672f); break;
|
||||
default: Drive::set_rotation_speed(590.1098f); break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DoubleDensityDrive::set_rotation_speed(float revolutions_per_minute) {
|
||||
if(!is_800k_) {
|
||||
// Don't allow drive speeds to drop below 10 RPM, as a temporary sop
|
||||
// to sanity.
|
||||
Drive::set_rotation_speed(std::max(10.0f, revolutions_per_minute));
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Control input/output.
|
||||
|
||||
void DoubleDensityDrive::set_enabled(bool) {
|
||||
}
|
||||
|
||||
void DoubleDensityDrive::set_control_lines(int lines) {
|
||||
const auto old_state = control_state_;
|
||||
control_state_ = lines;
|
||||
|
||||
// Catch low-to-high LSTRB transitions.
|
||||
if((old_state ^ control_state_) & control_state_ & Line::LSTRB) {
|
||||
switch(control_state_ & (Line::CA2 | Line::CA1 | Line::CA0 | Line::SEL)) {
|
||||
default:
|
||||
break;
|
||||
|
||||
case 0: // Set step direction — CA2 set => step outward.
|
||||
case Line::CA2:
|
||||
step_direction_ = (control_state_ & Line::CA2) ? -1 : 1;
|
||||
break;
|
||||
|
||||
case Line::CA1: // Set drive motor — CA2 set => motor off.
|
||||
case Line::CA1|Line::CA2:
|
||||
set_motor_on(!(control_state_ & Line::CA2));
|
||||
break;
|
||||
|
||||
case Line::CA0: // Initiate a step.
|
||||
step(Storage::Disk::HeadPosition(step_direction_));
|
||||
break;
|
||||
|
||||
case Line::SEL|Line::CA2: // Reset new disk flag.
|
||||
has_new_disk_ = false;
|
||||
break;
|
||||
|
||||
case Line::CA2 | Line::CA1 | Line::CA0: // Eject the disk.
|
||||
set_disk(nullptr);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool DoubleDensityDrive::read() {
|
||||
switch(control_state_ & (CA2 | CA1 | CA0 | SEL)) {
|
||||
default:
|
||||
return false;
|
||||
|
||||
case 0: // Head step direction.
|
||||
// (0 = inward)
|
||||
return step_direction_ <= 0;
|
||||
|
||||
case SEL: // Disk in place.
|
||||
// (0 = disk present)
|
||||
return !has_disk();
|
||||
|
||||
case CA0: // Disk head step completed.
|
||||
// (0 = still stepping)
|
||||
return true; // TODO: stepping delay. But at the main Drive level.
|
||||
|
||||
case CA0|SEL: // Disk locked.
|
||||
// (0 = write protected)
|
||||
return !get_is_read_only();
|
||||
|
||||
case CA1: // Disk motor running.
|
||||
// (0 = motor on)
|
||||
return !get_motor_on();
|
||||
|
||||
case CA1|SEL: // Head at track 0.
|
||||
// (0 = at track 0)
|
||||
// "This bit becomes valid beginning 12 msec after the step that places the head at track 0."
|
||||
return !get_is_track_zero();
|
||||
|
||||
case CA1|CA0: // Disk has been ejected.
|
||||
// (0 = user has ejected disk)
|
||||
return !has_new_disk_;
|
||||
|
||||
case CA1|CA0|SEL: // Tachometer.
|
||||
// (arbitrary)
|
||||
return get_tachometer();
|
||||
|
||||
case CA2: // Read data, lower head.
|
||||
set_head(0);
|
||||
return false;
|
||||
|
||||
case CA2|SEL: // Read data, upper head.
|
||||
set_head(1);
|
||||
return false;
|
||||
|
||||
case CA2|CA1: // Single- or double-sided drive.
|
||||
// (0 = single sided)
|
||||
return get_head_count() != 1;
|
||||
|
||||
case CA2|CA1|CA0: // "Present/HD" (per the Mac Plus ROM)
|
||||
// (0 = ??HD??)
|
||||
//
|
||||
// Alternative explanation: "Disk ready for reading?"
|
||||
// (0 = ready)
|
||||
return false;
|
||||
|
||||
case CA2|CA1|CA0|SEL: // Drive installed.
|
||||
// (0 = present, 1 = missing)
|
||||
//
|
||||
// TODO: why do I need to return this the wrong way around for the Mac Plus?
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
void DoubleDensityDrive::did_set_disk() {
|
||||
has_new_disk_ = true;
|
||||
}
|
||||
53
Components/DiskII/MacintoshDoubleDensityDrive.hpp
Normal file
53
Components/DiskII/MacintoshDoubleDensityDrive.hpp
Normal file
@@ -0,0 +1,53 @@
|
||||
//
|
||||
// MacintoshDoubleDensityDrive.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 10/07/2019.
|
||||
// Copyright © 2019 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef MacintoshDoubleDensityDrive_hpp
|
||||
#define MacintoshDoubleDensityDrive_hpp
|
||||
|
||||
#include "IWM.hpp"
|
||||
|
||||
namespace Apple {
|
||||
namespace Macintosh {
|
||||
|
||||
class DoubleDensityDrive: public IWMDrive {
|
||||
public:
|
||||
DoubleDensityDrive(int input_clock_rate, bool is_800k);
|
||||
|
||||
/*!
|
||||
@returns @c true if this is an 800kb drive; @c false otherwise.
|
||||
*/
|
||||
bool is_800k() {
|
||||
return is_800k_;
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets the current rotation speed of this drive only if it is a 400kb drive.
|
||||
800kb drives select their own rotation speed based on head position,
|
||||
and ignore this input.
|
||||
*/
|
||||
void set_rotation_speed(float revolutions_per_minute);
|
||||
|
||||
void set_enabled(bool) override;
|
||||
void set_control_lines(int) override;
|
||||
bool read() override;
|
||||
|
||||
private:
|
||||
// To receive the proper notifications from Storage::Disk::Drive.
|
||||
void did_step(Storage::Disk::HeadPosition to_position) override;
|
||||
void did_set_disk() override;
|
||||
|
||||
const bool is_800k_;
|
||||
bool has_new_disk_ = false;
|
||||
int control_state_ = 0;
|
||||
int step_direction_ = 1;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* MacintoshDoubleDensityDrive_hpp */
|
||||
@@ -6,8 +6,8 @@
|
||||
// Copyright 2017 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef Keyboard_hpp
|
||||
#define Keyboard_hpp
|
||||
#ifndef Inputs_Keyboard_hpp
|
||||
#define Inputs_Keyboard_hpp
|
||||
|
||||
#include <vector>
|
||||
#include <set>
|
||||
@@ -75,4 +75,4 @@ class Keyboard {
|
||||
|
||||
}
|
||||
|
||||
#endif /* Keyboard_hpp */
|
||||
#endif /* Inputs_Keyboard_hpp */
|
||||
|
||||
47
Inputs/Mouse.hpp
Normal file
47
Inputs/Mouse.hpp
Normal file
@@ -0,0 +1,47 @@
|
||||
//
|
||||
// Mouse.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 11/06/2019.
|
||||
// Copyright © 2019 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef Mouse_h
|
||||
#define Mouse_h
|
||||
|
||||
namespace Inputs {
|
||||
|
||||
/*!
|
||||
Models a classic-era mouse: something that provides 2d relative motion plus
|
||||
some quantity of buttons.
|
||||
*/
|
||||
class Mouse {
|
||||
public:
|
||||
/*!
|
||||
Indicates a movement of the mouse.
|
||||
*/
|
||||
virtual void move(int x, int y) {}
|
||||
|
||||
/*!
|
||||
@returns the number of buttons on this mouse.
|
||||
*/
|
||||
virtual int get_number_of_buttons() {
|
||||
return 1;
|
||||
}
|
||||
|
||||
/*!
|
||||
Indicates that button @c index is now either pressed or unpressed.
|
||||
The intention is that @c index be semantic, not positional:
|
||||
0 for the primary button, 1 for the secondary, 2 for the tertiary, etc.
|
||||
*/
|
||||
virtual void set_button_pressed(int index, bool is_pressed) {}
|
||||
|
||||
/*!
|
||||
Releases all depressed buttons.
|
||||
*/
|
||||
virtual void reset_all_buttons() {}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif /* Mouse_h */
|
||||
123
Inputs/QuadratureMouse/QuadratureMouse.hpp
Normal file
123
Inputs/QuadratureMouse/QuadratureMouse.hpp
Normal file
@@ -0,0 +1,123 @@
|
||||
//
|
||||
// QuadratureMouse.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 11/06/2019.
|
||||
// Copyright © 2019 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef QuadratureMouse_hpp
|
||||
#define QuadratureMouse_hpp
|
||||
|
||||
#include "../Mouse.hpp"
|
||||
#include <atomic>
|
||||
|
||||
namespace Inputs {
|
||||
|
||||
/*!
|
||||
Provides a simple implementation of a Mouse, designed for simple
|
||||
thread-safe feeding to a machine that accepts quadrature-encoded input.
|
||||
|
||||
TEMPORARY SIMPLIFICATION: it is assumed that the caller will be interested
|
||||
in observing a signal that dictates velocity, sampling the other to
|
||||
obtain direction only on transitions in the velocity signal.
|
||||
|
||||
Or, more concretely, of the two channels per axis, one is accurate only when
|
||||
the other transitions. Hence the discussion of 'primary' and 'secondary'
|
||||
channels below. This is intended to be fixed.
|
||||
*/
|
||||
class QuadratureMouse: public Mouse {
|
||||
public:
|
||||
QuadratureMouse(int number_of_buttons) :
|
||||
number_of_buttons_(number_of_buttons) {}
|
||||
|
||||
/*
|
||||
Inputs, to satisfy the Mouse interface.
|
||||
*/
|
||||
void move(int x, int y) override {
|
||||
// Accumulate all provided motion.
|
||||
axes_[0] += x;
|
||||
axes_[1] += y;
|
||||
}
|
||||
|
||||
int get_number_of_buttons() override {
|
||||
return number_of_buttons_;
|
||||
}
|
||||
|
||||
void set_button_pressed(int index, bool is_pressed) override {
|
||||
if(is_pressed)
|
||||
button_flags_ |= (1 << index);
|
||||
else
|
||||
button_flags_ &= ~(1 << index);
|
||||
}
|
||||
|
||||
void reset_all_buttons() override {
|
||||
button_flags_ = 0;
|
||||
}
|
||||
|
||||
/*
|
||||
Outputs.
|
||||
*/
|
||||
|
||||
/*!
|
||||
Applies a single step from the current accumulated mouse movement, which
|
||||
might involve the mouse moving right, or left, or not at all.
|
||||
*/
|
||||
void prepare_step() {
|
||||
for(int axis = 0; axis < 2; ++axis) {
|
||||
// Do nothing if there's no motion to communicate.
|
||||
const int axis_value = axes_[axis];
|
||||
if(!axis_value) continue;
|
||||
|
||||
// Toggle the primary channel and set the secondary for
|
||||
// negative motion. At present the y axis signals the
|
||||
// secondary channel the opposite way around from the
|
||||
// primary.
|
||||
primaries_[axis] ^= 1;
|
||||
secondaries_[axis] = primaries_[axis] ^ axis;
|
||||
if(axis_value > 0) {
|
||||
-- axes_[axis];
|
||||
secondaries_[axis] ^= 1; // Switch to positive motion.
|
||||
} else {
|
||||
++ axes_[axis];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
@returns the two quadrature channels — bit 0 is the 'primary' channel
|
||||
(i.e. the one that can be monitored to observe velocity) and
|
||||
bit 1 is the 'secondary' (i.e. that which can be queried to
|
||||
observe direction).
|
||||
*/
|
||||
int get_channel(int axis) {
|
||||
return primaries_[axis] | (secondaries_[axis] << 1);
|
||||
}
|
||||
|
||||
/*!
|
||||
@returns a bit mask of the currently pressed buttons.
|
||||
*/
|
||||
int get_button_mask() {
|
||||
return button_flags_;
|
||||
}
|
||||
|
||||
/*!
|
||||
@returns @c true if any mouse motion is waiting to be communicated;
|
||||
@c false otherwise.
|
||||
*/
|
||||
bool has_steps() {
|
||||
return axes_[0] || axes_[1];
|
||||
}
|
||||
|
||||
private:
|
||||
int number_of_buttons_ = 0;
|
||||
std::atomic<int> button_flags_;
|
||||
std::atomic<int> axes_[2];
|
||||
|
||||
int primaries_[2] = {0, 0};
|
||||
int secondaries_[2] = {0, 0};
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif /* QuadratureMouse_hpp */
|
||||
@@ -781,27 +781,37 @@ template <bool has_fdc> class ConcreteMachine:
|
||||
ay_.ay().set_port_handler(&key_state_);
|
||||
|
||||
// construct the list of necessary ROMs
|
||||
std::vector<std::string> required_roms = {"amsdos.rom"};
|
||||
const std::string machine_name = "AmstradCPC";
|
||||
std::vector<ROMMachine::ROM> required_roms = {
|
||||
ROMMachine::ROM(machine_name, "the Amstrad Disk Operating System", "amsdos.rom", 16*1024, 0x1fe22ecd)
|
||||
};
|
||||
std::string model_number;
|
||||
uint32_t crcs[2];
|
||||
switch(target.model) {
|
||||
default:
|
||||
model_number = "6128";
|
||||
has_128k_ = true;
|
||||
crcs[0] = 0x0219bb74;
|
||||
crcs[1] = 0xca6af63d;
|
||||
break;
|
||||
case Analyser::Static::AmstradCPC::Target::Model::CPC464:
|
||||
model_number = "464";
|
||||
has_128k_ = false;
|
||||
crcs[0] = 0x815752df;
|
||||
crcs[1] = 0x7d9a3bac;
|
||||
break;
|
||||
case Analyser::Static::AmstradCPC::Target::Model::CPC664:
|
||||
model_number = "664";
|
||||
has_128k_ = false;
|
||||
crcs[0] = 0x3f5a6dc4;
|
||||
crcs[1] = 0x32fee492;
|
||||
break;
|
||||
}
|
||||
required_roms.push_back("os" + model_number + ".rom");
|
||||
required_roms.push_back("basic" + model_number + ".rom");
|
||||
required_roms.emplace_back(machine_name, "the CPC " + model_number + " firmware", "os" + model_number + ".rom", 16*1024, crcs[0]);
|
||||
required_roms.emplace_back(machine_name, "the CPC " + model_number + " BASIC ROM", "basic" + model_number + ".rom", 16*1024, crcs[1]);
|
||||
|
||||
// fetch and verify the ROMs
|
||||
const auto roms = rom_fetcher("AmstradCPC", required_roms);
|
||||
const auto roms = rom_fetcher(required_roms);
|
||||
|
||||
for(std::size_t index = 0; index < roms.size(); ++index) {
|
||||
auto &data = roms[index];
|
||||
|
||||
@@ -8,33 +8,34 @@
|
||||
|
||||
#include "AppleII.hpp"
|
||||
|
||||
#include "../../Activity/Source.hpp"
|
||||
#include "../MediaTarget.hpp"
|
||||
#include "../CRTMachine.hpp"
|
||||
#include "../JoystickMachine.hpp"
|
||||
#include "../KeyboardMachine.hpp"
|
||||
#include "../Utility/MemoryFuzzer.hpp"
|
||||
#include "../Utility/StringSerialiser.hpp"
|
||||
#include "../../../Activity/Source.hpp"
|
||||
#include "../../MediaTarget.hpp"
|
||||
#include "../../CRTMachine.hpp"
|
||||
#include "../../JoystickMachine.hpp"
|
||||
#include "../../KeyboardMachine.hpp"
|
||||
#include "../../Utility/MemoryFuzzer.hpp"
|
||||
#include "../../Utility/StringSerialiser.hpp"
|
||||
|
||||
#include "../../Processors/6502/6502.hpp"
|
||||
#include "../../Components/AudioToggle/AudioToggle.hpp"
|
||||
#include "../../../Processors/6502/6502.hpp"
|
||||
#include "../../../Components/AudioToggle/AudioToggle.hpp"
|
||||
|
||||
#include "../../Outputs/Speaker/Implementation/LowpassSpeaker.hpp"
|
||||
#include "../../Outputs/Log.hpp"
|
||||
#include "../../../Outputs/Speaker/Implementation/LowpassSpeaker.hpp"
|
||||
#include "../../../Outputs/Log.hpp"
|
||||
|
||||
#include "Card.hpp"
|
||||
#include "DiskIICard.hpp"
|
||||
#include "Video.hpp"
|
||||
|
||||
#include "../../Analyser/Static/AppleII/Target.hpp"
|
||||
#include "../../ClockReceiver/ForceInline.hpp"
|
||||
#include "../../Configurable/StandardOptions.hpp"
|
||||
#include "../../../Analyser/Static/AppleII/Target.hpp"
|
||||
#include "../../../ClockReceiver/ForceInline.hpp"
|
||||
#include "../../../Configurable/StandardOptions.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <memory>
|
||||
|
||||
namespace AppleII {
|
||||
namespace Apple {
|
||||
namespace II {
|
||||
|
||||
std::vector<std::unique_ptr<Configurable::Option>> get_options() {
|
||||
return Configurable::standard_options(
|
||||
@@ -51,12 +52,12 @@ template <Analyser::Static::AppleII::Target::Model model> class ConcreteMachine:
|
||||
public CPU::MOS6502::BusHandler,
|
||||
public Inputs::Keyboard,
|
||||
public Configurable::Device,
|
||||
public AppleII::Machine,
|
||||
public Apple::II::Machine,
|
||||
public Activity::Source,
|
||||
public JoystickMachine::Machine,
|
||||
public AppleII::Card::Delegate {
|
||||
public Apple::II::Card::Delegate {
|
||||
private:
|
||||
struct VideoBusHandler : public AppleII::Video::BusHandler {
|
||||
struct VideoBusHandler : public Apple::II::Video::BusHandler {
|
||||
public:
|
||||
VideoBusHandler(uint8_t *ram, uint8_t *aux_ram) : ram_(ram), aux_ram_(aux_ram) {}
|
||||
|
||||
@@ -71,12 +72,12 @@ template <Analyser::Static::AppleII::Target::Model model> class ConcreteMachine:
|
||||
|
||||
CPU::MOS6502::Processor<(model == Analyser::Static::AppleII::Target::Model::EnhancedIIe) ? CPU::MOS6502::Personality::PSynertek65C02 : CPU::MOS6502::Personality::P6502, ConcreteMachine, false> m6502_;
|
||||
VideoBusHandler video_bus_handler_;
|
||||
AppleII::Video::Video<VideoBusHandler, is_iie()> video_;
|
||||
Apple::II::Video::Video<VideoBusHandler, is_iie()> video_;
|
||||
int cycles_into_current_line_ = 0;
|
||||
Cycles cycles_since_video_update_;
|
||||
|
||||
void update_video() {
|
||||
video_.run_for(cycles_since_video_update_.flush());
|
||||
video_.run_for(cycles_since_video_update_.flush<Cycles>());
|
||||
}
|
||||
static const int audio_divider = 8;
|
||||
void update_audio() {
|
||||
@@ -109,28 +110,28 @@ template <Analyser::Static::AppleII::Target::Model model> class ConcreteMachine:
|
||||
Cycles cycles_since_audio_update_;
|
||||
|
||||
// MARK: - Cards
|
||||
std::array<std::unique_ptr<AppleII::Card>, 7> cards_;
|
||||
std::array<std::unique_ptr<Apple::II::Card>, 7> cards_;
|
||||
Cycles cycles_since_card_update_;
|
||||
std::vector<AppleII::Card *> every_cycle_cards_;
|
||||
std::vector<AppleII::Card *> just_in_time_cards_;
|
||||
std::vector<Apple::II::Card *> every_cycle_cards_;
|
||||
std::vector<Apple::II::Card *> just_in_time_cards_;
|
||||
|
||||
int stretched_cycles_since_card_update_ = 0;
|
||||
|
||||
void install_card(std::size_t slot, AppleII::Card *card) {
|
||||
void install_card(std::size_t slot, Apple::II::Card *card) {
|
||||
assert(slot >= 1 && slot < 8);
|
||||
cards_[slot - 1].reset(card);
|
||||
card->set_delegate(this);
|
||||
pick_card_messaging_group(card);
|
||||
}
|
||||
|
||||
bool is_every_cycle_card(AppleII::Card *card) {
|
||||
bool is_every_cycle_card(Apple::II::Card *card) {
|
||||
return !card->get_select_constraints();
|
||||
}
|
||||
|
||||
void pick_card_messaging_group(AppleII::Card *card) {
|
||||
void pick_card_messaging_group(Apple::II::Card *card) {
|
||||
const bool is_every_cycle = is_every_cycle_card(card);
|
||||
std::vector<AppleII::Card *> &intended = is_every_cycle ? every_cycle_cards_ : just_in_time_cards_;
|
||||
std::vector<AppleII::Card *> &undesired = is_every_cycle ? just_in_time_cards_ : every_cycle_cards_;
|
||||
std::vector<Apple::II::Card *> &intended = is_every_cycle ? every_cycle_cards_ : just_in_time_cards_;
|
||||
std::vector<Apple::II::Card *> &undesired = is_every_cycle ? just_in_time_cards_ : every_cycle_cards_;
|
||||
|
||||
if(std::find(intended.begin(), intended.end(), card) != intended.end()) return;
|
||||
auto old_membership = std::find(undesired.begin(), undesired.end(), card);
|
||||
@@ -138,12 +139,12 @@ template <Analyser::Static::AppleII::Target::Model model> class ConcreteMachine:
|
||||
intended.push_back(card);
|
||||
}
|
||||
|
||||
void card_did_change_select_constraints(AppleII::Card *card) override {
|
||||
void card_did_change_select_constraints(Apple::II::Card *card) override {
|
||||
pick_card_messaging_group(card);
|
||||
}
|
||||
|
||||
AppleII::DiskIICard *diskii_card() {
|
||||
return dynamic_cast<AppleII::DiskIICard *>(cards_[5].get());
|
||||
Apple::II::DiskIICard *diskii_card() {
|
||||
return dynamic_cast<Apple::II::DiskIICard *>(cards_[5].get());
|
||||
}
|
||||
|
||||
// MARK: - Memory Map.
|
||||
@@ -346,30 +347,39 @@ template <Analyser::Static::AppleII::Target::Model model> class ConcreteMachine:
|
||||
|
||||
// Pick the required ROMs.
|
||||
using Target = Analyser::Static::AppleII::Target;
|
||||
std::vector<std::string> rom_names;
|
||||
const std::string machine_name = "AppleII";
|
||||
std::vector<ROMMachine::ROM> rom_descriptions;
|
||||
size_t rom_size = 12*1024;
|
||||
switch(target.model) {
|
||||
default:
|
||||
rom_names.push_back("apple2-character.rom");
|
||||
rom_names.push_back("apple2o.rom");
|
||||
rom_descriptions.emplace_back(machine_name, "the basic Apple II character ROM", "apple2-character.rom", 2*1024, 0x64f415c6);
|
||||
rom_descriptions.emplace_back(machine_name, "the original Apple II ROM", "apple2o.rom", 12*1024, 0xba210588);
|
||||
break;
|
||||
case Target::Model::IIplus:
|
||||
rom_names.push_back("apple2-character.rom");
|
||||
rom_names.push_back("apple2.rom");
|
||||
rom_descriptions.emplace_back(machine_name, "the basic Apple II character ROM", "apple2-character.rom", 2*1024, 0x64f415c6);
|
||||
rom_descriptions.emplace_back(machine_name, "the Apple II+ ROM", "apple2.rom", 12*1024, 0xf66f9c26);
|
||||
break;
|
||||
case Target::Model::IIe:
|
||||
rom_size += 3840;
|
||||
rom_names.push_back("apple2eu-character.rom");
|
||||
rom_names.push_back("apple2eu.rom");
|
||||
rom_descriptions.emplace_back(machine_name, "the Apple IIe character ROM", "apple2eu-character.rom", 4*1024, 0x816a86f1);
|
||||
rom_descriptions.emplace_back(machine_name, "the Apple IIe ROM", "apple2eu.rom", 32*1024, 0xe12be18d);
|
||||
break;
|
||||
case Target::Model::EnhancedIIe:
|
||||
rom_size += 3840;
|
||||
rom_names.push_back("apple2e-character.rom");
|
||||
rom_names.push_back("apple2e.rom");
|
||||
rom_descriptions.emplace_back(machine_name, "the Enhanced Apple IIe character ROM", "apple2e-character.rom", 4*1024, 0x2651014d);
|
||||
rom_descriptions.emplace_back(machine_name, "the Enhanced Apple IIe ROM", "apple2e.rom", 32*1024, 0x65989942);
|
||||
break;
|
||||
}
|
||||
const auto roms = rom_fetcher("AppleII", rom_names);
|
||||
const auto roms = rom_fetcher(rom_descriptions);
|
||||
|
||||
// Try to install a Disk II card now, before checking the ROM list,
|
||||
// to make sure that Disk II dependencies have been communicated.
|
||||
if(target.disk_controller != Target::DiskController::None) {
|
||||
// Apple recommended slot 6 for the (first) Disk II.
|
||||
install_card(6, new Apple::II::DiskIICard(rom_fetcher, target.disk_controller == Target::DiskController::SixteenSector));
|
||||
}
|
||||
|
||||
// Now, check and move the ROMs.
|
||||
if(!roms[0] || !roms[1]) {
|
||||
throw ROMMachine::Error::MissingROMs;
|
||||
}
|
||||
@@ -381,11 +391,6 @@ template <Analyser::Static::AppleII::Target::Model model> class ConcreteMachine:
|
||||
|
||||
video_.set_character_rom(*roms[0]);
|
||||
|
||||
if(target.disk_controller != Target::DiskController::None) {
|
||||
// Apple recommended slot 6 for the (first) Disk II.
|
||||
install_card(6, new AppleII::DiskIICard(rom_fetcher, target.disk_controller == Target::DiskController::SixteenSector));
|
||||
}
|
||||
|
||||
// Set up the default memory blocks. On a II or II+ these values will never change.
|
||||
// On a IIe they'll be affected by selection of auxiliary RAM.
|
||||
set_main_paging();
|
||||
@@ -700,7 +705,7 @@ template <Analyser::Static::AppleII::Target::Model model> class ConcreteMachine:
|
||||
// If this is a card access, figure out which card is at play before determining
|
||||
// the totality of who needs messaging.
|
||||
size_t card_number = 0;
|
||||
AppleII::Card::Select select = AppleII::Card::None;
|
||||
Apple::II::Card::Select select = Apple::II::Card::None;
|
||||
|
||||
if(address >= 0xc100) {
|
||||
/*
|
||||
@@ -708,20 +713,20 @@ template <Analyser::Static::AppleII::Target::Model model> class ConcreteMachine:
|
||||
0xCn00 to 0xCnff: card n.
|
||||
*/
|
||||
card_number = (address - 0xc100) >> 8;
|
||||
select = AppleII::Card::Device;
|
||||
select = Apple::II::Card::Device;
|
||||
} else {
|
||||
/*
|
||||
Decode the area conventionally used by cards for registers:
|
||||
C0n0 to C0nF: card n - 8.
|
||||
*/
|
||||
card_number = (address - 0xc090) >> 4;
|
||||
select = AppleII::Card::IO;
|
||||
select = Apple::II::Card::IO;
|
||||
}
|
||||
|
||||
// If the selected card is a just-in-time card, update the just-in-time cards,
|
||||
// and then message it specifically.
|
||||
const bool is_read = isReadOperation(operation);
|
||||
AppleII::Card *const target = cards_[static_cast<size_t>(card_number)].get();
|
||||
Apple::II::Card *const target = cards_[static_cast<size_t>(card_number)].get();
|
||||
if(target && !is_every_cycle_card(target)) {
|
||||
update_just_in_time_cards();
|
||||
target->perform_bus_operation(select, is_read, address, value);
|
||||
@@ -732,7 +737,7 @@ template <Analyser::Static::AppleII::Target::Model model> class ConcreteMachine:
|
||||
for(const auto &card: every_cycle_cards_) {
|
||||
card->run_for(Cycles(1), is_stretched_cycle);
|
||||
card->perform_bus_operation(
|
||||
(card == target) ? select : AppleII::Card::None,
|
||||
(card == target) ? select : Apple::II::Card::None,
|
||||
is_read, address, value);
|
||||
}
|
||||
has_updated_cards = true;
|
||||
@@ -744,7 +749,7 @@ template <Analyser::Static::AppleII::Target::Model model> class ConcreteMachine:
|
||||
const bool is_read = isReadOperation(operation);
|
||||
for(const auto &card: every_cycle_cards_) {
|
||||
card->run_for(Cycles(1), is_stretched_cycle);
|
||||
card->perform_bus_operation(AppleII::Card::None, is_read, address, value);
|
||||
card->perform_bus_operation(Apple::II::Card::None, is_read, address, value);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -818,7 +823,7 @@ template <Analyser::Static::AppleII::Target::Model model> class ConcreteMachine:
|
||||
|
||||
// MARK:: Configuration options.
|
||||
std::vector<std::unique_ptr<Configurable::Option>> get_options() override {
|
||||
return AppleII::get_options();
|
||||
return Apple::II::get_options();
|
||||
}
|
||||
|
||||
void set_selections(const Configurable::SelectionSet &selections_by_option) override {
|
||||
@@ -860,9 +865,10 @@ template <Analyser::Static::AppleII::Target::Model model> class ConcreteMachine:
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
using namespace AppleII;
|
||||
using namespace Apple::II;
|
||||
|
||||
Machine *Machine::AppleII(const Analyser::Static::Target *target, const ROMMachine::ROMFetcher &rom_fetcher) {
|
||||
using Target = Analyser::Static::AppleII::Target;
|
||||
@@ -9,14 +9,15 @@
|
||||
#ifndef AppleII_hpp
|
||||
#define AppleII_hpp
|
||||
|
||||
#include "../../Configurable/Configurable.hpp"
|
||||
#include "../../Analyser/Static/StaticAnalyser.hpp"
|
||||
#include "../ROMMachine.hpp"
|
||||
#include "../../../Configurable/Configurable.hpp"
|
||||
#include "../../../Analyser/Static/StaticAnalyser.hpp"
|
||||
#include "../../ROMMachine.hpp"
|
||||
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
namespace AppleII {
|
||||
namespace Apple {
|
||||
namespace II {
|
||||
|
||||
/// @returns The options available for an Apple II.
|
||||
std::vector<std::unique_ptr<Configurable::Option>> get_options();
|
||||
@@ -29,6 +30,7 @@ class Machine {
|
||||
static Machine *AppleII(const Analyser::Static::Target *target, const ROMMachine::ROMFetcher &rom_fetcher);
|
||||
};
|
||||
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* AppleII_hpp */
|
||||
@@ -9,11 +9,12 @@
|
||||
#ifndef Card_h
|
||||
#define Card_h
|
||||
|
||||
#include "../../Processors/6502/6502.hpp"
|
||||
#include "../../ClockReceiver/ClockReceiver.hpp"
|
||||
#include "../../Activity/Observer.hpp"
|
||||
#include "../../../Processors/6502/6502.hpp"
|
||||
#include "../../../ClockReceiver/ClockReceiver.hpp"
|
||||
#include "../../../Activity/Observer.hpp"
|
||||
|
||||
namespace AppleII {
|
||||
namespace Apple {
|
||||
namespace II {
|
||||
|
||||
/*!
|
||||
This provides a small subset of the interface offered to cards installed in
|
||||
@@ -109,6 +110,7 @@ class Card {
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* Card_h */
|
||||
@@ -8,15 +8,25 @@
|
||||
|
||||
#include "DiskIICard.hpp"
|
||||
|
||||
using namespace AppleII;
|
||||
using namespace Apple::II;
|
||||
|
||||
DiskIICard::DiskIICard(const ROMMachine::ROMFetcher &rom_fetcher, bool is_16_sector) : diskii_(2045454) {
|
||||
const auto roms = rom_fetcher(
|
||||
"DiskII",
|
||||
{
|
||||
is_16_sector ? "boot-16.rom" : "boot-13.rom",
|
||||
is_16_sector ? "state-machine-16.rom" : "state-machine-13.rom"
|
||||
std::vector<std::unique_ptr<std::vector<uint8_t>>> roms;
|
||||
if(is_16_sector) {
|
||||
roms = rom_fetcher({
|
||||
{"DiskII", "the Disk II 16-sector boot ROM", "boot-16.rom", 256, 0xce7144f6},
|
||||
{"DiskII", "the Disk II 16-sector state machine ROM", "state-machine-16.rom", 256, { 0x9796a238, 0xb72a2c70 } }
|
||||
});
|
||||
} else {
|
||||
roms = rom_fetcher({
|
||||
{"DiskII", "the Disk II 13-sector boot ROM", "boot-13.rom", 256, 0xd34eb2ff},
|
||||
{"DiskII", "the Disk II 13-sector state machine ROM", "state-machine-13.rom", 256, 0x62e22620 }
|
||||
});
|
||||
}
|
||||
if(!roms[0] || !roms[1]) {
|
||||
throw ROMMachine::Error::MissingROMs;
|
||||
}
|
||||
|
||||
boot_ = std::move(*roms[0]);
|
||||
diskii_.set_state_machine(*roms[1]);
|
||||
set_select_constraints(None);
|
||||
@@ -10,17 +10,18 @@
|
||||
#define DiskIICard_hpp
|
||||
|
||||
#include "Card.hpp"
|
||||
#include "../ROMMachine.hpp"
|
||||
#include "../../ROMMachine.hpp"
|
||||
|
||||
#include "../../Components/DiskII/DiskII.hpp"
|
||||
#include "../../Storage/Disk/Disk.hpp"
|
||||
#include "../../ClockReceiver/ClockingHintSource.hpp"
|
||||
#include "../../../Components/DiskII/DiskII.hpp"
|
||||
#include "../../../Storage/Disk/Disk.hpp"
|
||||
#include "../../../ClockReceiver/ClockingHintSource.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
namespace AppleII {
|
||||
namespace Apple {
|
||||
namespace II {
|
||||
|
||||
class DiskIICard: public Card, public ClockingHint::Observer {
|
||||
public:
|
||||
@@ -41,6 +42,7 @@ class DiskIICard: public Card, public ClockingHint::Observer {
|
||||
ClockingHint::Preference diskii_clocking_preference_ = ClockingHint::Preference::RealTime;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* DiskIICard_hpp */
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
#include "Video.hpp"
|
||||
|
||||
using namespace AppleII::Video;
|
||||
using namespace Apple::II::Video;
|
||||
|
||||
VideoBase::VideoBase(bool is_iie, std::function<void(Cycles)> &&target) :
|
||||
crt_(910, 1, Outputs::Display::Type::NTSC60, Outputs::Display::InputDataType::Luminance1),
|
||||
@@ -9,14 +9,15 @@
|
||||
#ifndef Video_hpp
|
||||
#define Video_hpp
|
||||
|
||||
#include "../../Outputs/CRT/CRT.hpp"
|
||||
#include "../../ClockReceiver/ClockReceiver.hpp"
|
||||
#include "../../ClockReceiver/ClockDeferrer.hpp"
|
||||
#include "../../../Outputs/CRT/CRT.hpp"
|
||||
#include "../../../ClockReceiver/ClockReceiver.hpp"
|
||||
#include "../../../ClockReceiver/DeferredQueue.hpp"
|
||||
|
||||
#include <array>
|
||||
#include <vector>
|
||||
|
||||
namespace AppleII {
|
||||
namespace Apple {
|
||||
namespace II {
|
||||
namespace Video {
|
||||
|
||||
class BusHandler {
|
||||
@@ -250,8 +251,8 @@ class VideoBase {
|
||||
*/
|
||||
void output_fat_low_resolution(uint8_t *target, const uint8_t *source, size_t length, int column, int row) const;
|
||||
|
||||
// Maintain a ClockDeferrer for delayed mode switches.
|
||||
ClockDeferrer<Cycles> deferrer_;
|
||||
// Maintain a DeferredQueue for delayed mode switches.
|
||||
DeferredQueue<Cycles> deferrer_;
|
||||
};
|
||||
|
||||
template <class BusHandler, bool is_iie> class Video: public VideoBase {
|
||||
@@ -600,6 +601,7 @@ template <class BusHandler, bool is_iie> class Video: public VideoBase {
|
||||
BusHandler &bus_handler_;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
97
Machines/Apple/Macintosh/Audio.cpp
Normal file
97
Machines/Apple/Macintosh/Audio.cpp
Normal file
@@ -0,0 +1,97 @@
|
||||
//
|
||||
// Audio.cpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 31/05/2019.
|
||||
// Copyright © 2019 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#include "Audio.hpp"
|
||||
|
||||
using namespace Apple::Macintosh;
|
||||
|
||||
namespace {
|
||||
|
||||
// The sample_length is coupled with the clock rate selected within the Macintosh proper;
|
||||
// as per the header-declaration a divide-by-two clock is expected to arrive here.
|
||||
const std::size_t sample_length = 352 / 2;
|
||||
|
||||
}
|
||||
|
||||
Audio::Audio(Concurrency::DeferringAsyncTaskQueue &task_queue) : task_queue_(task_queue) {}
|
||||
|
||||
// MARK: - Inputs
|
||||
|
||||
void Audio::post_sample(uint8_t sample) {
|
||||
// Store sample directly indexed by current write pointer; this ensures that collected samples
|
||||
// directly map to volume and enabled/disabled states.
|
||||
sample_queue_.buffer[sample_queue_.write_pointer] = sample;
|
||||
sample_queue_.write_pointer = (sample_queue_.write_pointer + 1) % sample_queue_.buffer.size();
|
||||
}
|
||||
|
||||
void Audio::set_volume(int volume) {
|
||||
// Do nothing if the volume hasn't changed.
|
||||
if(posted_volume_ == volume) return;
|
||||
posted_volume_ = volume;
|
||||
|
||||
// Post the volume change as a deferred event.
|
||||
task_queue_.defer([=] () {
|
||||
volume_ = volume;
|
||||
set_volume_multiplier();
|
||||
});
|
||||
}
|
||||
|
||||
void Audio::set_enabled(bool on) {
|
||||
// Do nothing if the mask hasn't changed.
|
||||
if(posted_enable_mask_ == int(on)) return;
|
||||
posted_enable_mask_ = int(on);
|
||||
|
||||
// Post the enabled mask change as a deferred event.
|
||||
task_queue_.defer([=] () {
|
||||
enabled_mask_ = int(on);
|
||||
set_volume_multiplier();
|
||||
});
|
||||
}
|
||||
|
||||
// MARK: - Output generation
|
||||
|
||||
bool Audio::is_zero_level() {
|
||||
return !volume_ || !enabled_mask_;
|
||||
}
|
||||
|
||||
void Audio::set_sample_volume_range(std::int16_t range) {
|
||||
// Some underflow here doesn't really matter.
|
||||
output_volume_ = range / (7 * 255);
|
||||
set_volume_multiplier();
|
||||
}
|
||||
|
||||
void Audio::set_volume_multiplier() {
|
||||
volume_multiplier_ = int16_t(output_volume_ * volume_ * enabled_mask_);
|
||||
}
|
||||
|
||||
void Audio::get_samples(std::size_t number_of_samples, int16_t *target) {
|
||||
// TODO: the implementation below acts as if the hardware uses pulse-amplitude modulation;
|
||||
// in fact it uses pulse-width modulation. But the scale for pulses isn't specified, so
|
||||
// that's something to return to.
|
||||
|
||||
while(number_of_samples) {
|
||||
// Determine how many output samples will be at the same level.
|
||||
const auto cycles_left_in_sample = std::min(number_of_samples, sample_length - subcycle_offset_);
|
||||
|
||||
// Determine the output level, and output that many samples.
|
||||
// (Hoping that the copiler substitutes an effective memset16-type operation here).
|
||||
const int16_t output_level = volume_multiplier_ * (int16_t(sample_queue_.buffer[sample_queue_.read_pointer]) - 128);
|
||||
for(size_t c = 0; c < cycles_left_in_sample; ++c) {
|
||||
target[c] = output_level;
|
||||
}
|
||||
target += cycles_left_in_sample;
|
||||
|
||||
// Advance the sample pointer.
|
||||
subcycle_offset_ += cycles_left_in_sample;
|
||||
sample_queue_.read_pointer = (sample_queue_.read_pointer + (subcycle_offset_ / sample_length)) % sample_queue_.buffer.size();
|
||||
subcycle_offset_ %= sample_length;
|
||||
|
||||
// Decreate the number of samples left to write.
|
||||
number_of_samples -= cycles_left_in_sample;
|
||||
}
|
||||
}
|
||||
88
Machines/Apple/Macintosh/Audio.hpp
Normal file
88
Machines/Apple/Macintosh/Audio.hpp
Normal file
@@ -0,0 +1,88 @@
|
||||
//
|
||||
// Audio.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 31/05/2019.
|
||||
// Copyright © 2019 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef Audio_hpp
|
||||
#define Audio_hpp
|
||||
|
||||
#include "../../../Concurrency/AsyncTaskQueue.hpp"
|
||||
#include "../../../ClockReceiver/ClockReceiver.hpp"
|
||||
#include "../../../Outputs/Speaker/Implementation/SampleSource.hpp"
|
||||
|
||||
#include <array>
|
||||
#include <atomic>
|
||||
|
||||
namespace Apple {
|
||||
namespace Macintosh {
|
||||
|
||||
/*!
|
||||
Implements the Macintosh's audio output hardware.
|
||||
|
||||
Designed to be clocked at half the rate of the real hardware — i.e.
|
||||
a shade less than 4Mhz.
|
||||
*/
|
||||
class Audio: public ::Outputs::Speaker::SampleSource {
|
||||
public:
|
||||
Audio(Concurrency::DeferringAsyncTaskQueue &task_queue);
|
||||
|
||||
/*!
|
||||
Macintosh audio is (partly) sourced by the same scanning
|
||||
hardware as the video; each line it collects an additional
|
||||
word of memory, half of which is used for audio output.
|
||||
|
||||
Use this method to add a newly-collected sample to the queue.
|
||||
*/
|
||||
void post_sample(uint8_t sample);
|
||||
|
||||
/*!
|
||||
Macintosh audio also separately receives an output volume
|
||||
level, in the range 0 to 7.
|
||||
|
||||
Use this method to set the current output volume.
|
||||
*/
|
||||
void set_volume(int volume);
|
||||
|
||||
/*!
|
||||
A further factor in audio output is the on-off toggle.
|
||||
*/
|
||||
void set_enabled(bool on);
|
||||
|
||||
// to satisfy ::Outputs::Speaker (included via ::Outputs::Filter.
|
||||
void get_samples(std::size_t number_of_samples, int16_t *target);
|
||||
bool is_zero_level();
|
||||
void set_sample_volume_range(std::int16_t range);
|
||||
|
||||
private:
|
||||
Concurrency::DeferringAsyncTaskQueue &task_queue_;
|
||||
|
||||
// A queue of fetched samples; read from by one thread,
|
||||
// written to by another.
|
||||
struct {
|
||||
std::array<uint8_t, 740> buffer;
|
||||
size_t read_pointer = 0, write_pointer = 0;
|
||||
} sample_queue_;
|
||||
|
||||
// Emulator-thread stateful variables, to avoid work posting
|
||||
// deferral updates if possible.
|
||||
int posted_volume_ = 0;
|
||||
int posted_enable_mask_ = 0;
|
||||
|
||||
// Stateful variables, modified from the audio generation
|
||||
// thread only.
|
||||
int volume_ = 0;
|
||||
int enabled_mask_ = 0;
|
||||
std::int16_t output_volume_ = 0;
|
||||
|
||||
std::int16_t volume_multiplier_ = 0;
|
||||
std::size_t subcycle_offset_ = 0;
|
||||
void set_volume_multiplier();
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* Audio_hpp */
|
||||
34
Machines/Apple/Macintosh/DeferredAudio.hpp
Normal file
34
Machines/Apple/Macintosh/DeferredAudio.hpp
Normal file
@@ -0,0 +1,34 @@
|
||||
//
|
||||
// DeferredAudio.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 01/06/2019.
|
||||
// Copyright © 2019 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef DeferredAudio_h
|
||||
#define DeferredAudio_h
|
||||
|
||||
#include "Audio.hpp"
|
||||
#include "../../../Outputs/Speaker/Implementation/LowpassSpeaker.hpp"
|
||||
|
||||
namespace Apple {
|
||||
namespace Macintosh {
|
||||
|
||||
struct DeferredAudio {
|
||||
Concurrency::DeferringAsyncTaskQueue queue;
|
||||
Audio audio;
|
||||
Outputs::Speaker::LowpassSpeaker<Audio> speaker;
|
||||
HalfCycles time_since_update;
|
||||
|
||||
DeferredAudio() : audio(queue), speaker(audio) {}
|
||||
|
||||
void flush() {
|
||||
speaker.run_for(queue, time_since_update.flush<Cycles>());
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* DeferredAudio_h */
|
||||
63
Machines/Apple/Macintosh/DriveSpeedAccumulator.cpp
Normal file
63
Machines/Apple/Macintosh/DriveSpeedAccumulator.cpp
Normal file
@@ -0,0 +1,63 @@
|
||||
//
|
||||
// DriveSpeedAccumulator.cpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 01/06/2019.
|
||||
// Copyright © 2019 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#include "DriveSpeedAccumulator.hpp"
|
||||
|
||||
using namespace Apple::Macintosh;
|
||||
|
||||
void DriveSpeedAccumulator::post_sample(uint8_t sample) {
|
||||
if(!number_of_drives_) return;
|
||||
|
||||
// An Euler-esque approximation is used here: just collect all
|
||||
// the samples until there is a certain small quantity of them,
|
||||
// then produce a new estimate of rotation speed and start the
|
||||
// buffer afresh.
|
||||
samples_[sample_pointer_] = sample;
|
||||
++sample_pointer_;
|
||||
|
||||
if(sample_pointer_ == samples_.size()) {
|
||||
sample_pointer_ = 0;
|
||||
|
||||
// The below fits for a function like `a + bc`; it encapsultes the following
|
||||
// beliefs:
|
||||
//
|
||||
// (i) motor speed is proportional to voltage supplied;
|
||||
// (ii) with pulse-width modulation it's therefore proportional to the duty cycle;
|
||||
// (iii) the Mac pulse-width modulates whatever it reads from the disk speed buffer;
|
||||
// (iv) ... subject to software pulse-width modulation of that pulse-width modulation.
|
||||
//
|
||||
// So, I believe current motor speed is proportional to a low-pass filtering of
|
||||
// the speed buffer. Which I've implemented very coarsely via 'large' bucketed-averages,
|
||||
// noting also that exact disk motor speed is always a little approximate.
|
||||
|
||||
// Sum all samples.
|
||||
// TODO: if the above is the correct test, do it on sample receipt rather than
|
||||
// bothering with an intermediate buffer.
|
||||
int sum = 0;
|
||||
for(auto s: samples_) {
|
||||
sum += s;
|
||||
}
|
||||
|
||||
// The formula below was derived from observing values the Mac wrote into its
|
||||
// disk-speed buffer. Given that it runs a calibration loop before doing so,
|
||||
// I cannot guarantee the accuracy of these numbers beyond being within the
|
||||
// range that the computer would accept.
|
||||
const float normalised_sum = float(sum) / float(samples_.size());
|
||||
const float rotation_speed = (normalised_sum * 27.08f) - 259.0f;
|
||||
|
||||
for(int c = 0; c < number_of_drives_; ++c) {
|
||||
drives_[c]->set_rotation_speed(rotation_speed);
|
||||
}
|
||||
// printf("RPM: %0.2f (%d sum)\n", rotation_speed, sum);
|
||||
}
|
||||
}
|
||||
|
||||
void DriveSpeedAccumulator::add_drive(Apple::Macintosh::DoubleDensityDrive *drive) {
|
||||
drives_[number_of_drives_] = drive;
|
||||
++number_of_drives_;
|
||||
}
|
||||
45
Machines/Apple/Macintosh/DriveSpeedAccumulator.hpp
Normal file
45
Machines/Apple/Macintosh/DriveSpeedAccumulator.hpp
Normal file
@@ -0,0 +1,45 @@
|
||||
//
|
||||
// DriveSpeedAccumulator.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 01/06/2019.
|
||||
// Copyright © 2019 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef DriveSpeedAccumulator_hpp
|
||||
#define DriveSpeedAccumulator_hpp
|
||||
|
||||
#include <array>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
|
||||
#include "../../../Components/DiskII/MacintoshDoubleDensityDrive.hpp"
|
||||
|
||||
namespace Apple {
|
||||
namespace Macintosh {
|
||||
|
||||
class DriveSpeedAccumulator {
|
||||
public:
|
||||
/*!
|
||||
Accepts fetched motor control values.
|
||||
*/
|
||||
void post_sample(uint8_t sample);
|
||||
|
||||
/*!
|
||||
Adds a connected drive. Up to two of these
|
||||
can be supplied. Only Macintosh DoubleDensityDrives
|
||||
are supported.
|
||||
*/
|
||||
void add_drive(Apple::Macintosh::DoubleDensityDrive *drive);
|
||||
|
||||
private:
|
||||
std::array<uint8_t, 20> samples_;
|
||||
std::size_t sample_pointer_ = 0;
|
||||
Apple::Macintosh::DoubleDensityDrive *drives_[2] = {nullptr, nullptr};
|
||||
int number_of_drives_ = 0;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* DriveSpeedAccumulator_hpp */
|
||||
294
Machines/Apple/Macintosh/Keyboard.hpp
Normal file
294
Machines/Apple/Macintosh/Keyboard.hpp
Normal file
@@ -0,0 +1,294 @@
|
||||
//
|
||||
// Keyboard.h
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 08/05/2019.
|
||||
// Copyright © 2019 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef Apple_Macintosh_Keyboard_hpp
|
||||
#define Apple_Macintosh_Keyboard_hpp
|
||||
|
||||
#include "../../KeyboardMachine.hpp"
|
||||
|
||||
#include <mutex>
|
||||
#include <vector>
|
||||
|
||||
namespace Apple {
|
||||
namespace Macintosh {
|
||||
|
||||
class Keyboard {
|
||||
public:
|
||||
void set_input(bool data) {
|
||||
switch(mode_) {
|
||||
case Mode::Waiting:
|
||||
/*
|
||||
"Only the computer can initiate communication over the keyboard lines. When the computer and keyboard
|
||||
are turned on, the computer is in charge of the keyboard interface and the keyboard is passive. The
|
||||
computer signals that it is ready to begin communication by pulling the Keyboard Data line low."
|
||||
*/
|
||||
if(!data) {
|
||||
mode_ = Mode::AcceptingCommand;
|
||||
phase_ = 0;
|
||||
command_ = 0;
|
||||
}
|
||||
break;
|
||||
|
||||
case Mode::AcceptingCommand:
|
||||
/* Note value, so that it can be latched upon a clock transition. */
|
||||
data_input_ = data;
|
||||
break;
|
||||
|
||||
case Mode::AwaitingEndOfCommand:
|
||||
/*
|
||||
The last bit of the command leaves the Keyboard Data line low; the computer then indicates that it is ready
|
||||
to receive the keyboard's response by setting the Keyboard Data line high.
|
||||
*/
|
||||
if(data) {
|
||||
mode_ = Mode::PerformingCommand;
|
||||
phase_ = 0;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
case Mode::SendingResponse:
|
||||
/* This line isn't currently an input; do nothing. */
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
bool get_clock() {
|
||||
return clock_output_;
|
||||
}
|
||||
|
||||
bool get_data() {
|
||||
return !!(response_ & 0x80);
|
||||
}
|
||||
|
||||
/*!
|
||||
The keyboard expects ~10 µs-frequency ticks, i.e. a clock rate of just around 100 kHz.
|
||||
*/
|
||||
void run_for(HalfCycles cycle) {
|
||||
switch(mode_) {
|
||||
default:
|
||||
case Mode::Waiting: return;
|
||||
|
||||
case Mode::AcceptingCommand: {
|
||||
/*
|
||||
"When the computer is sending data to the keyboard, the keyboard transmits eight cycles of 400 µS each (180 µS low,
|
||||
220 µS high) on the Keyboard Clock line. On the falling edge of each keyboard clock cycle, the Macintosh Plus places
|
||||
a data bit on the data line and holds it there for 400 µS. The keyboard reads the data bit 80 µS after the rising edge
|
||||
of the Keyboard Clock signal."
|
||||
*/
|
||||
const auto offset = phase_ % 40;
|
||||
clock_output_ = offset >= 18;
|
||||
|
||||
if(offset == 26) {
|
||||
command_ = (command_ << 1) | (data_input_ ? 1 : 0);
|
||||
}
|
||||
|
||||
++phase_;
|
||||
if(phase_ == 8*40) {
|
||||
mode_ = Mode::AwaitingEndOfCommand;
|
||||
phase_ = 0;
|
||||
clock_output_ = false;
|
||||
}
|
||||
} break;
|
||||
|
||||
case Mode::AwaitingEndOfCommand:
|
||||
// Time out if the end-of-command seems not to be forthcoming.
|
||||
// This is an elaboration on my part; a guess.
|
||||
++phase_;
|
||||
if(phase_ == 1000) {
|
||||
clock_output_ = false;
|
||||
mode_ = Mode::Waiting;
|
||||
phase_ = 0;
|
||||
}
|
||||
return;
|
||||
|
||||
case Mode::PerformingCommand: {
|
||||
response_ = perform_command(command_);
|
||||
|
||||
// Inquiry has a 0.25-second timeout; everything else is instant.
|
||||
++phase_;
|
||||
if(phase_ == 25000 || command_ != 0x10 || response_ != 0x7b) {
|
||||
mode_ = Mode::SendingResponse;
|
||||
phase_ = 0;
|
||||
}
|
||||
} break;
|
||||
|
||||
case Mode::SendingResponse: {
|
||||
/*
|
||||
"When sending data to the computer, the keyboard transmits eight cycles of 330 µS each (160 µS low, 170 µS high)
|
||||
on the normally high Keyboard Clock line. It places a data bit on the data line 40 µS before the falling edge of each
|
||||
clock cycle and maintains it for 330 µS. The VIA in the computer latches the data bit into its shift register on the
|
||||
rising edge of the Keyboard Clock signal."
|
||||
*/
|
||||
const auto offset = phase_ % 33;
|
||||
clock_output_ = offset >= 16;
|
||||
|
||||
if(offset == 29) {
|
||||
response_ <<= 1;
|
||||
}
|
||||
|
||||
++phase_;
|
||||
if(phase_ == 8*33) {
|
||||
clock_output_ = false;
|
||||
mode_ = Mode::Waiting;
|
||||
phase_ = 0;
|
||||
}
|
||||
} break;
|
||||
}
|
||||
}
|
||||
|
||||
void enqueue_key_state(uint16_t key, bool is_pressed) {
|
||||
// Front insert; messages will be pop_back'd.
|
||||
std::lock_guard<decltype(key_queue_mutex_)> lock(key_queue_mutex_);
|
||||
|
||||
// Keys on the keypad are preceded by a $79 keycode; in the internal naming scheme
|
||||
// they are indicated by having bit 8 set. So add the $79 prefix if required.
|
||||
if(key & 0x100) {
|
||||
key_queue_.insert(key_queue_.begin(), 0x79);
|
||||
}
|
||||
key_queue_.insert(key_queue_.begin(), (is_pressed ? 0x00 : 0x80) | uint8_t(key));
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
int perform_command(int command) {
|
||||
switch(command) {
|
||||
case 0x10: // Inquiry.
|
||||
case 0x14: { // Instant.
|
||||
std::lock_guard<decltype(key_queue_mutex_)> lock(key_queue_mutex_);
|
||||
if(!key_queue_.empty()) {
|
||||
const auto new_message = key_queue_.back();
|
||||
key_queue_.pop_back();
|
||||
return new_message;
|
||||
}
|
||||
} break;
|
||||
|
||||
case 0x16: // Model number.
|
||||
return
|
||||
0x01 | // b0: always 1
|
||||
(1 << 1) | // keyboard model number
|
||||
(1 << 4); // next device number
|
||||
// (b7 not set => no next device)
|
||||
|
||||
case 0x36: // Test
|
||||
return 0x7d; // 0x7d = ACK, 0x77 = not ACK.
|
||||
}
|
||||
return 0x7b; // No key transition.
|
||||
}
|
||||
|
||||
enum class Mode {
|
||||
Waiting,
|
||||
AcceptingCommand,
|
||||
AwaitingEndOfCommand,
|
||||
SendingResponse,
|
||||
PerformingCommand
|
||||
} mode_ = Mode::Waiting;
|
||||
int phase_ = 0;
|
||||
int command_ = 0;
|
||||
int response_ = 0;
|
||||
|
||||
bool data_input_ = false;
|
||||
bool clock_output_ = false;
|
||||
|
||||
// TODO: improve this very, very simple implementation.
|
||||
std::mutex key_queue_mutex_;
|
||||
std::vector<uint8_t> key_queue_;
|
||||
};
|
||||
|
||||
/*!
|
||||
Provides a mapping from idiomatic PC keys to Macintosh keys.
|
||||
*/
|
||||
class KeyboardMapper: public KeyboardMachine::MappedMachine::KeyboardMapper {
|
||||
uint16_t mapped_key_for_key(Inputs::Keyboard::Key key) override {
|
||||
using Key = Inputs::Keyboard::Key;
|
||||
switch(key) {
|
||||
default: return KeyboardMachine::MappedMachine::KeyNotMapped;
|
||||
|
||||
/*
|
||||
See p284 of the Apple Guide to the Macintosh Family Hardware
|
||||
for documentation of the mapping below.
|
||||
*/
|
||||
|
||||
case Key::BackTick: return 0x65;
|
||||
case Key::k1: return 0x25;
|
||||
case Key::k2: return 0x27;
|
||||
case Key::k3: return 0x29;
|
||||
case Key::k4: return 0x2b;
|
||||
case Key::k5: return 0x2f;
|
||||
case Key::k6: return 0x2d;
|
||||
case Key::k7: return 0x35;
|
||||
case Key::k8: return 0x39;
|
||||
case Key::k9: return 0x33;
|
||||
case Key::k0: return 0x3b;
|
||||
case Key::Hyphen: return 0x37;
|
||||
case Key::Equals: return 0x31;
|
||||
case Key::BackSpace: return 0x67;
|
||||
|
||||
case Key::Tab: return 0x61;
|
||||
case Key::Q: return 0x19;
|
||||
case Key::W: return 0x1b;
|
||||
case Key::E: return 0x1d;
|
||||
case Key::R: return 0x1f;
|
||||
case Key::T: return 0x23;
|
||||
case Key::Y: return 0x21;
|
||||
case Key::U: return 0x41;
|
||||
case Key::I: return 0x45;
|
||||
case Key::O: return 0x3f;
|
||||
case Key::P: return 0x47;
|
||||
case Key::OpenSquareBracket: return 0x43;
|
||||
case Key::CloseSquareBracket: return 0x3d;
|
||||
|
||||
case Key::CapsLock: return 0x73;
|
||||
case Key::A: return 0x01;
|
||||
case Key::S: return 0x03;
|
||||
case Key::D: return 0x05;
|
||||
case Key::F: return 0x07;
|
||||
case Key::G: return 0x0b;
|
||||
case Key::H: return 0x09;
|
||||
case Key::J: return 0x4d;
|
||||
case Key::K: return 0x51;
|
||||
case Key::L: return 0x4b;
|
||||
case Key::Semicolon: return 0x53;
|
||||
case Key::Quote: return 0x4f;
|
||||
case Key::Enter: return 0x49;
|
||||
|
||||
case Key::LeftShift: return 0x71;
|
||||
case Key::Z: return 0x0d;
|
||||
case Key::X: return 0x0f;
|
||||
case Key::C: return 0x11;
|
||||
case Key::V: return 0x13;
|
||||
case Key::B: return 0x17;
|
||||
case Key::N: return 0x5b;
|
||||
case Key::M: return 0x5d;
|
||||
case Key::Comma: return 0x57;
|
||||
case Key::FullStop: return 0x5f;
|
||||
case Key::ForwardSlash: return 0x59;
|
||||
case Key::RightShift: return 0x71;
|
||||
|
||||
case Key::Left: return 0x100 | 0x0d;
|
||||
case Key::Right: return 0x100 | 0x05;
|
||||
case Key::Up: return 0x100 | 0x1b;
|
||||
case Key::Down: return 0x100 | 0x11;
|
||||
|
||||
case Key::LeftOption:
|
||||
case Key::RightOption: return 0x75;
|
||||
case Key::LeftMeta:
|
||||
case Key::RightMeta: return 0x6f;
|
||||
|
||||
case Key::Space: return 0x63;
|
||||
case Key::BackSlash: return 0x55;
|
||||
|
||||
/* TODO: the numeric keypad. */
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* Apple_Macintosh_Keyboard_hpp */
|
||||
653
Machines/Apple/Macintosh/Macintosh.cpp
Normal file
653
Machines/Apple/Macintosh/Macintosh.cpp
Normal file
@@ -0,0 +1,653 @@
|
||||
//
|
||||
// Macintosh.cpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 03/05/2019.
|
||||
// Copyright © 2019 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#include "Macintosh.hpp"
|
||||
|
||||
#include <array>
|
||||
|
||||
#include "DeferredAudio.hpp"
|
||||
#include "DriveSpeedAccumulator.hpp"
|
||||
#include "Keyboard.hpp"
|
||||
#include "RealTimeClock.hpp"
|
||||
#include "Video.hpp"
|
||||
|
||||
#include "../../CRTMachine.hpp"
|
||||
#include "../../KeyboardMachine.hpp"
|
||||
#include "../../MediaTarget.hpp"
|
||||
#include "../../MouseMachine.hpp"
|
||||
|
||||
#include "../../../Inputs/QuadratureMouse/QuadratureMouse.hpp"
|
||||
#include "../../../Outputs/Log.hpp"
|
||||
|
||||
#include "../../../ClockReceiver/JustInTime.hpp"
|
||||
|
||||
//#define LOG_TRACE
|
||||
|
||||
#include "../../../Components/6522/6522.hpp"
|
||||
#include "../../../Components/8530/z8530.hpp"
|
||||
#include "../../../Components/DiskII/IWM.hpp"
|
||||
#include "../../../Components/DiskII/MacintoshDoubleDensityDrive.hpp"
|
||||
#include "../../../Processors/68000/68000.hpp"
|
||||
|
||||
#include "../../../Analyser/Static/Macintosh/Target.hpp"
|
||||
|
||||
#include "../../Utility/MemoryPacker.hpp"
|
||||
#include "../../Utility/MemoryFuzzer.hpp"
|
||||
|
||||
namespace {
|
||||
|
||||
const int CLOCK_RATE = 7833600;
|
||||
|
||||
}
|
||||
|
||||
namespace Apple {
|
||||
namespace Macintosh {
|
||||
|
||||
template <Analyser::Static::Macintosh::Target::Model model> class ConcreteMachine:
|
||||
public Machine,
|
||||
public CRTMachine::Machine,
|
||||
public MediaTarget::Machine,
|
||||
public MouseMachine::Machine,
|
||||
public CPU::MC68000::BusHandler,
|
||||
public KeyboardMachine::MappedMachine,
|
||||
public Zilog::SCC::z8530::Delegate {
|
||||
public:
|
||||
using Target = Analyser::Static::Macintosh::Target;
|
||||
|
||||
ConcreteMachine(const Target &target, const ROMMachine::ROMFetcher &rom_fetcher) :
|
||||
mc68000_(*this),
|
||||
iwm_(CLOCK_RATE),
|
||||
video_(ram_, audio_, drive_speed_accumulator_),
|
||||
via_(via_port_handler_),
|
||||
via_port_handler_(*this, clock_, keyboard_, video_, audio_, iwm_, mouse_),
|
||||
drives_{
|
||||
{CLOCK_RATE, model >= Analyser::Static::Macintosh::Target::Model::Mac512ke},
|
||||
{CLOCK_RATE, model >= Analyser::Static::Macintosh::Target::Model::Mac512ke}
|
||||
},
|
||||
mouse_(1) {
|
||||
|
||||
// Select a ROM name and determine the proper ROM and RAM sizes
|
||||
// based on the machine model.
|
||||
using Model = Analyser::Static::Macintosh::Target::Model;
|
||||
const std::string machine_name = "Macintosh";
|
||||
uint32_t ram_size, rom_size;
|
||||
std::vector<ROMMachine::ROM> rom_descriptions;
|
||||
switch(model) {
|
||||
default:
|
||||
case Model::Mac128k:
|
||||
ram_size = 128*1024;
|
||||
rom_size = 64*1024;
|
||||
rom_descriptions.emplace_back(machine_name, "the Macintosh 128k ROM", "mac128k.rom", 64*1024, 0x6d0c8a28);
|
||||
break;
|
||||
case Model::Mac512k:
|
||||
ram_size = 512*1024;
|
||||
rom_size = 64*1024;
|
||||
rom_descriptions.emplace_back(machine_name, "the Macintosh 512k ROM", "mac512k.rom", 64*1024, 0xcf759e0d);
|
||||
break;
|
||||
case Model::Mac512ke:
|
||||
case Model::MacPlus: {
|
||||
ram_size = 512*1024;
|
||||
rom_size = 128*1024;
|
||||
const std::initializer_list<uint32_t> crc32s = { 0x4fa5b399, 0x7cacd18f, 0xb2102e8e };
|
||||
rom_descriptions.emplace_back(machine_name, "the Macintosh Plus ROM", "macplus.rom", 128*1024, crc32s);
|
||||
} break;
|
||||
}
|
||||
ram_mask_ = (ram_size >> 1) - 1;
|
||||
rom_mask_ = (rom_size >> 1) - 1;
|
||||
video_.set_ram_mask(ram_mask_);
|
||||
|
||||
// Grab a copy of the ROM and convert it into big-endian data.
|
||||
const auto roms = rom_fetcher(rom_descriptions);
|
||||
if(!roms[0]) {
|
||||
throw ROMMachine::Error::MissingROMs;
|
||||
}
|
||||
roms[0]->resize(rom_size);
|
||||
Memory::PackBigEndian16(*roms[0], rom_);
|
||||
|
||||
// Randomise memory contents.
|
||||
Memory::Fuzz(ram_, sizeof(ram_) / sizeof(*ram_));
|
||||
|
||||
// Attach the drives to the IWM.
|
||||
iwm_.iwm.set_drive(0, &drives_[0]);
|
||||
iwm_.iwm.set_drive(1, &drives_[1]);
|
||||
|
||||
// If they are 400kb drives, also attach them to the drive-speed accumulator.
|
||||
if(!drives_[0].is_800k()) drive_speed_accumulator_.add_drive(&drives_[0]);
|
||||
if(!drives_[1].is_800k()) drive_speed_accumulator_.add_drive(&drives_[1]);
|
||||
|
||||
// Make sure interrupt changes from the SCC are observed.
|
||||
scc_.set_delegate(this);
|
||||
|
||||
// The Mac runs at 7.8336mHz.
|
||||
set_clock_rate(double(CLOCK_RATE));
|
||||
audio_.speaker.set_input_rate(float(CLOCK_RATE) / 2.0f);
|
||||
|
||||
// Insert any supplied media.
|
||||
insert_media(target.media);
|
||||
}
|
||||
|
||||
~ConcreteMachine() {
|
||||
audio_.queue.flush();
|
||||
}
|
||||
|
||||
void set_scan_target(Outputs::Display::ScanTarget *scan_target) override {
|
||||
video_.set_scan_target(scan_target);
|
||||
}
|
||||
|
||||
Outputs::Speaker::Speaker *get_speaker() override {
|
||||
return &audio_.speaker;
|
||||
}
|
||||
|
||||
void run_for(const Cycles cycles) override {
|
||||
mc68000_.run_for(cycles);
|
||||
}
|
||||
|
||||
using Microcycle = CPU::MC68000::Microcycle;
|
||||
|
||||
HalfCycles perform_bus_operation(const Microcycle &cycle, int is_supervisor) {
|
||||
// TODO: pick a delay if this is a video-clashing memory fetch.
|
||||
HalfCycles delay(0);
|
||||
|
||||
time_since_video_update_ += cycle.length;
|
||||
iwm_.time_since_update += cycle.length;
|
||||
|
||||
// The VIA runs at one-tenth of the 68000's clock speed, in sync with the E clock.
|
||||
// See: Guide to the Macintosh Hardware Family p149 (PDF p188). Some extra division
|
||||
// may occur here in order to provide VSYNC at a proper moment.
|
||||
// Possibly route vsync.
|
||||
if(time_since_video_update_ < time_until_video_event_) {
|
||||
via_clock_ += cycle.length;
|
||||
via_.run_for(via_clock_.divide(HalfCycles(10)));
|
||||
} else {
|
||||
auto via_time_base = time_since_video_update_ - cycle.length;
|
||||
auto via_cycles_outstanding = cycle.length;
|
||||
while(time_until_video_event_ < time_since_video_update_) {
|
||||
const auto via_cycles = time_until_video_event_ - via_time_base;
|
||||
via_time_base = HalfCycles(0);
|
||||
via_cycles_outstanding -= via_cycles;
|
||||
|
||||
via_clock_ += via_cycles;
|
||||
via_.run_for(via_clock_.divide(HalfCycles(10)));
|
||||
|
||||
video_.run_for(time_until_video_event_);
|
||||
time_since_video_update_ -= time_until_video_event_;
|
||||
time_until_video_event_ = video_.get_next_sequence_point();
|
||||
|
||||
via_.set_control_line_input(MOS::MOS6522::Port::A, MOS::MOS6522::Line::One, !video_.vsync());
|
||||
}
|
||||
|
||||
via_clock_ += via_cycles_outstanding;
|
||||
via_.run_for(via_clock_.divide(HalfCycles(10)));
|
||||
}
|
||||
|
||||
// The keyboard also has a clock, albeit a very slow one — 100,000 cycles/second.
|
||||
// Its clock and data lines are connected to the VIA.
|
||||
keyboard_clock_ += cycle.length;
|
||||
const auto keyboard_ticks = keyboard_clock_.divide(HalfCycles(CLOCK_RATE / 100000));
|
||||
if(keyboard_ticks > HalfCycles(0)) {
|
||||
keyboard_.run_for(keyboard_ticks);
|
||||
via_.set_control_line_input(MOS::MOS6522::Port::B, MOS::MOS6522::Line::Two, keyboard_.get_data());
|
||||
via_.set_control_line_input(MOS::MOS6522::Port::B, MOS::MOS6522::Line::One, keyboard_.get_clock());
|
||||
}
|
||||
|
||||
// Feed mouse inputs within at most 1250 cycles of each other.
|
||||
if(mouse_.has_steps()) {
|
||||
time_since_mouse_update_ += cycle.length;
|
||||
const auto mouse_ticks = time_since_mouse_update_.divide(HalfCycles(2500));
|
||||
if(mouse_ticks > HalfCycles(0)) {
|
||||
mouse_.prepare_step();
|
||||
scc_.set_dcd(0, mouse_.get_channel(1) & 1);
|
||||
scc_.set_dcd(1, mouse_.get_channel(0) & 1);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: SCC should be clocked at a divide-by-two, if and when it actually has
|
||||
// anything connected.
|
||||
|
||||
// Consider updating the real-time clock.
|
||||
real_time_clock_ += cycle.length;
|
||||
auto ticks = real_time_clock_.divide_cycles(Cycles(CLOCK_RATE)).as_int();
|
||||
while(ticks--) {
|
||||
clock_.update();
|
||||
// TODO: leave a delay between toggling the input rather than using this coupled hack.
|
||||
via_.set_control_line_input(MOS::MOS6522::Port::A, MOS::MOS6522::Line::Two, true);
|
||||
via_.set_control_line_input(MOS::MOS6522::Port::A, MOS::MOS6522::Line::Two, false);
|
||||
}
|
||||
|
||||
// A null cycle leaves nothing else to do.
|
||||
if(!(cycle.operation & (Microcycle::NewAddress | Microcycle::SameAddress))) return delay;
|
||||
|
||||
auto word_address = cycle.active_operation_word_address();
|
||||
|
||||
// Everything above E0 0000 is signalled as being on the peripheral bus.
|
||||
mc68000_.set_is_peripheral_address(word_address >= 0x700000);
|
||||
|
||||
// All code below deals only with reads and writes — cycles in which a
|
||||
// data select is active. So quit now if this is not the active part of
|
||||
// a read or write.
|
||||
if(!cycle.data_select_active()) return delay;
|
||||
|
||||
// Check whether this access maps into the IO area; if so then
|
||||
// apply more complicated decoding logic.
|
||||
if(word_address >= 0x400000) {
|
||||
const int register_address = word_address >> 8;
|
||||
|
||||
switch(word_address & 0x78f000) {
|
||||
case 0x70f000:
|
||||
// VIA accesses are via address 0xefe1fe + register*512,
|
||||
// which at word precision is 0x77f0ff + register*256.
|
||||
if(cycle.operation & Microcycle::Read) {
|
||||
cycle.value->halves.low = via_.get_register(register_address);
|
||||
} else {
|
||||
via_.set_register(register_address, cycle.value->halves.low);
|
||||
}
|
||||
break;
|
||||
|
||||
case 0x68f000:
|
||||
// The IWM; this is a purely polled device, so can be run on demand.
|
||||
iwm_.flush();
|
||||
if(cycle.operation & Microcycle::Read) {
|
||||
cycle.value->halves.low = iwm_.iwm.read(register_address);
|
||||
} else {
|
||||
iwm_.iwm.write(register_address, cycle.value->halves.low);
|
||||
}
|
||||
break;
|
||||
|
||||
case 0x780000:
|
||||
// Phase read.
|
||||
if(cycle.operation & Microcycle::Read) {
|
||||
cycle.value->halves.low = phase_ & 7;
|
||||
}
|
||||
break;
|
||||
|
||||
case 0x480000: case 0x48f000:
|
||||
case 0x580000: case 0x58f000:
|
||||
// Any word access here adjusts phase.
|
||||
if(cycle.operation & Microcycle::SelectWord) {
|
||||
++phase_;
|
||||
} else {
|
||||
if(word_address < 0x500000) {
|
||||
// A0 = 1 => reset; A0 = 0 => read.
|
||||
if(*cycle.address & 1) {
|
||||
scc_.reset();
|
||||
} else {
|
||||
const auto read = scc_.read(int(word_address));
|
||||
if(cycle.operation & Microcycle::Read) {
|
||||
cycle.value->halves.low = read;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if(*cycle.address & 1) {
|
||||
if(cycle.operation & Microcycle::Read) {
|
||||
scc_.write(int(word_address), 0xff);
|
||||
} else {
|
||||
scc_.write(int(word_address), cycle.value->halves.low);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
if(cycle.operation & Microcycle::Read) {
|
||||
LOG("Unrecognised read " << PADHEX(6) << (*cycle.address & 0xffffff));
|
||||
cycle.value->halves.low = 0x00;
|
||||
} else {
|
||||
LOG("Unrecognised write %06x" << PADHEX(6) << (*cycle.address & 0xffffff));
|
||||
}
|
||||
break;
|
||||
}
|
||||
if(cycle.operation & Microcycle::SelectWord) cycle.value->halves.high = 0xff;
|
||||
|
||||
return delay;
|
||||
}
|
||||
|
||||
// Having reached here, this is a RAM or ROM access.
|
||||
|
||||
// When ROM overlay is enabled, the ROM begins at both $000000 and $400000,
|
||||
// and RAM is available at $600000.
|
||||
//
|
||||
// Otherwise RAM is mapped at $000000 and ROM from $400000.
|
||||
uint16_t *memory_base;
|
||||
if(
|
||||
(!ROM_is_overlay_ && word_address < 0x200000) ||
|
||||
(ROM_is_overlay_ && word_address >= 0x300000)
|
||||
) {
|
||||
memory_base = ram_;
|
||||
word_address &= ram_mask_;
|
||||
|
||||
// This is coupled with the Macintosh implementation of video; the magic
|
||||
// constant should probably be factored into the Video class.
|
||||
// It embodies knowledge of the fact that video (and audio) will always
|
||||
// be fetched from the final $d900 bytes (i.e. $6c80 words) of memory.
|
||||
// (And that ram_mask_ = ram size - 1).
|
||||
// if(word_address > ram_mask_ - 0x6c80)
|
||||
update_video();
|
||||
} else {
|
||||
memory_base = rom_;
|
||||
word_address &= rom_mask_;
|
||||
|
||||
// Writes to ROM have no effect, and it doesn't mirror above 0x60000.
|
||||
if(!(cycle.operation & Microcycle::Read)) return delay;
|
||||
if(word_address >= 0x300000) {
|
||||
if(cycle.operation & Microcycle::SelectWord) {
|
||||
cycle.value->full = 0xffff;
|
||||
} else {
|
||||
cycle.value->halves.low = 0xff;
|
||||
}
|
||||
return delay;
|
||||
}
|
||||
}
|
||||
|
||||
switch(cycle.operation & (Microcycle::SelectWord | Microcycle::SelectByte | Microcycle::Read | Microcycle::InterruptAcknowledge)) {
|
||||
default:
|
||||
break;
|
||||
|
||||
// Catches the deliberation set of operation to 0 above.
|
||||
case 0: break;
|
||||
|
||||
case Microcycle::InterruptAcknowledge | Microcycle::SelectByte:
|
||||
// The Macintosh uses autovectored interrupts.
|
||||
mc68000_.set_is_peripheral_address(true);
|
||||
break;
|
||||
|
||||
case Microcycle::SelectWord | Microcycle::Read:
|
||||
cycle.value->full = memory_base[word_address];
|
||||
break;
|
||||
case Microcycle::SelectByte | Microcycle::Read:
|
||||
cycle.value->halves.low = uint8_t(memory_base[word_address] >> cycle.byte_shift());
|
||||
break;
|
||||
case Microcycle::SelectWord:
|
||||
memory_base[word_address] = cycle.value->full;
|
||||
break;
|
||||
case Microcycle::SelectByte:
|
||||
memory_base[word_address] = uint16_t(
|
||||
(cycle.value->halves.low << cycle.byte_shift()) |
|
||||
(memory_base[word_address] & cycle.untouched_byte_mask())
|
||||
);
|
||||
break;
|
||||
}
|
||||
|
||||
/*
|
||||
Normal memory map:
|
||||
|
||||
000000: RAM
|
||||
400000: ROM
|
||||
9FFFF8+: SCC read operations
|
||||
BFFFF8+: SCC write operations
|
||||
DFE1FF+: IWM
|
||||
EFE1FE+: VIA
|
||||
*/
|
||||
|
||||
return delay;
|
||||
}
|
||||
|
||||
void flush() {
|
||||
// Flush the video before the audio queue; in a Mac the
|
||||
// video is responsible for providing part of the
|
||||
// audio signal, so the two aren't as distinct as in
|
||||
// most machines.
|
||||
update_video();
|
||||
|
||||
// As above: flush audio after video.
|
||||
via_.flush();
|
||||
audio_.queue.perform();
|
||||
|
||||
// Experimental?
|
||||
iwm_.flush();
|
||||
}
|
||||
|
||||
void set_rom_is_overlay(bool rom_is_overlay) {
|
||||
ROM_is_overlay_ = rom_is_overlay;
|
||||
}
|
||||
|
||||
bool video_is_outputting() {
|
||||
return video_.is_outputting(time_since_video_update_);
|
||||
}
|
||||
|
||||
void set_use_alternate_buffers(bool use_alternate_screen_buffer, bool use_alternate_audio_buffer) {
|
||||
update_video();
|
||||
video_.set_use_alternate_buffers(use_alternate_screen_buffer, use_alternate_audio_buffer);
|
||||
}
|
||||
|
||||
bool insert_media(const Analyser::Static::Media &media) override {
|
||||
if(media.disks.empty())
|
||||
return false;
|
||||
|
||||
// TODO: shouldn't allow disks to be replaced like this, as the Mac
|
||||
// uses software eject. Will need to expand messaging ability of
|
||||
// insert_media.
|
||||
if(drives_[0].has_disk())
|
||||
drives_[1].set_disk(media.disks[0]);
|
||||
else
|
||||
drives_[0].set_disk(media.disks[0]);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// MARK: Keyboard input.
|
||||
|
||||
KeyboardMapper *get_keyboard_mapper() override {
|
||||
return &keyboard_mapper_;
|
||||
}
|
||||
|
||||
void set_key_state(uint16_t key, bool is_pressed) override {
|
||||
keyboard_.enqueue_key_state(key, is_pressed);
|
||||
}
|
||||
|
||||
// TODO: clear all keys.
|
||||
|
||||
// MARK: Interrupt updates.
|
||||
|
||||
void did_change_interrupt_status(Zilog::SCC::z8530 *sender, bool new_status) override {
|
||||
update_interrupt_input();
|
||||
}
|
||||
|
||||
void update_interrupt_input() {
|
||||
// Update interrupt input.
|
||||
// TODO: does this really cascade like this?
|
||||
if(scc_.get_interrupt_line()) {
|
||||
mc68000_.set_interrupt_level(2);
|
||||
} else if(via_.get_interrupt_line()) {
|
||||
mc68000_.set_interrupt_level(1);
|
||||
} else {
|
||||
mc68000_.set_interrupt_level(0);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
void update_video() {
|
||||
video_.run_for(time_since_video_update_.flush<HalfCycles>());
|
||||
time_until_video_event_ = video_.get_next_sequence_point();
|
||||
}
|
||||
|
||||
Inputs::Mouse &get_mouse() override {
|
||||
return mouse_;
|
||||
}
|
||||
|
||||
struct IWM {
|
||||
IWM(int clock_rate) : iwm(clock_rate) {}
|
||||
|
||||
HalfCycles time_since_update;
|
||||
Apple::IWM iwm;
|
||||
|
||||
void flush() {
|
||||
iwm.run_for(time_since_update.flush<Cycles>());
|
||||
}
|
||||
};
|
||||
|
||||
class VIAPortHandler: public MOS::MOS6522::PortHandler {
|
||||
public:
|
||||
VIAPortHandler(ConcreteMachine &machine, RealTimeClock &clock, Keyboard &keyboard, Video &video, DeferredAudio &audio, IWM &iwm, Inputs::QuadratureMouse &mouse) :
|
||||
machine_(machine), clock_(clock), keyboard_(keyboard), video_(video), audio_(audio), iwm_(iwm), mouse_(mouse) {}
|
||||
|
||||
using Port = MOS::MOS6522::Port;
|
||||
using Line = MOS::MOS6522::Line;
|
||||
|
||||
void set_port_output(Port port, uint8_t value, uint8_t direction_mask) {
|
||||
/*
|
||||
Peripheral lines: keyboard data, interrupt configuration.
|
||||
(See p176 [/215])
|
||||
*/
|
||||
switch(port) {
|
||||
case Port::A:
|
||||
/*
|
||||
Port A:
|
||||
b7: [input] SCC wait/request (/W/REQA and /W/REQB wired together for a logical OR)
|
||||
b6: 0 = alternate screen buffer, 1 = main screen buffer
|
||||
b5: floppy disk SEL state control (upper/lower head "among other things")
|
||||
b4: 1 = use ROM overlay memory map, 0 = use ordinary memory map
|
||||
b3: 0 = use alternate sound buffer, 1 = use ordinary sound buffer
|
||||
b2–b0: audio output volume
|
||||
*/
|
||||
iwm_.flush();
|
||||
iwm_.iwm.set_select(!!(value & 0x20));
|
||||
|
||||
machine_.set_use_alternate_buffers(!(value & 0x40), !(value&0x08));
|
||||
machine_.set_rom_is_overlay(!!(value & 0x10));
|
||||
|
||||
audio_.flush();
|
||||
audio_.audio.set_volume(value & 7);
|
||||
break;
|
||||
|
||||
case Port::B:
|
||||
/*
|
||||
Port B:
|
||||
b7: 0 = sound enabled, 1 = sound disabled
|
||||
b6: [input] 0 = video beam in visible portion of line, 1 = outside
|
||||
b5: [input] mouse y2
|
||||
b4: [input] mouse x2
|
||||
b3: [input] 0 = mouse button down, 1 = up
|
||||
b2: 0 = real-time clock enabled, 1 = disabled
|
||||
b1: clock's data-clock line
|
||||
b0: clock's serial data line
|
||||
*/
|
||||
if(value & 0x4) clock_.abort();
|
||||
else clock_.set_input(!!(value & 0x2), !!(value & 0x1));
|
||||
|
||||
audio_.flush();
|
||||
audio_.audio.set_enabled(!(value & 0x80));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t get_port_input(Port port) {
|
||||
switch(port) {
|
||||
case Port::A:
|
||||
// printf("6522 r A\n");
|
||||
return 0x00; // TODO: b7 = SCC wait/request
|
||||
|
||||
case Port::B:
|
||||
return uint8_t(
|
||||
((mouse_.get_button_mask() & 1) ? 0x00 : 0x08) |
|
||||
((mouse_.get_channel(0) & 2) << 3) |
|
||||
((mouse_.get_channel(1) & 2) << 4) |
|
||||
(clock_.get_data() ? 0x02 : 0x00) |
|
||||
(machine_.video_is_outputting() ? 0x00 : 0x40)
|
||||
);
|
||||
}
|
||||
|
||||
// Should be unreachable.
|
||||
return 0xff;
|
||||
}
|
||||
|
||||
void set_control_line_output(Port port, Line line, bool value) {
|
||||
/*
|
||||
Keyboard wiring (I believe):
|
||||
CB2 = data (input/output)
|
||||
CB1 = clock (input)
|
||||
|
||||
CA2 is used for receiving RTC interrupts.
|
||||
CA1 is used for receiving vsync.
|
||||
*/
|
||||
if(port == Port::B && line == Line::Two) {
|
||||
keyboard_.set_input(value);
|
||||
}
|
||||
else LOG("Unhandled control line output: " << (port ? 'B' : 'A') << int(line));
|
||||
}
|
||||
|
||||
void run_for(HalfCycles duration) {
|
||||
// The 6522 enjoys a divide-by-ten, so multiply back up here to make the
|
||||
// divided-by-two clock the audio works on.
|
||||
audio_.time_since_update += HalfCycles(duration.as_int() * 5);
|
||||
}
|
||||
|
||||
void flush() {
|
||||
audio_.flush();
|
||||
}
|
||||
|
||||
void set_interrupt_status(bool status) {
|
||||
machine_.update_interrupt_input();
|
||||
}
|
||||
|
||||
private:
|
||||
ConcreteMachine &machine_;
|
||||
RealTimeClock &clock_;
|
||||
Keyboard &keyboard_;
|
||||
Video &video_;
|
||||
DeferredAudio &audio_;
|
||||
IWM &iwm_;
|
||||
Inputs::QuadratureMouse &mouse_;
|
||||
};
|
||||
|
||||
CPU::MC68000::Processor<ConcreteMachine, true> mc68000_;
|
||||
|
||||
DriveSpeedAccumulator drive_speed_accumulator_;
|
||||
IWM iwm_;
|
||||
|
||||
DeferredAudio audio_;
|
||||
Video video_;
|
||||
|
||||
RealTimeClock clock_;
|
||||
Keyboard keyboard_;
|
||||
|
||||
MOS::MOS6522::MOS6522<VIAPortHandler> via_;
|
||||
VIAPortHandler via_port_handler_;
|
||||
|
||||
Zilog::SCC::z8530 scc_;
|
||||
|
||||
HalfCycles via_clock_;
|
||||
HalfCycles real_time_clock_;
|
||||
HalfCycles keyboard_clock_;
|
||||
HalfCycles time_since_video_update_;
|
||||
HalfCycles time_until_video_event_;
|
||||
HalfCycles time_since_mouse_update_;
|
||||
|
||||
bool ROM_is_overlay_ = true;
|
||||
int phase_ = 1;
|
||||
|
||||
DoubleDensityDrive drives_[2];
|
||||
Inputs::QuadratureMouse mouse_;
|
||||
|
||||
Apple::Macintosh::KeyboardMapper keyboard_mapper_;
|
||||
|
||||
uint32_t ram_mask_ = 0;
|
||||
uint32_t rom_mask_ = 0;
|
||||
uint16_t rom_[64*1024];
|
||||
uint16_t ram_[256*1024];
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
using namespace Apple::Macintosh;
|
||||
|
||||
Machine *Machine::Macintosh(const Analyser::Static::Target *target, const ROMMachine::ROMFetcher &rom_fetcher) {
|
||||
auto *const mac_target = dynamic_cast<const Analyser::Static::Macintosh::Target *>(target);
|
||||
|
||||
using Model = Analyser::Static::Macintosh::Target::Model;
|
||||
switch(mac_target->model) {
|
||||
default:
|
||||
case Model::Mac128k: return new ConcreteMachine<Model::Mac128k>(*mac_target, rom_fetcher);
|
||||
case Model::Mac512k: return new ConcreteMachine<Model::Mac512k>(*mac_target, rom_fetcher);
|
||||
case Model::Mac512ke: return new ConcreteMachine<Model::Mac512ke>(*mac_target, rom_fetcher);
|
||||
case Model::MacPlus: return new ConcreteMachine<Model::MacPlus>(*mac_target, rom_fetcher);
|
||||
}
|
||||
}
|
||||
|
||||
Machine::~Machine() {}
|
||||
30
Machines/Apple/Macintosh/Macintosh.hpp
Normal file
30
Machines/Apple/Macintosh/Macintosh.hpp
Normal file
@@ -0,0 +1,30 @@
|
||||
//
|
||||
// Macintosh.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 03/05/2019.
|
||||
// Copyright © 2019 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef Macintosh_hpp
|
||||
#define Macintosh_hpp
|
||||
|
||||
#include "../../../Analyser/Static/StaticAnalyser.hpp"
|
||||
#include "../../ROMMachine.hpp"
|
||||
|
||||
namespace Apple {
|
||||
namespace Macintosh {
|
||||
|
||||
class Machine {
|
||||
public:
|
||||
virtual ~Machine();
|
||||
|
||||
/// Creates and returns a Macintosh.
|
||||
static Machine *Macintosh(const Analyser::Static::Target *target, const ROMMachine::ROMFetcher &rom_fetcher);
|
||||
};
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* Macintosh_hpp */
|
||||
166
Machines/Apple/Macintosh/RealTimeClock.hpp
Normal file
166
Machines/Apple/Macintosh/RealTimeClock.hpp
Normal file
@@ -0,0 +1,166 @@
|
||||
//
|
||||
// RealTimeClock.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 07/05/2019.
|
||||
// Copyright © 2019 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef RealTimeClock_hpp
|
||||
#define RealTimeClock_hpp
|
||||
|
||||
#include "../../Utility/MemoryFuzzer.hpp"
|
||||
|
||||
namespace Apple {
|
||||
namespace Macintosh {
|
||||
|
||||
/*!
|
||||
Models the storage component of Apple's real-time clock.
|
||||
|
||||
Since tracking of time is pushed to this class, it is assumed
|
||||
that whomever is translating real time into emulated time
|
||||
will notify the VIA of a potential interrupt.
|
||||
*/
|
||||
class RealTimeClock {
|
||||
public:
|
||||
RealTimeClock() {
|
||||
// TODO: this should persist, if possible, rather than
|
||||
// being randomly initialised.
|
||||
Memory::Fuzz(data_, sizeof(data_));
|
||||
}
|
||||
|
||||
/*!
|
||||
Advances the clock by 1 second.
|
||||
|
||||
The caller should also notify the VIA.
|
||||
*/
|
||||
void update() {
|
||||
for(int c = 0; c < 4; ++c) {
|
||||
++seconds_[c];
|
||||
if(seconds_[c]) break;
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets the current clock and data inputs to the clock.
|
||||
*/
|
||||
void set_input(bool clock, bool data) {
|
||||
/*
|
||||
Documented commands:
|
||||
|
||||
z0000001 Seconds register 0 (lowest order byte)
|
||||
z0000101 Seconds register 1
|
||||
z0001001 Seconds register 2
|
||||
z0001101 Seconds register 3
|
||||
00110001 Test register (write only)
|
||||
00110101 Write-protect register (write only)
|
||||
z010aa01 RAM addresses 0x10 - 0x13
|
||||
z1aaaa01 RAM addresses 0x00 – 0x0f
|
||||
|
||||
z = 1 => a read; z = 0 => a write.
|
||||
|
||||
The top bit of the write-protect register enables (0) or disables (1)
|
||||
writes to other locations.
|
||||
|
||||
All the documentation says about the test register is to set the top
|
||||
two bits to 0 for normal operation. Abnormal operation is undefined.
|
||||
|
||||
The data line is valid when the clock transitions to level 0.
|
||||
*/
|
||||
|
||||
if(clock && !previous_clock_) {
|
||||
// Shift into the command_ register, no matter what.
|
||||
command_ = uint16_t((command_ << 1) | (data ? 1 : 0));
|
||||
result_ <<= 1;
|
||||
|
||||
// Increment phase.
|
||||
++phase_;
|
||||
|
||||
// When phase hits 8, inspect the command.
|
||||
// If it's a read, prepare a result.
|
||||
if(phase_ == 8) {
|
||||
if(command_ & 0x80) {
|
||||
// A read.
|
||||
const auto address = (command_ >> 2) & 0x1f;
|
||||
|
||||
// Begin pessimistically.
|
||||
result_ = 0xff;
|
||||
|
||||
if(address < 4) {
|
||||
result_ = seconds_[address];
|
||||
} else if(address >= 0x10) {
|
||||
result_ = data_[address & 0xf];
|
||||
} else if(address >= 0x8 && address <= 0xb) {
|
||||
result_ = data_[0x10 + (address & 0x3)];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If phase hits 16 and this was a read command,
|
||||
// just stop. If it was a write command, do the
|
||||
// actual write.
|
||||
if(phase_ == 16) {
|
||||
if(!(command_ & 0x8000)) {
|
||||
// A write.
|
||||
|
||||
const auto address = (command_ >> 10) & 0x1f;
|
||||
const uint8_t value = uint8_t(command_ & 0xff);
|
||||
|
||||
// First test: is this to the write-protect register?
|
||||
if(address == 0xd) {
|
||||
write_protect_ = value;
|
||||
}
|
||||
|
||||
// No other writing is permitted if the write protect
|
||||
// register won't allow it.
|
||||
if(!(write_protect_ & 0x80)) {
|
||||
if(address < 4) {
|
||||
seconds_[address] = value;
|
||||
} else if(address >= 0x10) {
|
||||
data_[address & 0xf] = value;
|
||||
} else if(address >= 0x8 && address <= 0xb) {
|
||||
data_[0x10 + (address & 0x3)] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// A phase of 16 always ends the command, so reset here.
|
||||
abort();
|
||||
}
|
||||
}
|
||||
|
||||
previous_clock_ = clock;
|
||||
}
|
||||
|
||||
/*!
|
||||
Reads the current data output level from the clock.
|
||||
*/
|
||||
bool get_data() {
|
||||
return !!(result_ & 0x80);
|
||||
}
|
||||
|
||||
/*!
|
||||
Announces that a serial command has been aborted.
|
||||
*/
|
||||
void abort() {
|
||||
result_ = 0;
|
||||
phase_ = 0;
|
||||
command_ = 0;
|
||||
}
|
||||
|
||||
private:
|
||||
uint8_t data_[0x14];
|
||||
uint8_t seconds_[4];
|
||||
uint8_t write_protect_;
|
||||
|
||||
int phase_ = 0;
|
||||
uint16_t command_;
|
||||
uint8_t result_ = 0;
|
||||
|
||||
bool previous_clock_ = false;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* RealTimeClock_hpp */
|
||||
201
Machines/Apple/Macintosh/Video.cpp
Normal file
201
Machines/Apple/Macintosh/Video.cpp
Normal file
@@ -0,0 +1,201 @@
|
||||
//
|
||||
// Video.cpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 03/05/2019.
|
||||
// Copyright © 2019 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#include "Video.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
using namespace Apple::Macintosh;
|
||||
|
||||
namespace {
|
||||
|
||||
const HalfCycles line_length(704);
|
||||
const int number_of_lines = 370;
|
||||
const HalfCycles frame_length(line_length * HalfCycles(number_of_lines));
|
||||
const int sync_start = 36;
|
||||
const int sync_end = 38;
|
||||
|
||||
}
|
||||
|
||||
// Re: CRT timings, see the Apple Guide to the Macintosh Hardware Family,
|
||||
// bottom of page 400:
|
||||
//
|
||||
// "For each scan line, 512 pixels are drawn on the screen ...
|
||||
// The horizontal blanking interval takes the time of an additional 192 pixels"
|
||||
//
|
||||
// And, at the top of 401:
|
||||
//
|
||||
// "The visible portion of a full-screen display consists of 342 horizontal scan lines...
|
||||
// During the vertical blanking interval, the turned-off beam ... traces out an additional 28 scan lines,"
|
||||
//
|
||||
Video::Video(uint16_t *ram, DeferredAudio &audio, DriveSpeedAccumulator &drive_speed_accumulator) :
|
||||
audio_(audio),
|
||||
drive_speed_accumulator_(drive_speed_accumulator),
|
||||
crt_(704, 1, 370, Outputs::Display::ColourSpace::YIQ, 1, 1, 6, false, Outputs::Display::InputDataType::Luminance1),
|
||||
ram_(ram) {
|
||||
|
||||
crt_.set_display_type(Outputs::Display::DisplayType::RGB);
|
||||
crt_.set_visible_area(Outputs::Display::Rect(0.08f, -0.025f, 0.82f, 0.82f));
|
||||
crt_.set_aspect_ratio(1.73f); // The Mac uses a non-standard scanning area.
|
||||
}
|
||||
|
||||
void Video::set_scan_target(Outputs::Display::ScanTarget *scan_target) {
|
||||
crt_.set_scan_target(scan_target);
|
||||
}
|
||||
|
||||
void Video::run_for(HalfCycles duration) {
|
||||
// Determine the current video and audio bases. These values don't appear to be latched, they apply immediately.
|
||||
const size_t video_base = (use_alternate_screen_buffer_ ? (0xffff2700 >> 1) : (0xffffa700 >> 1)) & ram_mask_;
|
||||
const size_t audio_base = (use_alternate_audio_buffer_ ? (0xffffa100 >> 1) : (0xfffffd00 >> 1)) & ram_mask_;
|
||||
|
||||
// The number of HalfCycles is literally the number of pixel clocks to move through,
|
||||
// since pixel output occurs at twice the processor clock. So divide by 16 to get
|
||||
// the number of fetches.
|
||||
while(duration > HalfCycles(0)) {
|
||||
const auto pixel_start = frame_position_ % line_length;
|
||||
const int line = (frame_position_ / line_length).as_int();
|
||||
|
||||
const auto cycles_left_in_line = std::min(line_length - pixel_start, duration);
|
||||
|
||||
// Line timing, entirely invented as I can find exactly zero words of documentation:
|
||||
//
|
||||
// First 342 lines:
|
||||
//
|
||||
// First 32 words = pixels;
|
||||
// next 5 words = right border;
|
||||
// next 2 words = sync level;
|
||||
// final 5 words = left border.
|
||||
//
|
||||
// Then 12 lines of border, 3 of sync, 11 more of border.
|
||||
|
||||
const int first_word = pixel_start.as_int() >> 4;
|
||||
const int final_word = (pixel_start + cycles_left_in_line).as_int() >> 4;
|
||||
|
||||
if(first_word != final_word) {
|
||||
if(line < 342) {
|
||||
// If there are any pixels left to output, do so.
|
||||
if(first_word < 32) {
|
||||
const int final_pixel_word = std::min(final_word, 32);
|
||||
|
||||
if(!first_word) {
|
||||
pixel_buffer_ = crt_.begin_data(512);
|
||||
}
|
||||
|
||||
if(pixel_buffer_) {
|
||||
for(int c = first_word; c < final_pixel_word; ++c) {
|
||||
uint16_t pixels = ram_[video_base + video_address_] ^ 0xffff;
|
||||
++video_address_;
|
||||
|
||||
pixel_buffer_[15] = pixels & 0x01;
|
||||
pixel_buffer_[14] = pixels & 0x02;
|
||||
pixel_buffer_[13] = pixels & 0x04;
|
||||
pixel_buffer_[12] = pixels & 0x08;
|
||||
pixel_buffer_[11] = pixels & 0x10;
|
||||
pixel_buffer_[10] = pixels & 0x20;
|
||||
pixel_buffer_[9] = pixels & 0x40;
|
||||
pixel_buffer_[8] = pixels & 0x80;
|
||||
|
||||
pixels >>= 8;
|
||||
pixel_buffer_[7] = pixels & 0x01;
|
||||
pixel_buffer_[6] = pixels & 0x02;
|
||||
pixel_buffer_[5] = pixels & 0x04;
|
||||
pixel_buffer_[4] = pixels & 0x08;
|
||||
pixel_buffer_[3] = pixels & 0x10;
|
||||
pixel_buffer_[2] = pixels & 0x20;
|
||||
pixel_buffer_[1] = pixels & 0x40;
|
||||
pixel_buffer_[0] = pixels & 0x80;
|
||||
|
||||
pixel_buffer_ += 16;
|
||||
}
|
||||
}
|
||||
|
||||
if(final_pixel_word == 32) {
|
||||
crt_.output_data(512);
|
||||
}
|
||||
}
|
||||
|
||||
if(first_word < sync_start && final_word >= sync_start) crt_.output_blank((sync_start - 32) * 16);
|
||||
if(first_word < sync_end && final_word >= sync_end) crt_.output_sync((sync_end - sync_start) * 16);
|
||||
if(final_word == 44) crt_.output_blank((44 - sync_end) * 16);
|
||||
} else if(final_word == 44) {
|
||||
if(line >= 353 && line < 356) {
|
||||
/* Output a sync line. */
|
||||
crt_.output_sync(sync_start * 16);
|
||||
crt_.output_blank((sync_end - sync_start) * 16);
|
||||
crt_.output_sync((44 - sync_end) * 16);
|
||||
} else {
|
||||
/* Output a blank line. */
|
||||
crt_.output_blank(sync_start * 16);
|
||||
crt_.output_sync((sync_end - sync_start) * 16);
|
||||
crt_.output_blank((44 - sync_end) * 16);
|
||||
}
|
||||
}
|
||||
|
||||
// Audio and disk fetches occur "just before video data".
|
||||
if(final_word == 44) {
|
||||
const uint16_t audio_word = ram_[audio_address_ + audio_base];
|
||||
++audio_address_;
|
||||
audio_.audio.post_sample(audio_word >> 8);
|
||||
drive_speed_accumulator_.post_sample(audio_word & 0xff);
|
||||
}
|
||||
}
|
||||
|
||||
duration -= cycles_left_in_line;
|
||||
frame_position_ = frame_position_ + cycles_left_in_line;
|
||||
if(frame_position_ == frame_length) {
|
||||
frame_position_ = HalfCycles(0);
|
||||
/*
|
||||
Video: $1A700 and the alternate buffer starts at $12700; for a 512K Macintosh, add $60000 to these numbers.
|
||||
*/
|
||||
video_address_ = 0;
|
||||
|
||||
/*
|
||||
"The main sound buffer is at $1FD00 in a 128K Macintosh, and the alternate buffer is at $1A100;
|
||||
for a 512K Macintosh, add $60000 to these values."
|
||||
*/
|
||||
audio_address_ = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool Video::vsync() {
|
||||
const int line = (frame_position_ / line_length).as_int();
|
||||
return line >= 353 && line < 356;
|
||||
}
|
||||
|
||||
HalfCycles Video::get_next_sequence_point() {
|
||||
const int line = (frame_position_ / line_length).as_int();
|
||||
if(line >= 353 && line < 356) {
|
||||
// Currently in vsync, so get time until start of line 357,
|
||||
// when vsync will end.
|
||||
return HalfCycles(356) * line_length - frame_position_;
|
||||
} else {
|
||||
// Not currently in vsync, so get time until start of line 353.
|
||||
const auto start_of_vsync = HalfCycles(353) * line_length;
|
||||
if(frame_position_ < start_of_vsync)
|
||||
return start_of_vsync - frame_position_;
|
||||
else
|
||||
return start_of_vsync + HalfCycles(number_of_lines) * line_length - frame_position_;
|
||||
}
|
||||
}
|
||||
|
||||
bool Video::is_outputting(HalfCycles offset) {
|
||||
const auto offset_position = frame_position_ + offset % frame_length;
|
||||
const int column = (offset_position % line_length).as_int() >> 4;
|
||||
const int line = (offset_position / line_length).as_int();
|
||||
return line < 342 && column < 32;
|
||||
}
|
||||
|
||||
void Video::set_use_alternate_buffers(bool use_alternate_screen_buffer, bool use_alternate_audio_buffer) {
|
||||
use_alternate_screen_buffer_ = use_alternate_screen_buffer;
|
||||
use_alternate_audio_buffer_ = use_alternate_audio_buffer;
|
||||
}
|
||||
|
||||
void Video::set_ram_mask(uint32_t mask) {
|
||||
ram_mask_ = mask;
|
||||
}
|
||||
94
Machines/Apple/Macintosh/Video.hpp
Normal file
94
Machines/Apple/Macintosh/Video.hpp
Normal file
@@ -0,0 +1,94 @@
|
||||
//
|
||||
// Video.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 03/05/2019.
|
||||
// Copyright © 2019 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef Video_hpp
|
||||
#define Video_hpp
|
||||
|
||||
#include "../../../Outputs/CRT/CRT.hpp"
|
||||
#include "../../../ClockReceiver/ClockReceiver.hpp"
|
||||
#include "DeferredAudio.hpp"
|
||||
#include "DriveSpeedAccumulator.hpp"
|
||||
|
||||
namespace Apple {
|
||||
namespace Macintosh {
|
||||
|
||||
/*!
|
||||
Models the 68000-era Macintosh video hardware, producing a 512x348 pixel image,
|
||||
within a total scanning area of 370 lines, at 352 cycles per line.
|
||||
|
||||
This class also collects audio and 400kb drive-speed data, forwarding those values.
|
||||
*/
|
||||
class Video {
|
||||
public:
|
||||
/*!
|
||||
Constructs an instance of @c Video sourcing its pixel data from @c ram and
|
||||
providing audio and drive-speed bytes to @c audio and @c drive_speed_accumulator.
|
||||
*/
|
||||
Video(uint16_t *ram, DeferredAudio &audio, DriveSpeedAccumulator &drive_speed_accumulator);
|
||||
|
||||
/*!
|
||||
Sets the target device for video data.
|
||||
*/
|
||||
void set_scan_target(Outputs::Display::ScanTarget *scan_target);
|
||||
|
||||
/*!
|
||||
Produces the next @c duration period of pixels.
|
||||
*/
|
||||
void run_for(HalfCycles duration);
|
||||
|
||||
/*!
|
||||
Sets whether the alternate screen and/or audio buffers should be used to source data.
|
||||
*/
|
||||
void set_use_alternate_buffers(bool use_alternate_screen_buffer, bool use_alternate_audio_buffer);
|
||||
|
||||
/*!
|
||||
Provides a mask indicating which parts of the generated video and audio/drive addresses are
|
||||
actually decoded, accessing *word-sized memory*; e.g. for a 128kb Macintosh this should be (1 << 16) - 1 = 0xffff.
|
||||
*/
|
||||
void set_ram_mask(uint32_t);
|
||||
|
||||
/*!
|
||||
@returns @c true if the video is currently outputting a vertical sync, @c false otherwise.
|
||||
*/
|
||||
bool vsync();
|
||||
|
||||
/*
|
||||
@returns @c true if in @c offset half cycles from now, the video will be outputting pixels;
|
||||
@c false otherwise.
|
||||
*/
|
||||
bool is_outputting(HalfCycles offset = HalfCycles(0));
|
||||
|
||||
/*!
|
||||
@returns the amount of time until there is next a transition on the
|
||||
vsync signal.
|
||||
*/
|
||||
HalfCycles get_next_sequence_point();
|
||||
|
||||
private:
|
||||
DeferredAudio &audio_;
|
||||
DriveSpeedAccumulator &drive_speed_accumulator_;
|
||||
|
||||
Outputs::CRT::CRT crt_;
|
||||
uint16_t *ram_ = nullptr;
|
||||
uint32_t ram_mask_ = 0;
|
||||
|
||||
HalfCycles frame_position_;
|
||||
|
||||
size_t video_address_ = 0;
|
||||
size_t audio_address_ = 0;
|
||||
|
||||
uint8_t *pixel_buffer_ = nullptr;
|
||||
|
||||
bool use_alternate_screen_buffer_ = false;
|
||||
bool use_alternate_audio_buffer_ = false;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* Video_hpp */
|
||||
@@ -55,13 +55,13 @@ class Bus {
|
||||
// video backlog accumulation counter
|
||||
Cycles cycles_since_video_update_;
|
||||
inline void update_video() {
|
||||
tia_.run_for(cycles_since_video_update_.flush());
|
||||
tia_.run_for(cycles_since_video_update_.flush<Cycles>());
|
||||
}
|
||||
|
||||
// RIOT backlog accumulation counter
|
||||
Cycles cycles_since_6532_update_;
|
||||
inline void update_6532() {
|
||||
mos6532_.run_for(cycles_since_6532_update_.flush());
|
||||
mos6532_.run_for(cycles_since_6532_update_.flush<Cycles>());
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
|
||||
#include "../../Configurable/StandardOptions.hpp"
|
||||
#include "../../ClockReceiver/ForceInline.hpp"
|
||||
#include "../../ClockReceiver/JustInTime.hpp"
|
||||
|
||||
#include "../../Outputs/Speaker/Implementation/CompoundSource.hpp"
|
||||
#include "../../Outputs/Speaker/Implementation/LowpassSpeaker.hpp"
|
||||
@@ -131,8 +132,7 @@ class ConcreteMachine:
|
||||
joysticks_.emplace_back(new Joystick);
|
||||
|
||||
const auto roms = rom_fetcher(
|
||||
"ColecoVision",
|
||||
{ "coleco.rom" });
|
||||
{ {"ColecoVision", "the ColecoVision BIOS", "coleco.rom", 8*1024, 0x3aa93ef3} });
|
||||
|
||||
if(!roms[0]) {
|
||||
throw ROMMachine::Error::MissingROMs;
|
||||
@@ -170,7 +170,7 @@ class ConcreteMachine:
|
||||
}
|
||||
|
||||
// ColecoVisions have composite output only.
|
||||
vdp_.set_display_type(Outputs::Display::DisplayType::CompositeColour);
|
||||
vdp_->set_display_type(Outputs::Display::DisplayType::CompositeColour);
|
||||
}
|
||||
|
||||
~ConcreteMachine() {
|
||||
@@ -182,11 +182,11 @@ class ConcreteMachine:
|
||||
}
|
||||
|
||||
void set_scan_target(Outputs::Display::ScanTarget *scan_target) override {
|
||||
vdp_.set_scan_target(scan_target);
|
||||
vdp_->set_scan_target(scan_target);
|
||||
}
|
||||
|
||||
void set_display_type(Outputs::Display::DisplayType display_type) override {
|
||||
vdp_.set_display_type(display_type);
|
||||
vdp_->set_display_type(display_type);
|
||||
}
|
||||
|
||||
Outputs::Speaker::Speaker *get_speaker() override {
|
||||
@@ -211,7 +211,7 @@ class ConcreteMachine:
|
||||
}
|
||||
const HalfCycles length = cycle.length + penalty;
|
||||
|
||||
time_since_vdp_update_ += length;
|
||||
vdp_ += length;
|
||||
time_since_sn76489_update_ += length;
|
||||
|
||||
// Act only if necessary.
|
||||
@@ -256,10 +256,9 @@ class ConcreteMachine:
|
||||
case CPU::Z80::PartialMachineCycle::Input:
|
||||
switch((address >> 5) & 7) {
|
||||
case 5:
|
||||
update_video();
|
||||
*cycle.value = vdp_.get_register(address);
|
||||
z80_.set_non_maskable_interrupt_line(vdp_.get_interrupt_line());
|
||||
time_until_interrupt_ = vdp_.get_time_until_interrupt();
|
||||
*cycle.value = vdp_->get_register(address);
|
||||
z80_.set_non_maskable_interrupt_line(vdp_->get_interrupt_line());
|
||||
time_until_interrupt_ = vdp_->get_time_until_interrupt();
|
||||
break;
|
||||
|
||||
case 7: {
|
||||
@@ -300,10 +299,9 @@ class ConcreteMachine:
|
||||
break;
|
||||
|
||||
case 5:
|
||||
update_video();
|
||||
vdp_.set_register(address, *cycle.value);
|
||||
z80_.set_non_maskable_interrupt_line(vdp_.get_interrupt_line());
|
||||
time_until_interrupt_ = vdp_.get_time_until_interrupt();
|
||||
vdp_->set_register(address, *cycle.value);
|
||||
z80_.set_non_maskable_interrupt_line(vdp_->get_interrupt_line());
|
||||
time_until_interrupt_ = vdp_->get_time_until_interrupt();
|
||||
break;
|
||||
|
||||
case 7:
|
||||
@@ -355,7 +353,7 @@ class ConcreteMachine:
|
||||
}
|
||||
|
||||
void flush() {
|
||||
update_video();
|
||||
vdp_.flush();
|
||||
update_audio();
|
||||
audio_queue_.perform();
|
||||
}
|
||||
@@ -397,12 +395,9 @@ class ConcreteMachine:
|
||||
inline void update_audio() {
|
||||
speaker_.run_for(audio_queue_, time_since_sn76489_update_.divide_cycles(Cycles(sn76489_divider)));
|
||||
}
|
||||
inline void update_video() {
|
||||
vdp_.run_for(time_since_vdp_update_.flush());
|
||||
}
|
||||
|
||||
CPU::Z80::Processor<ConcreteMachine, false, false> z80_;
|
||||
TI::TMS::TMS9918 vdp_;
|
||||
JustInTimeActor<TI::TMS::TMS9918, HalfCycles> vdp_;
|
||||
|
||||
Concurrency::DeferringAsyncTaskQueue audio_queue_;
|
||||
TI::SN76489 sn76489_;
|
||||
@@ -425,7 +420,6 @@ class ConcreteMachine:
|
||||
std::vector<std::unique_ptr<Inputs::Joystick>> joysticks_;
|
||||
bool joysticks_in_keypad_mode_ = false;
|
||||
|
||||
HalfCycles time_since_vdp_update_;
|
||||
HalfCycles time_since_sn76489_update_;
|
||||
HalfCycles time_until_interrupt_;
|
||||
|
||||
|
||||
@@ -39,13 +39,20 @@ MachineBase::MachineBase(Personality personality, const ROMMachine::ROMFetcher &
|
||||
// attach the only drive there is
|
||||
set_drive(drive_);
|
||||
|
||||
std::string rom_name;
|
||||
std::string device_name;
|
||||
uint32_t crc = 0;
|
||||
switch(personality) {
|
||||
case Personality::C1540: rom_name = "1540.bin"; break;
|
||||
case Personality::C1541: rom_name = "1541.bin"; break;
|
||||
case Personality::C1540:
|
||||
device_name = "1540";
|
||||
crc = 0x718d42b1;
|
||||
break;
|
||||
case Personality::C1541:
|
||||
device_name = "1541";
|
||||
crc = 0xfb760019;
|
||||
break;
|
||||
}
|
||||
|
||||
auto roms = rom_fetcher("Commodore1540", {rom_name});
|
||||
auto roms = rom_fetcher({ {"Commodore1540", "the " + device_name + " ROM", device_name + ".bin", 16*1024, crc} });
|
||||
if(!roms[0]) {
|
||||
throw ROMMachine::Error::MissingROMs;
|
||||
}
|
||||
|
||||
@@ -323,31 +323,34 @@ class ConcreteMachine:
|
||||
// install a joystick
|
||||
joysticks_.emplace_back(new Joystick(*user_port_via_port_handler_, *keyboard_via_port_handler_));
|
||||
|
||||
std::vector<std::string> rom_names = { "basic.bin" };
|
||||
const std::string machine_name = "Vic20";
|
||||
std::vector<ROMMachine::ROM> rom_names = {
|
||||
{machine_name, "the VIC-20 BASIC ROM", "basic.bin", 8*1024, 0xdb4c43c1}
|
||||
};
|
||||
switch(target.region) {
|
||||
default:
|
||||
rom_names.push_back("characters-english.bin");
|
||||
rom_names.push_back("kernel-pal.bin");
|
||||
rom_names.emplace_back(machine_name, "the English-language VIC-20 character ROM", "characters-english.bin", 4*1024, 0x83e032a6);
|
||||
rom_names.emplace_back(machine_name, "the English-language PAL VIC-20 kernel ROM", "kernel-pal.bin", 8*1024, 0x4be07cb4);
|
||||
break;
|
||||
case Analyser::Static::Commodore::Target::Region::American:
|
||||
rom_names.push_back("characters-english.bin");
|
||||
rom_names.push_back("kernel-ntsc.bin");
|
||||
rom_names.emplace_back(machine_name, "the English-language VIC-20 character ROM", "characters-english.bin", 4*1024, 0x83e032a6);
|
||||
rom_names.emplace_back(machine_name, "the English-language NTSC VIC-20 kernel ROM", "kernel-ntsc.bin", 8*1024, 0xe5e7c174);
|
||||
break;
|
||||
case Analyser::Static::Commodore::Target::Region::Danish:
|
||||
rom_names.push_back("characters-danish.bin");
|
||||
rom_names.push_back("kernel-danish.bin");
|
||||
rom_names.emplace_back(machine_name, "the Danish VIC-20 character ROM", "characters-danish.bin", 4*1024, 0x7fc11454);
|
||||
rom_names.emplace_back(machine_name, "the Danish VIC-20 kernel ROM", "kernel-danish.bin", 8*1024, 0x02adaf16);
|
||||
break;
|
||||
case Analyser::Static::Commodore::Target::Region::Japanese:
|
||||
rom_names.push_back("characters-japanese.bin");
|
||||
rom_names.push_back("kernel-japanese.bin");
|
||||
rom_names.emplace_back(machine_name, "the Japanese VIC-20 character ROM", "characters-japanese.bin", 4*1024, 0xfcfd8a4b);
|
||||
rom_names.emplace_back(machine_name, "the Japanese VIC-20 kernel ROM", "kernel-japanese.bin", 8*1024, 0x336900d7);
|
||||
break;
|
||||
case Analyser::Static::Commodore::Target::Region::Swedish:
|
||||
rom_names.push_back("characters-swedish.bin");
|
||||
rom_names.push_back("kernel-japanese.bin");
|
||||
rom_names.emplace_back(machine_name, "the Swedish VIC-20 character ROM", "characters-swedish.bin", 4*1024, 0xd808551d);
|
||||
rom_names.emplace_back(machine_name, "the Swedish VIC-20 kernel ROM", "kernel-swedish.bin", 8*1024, 0xb2a60662);
|
||||
break;
|
||||
}
|
||||
|
||||
const auto roms = rom_fetcher("Vic20", rom_names);
|
||||
const auto roms = rom_fetcher(rom_names);
|
||||
|
||||
for(const auto &rom: roms) {
|
||||
if(!rom) {
|
||||
@@ -699,7 +702,7 @@ class ConcreteMachine:
|
||||
|
||||
private:
|
||||
void update_video() {
|
||||
mos6560_.run_for(cycles_since_mos6560_update_.flush());
|
||||
mos6560_.run_for(cycles_since_mos6560_update_.flush<Cycles>());
|
||||
}
|
||||
CPU::MOS6502::Processor<CPU::MOS6502::Personality::P6502, ConcreteMachine, false> m6502_;
|
||||
|
||||
|
||||
@@ -11,10 +11,13 @@
|
||||
|
||||
#include "../Configurable/Configurable.hpp"
|
||||
#include "../Activity/Source.hpp"
|
||||
#include "MediaTarget.hpp"
|
||||
|
||||
#include "CRTMachine.hpp"
|
||||
#include "JoystickMachine.hpp"
|
||||
#include "KeyboardMachine.hpp"
|
||||
#include "MediaTarget.hpp"
|
||||
#include "MouseMachine.hpp"
|
||||
|
||||
#include "Utility/Typer.hpp"
|
||||
|
||||
namespace Machine {
|
||||
@@ -31,6 +34,7 @@ struct DynamicMachine {
|
||||
virtual CRTMachine::Machine *crt_machine() = 0;
|
||||
virtual JoystickMachine::Machine *joystick_machine() = 0;
|
||||
virtual KeyboardMachine::Machine *keyboard_machine() = 0;
|
||||
virtual MouseMachine::Machine *mouse_machine() = 0;
|
||||
virtual MediaTarget::Machine *media_target() = 0;
|
||||
|
||||
/*!
|
||||
|
||||
@@ -62,17 +62,22 @@ class ConcreteMachine:
|
||||
set_clock_rate(2000000);
|
||||
|
||||
speaker_.set_input_rate(2000000 / SoundGenerator::clock_rate_divider);
|
||||
speaker_.set_high_frequency_cutoff(7000);
|
||||
|
||||
std::vector<std::string> rom_names = {"basic.rom", "os.rom"};
|
||||
const std::string machine_name = "Electron";
|
||||
std::vector<ROMMachine::ROM> required_roms = {
|
||||
{machine_name, "the Acorn BASIC II ROM", "basic.rom", 16*1024, 0x79434781},
|
||||
{machine_name, "the Electron MOS ROM", "os.rom", 16*1024, 0xbf63fb1f}
|
||||
};
|
||||
if(target.has_adfs) {
|
||||
rom_names.push_back("ADFS-E00_1.rom");
|
||||
rom_names.push_back("ADFS-E00_2.rom");
|
||||
required_roms.emplace_back(machine_name, "the E00 ADFS ROM, first slot", "ADFS-E00_1.rom", 16*1024, 0x51523993);
|
||||
required_roms.emplace_back(machine_name, "the E00 ADFS ROM, second slot", "ADFS-E00_2.rom", 16*1024, 0x8d17de0e);
|
||||
}
|
||||
const size_t dfs_rom_position = rom_names.size();
|
||||
const size_t dfs_rom_position = required_roms.size();
|
||||
if(target.has_dfs) {
|
||||
rom_names.push_back("DFS-1770-2.20.rom");
|
||||
required_roms.emplace_back(machine_name, "the 1770 DFS ROM", "DFS-1770-2.20.rom", 16*1024, 0xf3dc9bc5);
|
||||
}
|
||||
const auto roms = rom_fetcher("Electron", rom_names);
|
||||
const auto roms = rom_fetcher(required_roms);
|
||||
|
||||
for(const auto &rom: roms) {
|
||||
if(!rom) {
|
||||
@@ -505,7 +510,7 @@ class ConcreteMachine:
|
||||
// MARK: - Work deferral updates.
|
||||
inline void update_display() {
|
||||
if(cycles_since_display_update_ > 0) {
|
||||
video_output_.run_for(cycles_since_display_update_.flush());
|
||||
video_output_.run_for(cycles_since_display_update_.flush<Cycles>());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -34,7 +34,7 @@ enum Key: uint16_t {
|
||||
};
|
||||
|
||||
struct KeyboardMapper: public KeyboardMachine::MappedMachine::KeyboardMapper {
|
||||
uint16_t mapped_key_for_key(Inputs::Keyboard::Key key);
|
||||
uint16_t mapped_key_for_key(Inputs::Keyboard::Key key) override;
|
||||
};
|
||||
|
||||
struct CharacterMapper: public ::Utility::CharacterMapper {
|
||||
|
||||
@@ -45,6 +45,7 @@
|
||||
|
||||
#include "../../Configurable/StandardOptions.hpp"
|
||||
#include "../../ClockReceiver/ForceInline.hpp"
|
||||
#include "../../ClockReceiver/JustInTime.hpp"
|
||||
|
||||
#include "../../Analyser/Static/MSX/Target.hpp"
|
||||
|
||||
@@ -173,33 +174,37 @@ class ConcreteMachine:
|
||||
mixer_.set_relative_volumes({0.5f, 0.1f, 0.4f});
|
||||
|
||||
// Install the proper TV standard and select an ideal BIOS name.
|
||||
std::vector<std::string> rom_names = {"msx.rom"};
|
||||
const std::string machine_name = "MSX";
|
||||
std::vector<ROMMachine::ROM> required_roms = {
|
||||
{machine_name, "any MSX BIOS", "msx.rom", 32*1024, 0x94ee12f3}
|
||||
};
|
||||
|
||||
bool is_ntsc = true;
|
||||
uint8_t character_generator = 1; /* 0 = Japan, 1 = USA, etc, 2 = USSR */
|
||||
uint8_t date_format = 1; /* 0 = Y/M/D, 1 = M/D/Y, 2 = D/M/Y */
|
||||
uint8_t keyboard = 1; /* 0 = Japan, 1 = USA, 2 = France, 3 = UK, 4 = Germany, 5 = USSR, 6 = Spain */
|
||||
|
||||
// TODO: CRCs below are incomplete, at best.
|
||||
switch(target.region) {
|
||||
case Target::Region::Japan:
|
||||
rom_names.push_back("msx-japanese.rom");
|
||||
vdp_.set_tv_standard(TI::TMS::TVStandard::NTSC);
|
||||
required_roms.emplace_back(machine_name, "a Japanese MSX BIOS", "msx-japanese.rom", 32*1024, 0xee229390);
|
||||
vdp_->set_tv_standard(TI::TMS::TVStandard::NTSC);
|
||||
|
||||
is_ntsc = true;
|
||||
character_generator = 0;
|
||||
date_format = 0;
|
||||
break;
|
||||
case Target::Region::USA:
|
||||
rom_names.push_back("msx-american.rom");
|
||||
vdp_.set_tv_standard(TI::TMS::TVStandard::NTSC);
|
||||
required_roms.emplace_back(machine_name, "an American MSX BIOS", "msx-american.rom", 32*1024, 0);
|
||||
vdp_->set_tv_standard(TI::TMS::TVStandard::NTSC);
|
||||
|
||||
is_ntsc = true;
|
||||
character_generator = 1;
|
||||
date_format = 1;
|
||||
break;
|
||||
case Target::Region::Europe:
|
||||
rom_names.push_back("msx-european.rom");
|
||||
vdp_.set_tv_standard(TI::TMS::TVStandard::PAL);
|
||||
required_roms.emplace_back(machine_name, "a European MSX BIOS", "msx-european.rom", 32*1024, 0);
|
||||
vdp_->set_tv_standard(TI::TMS::TVStandard::PAL);
|
||||
|
||||
is_ntsc = false;
|
||||
character_generator = 1;
|
||||
@@ -209,10 +214,12 @@ class ConcreteMachine:
|
||||
|
||||
// Fetch the necessary ROMs; try the region-specific ROM first,
|
||||
// but failing that fall back on patching the main one.
|
||||
size_t disk_index = 0;
|
||||
if(target.has_disk_drive) {
|
||||
rom_names.push_back("disk.rom");
|
||||
disk_index = required_roms.size();
|
||||
required_roms.emplace_back(machine_name, "the MSX-DOS ROM", "disk.rom", 16*1024, 0x721f61df);
|
||||
}
|
||||
const auto roms = rom_fetcher("MSX", rom_names);
|
||||
const auto roms = rom_fetcher(required_roms);
|
||||
|
||||
if((!roms[0] && !roms[1]) || (target.has_disk_drive && !roms[2])) {
|
||||
throw ROMMachine::Error::MissingROMs;
|
||||
@@ -227,7 +234,6 @@ class ConcreteMachine:
|
||||
memory_slots_[0].source = std::move(*roms[0]);
|
||||
memory_slots_[0].source.resize(32768);
|
||||
|
||||
|
||||
memory_slots_[0].source[0x2b] = uint8_t(
|
||||
(is_ntsc ? 0x00 : 0x80) |
|
||||
(date_format << 4) |
|
||||
@@ -252,7 +258,7 @@ class ConcreteMachine:
|
||||
// Add a disk cartridge if any disks were supplied.
|
||||
if(target.has_disk_drive) {
|
||||
memory_slots_[2].set_handler(new DiskROM(memory_slots_[2].source));
|
||||
memory_slots_[2].source = std::move(*roms[1]);
|
||||
memory_slots_[2].source = std::move(*roms[disk_index]);
|
||||
memory_slots_[2].source.resize(16384);
|
||||
|
||||
map(2, 0, 0x4000, 0x2000);
|
||||
@@ -273,11 +279,11 @@ class ConcreteMachine:
|
||||
}
|
||||
|
||||
void set_scan_target(Outputs::Display::ScanTarget *scan_target) override {
|
||||
vdp_.set_scan_target(scan_target);
|
||||
vdp_->set_scan_target(scan_target);
|
||||
}
|
||||
|
||||
void set_display_type(Outputs::Display::DisplayType display_type) override {
|
||||
vdp_.set_display_type(display_type);
|
||||
vdp_->set_display_type(display_type);
|
||||
}
|
||||
|
||||
Outputs::Speaker::Speaker *get_speaker() override {
|
||||
@@ -406,7 +412,7 @@ class ConcreteMachine:
|
||||
// but otherwise runs without pause.
|
||||
const HalfCycles addition((cycle.operation == CPU::Z80::PartialMachineCycle::ReadOpcode) ? 2 : 0);
|
||||
const HalfCycles total_length = addition + cycle.length;
|
||||
time_since_vdp_update_ += total_length;
|
||||
vdp_ += total_length;
|
||||
time_since_ay_update_ += total_length;
|
||||
memory_slots_[0].cycles_since_update += total_length;
|
||||
memory_slots_[1].cycles_since_update += total_length;
|
||||
@@ -484,7 +490,7 @@ class ConcreteMachine:
|
||||
*cycle.value = read_pointers_[address >> 13][address & 8191];
|
||||
} else {
|
||||
int slot_hit = (paged_memory_ >> ((address >> 14) * 2)) & 3;
|
||||
memory_slots_[slot_hit].handler->run_for(memory_slots_[slot_hit].cycles_since_update.flush());
|
||||
memory_slots_[slot_hit].handler->run_for(memory_slots_[slot_hit].cycles_since_update.flush<HalfCycles>());
|
||||
*cycle.value = memory_slots_[slot_hit].handler->read(address);
|
||||
}
|
||||
break;
|
||||
@@ -495,7 +501,7 @@ class ConcreteMachine:
|
||||
int slot_hit = (paged_memory_ >> ((address >> 14) * 2)) & 3;
|
||||
if(memory_slots_[slot_hit].handler) {
|
||||
update_audio();
|
||||
memory_slots_[slot_hit].handler->run_for(memory_slots_[slot_hit].cycles_since_update.flush());
|
||||
memory_slots_[slot_hit].handler->run_for(memory_slots_[slot_hit].cycles_since_update.flush<HalfCycles>());
|
||||
memory_slots_[slot_hit].handler->write(address, *cycle.value, read_pointers_[pc_address_ >> 13] != memory_slots_[0].read_pointers[pc_address_ >> 13]);
|
||||
}
|
||||
} break;
|
||||
@@ -503,10 +509,9 @@ class ConcreteMachine:
|
||||
case CPU::Z80::PartialMachineCycle::Input:
|
||||
switch(address & 0xff) {
|
||||
case 0x98: case 0x99:
|
||||
vdp_.run_for(time_since_vdp_update_.flush());
|
||||
*cycle.value = vdp_.get_register(address);
|
||||
z80_.set_interrupt_line(vdp_.get_interrupt_line());
|
||||
time_until_interrupt_ = vdp_.get_time_until_interrupt();
|
||||
*cycle.value = vdp_->get_register(address);
|
||||
z80_.set_interrupt_line(vdp_->get_interrupt_line());
|
||||
time_until_interrupt_ = vdp_->get_time_until_interrupt();
|
||||
break;
|
||||
|
||||
case 0xa2:
|
||||
@@ -531,10 +536,9 @@ class ConcreteMachine:
|
||||
const int port = address & 0xff;
|
||||
switch(port) {
|
||||
case 0x98: case 0x99:
|
||||
vdp_.run_for(time_since_vdp_update_.flush());
|
||||
vdp_.set_register(address, *cycle.value);
|
||||
z80_.set_interrupt_line(vdp_.get_interrupt_line());
|
||||
time_until_interrupt_ = vdp_.get_time_until_interrupt();
|
||||
vdp_->set_register(address, *cycle.value);
|
||||
z80_.set_interrupt_line(vdp_->get_interrupt_line());
|
||||
time_until_interrupt_ = vdp_->get_time_until_interrupt();
|
||||
break;
|
||||
|
||||
case 0xa0: case 0xa1:
|
||||
@@ -607,7 +611,7 @@ class ConcreteMachine:
|
||||
}
|
||||
|
||||
void flush() {
|
||||
vdp_.run_for(time_since_vdp_update_.flush());
|
||||
vdp_.flush();
|
||||
update_audio();
|
||||
audio_queue_.perform();
|
||||
}
|
||||
@@ -748,7 +752,7 @@ class ConcreteMachine:
|
||||
};
|
||||
|
||||
CPU::Z80::Processor<ConcreteMachine, false, false> z80_;
|
||||
TI::TMS::TMS9918 vdp_;
|
||||
JustInTimeActor<TI::TMS::TMS9918, HalfCycles> vdp_;
|
||||
Intel::i8255::i8255<i8255PortHandler> i8255_;
|
||||
|
||||
Concurrency::DeferringAsyncTaskQueue audio_queue_;
|
||||
@@ -792,7 +796,6 @@ class ConcreteMachine:
|
||||
uint8_t scratch_[8192];
|
||||
uint8_t unpopulated_[8192];
|
||||
|
||||
HalfCycles time_since_vdp_update_;
|
||||
HalfCycles time_since_ay_update_;
|
||||
HalfCycles time_until_interrupt_;
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
#include "../KeyboardMachine.hpp"
|
||||
|
||||
#include "../../ClockReceiver/ForceInline.hpp"
|
||||
#include "../../ClockReceiver/JustInTime.hpp"
|
||||
|
||||
#include "../../Outputs/Speaker/Implementation/LowpassSpeaker.hpp"
|
||||
#include "../../Outputs/Log.hpp"
|
||||
@@ -132,7 +133,12 @@ class ConcreteMachine:
|
||||
|
||||
// Load the BIOS if relevant.
|
||||
if(has_bios()) {
|
||||
const auto roms = rom_fetcher("MasterSystem", {"bios.sms"});
|
||||
// TODO: there's probably a million other versions of the Master System BIOS; try to build a
|
||||
// CRC32 catalogue of those. So far:
|
||||
//
|
||||
// 0072ed54 = US/European BIOS 1.3
|
||||
// 48d44a13 = Japanese BIOS 2.1
|
||||
const auto roms = rom_fetcher({ {"MasterSystem", "the Master System BIOS", "bios.sms", 8*1024, { 0x0072ed54, 0x48d44a13 } } });
|
||||
if(!roms[0]) {
|
||||
// No BIOS found; attempt to boot as though it has already disabled itself.
|
||||
memory_control_ |= 0x08;
|
||||
@@ -163,16 +169,16 @@ class ConcreteMachine:
|
||||
}
|
||||
|
||||
void set_scan_target(Outputs::Display::ScanTarget *scan_target) override {
|
||||
vdp_.set_tv_standard(
|
||||
vdp_->set_tv_standard(
|
||||
(region_ == Target::Region::Europe) ?
|
||||
TI::TMS::TVStandard::PAL : TI::TMS::TVStandard::NTSC);
|
||||
time_until_debounce_ = vdp_.get_time_until_line(-1);
|
||||
time_until_debounce_ = vdp_->get_time_until_line(-1);
|
||||
|
||||
vdp_.set_scan_target(scan_target);
|
||||
vdp_->set_scan_target(scan_target);
|
||||
}
|
||||
|
||||
void set_display_type(Outputs::Display::DisplayType display_type) override {
|
||||
vdp_.set_display_type(display_type);
|
||||
vdp_->set_display_type(display_type);
|
||||
}
|
||||
|
||||
Outputs::Speaker::Speaker *get_speaker() override {
|
||||
@@ -184,7 +190,7 @@ class ConcreteMachine:
|
||||
}
|
||||
|
||||
forceinline HalfCycles perform_machine_cycle(const CPU::Z80::PartialMachineCycle &cycle) {
|
||||
time_since_vdp_update_ += cycle.length;
|
||||
vdp_ += cycle.length;
|
||||
time_since_sn76489_update_ += cycle.length;
|
||||
|
||||
if(cycle.is_terminal()) {
|
||||
@@ -228,17 +234,15 @@ class ConcreteMachine:
|
||||
*cycle.value = 0xff;
|
||||
break;
|
||||
case 0x40:
|
||||
update_video();
|
||||
*cycle.value = vdp_.get_current_line();
|
||||
*cycle.value = vdp_->get_current_line();
|
||||
break;
|
||||
case 0x41:
|
||||
*cycle.value = vdp_.get_latched_horizontal_counter();
|
||||
*cycle.value = vdp_.last_valid()->get_latched_horizontal_counter();
|
||||
break;
|
||||
case 0x80: case 0x81:
|
||||
update_video();
|
||||
*cycle.value = vdp_.get_register(address);
|
||||
z80_.set_interrupt_line(vdp_.get_interrupt_line());
|
||||
time_until_interrupt_ = vdp_.get_time_until_interrupt();
|
||||
*cycle.value = vdp_->get_register(address);
|
||||
z80_.set_interrupt_line(vdp_->get_interrupt_line());
|
||||
time_until_interrupt_ = vdp_->get_time_until_interrupt();
|
||||
break;
|
||||
case 0xc0: {
|
||||
Joystick *const joypad1 = static_cast<Joystick *>(joysticks_[0].get());
|
||||
@@ -279,8 +283,7 @@ class ConcreteMachine:
|
||||
|
||||
// Latch if either TH has newly gone to 1.
|
||||
if((new_ths^previous_ths)&new_ths) {
|
||||
update_video();
|
||||
vdp_.latch_horizontal_counter();
|
||||
vdp_->latch_horizontal_counter();
|
||||
}
|
||||
} break;
|
||||
case 0x40: case 0x41:
|
||||
@@ -288,10 +291,9 @@ class ConcreteMachine:
|
||||
sn76489_.set_register(*cycle.value);
|
||||
break;
|
||||
case 0x80: case 0x81:
|
||||
update_video();
|
||||
vdp_.set_register(address, *cycle.value);
|
||||
z80_.set_interrupt_line(vdp_.get_interrupt_line());
|
||||
time_until_interrupt_ = vdp_.get_time_until_interrupt();
|
||||
vdp_->set_register(address, *cycle.value);
|
||||
z80_.set_interrupt_line(vdp_->get_interrupt_line());
|
||||
time_until_interrupt_ = vdp_->get_time_until_interrupt();
|
||||
break;
|
||||
case 0xc0:
|
||||
LOG("TODO: [output] I/O port A/N; " << int(*cycle.value));
|
||||
@@ -326,15 +328,14 @@ class ConcreteMachine:
|
||||
time_until_debounce_ -= cycle.length;
|
||||
if(time_until_debounce_ <= HalfCycles(0)) {
|
||||
z80_.set_non_maskable_interrupt_line(pause_is_pressed_);
|
||||
update_video();
|
||||
time_until_debounce_ = vdp_.get_time_until_line(-1);
|
||||
time_until_debounce_ = vdp_->get_time_until_line(-1);
|
||||
}
|
||||
|
||||
return HalfCycles(0);
|
||||
}
|
||||
|
||||
void flush() {
|
||||
update_video();
|
||||
vdp_.flush();
|
||||
update_audio();
|
||||
audio_queue_.perform();
|
||||
}
|
||||
@@ -407,16 +408,13 @@ class ConcreteMachine:
|
||||
inline void update_audio() {
|
||||
speaker_.run_for(audio_queue_, time_since_sn76489_update_.divide_cycles(Cycles(sn76489_divider)));
|
||||
}
|
||||
inline void update_video() {
|
||||
vdp_.run_for(time_since_vdp_update_.flush());
|
||||
}
|
||||
|
||||
using Target = Analyser::Static::Sega::Target;
|
||||
Target::Model model_;
|
||||
Target::Region region_;
|
||||
Target::PagingScheme paging_scheme_;
|
||||
CPU::Z80::Processor<ConcreteMachine, false, false> z80_;
|
||||
TI::TMS::TMS9918 vdp_;
|
||||
JustInTimeActor<TI::TMS::TMS9918, HalfCycles> vdp_;
|
||||
|
||||
Concurrency::DeferringAsyncTaskQueue audio_queue_;
|
||||
TI::SN76489 sn76489_;
|
||||
@@ -426,7 +424,6 @@ class ConcreteMachine:
|
||||
Inputs::Keyboard keyboard_;
|
||||
bool reset_is_pressed_ = false, pause_is_pressed_ = false;
|
||||
|
||||
HalfCycles time_since_vdp_update_;
|
||||
HalfCycles time_since_sn76489_update_;
|
||||
HalfCycles time_until_interrupt_;
|
||||
HalfCycles time_until_debounce_;
|
||||
|
||||
23
Machines/MouseMachine.hpp
Normal file
23
Machines/MouseMachine.hpp
Normal file
@@ -0,0 +1,23 @@
|
||||
//
|
||||
// MouseMachine.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 11/06/2019.
|
||||
// Copyright © 2019 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef MouseMachine_hpp
|
||||
#define MouseMachine_hpp
|
||||
|
||||
#include "../Inputs/Mouse.hpp"
|
||||
|
||||
namespace MouseMachine {
|
||||
|
||||
class Machine {
|
||||
public:
|
||||
virtual Inputs::Mouse &get_mouse() = 0;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif /* MouseMachine_hpp */
|
||||
@@ -170,7 +170,7 @@ class VIAPortHandler: public MOS::MOS6522::IRQDelegatePortHandler {
|
||||
/*!
|
||||
Advances time. This class manages the AY's concept of time to permit updating-on-demand.
|
||||
*/
|
||||
inline void run_for(const Cycles cycles) {
|
||||
inline void run_for(const HalfCycles cycles) {
|
||||
cycles_since_ay_update_ += cycles;
|
||||
}
|
||||
|
||||
@@ -182,11 +182,11 @@ class VIAPortHandler: public MOS::MOS6522::IRQDelegatePortHandler {
|
||||
|
||||
private:
|
||||
void update_ay() {
|
||||
speaker_.run_for(audio_queue_, cycles_since_ay_update_.flush());
|
||||
speaker_.run_for(audio_queue_, cycles_since_ay_update_.flush<Cycles>());
|
||||
}
|
||||
bool ay_bdir_ = false;
|
||||
bool ay_bc1_ = false;
|
||||
Cycles cycles_since_ay_update_;
|
||||
HalfCycles cycles_since_ay_update_;
|
||||
|
||||
Concurrency::DeferringAsyncTaskQueue &audio_queue_;
|
||||
GI::AY38910::AY38910 &ay8910_;
|
||||
@@ -228,19 +228,34 @@ template <Analyser::Static::Oric::Target::DiskInterface disk_interface> class Co
|
||||
diskii_.set_clocking_hint_observer(this);
|
||||
}
|
||||
|
||||
std::vector<std::string> rom_names = {"colour.rom"};
|
||||
const std::string machine_name = "Oric";
|
||||
std::vector<ROMMachine::ROM> rom_names = { {machine_name, "the Oric colour ROM", "colour.rom", 128, 0xd50fca65} };
|
||||
switch(target.rom) {
|
||||
case Analyser::Static::Oric::Target::ROM::BASIC10: rom_names.push_back("basic10.rom"); break;
|
||||
case Analyser::Static::Oric::Target::ROM::BASIC11: rom_names.push_back("basic11.rom"); break;
|
||||
case Analyser::Static::Oric::Target::ROM::Pravetz: rom_names.push_back("pravetz.rom"); break;
|
||||
case Analyser::Static::Oric::Target::ROM::BASIC10:
|
||||
rom_names.emplace_back(machine_name, "Oric BASIC 1.0", "basic10.rom", 16*1024, 0xf18710b4);
|
||||
break;
|
||||
case Analyser::Static::Oric::Target::ROM::BASIC11:
|
||||
rom_names.emplace_back(machine_name, "Oric BASIC 1.1", "basic11.rom", 16*1024, 0xc3a92bef);
|
||||
break;
|
||||
case Analyser::Static::Oric::Target::ROM::Pravetz:
|
||||
rom_names.emplace_back(machine_name, "Pravetz BASIC", "pravetz.rom", 16*1024, 0x58079502);
|
||||
break;
|
||||
}
|
||||
size_t diskii_state_machine_index = 0;
|
||||
switch(disk_interface) {
|
||||
default: break;
|
||||
case Analyser::Static::Oric::Target::DiskInterface::Microdisc: rom_names.push_back("microdisc.rom"); break;
|
||||
case Analyser::Static::Oric::Target::DiskInterface::Pravetz: rom_names.push_back("8dos.rom"); break;
|
||||
case Analyser::Static::Oric::Target::DiskInterface::Microdisc:
|
||||
rom_names.emplace_back(machine_name, "the ORIC Microdisc ROM", "microdisc.rom", 8*1024, 0xa9664a9c);
|
||||
break;
|
||||
case Analyser::Static::Oric::Target::DiskInterface::Pravetz:
|
||||
rom_names.emplace_back(machine_name, "the 8DOS boot ROM", "8dos.rom", 512, 0x49a74c06);
|
||||
// These ROM details are coupled with those in the DiskIICard.
|
||||
diskii_state_machine_index = rom_names.size();
|
||||
rom_names.push_back({"DiskII", "the Disk II 16-sector state machine ROM", "state-machine-16.rom", 256, { 0x9796a238, 0xb72a2c70 }});
|
||||
break;
|
||||
}
|
||||
|
||||
const auto roms = rom_fetcher("Oric", rom_names);
|
||||
const auto roms = rom_fetcher(rom_names);
|
||||
|
||||
for(std::size_t index = 0; index < roms.size(); ++index) {
|
||||
if(!roms[index]) {
|
||||
@@ -261,11 +276,7 @@ template <Analyser::Static::Oric::Target::DiskInterface disk_interface> class Co
|
||||
pravetz_rom_ = std::move(*roms[2]);
|
||||
pravetz_rom_.resize(512);
|
||||
|
||||
auto state_machine_rom = rom_fetcher("DiskII", {"state-machine-16.rom"});
|
||||
if(!state_machine_rom[0]) {
|
||||
throw ROMMachine::Error::MissingROMs;
|
||||
}
|
||||
diskii_.set_state_machine(*state_machine_rom[0]);
|
||||
diskii_.set_state_machine(*roms[diskii_state_machine_index]);
|
||||
} break;
|
||||
}
|
||||
|
||||
@@ -434,7 +445,6 @@ template <Analyser::Static::Oric::Target::DiskInterface disk_interface> class Co
|
||||
}
|
||||
|
||||
via_.run_for(Cycles(1));
|
||||
via_port_handler_.run_for(Cycles(1));
|
||||
tape_player_.run_for(Cycles(1));
|
||||
switch(disk_interface) {
|
||||
default: break;
|
||||
@@ -456,7 +466,7 @@ template <Analyser::Static::Oric::Target::DiskInterface disk_interface> class Co
|
||||
|
||||
forceinline void flush() {
|
||||
update_video();
|
||||
via_port_handler_.flush();
|
||||
via_.flush();
|
||||
flush_diskii();
|
||||
}
|
||||
|
||||
@@ -575,7 +585,7 @@ template <Analyser::Static::Oric::Target::DiskInterface disk_interface> class Co
|
||||
uint8_t ram_[65536];
|
||||
Cycles cycles_since_video_update_;
|
||||
inline void update_video() {
|
||||
video_output_.run_for(cycles_since_video_update_.flush());
|
||||
video_output_.run_for(cycles_since_video_update_.flush<Cycles>());
|
||||
}
|
||||
|
||||
// ROM bookkeeping
|
||||
@@ -607,7 +617,7 @@ template <Analyser::Static::Oric::Target::DiskInterface disk_interface> class Co
|
||||
Apple::DiskII diskii_;
|
||||
Cycles cycles_since_diskii_update_;
|
||||
void flush_diskii() {
|
||||
diskii_.run_for(cycles_since_diskii_update_.flush());
|
||||
diskii_.run_for(cycles_since_diskii_update_.flush<Cycles>());
|
||||
}
|
||||
std::vector<uint8_t> pravetz_rom_;
|
||||
std::size_t pravetz_rom_base_pointer_ = 0;
|
||||
|
||||
@@ -16,15 +16,41 @@
|
||||
|
||||
namespace ROMMachine {
|
||||
|
||||
/*!
|
||||
Describes a ROM image; this term is used in this emulator strictly in the sense of firmware —
|
||||
system software that is an inherent part of a machine.
|
||||
*/
|
||||
struct ROM {
|
||||
/// The machine with which this ROM is associated, in a form that is safe for using as
|
||||
/// part of a file name.
|
||||
std::string machine_name;
|
||||
/// A descriptive name for this ROM, suitable for use in a bullet-point list, a bracket
|
||||
/// clause, etc, e.g. "the Electron MOS 1.0".
|
||||
std::string descriptive_name;
|
||||
/// An idiomatic file name for this ROM, e.g. "os10.rom".
|
||||
std::string file_name;
|
||||
/// The expected size of this ROM in bytes, e.g. 32768.
|
||||
size_t size = 0;
|
||||
/// CRC32s for all known acceptable copies of this ROM; intended to allow a host platform
|
||||
/// to test user-provided ROMs of unknown provenance. **Not** intended to be used
|
||||
/// to exclude ROMs where the user's intent is otherwise clear.
|
||||
std::vector<uint32_t> crc32s;
|
||||
|
||||
ROM(std::string machine_name, std::string descriptive_name, std::string file_name, size_t size, uint32_t crc32) :
|
||||
machine_name(machine_name), descriptive_name(descriptive_name), file_name(file_name), size(size), crc32s({crc32}) {}
|
||||
ROM(std::string machine_name, std::string descriptive_name, std::string file_name, size_t size, std::initializer_list<uint32_t> crc32s) :
|
||||
machine_name(machine_name), descriptive_name(descriptive_name), file_name(file_name), size(size), crc32s(crc32s) {}
|
||||
};
|
||||
|
||||
/*!
|
||||
Defines the signature for a function that must be supplied by the host environment in order to give machines
|
||||
a route for fetching any system ROMs they might need.
|
||||
|
||||
The caller will supply the idiomatic name of the machine plus a vector of the names of ROM files that it expects
|
||||
to be present. The recevier should return a vector of unique_ptrs that either contain the contents of the
|
||||
ROM from @c names that corresponds by index, or else are the nullptr
|
||||
The caller will supply a vector of the names of ROM files that it would like to inspect. The recevier should
|
||||
return a vector of unique_ptrs that either contain the contents of the ROM from @c names that corresponds by
|
||||
index, or else are @c nullptr.
|
||||
*/
|
||||
typedef std::function<std::vector<std::unique_ptr<std::vector<uint8_t>>>(const std::string &machine, const std::vector<std::string> &names)> ROMFetcher;
|
||||
typedef std::function<std::vector<std::unique_ptr<std::vector<uint8_t>>>(const std::vector<ROM> &roms)> ROMFetcher;
|
||||
|
||||
enum class Error {
|
||||
MissingROMs
|
||||
|
||||
@@ -9,7 +9,8 @@
|
||||
#include "MachineForTarget.hpp"
|
||||
|
||||
#include "../AmstradCPC/AmstradCPC.hpp"
|
||||
#include "../AppleII/AppleII.hpp"
|
||||
#include "../Apple/AppleII/AppleII.hpp"
|
||||
#include "../Apple/Macintosh/Macintosh.hpp"
|
||||
#include "../Atari2600/Atari2600.hpp"
|
||||
#include "../ColecoVision/ColecoVision.hpp"
|
||||
#include "../Commodore/Vic-20/Vic20.hpp"
|
||||
@@ -33,14 +34,15 @@ namespace {
|
||||
#define Bind(m) BindD(m, m)
|
||||
switch(target->machine) {
|
||||
Bind(AmstradCPC)
|
||||
Bind(AppleII)
|
||||
BindD(Apple::II, AppleII)
|
||||
BindD(Apple::Macintosh, Macintosh)
|
||||
Bind(Atari2600)
|
||||
BindD(Coleco::Vision, ColecoVision)
|
||||
BindD(Commodore::Vic20, Vic20)
|
||||
Bind(Electron)
|
||||
BindD(Sega::MasterSystem, MasterSystem)
|
||||
Bind(MSX)
|
||||
Bind(Oric)
|
||||
BindD(Commodore::Vic20, Vic20)
|
||||
BindD(Sega::MasterSystem, MasterSystem)
|
||||
Bind(ZX8081)
|
||||
|
||||
default:
|
||||
@@ -103,6 +105,7 @@ std::string Machine::ShortNameForTargetMachine(const Analyser::Machine machine)
|
||||
case Analyser::Machine::Atari2600: return "Atari2600";
|
||||
case Analyser::Machine::ColecoVision: return "ColecoVision";
|
||||
case Analyser::Machine::Electron: return "Electron";
|
||||
case Analyser::Machine::Macintosh: return "Macintosh";
|
||||
case Analyser::Machine::MSX: return "MSX";
|
||||
case Analyser::Machine::Oric: return "Oric";
|
||||
case Analyser::Machine::Vic20: return "Vic20";
|
||||
@@ -119,6 +122,7 @@ std::string Machine::LongNameForTargetMachine(Analyser::Machine machine) {
|
||||
case Analyser::Machine::Atari2600: return "Atari 2600";
|
||||
case Analyser::Machine::ColecoVision: return "ColecoVision";
|
||||
case Analyser::Machine::Electron: return "Acorn Electron";
|
||||
case Analyser::Machine::Macintosh: return "Apple Macintosh";
|
||||
case Analyser::Machine::MSX: return "MSX";
|
||||
case Analyser::Machine::Oric: return "Oric";
|
||||
case Analyser::Machine::Vic20: return "Vic 20";
|
||||
@@ -132,7 +136,7 @@ std::map<std::string, std::vector<std::unique_ptr<Configurable::Option>>> Machin
|
||||
std::map<std::string, std::vector<std::unique_ptr<Configurable::Option>>> options;
|
||||
|
||||
options.emplace(std::make_pair(LongNameForTargetMachine(Analyser::Machine::AmstradCPC), AmstradCPC::get_options()));
|
||||
options.emplace(std::make_pair(LongNameForTargetMachine(Analyser::Machine::AppleII), AppleII::get_options()));
|
||||
options.emplace(std::make_pair(LongNameForTargetMachine(Analyser::Machine::AppleII), Apple::II::get_options()));
|
||||
options.emplace(std::make_pair(LongNameForTargetMachine(Analyser::Machine::ColecoVision), Coleco::Vision::get_options()));
|
||||
options.emplace(std::make_pair(LongNameForTargetMachine(Analyser::Machine::Electron), Electron::get_options()));
|
||||
options.emplace(std::make_pair(LongNameForTargetMachine(Analyser::Machine::MSX), MSX::get_options()));
|
||||
|
||||
@@ -23,6 +23,10 @@ void Memory::Fuzz(uint8_t *buffer, std::size_t size) {
|
||||
}
|
||||
}
|
||||
|
||||
void Memory::Fuzz(uint16_t *buffer, std::size_t size) {
|
||||
Fuzz(reinterpret_cast<uint8_t *>(buffer), size * sizeof(uint16_t));
|
||||
}
|
||||
|
||||
void Memory::Fuzz(std::vector<uint8_t> &buffer) {
|
||||
Fuzz(buffer.data(), buffer.size());
|
||||
}
|
||||
|
||||
@@ -18,7 +18,10 @@ namespace Memory {
|
||||
/// Stores @c size random bytes from @c buffer onwards.
|
||||
void Fuzz(uint8_t *buffer, std::size_t size);
|
||||
|
||||
// Replaces all existing vector contents with random bytes.
|
||||
/// Stores @c size random 16-bit words from @c buffer onwards.
|
||||
void Fuzz(uint16_t *buffer, std::size_t size);
|
||||
|
||||
/// Replaces all existing vector contents with random bytes.
|
||||
void Fuzz(std::vector<uint8_t> &buffer);
|
||||
|
||||
}
|
||||
|
||||
15
Machines/Utility/MemoryPacker.cpp
Normal file
15
Machines/Utility/MemoryPacker.cpp
Normal file
@@ -0,0 +1,15 @@
|
||||
//
|
||||
// MemoryPacker.cpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 03/05/2019.
|
||||
// Copyright © 2019 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#include "MemoryPacker.hpp"
|
||||
|
||||
void Memory::PackBigEndian16(const std::vector<uint8_t> &source, uint16_t *target) {
|
||||
for(std::size_t c = 0; c < source.size(); c += 2) {
|
||||
target[c >> 1] = uint16_t(source[c] << 8) | uint16_t(source[c+1]);
|
||||
}
|
||||
}
|
||||
24
Machines/Utility/MemoryPacker.hpp
Normal file
24
Machines/Utility/MemoryPacker.hpp
Normal file
@@ -0,0 +1,24 @@
|
||||
//
|
||||
// MemoryPacker.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 03/05/2019.
|
||||
// Copyright © 2019 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef MemoryPacker_hpp
|
||||
#define MemoryPacker_hpp
|
||||
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
|
||||
namespace Memory {
|
||||
|
||||
/*!
|
||||
Copies the bytes from @c source into @c target, interpreting them
|
||||
as big-endian 16-bit data.
|
||||
*/
|
||||
void PackBigEndian16(const std::vector<uint8_t> &source, uint16_t *target);
|
||||
|
||||
}
|
||||
#endif /* MemoryPacker_hpp */
|
||||
@@ -45,6 +45,10 @@ template<typename T> class TypedDynamicMachine: public ::Machine::DynamicMachine
|
||||
return get<KeyboardMachine::Machine>();
|
||||
}
|
||||
|
||||
MouseMachine::Machine *mouse_machine() override {
|
||||
return get<MouseMachine::Machine>();
|
||||
}
|
||||
|
||||
Configurable::Device *configurable_device() override {
|
||||
return get<Configurable::Device>();
|
||||
}
|
||||
|
||||
@@ -76,7 +76,11 @@ template<bool is_zx81> class ConcreteMachine:
|
||||
clear_all_keys();
|
||||
|
||||
const bool use_zx81_rom = target.is_ZX81 || target.ZX80_uses_ZX81_ROM;
|
||||
const auto roms = rom_fetcher("ZX8081", { use_zx81_rom ? "zx81.rom" : "zx80.rom" });
|
||||
const auto roms =
|
||||
use_zx81_rom ?
|
||||
rom_fetcher({ {"ZX8081", "the ZX81 BASIC ROM", "zx81.rom", 8 * 1024, 0x4b1dd6eb} }) :
|
||||
rom_fetcher({ {"ZX8081", "the ZX80 BASIC ROM", "zx80.rom", 4 * 1024, 0x4c7fc597} });
|
||||
|
||||
if(!roms[0]) throw ROMMachine::Error::MissingROMs;
|
||||
|
||||
rom_ = std::move(*roms[0]);
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,9 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Workspace
|
||||
version = "1.0">
|
||||
<FileRef
|
||||
location = "group:/Users/thomasharte/Projects/CLK/OSBindings/Mac/Clock SignalTests/68000ArithmeticTests.mm">
|
||||
</FileRef>
|
||||
<FileRef
|
||||
location = "self:Clock Signal.xcodeproj">
|
||||
</FileRef>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "0930"
|
||||
LastUpgradeVersion = "1030"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
@@ -26,6 +26,7 @@
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
disableMainThreadChecker = "YES"
|
||||
codeCoverageEnabled = "YES"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||
<Testables>
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="14113" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
|
||||
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="14490.70" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
|
||||
<dependencies>
|
||||
<deployment identifier="macosx"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="14113"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="14490.70"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<objects>
|
||||
@@ -14,7 +14,7 @@
|
||||
</customObject>
|
||||
<customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
|
||||
<customObject id="-3" userLabel="Application" customClass="NSObject"/>
|
||||
<window title="Window" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" oneShot="NO" releasedWhenClosed="NO" showsToolbarButton="NO" visibleAtLaunch="NO" animationBehavior="default" id="xOd-HO-29H" userLabel="Window">
|
||||
<window title="Window" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" releasedWhenClosed="NO" visibleAtLaunch="NO" animationBehavior="default" id="xOd-HO-29H" userLabel="Window">
|
||||
<windowStyleMask key="styleMask" titled="YES" closable="YES" miniaturizable="YES" resizable="YES"/>
|
||||
<windowCollectionBehavior key="collectionBehavior" fullScreenPrimary="YES"/>
|
||||
<windowPositionMask key="initialPositionMask" leftStrut="YES" rightStrut="YES" topStrut="YES" bottomStrut="YES"/>
|
||||
@@ -25,7 +25,7 @@
|
||||
<rect key="frame" x="0.0" y="0.0" width="600" height="450"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<openGLView hidden="YES" useAuxiliaryDepthBufferStencil="NO" allowOffline="YES" wantsBestResolutionOpenGLSurface="YES" translatesAutoresizingMaskIntoConstraints="NO" id="DEG-fq-cjd" customClass="CSOpenGLView">
|
||||
<openGLView hidden="YES" wantsLayer="YES" useAuxiliaryDepthBufferStencil="NO" allowOffline="YES" wantsBestResolutionOpenGLSurface="YES" translatesAutoresizingMaskIntoConstraints="NO" id="DEG-fq-cjd" customClass="CSOpenGLView">
|
||||
<rect key="frame" x="0.0" y="0.0" width="600" height="450"/>
|
||||
</openGLView>
|
||||
</subviews>
|
||||
@@ -36,6 +36,11 @@
|
||||
<constraint firstItem="DEG-fq-cjd" firstAttribute="width" secondItem="gIp-Ho-8D9" secondAttribute="width" id="mYS-bH-DST"/>
|
||||
</constraints>
|
||||
</view>
|
||||
<userDefinedRuntimeAttributes>
|
||||
<userDefinedRuntimeAttribute type="color" keyPath="backgroundColor">
|
||||
<color key="value" white="0.0" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
</userDefinedRuntimeAttribute>
|
||||
</userDefinedRuntimeAttributes>
|
||||
<connections>
|
||||
<outlet property="delegate" destination="-2" id="0bl-1N-x8E"/>
|
||||
<outlet property="initialFirstResponder" destination="DEG-fq-cjd" id="9RI-Kx-QeN"/>
|
||||
|
||||
@@ -10,10 +10,13 @@
|
||||
|
||||
#import "CSStaticAnalyser.h"
|
||||
|
||||
#import "CSOpenGLView.h"
|
||||
#import "CSAudioQueue.h"
|
||||
#import "CSOpenGLView.h"
|
||||
#import "CSROMReceiverView.h"
|
||||
|
||||
#import "CSBestEffortUpdater.h"
|
||||
#import "CSJoystickManager.h"
|
||||
|
||||
#import "NSData+CRC32.h"
|
||||
|
||||
#include "KeyCodes.h"
|
||||
|
||||
@@ -16,109 +16,76 @@ class MachineDocument:
|
||||
CSOpenGLViewDelegate,
|
||||
CSOpenGLViewResponderDelegate,
|
||||
CSBestEffortUpdaterDelegate,
|
||||
CSAudioQueueDelegate
|
||||
CSAudioQueueDelegate,
|
||||
CSROMReciverViewDelegate
|
||||
{
|
||||
fileprivate let actionLock = NSLock()
|
||||
fileprivate let drawLock = NSLock()
|
||||
fileprivate let bestEffortLock = NSLock()
|
||||
// MARK: - Mutual Exclusion.
|
||||
|
||||
var machine: CSMachine!
|
||||
var name: String! {
|
||||
get {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
var optionsPanelNibName: String?
|
||||
/// Ensures exclusive access between calls to self.machine.run and close().
|
||||
private let actionLock = NSLock()
|
||||
/// Ensures exclusive access between calls to machine.updateView and machine.drawView, and close().
|
||||
private let drawLock = NSLock()
|
||||
/// Ensures exclusive access to the best-effort updater.
|
||||
private let bestEffortLock = NSLock()
|
||||
|
||||
func aspectRatio() -> NSSize {
|
||||
// MARK: - Machine details.
|
||||
|
||||
/// A description of the machine this document should represent once fully set up.
|
||||
private var machineDescription: CSStaticAnalyser?
|
||||
|
||||
/// The active machine, following its successful creation.
|
||||
private var machine: CSMachine!
|
||||
|
||||
/// @returns the appropriate window content aspect ratio for this @c self.machine.
|
||||
private func aspectRatio() -> NSSize {
|
||||
return NSSize(width: 4.0, height: 3.0)
|
||||
}
|
||||
|
||||
/// The output audio queue, if any.
|
||||
private var audioQueue: CSAudioQueue!
|
||||
|
||||
/// The best-effort updater.
|
||||
private var bestEffortUpdater: CSBestEffortUpdater?
|
||||
|
||||
// MARK: - Main NIB connections.
|
||||
|
||||
/// The OpenGL view to receive this machine's display.
|
||||
@IBOutlet weak var openGLView: CSOpenGLView!
|
||||
|
||||
/// The options panel, if any.
|
||||
@IBOutlet var optionsPanel: MachinePanel!
|
||||
|
||||
/// An action to display the options panel, if there is one.
|
||||
@IBAction func showOptions(_ sender: AnyObject!) {
|
||||
optionsPanel?.setIsVisible(true)
|
||||
}
|
||||
|
||||
/// The activity panel, if one is deemed appropriate.
|
||||
@IBOutlet var activityPanel: NSPanel!
|
||||
|
||||
/// An action to display the activity panel, if there is one.
|
||||
@IBAction func showActivity(_ sender: AnyObject!) {
|
||||
activityPanel.setIsVisible(true)
|
||||
}
|
||||
|
||||
fileprivate var audioQueue: CSAudioQueue! = nil
|
||||
fileprivate var bestEffortUpdater: CSBestEffortUpdater?
|
||||
// MARK: - NSDocument Overrides and NSWindowDelegate methods.
|
||||
|
||||
/// Links this class to the MachineDocument NIB.
|
||||
override var windowNibName: NSNib.Name? {
|
||||
return NSNib.Name(rawValue: "MachineDocument")
|
||||
return "MachineDocument"
|
||||
}
|
||||
|
||||
override func windowControllerDidLoadNib(_ aController: NSWindowController) {
|
||||
super.windowControllerDidLoadNib(aController)
|
||||
aController.window?.contentAspectRatio = self.aspectRatio()
|
||||
setupMachineOutput()
|
||||
convenience init(type typeName: String) throws {
|
||||
self.init()
|
||||
self.fileType = typeName
|
||||
}
|
||||
|
||||
// Attempting to show a sheet before the window is visible (such as when the NIB is loaded) results in
|
||||
// a sheet mysteriously floating on its own. For now, use windowDidUpdate as a proxy to know that the window
|
||||
// is visible, though it's a little premature.
|
||||
func windowDidUpdate(_ notification: Notification) {
|
||||
if self.shouldShowNewMachinePanel {
|
||||
self.shouldShowNewMachinePanel = false
|
||||
Bundle.main.loadNibNamed(NSNib.Name(rawValue: "MachinePicker"), owner: self, topLevelObjects: nil)
|
||||
self.machinePicker?.establishStoredOptions()
|
||||
self.windowControllers[0].window?.beginSheet(self.machinePickerPanel!, completionHandler: nil)
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate func setupMachineOutput() {
|
||||
if let machine = self.machine, let openGLView = self.openGLView {
|
||||
// establish the output aspect ratio and audio
|
||||
let aspectRatio = self.aspectRatio()
|
||||
openGLView.perform(glContext: {
|
||||
machine.setView(openGLView, aspectRatio: Float(aspectRatio.width / aspectRatio.height))
|
||||
})
|
||||
|
||||
// attach an options panel if one is available
|
||||
if let optionsPanelNibName = self.optionsPanelNibName {
|
||||
Bundle.main.loadNibNamed(NSNib.Name(rawValue: optionsPanelNibName), owner: self, topLevelObjects: nil)
|
||||
self.optionsPanel.machine = machine
|
||||
self.optionsPanel?.establishStoredOptions()
|
||||
showOptions(self)
|
||||
}
|
||||
|
||||
machine.delegate = self
|
||||
self.bestEffortUpdater = CSBestEffortUpdater()
|
||||
|
||||
// callbacks from the OpenGL may come on a different thread, immediately following the .delegate set;
|
||||
// hence the full setup of the best-effort updater prior to setting self as a delegate
|
||||
openGLView.delegate = self
|
||||
openGLView.responderDelegate = self
|
||||
|
||||
setupAudioQueueClockRate()
|
||||
|
||||
// bring OpenGL view-holding window on top of the options panel and show the content
|
||||
openGLView.isHidden = false
|
||||
openGLView.window!.makeKeyAndOrderFront(self)
|
||||
openGLView.window!.makeFirstResponder(openGLView)
|
||||
|
||||
// start accepting best effort updates
|
||||
self.bestEffortUpdater!.delegate = self
|
||||
}
|
||||
}
|
||||
|
||||
func machineSpeakerDidChangeInputClock(_ machine: CSMachine) {
|
||||
setupAudioQueueClockRate()
|
||||
}
|
||||
|
||||
fileprivate func setupAudioQueueClockRate() {
|
||||
// establish and provide the audio queue, taking advice as to an appropriate sampling rate
|
||||
let maximumSamplingRate = CSAudioQueue.preferredSamplingRate()
|
||||
let selectedSamplingRate = self.machine.idealSamplingRate(from: NSRange(location: 0, length: NSInteger(maximumSamplingRate)))
|
||||
if selectedSamplingRate > 0 {
|
||||
audioQueue = CSAudioQueue(samplingRate: Float64(selectedSamplingRate))
|
||||
audioQueue.delegate = self
|
||||
self.machine.audioQueue = self.audioQueue
|
||||
self.machine.setAudioSamplingRate(selectedSamplingRate, bufferSize:audioQueue.preferredBufferSize)
|
||||
override func read(from url: URL, ofType typeName: String) throws {
|
||||
if let analyser = CSStaticAnalyser(fileAt: url) {
|
||||
self.displayName = analyser.displayName
|
||||
self.configureAs(analyser)
|
||||
} else {
|
||||
throw NSError(domain: "MachineDocument", code: -1, userInfo: nil)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -148,56 +115,135 @@ class MachineDocument:
|
||||
super.close()
|
||||
}
|
||||
|
||||
// MARK: configuring
|
||||
override func data(ofType typeName: String) throws -> Data {
|
||||
throw NSError(domain: NSOSStatusErrorDomain, code: unimpErr, userInfo: nil)
|
||||
}
|
||||
|
||||
override func windowControllerDidLoadNib(_ aController: NSWindowController) {
|
||||
super.windowControllerDidLoadNib(aController)
|
||||
aController.window?.contentAspectRatio = self.aspectRatio()
|
||||
}
|
||||
|
||||
private var missingROMs: [CSMissingROM] = []
|
||||
func configureAs(_ analysis: CSStaticAnalyser) {
|
||||
if let machine = CSMachine(analyser: analysis) {
|
||||
self.machineDescription = analysis
|
||||
|
||||
let missingROMs = NSMutableArray()
|
||||
if let machine = CSMachine(analyser: analysis, missingROMs: missingROMs) {
|
||||
self.machine = machine
|
||||
self.optionsPanelNibName = analysis.optionsPanelNibName
|
||||
setupMachineOutput()
|
||||
setupActivityDisplay()
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate var shouldShowNewMachinePanel = false
|
||||
override func read(from url: URL, ofType typeName: String) throws {
|
||||
if let analyser = CSStaticAnalyser(fileAt: url) {
|
||||
self.displayName = analyser.displayName
|
||||
self.configureAs(analyser)
|
||||
} else {
|
||||
throw NSError(domain: "MachineDocument", code: -1, userInfo: nil)
|
||||
// Store the selected machine and list of missing ROMs, and
|
||||
// show the missing ROMs dialogue.
|
||||
self.missingROMs = []
|
||||
for untypedMissingROM in missingROMs {
|
||||
self.missingROMs.append(untypedMissingROM as! CSMissingROM)
|
||||
}
|
||||
|
||||
requestRoms()
|
||||
}
|
||||
}
|
||||
|
||||
convenience init(type typeName: String) throws {
|
||||
self.init()
|
||||
self.fileType = typeName
|
||||
self.shouldShowNewMachinePanel = true
|
||||
enum InteractionMode {
|
||||
case notStarted, showingMachinePicker, showingROMRequester, showingMachine
|
||||
}
|
||||
private var interactionMode: InteractionMode = .notStarted
|
||||
|
||||
// MARK: the pasteboard
|
||||
func paste(_ sender: Any) {
|
||||
let pasteboard = NSPasteboard.general
|
||||
if let string = pasteboard.string(forType: .string) {
|
||||
self.machine.paste(string)
|
||||
// Attempting to show a sheet before the window is visible (such as when the NIB is loaded) results in
|
||||
// a sheet mysteriously floating on its own. For now, use windowDidUpdate as a proxy to know that the window
|
||||
// is visible, though it's a little premature.
|
||||
func windowDidUpdate(_ notification: Notification) {
|
||||
// If an interaction mode is not yet in effect, pick the proper one and display the relevant thing.
|
||||
if self.interactionMode == .notStarted {
|
||||
// If a full machine exists, just continue showing it.
|
||||
if self.machine != nil {
|
||||
self.interactionMode = .showingMachine
|
||||
setupMachineOutput()
|
||||
return
|
||||
}
|
||||
|
||||
// If a machine has been picked but is not showing, there must be ROMs missing.
|
||||
if self.machineDescription != nil {
|
||||
self.interactionMode = .showingROMRequester
|
||||
requestRoms()
|
||||
return
|
||||
}
|
||||
|
||||
// If a machine hasn't even been picked yet, show the machine picker.
|
||||
self.interactionMode = .showingMachinePicker
|
||||
Bundle.main.loadNibNamed("MachinePicker", owner: self, topLevelObjects: nil)
|
||||
self.machinePicker?.establishStoredOptions()
|
||||
self.windowControllers[0].window?.beginSheet(self.machinePickerPanel!, completionHandler: nil)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: CSBestEffortUpdaterDelegate
|
||||
final func bestEffortUpdater(_ bestEffortUpdater: CSBestEffortUpdater!, runForInterval duration: TimeInterval, didSkipPreviousUpdate: Bool) {
|
||||
if actionLock.try() {
|
||||
self.machine.run(forInterval: duration)
|
||||
actionLock.unlock()
|
||||
// MARK: - Connections Between Machine and the Outside World
|
||||
|
||||
private func setupMachineOutput() {
|
||||
if let machine = self.machine, let openGLView = self.openGLView {
|
||||
// Establish the output aspect ratio and audio.
|
||||
let aspectRatio = self.aspectRatio()
|
||||
openGLView.perform(glContext: {
|
||||
machine.setView(openGLView, aspectRatio: Float(aspectRatio.width / aspectRatio.height))
|
||||
})
|
||||
|
||||
// Attach an options panel if one is available.
|
||||
if let optionsPanelNibName = self.machineDescription?.optionsPanelNibName {
|
||||
Bundle.main.loadNibNamed(optionsPanelNibName, owner: self, topLevelObjects: nil)
|
||||
self.optionsPanel.machine = machine
|
||||
self.optionsPanel?.establishStoredOptions()
|
||||
showOptions(self)
|
||||
}
|
||||
|
||||
machine.delegate = self
|
||||
self.bestEffortUpdater = CSBestEffortUpdater()
|
||||
|
||||
// Callbacks from the OpenGL may come on a different thread, immediately following the .delegate set;
|
||||
// hence the full setup of the best-effort updater prior to setting self as a delegate.
|
||||
openGLView.delegate = self
|
||||
openGLView.responderDelegate = self
|
||||
|
||||
// If this machine has a mouse, enable mouse capture.
|
||||
openGLView.shouldCaptureMouse = machine.hasMouse
|
||||
|
||||
setupAudioQueueClockRate()
|
||||
|
||||
// Bring OpenGL view-holding window on top of the options panel and show the content.
|
||||
openGLView.isHidden = false
|
||||
openGLView.window!.makeKeyAndOrderFront(self)
|
||||
openGLView.window!.makeFirstResponder(openGLView)
|
||||
|
||||
// Start accepting best effort updates.
|
||||
self.bestEffortUpdater!.delegate = self
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: CSAudioQueueDelegate
|
||||
func machineSpeakerDidChangeInputClock(_ machine: CSMachine) {
|
||||
setupAudioQueueClockRate()
|
||||
}
|
||||
|
||||
private func setupAudioQueueClockRate() {
|
||||
// establish and provide the audio queue, taking advice as to an appropriate sampling rate
|
||||
let maximumSamplingRate = CSAudioQueue.preferredSamplingRate()
|
||||
let selectedSamplingRate = self.machine.idealSamplingRate(from: NSRange(location: 0, length: NSInteger(maximumSamplingRate)))
|
||||
if selectedSamplingRate > 0 {
|
||||
audioQueue = CSAudioQueue(samplingRate: Float64(selectedSamplingRate))
|
||||
audioQueue.delegate = self
|
||||
self.machine.audioQueue = self.audioQueue
|
||||
self.machine.setAudioSamplingRate(selectedSamplingRate, bufferSize:audioQueue.preferredBufferSize)
|
||||
}
|
||||
}
|
||||
|
||||
/// Responds to the CSAudioQueueDelegate dry-queue warning message by requesting a machine update.
|
||||
final func audioQueueIsRunningDry(_ audioQueue: CSAudioQueue) {
|
||||
bestEffortLock.lock()
|
||||
bestEffortUpdater?.update()
|
||||
bestEffortLock.unlock()
|
||||
}
|
||||
|
||||
// MARK: CSOpenGLViewDelegate
|
||||
/// Responds to the CSOpenGLViewDelegate redraw message by requesting a machine update if this is a timed
|
||||
/// request, and ordering a redraw regardless of the motivation.
|
||||
final func openGLViewRedraw(_ view: CSOpenGLView, event redrawEvent: CSOpenGLViewRedrawEvent) {
|
||||
if redrawEvent == .timer {
|
||||
bestEffortLock.lock()
|
||||
@@ -218,7 +264,27 @@ class MachineDocument:
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: Runtime media insertion.
|
||||
/// Responds to CSBestEffortUpdaterDelegate update message by running the machine.
|
||||
final func bestEffortUpdater(_ bestEffortUpdater: CSBestEffortUpdater!, runForInterval duration: TimeInterval, didSkipPreviousUpdate: Bool) {
|
||||
if let machine = self.machine, actionLock.try() {
|
||||
machine.run(forInterval: duration)
|
||||
actionLock.unlock()
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Pasteboard Forwarding.
|
||||
|
||||
/// Forwards any text currently on the pasteboard into the active machine.
|
||||
func paste(_ sender: Any) {
|
||||
let pasteboard = NSPasteboard.general
|
||||
if let string = pasteboard.string(forType: .string), let machine = self.machine {
|
||||
machine.paste(string)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Runtime Media Insertion.
|
||||
|
||||
/// Delegate message to receive drag and drop files.
|
||||
final func openGLView(_ view: CSOpenGLView, didReceiveFileAt URL: URL) {
|
||||
let mediaSet = CSMediaSet(fileAt: URL)
|
||||
if let mediaSet = mediaSet {
|
||||
@@ -226,6 +292,8 @@ class MachineDocument:
|
||||
}
|
||||
}
|
||||
|
||||
/// Action for the insert menu command; displays an NSOpenPanel and then segues into the same process
|
||||
/// as if a file had been received via drag and drop.
|
||||
@IBAction final func insertMedia(_ sender: AnyObject!) {
|
||||
let openPanel = NSOpenPanel()
|
||||
openPanel.message = "Hint: you can also insert media by dragging and dropping it onto the machine's window."
|
||||
@@ -241,37 +309,40 @@ class MachineDocument:
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: NSDocument overrides
|
||||
override func data(ofType typeName: String) throws -> Data {
|
||||
throw NSError(domain: NSOSStatusErrorDomain, code: unimpErr, userInfo: nil)
|
||||
}
|
||||
// MARK: - Input Management.
|
||||
|
||||
// MARK: Input management
|
||||
/// Upon a resign key, immediately releases all ongoing input mechanisms — any currently pressed keys,
|
||||
/// and joystick and mouse inputs.
|
||||
func windowDidResignKey(_ notification: Notification) {
|
||||
if let machine = self.machine {
|
||||
machine.clearAllKeys()
|
||||
machine.joystickManager = nil
|
||||
}
|
||||
self.openGLView.releaseMouse()
|
||||
}
|
||||
|
||||
/// Upon becoming key, attaches joystick input to the machine.
|
||||
func windowDidBecomeKey(_ notification: Notification) {
|
||||
if let machine = self.machine {
|
||||
machine.joystickManager = (DocumentController.shared as! DocumentController).joystickManager
|
||||
}
|
||||
}
|
||||
|
||||
/// Forwards key down events directly to the machine.
|
||||
func keyDown(_ event: NSEvent) {
|
||||
if let machine = self.machine {
|
||||
machine.setKey(event.keyCode, characters: event.characters, isPressed: true)
|
||||
}
|
||||
}
|
||||
|
||||
/// Forwards key up events directly to the machine.
|
||||
func keyUp(_ event: NSEvent) {
|
||||
if let machine = self.machine {
|
||||
machine.setKey(event.keyCode, characters: event.characters, isPressed: false)
|
||||
}
|
||||
}
|
||||
|
||||
/// Synthesies appropriate key up and key down events upon any change in modifiers.
|
||||
func flagsChanged(_ newModifiers: NSEvent) {
|
||||
if let machine = self.machine {
|
||||
machine.setKey(VK_Shift, characters: nil, isPressed: newModifiers.modifierFlags.contains(.shift))
|
||||
@@ -281,19 +352,199 @@ class MachineDocument:
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: New machine creation
|
||||
/// Forwards mouse movement events to the mouse.
|
||||
func mouseMoved(_ event: NSEvent) {
|
||||
if let machine = self.machine {
|
||||
machine.addMouseMotionX(event.deltaX, y: event.deltaY)
|
||||
}
|
||||
}
|
||||
|
||||
/// Forwards mouse button down events to the mouse.
|
||||
func mouseUp(_ event: NSEvent) {
|
||||
if let machine = self.machine {
|
||||
machine.setMouseButton(Int32(event.buttonNumber), isPressed: false)
|
||||
}
|
||||
}
|
||||
|
||||
/// Forwards mouse button up events to the mouse.
|
||||
func mouseDown(_ event: NSEvent) {
|
||||
if let machine = self.machine {
|
||||
machine.setMouseButton(Int32(event.buttonNumber), isPressed: true)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - MachinePicker Outlets and Actions
|
||||
@IBOutlet var machinePicker: MachinePicker?
|
||||
@IBOutlet var machinePickerPanel: NSWindow?
|
||||
@IBAction func createMachine(_ sender: NSButton?) {
|
||||
self.configureAs(machinePicker!.selectedMachine())
|
||||
machinePicker = nil
|
||||
let selectedMachine = machinePicker!.selectedMachine()
|
||||
self.windowControllers[0].window?.endSheet(self.machinePickerPanel!)
|
||||
self.machinePicker = nil
|
||||
self.configureAs(selectedMachine)
|
||||
}
|
||||
|
||||
@IBAction func cancelCreateMachine(_ sender: NSButton?) {
|
||||
close()
|
||||
}
|
||||
|
||||
// MARK: - ROMRequester Outlets and Actions
|
||||
@IBOutlet var romRequesterPanel: NSWindow?
|
||||
@IBOutlet var romRequesterText: NSTextField?
|
||||
@IBOutlet var romReceiverErrorField: NSTextField?
|
||||
@IBOutlet var romReceiverView: CSROMReceiverView?
|
||||
private var romRequestBaseText = ""
|
||||
func requestRoms() {
|
||||
// Don't act yet if there's no window controller yet.
|
||||
if self.windowControllers.count == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
// Load the ROM requester dialogue.
|
||||
Bundle.main.loadNibNamed("ROMRequester", owner: self, topLevelObjects: nil)
|
||||
self.romReceiverView!.delegate = self
|
||||
self.romRequestBaseText = romRequesterText!.stringValue
|
||||
romReceiverErrorField?.alphaValue = 0.0
|
||||
|
||||
// Populate the current absentee list.
|
||||
populateMissingRomList()
|
||||
|
||||
// Show the thing.
|
||||
self.windowControllers[0].window?.beginSheet(self.romRequesterPanel!, completionHandler: nil)
|
||||
}
|
||||
|
||||
@IBAction func cancelRequestROMs(_ sender: NSButton?) {
|
||||
close()
|
||||
}
|
||||
|
||||
func populateMissingRomList() {
|
||||
// Fill in the missing details; first build a list of all the individual
|
||||
// line items.
|
||||
var requestLines: [String] = []
|
||||
for missingROM in self.missingROMs {
|
||||
if let descriptiveName = missingROM.descriptiveName {
|
||||
requestLines.append("• " + descriptiveName)
|
||||
} else {
|
||||
requestLines.append("• " + missingROM.fileName)
|
||||
}
|
||||
}
|
||||
|
||||
// Suffix everything up to the penultimate line with a semicolon;
|
||||
// the penultimate line with a semicolon and a conjunctive; the final
|
||||
// line with a full stop.
|
||||
for x in 0 ..< requestLines.count {
|
||||
if x < requestLines.count - 2 {
|
||||
requestLines[x].append(";")
|
||||
} else if x < requestLines.count - 1 {
|
||||
requestLines[x].append("; and")
|
||||
} else {
|
||||
requestLines[x].append(".")
|
||||
}
|
||||
}
|
||||
romRequesterText!.stringValue = self.romRequestBaseText + requestLines.joined(separator: "\n")
|
||||
}
|
||||
|
||||
func romReceiverView(_ view: CSROMReceiverView, didReceiveFileAt URL: URL) {
|
||||
// Test whether the file identified matches any of the currently missing ROMs.
|
||||
// If so then remove that ROM from the missing list and update the request screen.
|
||||
// If no ROMs are still missing, start the machine.
|
||||
do {
|
||||
let fileData = try Data(contentsOf: URL)
|
||||
var didInstallRom = false
|
||||
|
||||
// Try to match by size first, CRC second. Accept that some ROMs may have
|
||||
// some additional appended data. Arbitrarily allow them to be up to 10kb
|
||||
// too large.
|
||||
var index = 0
|
||||
for missingROM in self.missingROMs {
|
||||
if fileData.count >= missingROM.size && fileData.count < missingROM.size + 10*1024 {
|
||||
// Trim to size.
|
||||
let trimmedData = fileData[0 ..< missingROM.size]
|
||||
|
||||
// Get CRC.
|
||||
if missingROM.crc32s.contains( (trimmedData as NSData).crc32 ) {
|
||||
// This ROM matches; copy it into the application library,
|
||||
// strike it from the missing ROM list and decide how to
|
||||
// proceed.
|
||||
let fileManager = FileManager.default
|
||||
let targetPath = fileManager.urls(for: .applicationSupportDirectory, in: .userDomainMask)[0]
|
||||
.appendingPathComponent("ROMImages")
|
||||
.appendingPathComponent(missingROM.machineName)
|
||||
let targetFile = targetPath
|
||||
.appendingPathComponent(missingROM.fileName)
|
||||
|
||||
do {
|
||||
try fileManager.createDirectory(atPath: targetPath.path, withIntermediateDirectories: true, attributes: nil)
|
||||
try trimmedData.write(to: targetFile)
|
||||
} catch let error {
|
||||
showRomReceiverError(error: "Couldn't write to application support directory: \(error)")
|
||||
}
|
||||
|
||||
self.missingROMs.remove(at: index)
|
||||
didInstallRom = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
index = index + 1
|
||||
}
|
||||
|
||||
if didInstallRom {
|
||||
if self.missingROMs.count == 0 {
|
||||
self.windowControllers[0].window?.endSheet(self.romRequesterPanel!)
|
||||
configureAs(self.machineDescription!)
|
||||
} else {
|
||||
populateMissingRomList()
|
||||
}
|
||||
} else {
|
||||
showRomReceiverError(error: "Didn't recognise contents of \(URL.lastPathComponent)")
|
||||
}
|
||||
} catch let error {
|
||||
showRomReceiverError(error: "Couldn't read file at \(URL.absoluteString): \(error)")
|
||||
}
|
||||
}
|
||||
|
||||
// Yucky ugliness follows; my experience as an iOS developer intersects poorly with
|
||||
// NSAnimationContext hence the various stateful diplications below. isShowingError
|
||||
// should be essentially a duplicate of the current alphaValue, and animationCount
|
||||
// is to resolve my inability to figure out how to cancel scheduled animations.
|
||||
private var errorText = ""
|
||||
private var isShowingError = false
|
||||
private var animationCount = 0
|
||||
private func showRomReceiverError(error: String) {
|
||||
// Set or append the new error.
|
||||
if self.errorText.count > 0 {
|
||||
self.errorText = self.errorText + "\n" + error
|
||||
} else {
|
||||
self.errorText = error
|
||||
}
|
||||
|
||||
// Apply the new complete text.
|
||||
romReceiverErrorField!.stringValue = self.errorText
|
||||
|
||||
if !isShowingError {
|
||||
// Schedule the box's appearance.
|
||||
NSAnimationContext.beginGrouping()
|
||||
NSAnimationContext.current.duration = 0.1
|
||||
romReceiverErrorField?.animator().alphaValue = 1.0
|
||||
NSAnimationContext.endGrouping()
|
||||
isShowingError = true
|
||||
}
|
||||
|
||||
// Schedule the box to disappear.
|
||||
self.animationCount = self.animationCount + 1
|
||||
let capturedAnimationCount = animationCount
|
||||
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + .seconds(2)) {
|
||||
if self.animationCount == capturedAnimationCount {
|
||||
NSAnimationContext.beginGrouping()
|
||||
NSAnimationContext.current.duration = 1.0
|
||||
self.romReceiverErrorField?.animator().alphaValue = 0.0
|
||||
NSAnimationContext.endGrouping()
|
||||
self.isShowingError = false
|
||||
self.errorText = ""
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: Joystick-via-the-keyboard selection
|
||||
@IBAction func useKeyboardAsKeyboard(_ sender: NSMenuItem?) {
|
||||
machine.inputMode = .keyboard
|
||||
@@ -303,6 +554,9 @@ class MachineDocument:
|
||||
machine.inputMode = .joystick
|
||||
}
|
||||
|
||||
/// Determines which of the menu items to enable and disable based on the ability of the
|
||||
/// current machine to handle keyboard and joystick input, accept new media and whether
|
||||
/// it has an associted activity window.
|
||||
override func validateUserInterfaceItem(_ item: NSValidatedUserInterfaceItem) -> Bool {
|
||||
if let menuItem = item as? NSMenuItem {
|
||||
switch item.action {
|
||||
@@ -336,7 +590,7 @@ class MachineDocument:
|
||||
return super.validateUserInterfaceItem(item)
|
||||
}
|
||||
|
||||
// Screenshot capture.
|
||||
/// Saves a screenshot of the
|
||||
@IBAction func saveScreenshot(_ sender: AnyObject!) {
|
||||
// Grab a date formatter and form a file name.
|
||||
let dateFormatter = DateFormatter()
|
||||
@@ -360,7 +614,8 @@ class MachineDocument:
|
||||
}
|
||||
|
||||
// MARK: Activity display.
|
||||
class LED {
|
||||
|
||||
private class LED {
|
||||
let levelIndicator: NSLevelIndicator
|
||||
init(levelIndicator: NSLevelIndicator) {
|
||||
self.levelIndicator = levelIndicator
|
||||
@@ -368,11 +623,12 @@ class MachineDocument:
|
||||
var isLit = false
|
||||
var isBlinking = false
|
||||
}
|
||||
fileprivate var leds: [String: LED] = [:]
|
||||
private var leds: [String: LED] = [:]
|
||||
|
||||
func setupActivityDisplay() {
|
||||
var leds = machine.leds
|
||||
if leds.count > 0 {
|
||||
Bundle.main.loadNibNamed(NSNib.Name(rawValue: "Activity"), owner: self, topLevelObjects: nil)
|
||||
Bundle.main.loadNibNamed("Activity", owner: self, topLevelObjects: nil)
|
||||
showActivity(nil)
|
||||
|
||||
// Inspect the activity panel for indicators.
|
||||
|
||||
@@ -14,42 +14,42 @@
|
||||
</array>
|
||||
<key>CFBundleTypeIconFile</key>
|
||||
<string>cartridge.png</string>
|
||||
<key>CFBundleTypeName</key>
|
||||
<string>Atari 2600 Cartridge</string>
|
||||
<key>CFBundleTypeOSTypes</key>
|
||||
<array>
|
||||
<string>????</string>
|
||||
</array>
|
||||
<key>CFBundleTypeName</key>
|
||||
<string>Atari 2600 Cartridge</string>
|
||||
<key>CFBundleTypeRole</key>
|
||||
<string>Viewer</string>
|
||||
<key>LSHandlerRank</key>
|
||||
<string>Owner</string>
|
||||
<key>LSTypeIsPackage</key>
|
||||
<false/>
|
||||
<key>NSDocumentClass</key>
|
||||
<string>$(PRODUCT_MODULE_NAME).MachineDocument</string>
|
||||
<key>LSHandlerRank</key>
|
||||
<string>Owner</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>CFBundleTypeExtensions</key>
|
||||
<array>
|
||||
<string>rom</string>
|
||||
</array>
|
||||
<key>CFBundleTypeOSTypes</key>
|
||||
<array>
|
||||
<string>????</string>
|
||||
</array>
|
||||
<key>CFBundleTypeIconFile</key>
|
||||
<string>chip.png</string>
|
||||
<key>CFBundleTypeName</key>
|
||||
<string>ROM Image</string>
|
||||
<key>CFBundleTypeOSTypes</key>
|
||||
<array>
|
||||
<string>????</string>
|
||||
</array>
|
||||
<key>CFBundleTypeRole</key>
|
||||
<string>Viewer</string>
|
||||
<key>LSHandlerRank</key>
|
||||
<string>Owner</string>
|
||||
<key>LSTypeIsPackage</key>
|
||||
<false/>
|
||||
<key>NSDocumentClass</key>
|
||||
<string>$(PRODUCT_MODULE_NAME).MachineDocument</string>
|
||||
<key>LSHandlerRank</key>
|
||||
<string>Owner</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>CFBundleTypeExtensions</key>
|
||||
@@ -57,110 +57,110 @@
|
||||
<string>uef</string>
|
||||
<string>uef.gz</string>
|
||||
</array>
|
||||
<key>CFBundleTypeOSTypes</key>
|
||||
<array>
|
||||
<string>????</string>
|
||||
</array>
|
||||
<key>CFBundleTypeIconFile</key>
|
||||
<string>cassette.png</string>
|
||||
<key>CFBundleTypeName</key>
|
||||
<string>Electron/BBC UEF Image</string>
|
||||
<key>CFBundleTypeOSTypes</key>
|
||||
<array>
|
||||
<string>????</string>
|
||||
</array>
|
||||
<key>CFBundleTypeRole</key>
|
||||
<string>Viewer</string>
|
||||
<key>LSHandlerRank</key>
|
||||
<string>Owner</string>
|
||||
<key>LSTypeIsPackage</key>
|
||||
<false/>
|
||||
<key>NSDocumentClass</key>
|
||||
<string>$(PRODUCT_MODULE_NAME).MachineDocument</string>
|
||||
<key>LSHandlerRank</key>
|
||||
<string>Owner</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>CFBundleTypeExtensions</key>
|
||||
<array>
|
||||
<string>prg</string>
|
||||
</array>
|
||||
<key>CFBundleTypeOSTypes</key>
|
||||
<array>
|
||||
<string>????</string>
|
||||
</array>
|
||||
<key>CFBundleTypeIconFile</key>
|
||||
<string>floppy525.png</string>
|
||||
<key>CFBundleTypeName</key>
|
||||
<string>Commodore Program</string>
|
||||
<key>CFBundleTypeOSTypes</key>
|
||||
<array>
|
||||
<string>????</string>
|
||||
</array>
|
||||
<key>CFBundleTypeRole</key>
|
||||
<string>Viewer</string>
|
||||
<key>LSHandlerRank</key>
|
||||
<string>Owner</string>
|
||||
<key>LSTypeIsPackage</key>
|
||||
<false/>
|
||||
<key>NSDocumentClass</key>
|
||||
<string>$(PRODUCT_MODULE_NAME).MachineDocument</string>
|
||||
<key>LSHandlerRank</key>
|
||||
<string>Owner</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>CFBundleTypeExtensions</key>
|
||||
<array>
|
||||
<string>tap</string>
|
||||
</array>
|
||||
<key>CFBundleTypeOSTypes</key>
|
||||
<array>
|
||||
<string>????</string>
|
||||
</array>
|
||||
<key>CFBundleTypeIconFile</key>
|
||||
<string>cassette.png</string>
|
||||
<key>CFBundleTypeName</key>
|
||||
<string>Tape Image</string>
|
||||
<key>CFBundleTypeOSTypes</key>
|
||||
<array>
|
||||
<string>????</string>
|
||||
</array>
|
||||
<key>CFBundleTypeRole</key>
|
||||
<string>Viewer</string>
|
||||
<key>LSHandlerRank</key>
|
||||
<string>Owner</string>
|
||||
<key>LSTypeIsPackage</key>
|
||||
<false/>
|
||||
<key>NSDocumentClass</key>
|
||||
<string>$(PRODUCT_MODULE_NAME).MachineDocument</string>
|
||||
<key>LSHandlerRank</key>
|
||||
<string>Owner</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>CFBundleTypeExtensions</key>
|
||||
<array>
|
||||
<string>g64</string>
|
||||
</array>
|
||||
<key>CFBundleTypeOSTypes</key>
|
||||
<array>
|
||||
<string>????</string>
|
||||
</array>
|
||||
<key>CFBundleTypeIconFile</key>
|
||||
<string>floppy525.png</string>
|
||||
<key>CFBundleTypeName</key>
|
||||
<string>Commodore Disk</string>
|
||||
<key>CFBundleTypeOSTypes</key>
|
||||
<array>
|
||||
<string>????</string>
|
||||
</array>
|
||||
<key>CFBundleTypeRole</key>
|
||||
<string>Editor</string>
|
||||
<key>LSHandlerRank</key>
|
||||
<string>Owner</string>
|
||||
<key>LSTypeIsPackage</key>
|
||||
<false/>
|
||||
<key>NSDocumentClass</key>
|
||||
<string>$(PRODUCT_MODULE_NAME).MachineDocument</string>
|
||||
<key>LSHandlerRank</key>
|
||||
<string>Owner</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>CFBundleTypeExtensions</key>
|
||||
<array>
|
||||
<string>d64</string>
|
||||
</array>
|
||||
<key>CFBundleTypeOSTypes</key>
|
||||
<array>
|
||||
<string>????</string>
|
||||
</array>
|
||||
<key>CFBundleTypeIconFile</key>
|
||||
<string>floppy525.png</string>
|
||||
<key>CFBundleTypeName</key>
|
||||
<string>Commodore 1540/1 Disk</string>
|
||||
<key>CFBundleTypeOSTypes</key>
|
||||
<array>
|
||||
<string>????</string>
|
||||
</array>
|
||||
<key>CFBundleTypeRole</key>
|
||||
<string>Editor</string>
|
||||
<key>LSHandlerRank</key>
|
||||
<string>Owner</string>
|
||||
<key>LSTypeIsPackage</key>
|
||||
<false/>
|
||||
<key>NSDocumentClass</key>
|
||||
<string>$(PRODUCT_MODULE_NAME).MachineDocument</string>
|
||||
<key>LSHandlerRank</key>
|
||||
<string>Owner</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>CFBundleTypeExtensions</key>
|
||||
@@ -171,44 +171,44 @@
|
||||
<string>adl</string>
|
||||
<string>adm</string>
|
||||
</array>
|
||||
<key>CFBundleTypeOSTypes</key>
|
||||
<array>
|
||||
<string>????</string>
|
||||
</array>
|
||||
<key>CFBundleTypeIconFile</key>
|
||||
<string>floppy35.png</string>
|
||||
<key>CFBundleTypeName</key>
|
||||
<string>Electron/BBC Disk Image</string>
|
||||
<key>CFBundleTypeOSTypes</key>
|
||||
<array>
|
||||
<string>????</string>
|
||||
</array>
|
||||
<key>CFBundleTypeRole</key>
|
||||
<string>Editor</string>
|
||||
<key>LSHandlerRank</key>
|
||||
<string>Owner</string>
|
||||
<key>LSTypeIsPackage</key>
|
||||
<false/>
|
||||
<key>NSDocumentClass</key>
|
||||
<string>$(PRODUCT_MODULE_NAME).MachineDocument</string>
|
||||
<key>LSHandlerRank</key>
|
||||
<string>Owner</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>CFBundleTypeExtensions</key>
|
||||
<array>
|
||||
<string>dsk</string>
|
||||
</array>
|
||||
<key>CFBundleTypeOSTypes</key>
|
||||
<array>
|
||||
<string>????</string>
|
||||
</array>
|
||||
<key>CFBundleTypeIconFile</key>
|
||||
<string>floppy35.png</string>
|
||||
<key>CFBundleTypeName</key>
|
||||
<string>Disk Image</string>
|
||||
<key>CFBundleTypeOSTypes</key>
|
||||
<array>
|
||||
<string>????</string>
|
||||
</array>
|
||||
<key>CFBundleTypeRole</key>
|
||||
<string>Editor</string>
|
||||
<key>LSHandlerRank</key>
|
||||
<string>Owner</string>
|
||||
<key>LSTypeIsPackage</key>
|
||||
<false/>
|
||||
<key>NSDocumentClass</key>
|
||||
<string>$(PRODUCT_MODULE_NAME).MachineDocument</string>
|
||||
<key>LSHandlerRank</key>
|
||||
<string>Owner</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>CFBundleTypeExtensions</key>
|
||||
@@ -216,22 +216,22 @@
|
||||
<string>o</string>
|
||||
<string>80</string>
|
||||
</array>
|
||||
<key>CFBundleTypeOSTypes</key>
|
||||
<array>
|
||||
<string>????</string>
|
||||
</array>
|
||||
<key>CFBundleTypeIconFile</key>
|
||||
<string>cassette.png</string>
|
||||
<key>CFBundleTypeName</key>
|
||||
<string>ZX80 Tape Image</string>
|
||||
<key>CFBundleTypeOSTypes</key>
|
||||
<array>
|
||||
<string>????</string>
|
||||
</array>
|
||||
<key>CFBundleTypeRole</key>
|
||||
<string>Viewer</string>
|
||||
<key>LSHandlerRank</key>
|
||||
<string>Owner</string>
|
||||
<key>LSTypeIsPackage</key>
|
||||
<false/>
|
||||
<key>NSDocumentClass</key>
|
||||
<string>$(PRODUCT_MODULE_NAME).MachineDocument</string>
|
||||
<key>LSHandlerRank</key>
|
||||
<string>Owner</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>CFBundleTypeExtensions</key>
|
||||
@@ -240,240 +240,240 @@
|
||||
<string>81</string>
|
||||
<string>p81</string>
|
||||
</array>
|
||||
<key>CFBundleTypeOSTypes</key>
|
||||
<array>
|
||||
<string>????</string>
|
||||
</array>
|
||||
<key>CFBundleTypeIconFile</key>
|
||||
<string>cassette.png</string>
|
||||
<key>CFBundleTypeName</key>
|
||||
<string>ZX81 Tape Image</string>
|
||||
<key>CFBundleTypeOSTypes</key>
|
||||
<array>
|
||||
<string>????</string>
|
||||
</array>
|
||||
<key>CFBundleTypeRole</key>
|
||||
<string>Viewer</string>
|
||||
<key>LSHandlerRank</key>
|
||||
<string>Owner</string>
|
||||
<key>LSTypeIsPackage</key>
|
||||
<false/>
|
||||
<key>NSDocumentClass</key>
|
||||
<string>$(PRODUCT_MODULE_NAME).MachineDocument</string>
|
||||
<key>LSHandlerRank</key>
|
||||
<string>Owner</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>CFBundleTypeExtensions</key>
|
||||
<array>
|
||||
<string>csw</string>
|
||||
</array>
|
||||
<key>CFBundleTypeOSTypes</key>
|
||||
<array>
|
||||
<string>????</string>
|
||||
</array>
|
||||
<key>CFBundleTypeIconFile</key>
|
||||
<string>cassette.png</string>
|
||||
<key>CFBundleTypeName</key>
|
||||
<string>Tape Image</string>
|
||||
<key>CFBundleTypeOSTypes</key>
|
||||
<array>
|
||||
<string>????</string>
|
||||
</array>
|
||||
<key>CFBundleTypeRole</key>
|
||||
<string>Viewer</string>
|
||||
<key>LSHandlerRank</key>
|
||||
<string>Owner</string>
|
||||
<key>LSTypeIsPackage</key>
|
||||
<false/>
|
||||
<key>NSDocumentClass</key>
|
||||
<string>$(PRODUCT_MODULE_NAME).MachineDocument</string>
|
||||
<key>LSHandlerRank</key>
|
||||
<string>Owner</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>CFBundleTypeExtensions</key>
|
||||
<array>
|
||||
<string>tzx</string>
|
||||
</array>
|
||||
<key>CFBundleTypeOSTypes</key>
|
||||
<array>
|
||||
<string>????</string>
|
||||
</array>
|
||||
<key>CFBundleTypeIconFile</key>
|
||||
<string>cassette.png</string>
|
||||
<key>CFBundleTypeName</key>
|
||||
<string>Tape Image</string>
|
||||
<key>CFBundleTypeOSTypes</key>
|
||||
<array>
|
||||
<string>????</string>
|
||||
</array>
|
||||
<key>CFBundleTypeRole</key>
|
||||
<string>Viewer</string>
|
||||
<key>LSHandlerRank</key>
|
||||
<string>Owner</string>
|
||||
<key>LSTypeIsPackage</key>
|
||||
<false/>
|
||||
<key>NSDocumentClass</key>
|
||||
<string>$(PRODUCT_MODULE_NAME).MachineDocument</string>
|
||||
<key>LSHandlerRank</key>
|
||||
<string>Owner</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>CFBundleTypeExtensions</key>
|
||||
<array>
|
||||
<string>cdt</string>
|
||||
</array>
|
||||
<key>CFBundleTypeOSTypes</key>
|
||||
<array>
|
||||
<string>????</string>
|
||||
</array>
|
||||
<key>CFBundleTypeIconFile</key>
|
||||
<string>cassette.png</string>
|
||||
<key>CFBundleTypeName</key>
|
||||
<string>Amstrad CPC Tape Image</string>
|
||||
<key>CFBundleTypeOSTypes</key>
|
||||
<array>
|
||||
<string>????</string>
|
||||
</array>
|
||||
<key>CFBundleTypeRole</key>
|
||||
<string>Viewer</string>
|
||||
<key>LSHandlerRank</key>
|
||||
<string>Owner</string>
|
||||
<key>LSTypeIsPackage</key>
|
||||
<false/>
|
||||
<key>NSDocumentClass</key>
|
||||
<string>$(PRODUCT_MODULE_NAME).MachineDocument</string>
|
||||
<key>LSHandlerRank</key>
|
||||
<string>Owner</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>CFBundleTypeExtensions</key>
|
||||
<array>
|
||||
<string>hfe</string>
|
||||
</array>
|
||||
<key>CFBundleTypeOSTypes</key>
|
||||
<array>
|
||||
<string>????</string>
|
||||
</array>
|
||||
<key>CFBundleTypeIconFile</key>
|
||||
<string>floppy35.png</string>
|
||||
<key>CFBundleTypeName</key>
|
||||
<string>HxC Disk Image</string>
|
||||
<key>CFBundleTypeOSTypes</key>
|
||||
<array>
|
||||
<string>????</string>
|
||||
</array>
|
||||
<key>CFBundleTypeRole</key>
|
||||
<string>Viewer</string>
|
||||
<key>LSHandlerRank</key>
|
||||
<string>Owner</string>
|
||||
<key>LSTypeIsPackage</key>
|
||||
<false/>
|
||||
<key>NSDocumentClass</key>
|
||||
<string>$(PRODUCT_MODULE_NAME).MachineDocument</string>
|
||||
<key>LSHandlerRank</key>
|
||||
<string>Owner</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>CFBundleTypeExtensions</key>
|
||||
<array>
|
||||
<string>cas</string>
|
||||
</array>
|
||||
<key>CFBundleTypeOSTypes</key>
|
||||
<array>
|
||||
<string>????</string>
|
||||
</array>
|
||||
<key>CFBundleTypeIconFile</key>
|
||||
<string>cassette.png</string>
|
||||
<key>CFBundleTypeName</key>
|
||||
<string>MSX Tape Image</string>
|
||||
<key>CFBundleTypeOSTypes</key>
|
||||
<array>
|
||||
<string>????</string>
|
||||
</array>
|
||||
<key>CFBundleTypeRole</key>
|
||||
<string>Viewer</string>
|
||||
<key>LSHandlerRank</key>
|
||||
<string>Owner</string>
|
||||
<key>LSTypeIsPackage</key>
|
||||
<false/>
|
||||
<key>NSDocumentClass</key>
|
||||
<string>$(PRODUCT_MODULE_NAME).MachineDocument</string>
|
||||
<key>LSHandlerRank</key>
|
||||
<string>Owner</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>CFBundleTypeExtensions</key>
|
||||
<array>
|
||||
<string>dmk</string>
|
||||
</array>
|
||||
<key>CFBundleTypeOSTypes</key>
|
||||
<array>
|
||||
<string>????</string>
|
||||
</array>
|
||||
<key>CFBundleTypeIconFile</key>
|
||||
<string>floppy35.png</string>
|
||||
<key>CFBundleTypeName</key>
|
||||
<string>Disk Image</string>
|
||||
<key>CFBundleTypeOSTypes</key>
|
||||
<array>
|
||||
<string>????</string>
|
||||
</array>
|
||||
<key>CFBundleTypeRole</key>
|
||||
<string>Viewer</string>
|
||||
<key>LSTypeIsPackage</key>
|
||||
<false/>
|
||||
<key>LSHandlerRank</key>
|
||||
<string>Owner</string>
|
||||
<key>LSTypeIsPackage</key>
|
||||
<false/>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>CFBundleTypeExtensions</key>
|
||||
<array>
|
||||
<string>tsx</string>
|
||||
</array>
|
||||
<key>CFBundleTypeOSTypes</key>
|
||||
<array>
|
||||
<string>????</string>
|
||||
</array>
|
||||
<key>CFBundleTypeIconFile</key>
|
||||
<string>cassette.png</string>
|
||||
<key>CFBundleTypeName</key>
|
||||
<string>MSX Tape Image</string>
|
||||
<key>CFBundleTypeOSTypes</key>
|
||||
<array>
|
||||
<string>????</string>
|
||||
</array>
|
||||
<key>CFBundleTypeRole</key>
|
||||
<string>Viewer</string>
|
||||
<key>LSHandlerRank</key>
|
||||
<string>Owner</string>
|
||||
<key>LSTypeIsPackage</key>
|
||||
<false/>
|
||||
<key>NSDocumentClass</key>
|
||||
<string>$(PRODUCT_MODULE_NAME).MachineDocument</string>
|
||||
<key>LSHandlerRank</key>
|
||||
<string>Owner</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>CFBundleTypeExtensions</key>
|
||||
<array>
|
||||
<string>col</string>
|
||||
</array>
|
||||
<key>CFBundleTypeOSTypes</key>
|
||||
<array>
|
||||
<string>????</string>
|
||||
</array>
|
||||
<key>CFBundleTypeIconFile</key>
|
||||
<string>cartridge.png</string>
|
||||
<key>CFBundleTypeName</key>
|
||||
<string>ColecoVision Cartridge</string>
|
||||
<key>CFBundleTypeOSTypes</key>
|
||||
<array>
|
||||
<string>????</string>
|
||||
</array>
|
||||
<key>CFBundleTypeRole</key>
|
||||
<string>Viewer</string>
|
||||
<key>LSHandlerRank</key>
|
||||
<string>Owner</string>
|
||||
<key>LSTypeIsPackage</key>
|
||||
<false/>
|
||||
<key>NSDocumentClass</key>
|
||||
<string>$(PRODUCT_MODULE_NAME).MachineDocument</string>
|
||||
<key>LSHandlerRank</key>
|
||||
<string>Owner</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>CFBundleTypeExtensions</key>
|
||||
<array>
|
||||
<string>sms</string>
|
||||
</array>
|
||||
<key>CFBundleTypeOSTypes</key>
|
||||
<array>
|
||||
<string>????</string>
|
||||
</array>
|
||||
<key>CFBundleTypeIconFile</key>
|
||||
<string>cartridge.png</string>
|
||||
<key>CFBundleTypeName</key>
|
||||
<string>Master System Cartridge</string>
|
||||
<key>CFBundleTypeOSTypes</key>
|
||||
<array>
|
||||
<string>????</string>
|
||||
</array>
|
||||
<key>CFBundleTypeRole</key>
|
||||
<string>Viewer</string>
|
||||
<key>LSHandlerRank</key>
|
||||
<string>Owner</string>
|
||||
<key>LSTypeIsPackage</key>
|
||||
<false/>
|
||||
<key>NSDocumentClass</key>
|
||||
<string>$(PRODUCT_MODULE_NAME).MachineDocument</string>
|
||||
<key>LSHandlerRank</key>
|
||||
<string>Owner</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>CFBundleTypeExtensions</key>
|
||||
<array>
|
||||
<string>sg</string>
|
||||
</array>
|
||||
<key>CFBundleTypeOSTypes</key>
|
||||
<array>
|
||||
<string>????</string>
|
||||
</array>
|
||||
<key>CFBundleTypeIconFile</key>
|
||||
<string>cartridge.png</string>
|
||||
<key>CFBundleTypeName</key>
|
||||
<string>SG1000 Cartridge</string>
|
||||
<key>CFBundleTypeOSTypes</key>
|
||||
<array>
|
||||
<string>????</string>
|
||||
</array>
|
||||
<key>CFBundleTypeRole</key>
|
||||
<string>Viewer</string>
|
||||
<key>LSHandlerRank</key>
|
||||
<string>Owner</string>
|
||||
<key>LSTypeIsPackage</key>
|
||||
<false/>
|
||||
<key>NSDocumentClass</key>
|
||||
<string>$(PRODUCT_MODULE_NAME).MachineDocument</string>
|
||||
<key>LSHandlerRank</key>
|
||||
<string>Owner</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>CFBundleTypeExtensions</key>
|
||||
@@ -483,22 +483,48 @@
|
||||
<string>do</string>
|
||||
<string>po</string>
|
||||
</array>
|
||||
<key>CFBundleTypeOSTypes</key>
|
||||
<array>
|
||||
<string>????</string>
|
||||
</array>
|
||||
<key>CFBundleTypeIconFile</key>
|
||||
<string>floppy525.png</string>
|
||||
<key>CFBundleTypeName</key>
|
||||
<string>Apple II Disk Image</string>
|
||||
<key>CFBundleTypeOSTypes</key>
|
||||
<array>
|
||||
<string>????</string>
|
||||
</array>
|
||||
<key>CFBundleTypeRole</key>
|
||||
<string>Viewer</string>
|
||||
<key>LSHandlerRank</key>
|
||||
<string>Owner</string>
|
||||
<key>LSTypeIsPackage</key>
|
||||
<false/>
|
||||
<key>NSDocumentClass</key>
|
||||
<string>$(PRODUCT_MODULE_NAME).MachineDocument</string>
|
||||
<key>LSHandlerRank</key>
|
||||
<string>Owner</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>CFBundleTypeExtensions</key>
|
||||
<array>
|
||||
<string>img</string>
|
||||
<string>hfv</string>
|
||||
<string>image</string>
|
||||
</array>
|
||||
<key>CFBundleTypeIconFile</key>
|
||||
<string>floppy35</string>
|
||||
<key>CFBundleTypeMIMETypes</key>
|
||||
<array>
|
||||
<string>application/x-apple-diskimage</string>
|
||||
</array>
|
||||
<key>CFBundleTypeName</key>
|
||||
<string>DiskCopy 4.2 Disk Image</string>
|
||||
<key>CFBundleTypeOSTypes</key>
|
||||
<array>
|
||||
<string>????</string>
|
||||
</array>
|
||||
<key>CFBundleTypeRole</key>
|
||||
<string>Viewer</string>
|
||||
<key>LSTypeIsPackage</key>
|
||||
<integer>0</integer>
|
||||
<key>NSDocumentClass</key>
|
||||
<string>$(PRODUCT_MODULE_NAME).MachineDocument</string>
|
||||
</dict>
|
||||
</array>
|
||||
<key>CFBundleExecutable</key>
|
||||
@@ -524,7 +550,7 @@
|
||||
<key>LSMinimumSystemVersion</key>
|
||||
<string>$(MACOSX_DEPLOYMENT_TARGET)</string>
|
||||
<key>NSHumanReadableCopyright</key>
|
||||
<string>Copyright 2018 Thomas Harte. All rights reserved.</string>
|
||||
<string>Copyright 2019 Thomas Harte. All rights reserved.</string>
|
||||
<key>NSMainNibFile</key>
|
||||
<string>MainMenu</string>
|
||||
<key>NSPrincipalClass</key>
|
||||
|
||||
@@ -33,6 +33,14 @@ typedef NS_ENUM(NSInteger, CSMachineKeyboardInputMode) {
|
||||
CSMachineKeyboardInputModeJoystick
|
||||
};
|
||||
|
||||
@interface CSMissingROM: NSObject
|
||||
@property (nonatomic, readonly, nonnull) NSString *machineName;
|
||||
@property (nonatomic, readonly, nonnull) NSString *fileName;
|
||||
@property (nonatomic, readonly, nullable) NSString *descriptiveName;
|
||||
@property (nonatomic, readonly) NSUInteger size;
|
||||
@property (nonatomic, readonly, nonnull) NSArray<NSNumber *> *crc32s;
|
||||
@end
|
||||
|
||||
// Deliberately low; to ensure CSMachine has been declared as an @class already.
|
||||
#import "CSAtari2600.h"
|
||||
#import "CSZX8081.h"
|
||||
@@ -45,8 +53,10 @@ typedef NS_ENUM(NSInteger, CSMachineKeyboardInputMode) {
|
||||
Initialises an instance of CSMachine.
|
||||
|
||||
@param result The CSStaticAnalyser result that describes the machine needed.
|
||||
@param missingROMs An array that is filled with a list of ROMs that the machine requested but which
|
||||
were not found; populated only if this `init` has failed.
|
||||
*/
|
||||
- (nullable instancetype)initWithAnalyser:(nonnull CSStaticAnalyser *)result NS_DESIGNATED_INITIALIZER;
|
||||
- (nullable instancetype)initWithAnalyser:(nonnull CSStaticAnalyser *)result missingROMs:(nullable inout NSMutableArray<CSMissingROM *> *)missingROMs NS_DESIGNATED_INITIALIZER;
|
||||
|
||||
- (void)runForInterval:(NSTimeInterval)interval;
|
||||
|
||||
@@ -61,6 +71,9 @@ typedef NS_ENUM(NSInteger, CSMachineKeyboardInputMode) {
|
||||
- (void)setKey:(uint16_t)key characters:(nullable NSString *)characters isPressed:(BOOL)isPressed;
|
||||
- (void)clearAllKeys;
|
||||
|
||||
- (void)setMouseButton:(int)button isPressed:(BOOL)isPressed;
|
||||
- (void)addMouseMotionX:(CGFloat)deltaX y:(CGFloat)deltaY;
|
||||
|
||||
@property (nonatomic, strong, nullable) CSAudioQueue *audioQueue;
|
||||
@property (nonatomic, readonly, nonnull) CSOpenGLView *view;
|
||||
@property (nonatomic, weak, nullable) id<CSMachineDelegate> delegate;
|
||||
@@ -81,6 +94,7 @@ typedef NS_ENUM(NSInteger, CSMachineKeyboardInputMode) {
|
||||
// Input control.
|
||||
@property (nonatomic, readonly) BOOL hasExclusiveKeyboard;
|
||||
@property (nonatomic, readonly) BOOL hasJoystick;
|
||||
@property (nonatomic, readonly) BOOL hasMouse;
|
||||
@property (nonatomic, assign) CSMachineKeyboardInputMode inputMode;
|
||||
@property (nonatomic, nullable) CSJoystickManager *joystickManager;
|
||||
|
||||
|
||||
@@ -74,6 +74,68 @@ struct ActivityObserver: public Activity::Observer {
|
||||
__unsafe_unretained CSMachine *machine;
|
||||
};
|
||||
|
||||
@interface CSMissingROM (Mutability)
|
||||
@property (nonatomic, nonnull, copy) NSString *machineName;
|
||||
@property (nonatomic, nonnull, copy) NSString *fileName;
|
||||
@property (nonatomic, nullable, copy) NSString *descriptiveName;
|
||||
@property (nonatomic, readwrite) NSUInteger size;
|
||||
@property (nonatomic, copy) NSArray<NSNumber *> *crc32s;
|
||||
@end
|
||||
|
||||
@implementation CSMissingROM {
|
||||
NSString *_machineName;
|
||||
NSString *_fileName;
|
||||
NSString *_descriptiveName;
|
||||
NSUInteger _size;
|
||||
NSArray<NSNumber *> *_crc32s;
|
||||
}
|
||||
|
||||
- (NSString *)machineName {
|
||||
return _machineName;
|
||||
}
|
||||
|
||||
- (void)setMachineName:(NSString *)machineName {
|
||||
_machineName = [machineName copy];
|
||||
}
|
||||
|
||||
- (NSString *)fileName {
|
||||
return _fileName;
|
||||
}
|
||||
|
||||
- (void)setFileName:(NSString *)fileName {
|
||||
_fileName = [fileName copy];
|
||||
}
|
||||
|
||||
- (NSString *)descriptiveName {
|
||||
return _descriptiveName;
|
||||
}
|
||||
|
||||
- (void)setDescriptiveName:(NSString *)descriptiveName {
|
||||
_descriptiveName = [descriptiveName copy];
|
||||
}
|
||||
|
||||
- (NSUInteger)size {
|
||||
return _size;
|
||||
}
|
||||
|
||||
- (void)setSize:(NSUInteger)size {
|
||||
_size = size;
|
||||
}
|
||||
|
||||
- (NSArray<NSNumber *> *)crc32s {
|
||||
return _crc32s;
|
||||
}
|
||||
|
||||
- (void)setCrc32s:(NSArray<NSNumber *> *)crc32s {
|
||||
_crc32s = [crc32s copy];
|
||||
}
|
||||
|
||||
- (NSString *)description {
|
||||
return [NSString stringWithFormat:@"%@/%@, %@ bytes, CRCs: %@", _fileName, _descriptiveName, @(_size), _crc32s];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation CSMachine {
|
||||
SpeakerDelegate _speakerDelegate;
|
||||
ActivityObserver _activityObserver;
|
||||
@@ -90,14 +152,37 @@ struct ActivityObserver: public Activity::Observer {
|
||||
std::unique_ptr<Outputs::Display::OpenGL::ScanTarget> _scanTarget;
|
||||
}
|
||||
|
||||
- (instancetype)initWithAnalyser:(CSStaticAnalyser *)result {
|
||||
- (instancetype)initWithAnalyser:(CSStaticAnalyser *)result missingROMs:(inout NSMutableArray<CSMissingROM *> *)missingROMs {
|
||||
self = [super init];
|
||||
if(self) {
|
||||
_analyser = result;
|
||||
|
||||
Machine::Error error;
|
||||
_machine.reset(Machine::MachineForTargets(_analyser.targets, CSROMFetcher(), error));
|
||||
if(!_machine) return nil;
|
||||
std::vector<ROMMachine::ROM> missing_roms;
|
||||
_machine.reset(Machine::MachineForTargets(_analyser.targets, CSROMFetcher(&missing_roms), error));
|
||||
if(!_machine) {
|
||||
for(const auto &missing_rom : missing_roms) {
|
||||
CSMissingROM *rom = [[CSMissingROM alloc] init];
|
||||
|
||||
// Copy/convert the primitive fields.
|
||||
rom.machineName = [NSString stringWithUTF8String:missing_rom.machine_name.c_str()];
|
||||
rom.fileName = [NSString stringWithUTF8String:missing_rom.file_name.c_str()];
|
||||
rom.descriptiveName = missing_rom.descriptive_name.empty() ? nil : [NSString stringWithUTF8String:missing_rom.descriptive_name.c_str()];
|
||||
rom.size = missing_rom.size;
|
||||
|
||||
// Convert the CRC list.
|
||||
NSMutableArray<NSNumber *> *crc32s = [[NSMutableArray alloc] init];
|
||||
for(const auto &crc : missing_rom.crc32s) {
|
||||
[crc32s addObject:@(crc)];
|
||||
}
|
||||
rom.crc32s = [crc32s copy];
|
||||
|
||||
// Add to the missing list.
|
||||
[missingROMs addObject:rom];
|
||||
}
|
||||
|
||||
return nil;
|
||||
}
|
||||
|
||||
_inputMode =
|
||||
(_machine->keyboard_machine() && _machine->keyboard_machine()->get_keyboard().is_exclusive())
|
||||
@@ -410,14 +495,14 @@ struct ActivityObserver: public Activity::Observer {
|
||||
}
|
||||
|
||||
- (void)clearAllKeys {
|
||||
auto keyboard_machine = _machine->keyboard_machine();
|
||||
const auto keyboard_machine = _machine->keyboard_machine();
|
||||
if(keyboard_machine) {
|
||||
@synchronized(self) {
|
||||
keyboard_machine->get_keyboard().reset_all_keys();
|
||||
}
|
||||
}
|
||||
|
||||
auto joystick_machine = _machine->joystick_machine();
|
||||
const auto joystick_machine = _machine->joystick_machine();
|
||||
if(joystick_machine) {
|
||||
@synchronized(self) {
|
||||
for(auto &joystick : joystick_machine->get_joysticks()) {
|
||||
@@ -425,6 +510,31 @@ struct ActivityObserver: public Activity::Observer {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const auto mouse_machine = _machine->mouse_machine();
|
||||
if(mouse_machine) {
|
||||
@synchronized(self) {
|
||||
mouse_machine->get_mouse().reset_all_buttons();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setMouseButton:(int)button isPressed:(BOOL)isPressed {
|
||||
auto mouse_machine = _machine->mouse_machine();
|
||||
if(mouse_machine) {
|
||||
@synchronized(self) {
|
||||
mouse_machine->get_mouse().set_button_pressed(button % mouse_machine->get_mouse().get_number_of_buttons(), isPressed);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)addMouseMotionX:(CGFloat)deltaX y:(CGFloat)deltaY {
|
||||
auto mouse_machine = _machine->mouse_machine();
|
||||
if(mouse_machine) {
|
||||
@synchronized(self) {
|
||||
mouse_machine->get_mouse().move(int(deltaX), int(deltaY));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - Options
|
||||
@@ -540,6 +650,10 @@ struct ActivityObserver: public Activity::Observer {
|
||||
return !!_machine->joystick_machine();
|
||||
}
|
||||
|
||||
- (BOOL)hasMouse {
|
||||
return !!_machine->mouse_machine();
|
||||
}
|
||||
|
||||
- (BOOL)hasExclusiveKeyboard {
|
||||
return !!_machine->keyboard_machine() && _machine->keyboard_machine()->get_keyboard().is_exclusive();
|
||||
}
|
||||
|
||||
@@ -8,4 +8,4 @@
|
||||
|
||||
#include "ROMMachine.hpp"
|
||||
|
||||
ROMMachine::ROMFetcher CSROMFetcher();
|
||||
ROMMachine::ROMFetcher CSROMFetcher(std::vector<ROMMachine::ROM> *missing_roms = nullptr);
|
||||
|
||||
@@ -14,15 +14,39 @@
|
||||
|
||||
#include <string>
|
||||
|
||||
ROMMachine::ROMFetcher CSROMFetcher() {
|
||||
return [] (const std::string &machine, const std::vector<std::string> &names) -> std::vector<std::unique_ptr<std::vector<std::uint8_t>>> {
|
||||
NSString *subDirectory = [@"ROMImages/" stringByAppendingString:[NSString stringWithUTF8String:machine.c_str()]];
|
||||
std::vector<std::unique_ptr<std::vector<std::uint8_t>>> results;
|
||||
for(const auto &name: names) {
|
||||
NSData *fileData = [[NSBundle mainBundle] dataForResource:[NSString stringWithUTF8String:name.c_str()] withExtension:nil subdirectory:subDirectory];
|
||||
ROMMachine::ROMFetcher CSROMFetcher(std::vector<ROMMachine::ROM> *missing_roms) {
|
||||
return [missing_roms] (const std::vector<ROMMachine::ROM> &roms) -> std::vector<std::unique_ptr<std::vector<std::uint8_t>>> {
|
||||
NSArray<NSURL *> *const supportURLs = [[NSFileManager defaultManager] URLsForDirectory:NSApplicationSupportDirectory inDomains:NSUserDomainMask];
|
||||
|
||||
if(!fileData)
|
||||
std::vector<std::unique_ptr<std::vector<std::uint8_t>>> results;
|
||||
for(const auto &rom: roms) {
|
||||
NSData *fileData;
|
||||
NSString *const subdirectory = [@"ROMImages/" stringByAppendingString:[NSString stringWithUTF8String:rom.machine_name.c_str()]];
|
||||
|
||||
// Check for this file first within the application support directories.
|
||||
for(NSURL *supportURL in supportURLs) {
|
||||
NSURL *const fullURL = [[supportURL URLByAppendingPathComponent:subdirectory]
|
||||
URLByAppendingPathComponent:[NSString stringWithUTF8String:rom.file_name.c_str()]];
|
||||
fileData = [NSData dataWithContentsOfURL:fullURL];
|
||||
if(fileData) break;
|
||||
}
|
||||
|
||||
// Failing that, check inside the application bundle.
|
||||
if(!fileData) {
|
||||
fileData = [[NSBundle mainBundle]
|
||||
dataForResource:[NSString stringWithUTF8String:rom.file_name.c_str()]
|
||||
withExtension:nil
|
||||
subdirectory:subdirectory];
|
||||
}
|
||||
|
||||
// Store an appropriate result, accumulating a list of the missing if requested.
|
||||
if(!fileData) {
|
||||
results.emplace_back(nullptr);
|
||||
|
||||
if(missing_roms) {
|
||||
missing_roms->push_back(rom);
|
||||
}
|
||||
}
|
||||
else {
|
||||
std::unique_ptr<std::vector<std::uint8_t>> data(new std::vector<std::uint8_t>);
|
||||
*data = fileData.stdVector8;
|
||||
|
||||
17
OSBindings/Mac/Clock Signal/Machine/NSData+CRC32.h
Normal file
17
OSBindings/Mac/Clock Signal/Machine/NSData+CRC32.h
Normal file
@@ -0,0 +1,17 @@
|
||||
//
|
||||
// NSData+CRC32.m
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 22/07/2019.
|
||||
// Copyright 2019 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
@interface NSData (CRC32)
|
||||
|
||||
@property(nonnull, nonatomic, readonly) NSNumber *crc32;
|
||||
|
||||
@end
|
||||
19
OSBindings/Mac/Clock Signal/Machine/NSData+CRC32.m
Normal file
19
OSBindings/Mac/Clock Signal/Machine/NSData+CRC32.m
Normal file
@@ -0,0 +1,19 @@
|
||||
//
|
||||
// NSData+CRC32.m
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 22/07/2019.
|
||||
// Copyright 2019 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#import "NSData+CRC32.h"
|
||||
|
||||
#include <zlib.h>
|
||||
|
||||
@implementation NSData (StdVector)
|
||||
|
||||
- (NSNumber *)crc32 {
|
||||
return @(crc32(crc32(0, Z_NULL, 0), self.bytes, (uInt)self.length));
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -29,6 +29,13 @@ typedef NS_ENUM(NSInteger, CSMachineCPCModel) {
|
||||
CSMachineCPCModel6128
|
||||
};
|
||||
|
||||
typedef NS_ENUM(NSInteger, CSMachineMacintoshModel) {
|
||||
CSMachineMacintoshModel128k,
|
||||
CSMachineMacintoshModel512k,
|
||||
CSMachineMacintoshModel512ke,
|
||||
CSMachineMacintoshModelPlus,
|
||||
};
|
||||
|
||||
typedef NS_ENUM(NSInteger, CSMachineOricModel) {
|
||||
CSMachineOricModelOric1,
|
||||
CSMachineOricModelOricAtmos,
|
||||
@@ -69,6 +76,7 @@ typedef int Kilobytes;
|
||||
- (instancetype)initWithZX80MemorySize:(Kilobytes)memorySize useZX81ROM:(BOOL)useZX81ROM;
|
||||
- (instancetype)initWithZX81MemorySize:(Kilobytes)memorySize;
|
||||
- (instancetype)initWithAppleIIModel:(CSMachineAppleIIModel)model diskController:(CSMachineAppleIIDiskController)diskController;
|
||||
- (instancetype)initWithMacintoshModel:(CSMachineMacintoshModel)model;
|
||||
|
||||
@property(nonatomic, readonly) NSString *optionsPanelNibName;
|
||||
@property(nonatomic, readonly) NSString *displayName;
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
#include "../../../../../Analyser/Static/AmstradCPC/Target.hpp"
|
||||
#include "../../../../../Analyser/Static/AppleII/Target.hpp"
|
||||
#include "../../../../../Analyser/Static/Commodore/Target.hpp"
|
||||
#include "../../../../../Analyser/Static/Macintosh/Target.hpp"
|
||||
#include "../../../../../Analyser/Static/MSX/Target.hpp"
|
||||
#include "../../../../../Analyser/Static/Oric/Target.hpp"
|
||||
#include "../../../../../Analyser/Static/ZX8081/Target.hpp"
|
||||
@@ -188,7 +189,27 @@ static Analyser::Static::ZX8081::Target::MemoryModel ZX8081MemoryModelFromSize(K
|
||||
_targets.push_back(std::move(target));
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (instancetype)initWithMacintoshModel:(CSMachineMacintoshModel)model {
|
||||
self = [super init];
|
||||
if(self) {
|
||||
using Target = Analyser::Static::Macintosh::Target;
|
||||
std::unique_ptr<Target> target(new Target);
|
||||
target->machine = Analyser::Machine::Macintosh;
|
||||
|
||||
using Model = Target::Model;
|
||||
switch(model) {
|
||||
default:
|
||||
case CSMachineMacintoshModel128k: target->model = Model::Mac128k; break;
|
||||
case CSMachineMacintoshModel512k: target->model = Model::Mac512k; break;
|
||||
case CSMachineMacintoshModel512ke: target->model = Model::Mac512ke; break;
|
||||
case CSMachineMacintoshModelPlus: target->model = Model::MacPlus; break;
|
||||
}
|
||||
|
||||
_targets.push_back(std::move(target));
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (NSString *)optionsPanelNibName {
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="14460.31" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
|
||||
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="14490.70" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
|
||||
<dependencies>
|
||||
<deployment identifier="macosx"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="14460.31"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="14490.70"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<objects>
|
||||
@@ -17,14 +17,14 @@
|
||||
<window title="Window" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" releasedWhenClosed="NO" visibleAtLaunch="NO" animationBehavior="default" titleVisibility="hidden" id="QvC-M9-y7g">
|
||||
<windowStyleMask key="styleMask" titled="YES" documentModal="YES"/>
|
||||
<windowPositionMask key="initialPositionMask" leftStrut="YES" rightStrut="YES" topStrut="YES" bottomStrut="YES"/>
|
||||
<rect key="contentRect" x="196" y="240" width="600" height="205"/>
|
||||
<rect key="contentRect" x="196" y="240" width="650" height="205"/>
|
||||
<rect key="screenRect" x="0.0" y="0.0" width="1440" height="900"/>
|
||||
<view key="contentView" wantsLayer="YES" id="EiT-Mj-1SZ">
|
||||
<rect key="frame" x="0.0" y="0.0" width="600" height="205"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="650" height="205"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="hKn-1l-OSN">
|
||||
<rect key="frame" x="499" y="13" width="87" height="32"/>
|
||||
<rect key="frame" x="549" y="13" width="87" height="32"/>
|
||||
<buttonCell key="cell" type="push" title="Choose" bezelStyle="rounded" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="MnM-xo-4Qa">
|
||||
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
|
||||
<font key="font" metaFont="system"/>
|
||||
@@ -37,7 +37,7 @@ DQ
|
||||
</connections>
|
||||
</button>
|
||||
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="JQy-Cj-AOK">
|
||||
<rect key="frame" x="418" y="13" width="82" height="32"/>
|
||||
<rect key="frame" x="468" y="13" width="82" height="32"/>
|
||||
<buttonCell key="cell" type="push" title="Cancel" bezelStyle="rounded" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="sub-rB-Req">
|
||||
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
|
||||
<font key="font" metaFont="system"/>
|
||||
@@ -59,12 +59,12 @@ Gw
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
<tabView translatesAutoresizingMaskIntoConstraints="NO" id="VUb-QG-x7c">
|
||||
<rect key="frame" x="13" y="51" width="574" height="140"/>
|
||||
<rect key="frame" x="13" y="51" width="624" height="140"/>
|
||||
<font key="font" metaFont="system"/>
|
||||
<tabViewItems>
|
||||
<tabViewItem label="Apple II" identifier="appleii" id="P59-QG-LOa">
|
||||
<view key="view" id="dHz-Yv-GNq">
|
||||
<rect key="frame" x="10" y="33" width="554" height="94"/>
|
||||
<rect key="frame" x="10" y="33" width="604" height="94"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="V5Z-dX-Ns4">
|
||||
@@ -130,7 +130,7 @@ Gw
|
||||
</tabViewItem>
|
||||
<tabViewItem label="Amstrad CPC" identifier="cpc" id="JmB-OF-xcM">
|
||||
<view key="view" id="5zS-Nj-Ynx">
|
||||
<rect key="frame" x="10" y="33" width="554" height="94"/>
|
||||
<rect key="frame" x="10" y="33" width="604" height="94"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<popUpButton verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="00d-sg-Krh">
|
||||
@@ -168,18 +168,18 @@ Gw
|
||||
</tabViewItem>
|
||||
<tabViewItem label="Electron" identifier="electron" id="muc-z9-Vqc">
|
||||
<view key="view" id="SRc-2D-95G">
|
||||
<rect key="frame" x="10" y="33" width="554" height="93"/>
|
||||
<rect key="frame" x="10" y="33" width="604" height="94"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="JqM-IK-FMP">
|
||||
<rect key="frame" x="15" y="74" width="164" height="18"/>
|
||||
<rect key="frame" x="15" y="75" width="164" height="18"/>
|
||||
<buttonCell key="cell" type="check" title="With Disk Filing System" bezelStyle="regularSquare" imagePosition="left" state="on" inset="2" id="tpW-5C-xKp">
|
||||
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
|
||||
<font key="font" metaFont="system"/>
|
||||
</buttonCell>
|
||||
</button>
|
||||
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="945-wU-JOH">
|
||||
<rect key="frame" x="15" y="54" width="228" height="18"/>
|
||||
<rect key="frame" x="15" y="55" width="228" height="18"/>
|
||||
<buttonCell key="cell" type="check" title="With Advanced Disk Filing System" bezelStyle="regularSquare" imagePosition="left" state="on" inset="2" id="S0c-Jg-7Pu">
|
||||
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
|
||||
<font key="font" metaFont="system"/>
|
||||
@@ -197,9 +197,31 @@ Gw
|
||||
</constraints>
|
||||
</view>
|
||||
</tabViewItem>
|
||||
<tabViewItem label="Macintosh" identifier="mac" id="lmR-z3-xSm">
|
||||
<view key="view" id="7Yf-vi-Q0W">
|
||||
<rect key="frame" x="10" y="33" width="604" height="94"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="F6e-UC-25u">
|
||||
<rect key="frame" x="15" y="74" width="574" height="17"/>
|
||||
<textFieldCell key="cell" lineBreakMode="clipping" title="At present Clock Signal emulates only the Macintosh 512ke." id="IGV-Yp-6Af">
|
||||
<font key="font" usesAppearanceFont="YES"/>
|
||||
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
</subviews>
|
||||
<constraints>
|
||||
<constraint firstAttribute="trailing" secondItem="F6e-UC-25u" secondAttribute="trailing" constant="17" id="42z-hS-aPq"/>
|
||||
<constraint firstItem="F6e-UC-25u" firstAttribute="leading" secondItem="7Yf-vi-Q0W" secondAttribute="leading" constant="17" id="bIg-C3-xdz"/>
|
||||
<constraint firstItem="F6e-UC-25u" firstAttribute="top" secondItem="7Yf-vi-Q0W" secondAttribute="top" constant="3" id="cxs-OP-oH5"/>
|
||||
<constraint firstAttribute="bottom" relation="greaterThanOrEqual" secondItem="F6e-UC-25u" secondAttribute="bottom" constant="17" id="vpF-ER-pmD"/>
|
||||
</constraints>
|
||||
</view>
|
||||
</tabViewItem>
|
||||
<tabViewItem label="MSX" identifier="msx" id="6SR-DY-zdI">
|
||||
<view key="view" id="mWD-An-tR7">
|
||||
<rect key="frame" x="10" y="33" width="554" height="94"/>
|
||||
<rect key="frame" x="10" y="33" width="604" height="94"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="8xT-Pr-8SE">
|
||||
@@ -233,6 +255,7 @@ Gw
|
||||
</textField>
|
||||
</subviews>
|
||||
<constraints>
|
||||
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="LG6-mP-SeG" secondAttribute="trailing" constant="17" id="0Oc-n7-gaM"/>
|
||||
<constraint firstItem="8xT-Pr-8SE" firstAttribute="top" secondItem="LG6-mP-SeG" secondAttribute="bottom" constant="6" id="LBt-4m-GDc"/>
|
||||
<constraint firstItem="LG6-mP-SeG" firstAttribute="top" secondItem="mWD-An-tR7" secondAttribute="top" constant="3" id="bcb-ZZ-VpQ"/>
|
||||
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="8xT-Pr-8SE" secondAttribute="trailing" constant="17" id="l8P-UW-8ig"/>
|
||||
@@ -246,7 +269,7 @@ Gw
|
||||
</tabViewItem>
|
||||
<tabViewItem label="Oric" identifier="oric" id="NSx-DC-p4M">
|
||||
<view key="view" id="sOR-e0-8iZ">
|
||||
<rect key="frame" x="10" y="33" width="554" height="94"/>
|
||||
<rect key="frame" x="10" y="33" width="604" height="94"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="0ct-tf-uRH">
|
||||
@@ -311,7 +334,7 @@ Gw
|
||||
</tabViewItem>
|
||||
<tabViewItem label="Vic-20" identifier="vic20" id="cyO-PU-hSU">
|
||||
<view key="view" id="fLI-XB-QCr">
|
||||
<rect key="frame" x="10" y="33" width="554" height="94"/>
|
||||
<rect key="frame" x="10" y="33" width="604" height="94"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<popUpButton verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="ueK-gq-gaF">
|
||||
@@ -388,11 +411,11 @@ Gw
|
||||
</tabViewItem>
|
||||
<tabViewItem label="ZX80" identifier="zx80" id="tMH-kF-GUz">
|
||||
<view key="view" id="8hL-Vn-Hg0">
|
||||
<rect key="frame" x="10" y="33" width="554" height="93"/>
|
||||
<rect key="frame" x="10" y="33" width="554" height="94"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<popUpButton verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="I1a-Eu-5UB">
|
||||
<rect key="frame" x="106" y="66" width="115" height="25"/>
|
||||
<rect key="frame" x="106" y="67" width="115" height="25"/>
|
||||
<popUpButtonCell key="cell" type="push" title="Unexpanded" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" imageScaling="proportionallyDown" inset="2" selectedItem="4Sa-jR-xOd" id="B8M-do-Yod">
|
||||
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
|
||||
<font key="font" metaFont="menu"/>
|
||||
@@ -406,7 +429,7 @@ Gw
|
||||
</popUpButtonCell>
|
||||
</popUpButton>
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="NCX-4e-lSu">
|
||||
<rect key="frame" x="15" y="71" width="87" height="17"/>
|
||||
<rect key="frame" x="15" y="72" width="87" height="17"/>
|
||||
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Memory Size:" id="e6x-TE-OC5">
|
||||
<font key="font" metaFont="system"/>
|
||||
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
|
||||
@@ -414,7 +437,7 @@ Gw
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="ReP-bV-Thu">
|
||||
<rect key="frame" x="15" y="47" width="114" height="18"/>
|
||||
<rect key="frame" x="15" y="48" width="114" height="18"/>
|
||||
<buttonCell key="cell" type="check" title="Use ZX81 ROM" bezelStyle="regularSquare" imagePosition="left" state="on" inset="2" id="VQH-nv-Pfm">
|
||||
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
|
||||
<font key="font" metaFont="system"/>
|
||||
@@ -436,11 +459,11 @@ Gw
|
||||
</tabViewItem>
|
||||
<tabViewItem label="ZX81" identifier="zx81" id="Wnn-nQ-gZ6">
|
||||
<view key="view" id="bmd-gL-gzT">
|
||||
<rect key="frame" x="10" y="33" width="554" height="93"/>
|
||||
<rect key="frame" x="10" y="33" width="554" height="94"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<popUpButton verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="5aO-UX-HnX">
|
||||
<rect key="frame" x="106" y="66" width="115" height="25"/>
|
||||
<rect key="frame" x="106" y="67" width="115" height="25"/>
|
||||
<popUpButtonCell key="cell" type="push" title="Unexpanded" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" imageScaling="proportionallyDown" inset="2" selectedItem="7QC-Ij-hES" id="d3W-Gl-3Mf">
|
||||
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
|
||||
<font key="font" metaFont="menu"/>
|
||||
@@ -454,7 +477,7 @@ Gw
|
||||
</popUpButtonCell>
|
||||
</popUpButton>
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="8tU-73-XEE">
|
||||
<rect key="frame" x="15" y="71" width="87" height="17"/>
|
||||
<rect key="frame" x="15" y="72" width="87" height="17"/>
|
||||
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Memory Size:" id="z4b-oR-Yl2">
|
||||
<font key="font" metaFont="system"/>
|
||||
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
|
||||
@@ -490,7 +513,7 @@ Gw
|
||||
<constraint firstItem="VUb-QG-x7c" firstAttribute="top" secondItem="EiT-Mj-1SZ" secondAttribute="top" constant="20" id="zT3-Ea-QQJ"/>
|
||||
</constraints>
|
||||
</view>
|
||||
<point key="canvasLocation" x="34" y="89"/>
|
||||
<point key="canvasLocation" x="34" y="88.5"/>
|
||||
</window>
|
||||
<customObject id="192-Eb-Rpg" customClass="MachinePicker" customModule="Clock_Signal" customModuleProvider="target">
|
||||
<connections>
|
||||
|
||||
@@ -157,6 +157,9 @@ class MachinePicker: NSObject {
|
||||
default: return CSStaticAnalyser(amstradCPCModel: .model6128)
|
||||
}
|
||||
|
||||
case "mac":
|
||||
return CSStaticAnalyser(macintoshModel: .model512ke)
|
||||
|
||||
case "msx":
|
||||
let hasDiskDrive = msxHasDiskDriveButton!.state == .on
|
||||
switch msxRegionButton!.selectedItem?.tag {
|
||||
|
||||
27
OSBindings/Mac/Clock Signal/ROMRequester/CSROMReceiverView.h
Normal file
27
OSBindings/Mac/Clock Signal/ROMRequester/CSROMReceiverView.h
Normal file
@@ -0,0 +1,27 @@
|
||||
//
|
||||
// CSROMReceiverView.h
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 22/07/2019.
|
||||
// Copyright © 2019 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Cocoa/Cocoa.h>
|
||||
|
||||
@class CSROMReceiverView;
|
||||
|
||||
@protocol CSROMReciverViewDelegate
|
||||
/*!
|
||||
Announces receipt of a file by drag and drop to the delegate.
|
||||
@param view The view making the request.
|
||||
@param URL The file URL of the received file.
|
||||
*/
|
||||
- (void)romReceiverView:(nonnull CSROMReceiverView *)view didReceiveFileAtURL:(nonnull NSURL *)URL;
|
||||
|
||||
@end
|
||||
|
||||
@interface CSROMReceiverView : NSView
|
||||
|
||||
@property(nonatomic, weak, nullable) id <CSROMReciverViewDelegate> delegate;
|
||||
|
||||
@end
|
||||
40
OSBindings/Mac/Clock Signal/ROMRequester/CSROMReceiverView.m
Normal file
40
OSBindings/Mac/Clock Signal/ROMRequester/CSROMReceiverView.m
Normal file
@@ -0,0 +1,40 @@
|
||||
//
|
||||
// CSROMReceiverView.m
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 22/07/2019.
|
||||
// Copyright © 2019 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#import "CSROMReceiverView.h"
|
||||
|
||||
@interface CSROMReceiverView () <NSDraggingDestination>
|
||||
@end
|
||||
|
||||
|
||||
@implementation CSROMReceiverView
|
||||
|
||||
- (void)awakeFromNib {
|
||||
[super awakeFromNib];
|
||||
|
||||
// Accept file URLs by drag and drop.
|
||||
[self registerForDraggedTypes:@[(__bridge NSString *)kUTTypeFileURL]];
|
||||
}
|
||||
|
||||
#pragma mark - NSDraggingDestination
|
||||
|
||||
- (BOOL)performDragOperation:(id <NSDraggingInfo>)sender {
|
||||
// Just forward the URLs.
|
||||
for(NSPasteboardItem *item in [[sender draggingPasteboard] pasteboardItems]) {
|
||||
NSURL *URL = [NSURL URLWithString:[item stringForType:(__bridge NSString *)kUTTypeFileURL]];
|
||||
[self.delegate romReceiverView:self didReceiveFileAtURL:URL];
|
||||
}
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (NSDragOperation)draggingEntered:(id < NSDraggingInfo >)sender {
|
||||
// The emulator will take a copy of any deposited ROM files.
|
||||
return NSDragOperationCopy;
|
||||
}
|
||||
|
||||
@end
|
||||
75
OSBindings/Mac/Clock Signal/ROMRequester/ROMRequester.xib
Normal file
75
OSBindings/Mac/Clock Signal/ROMRequester/ROMRequester.xib
Normal file
@@ -0,0 +1,75 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="14490.70" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
|
||||
<dependencies>
|
||||
<deployment identifier="macosx"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="14490.70"/>
|
||||
<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="romReceiverErrorField" destination="Bia-0m-GxK" id="wnH-Kz-uXD"/>
|
||||
<outlet property="romReceiverView" destination="EiT-Mj-1SZ" id="Y2M-Qd-i7y"/>
|
||||
<outlet property="romRequesterPanel" destination="QvC-M9-y7g" id="saI-YH-9NP"/>
|
||||
<outlet property="romRequesterText" destination="5qG-I3-Qav" id="J9M-T8-Xyy"/>
|
||||
</connections>
|
||||
</customObject>
|
||||
<customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
|
||||
<customObject id="-3" userLabel="Application" customClass="NSObject"/>
|
||||
<window title="Window" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" releasedWhenClosed="NO" visibleAtLaunch="NO" animationBehavior="default" titleVisibility="hidden" id="QvC-M9-y7g">
|
||||
<windowStyleMask key="styleMask" titled="YES"/>
|
||||
<windowPositionMask key="initialPositionMask" leftStrut="YES" rightStrut="YES" topStrut="YES" bottomStrut="YES"/>
|
||||
<rect key="contentRect" x="196" y="240" width="480" height="270"/>
|
||||
<rect key="screenRect" x="0.0" y="0.0" width="1440" height="900"/>
|
||||
<view key="contentView" wantsLayer="YES" id="EiT-Mj-1SZ" customClass="CSROMReceiverView">
|
||||
<rect key="frame" x="0.0" y="0.0" width="480" height="270"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<textField verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" textCompletion="NO" translatesAutoresizingMaskIntoConstraints="NO" id="5qG-I3-Qav">
|
||||
<rect key="frame" x="18" y="148" width="444" height="102"/>
|
||||
<textFieldCell key="cell" enabled="NO" allowsUndo="NO" id="itJ-2T-0ia">
|
||||
<font key="font" usesAppearanceFont="YES"/>
|
||||
<string key="title">Clock Signal requires you to provide images of the system ROMs for this machine. They will be stored permanently; you need do this only once.
Please drag and drop the following over this view:
|
||||
|
||||
</string>
|
||||
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="nvl-dm-8bK">
|
||||
<rect key="frame" x="384" y="13" width="82" height="32"/>
|
||||
<buttonCell key="cell" type="push" title="Cancel" bezelStyle="rounded" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="oS1-Pk-LsO">
|
||||
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
|
||||
<font key="font" metaFont="system"/>
|
||||
<string key="keyEquivalent" base64-UTF8="YES">
|
||||
DQ
|
||||
</string>
|
||||
</buttonCell>
|
||||
<connections>
|
||||
<action selector="cancelRequestROMs:" target="-2" id="58q-yi-dlv"/>
|
||||
</connections>
|
||||
</button>
|
||||
<textField verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" textCompletion="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Bia-0m-GxK">
|
||||
<rect key="frame" x="20" y="61" width="442" height="17"/>
|
||||
<textFieldCell key="cell" allowsUndo="NO" alignment="center" title="Multiline Label" drawsBackground="YES" id="8jl-xs-LjP">
|
||||
<font key="font" metaFont="message"/>
|
||||
<color key="textColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="tertiaryLabelColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
</subviews>
|
||||
<constraints>
|
||||
<constraint firstItem="Bia-0m-GxK" firstAttribute="leading" secondItem="EiT-Mj-1SZ" secondAttribute="leading" constant="20" id="9Lj-3p-7Vr"/>
|
||||
<constraint firstAttribute="trailing" secondItem="5qG-I3-Qav" secondAttribute="trailing" constant="20" id="AwT-WO-Nyf"/>
|
||||
<constraint firstAttribute="bottom" secondItem="nvl-dm-8bK" secondAttribute="bottom" constant="20" id="BU0-5T-YRH"/>
|
||||
<constraint firstAttribute="bottom" secondItem="Bia-0m-GxK" secondAttribute="bottom" constant="61" id="ENn-7a-NGa"/>
|
||||
<constraint firstAttribute="trailing" secondItem="Bia-0m-GxK" secondAttribute="trailing" constant="18" id="UUy-bf-87w"/>
|
||||
<constraint firstItem="5qG-I3-Qav" firstAttribute="leading" secondItem="EiT-Mj-1SZ" secondAttribute="leading" constant="20" id="bRM-z1-cRf"/>
|
||||
<constraint firstAttribute="trailing" secondItem="nvl-dm-8bK" secondAttribute="trailing" constant="20" id="eQI-Nj-ane"/>
|
||||
<constraint firstItem="nvl-dm-8bK" firstAttribute="top" relation="greaterThanOrEqual" secondItem="5qG-I3-Qav" secondAttribute="bottom" constant="17" id="plB-x4-He5"/>
|
||||
<constraint firstItem="5qG-I3-Qav" firstAttribute="top" secondItem="EiT-Mj-1SZ" secondAttribute="top" constant="20" id="qeC-Rh-hmr"/>
|
||||
</constraints>
|
||||
</view>
|
||||
</window>
|
||||
</objects>
|
||||
</document>
|
||||
@@ -63,6 +63,29 @@ typedef NS_ENUM(NSInteger, CSOpenGLViewRedrawEvent) {
|
||||
*/
|
||||
- (void)paste:(nonnull id)sender;
|
||||
|
||||
@optional
|
||||
|
||||
/*!
|
||||
Supplies a mouse moved event to the delegate. This functions only if
|
||||
shouldCaptureMouse is set to YES, in which case the view will ensure it captures
|
||||
the mouse and returns only relative motion
|
||||
(Cf. CGAssociateMouseAndMouseCursorPosition). It will also elide mouseDragged:
|
||||
(and rightMouseDragged:, etc) and mouseMoved: events.
|
||||
*/
|
||||
- (void)mouseMoved:(nonnull NSEvent *)event;
|
||||
|
||||
/*!
|
||||
Supplies a mouse button down event. This elides mouseDown, rightMouseDown and otherMouseDown.
|
||||
@c shouldCaptureMouse must be set to @c YES to receive these events.
|
||||
*/
|
||||
- (void)mouseDown:(nonnull NSEvent *)event;
|
||||
|
||||
/*!
|
||||
Supplies a mouse button up event. This elides mouseUp, rightMouseUp and otherMouseUp.
|
||||
@c shouldCaptureMouse must be set to @c YES to receive these events.
|
||||
*/
|
||||
- (void)mouseUp:(nonnull NSEvent *)event;
|
||||
|
||||
@end
|
||||
|
||||
/*!
|
||||
@@ -74,6 +97,8 @@ typedef NS_ENUM(NSInteger, CSOpenGLViewRedrawEvent) {
|
||||
@property (atomic, weak, nullable) id <CSOpenGLViewDelegate> delegate;
|
||||
@property (nonatomic, weak, nullable) id <CSOpenGLViewResponderDelegate> responderDelegate;
|
||||
|
||||
@property (nonatomic, assign) BOOL shouldCaptureMouse;
|
||||
|
||||
/*!
|
||||
Ends the timer tracking time; should be called prior to giving up the last owning reference
|
||||
to ensure that any retain cycles implied by the timer are resolved.
|
||||
@@ -89,4 +114,9 @@ typedef NS_ENUM(NSInteger, CSOpenGLViewRedrawEvent) {
|
||||
*/
|
||||
- (void)performWithGLContext:(nonnull dispatch_block_t)action;
|
||||
|
||||
/*!
|
||||
Instructs that the mouse cursor, if currently captured, should be released.
|
||||
*/
|
||||
- (void)releaseMouse;
|
||||
|
||||
@end
|
||||
|
||||
@@ -19,9 +19,12 @@
|
||||
|
||||
NSTrackingArea *_mouseTrackingArea;
|
||||
NSTimer *_mouseHideTimer;
|
||||
BOOL _mouseIsCaptured;
|
||||
}
|
||||
|
||||
- (void)prepareOpenGL {
|
||||
[super prepareOpenGL];
|
||||
|
||||
// Synchronize buffer swaps with vertical refresh rate
|
||||
GLint swapInt = 1;
|
||||
[[self openGLContext] setValues:&swapInt forParameter:NSOpenGLCPSwapInterval];
|
||||
@@ -146,6 +149,13 @@ static CVReturn DisplayLinkCallback(CVDisplayLinkRef displayLink, const CVTimeSt
|
||||
|
||||
- (void)flagsChanged:(NSEvent *)theEvent {
|
||||
[self.responderDelegate flagsChanged:theEvent];
|
||||
|
||||
// Release the mouse upon a control + command.
|
||||
if(_mouseIsCaptured &&
|
||||
theEvent.modifierFlags & NSEventModifierFlagControl &&
|
||||
theEvent.modifierFlags & NSEventModifierFlagCommand) {
|
||||
[self releaseMouse];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)paste:(id)sender {
|
||||
@@ -168,6 +178,10 @@ static CVReturn DisplayLinkCallback(CVDisplayLinkRef displayLink, const CVTimeSt
|
||||
|
||||
#pragma mark - Mouse hiding
|
||||
|
||||
- (void)setShouldCaptureMouse:(BOOL)shouldCaptureMouse {
|
||||
_shouldCaptureMouse = shouldCaptureMouse;
|
||||
}
|
||||
|
||||
- (void)updateTrackingAreas {
|
||||
[super updateTrackingAreas];
|
||||
|
||||
@@ -183,9 +197,14 @@ static CVReturn DisplayLinkCallback(CVDisplayLinkRef displayLink, const CVTimeSt
|
||||
[self addTrackingArea:_mouseTrackingArea];
|
||||
}
|
||||
|
||||
- (void)mouseMoved:(NSEvent *)event {
|
||||
[super mouseMoved:event];
|
||||
[self scheduleMouseHide];
|
||||
- (void)scheduleMouseHide {
|
||||
if(!self.shouldCaptureMouse) {
|
||||
[_mouseHideTimer invalidate];
|
||||
|
||||
_mouseHideTimer = [NSTimer scheduledTimerWithTimeInterval:3.0 repeats:NO block:^(NSTimer * _Nonnull timer) {
|
||||
[NSCursor setHiddenUntilMouseMoves:YES];
|
||||
}];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)mouseEntered:(NSEvent *)event {
|
||||
@@ -193,18 +212,119 @@ static CVReturn DisplayLinkCallback(CVDisplayLinkRef displayLink, const CVTimeSt
|
||||
[self scheduleMouseHide];
|
||||
}
|
||||
|
||||
- (void)scheduleMouseHide {
|
||||
[_mouseHideTimer invalidate];
|
||||
_mouseHideTimer = [NSTimer scheduledTimerWithTimeInterval:3.0 repeats:NO block:^(NSTimer * _Nonnull timer) {
|
||||
[NSCursor setHiddenUntilMouseMoves:YES];
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)mouseExited:(NSEvent *)event {
|
||||
[super mouseExited:event];
|
||||
|
||||
[_mouseHideTimer invalidate];
|
||||
_mouseHideTimer = nil;
|
||||
}
|
||||
|
||||
- (void)releaseMouse {
|
||||
if(_mouseIsCaptured) {
|
||||
_mouseIsCaptured = NO;
|
||||
CGAssociateMouseAndMouseCursorPosition(true);
|
||||
[NSCursor unhide];
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - Mouse motion
|
||||
|
||||
- (void)applyMouseMotion:(NSEvent *)event {
|
||||
if(!self.shouldCaptureMouse) {
|
||||
// Mouse capture is off, so don't play games with the cursor, just schedule it to
|
||||
// hide in the near future.
|
||||
[self scheduleMouseHide];
|
||||
} else {
|
||||
if(_mouseIsCaptured) {
|
||||
// Mouse capture is on, so move the cursor back to the middle of the window, and
|
||||
// forward the deltas to the listener.
|
||||
//
|
||||
// TODO: should I really need to invert the y coordinate myself? It suggests I
|
||||
// might have an error in mapping here.
|
||||
const NSPoint windowCentre = [self convertPoint:CGPointMake(self.bounds.size.width * 0.5, self.bounds.size.height * 0.5) toView:nil];
|
||||
const NSPoint screenCentre = [self.window convertPointToScreen:windowCentre];
|
||||
const CGRect screenFrame = self.window.screen.frame;
|
||||
CGWarpMouseCursorPosition(NSMakePoint(
|
||||
screenFrame.origin.x + screenCentre.x,
|
||||
screenFrame.origin.y + screenFrame.size.height - screenCentre.y
|
||||
));
|
||||
|
||||
[self.responderDelegate mouseMoved:event];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)mouseDragged:(NSEvent *)event {
|
||||
[self applyMouseMotion:event];
|
||||
[super mouseDragged:event];
|
||||
}
|
||||
|
||||
- (void)rightMouseDragged:(NSEvent *)event {
|
||||
[self applyMouseMotion:event];
|
||||
[super rightMouseDragged:event];
|
||||
}
|
||||
|
||||
- (void)otherMouseDragged:(NSEvent *)event {
|
||||
[self applyMouseMotion:event];
|
||||
[super otherMouseDragged:event];
|
||||
}
|
||||
|
||||
- (void)mouseMoved:(NSEvent *)event {
|
||||
[self applyMouseMotion:event];
|
||||
[super mouseMoved:event];
|
||||
}
|
||||
|
||||
#pragma mark - Mouse buttons
|
||||
|
||||
- (void)applyButtonDown:(NSEvent *)event {
|
||||
if(self.shouldCaptureMouse) {
|
||||
if(!_mouseIsCaptured) {
|
||||
_mouseIsCaptured = YES;
|
||||
[NSCursor hide];
|
||||
CGAssociateMouseAndMouseCursorPosition(false);
|
||||
|
||||
// Don't report the first click to the delegate; treat that as merely
|
||||
// an invitation to capture the cursor.
|
||||
return;
|
||||
}
|
||||
|
||||
[self.responderDelegate mouseDown:event];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)applyButtonUp:(NSEvent *)event {
|
||||
if(self.shouldCaptureMouse) {
|
||||
[self.responderDelegate mouseUp:event];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)mouseDown:(NSEvent *)event {
|
||||
[self applyButtonDown:event];
|
||||
[super mouseDown:event];
|
||||
}
|
||||
|
||||
- (void)rightMouseDown:(NSEvent *)event {
|
||||
[self applyButtonDown:event];
|
||||
[super rightMouseDown:event];
|
||||
}
|
||||
|
||||
- (void)otherMouseDown:(NSEvent *)event {
|
||||
[self applyButtonDown:event];
|
||||
[super otherMouseDown:event];
|
||||
}
|
||||
|
||||
- (void)mouseUp:(NSEvent *)event {
|
||||
[self applyButtonUp:event];
|
||||
[super mouseUp:event];
|
||||
}
|
||||
|
||||
- (void)rightMouseUp:(NSEvent *)event {
|
||||
[self applyButtonUp:event];
|
||||
[super rightMouseUp:event];
|
||||
}
|
||||
|
||||
- (void)otherMouseUp:(NSEvent *)event {
|
||||
[self applyButtonUp:event];
|
||||
[super otherMouseUp:event];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -11,118 +11,244 @@ import Foundation
|
||||
|
||||
class MOS6522Tests: XCTestCase {
|
||||
|
||||
fileprivate func with6522(_ action: (MOS6522Bridge) -> ()) {
|
||||
let bridge = MOS6522Bridge()
|
||||
action(bridge)
|
||||
private var m6522: MOS6522Bridge!
|
||||
|
||||
override func setUp() {
|
||||
m6522 = MOS6522Bridge()
|
||||
}
|
||||
|
||||
// MARK: Timer tests
|
||||
|
||||
func testTimerCount() {
|
||||
with6522 {
|
||||
// set timer 1 to a value of $000a
|
||||
$0.setValue(10, forRegister: 4)
|
||||
$0.setValue(0, forRegister: 5)
|
||||
// set timer 1 to a value of m652200a
|
||||
m6522.setValue(10, forRegister: 4)
|
||||
m6522.setValue(0, forRegister: 5)
|
||||
|
||||
// complete the setting cycle
|
||||
$0.run(forHalfCycles: 2)
|
||||
// complete the setting cycle
|
||||
m6522.run(forHalfCycles: 2)
|
||||
|
||||
// run for 5 cycles
|
||||
$0.run(forHalfCycles: 10)
|
||||
// run for 5 cycles
|
||||
m6522.run(forHalfCycles: 10)
|
||||
|
||||
// check that the timer has gone down by 5
|
||||
XCTAssert($0.value(forRegister: 4) == 5, "Low order byte should be 5; was \($0.value(forRegister: 4))")
|
||||
XCTAssert($0.value(forRegister: 5) == 0, "High order byte should be 0; was \($0.value(forRegister: 5))")
|
||||
}
|
||||
// check that the timer has gone down by 5
|
||||
XCTAssert(m6522.value(forRegister: 4) == 5, "Low order byte should be 5; was \(m6522.value(forRegister: 4))")
|
||||
XCTAssert(m6522.value(forRegister: 5) == 0, "High order byte should be 0; was \(m6522.value(forRegister: 5))")
|
||||
}
|
||||
|
||||
func testTimerLatches() {
|
||||
with6522 {
|
||||
// set timer 2 to $1020
|
||||
$0.setValue(0x10, forRegister: 8)
|
||||
$0.setValue(0x20, forRegister: 9)
|
||||
// set timer 2 to $1020
|
||||
m6522.setValue(0x10, forRegister: 8)
|
||||
m6522.setValue(0x20, forRegister: 9)
|
||||
|
||||
// change the low-byte latch
|
||||
$0.setValue(0x40, forRegister: 8)
|
||||
// change the low-byte latch
|
||||
m6522.setValue(0x40, forRegister: 8)
|
||||
|
||||
// complete the cycle
|
||||
$0.run(forHalfCycles: 2)
|
||||
// complete the cycle
|
||||
m6522.run(forHalfCycles: 2)
|
||||
|
||||
// chek that the new latched value hasn't been copied
|
||||
XCTAssert($0.value(forRegister: 8) == 0x10, "Low order byte should be 0x10; was \($0.value(forRegister: 8))")
|
||||
XCTAssert($0.value(forRegister: 9) == 0x20, "High order byte should be 0x20; was \($0.value(forRegister: 9))")
|
||||
// chek that the new latched value hasn't been copied
|
||||
XCTAssert(m6522.value(forRegister: 8) == 0x10, "Low order byte should be 0x10; was \(m6522.value(forRegister: 8))")
|
||||
XCTAssert(m6522.value(forRegister: 9) == 0x20, "High order byte should be 0x20; was \(m6522.value(forRegister: 9))")
|
||||
|
||||
// write the low-byte latch
|
||||
$0.setValue(0x50, forRegister: 9)
|
||||
// write the low-byte latch
|
||||
m6522.setValue(0x50, forRegister: 9)
|
||||
|
||||
// complete the cycle
|
||||
$0.run(forHalfCycles: 2)
|
||||
// complete the cycle
|
||||
m6522.run(forHalfCycles: 2)
|
||||
|
||||
// chek that the latched value has been copied
|
||||
XCTAssert($0.value(forRegister: 8) == 0x40, "Low order byte should be 0x50; was \($0.value(forRegister: 8))")
|
||||
XCTAssert($0.value(forRegister: 9) == 0x50, "High order byte should be 0x40; was \($0.value(forRegister: 9))")
|
||||
}
|
||||
// chek that the latched value has been copied
|
||||
XCTAssert(m6522.value(forRegister: 8) == 0x40, "Low order byte should be 0x50; was \(m6522.value(forRegister: 8))")
|
||||
XCTAssert(m6522.value(forRegister: 9) == 0x50, "High order byte should be 0x40; was \(m6522.value(forRegister: 9))")
|
||||
}
|
||||
|
||||
func testTimerReload() {
|
||||
with6522 {
|
||||
// set timer 1 to a value of $0010, enable repeating mode
|
||||
$0.setValue(16, forRegister: 4)
|
||||
$0.setValue(0, forRegister: 5)
|
||||
$0.setValue(0x40, forRegister: 11)
|
||||
$0.setValue(0x40 | 0x80, forRegister: 14)
|
||||
// set timer 1 to a value of m6522010, enable repeating mode
|
||||
m6522.setValue(16, forRegister: 4)
|
||||
m6522.setValue(0, forRegister: 5)
|
||||
m6522.setValue(0x40, forRegister: 11)
|
||||
m6522.setValue(0x40 | 0x80, forRegister: 14)
|
||||
|
||||
// complete the cycle to set initial values
|
||||
$0.run(forHalfCycles: 2)
|
||||
// complete the cycle to set initial values
|
||||
m6522.run(forHalfCycles: 2)
|
||||
|
||||
// run for 16 cycles
|
||||
$0.run(forHalfCycles: 32)
|
||||
// run for 16 cycles
|
||||
m6522.run(forHalfCycles: 32)
|
||||
|
||||
// check that the timer has gone down to 0 but not yet triggered an interrupt
|
||||
XCTAssert($0.value(forRegister: 4) == 0, "Low order byte should be 0; was \($0.value(forRegister: 4))")
|
||||
XCTAssert($0.value(forRegister: 5) == 0, "High order byte should be 0; was \($0.value(forRegister: 5))")
|
||||
XCTAssert(!$0.irqLine, "IRQ should not yet be active")
|
||||
// check that the timer has gone down to 0 but not yet triggered an interrupt
|
||||
XCTAssert(m6522.value(forRegister: 4) == 0, "Low order byte should be 0; was \(m6522.value(forRegister: 4))")
|
||||
XCTAssert(m6522.value(forRegister: 5) == 0, "High order byte should be 0; was \(m6522.value(forRegister: 5))")
|
||||
XCTAssert(!m6522.irqLine, "IRQ should not yet be active")
|
||||
|
||||
// check that two half-cycles later the timer is $ffff but IRQ still hasn't triggered
|
||||
$0.run(forHalfCycles: 2)
|
||||
XCTAssert($0.value(forRegister: 4) == 0xff, "Low order byte should be 0xff; was \($0.value(forRegister: 4))")
|
||||
XCTAssert($0.value(forRegister: 5) == 0xff, "High order byte should be 0xff; was \($0.value(forRegister: 5))")
|
||||
XCTAssert(!$0.irqLine, "IRQ should not yet be active")
|
||||
// check that two half-cycles later the timer is $ffff but IRQ still hasn't triggered
|
||||
m6522.run(forHalfCycles: 2)
|
||||
XCTAssert(m6522.value(forRegister: 4) == 0xff, "Low order byte should be 0xff; was \(m6522.value(forRegister: 4))")
|
||||
XCTAssert(m6522.value(forRegister: 5) == 0xff, "High order byte should be 0xff; was \(m6522.value(forRegister: 5))")
|
||||
XCTAssert(!m6522.irqLine, "IRQ should not yet be active")
|
||||
|
||||
// check that one half-cycle later the timer is still $ffff and IRQ has triggered...
|
||||
$0.run(forHalfCycles: 1)
|
||||
XCTAssert($0.irqLine, "IRQ should be active")
|
||||
XCTAssert($0.value(forRegister: 4) == 0xff, "Low order byte should be 0xff; was \($0.value(forRegister: 4))")
|
||||
XCTAssert($0.value(forRegister: 5) == 0xff, "High order byte should be 0xff; was \($0.value(forRegister: 5))")
|
||||
// check that one half-cycle later the timer is still $ffff and IRQ has triggered...
|
||||
m6522.run(forHalfCycles: 1)
|
||||
XCTAssert(m6522.irqLine, "IRQ should be active")
|
||||
XCTAssert(m6522.value(forRegister: 4) == 0xff, "Low order byte should be 0xff; was \(m6522.value(forRegister: 4))")
|
||||
XCTAssert(m6522.value(forRegister: 5) == 0xff, "High order byte should be 0xff; was \(m6522.value(forRegister: 5))")
|
||||
|
||||
// ... but that reading the timer cleared the interrupt
|
||||
XCTAssert(!$0.irqLine, "IRQ should be active")
|
||||
// ... but that reading the timer cleared the interrupt
|
||||
XCTAssert(!m6522.irqLine, "IRQ should be active")
|
||||
|
||||
// check that one half-cycles later the timer has reloaded
|
||||
$0.run(forHalfCycles: 1)
|
||||
XCTAssert($0.value(forRegister: 4) == 0x10, "Low order byte should be 0x10; was \($0.value(forRegister: 4))")
|
||||
XCTAssert($0.value(forRegister: 5) == 0x00, "High order byte should be 0x00; was \($0.value(forRegister: 5))")
|
||||
}
|
||||
// check that one half-cycles later the timer has reloaded
|
||||
m6522.run(forHalfCycles: 1)
|
||||
XCTAssert(m6522.value(forRegister: 4) == 0x10, "Low order byte should be 0x10; was \(m6522.value(forRegister: 4))")
|
||||
XCTAssert(m6522.value(forRegister: 5) == 0x00, "High order byte should be 0x00; was \(m6522.value(forRegister: 5))")
|
||||
}
|
||||
|
||||
|
||||
// MARK: Data direction tests
|
||||
func testDataDirection() {
|
||||
with6522 {
|
||||
// set low four bits of register B as output, the top four as input
|
||||
$0.setValue(0xf0, forRegister: 2)
|
||||
// set low four bits of register B as output, the top four as input
|
||||
m6522.setValue(0xf0, forRegister: 2)
|
||||
|
||||
// ask to output 0x8c
|
||||
$0.setValue(0x8c, forRegister: 0)
|
||||
// ask to output 0x8c
|
||||
m6522.setValue(0x8c, forRegister: 0)
|
||||
|
||||
// complete the cycle
|
||||
$0.run(forHalfCycles: 2)
|
||||
// complete the cycle
|
||||
m6522.run(forHalfCycles: 2)
|
||||
|
||||
// set current input as 0xda
|
||||
$0.portBInput = 0xda
|
||||
// set current input as 0xda
|
||||
m6522.portBInput = 0xda
|
||||
|
||||
// test that the result of reading register B is therefore 0x8a
|
||||
XCTAssert($0.value(forRegister: 0) == 0x8a, "Data direction register should mix input and output; got \($0.value(forRegister: 0))")
|
||||
// test that the result of reading register B is therefore 0x8a
|
||||
XCTAssert(m6522.value(forRegister: 0) == 0x8a, "Data direction register should mix input and output; got \(m6522.value(forRegister: 0))")
|
||||
}
|
||||
|
||||
func testShiftDisabled() {
|
||||
/*
|
||||
Mode 0 disables the Shift Register. In this mode the microprocessor can
|
||||
write or read the SR and the SR will shift on each CB1 positive edge
|
||||
shifting in the value on CB2. In this mode the SR Interrupt Flag is
|
||||
disabled (held to a logic 0).
|
||||
*/
|
||||
}
|
||||
|
||||
func testShiftInUnderT2() {
|
||||
/*
|
||||
In mode 1, the shifting rate is controlled by the low order 8 bits of T2
|
||||
(Figure 22). Shift pulses are generated on the CB1 pin to control shifting
|
||||
in external devices. The time between transitions of this output clock is a
|
||||
function of the system clock period and the contents of the low order T2
|
||||
latch (N).
|
||||
|
||||
The shifting operation is triggered by the read or write of the SR if the
|
||||
SR flag is set in the IFR. Otherwise the first shift will occur at the next
|
||||
time-out of T2 after a read or write of the SR. Data is shifted first into
|
||||
the low order bit of SR and is then shifted into the next higher order bit
|
||||
of the shift register on the negative-going edge of each clock pulse. The
|
||||
input data should change before the positive-going edge of the CB1 clock
|
||||
pulse. This data is shifted into shift register during the 02 clock cycle
|
||||
following the positive-going edge of the CB1 clock pulse. After 8 CB1 clock
|
||||
pulses, the shift register interrupt flag will set and IRQ will go low.
|
||||
*/
|
||||
}
|
||||
|
||||
func testShiftInUnderPhase2() {
|
||||
/*
|
||||
In mode 2, the shift rate is a direct function of the system clock
|
||||
frequency (Figure 23). CB1 becomes an output which generates shift pulses
|
||||
for controlling external devices. Timer 2 operates as an independent
|
||||
interval timer and has no effect on SR. The shifting operation is triggered
|
||||
by reading or writing the Shift Register. Data is shifted, first into bit 0
|
||||
and is then shifted into the next higher order bit of the shift register on
|
||||
the trailing edge of each 02 clock pulse. After 8 clock pulses, the shift
|
||||
register interrupt flag will be set, and the output clock pulses on CB1
|
||||
will stop.
|
||||
*/
|
||||
}
|
||||
|
||||
func testShiftInUnderCB1() {
|
||||
/*
|
||||
In mode 3, external pin CB1 becomes an input (Figure 24). This allows an
|
||||
external device to load the shift register at its own pace. The shift
|
||||
register counter will interrupt the processor each time 8 bits have been
|
||||
shifted in. However the shift register counter does not stop the shifting
|
||||
operation; it acts simply as a pulse counter. Reading or writing the Shift
|
||||
Register resets the Interrupt Flag and initializes the SR counter to count
|
||||
another 8 pulses.
|
||||
|
||||
Note that the data is shifted during the first system clock cycle
|
||||
following the positive-going edge of the CB1 shift pulse. For this reason,
|
||||
data must be held stable during the first full cycle following CB1 going
|
||||
high.
|
||||
*/
|
||||
}
|
||||
|
||||
func testShiftOutUnderT2FreeRunning() {
|
||||
/*
|
||||
Mode 4 is very similar to mode 5 in which the shifting rate is set by T2.
|
||||
However, in mode 4 the SR Counter does not stop the shifting operation
|
||||
(Figure 25). Since the Shift Register bit 7 (SR7) is recirculated back into
|
||||
bit 0, the 8 bits loaded into the Shift Register will be clocked onto CB2
|
||||
repetitively. In this mode the Shift Register Counter is disabled.
|
||||
*/
|
||||
}
|
||||
|
||||
func testShiftOutUnderT2() {
|
||||
/*
|
||||
In mode 5, the shift rate is controlled by T2 (as in mode 4). The shifting
|
||||
operation is triggered by the read or write of the SR if the SR flag is set
|
||||
in the IFR (Figure 26). Otherwise the first shift will occur at the next
|
||||
time-out of T2 after a read or write of the SR. However, with each read or
|
||||
write of the shift register the SR Counter is reset and 8 bits are shifted
|
||||
onto CB2. At the same time, 8 shift pulses are generated on CB1 to control
|
||||
shifting in external devices. After the 8 shift pulses, the shifting is
|
||||
disabled, the SR Interrupt Flag is set and CB2 remains at the last data
|
||||
level.
|
||||
*/
|
||||
}
|
||||
|
||||
func testShiftOutUnderPhase2() {
|
||||
/*
|
||||
In mode 6, the shift rate is controlled by the 02 system clock (Figure 27).
|
||||
|
||||
(... and I'm assuming the same behaviour as shift out under control of T2
|
||||
otherwise, based on original context)
|
||||
*/
|
||||
// Set the shift register to a non-zero something.
|
||||
m6522.setValue(0xaa, forRegister: 10)
|
||||
|
||||
// Set shift register mode 6.
|
||||
m6522.setValue(6 << 2, forRegister: 11)
|
||||
|
||||
// Make sure the shift register's interrupt bit is set.
|
||||
m6522.run(forHalfCycles: 16)
|
||||
XCTAssertEqual(m6522.value(forRegister: 13) & 0x04, 0x04)
|
||||
|
||||
// Test that output is now inhibited: CB2 should remain unchanged.
|
||||
let initialOutput = m6522.value(forControlLine: .two, port: .B)
|
||||
for _ in 1...8 {
|
||||
m6522.run(forHalfCycles: 2)
|
||||
XCTAssertEqual(m6522.value(forControlLine: .two, port: .B), initialOutput)
|
||||
}
|
||||
|
||||
// Set a new value to the shift register.
|
||||
m6522.setValue(0x16, forRegister: 10)
|
||||
|
||||
// Test that the new value is shifted out.
|
||||
var output = 0
|
||||
for _ in 1..<8 {
|
||||
m6522.run(forHalfCycles: 2)
|
||||
output = (output << 1) | (m6522.value(forControlLine: .two, port: .B) ? 1 : 0)
|
||||
}
|
||||
XCTAssertEqual(output, 0x16)
|
||||
}
|
||||
|
||||
func testShiftOutUnderCB1() {
|
||||
/*
|
||||
In mode 7, shifting is controlled by pulses applied to the CB1 pin by an
|
||||
external device (Figure 28). The SR counter sets the SR Interrupt Flag each
|
||||
time it counts 8 pulses but it does not disable the shifting function. Each
|
||||
time the microprocessor, writes or reads the shift register, the SR
|
||||
Interrupt Flag is reset and the SR counter is initialized to begin counting
|
||||
the next 8 shift pulses on pin CB1. After 8 shift pulses, the Interrupt
|
||||
Flag is set. The microprocessor can then load the shift register with the
|
||||
next byte of data.
|
||||
*/
|
||||
}
|
||||
}
|
||||
|
||||
BIN
OSBindings/Mac/Clock SignalTests/68000 Coverage/OPCLOGR2.BIN
Executable file
BIN
OSBindings/Mac/Clock SignalTests/68000 Coverage/OPCLOGR2.BIN
Executable file
Binary file not shown.
@@ -0,0 +1,5 @@
|
||||
This file provides a record of which opcodes are valid and which are invalid on a
|
||||
real 68000. It was generated by AtariZoll and made available via
|
||||
http://www.atari-forum.com/viewtopic.php?f=68&t=26820 .
|
||||
|
||||
No licence was specified.
|
||||
1580
OSBindings/Mac/Clock SignalTests/68000ArithmeticTests.mm
Normal file
1580
OSBindings/Mac/Clock SignalTests/68000ArithmeticTests.mm
Normal file
File diff suppressed because it is too large
Load Diff
291
OSBindings/Mac/Clock SignalTests/68000BCDTests.mm
Normal file
291
OSBindings/Mac/Clock SignalTests/68000BCDTests.mm
Normal file
@@ -0,0 +1,291 @@
|
||||
//
|
||||
// 68000BCDTests.m
|
||||
// Clock SignalTests
|
||||
//
|
||||
// Created by Thomas Harte on 29/06/2019.
|
||||
//
|
||||
// Largely ported from the tests of the Portable 68k Emulator.
|
||||
//
|
||||
|
||||
#import <XCTest/XCTest.h>
|
||||
|
||||
#include "TestRunner68000.hpp"
|
||||
|
||||
@interface M68000BCDTests : XCTestCase
|
||||
@end
|
||||
|
||||
@implementation M68000BCDTests {
|
||||
std::unique_ptr<RAM68000> _machine;
|
||||
}
|
||||
|
||||
- (void)setUp {
|
||||
_machine.reset(new RAM68000());
|
||||
}
|
||||
|
||||
- (void)tearDown {
|
||||
_machine.reset();
|
||||
}
|
||||
|
||||
// MARK: ABCD
|
||||
|
||||
- (void)testABCD {
|
||||
_machine->set_program({
|
||||
0xc302, // ABCD D2, D1
|
||||
});
|
||||
auto state = _machine->get_processor_state();
|
||||
state.data[1] = 0x1234567a;
|
||||
state.data[2] = 0xf745ff78;
|
||||
_machine->set_processor_state(state);
|
||||
_machine->run_for_instructions(1);
|
||||
|
||||
state = _machine->get_processor_state();
|
||||
XCTAssert(state.status & Flag::Carry);
|
||||
XCTAssertEqual(state.data[1], 0x12345658);
|
||||
XCTAssertEqual(state.data[2], 0xf745ff78);
|
||||
}
|
||||
|
||||
- (void)testABCDZero {
|
||||
_machine->set_program({
|
||||
0xc302, // ABCD D2, D1
|
||||
});
|
||||
auto state = _machine->get_processor_state();
|
||||
state.data[1] = 0x12345600;
|
||||
state.data[2] = 0x12345600;
|
||||
state.status = Flag::Zero;
|
||||
_machine->set_processor_state(state);
|
||||
_machine->run_for_instructions(1);
|
||||
|
||||
state = _machine->get_processor_state();
|
||||
XCTAssert(state.status & Flag::Zero);
|
||||
XCTAssertEqual(state.data[1], 0x12345600);
|
||||
XCTAssertEqual(state.data[2], 0x12345600);
|
||||
}
|
||||
|
||||
- (void)testABCDNegative {
|
||||
_machine->set_program({
|
||||
0xc302, // ABCD D2, D1
|
||||
});
|
||||
auto state = _machine->get_processor_state();
|
||||
state.data[1] = 0x12345645;
|
||||
state.data[2] = 0x12345654;
|
||||
state.status = Flag::Zero;
|
||||
_machine->set_processor_state(state);
|
||||
_machine->run_for_instructions(1);
|
||||
|
||||
state = _machine->get_processor_state();
|
||||
XCTAssert(state.status & Flag::Negative);
|
||||
XCTAssertEqual(state.data[1], 0x12345699);
|
||||
XCTAssertEqual(state.data[2], 0x12345654);
|
||||
}
|
||||
|
||||
- (void)testABCDWithX {
|
||||
_machine->set_program({
|
||||
0xc302, // ABCD D2, D1
|
||||
});
|
||||
auto state = _machine->get_processor_state();
|
||||
state.data[1] = 0x12345645;
|
||||
state.data[2] = 0x12345654;
|
||||
state.status = Flag::Extend;
|
||||
_machine->set_processor_state(state);
|
||||
_machine->run_for_instructions(1);
|
||||
|
||||
state = _machine->get_processor_state();
|
||||
XCTAssert(state.status & Flag::Carry);
|
||||
XCTAssertEqual(state.data[1], 0x12345600);
|
||||
XCTAssertEqual(state.data[2], 0x12345654);
|
||||
}
|
||||
|
||||
- (void)testABCDOverflow {
|
||||
_machine->set_program({
|
||||
0xc302, // ABCD D2, D1
|
||||
});
|
||||
auto state = _machine->get_processor_state();
|
||||
state.data[1] = 0x1234563e;
|
||||
state.data[2] = 0x1234563e;
|
||||
state.status = Flag::Extend;
|
||||
_machine->set_processor_state(state);
|
||||
_machine->run_for_instructions(1);
|
||||
|
||||
state = _machine->get_processor_state();
|
||||
XCTAssert(state.status & Flag::Overflow);
|
||||
XCTAssertEqual(state.data[1], 0x12345683);
|
||||
XCTAssertEqual(state.data[2], 0x1234563e);
|
||||
}
|
||||
|
||||
- (void)testABCDPredecDifferent {
|
||||
_machine->set_program({
|
||||
0xc30a, // ABCD -(A2), -(A1)
|
||||
});
|
||||
*_machine->ram_at(0x3000) = 0xa200;
|
||||
*_machine->ram_at(0x4000) = 0x1900;
|
||||
|
||||
auto state = _machine->get_processor_state();
|
||||
state.address[1] = 0x3001;
|
||||
state.address[2] = 0x4001;
|
||||
state.status = Flag::Extend;
|
||||
_machine->set_processor_state(state);
|
||||
_machine->run_for_instructions(1);
|
||||
|
||||
state = _machine->get_processor_state();
|
||||
XCTAssert(state.status & Flag::Carry);
|
||||
XCTAssert(state.status & Flag::Extend);
|
||||
XCTAssertEqual(state.address[1], 0x3000);
|
||||
XCTAssertEqual(state.address[2], 0x4000);
|
||||
XCTAssertEqual(*_machine->ram_at(0x3000), 0x2200);
|
||||
XCTAssertEqual(*_machine->ram_at(0x4000), 0x1900);
|
||||
}
|
||||
|
||||
- (void)testABCDPredecSame {
|
||||
_machine->set_program({
|
||||
0xc309, // ABCD -(A1), -(A1)
|
||||
});
|
||||
*_machine->ram_at(0x3000) = 0x19a2;
|
||||
|
||||
auto state = _machine->get_processor_state();
|
||||
state.address[1] = 0x3002;
|
||||
state.status = Flag::Extend;
|
||||
_machine->set_processor_state(state);
|
||||
_machine->run_for_instructions(1);
|
||||
|
||||
state = _machine->get_processor_state();
|
||||
XCTAssert(state.status & Flag::Carry);
|
||||
XCTAssert(state.status & Flag::Extend);
|
||||
XCTAssertEqual(state.address[1], 0x3000);
|
||||
XCTAssertEqual(*_machine->ram_at(0x3000), 0x22a2);
|
||||
}
|
||||
|
||||
// MARK: NBCD
|
||||
|
||||
- (void)performNBCDd1:(uint32_t)d1 ccr:(uint8_t)ccr {
|
||||
_machine->set_program({
|
||||
0x4801 // NBCD D1
|
||||
});
|
||||
auto state = _machine->get_processor_state();
|
||||
state.status |= ccr;
|
||||
state.data[1] = d1;
|
||||
|
||||
_machine->set_processor_state(state);
|
||||
_machine->run_for_instructions(1);
|
||||
|
||||
XCTAssertEqual(6, _machine->get_cycle_count());
|
||||
}
|
||||
|
||||
- (void)testNBCD_Dn {
|
||||
[self performNBCDd1:0x7a ccr:0];
|
||||
|
||||
const auto state = _machine->get_processor_state();
|
||||
XCTAssertEqual(state.data[1], 0x20);
|
||||
XCTAssertEqual(state.status & Flag::ConditionCodes, Flag::Extend | Flag::Carry | Flag::Overflow);
|
||||
}
|
||||
|
||||
- (void)testNBCD_Dn_extend {
|
||||
[self performNBCDd1:0x1234567a ccr:Flag::Extend | Flag::Zero];
|
||||
|
||||
const auto state = _machine->get_processor_state();
|
||||
XCTAssertEqual(state.data[1], 0x1234561f);
|
||||
XCTAssertEqual(state.status & Flag::ConditionCodes, Flag::Extend | Flag::Carry | Flag::Overflow);
|
||||
}
|
||||
|
||||
- (void)testNBCD_Dn_zero {
|
||||
[self performNBCDd1:0x12345600 ccr:Flag::Zero];
|
||||
|
||||
const auto state = _machine->get_processor_state();
|
||||
XCTAssertEqual(state.data[1], 0x12345600);
|
||||
XCTAssertEqual(state.status & Flag::ConditionCodes, Flag::Zero);
|
||||
}
|
||||
|
||||
- (void)testNBCD_Dn_negative {
|
||||
[self performNBCDd1:0x123456ff ccr:Flag::Extend | Flag::Zero];
|
||||
|
||||
const auto state = _machine->get_processor_state();
|
||||
XCTAssertEqual(state.data[1], 0x1234569a);
|
||||
XCTAssertEqual(state.status & Flag::ConditionCodes, Flag::Extend | Flag::Carry | Flag::Negative);
|
||||
}
|
||||
|
||||
- (void)testNBCD_Dn_XXXw {
|
||||
_machine->set_program({
|
||||
0x4838, 0x3000 // NBCD ($3000).w
|
||||
});
|
||||
*_machine->ram_at(0x3000) = 0x0100;
|
||||
|
||||
_machine->run_for_instructions(1);
|
||||
|
||||
const auto state = _machine->get_processor_state();
|
||||
XCTAssertEqual(16, _machine->get_cycle_count());
|
||||
XCTAssertEqual(*_machine->ram_at(0x3000), 0x9900);
|
||||
XCTAssertEqual(state.status & Flag::ConditionCodes, Flag::Extend | Flag::Carry | Flag::Negative);
|
||||
}
|
||||
|
||||
// MARK: SBCD
|
||||
|
||||
- (void)performSBCDd1:(uint32_t)d1 d2:(uint32_t)d2 ccr:(uint8_t)ccr {
|
||||
_machine->set_program({
|
||||
0x8302 // SBCD D2, D1
|
||||
});
|
||||
auto state = _machine->get_processor_state();
|
||||
state.status |= ccr;
|
||||
state.data[1] = d1;
|
||||
state.data[2] = d2;
|
||||
|
||||
_machine->set_processor_state(state);
|
||||
_machine->run_for_instructions(1);
|
||||
|
||||
state = _machine->get_processor_state();
|
||||
XCTAssertEqual(6, _machine->get_cycle_count());
|
||||
XCTAssertEqual(state.data[2], d2);
|
||||
}
|
||||
|
||||
- (void)testSBCD_Dn {
|
||||
[self performSBCDd1:0x12345689 d2:0xf745ff78 ccr:Flag::Zero];
|
||||
|
||||
const auto state = _machine->get_processor_state();
|
||||
XCTAssertEqual(state.data[1], 0x12345611);
|
||||
XCTAssertEqual(state.status & Flag::ConditionCodes, 0);
|
||||
}
|
||||
|
||||
- (void)testSBCD_Dn_zero {
|
||||
[self performSBCDd1:0x123456ff d2:0xf745ffff ccr:Flag::Zero];
|
||||
|
||||
const auto state = _machine->get_processor_state();
|
||||
XCTAssertEqual(state.data[1], 0x12345600);
|
||||
XCTAssertEqual(state.status & Flag::ConditionCodes, Flag::Zero);
|
||||
}
|
||||
|
||||
- (void)testSBCD_Dn_negative {
|
||||
[self performSBCDd1:0x12345634 d2:0xf745ff45 ccr:Flag::Extend];
|
||||
|
||||
const auto state = _machine->get_processor_state();
|
||||
XCTAssertEqual(state.data[1], 0x12345688);
|
||||
XCTAssertEqual(state.status & Flag::ConditionCodes, Flag::Extend | Flag::Carry | Flag::Negative);
|
||||
}
|
||||
|
||||
- (void)testSBCD_Dn_overflow {
|
||||
[self performSBCDd1:0x123456a9 d2:0xf745ffff ccr:Flag::Extend];
|
||||
|
||||
const auto state = _machine->get_processor_state();
|
||||
XCTAssertEqual(state.data[1], 0x12345643);
|
||||
XCTAssertEqual(state.status & Flag::ConditionCodes, Flag::Extend | Flag::Carry | Flag::Overflow);
|
||||
}
|
||||
|
||||
- (void)testSBCD_Dn_PreDec {
|
||||
_machine->set_program({
|
||||
0x830a // SBCD -(A2), -(A1)
|
||||
});
|
||||
*_machine->ram_at(0x3000) = 0xa200;
|
||||
*_machine->ram_at(0x4000) = 0x1900;
|
||||
auto state = _machine->get_processor_state();
|
||||
state.address[1] = 0x3001;
|
||||
state.address[2] = 0x4001;
|
||||
state.status |= Flag::Extend;
|
||||
|
||||
_machine->set_processor_state(state);
|
||||
_machine->run_for_instructions(1);
|
||||
|
||||
state = _machine->get_processor_state();
|
||||
XCTAssertEqual(18, _machine->get_cycle_count());
|
||||
XCTAssertEqual(*_machine->ram_at(0x3000), 0x8200);
|
||||
XCTAssertEqual(*_machine->ram_at(0x4000), 0x1900);
|
||||
XCTAssertEqual(state.status & Flag::ConditionCodes, Flag::Negative);
|
||||
}
|
||||
|
||||
@end
|
||||
1502
OSBindings/Mac/Clock SignalTests/68000BitwiseTests.mm
Normal file
1502
OSBindings/Mac/Clock SignalTests/68000BitwiseTests.mm
Normal file
File diff suppressed because it is too large
Load Diff
557
OSBindings/Mac/Clock SignalTests/68000ControlFlowTests.mm
Normal file
557
OSBindings/Mac/Clock SignalTests/68000ControlFlowTests.mm
Normal file
@@ -0,0 +1,557 @@
|
||||
//
|
||||
// 68000ControlFlowTests.m
|
||||
// Clock SignalTests
|
||||
//
|
||||
// Created by Thomas Harte on 28/06/2019.
|
||||
//
|
||||
// Largely ported from the tests of the Portable 68k Emulator.
|
||||
//
|
||||
|
||||
#import <XCTest/XCTest.h>
|
||||
|
||||
#include "TestRunner68000.hpp"
|
||||
|
||||
|
||||
@interface M68000ControlFlowTests : XCTestCase
|
||||
@end
|
||||
|
||||
@implementation M68000ControlFlowTests {
|
||||
std::unique_ptr<RAM68000> _machine;
|
||||
}
|
||||
|
||||
- (void)setUp {
|
||||
_machine.reset(new RAM68000());
|
||||
}
|
||||
|
||||
- (void)tearDown {
|
||||
_machine.reset();
|
||||
}
|
||||
|
||||
// MARK: Bcc
|
||||
|
||||
- (void)performBccb:(uint16_t)opcode {
|
||||
_machine->set_program({
|
||||
uint16_t(opcode | 6) // Bcc.b +6
|
||||
});
|
||||
|
||||
_machine->run_for_instructions(1);
|
||||
}
|
||||
|
||||
- (void)performBccw:(uint16_t)opcode {
|
||||
_machine->set_program({
|
||||
opcode, 0x0006 // Bcc.w +6
|
||||
});
|
||||
|
||||
_machine->run_for_instructions(1);
|
||||
}
|
||||
|
||||
- (void)testBHIb {
|
||||
[self performBccb:0x6200];
|
||||
|
||||
const auto state = _machine->get_processor_state();
|
||||
XCTAssertEqual(state.program_counter, 0x1008 + 4);
|
||||
XCTAssertEqual(_machine->get_cycle_count(), 10);
|
||||
}
|
||||
|
||||
- (void)testBLOb {
|
||||
[self performBccb:0x6500];
|
||||
|
||||
const auto state = _machine->get_processor_state();
|
||||
XCTAssertEqual(state.program_counter, 0x1002 + 4);
|
||||
XCTAssertEqual(_machine->get_cycle_count(), 8);
|
||||
}
|
||||
|
||||
- (void)testBHIw {
|
||||
[self performBccw:0x6200];
|
||||
|
||||
const auto state = _machine->get_processor_state();
|
||||
XCTAssertEqual(state.program_counter, 0x1008 + 4);
|
||||
XCTAssertEqual(_machine->get_cycle_count(), 10);
|
||||
}
|
||||
|
||||
- (void)testBLOw {
|
||||
[self performBccw:0x6500];
|
||||
|
||||
const auto state = _machine->get_processor_state();
|
||||
XCTAssertEqual(state.program_counter, 0x1004 + 4);
|
||||
XCTAssertEqual(_machine->get_cycle_count(), 12);
|
||||
}
|
||||
|
||||
// MARK: BRA
|
||||
|
||||
- (void)testBRAb {
|
||||
_machine->set_program({
|
||||
0x6004 // BRA.b +4
|
||||
});
|
||||
|
||||
_machine->run_for_instructions(1);
|
||||
|
||||
const auto state = _machine->get_processor_state();
|
||||
XCTAssertEqual(state.program_counter, 0x1006 + 4);
|
||||
XCTAssertEqual(_machine->get_cycle_count(), 10);
|
||||
}
|
||||
|
||||
- (void)testBRAw {
|
||||
_machine->set_program({
|
||||
0x6000, 0x0004 // BRA.b +4
|
||||
});
|
||||
|
||||
_machine->run_for_instructions(1);
|
||||
|
||||
const auto state = _machine->get_processor_state();
|
||||
XCTAssertEqual(state.program_counter, 0x1006 + 4);
|
||||
XCTAssertEqual(_machine->get_cycle_count(), 10);
|
||||
}
|
||||
|
||||
// MARK: BSR
|
||||
|
||||
- (void)testBSRw {
|
||||
_machine->set_program({
|
||||
0x6100, 0x0006 // BSR.w $1008
|
||||
});
|
||||
_machine->set_initial_stack_pointer(0x3000);
|
||||
|
||||
_machine->run_for_instructions(1);
|
||||
|
||||
const auto state = _machine->get_processor_state();
|
||||
XCTAssertEqual(state.program_counter, 0x1008 + 4);
|
||||
XCTAssertEqual(state.stack_pointer(), 0x2ffc);
|
||||
XCTAssertEqual(state.status & Flag::ConditionCodes, 0);
|
||||
XCTAssertEqual(*_machine->ram_at(0x2ffc), 0);
|
||||
XCTAssertEqual(*_machine->ram_at(0x2ffe), 0x1004);
|
||||
|
||||
XCTAssertEqual(_machine->get_cycle_count(), 18);
|
||||
}
|
||||
|
||||
- (void)testBSRb {
|
||||
_machine->set_program({
|
||||
0x6106 // BSR.b $1008
|
||||
});
|
||||
_machine->set_initial_stack_pointer(0x3000);
|
||||
|
||||
_machine->run_for_instructions(1);
|
||||
|
||||
const auto state = _machine->get_processor_state();
|
||||
XCTAssertEqual(state.program_counter, 0x1008 + 4);
|
||||
XCTAssertEqual(state.stack_pointer(), 0x2ffc);
|
||||
XCTAssertEqual(state.status & Flag::ConditionCodes, 0);
|
||||
XCTAssertEqual(*_machine->ram_at(0x2ffc), 0);
|
||||
XCTAssertEqual(*_machine->ram_at(0x2ffe), 0x1002);
|
||||
|
||||
XCTAssertEqual(_machine->get_cycle_count(), 18);
|
||||
}
|
||||
|
||||
// MARK: CHK
|
||||
|
||||
- (void)performCHKd1:(uint32_t)d1 d2:(uint32_t)d2 {
|
||||
_machine->set_program({
|
||||
0x4581 // CHK D1, D2
|
||||
});
|
||||
auto state = _machine->get_processor_state();
|
||||
state.data[1] = d1;
|
||||
state.data[2] = d2;
|
||||
state.status |= Flag::ConditionCodes;
|
||||
|
||||
_machine->set_initial_stack_pointer(0);
|
||||
_machine->set_processor_state(state);
|
||||
_machine->run_for_instructions(1);
|
||||
|
||||
state = _machine->get_processor_state();
|
||||
XCTAssertEqual(state.data[1], d1);
|
||||
XCTAssertEqual(state.data[2], d2);
|
||||
}
|
||||
|
||||
- (void)testCHK_1111v1111 {
|
||||
[self performCHKd1:0x1111 d2:0x1111];
|
||||
|
||||
const auto state = _machine->get_processor_state();
|
||||
XCTAssertEqual(state.program_counter, 0x1002 + 4);
|
||||
XCTAssertEqual(state.status & Flag::ConditionCodes, Flag::Extend);
|
||||
XCTAssertEqual(10, _machine->get_cycle_count());
|
||||
}
|
||||
|
||||
- (void)testCHK_1111v0000 {
|
||||
[self performCHKd1:0x1111 d2:0x0000];
|
||||
|
||||
const auto state = _machine->get_processor_state();
|
||||
XCTAssertEqual(state.program_counter, 0x1002 + 4);
|
||||
XCTAssertEqual(state.status & Flag::ConditionCodes, Flag::Extend | Flag::Zero);
|
||||
XCTAssertEqual(10, _machine->get_cycle_count());
|
||||
}
|
||||
|
||||
- (void)testCHK_8000v8001 {
|
||||
[self performCHKd1:0x8000 d2:0x8001];
|
||||
|
||||
const auto state = _machine->get_processor_state();
|
||||
XCTAssertNotEqual(state.program_counter, 0x1002 + 4);
|
||||
XCTAssertEqual(state.stack_pointer(), 0xfffffffa);
|
||||
XCTAssertEqual(state.status & Flag::ConditionCodes, Flag::Extend);
|
||||
XCTAssertEqual(42, _machine->get_cycle_count());
|
||||
}
|
||||
|
||||
- (void)testCHK_8000v8000 {
|
||||
[self performCHKd1:0x8000 d2:0x8000];
|
||||
|
||||
const auto state = _machine->get_processor_state();
|
||||
XCTAssertNotEqual(state.program_counter, 0x1002 + 4);
|
||||
XCTAssertEqual(state.status & Flag::ConditionCodes, Flag::Extend | Flag::Negative);
|
||||
XCTAssertEqual(44, _machine->get_cycle_count());
|
||||
}
|
||||
|
||||
// MARK: DBcc
|
||||
|
||||
- (void)performDBccTestOpcode:(uint16_t)opcode status:(uint16_t)status d2Outcome:(uint32_t)d2Output {
|
||||
_machine->set_program({
|
||||
opcode, 0x0008 // DBcc D2, +8
|
||||
});
|
||||
auto state = _machine->get_processor_state();
|
||||
state.status = status;
|
||||
state.data[2] = 1;
|
||||
|
||||
_machine->set_processor_state(state);
|
||||
_machine->run_for_instructions(1);
|
||||
|
||||
state = _machine->get_processor_state();
|
||||
XCTAssertEqual(state.data[2], d2Output);
|
||||
XCTAssertEqual(state.status, status);
|
||||
}
|
||||
|
||||
- (void)testDBT {
|
||||
[self performDBccTestOpcode:0x50ca status:0 d2Outcome:1];
|
||||
}
|
||||
|
||||
- (void)testDBF {
|
||||
[self performDBccTestOpcode:0x51ca status:0 d2Outcome:0];
|
||||
}
|
||||
|
||||
- (void)testDBHI {
|
||||
[self performDBccTestOpcode:0x52ca status:0 d2Outcome:1];
|
||||
}
|
||||
|
||||
- (void)testDBHI_Carry {
|
||||
[self performDBccTestOpcode:0x52ca status:Flag::Carry d2Outcome:0];
|
||||
}
|
||||
|
||||
- (void)testDBHI_Zero {
|
||||
[self performDBccTestOpcode:0x52ca status:Flag::Zero d2Outcome:0];
|
||||
}
|
||||
|
||||
- (void)testDBLS_CarryOverflow {
|
||||
[self performDBccTestOpcode:0x53ca status:Flag::Carry | Flag::Overflow d2Outcome:1];
|
||||
}
|
||||
|
||||
- (void)testDBLS_Carry {
|
||||
[self performDBccTestOpcode:0x53ca status:Flag::Carry d2Outcome:1];
|
||||
}
|
||||
|
||||
- (void)testDBLS_Overflow {
|
||||
[self performDBccTestOpcode:0x53ca status:Flag::Overflow d2Outcome:0];
|
||||
}
|
||||
|
||||
- (void)testDBCC_Carry {
|
||||
[self performDBccTestOpcode:0x54ca status:Flag::Carry d2Outcome:0];
|
||||
}
|
||||
|
||||
- (void)testDBCC {
|
||||
[self performDBccTestOpcode:0x54ca status:0 d2Outcome:1];
|
||||
}
|
||||
|
||||
- (void)testDBCS {
|
||||
[self performDBccTestOpcode:0x55ca status:0 d2Outcome:0];
|
||||
}
|
||||
|
||||
- (void)testDBCS_Carry {
|
||||
[self performDBccTestOpcode:0x55ca status:Flag::Carry d2Outcome:1];
|
||||
}
|
||||
|
||||
- (void)testDBNE {
|
||||
[self performDBccTestOpcode:0x56ca status:0 d2Outcome:1];
|
||||
}
|
||||
|
||||
- (void)testDBNE_Zero {
|
||||
[self performDBccTestOpcode:0x56ca status:Flag::Zero d2Outcome:0];
|
||||
}
|
||||
|
||||
- (void)testDBEQ {
|
||||
[self performDBccTestOpcode:0x57ca status:0 d2Outcome:0];
|
||||
}
|
||||
|
||||
- (void)testDBEQ_Zero {
|
||||
[self performDBccTestOpcode:0x57ca status:Flag::Zero d2Outcome:1];
|
||||
}
|
||||
|
||||
- (void)testDBVC {
|
||||
[self performDBccTestOpcode:0x58ca status:0 d2Outcome:1];
|
||||
}
|
||||
|
||||
- (void)testDBVC_Overflow {
|
||||
[self performDBccTestOpcode:0x58ca status:Flag::Overflow d2Outcome:0];
|
||||
}
|
||||
|
||||
- (void)testDBVS {
|
||||
[self performDBccTestOpcode:0x59ca status:0 d2Outcome:0];
|
||||
}
|
||||
|
||||
- (void)testDBVS_Overflow {
|
||||
[self performDBccTestOpcode:0x59ca status:Flag::Overflow d2Outcome:1];
|
||||
}
|
||||
|
||||
- (void)testDBPL {
|
||||
[self performDBccTestOpcode:0x5aca status:0 d2Outcome:1];
|
||||
}
|
||||
|
||||
- (void)testDBPL_Negative {
|
||||
[self performDBccTestOpcode:0x5aca status:Flag::Negative d2Outcome:0];
|
||||
}
|
||||
|
||||
- (void)testDBMI {
|
||||
[self performDBccTestOpcode:0x5bca status:0 d2Outcome:0];
|
||||
}
|
||||
|
||||
- (void)testDBMI_Negative {
|
||||
[self performDBccTestOpcode:0x5bca status:Flag::Negative d2Outcome:1];
|
||||
}
|
||||
|
||||
- (void)testDBGE_NegativeOverflow {
|
||||
[self performDBccTestOpcode:0x5cca status:Flag::Negative | Flag::Overflow d2Outcome:1];
|
||||
}
|
||||
|
||||
- (void)testDBGE {
|
||||
[self performDBccTestOpcode:0x5cca status:0 d2Outcome:1];
|
||||
}
|
||||
|
||||
- (void)testDBGE_Negative {
|
||||
[self performDBccTestOpcode:0x5cca status:Flag::Negative d2Outcome:0];
|
||||
}
|
||||
|
||||
- (void)testDBGE_Overflow {
|
||||
[self performDBccTestOpcode:0x5cca status:Flag::Overflow d2Outcome:0];
|
||||
}
|
||||
|
||||
- (void)testDBLT_NegativeOverflow {
|
||||
[self performDBccTestOpcode:0x5dca status:Flag::Negative | Flag::Overflow d2Outcome:0];
|
||||
}
|
||||
|
||||
- (void)testDBLT {
|
||||
[self performDBccTestOpcode:0x5dca status:0 d2Outcome:0];
|
||||
}
|
||||
|
||||
- (void)testDBLT_Negative {
|
||||
[self performDBccTestOpcode:0x5dca status:Flag::Negative d2Outcome:1];
|
||||
}
|
||||
|
||||
- (void)testDBLT_Overflow {
|
||||
[self performDBccTestOpcode:0x5dca status:Flag::Overflow d2Outcome:1];
|
||||
}
|
||||
|
||||
- (void)testDBGT {
|
||||
[self performDBccTestOpcode:0x5eca status:0 d2Outcome:1];
|
||||
}
|
||||
|
||||
- (void)testDBGT_ZeroNegativeOverflow {
|
||||
[self performDBccTestOpcode:0x5eca status:Flag::Zero | Flag::Negative | Flag::Overflow d2Outcome:0];
|
||||
}
|
||||
|
||||
- (void)testDBGT_NegativeOverflow {
|
||||
[self performDBccTestOpcode:0x5eca status:Flag::Negative | Flag::Overflow d2Outcome:1];
|
||||
}
|
||||
|
||||
- (void)testDBGT_Zero {
|
||||
[self performDBccTestOpcode:0x5eca status:Flag::Zero d2Outcome:0];
|
||||
}
|
||||
|
||||
- (void)testDBLE {
|
||||
[self performDBccTestOpcode:0x5fca status:0 d2Outcome:0];
|
||||
}
|
||||
|
||||
- (void)testDBLE_Zero {
|
||||
[self performDBccTestOpcode:0x5fca status:Flag::Zero d2Outcome:1];
|
||||
}
|
||||
|
||||
- (void)testDBLE_Negative {
|
||||
[self performDBccTestOpcode:0x5fca status:Flag::Negative d2Outcome:1];
|
||||
}
|
||||
|
||||
- (void)testDBLE_NegativeOverflow {
|
||||
[self performDBccTestOpcode:0x5fca status:Flag::Negative | Flag::Overflow d2Outcome:0];
|
||||
}
|
||||
|
||||
/* Further DBF tests omitted; they seemed to be duplicative, assuming I'm not suffering a failure of comprehension. */
|
||||
|
||||
// MARK: JMP
|
||||
|
||||
- (void)testJMP_A1 {
|
||||
_machine->set_program({
|
||||
0x4ed1 // JMP (A1)
|
||||
});
|
||||
|
||||
auto state = _machine->get_processor_state();
|
||||
state.address[1] = 0x3000;
|
||||
|
||||
_machine->set_processor_state(state);
|
||||
_machine->run_for_instructions(1);
|
||||
|
||||
state = _machine->get_processor_state();
|
||||
XCTAssertEqual(state.address[1], 0x3000);
|
||||
XCTAssertEqual(state.program_counter, 0x3000 + 4);
|
||||
XCTAssertEqual(8, _machine->get_cycle_count());
|
||||
}
|
||||
|
||||
- (void)testJMP_PC {
|
||||
_machine->set_program({
|
||||
0x4efa, 0x000a // JMP PC+a (i.e. to 0x100c)
|
||||
});
|
||||
|
||||
_machine->run_for_instructions(1);
|
||||
|
||||
const auto state = _machine->get_processor_state();
|
||||
XCTAssertEqual(state.program_counter, 0x100c + 4);
|
||||
XCTAssertEqual(10, _machine->get_cycle_count());
|
||||
}
|
||||
|
||||
// MARK: JSR
|
||||
|
||||
- (void)testJSR_PC {
|
||||
_machine->set_program({
|
||||
0x4eba, 0x000a // JSR (+a)PC ; JSR to $100c
|
||||
});
|
||||
_machine->set_initial_stack_pointer(0x2000);
|
||||
|
||||
_machine->run_for_instructions(1);
|
||||
|
||||
const auto state = _machine->get_processor_state();
|
||||
XCTAssertEqual(state.stack_pointer(), 0x1ffc);
|
||||
XCTAssertEqual(state.program_counter, 0x100c + 4);
|
||||
XCTAssertEqual(*_machine->ram_at(0x1ffc), 0x0000);
|
||||
XCTAssertEqual(*_machine->ram_at(0x1ffe), 0x1004);
|
||||
XCTAssertEqual(18, _machine->get_cycle_count());
|
||||
}
|
||||
|
||||
- (void)testJSR_XXXl {
|
||||
_machine->set_program({
|
||||
0x4eb9, 0x0000, 0x1008 // JSR ($1008).l
|
||||
});
|
||||
_machine->set_initial_stack_pointer(0x2000);
|
||||
|
||||
_machine->run_for_instructions(1);
|
||||
|
||||
const auto state = _machine->get_processor_state();
|
||||
XCTAssertEqual(state.stack_pointer(), 0x1ffc);
|
||||
XCTAssertEqual(state.program_counter, 0x1008 + 4);
|
||||
XCTAssertEqual(*_machine->ram_at(0x1ffc), 0x0000);
|
||||
XCTAssertEqual(*_machine->ram_at(0x1ffe), 0x1006);
|
||||
XCTAssertEqual(20, _machine->get_cycle_count());
|
||||
}
|
||||
|
||||
// MARK: NOP
|
||||
|
||||
- (void)testNOP {
|
||||
_machine->set_program({
|
||||
0x4e71 // NOP
|
||||
});
|
||||
_machine->run_for_instructions(1);
|
||||
XCTAssertEqual(4, _machine->get_cycle_count());
|
||||
}
|
||||
|
||||
// MARK: RTR
|
||||
|
||||
- (void)testRTR {
|
||||
_machine->set_program({
|
||||
0x4e77 // RTR
|
||||
});
|
||||
_machine->set_initial_stack_pointer(0x2000);
|
||||
*_machine->ram_at(0x2000) = 0x7fff;
|
||||
*_machine->ram_at(0x2002) = 0;
|
||||
*_machine->ram_at(0x2004) = 0xc;
|
||||
|
||||
_machine->run_for_instructions(1);
|
||||
|
||||
const auto state = _machine->get_processor_state();
|
||||
XCTAssertEqual(state.stack_pointer(), 0x2006);
|
||||
XCTAssertEqual(state.program_counter, 0x10);
|
||||
XCTAssertEqual(state.status & Flag::ConditionCodes, Flag::ConditionCodes);
|
||||
XCTAssertEqual(20, _machine->get_cycle_count());
|
||||
}
|
||||
|
||||
// MARK: RTS
|
||||
|
||||
- (void)testRTS {
|
||||
_machine->set_program({
|
||||
0x4e75 // RTS
|
||||
});
|
||||
_machine->set_initial_stack_pointer(0x2000);
|
||||
*_machine->ram_at(0x2000) = 0x0000;
|
||||
*_machine->ram_at(0x2002) = 0x000c;
|
||||
|
||||
_machine->run_for_instructions(1);
|
||||
|
||||
const auto state = _machine->get_processor_state();
|
||||
XCTAssertEqual(state.stack_pointer(), 0x2004);
|
||||
XCTAssertEqual(state.program_counter, 0x000c + 4);
|
||||
XCTAssertEqual(16, _machine->get_cycle_count());
|
||||
}
|
||||
|
||||
// MARK: TRAP
|
||||
|
||||
- (void)testTRAP {
|
||||
_machine->set_program({
|
||||
0x4e41 // TRAP #1
|
||||
});
|
||||
auto state = _machine->get_processor_state();
|
||||
state.status = 0x700;
|
||||
state.user_stack_pointer = 0x200;
|
||||
state.supervisor_stack_pointer = 0x206;
|
||||
*_machine->ram_at(0x84) = 0xfffe;
|
||||
*_machine->ram_at(0xfffe) = 0x4e71;
|
||||
|
||||
_machine->set_processor_state(state);
|
||||
_machine->run_for_instructions(1);
|
||||
|
||||
state = _machine->get_processor_state();
|
||||
XCTAssertEqual(state.status, 0x2700);
|
||||
XCTAssertEqual(*_machine->ram_at(0x200), 0x700);
|
||||
XCTAssertEqual(*_machine->ram_at(0x202), 0x0000);
|
||||
XCTAssertEqual(*_machine->ram_at(0x204), 0x1002);
|
||||
XCTAssertEqual(state.supervisor_stack_pointer, 0x200);
|
||||
XCTAssertEqual(34, _machine->get_cycle_count());
|
||||
}
|
||||
|
||||
// MARK: TRAPV
|
||||
|
||||
- (void)testTRAPV_taken {
|
||||
_machine->set_program({
|
||||
0x4e76 // TRAPV
|
||||
});
|
||||
_machine->set_initial_stack_pointer(0x206);
|
||||
|
||||
auto state = _machine->get_processor_state();
|
||||
state.status = 0x702;
|
||||
state.supervisor_stack_pointer = 0x206;
|
||||
*_machine->ram_at(0x1e) = 0xfffe;
|
||||
*_machine->ram_at(0xfffe) = 0x4e71;
|
||||
|
||||
_machine->set_processor_state(state);
|
||||
_machine->run_for_instructions(1);
|
||||
|
||||
state = _machine->get_processor_state();
|
||||
XCTAssertEqual(state.status, 0x2702);
|
||||
XCTAssertEqual(state.stack_pointer(), 0x200);
|
||||
XCTAssertEqual(*_machine->ram_at(0x202), 0x0000);
|
||||
XCTAssertEqual(*_machine->ram_at(0x204), 0x1002);
|
||||
XCTAssertEqual(*_machine->ram_at(0x200), 0x702);
|
||||
XCTAssertEqual(34, _machine->get_cycle_count());
|
||||
}
|
||||
|
||||
- (void)testTRAPV_untaken {
|
||||
_machine->set_program({
|
||||
0x4e76 // TRAPV
|
||||
});
|
||||
|
||||
_machine->run_for_instructions(1);
|
||||
|
||||
const auto state = _machine->get_processor_state();
|
||||
XCTAssertEqual(state.program_counter, 0x1002 + 4);
|
||||
XCTAssertEqual(4, _machine->get_cycle_count());
|
||||
}
|
||||
|
||||
@end
|
||||
1250
OSBindings/Mac/Clock SignalTests/68000MoveTests.mm
Normal file
1250
OSBindings/Mac/Clock SignalTests/68000MoveTests.mm
Normal file
File diff suppressed because it is too large
Load Diff
1122
OSBindings/Mac/Clock SignalTests/68000RollShiftTests.mm
Normal file
1122
OSBindings/Mac/Clock SignalTests/68000RollShiftTests.mm
Normal file
File diff suppressed because it is too large
Load Diff
425
OSBindings/Mac/Clock SignalTests/68000Tests.mm
Normal file
425
OSBindings/Mac/Clock SignalTests/68000Tests.mm
Normal file
@@ -0,0 +1,425 @@
|
||||
//
|
||||
// 68000Tests.m
|
||||
// Clock SignalTests
|
||||
//
|
||||
// Created by Thomas Harte on 13/03/2019.
|
||||
// Copyright © 2019 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#import <XCTest/XCTest.h>
|
||||
|
||||
#include <cassert>
|
||||
|
||||
#define LOG_TRACE
|
||||
#include "TestRunner68000.hpp"
|
||||
|
||||
class CPU::MC68000::ProcessorStorageTests {
|
||||
public:
|
||||
ProcessorStorageTests(const CPU::MC68000::ProcessorStorage &storage, const char *coverage_file_name) {
|
||||
false_valids_ = [NSMutableSet set];
|
||||
false_invalids_ = [NSMutableSet set];
|
||||
|
||||
FILE *source = fopen(coverage_file_name, "rb");
|
||||
|
||||
// The file format here is [2 bytes opcode][2 ASCII characters:VA for valid, IN for invalid]...
|
||||
// The file terminates with four additional bytes that begin with two zero bytes.
|
||||
//
|
||||
// The version of the file I grabbed seems to cover all opcodes, making their enumeration
|
||||
// arguably redundant; the code below nevertheless uses the codes from the file.
|
||||
//
|
||||
// Similarly, I'm testing for exactly the strings VA or IN to ensure no further
|
||||
// types creep into any updated version of the table that I then deal with incorrectly.
|
||||
uint16_t last_observed = 0;
|
||||
while(true) {
|
||||
// Fetch opcode number.
|
||||
uint16_t next_opcode = fgetc(source) << 8;
|
||||
next_opcode |= fgetc(source);
|
||||
if(next_opcode < last_observed) break;
|
||||
last_observed = next_opcode;
|
||||
|
||||
// Determine whether it's meant to be valid.
|
||||
char type[3];
|
||||
type[0] = fgetc(source);
|
||||
type[1] = fgetc(source);
|
||||
type[2] = '\0';
|
||||
|
||||
// TEMPORARY: factor out A- and F-line exceptions.
|
||||
if((next_opcode&0xf000) == 0xa000) continue;
|
||||
if((next_opcode&0xf000) == 0xf000) continue;
|
||||
|
||||
if(!strcmp(type, "VA")) {
|
||||
// Test for validity.
|
||||
if(storage.instructions[next_opcode].micro_operations == std::numeric_limits<uint32_t>::max()) {
|
||||
[false_invalids_ addObject:@(next_opcode)];
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if(!strcmp(type, "IN")) {
|
||||
// Test for invalidity.
|
||||
if(storage.instructions[next_opcode].micro_operations != std::numeric_limits<uint32_t>::max()) {
|
||||
[false_valids_ addObject:@(next_opcode)];
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
assert(false);
|
||||
}
|
||||
|
||||
fclose(source);
|
||||
}
|
||||
|
||||
NSSet<NSNumber *> *false_valids() const {
|
||||
return false_valids_;
|
||||
}
|
||||
|
||||
NSSet<NSNumber *> *false_invalids() const {
|
||||
return false_invalids_;
|
||||
}
|
||||
|
||||
private:
|
||||
NSMutableSet<NSNumber *> *false_invalids_;
|
||||
NSMutableSet<NSNumber *> *false_valids_;
|
||||
};
|
||||
|
||||
@interface NSSet (CSHexDump)
|
||||
|
||||
- (NSString *)hexDump;
|
||||
|
||||
@end
|
||||
|
||||
@implementation NSSet (CSHexDump)
|
||||
|
||||
- (NSString *)hexDump {
|
||||
NSMutableArray<NSString *> *components = [NSMutableArray array];
|
||||
|
||||
for(NSNumber *number in [[self allObjects] sortedArrayUsingSelector:@selector(compare:)]) {
|
||||
[components addObject:[NSString stringWithFormat:@"%04x", number.intValue]];
|
||||
}
|
||||
|
||||
return [components componentsJoinedByString:@" "];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@interface M68000Tests : XCTestCase
|
||||
@end
|
||||
|
||||
@implementation M68000Tests {
|
||||
std::unique_ptr<RAM68000> _machine;
|
||||
}
|
||||
|
||||
- (void)setUp {
|
||||
_machine.reset(new RAM68000());
|
||||
}
|
||||
|
||||
- (void)tearDown {
|
||||
_machine.reset();
|
||||
}
|
||||
|
||||
- (void)testABCDLong {
|
||||
for(int d = 0; d < 100; ++d) {
|
||||
_machine.reset(new RAM68000());
|
||||
_machine->set_program({
|
||||
0xc100 // ABCD D0, D0
|
||||
});
|
||||
|
||||
auto state = _machine->get_processor_state();
|
||||
const uint8_t bcd_d = ((d / 10) * 16) + (d % 10);
|
||||
state.data[0] = bcd_d;
|
||||
_machine->set_processor_state(state);
|
||||
|
||||
_machine->run_for_instructions(1);
|
||||
|
||||
state = _machine->get_processor_state();
|
||||
const uint8_t double_d = (d * 2) % 100;
|
||||
const uint8_t bcd_double_d = ((double_d / 10) * 16) + (double_d % 10);
|
||||
XCTAssert(state.data[0] == bcd_double_d, "%02x + %02x = %02x; should equal %02x", bcd_d, bcd_d, state.data[0], bcd_double_d);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)testDivideByZero {
|
||||
_machine->set_program({
|
||||
0x7000, // MOVE #0, D0; location 0x400
|
||||
0x3200, // MOVE D0, D1; location 0x402
|
||||
|
||||
0x82C0, // DIVU; location 0x404
|
||||
|
||||
/* Next instruction would be at 0x406 */
|
||||
});
|
||||
_machine->set_initial_stack_pointer(0x1000);
|
||||
|
||||
_machine->run_for_instructions(4);
|
||||
|
||||
const auto state = _machine->get_processor_state();
|
||||
XCTAssert(state.supervisor_stack_pointer == 0x1000 - 6, @"Exception information should have been pushed to stack.");
|
||||
|
||||
const uint16_t *const stack_top = _machine->ram_at(state.supervisor_stack_pointer);
|
||||
XCTAssert(stack_top[1] == 0x0000 && stack_top[2] == 0x1006, @"Return address should point to instruction after DIVU.");
|
||||
}
|
||||
|
||||
- (void)testMOVE {
|
||||
_machine->set_program({
|
||||
0x303c, 0xfb2e, // MOVE #fb2e, D0
|
||||
0x3200, // MOVE D0, D1
|
||||
|
||||
0x3040, // MOVEA D0, A0
|
||||
0x3278, 0x1000, // MOVEA.w (0x1000), A1
|
||||
|
||||
0x387c, 0x1000, // MOVE #$1000, A4
|
||||
0x2414, // MOVE.l (A4), D2
|
||||
});
|
||||
|
||||
// run_for_instructions technically runs up to the next instruction
|
||||
// fetch; therefore run for '1' to get past the implied RESET.
|
||||
_machine->run_for_instructions(1);
|
||||
|
||||
// Perform MOVE #fb2e, D0
|
||||
_machine->run_for_instructions(1);
|
||||
auto state = _machine->get_processor_state();
|
||||
XCTAssert(state.data[0] == 0xfb2e);
|
||||
|
||||
// Perform MOVE D0, D1
|
||||
_machine->run_for_instructions(1);
|
||||
state = _machine->get_processor_state();
|
||||
XCTAssert(state.data[1] == 0xfb2e);
|
||||
|
||||
// Perform MOVEA D0, A0
|
||||
_machine->run_for_instructions(1);
|
||||
state = _machine->get_processor_state();
|
||||
XCTAssert(state.address[0] == 0xfffffb2e, "A0 was %08x instead of 0xfffffb2e", state.address[0]);
|
||||
|
||||
// Perform MOVEA.w (0x1000), A1
|
||||
_machine->run_for_instructions(1);
|
||||
state = _machine->get_processor_state();
|
||||
XCTAssert(state.address[1] == 0x0000303c, "A1 was %08x instead of 0x0000303c", state.address[1]);
|
||||
|
||||
// Perform MOVE #$400, A4; MOVE.l (A4), D2
|
||||
_machine->run_for_instructions(2);
|
||||
state = _machine->get_processor_state();
|
||||
XCTAssert(state.address[4] == 0x1000, "A4 was %08x instead of 0x00001000", state.address[4]);
|
||||
XCTAssert(state.data[2] == 0x303cfb2e, "D2 was %08x instead of 0x303cfb2e", state.data[2]);
|
||||
}
|
||||
|
||||
- (void)testVectoredInterrupt {
|
||||
_machine->set_program({
|
||||
0x46fc, 0x2000, // MOVE.w #$2000, SR
|
||||
0x4e71, // NOP
|
||||
0x4e71, // NOP
|
||||
0x4e71, // NOP
|
||||
0x4e71, // NOP
|
||||
0x4e71, // NOP
|
||||
});
|
||||
|
||||
// Set the vector that will be supplied back to the start of the
|
||||
// program; this will ensure no further exceptions following
|
||||
// the interrupt.
|
||||
const auto vector = _machine->ram_at(40);
|
||||
vector[0] = 0x0000;
|
||||
vector[1] = 0x1004;
|
||||
|
||||
_machine->run_for_instructions(3);
|
||||
_machine->processor().set_interrupt_level(1);
|
||||
_machine->run_for_instructions(1);
|
||||
|
||||
const auto state = _machine->processor().get_state();
|
||||
XCTAssert(state.program_counter == 0x1008); // i.e. the interrupt happened, the instruction performed was the one at 1004, and therefore
|
||||
// by the wonders of prefetch the program counter is now at 1008.
|
||||
}
|
||||
|
||||
- (void)testOpcodeCoverage {
|
||||
// Perform an audit of implemented instructions.
|
||||
CPU::MC68000::ProcessorStorageTests storage_tests(
|
||||
_machine->processor(),
|
||||
[[NSBundle bundleForClass:[self class]] pathForResource:@"OPCLOGR2" ofType:@"BIN"].UTF8String
|
||||
);
|
||||
|
||||
// This is a list of instructions nominated as valid with OPCLOGR2.BIN but with no obvious decoding —
|
||||
// the disassemblers I tried couldn't figure them out, and I didn't spot them anywhere in the PRM.
|
||||
NSSet<NSNumber *> *const undecodables = [NSSet setWithArray:@[
|
||||
// These look like malformed MOVEs.
|
||||
@(0x2e7d), @(0x2e7e), @(0x2e7f), @(0x2efd), @(0x2efe), @(0x2eff), @(0x2f7d), @(0x2f7e),
|
||||
@(0x2f7f), @(0x2fc0), @(0x2fc1), @(0x2fc2), @(0x2fc3), @(0x2fc4), @(0x2fc5), @(0x2fc6),
|
||||
@(0x2fc7), @(0x2fc8), @(0x2fc9), @(0x2fca), @(0x2fcb), @(0x2fcc), @(0x2fcd), @(0x2fce),
|
||||
@(0x2fcf), @(0x2fd0), @(0x2fd1), @(0x2fd2), @(0x2fd3), @(0x2fd4), @(0x2fd5), @(0x2fd6),
|
||||
@(0x2fd7), @(0x2fd8), @(0x2fd9), @(0x2fda), @(0x2fdb), @(0x2fdc), @(0x2fdd), @(0x2fde),
|
||||
@(0x2fdf), @(0x2fe0), @(0x2fe1), @(0x2fe2), @(0x2fe3), @(0x2fe4), @(0x2fe5), @(0x2fe6),
|
||||
@(0x2fe7), @(0x2fe8), @(0x2fe9), @(0x2fea), @(0x2feb), @(0x2fec), @(0x2fed), @(0x2fee),
|
||||
@(0x2fef), @(0x2ff0), @(0x2ff1), @(0x2ff2), @(0x2ff3), @(0x2ff4), @(0x2ff5), @(0x2ff6),
|
||||
@(0x2ff7), @(0x2ff8), @(0x2ff9), @(0x2ffa), @(0x2ffb), @(0x2ffc), @(0x2ffd), @(0x2ffe),
|
||||
@(0x2fff),
|
||||
|
||||
@(0x3e7d), @(0x3e7e), @(0x3e7f), @(0x3efd), @(0x3efe), @(0x3eff), @(0x3f7d), @(0x3f7e),
|
||||
@(0x3f7f), @(0x3fc0), @(0x3fc1), @(0x3fc2), @(0x3fc3), @(0x3fc4), @(0x3fc5), @(0x3fc6),
|
||||
@(0x3fc7), @(0x3fc8), @(0x3fc9), @(0x3fca), @(0x3fcb), @(0x3fcc), @(0x3fcd), @(0x3fce),
|
||||
@(0x3fcf), @(0x3fd0), @(0x3fd1), @(0x3fd2), @(0x3fd3), @(0x3fd4), @(0x3fd5), @(0x3fd6),
|
||||
@(0x3fd7), @(0x3fd8), @(0x3fd9), @(0x3fda), @(0x3fdb), @(0x3fdc), @(0x3fdd), @(0x3fde),
|
||||
@(0x3fdf), @(0x3fe0), @(0x3fe1), @(0x3fe2), @(0x3fe3), @(0x3fe4), @(0x3fe5), @(0x3fe6),
|
||||
@(0x3fe7), @(0x3fe8), @(0x3fe9), @(0x3fea), @(0x3feb), @(0x3fec), @(0x3fed), @(0x3fee),
|
||||
@(0x3fef), @(0x3ff0), @(0x3ff1), @(0x3ff2), @(0x3ff3), @(0x3ff4), @(0x3ff5), @(0x3ff6),
|
||||
@(0x3ff7), @(0x3ff8), @(0x3ff9), @(0x3ffa), @(0x3ffb), @(0x3ffc), @(0x3ffd), @(0x3ffe),
|
||||
@(0x3fff),
|
||||
|
||||
@(0x46c8), @(0x46c9), @(0x46ca), @(0x46cb), @(0x46cc), @(0x46cd), @(0x46ce), @(0x46cf),
|
||||
@(0x46fd), @(0x46fe), @(0x46ff), @(0x47c0), @(0x47c1), @(0x47c2), @(0x47c3), @(0x47c4),
|
||||
@(0x47c5), @(0x47c6), @(0x47c7), @(0x47c8), @(0x47c9), @(0x47ca), @(0x47cb), @(0x47cc),
|
||||
@(0x47cd), @(0x47ce), @(0x47cf), @(0x47d8), @(0x47d9), @(0x47da), @(0x47db), @(0x47dc),
|
||||
@(0x47dd), @(0x47de), @(0x47df), @(0x47e0), @(0x47e1), @(0x47e2), @(0x47e3), @(0x47e4),
|
||||
@(0x47e5), @(0x47e6), @(0x47e7), @(0x47fc), @(0x47fd), @(0x47fe), @(0x47ff), @(0x4e80),
|
||||
@(0x4e81), @(0x4e82), @(0x4e83), @(0x4e84), @(0x4e85), @(0x4e86), @(0x4e87), @(0x4e88),
|
||||
@(0x4e89), @(0x4e8a), @(0x4e8b), @(0x4e8c), @(0x4e8d), @(0x4e8e), @(0x4e8f), @(0x4e98),
|
||||
@(0x4e99), @(0x4e9a), @(0x4e9b), @(0x4e9c), @(0x4e9d), @(0x4e9e), @(0x4e9f), @(0x4ea0),
|
||||
@(0x4ea1), @(0x4ea2), @(0x4ea3), @(0x4ea4), @(0x4ea5), @(0x4ea6), @(0x4ea7), @(0x4ebc),
|
||||
@(0x4ebd), @(0x4ebe), @(0x4ebf), @(0x4ec0), @(0x4ec1), @(0x4ec2), @(0x4ec3), @(0x4ec4),
|
||||
@(0x4ec5), @(0x4ec6), @(0x4ec7), @(0x4ec8), @(0x4ec9), @(0x4eca), @(0x4ecb), @(0x4ecc),
|
||||
@(0x4ecd), @(0x4ece), @(0x4ecf), @(0x4ed8), @(0x4ed9), @(0x4eda), @(0x4edb), @(0x4edc),
|
||||
@(0x4edd), @(0x4ede), @(0x4edf), @(0x4ee0), @(0x4ee1), @(0x4ee2), @(0x4ee3), @(0x4ee4),
|
||||
@(0x4ee5), @(0x4ee6), @(0x4ee7), @(0x4efc), @(0x4efd), @(0x4efe), @(0x4eff), @(0x4f88),
|
||||
@(0x4f89), @(0x4f8a), @(0x4f8b), @(0x4f8c), @(0x4f8d), @(0x4f8e), @(0x4f8f), @(0x4fbd),
|
||||
@(0x4fbe), @(0x4fbf), @(0x4fc0), @(0x4fc1), @(0x4fc2), @(0x4fc3), @(0x4fc4), @(0x4fc5),
|
||||
@(0x4fc6), @(0x4fc7), @(0x4fc8), @(0x4fc9), @(0x4fca), @(0x4fcb), @(0x4fcc), @(0x4fcd),
|
||||
@(0x4fce), @(0x4fcf), @(0x4fd8), @(0x4fd9), @(0x4fda), @(0x4fdb), @(0x4fdc), @(0x4fdd),
|
||||
@(0x4fde), @(0x4fdf), @(0x4fe0), @(0x4fe1), @(0x4fe2), @(0x4fe3), @(0x4fe4), @(0x4fe5),
|
||||
@(0x4fe6), @(0x4fe7), @(0x4ffc), @(0x4ffd), @(0x4ffe), @(0x4fff),
|
||||
|
||||
@(0x50fa), @(0x50fb), @(0x50fc), @(0x50fd), @(0x50fe), @(0x50ff), @(0x51fa), @(0x51fb),
|
||||
@(0x51fc), @(0x51fd), @(0x51fe), @(0x51ff), @(0x52fa), @(0x52fb), @(0x52fc), @(0x52fd),
|
||||
@(0x52fe), @(0x52ff), @(0x53fa), @(0x53fb), @(0x53fc), @(0x53fd), @(0x53fe), @(0x53ff),
|
||||
@(0x54fa), @(0x54fb), @(0x54fc), @(0x54fd), @(0x54fe), @(0x54ff), @(0x55fa), @(0x55fb),
|
||||
@(0x55fc), @(0x55fd), @(0x55fe), @(0x55ff), @(0x56fa), @(0x56fb), @(0x56fc), @(0x56fd),
|
||||
@(0x56fe), @(0x56ff), @(0x57fa), @(0x57fb), @(0x57fc), @(0x57fd), @(0x57fe), @(0x57ff),
|
||||
@(0x58fa), @(0x58fb), @(0x58fc), @(0x58fd), @(0x58fe), @(0x58ff), @(0x59fa), @(0x59fb),
|
||||
@(0x59fc), @(0x59fd), @(0x59fe), @(0x59ff), @(0x5afa), @(0x5afb), @(0x5afc), @(0x5afd),
|
||||
@(0x5afe), @(0x5aff), @(0x5bfa), @(0x5bfb), @(0x5bfc), @(0x5bfd), @(0x5bfe), @(0x5bff),
|
||||
@(0x5cfa), @(0x5cfb), @(0x5cfc), @(0x5cfd), @(0x5cfe), @(0x5cff), @(0x5dfa), @(0x5dfb),
|
||||
@(0x5dfc), @(0x5dfd), @(0x5dfe), @(0x5dff), @(0x5eba), @(0x5ebb), @(0x5ebc), @(0x5ebd),
|
||||
@(0x5ebe), @(0x5ebf), @(0x5efa), @(0x5efb), @(0x5efc), @(0x5efd), @(0x5efe), @(0x5eff),
|
||||
@(0x5fba), @(0x5fbb), @(0x5fbc), @(0x5fbd), @(0x5fbe), @(0x5fbf), @(0x5ffa), @(0x5ffb),
|
||||
@(0x5ffc), @(0x5ffd), @(0x5ffe), @(0x5fff),
|
||||
|
||||
// These are almost MOVEQs if only bit 8 weren't set.
|
||||
@(0x71c8), @(0x71c9), @(0x71ca), @(0x71cb), @(0x71cc), @(0x71cd), @(0x71ce), @(0x71cf),
|
||||
@(0x71d8), @(0x71d9), @(0x71da), @(0x71db), @(0x71dc), @(0x71dd), @(0x71de), @(0x71df),
|
||||
@(0x71e8), @(0x71e9), @(0x71ea), @(0x71eb), @(0x71ec), @(0x71ed), @(0x71ee), @(0x71ef),
|
||||
@(0x71f8), @(0x71f9), @(0x71fa), @(0x71fb), @(0x71fc), @(0x71fd), @(0x71fe), @(0x71ff),
|
||||
@(0x73c8), @(0x73c9), @(0x73ca), @(0x73cb), @(0x73cc), @(0x73cd), @(0x73ce), @(0x73cf),
|
||||
@(0x73d8), @(0x73d9), @(0x73da), @(0x73db), @(0x73dc), @(0x73dd), @(0x73de), @(0x73df),
|
||||
@(0x73e8), @(0x73e9), @(0x73ea), @(0x73eb), @(0x73ec), @(0x73ed), @(0x73ee), @(0x73ef),
|
||||
@(0x73f8), @(0x73f9), @(0x73fa), @(0x73fb), @(0x73fc), @(0x73fd), @(0x73fe), @(0x73ff),
|
||||
@(0x75c8), @(0x75c9), @(0x75ca), @(0x75cb), @(0x75cc), @(0x75cd), @(0x75ce), @(0x75cf),
|
||||
@(0x75d8), @(0x75d9), @(0x75da), @(0x75db), @(0x75dc), @(0x75dd), @(0x75de), @(0x75df),
|
||||
@(0x75e8), @(0x75e9), @(0x75ea), @(0x75eb), @(0x75ec), @(0x75ed), @(0x75ee), @(0x75ef),
|
||||
@(0x75f8), @(0x75f9), @(0x75fa), @(0x75fb), @(0x75fc), @(0x75fd), @(0x75fe), @(0x75ff),
|
||||
@(0x77c0), @(0x77c1), @(0x77c2), @(0x77c3), @(0x77c4), @(0x77c5), @(0x77c6), @(0x77c7),
|
||||
@(0x77c8), @(0x77c9), @(0x77ca), @(0x77cb), @(0x77cc), @(0x77cd), @(0x77ce), @(0x77cf),
|
||||
@(0x77d0), @(0x77d1), @(0x77d2), @(0x77d3), @(0x77d4), @(0x77d5), @(0x77d6), @(0x77d7),
|
||||
@(0x77d8), @(0x77d9), @(0x77da), @(0x77db), @(0x77dc), @(0x77dd), @(0x77de), @(0x77df),
|
||||
@(0x77e0), @(0x77e1), @(0x77e2), @(0x77e3), @(0x77e4), @(0x77e5), @(0x77e6), @(0x77e7),
|
||||
@(0x77e8), @(0x77e9), @(0x77ea), @(0x77eb), @(0x77ec), @(0x77ed), @(0x77ee), @(0x77ef),
|
||||
@(0x77f0), @(0x77f1), @(0x77f2), @(0x77f3), @(0x77f4), @(0x77f5), @(0x77f6), @(0x77f7),
|
||||
@(0x77f8), @(0x77f9), @(0x77fa), @(0x77fb), @(0x77fc), @(0x77fd), @(0x77fe), @(0x77ff),
|
||||
@(0x79c8), @(0x79c9), @(0x79ca), @(0x79cb), @(0x79cc), @(0x79cd), @(0x79ce), @(0x79cf),
|
||||
@(0x79d8), @(0x79d9), @(0x79da), @(0x79db), @(0x79dc), @(0x79dd), @(0x79de), @(0x79df),
|
||||
@(0x79e8), @(0x79e9), @(0x79ea), @(0x79eb), @(0x79ec), @(0x79ed), @(0x79ee), @(0x79ef),
|
||||
@(0x79f8), @(0x79f9), @(0x79fa), @(0x79fb), @(0x79fc), @(0x79fd), @(0x79fe), @(0x79ff),
|
||||
@(0x7bc8), @(0x7bc9), @(0x7bca), @(0x7bcb), @(0x7bcc), @(0x7bcd), @(0x7bce), @(0x7bcf),
|
||||
@(0x7bd8), @(0x7bd9), @(0x7bda), @(0x7bdb), @(0x7bdc), @(0x7bdd), @(0x7bde), @(0x7bdf),
|
||||
@(0x7be8), @(0x7be9), @(0x7bea), @(0x7beb), @(0x7bec), @(0x7bed), @(0x7bee), @(0x7bef),
|
||||
@(0x7bf8), @(0x7bf9), @(0x7bfa), @(0x7bfb), @(0x7bfc), @(0x7bfd), @(0x7bfe), @(0x7bff),
|
||||
@(0x7dc8), @(0x7dc9), @(0x7dca), @(0x7dcb), @(0x7dcc), @(0x7dcd), @(0x7dce), @(0x7dcf),
|
||||
@(0x7dd8), @(0x7dd9), @(0x7dda), @(0x7ddb), @(0x7ddc), @(0x7ddd), @(0x7dde), @(0x7ddf),
|
||||
@(0x7de8), @(0x7de9), @(0x7dea), @(0x7deb), @(0x7dec), @(0x7ded), @(0x7dee), @(0x7def),
|
||||
@(0x7df8), @(0x7df9), @(0x7dfa), @(0x7dfb), @(0x7dfc), @(0x7dfd), @(0x7dfe), @(0x7dff),
|
||||
@(0x7f40), @(0x7f41), @(0x7f42), @(0x7f43), @(0x7f44), @(0x7f45), @(0x7f46), @(0x7f47),
|
||||
@(0x7f48), @(0x7f49), @(0x7f4a), @(0x7f4b), @(0x7f4c), @(0x7f4d), @(0x7f4e), @(0x7f4f),
|
||||
@(0x7f50), @(0x7f51), @(0x7f52), @(0x7f53), @(0x7f54), @(0x7f55), @(0x7f56), @(0x7f57),
|
||||
@(0x7f58), @(0x7f59), @(0x7f5a), @(0x7f5b), @(0x7f5c), @(0x7f5d), @(0x7f5e), @(0x7f5f),
|
||||
@(0x7f60), @(0x7f61), @(0x7f62), @(0x7f63), @(0x7f64), @(0x7f65), @(0x7f66), @(0x7f67),
|
||||
@(0x7f68), @(0x7f69), @(0x7f6a), @(0x7f6b), @(0x7f6c), @(0x7f6d), @(0x7f6e), @(0x7f6f),
|
||||
@(0x7f70), @(0x7f71), @(0x7f72), @(0x7f73), @(0x7f74), @(0x7f75), @(0x7f76), @(0x7f77),
|
||||
@(0x7f78), @(0x7f79), @(0x7f7a), @(0x7f7b), @(0x7f7c), @(0x7f7d), @(0x7f7e), @(0x7f7f),
|
||||
@(0x7f80), @(0x7f81), @(0x7f82), @(0x7f83), @(0x7f84), @(0x7f85), @(0x7f86), @(0x7f87),
|
||||
@(0x7f88), @(0x7f89), @(0x7f8a), @(0x7f8b), @(0x7f8c), @(0x7f8d), @(0x7f8e), @(0x7f8f),
|
||||
@(0x7f90), @(0x7f91), @(0x7f92), @(0x7f93), @(0x7f94), @(0x7f95), @(0x7f96), @(0x7f97),
|
||||
@(0x7f98), @(0x7f99), @(0x7f9a), @(0x7f9b), @(0x7f9c), @(0x7f9d), @(0x7f9e), @(0x7f9f),
|
||||
@(0x7fa0), @(0x7fa1), @(0x7fa2), @(0x7fa3), @(0x7fa4), @(0x7fa5), @(0x7fa6), @(0x7fa7),
|
||||
@(0x7fa8), @(0x7fa9), @(0x7faa), @(0x7fab), @(0x7fac), @(0x7fad), @(0x7fae), @(0x7faf),
|
||||
@(0x7fb0), @(0x7fb1), @(0x7fb2), @(0x7fb3), @(0x7fb4), @(0x7fb5), @(0x7fb6), @(0x7fb7),
|
||||
@(0x7fb8), @(0x7fb9), @(0x7fba), @(0x7fbb), @(0x7fbc), @(0x7fbd), @(0x7fbe), @(0x7fbf),
|
||||
@(0x7fc0), @(0x7fc1), @(0x7fc2), @(0x7fc3), @(0x7fc4), @(0x7fc5), @(0x7fc6), @(0x7fc7),
|
||||
@(0x7fc8), @(0x7fc9), @(0x7fca), @(0x7fcb), @(0x7fcc), @(0x7fcd), @(0x7fce), @(0x7fcf),
|
||||
@(0x7fd0), @(0x7fd1), @(0x7fd2), @(0x7fd3), @(0x7fd4), @(0x7fd5), @(0x7fd6), @(0x7fd7),
|
||||
@(0x7fd8), @(0x7fd9), @(0x7fda), @(0x7fdb), @(0x7fdc), @(0x7fdd), @(0x7fde), @(0x7fdf),
|
||||
@(0x7fe0), @(0x7fe1), @(0x7fe2), @(0x7fe3), @(0x7fe4), @(0x7fe5), @(0x7fe6), @(0x7fe7),
|
||||
@(0x7fe8), @(0x7fe9), @(0x7fea), @(0x7feb), @(0x7fec), @(0x7fed), @(0x7fee), @(0x7fef),
|
||||
@(0x7ff0), @(0x7ff1), @(0x7ff2), @(0x7ff3), @(0x7ff4), @(0x7ff5), @(0x7ff6), @(0x7ff7),
|
||||
@(0x7ff8), @(0x7ff9), @(0x7ffa), @(0x7ffb), @(0x7ffc), @(0x7ffd), @(0x7ffe), @(0x7fff),
|
||||
|
||||
@(0xbe7d), @(0xbe7e), @(0xbe7f), @(0xbefd), @(0xbefe), @(0xbeff), @(0xbf7a), @(0xbf7b),
|
||||
@(0xbf7c), @(0xbf7d), @(0xbf7e), @(0xbf7f), @(0xbffd), @(0xbffe), @(0xbfff),
|
||||
|
||||
//
|
||||
@(0xc6c8), @(0xc6c9), @(0xc6ca), @(0xc6cb), @(0xc6cc), @(0xc6cd), @(0xc6ce), @(0xc6cf),
|
||||
@(0xc6fd), @(0xc6fe), @(0xc6ff), @(0xc7c8), @(0xc7c9), @(0xc7ca), @(0xc7cb), @(0xc7cc),
|
||||
@(0xc7cd), @(0xc7ce), @(0xc7cf), @(0xc7fd), @(0xc7fe), @(0xc7ff), @(0xce88), @(0xce89),
|
||||
@(0xce8a), @(0xce8b), @(0xce8c), @(0xce8d), @(0xce8e), @(0xce8f), @(0xcebd), @(0xcebe),
|
||||
@(0xcebf), @(0xcec8), @(0xcec9), @(0xceca), @(0xcecb), @(0xcecc), @(0xcecd), @(0xcece),
|
||||
@(0xcecf), @(0xcefd), @(0xcefe), @(0xceff), @(0xcf80), @(0xcf81), @(0xcf82), @(0xcf83),
|
||||
@(0xcf84), @(0xcf85), @(0xcf86), @(0xcf87), @(0xcfba), @(0xcfbb), @(0xcfbc), @(0xcfbd),
|
||||
@(0xcfbe), @(0xcfbf), @(0xcfc8), @(0xcfc9), @(0xcfca), @(0xcfcb), @(0xcfcc), @(0xcfcd),
|
||||
@(0xcfce), @(0xcfcf), @(0xcffd), @(0xcffe), @(0xcfff),
|
||||
|
||||
// These are from the Bcc/BRA/BSR page.
|
||||
@(0xd0fd), @(0xd0fe), @(0xd0ff), @(0xd1fd), @(0xd1fe), @(0xd1ff), @(0xd2fd), @(0xd2fe),
|
||||
@(0xd2ff), @(0xd3fd), @(0xd3fe), @(0xd3ff), @(0xd4fd), @(0xd4fe), @(0xd4ff), @(0xd5fd),
|
||||
@(0xd5fe), @(0xd5ff), @(0xd6fd), @(0xd6fe), @(0xd6ff), @(0xd7fd), @(0xd7fe), @(0xd7ff),
|
||||
@(0xd8fd), @(0xd8fe), @(0xd8ff), @(0xd9fd), @(0xd9fe), @(0xd9ff), @(0xdafd), @(0xdafe),
|
||||
@(0xdaff), @(0xdbfd), @(0xdbfe), @(0xdbff), @(0xdcfd), @(0xdcfe), @(0xdcff), @(0xddfd),
|
||||
@(0xddfe), @(0xddff), @(0xdebd), @(0xdebe), @(0xdebf), @(0xdefd), @(0xdefe), @(0xdeff),
|
||||
@(0xdfba), @(0xdfbb), @(0xdfbc), @(0xdfbd), @(0xdfbe), @(0xdfbf), @(0xdffd), @(0xdffe),
|
||||
@(0xdfff),
|
||||
|
||||
// The E line is for shifts and rolls; none of the those listed below appear to nominate valid
|
||||
// addressing modes.
|
||||
@(0xe6c0), @(0xe6c1), @(0xe6c2), @(0xe6c3), @(0xe6c4), @(0xe6c5), @(0xe6c6), @(0xe6c7),
|
||||
@(0xe6c8), @(0xe6c9), @(0xe6ca), @(0xe6cb), @(0xe6cc), @(0xe6cd), @(0xe6ce), @(0xe6cf),
|
||||
@(0xe6fa), @(0xe6fb), @(0xe6fc), @(0xe6fd), @(0xe6fe), @(0xe6ff), @(0xe7c0), @(0xe7c1),
|
||||
@(0xe7c2), @(0xe7c3), @(0xe7c4), @(0xe7c5), @(0xe7c6), @(0xe7c7), @(0xe7c8), @(0xe7c9),
|
||||
@(0xe7ca), @(0xe7cb), @(0xe7cc), @(0xe7cd), @(0xe7ce), @(0xe7cf), @(0xe7fa), @(0xe7fb),
|
||||
@(0xe7fc), @(0xe7fd), @(0xe7fe), @(0xe7ff), @(0xeec0), @(0xeec1), @(0xeec2), @(0xeec3),
|
||||
@(0xeec4), @(0xeec5), @(0xeec6), @(0xeec7), @(0xeec8), @(0xeec9), @(0xeeca), @(0xeecb),
|
||||
@(0xeecc), @(0xeecd), @(0xeece), @(0xeecf), @(0xeed0), @(0xeed1), @(0xeed2), @(0xeed3),
|
||||
@(0xeed4), @(0xeed5), @(0xeed6), @(0xeed7), @(0xeed8), @(0xeed9), @(0xeeda), @(0xeedb),
|
||||
@(0xeedc), @(0xeedd), @(0xeede), @(0xeedf), @(0xeee0), @(0xeee1), @(0xeee2), @(0xeee3),
|
||||
@(0xeee4), @(0xeee5), @(0xeee6), @(0xeee7), @(0xeee8), @(0xeee9), @(0xeeea), @(0xeeeb),
|
||||
@(0xeeec), @(0xeeed), @(0xeeee), @(0xeeef), @(0xeef0), @(0xeef1), @(0xeef2), @(0xeef3),
|
||||
@(0xeef4), @(0xeef5), @(0xeef6), @(0xeef7), @(0xeef8), @(0xeef9), @(0xeefa), @(0xeefb),
|
||||
@(0xeefc), @(0xeefd), @(0xeefe), @(0xeeff), @(0xefc0), @(0xefc1), @(0xefc2), @(0xefc3),
|
||||
@(0xefc4), @(0xefc5), @(0xefc6), @(0xefc7), @(0xefc8), @(0xefc9), @(0xefca), @(0xefcb),
|
||||
@(0xefcc), @(0xefcd), @(0xefce), @(0xefcf), @(0xefd0), @(0xefd1), @(0xefd2), @(0xefd3),
|
||||
@(0xefd4), @(0xefd5), @(0xefd6), @(0xefd7), @(0xefd8), @(0xefd9), @(0xefda), @(0xefdb),
|
||||
@(0xefdc), @(0xefdd), @(0xefde), @(0xefdf), @(0xefe0), @(0xefe1), @(0xefe2), @(0xefe3),
|
||||
@(0xefe4), @(0xefe5), @(0xefe6), @(0xefe7), @(0xefe8), @(0xefe9), @(0xefea), @(0xefeb),
|
||||
@(0xefec), @(0xefed), @(0xefee), @(0xefef), @(0xeff0), @(0xeff1), @(0xeff2), @(0xeff3),
|
||||
@(0xeff4), @(0xeff5), @(0xeff6), @(0xeff7), @(0xeff8), @(0xeff9), @(0xeffa), @(0xeffb),
|
||||
@(0xeffc), @(0xeffd), @(0xeffe), @(0xefff)
|
||||
]];
|
||||
|
||||
NSSet<NSNumber *> *const falseValids = storage_tests.false_valids();
|
||||
NSSet<NSNumber *> *const falseInvalids = storage_tests.false_invalids();
|
||||
|
||||
XCTAssert(!falseValids.count, "%@ opcodes should be invalid but aren't: %@", @(falseValids.count), falseValids.hexDump);
|
||||
|
||||
NSMutableSet<NSNumber *> *const decodedUndecodables = [undecodables mutableCopy];
|
||||
[decodedUndecodables minusSet:falseInvalids];
|
||||
XCTAssert(!decodedUndecodables.count, "This test considers these undecodable but they were decoded: %@", decodedUndecodables.hexDump);
|
||||
|
||||
NSMutableSet<NSNumber *> *const trimmedInvalids = [falseInvalids mutableCopy];
|
||||
[trimmedInvalids minusSet:undecodables];
|
||||
XCTAssert(!trimmedInvalids.count, "%@ opcodes should be valid but aren't: %@", @(trimmedInvalids.count), trimmedInvalids.hexDump);
|
||||
|
||||
// XCTAssert(!falseInvalids.count, "%@ opcodes should be valid but aren't: %@", @(falseInvalids.count), falseInvalids.hexDump);
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -1,140 +0,0 @@
|
||||
//
|
||||
// ArrayBuilderTests.m
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 19/11/2016.
|
||||
// Copyright 2016 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#import <XCTest/XCTest.h>
|
||||
|
||||
#include "ArrayBuilder.hpp"
|
||||
|
||||
static NSData *inputData, *outputData;
|
||||
|
||||
static void setData(bool is_input, uint8_t *data, size_t size)
|
||||
{
|
||||
NSData *dataObject = [NSData dataWithBytes:data length:size];
|
||||
if(is_input) inputData = dataObject; else outputData = dataObject;
|
||||
}
|
||||
|
||||
@interface ArrayBuilderTests : XCTestCase
|
||||
@end
|
||||
|
||||
@implementation ArrayBuilderTests
|
||||
|
||||
+ (void)setUp
|
||||
{
|
||||
inputData = nil;
|
||||
outputData = nil;
|
||||
}
|
||||
|
||||
- (void)assertMonotonicForInputSize:(size_t)inputSize outputSize:(size_t)outputSize
|
||||
{
|
||||
XCTAssert(inputData != nil, @"Should have received some input data");
|
||||
XCTAssert(outputData != nil, @"Should have received some output data");
|
||||
|
||||
XCTAssert(inputData.length == inputSize, @"Input data should be %lu bytes long, was %lu", inputSize, (unsigned long)inputData.length);
|
||||
XCTAssert(outputData.length == outputSize, @"Output data should be %lu bytes long, was %lu", outputSize, (unsigned long)outputData.length);
|
||||
|
||||
if(inputData.length == inputSize && outputData.length == outputSize)
|
||||
{
|
||||
uint8_t *input = (uint8_t *)inputData.bytes;
|
||||
uint8_t *output = (uint8_t *)outputData.bytes;
|
||||
|
||||
for(int c = 0; c < inputSize; c++) XCTAssert(input[c] == c, @"Input item %d should be %d, was %d", c, c, input[c]);
|
||||
for(int c = 0; c < outputSize; c++) XCTAssert(output[c] == c + 0x80, @"Output item %d should be %d, was %d", c, c+0x80, output[c]);
|
||||
}
|
||||
}
|
||||
|
||||
- (std::function<void(uint8_t *input, size_t input_size, uint8_t *output, size_t output_size)>)emptyFlushFunction
|
||||
{
|
||||
return [=] (uint8_t *input, size_t input_size, uint8_t *output, size_t output_size) {};
|
||||
}
|
||||
|
||||
- (void)testSingleWriteSingleFlush
|
||||
{
|
||||
Outputs::CRT::ArrayBuilder arrayBuilder(200, 100, setData);
|
||||
|
||||
uint8_t *input = arrayBuilder.get_input_storage(5);
|
||||
uint8_t *output = arrayBuilder.get_output_storage(3);
|
||||
|
||||
for(int c = 0; c < 5; c++) input[c] = c;
|
||||
for(int c = 0; c < 3; c++) output[c] = c + 0x80;
|
||||
|
||||
arrayBuilder.flush(self.emptyFlushFunction);
|
||||
arrayBuilder.submit();
|
||||
|
||||
[self assertMonotonicForInputSize:5 outputSize:3];
|
||||
}
|
||||
|
||||
- (void)testDoubleWriteSingleFlush
|
||||
{
|
||||
Outputs::CRT::ArrayBuilder arrayBuilder(200, 100, setData);
|
||||
uint8_t *input;
|
||||
uint8_t *output;
|
||||
|
||||
input = arrayBuilder.get_input_storage(2);
|
||||
output = arrayBuilder.get_output_storage(2);
|
||||
|
||||
for(int c = 0; c < 2; c++) input[c] = c;
|
||||
for(int c = 0; c < 2; c++) output[c] = c + 0x80;
|
||||
|
||||
input = arrayBuilder.get_input_storage(2);
|
||||
output = arrayBuilder.get_output_storage(2);
|
||||
|
||||
for(int c = 0; c < 2; c++) input[c] = c+2;
|
||||
for(int c = 0; c < 2; c++) output[c] = c+2 + 0x80;
|
||||
|
||||
arrayBuilder.flush(self.emptyFlushFunction);
|
||||
arrayBuilder.submit();
|
||||
|
||||
[self assertMonotonicForInputSize:4 outputSize:4];
|
||||
}
|
||||
|
||||
- (void)testSubmitWithoutFlush
|
||||
{
|
||||
Outputs::CRT::ArrayBuilder arrayBuilder(200, 100, setData);
|
||||
|
||||
arrayBuilder.get_input_storage(5);
|
||||
arrayBuilder.get_input_storage(8);
|
||||
arrayBuilder.get_output_storage(6);
|
||||
arrayBuilder.get_input_storage(12);
|
||||
arrayBuilder.get_output_storage(3);
|
||||
|
||||
arrayBuilder.submit();
|
||||
|
||||
XCTAssert(inputData.length == 0, @"No input data should have been received; %lu bytes were received", (unsigned long)inputData.length);
|
||||
XCTAssert(outputData.length == 0, @"No output data should have been received; %lu bytes were received", (unsigned long)outputData.length);
|
||||
|
||||
arrayBuilder.flush(self.emptyFlushFunction);
|
||||
arrayBuilder.submit();
|
||||
|
||||
XCTAssert(inputData.length == 25, @"All input data should have been received; %lu bytes were received", (unsigned long)inputData.length);
|
||||
XCTAssert(outputData.length == 9, @"All output data should have been received; %lu bytes were received", (unsigned long)outputData.length);
|
||||
}
|
||||
|
||||
- (void)testSubmitContinuity
|
||||
{
|
||||
Outputs::CRT::ArrayBuilder arrayBuilder(200, 100, setData);
|
||||
|
||||
arrayBuilder.get_input_storage(5);
|
||||
arrayBuilder.get_output_storage(5);
|
||||
|
||||
arrayBuilder.flush(self.emptyFlushFunction);
|
||||
|
||||
uint8_t *input = arrayBuilder.get_input_storage(5);
|
||||
uint8_t *output = arrayBuilder.get_output_storage(5);
|
||||
|
||||
arrayBuilder.submit();
|
||||
|
||||
for(int c = 0; c < 5; c++) input[c] = c;
|
||||
for(int c = 0; c < 5; c++) output[c] = c + 0x80;
|
||||
|
||||
arrayBuilder.flush(self.emptyFlushFunction);
|
||||
arrayBuilder.submit();
|
||||
|
||||
[self assertMonotonicForInputSize:5 outputSize:5];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -8,6 +8,14 @@
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
typedef NS_ENUM(NSInteger, MOS6522BridgePort) {
|
||||
MOS6522BridgePortA = 0, MOS6522BridgePortB = 1
|
||||
};
|
||||
|
||||
typedef NS_ENUM(NSInteger, MOS6522BridgeLine) {
|
||||
MOS6522BridgeLineOne = 0, MOS6522BridgeLineTwo = 1
|
||||
};
|
||||
|
||||
@interface MOS6522Bridge : NSObject
|
||||
|
||||
@property (nonatomic, readonly) BOOL irqLine;
|
||||
@@ -16,6 +24,7 @@
|
||||
|
||||
- (void)setValue:(uint8_t)value forRegister:(NSUInteger)registerNumber;
|
||||
- (uint8_t)valueForRegister:(NSUInteger)registerNumber;
|
||||
- (BOOL)valueForControlLine:(MOS6522BridgeLine)line port:(MOS6522BridgePort)port;
|
||||
|
||||
- (void)runForHalfCycles:(NSUInteger)numberOfHalfCycles;
|
||||
|
||||
|
||||
@@ -19,7 +19,12 @@ class VanillaVIAPortHandler: public MOS::MOS6522::PortHandler {
|
||||
bool irq_line;
|
||||
uint8_t port_a_value;
|
||||
uint8_t port_b_value;
|
||||
bool control_line_values[2][2];
|
||||
|
||||
/*
|
||||
All methods below here are to replace those defined by
|
||||
MOS::MOS6522::PortHandler.
|
||||
*/
|
||||
void set_interrupt_status(bool new_status) {
|
||||
irq_line = new_status;
|
||||
}
|
||||
@@ -27,6 +32,10 @@ class VanillaVIAPortHandler: public MOS::MOS6522::PortHandler {
|
||||
uint8_t get_port_input(MOS::MOS6522::Port port) {
|
||||
return port ? port_b_value : port_a_value;
|
||||
}
|
||||
|
||||
void set_control_line_output(MOS::MOS6522::Port port, MOS::MOS6522::Line line, bool value) {
|
||||
control_line_values[int(port)][int(line)] = value;
|
||||
}
|
||||
};
|
||||
|
||||
@implementation MOS6522Bridge {
|
||||
@@ -75,4 +84,8 @@ class VanillaVIAPortHandler: public MOS::MOS6522::PortHandler {
|
||||
return _viaPortHandler.port_b_value;
|
||||
}
|
||||
|
||||
- (BOOL)valueForControlLine:(MOS6522BridgeLine)line port:(MOS6522BridgePort)port {
|
||||
return _viaPortHandler.control_line_values[port][line];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
59
OSBindings/Mac/Clock SignalTests/Comparative68000.hpp
Normal file
59
OSBindings/Mac/Clock SignalTests/Comparative68000.hpp
Normal file
@@ -0,0 +1,59 @@
|
||||
//
|
||||
// Comparative68000.hpp
|
||||
// Clock SignalTests
|
||||
//
|
||||
// Created by Thomas Harte on 29/04/2019.
|
||||
// Copyright © 2019 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef Comparative68000_hpp
|
||||
#define Comparative68000_hpp
|
||||
|
||||
#include <zlib.h>
|
||||
|
||||
#include "68000.hpp"
|
||||
|
||||
class ComparativeBusHandler: public CPU::MC68000::BusHandler {
|
||||
public:
|
||||
ComparativeBusHandler(const char *trace_name) {
|
||||
trace = gzopen(trace_name, "rt");
|
||||
}
|
||||
|
||||
~ComparativeBusHandler() {
|
||||
gzclose(trace);
|
||||
}
|
||||
|
||||
void will_perform(uint32_t address, uint16_t opcode) {
|
||||
// Obtain the next line from the trace file.
|
||||
char correct_state[300] = "\n";
|
||||
gzgets(trace, correct_state, sizeof(correct_state));
|
||||
++line_count;
|
||||
|
||||
// Generate state locally.
|
||||
const auto state = get_state();
|
||||
char local_state[300];
|
||||
sprintf(local_state, "%04x: %02x %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x\n",
|
||||
address,
|
||||
state.status,
|
||||
state.data[0], state.data[1], state.data[2], state.data[3], state.data[4], state.data[5], state.data[6], state.data[7],
|
||||
state.address[0], state.address[1], state.address[2], state.address[3], state.address[4], state.address[5], state.address[6],
|
||||
(state.status & 0x2000) ? state.supervisor_stack_pointer : state.user_stack_pointer
|
||||
);
|
||||
|
||||
// Check that the two coincide.
|
||||
if(strcmp(correct_state, local_state)) {
|
||||
fprintf(stderr, "Diverges at line %d\n", line_count);
|
||||
fprintf(stderr, "Good: %s", correct_state);
|
||||
fprintf(stderr, "Bad: %s", local_state);
|
||||
assert(false);
|
||||
}
|
||||
}
|
||||
|
||||
virtual CPU::MC68000::ProcessorState get_state() = 0;
|
||||
|
||||
private:
|
||||
int line_count = 0;
|
||||
gzFile trace;
|
||||
};
|
||||
|
||||
#endif /* Comparative68000_hpp */
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user