mirror of
https://github.com/TomHarte/CLK.git
synced 2025-10-25 09:27:01 +00:00
Compare commits
2740 Commits
2020-09-16
...
QtFullscre
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1f7f9884f5 | ||
|
|
2bee9b3d56 | ||
|
|
18735ee571 | ||
|
|
1ce07e2ee8 | ||
|
|
7cbee172b2 | ||
|
|
fca974723f | ||
|
|
6a2d4ae11d | ||
|
|
6da634b79f | ||
|
|
c85ca09236 | ||
|
|
a5b7ef5498 | ||
|
|
970087eefb | ||
|
|
11305c2e6b | ||
|
|
5da16023d8 | ||
|
|
b1d8a45339 | ||
|
|
c133f80c73 | ||
|
|
58b04cdfa4 | ||
|
|
1e149d0add | ||
|
|
f7e75da4bd | ||
|
|
c2938a4f63 | ||
|
|
825136b168 | ||
|
|
5a9eb58d33 | ||
|
|
beb4993548 | ||
|
|
48e8bfbb0e | ||
|
|
5dfbc58959 | ||
|
|
924de35cf3 | ||
|
|
7cf9e08948 | ||
|
|
60d3519993 | ||
|
|
c6b4570424 | ||
|
|
f5d56cc473 | ||
|
|
4e52572b03 | ||
|
|
6abc317986 | ||
|
|
22c0b588c4 | ||
|
|
6c9fc0ac75 | ||
|
|
ef322dc705 | ||
|
|
94fcc90886 | ||
|
|
d0df156b05 | ||
|
|
7aeaa4a485 | ||
|
|
823c7765f8 | ||
|
|
5cb0aebdf4 | ||
|
|
ef40a81be2 | ||
|
|
21842052cf | ||
|
|
686dccb48d | ||
|
|
1f7700edac | ||
|
|
5adc656066 | ||
|
|
e0ec3c986d | ||
|
|
827b137c86 | ||
|
|
9cf64ea643 | ||
|
|
fc1952bf42 | ||
|
|
9888f079fa | ||
|
|
f2c2027a8c | ||
|
|
4467eb1c41 | ||
|
|
ef5ac1442f | ||
|
|
1c1ce625a7 | ||
|
|
a442077eac | ||
|
|
6c638712f3 | ||
|
|
069a057a94 | ||
|
|
4ed3b21bf3 | ||
|
|
2e7afb13c7 | ||
|
|
a23b0f5122 | ||
|
|
da552abf75 | ||
|
|
380b5141fb | ||
|
|
66775b2c4e | ||
|
|
2c12a7d968 | ||
|
|
5a97c09238 | ||
|
|
3112376943 | ||
|
|
65140b341d | ||
|
|
ecfd17a259 | ||
|
|
a72dd96dc6 | ||
|
|
944e5ebbfa | ||
|
|
76767110b7 | ||
|
|
2f684ee66d | ||
|
|
7dcfa9eb65 | ||
|
|
ec98736bd7 | ||
|
|
ab0c290489 | ||
|
|
15ac2c3e5a | ||
|
|
0c24a27ba6 | ||
|
|
eb82e06fab | ||
|
|
f8e6954739 | ||
|
|
586ef4810b | ||
|
|
b62f484d93 | ||
|
|
07b600ccaf | ||
|
|
907dc75e5b | ||
|
|
ea0d2971eb | ||
|
|
a0bc332fe6 | ||
|
|
b0ab5b7b62 | ||
|
|
a3fc8dbf42 | ||
|
|
37516e6f6b | ||
|
|
9fde7c0f89 | ||
|
|
dc8103ea82 | ||
|
|
e248092014 | ||
|
|
f343635cab | ||
|
|
60daf9678f | ||
|
|
5d5bd6791b | ||
|
|
fe748507f0 | ||
|
|
cb7230e7b7 | ||
|
|
cb162b6755 | ||
|
|
7d00b50e13 | ||
|
|
12b058867e | ||
|
|
8ff09a1923 | ||
|
|
62fa0991ed | ||
|
|
52a8566e7f | ||
|
|
3bfcf252a8 | ||
|
|
f4ae58b1e5 | ||
|
|
12277d9305 | ||
|
|
c9d3f4492d | ||
|
|
f23c5cc6df | ||
|
|
24823233ff | ||
|
|
bd056973ba | ||
|
|
5420fd5aa3 | ||
|
|
44cdb4726e | ||
|
|
698cc182ae | ||
|
|
93615f6647 | ||
|
|
fba709f46d | ||
|
|
bb34aaa0c7 | ||
|
|
5661c3317a | ||
|
|
733ffc0eee | ||
|
|
6cc41d6dda | ||
|
|
d91f8a264e | ||
|
|
0ace9634ce | ||
|
|
48d51759cd | ||
|
|
bfd0b683bf | ||
|
|
61e0f60e94 | ||
|
|
7fa715e37a | ||
|
|
e066546c13 | ||
|
|
7dc66128c2 | ||
|
|
e484e4c9d7 | ||
|
|
4a75691005 | ||
|
|
8ada73b283 | ||
|
|
f316cbcf94 | ||
|
|
2a9a05785c | ||
|
|
0a6b2b7d32 | ||
|
|
c3345dd839 | ||
|
|
917b7fbf80 | ||
|
|
97715e7ccc | ||
|
|
43c0dea1bd | ||
|
|
2e4652209b | ||
|
|
aec4bf9d45 | ||
|
|
e2d811a7a0 | ||
|
|
f8643a62e6 | ||
|
|
dd5c903fd6 | ||
|
|
2e1675066d | ||
|
|
be84ce657b | ||
|
|
64053d697f | ||
|
|
a59ad06438 | ||
|
|
5af03d74ec | ||
|
|
ba2803c807 | ||
|
|
fdcbf617d8 | ||
|
|
cc7a4f7f91 | ||
|
|
2e42bda0a3 | ||
|
|
da8e6737c6 | ||
|
|
670201fcc2 | ||
|
|
168dc12e27 | ||
|
|
fd1955e15b | ||
|
|
ab35016aae | ||
|
|
f4f93f4836 | ||
|
|
6efb9b24e0 | ||
|
|
dd0a7533ab | ||
|
|
079c3fd263 | ||
|
|
8cbf929671 | ||
|
|
50130b7004 | ||
|
|
ab52c5cef2 | ||
|
|
c7fa93a5bc | ||
|
|
400b73b5a2 | ||
|
|
788b026cf5 | ||
|
|
9009645cea | ||
|
|
c4ae5d4c8d | ||
|
|
ca8dd61045 | ||
|
|
ac037bcffd | ||
|
|
d429dec2e5 | ||
|
|
d779bc3784 | ||
|
|
a4baa33e2f | ||
|
|
56aa182fb6 | ||
|
|
9818c7e78c | ||
|
|
6aa599a17c | ||
|
|
57858b2fa5 | ||
|
|
d4c1e92b1c | ||
|
|
403eda7024 | ||
|
|
1671827d24 | ||
|
|
578c3e21a5 | ||
|
|
87ef0d9ab3 | ||
|
|
cfafbfd141 | ||
|
|
9289a6c1bb | ||
|
|
4a740fbd14 | ||
|
|
7eb00c131f | ||
|
|
5ae461eb0b | ||
|
|
542126194a | ||
|
|
a61f7e38b6 | ||
|
|
3d059cb751 | ||
|
|
74e96b881c | ||
|
|
9848fa9a4d | ||
|
|
c24a7a8b58 | ||
|
|
71e38a6781 | ||
|
|
7b3cf6e747 | ||
|
|
676e4a6112 | ||
|
|
fd66a9b396 | ||
|
|
640b04e59e | ||
|
|
1625796cfe | ||
|
|
93749cd650 | ||
|
|
02b6ea6c46 | ||
|
|
6fcaf3571e | ||
|
|
10b9b13673 | ||
|
|
6cb559f65e | ||
|
|
c3b436fe96 | ||
|
|
aaac777651 | ||
|
|
103de74063 | ||
|
|
7f33a5ca0c | ||
|
|
e389dcb912 | ||
|
|
9d278d80f1 | ||
|
|
e994910ff6 | ||
|
|
e7b3705060 | ||
|
|
f17502fe81 | ||
|
|
8e7df5c1b1 | ||
|
|
8ba1b4e0cf | ||
|
|
93679f8d48 | ||
|
|
a292483344 | ||
|
|
90d720ca28 | ||
|
|
f8e933438e | ||
|
|
6dd89eb0d7 | ||
|
|
2bd20446bb | ||
|
|
659e4f6987 | ||
|
|
cd5f3c90c2 | ||
|
|
91a6911a51 | ||
|
|
0857dd0ae5 | ||
|
|
8c242fa2dd | ||
|
|
5a4f117a12 | ||
|
|
62ed1ca2fd | ||
|
|
d1298c8863 | ||
|
|
75e85b80aa | ||
|
|
73815ba1dd | ||
|
|
e1abf431cb | ||
|
|
8e0fa3bb5f | ||
|
|
8ffaf1a8e4 | ||
|
|
7788a109b0 | ||
|
|
9eea471e72 | ||
|
|
3ef53315a2 | ||
|
|
2a40e419fc | ||
|
|
d6f72d9862 | ||
|
|
3da720c789 | ||
|
|
dbf7909b85 | ||
|
|
57aa8d2f17 | ||
|
|
a318a49c72 | ||
|
|
35e73b77f4 | ||
|
|
698d1a7111 | ||
|
|
1365fca161 | ||
|
|
d17d77714f | ||
|
|
e8dd8215ba | ||
|
|
e11990e453 | ||
|
|
165ebe8ae3 | ||
|
|
e746637bee | ||
|
|
0e6370d467 | ||
|
|
512cd333e5 | ||
|
|
f599a78cad | ||
|
|
7601dab464 | ||
|
|
a8623eab4a | ||
|
|
c367ddff1b | ||
|
|
67b340fa5e | ||
|
|
c97245e626 | ||
|
|
79e2c17f93 | ||
|
|
5937737bb7 | ||
|
|
5f030edea4 | ||
|
|
88e33353a1 | ||
|
|
f3c0c62c79 | ||
|
|
866787c5d3 | ||
|
|
367ad8079a | ||
|
|
64491525b4 | ||
|
|
68b184885f | ||
|
|
06f3c716f5 | ||
|
|
22714b8c7f | ||
|
|
80c1bedffb | ||
|
|
56ad6d24ee | ||
|
|
4ad0e04c23 | ||
|
|
f9d1c554b7 | ||
|
|
ee58301a46 | ||
|
|
f2a7660390 | ||
|
|
d4c7ce2d6f | ||
|
|
4961e39fb6 | ||
|
|
0bedf608c0 | ||
|
|
1ab831f571 | ||
|
|
b90f1a48ce | ||
|
|
72425fc2e1 | ||
|
|
a5f2dfbc0c | ||
|
|
5db6a937cb | ||
|
|
9709b9b1b1 | ||
|
|
2c6b9b4c9d | ||
|
|
463fbb07f9 | ||
|
|
b6e473a515 | ||
|
|
24f7b5806c | ||
|
|
5872e0ea4a | ||
|
|
04d2d6012a | ||
|
|
f43d27541b | ||
|
|
c8d3d980ba | ||
|
|
f93bf06b99 | ||
|
|
0f7cb2fa5a | ||
|
|
01e93ba916 | ||
|
|
780954f27b | ||
|
|
19d69bdbb5 | ||
|
|
27fac7af86 | ||
|
|
6f048de973 | ||
|
|
a611a745e7 | ||
|
|
0dfaa7d9cf | ||
|
|
eab720f6ea | ||
|
|
8ad1d6b813 | ||
|
|
be684d66fd | ||
|
|
a7e8aef9d3 | ||
|
|
4b07c41df9 | ||
|
|
df54f1f1b7 | ||
|
|
9e3c2b68d7 | ||
|
|
3349bcaaed | ||
|
|
3a4fb81242 | ||
|
|
1df3ad0671 | ||
|
|
523cdd859b | ||
|
|
b037c76da6 | ||
|
|
9cac4ca317 | ||
|
|
34e5f39571 | ||
|
|
e0a279344c | ||
|
|
e2f4db3e45 | ||
|
|
cdb9eae1ee | ||
|
|
c1837af84a | ||
|
|
a87f6a28c9 | ||
|
|
98325325b1 | ||
|
|
26bf66e3f8 | ||
|
|
363cd97154 | ||
|
|
5eb19da91f | ||
|
|
c6b3281274 | ||
|
|
1e8adc2bd9 | ||
|
|
c73021cf3c | ||
|
|
1b3acf9cd8 | ||
|
|
1a26d4e409 | ||
|
|
c8ede400eb | ||
|
|
269263eecf | ||
|
|
4e21cdfc63 | ||
|
|
faef5633f8 | ||
|
|
7d1f1a3175 | ||
|
|
4e34727195 | ||
|
|
1dd6ed6ae3 | ||
|
|
cb4d6710df | ||
|
|
3b68b9a83b | ||
|
|
4279ce87ea | ||
|
|
3c1c4f89e9 | ||
|
|
4a6512f5d5 | ||
|
|
284f23c6ea | ||
|
|
11a9a5c126 | ||
|
|
4993801741 | ||
|
|
4b35899a12 | ||
|
|
1304e930eb | ||
|
|
94288d5a94 | ||
|
|
3811ab1b82 | ||
|
|
c869eb1eec | ||
|
|
f97d2a0eb9 | ||
|
|
176c8355cb | ||
|
|
2258434326 | ||
|
|
e46a3c4046 | ||
|
|
0e4cfde657 | ||
|
|
4bd9c36922 | ||
|
|
256da43fe5 | ||
|
|
6a442e0136 | ||
|
|
a818650027 | ||
|
|
9d79e64f5c | ||
|
|
c7c12f9638 | ||
|
|
ee942c5c17 | ||
|
|
d157819c49 | ||
|
|
2d91fb5441 | ||
|
|
81431a5453 | ||
|
|
6d7ec07216 | ||
|
|
b4978d1452 | ||
|
|
cb77519af8 | ||
|
|
45e9648b8c | ||
|
|
ce32957d9d | ||
|
|
ba8592ceae | ||
|
|
4327af3760 | ||
|
|
860cc63e21 | ||
|
|
452dd3ccfd | ||
|
|
e5c1621382 | ||
|
|
af3518dc1f | ||
|
|
6cfc0e80d9 | ||
|
|
1ee9c585ca | ||
|
|
efe5a5ac26 | ||
|
|
334e3ec529 | ||
|
|
84c165459f | ||
|
|
282c4121d6 | ||
|
|
6c2eee0e44 | ||
|
|
eeb6a088b8 | ||
|
|
22b63fe1f8 | ||
|
|
7ef526e2d3 | ||
|
|
ce7f94559b | ||
|
|
0471decfc8 | ||
|
|
e4c0a89889 | ||
|
|
084d6ca11d | ||
|
|
274902c3c1 | ||
|
|
f46e7c65c5 | ||
|
|
c6c6213460 | ||
|
|
29f6b02c04 | ||
|
|
1bf7c0ae5f | ||
|
|
1b87626b82 | ||
|
|
44ae084794 | ||
|
|
13a1809101 | ||
|
|
c35200fbd0 | ||
|
|
da9fb216b1 | ||
|
|
bef12f3d65 | ||
|
|
aa9e7eb7a2 | ||
|
|
f3d3e588fd | ||
|
|
4a40581deb | ||
|
|
eed2672db5 | ||
|
|
84071ac6d0 | ||
|
|
1a27eea46c | ||
|
|
d0b6451f02 | ||
|
|
2147c5a5f2 | ||
|
|
c7aa4d8b6d | ||
|
|
e94efe887c | ||
|
|
3db2de7478 | ||
|
|
345f7c3c62 | ||
|
|
13848ddbbc | ||
|
|
6f6e466c08 | ||
|
|
b0518040b5 | ||
|
|
29c872d867 | ||
|
|
acb63a1307 | ||
|
|
341bf2e480 | ||
|
|
20a191f144 | ||
|
|
81f4581f41 | ||
|
|
dfaf8ce64e | ||
|
|
f60f1932f2 | ||
|
|
ff8e4754d7 | ||
|
|
27c4d19455 | ||
|
|
7f704fdae1 | ||
|
|
dd63a6b61e | ||
|
|
1935d968c5 | ||
|
|
f83954f5b7 | ||
|
|
77b56c50e6 | ||
|
|
002a8c061f | ||
|
|
4299334e24 | ||
|
|
4d03c73222 | ||
|
|
84cfbaa0a4 | ||
|
|
7a2fd93d08 | ||
|
|
0d81992f6a | ||
|
|
5b67c9bf4a | ||
|
|
6594b38567 | ||
|
|
6c854e8ecc | ||
|
|
2e796f31d4 | ||
|
|
3d8f5d4302 | ||
|
|
2fa6b2301b | ||
|
|
4ba20132b9 | ||
|
|
a6e4d23c29 | ||
|
|
6d43576db7 | ||
|
|
b7d1bff0c7 | ||
|
|
79c5af755f | ||
|
|
c6d84e7e60 | ||
|
|
192513656a | ||
|
|
41dc728c9b | ||
|
|
f3c1b1f052 | ||
|
|
bd61c72007 | ||
|
|
0efeea1294 | ||
|
|
56ce1ec6e8 | ||
|
|
de168956e4 | ||
|
|
5b80844d81 | ||
|
|
a9902fc817 | ||
|
|
ed75688251 | ||
|
|
17add4b585 | ||
|
|
d492156453 | ||
|
|
96af3d5ec5 | ||
|
|
69ba14e34e | ||
|
|
943c924382 | ||
|
|
4b97427937 | ||
|
|
ab8e1fdcbf | ||
|
|
477979c275 | ||
|
|
c635720a09 | ||
|
|
f2a6a12f79 | ||
|
|
7445c617bc | ||
|
|
8e7340860e | ||
|
|
2ca1eb4cf8 | ||
|
|
0af8660181 | ||
|
|
330ec1b848 | ||
|
|
2f7cff84d9 | ||
|
|
8e5650fde9 | ||
|
|
539932dc56 | ||
|
|
5ab5e1270e | ||
|
|
e35de357fa | ||
|
|
0818fd7828 | ||
|
|
98cb9cc1eb | ||
|
|
bf8c97abbb | ||
|
|
ad6cf5e401 | ||
|
|
2b3900fd14 | ||
|
|
1defeca1ad | ||
|
|
ac6a9ab631 | ||
|
|
8176bb6f79 | ||
|
|
9c266d4316 | ||
|
|
d478a1b448 | ||
|
|
190a351a29 | ||
|
|
607ddd2f78 | ||
|
|
fed79a116f | ||
|
|
5db0ea0236 | ||
|
|
06fe320cc0 | ||
|
|
f7991e18de | ||
|
|
d7d0a5c15e | ||
|
|
47f4bbeec6 | ||
|
|
9ab70b340c | ||
|
|
70cdc2ca9f | ||
|
|
f63a872387 | ||
|
|
67462c2f92 | ||
|
|
4a4e786060 | ||
|
|
665f2d4c00 | ||
|
|
64586ca7ba | ||
|
|
46686b4b9c | ||
|
|
15c90e546f | ||
|
|
5aabe01b6d | ||
|
|
5d1d94848c | ||
|
|
7d10976e08 | ||
|
|
d3b55a74a5 | ||
|
|
de58ec71fd | ||
|
|
052ba80fd7 | ||
|
|
39f0ec7536 | ||
|
|
af973138df | ||
|
|
5a87506f3d | ||
|
|
90f0005cf2 | ||
|
|
d8b3748d24 | ||
|
|
1b224c961e | ||
|
|
b6ffff5bbd | ||
|
|
5ebae85a16 | ||
|
|
b3cf13775b | ||
|
|
c61809f0c4 | ||
|
|
2f2d6bc08b | ||
|
|
1bb809098c | ||
|
|
17a2ce0464 | ||
|
|
011506f00d | ||
|
|
25ab478461 | ||
|
|
fc9a35dd04 | ||
|
|
7efe30f34c | ||
|
|
ef28d5512b | ||
|
|
3827ecd6d3 | ||
|
|
fa49737538 | ||
|
|
14532867a4 | ||
|
|
73f340586d | ||
|
|
56fe00c5fb | ||
|
|
3c26177239 | ||
|
|
fe8f0d960d | ||
|
|
c72caef4fd | ||
|
|
0720a391e8 | ||
|
|
d16ac70a50 | ||
|
|
fc8e020436 | ||
|
|
6b073c6067 | ||
|
|
0b19bbff8d | ||
|
|
42927c1e32 | ||
|
|
df999978f1 | ||
|
|
43cd740a7b | ||
|
|
52f355db24 | ||
|
|
a86c5ccdc9 | ||
|
|
e532562108 | ||
|
|
4293ab2acb | ||
|
|
8d24c00df2 | ||
|
|
f4074e0bba | ||
|
|
e4426dc952 | ||
|
|
9359f6477b | ||
|
|
a103f30d51 | ||
|
|
78b60dbd1a | ||
|
|
cde75a1c00 | ||
|
|
b9d243552c | ||
|
|
85242ba896 | ||
|
|
d16dab6f62 | ||
|
|
8066b19f93 | ||
|
|
abd2a831a3 | ||
|
|
824d3ae3f7 | ||
|
|
727a14c6f9 | ||
|
|
13d20137d3 | ||
|
|
9680566595 | ||
|
|
33c9ea2cf7 | ||
|
|
1d8d2b373b | ||
|
|
611b472b12 | ||
|
|
bb73eb0db3 | ||
|
|
8a18685902 | ||
|
|
872b941b20 | ||
|
|
39261436c8 | ||
|
|
5e355383df | ||
|
|
90bfec8c04 | ||
|
|
866b6c6129 | ||
|
|
649fe7a1ec | ||
|
|
9cbbb6e508 | ||
|
|
9908769bb3 | ||
|
|
8902bb1af0 | ||
|
|
baf1bd354d | ||
|
|
539c2985aa | ||
|
|
5c356e15b5 | ||
|
|
8ff0b71b29 | ||
|
|
4e5a6c89b9 | ||
|
|
8f8f201186 | ||
|
|
0c688757b0 | ||
|
|
5778e92e70 | ||
|
|
3268ea42ff | ||
|
|
1538500903 | ||
|
|
6ca30a16ca | ||
|
|
e6dc2e0d31 | ||
|
|
9bbd1390c1 | ||
|
|
27f8db6e8b | ||
|
|
dda0c0e097 | ||
|
|
f5ea5c26a3 | ||
|
|
d01fa96177 | ||
|
|
03caa53863 | ||
|
|
4f4a2e6d92 | ||
|
|
87178ed725 | ||
|
|
94e5436f6e | ||
|
|
b965f2053a | ||
|
|
959db77b88 | ||
|
|
edee078f0a | ||
|
|
d4b766bf3f | ||
|
|
72772c9a83 | ||
|
|
4c806d7c51 | ||
|
|
c16a60c5ea | ||
|
|
96afcb7a43 | ||
|
|
e5a8d8b9ad | ||
|
|
efeee5160e | ||
|
|
06fb502047 | ||
|
|
977192f480 | ||
|
|
cf66d9d38d | ||
|
|
25eeff8fc5 | ||
|
|
d342cdad2b | ||
|
|
c899ee0d55 | ||
|
|
220408fcaa | ||
|
|
f4e99be7e1 | ||
|
|
bf9fc0ae96 | ||
|
|
9697e666b7 | ||
|
|
a8a1a74b79 | ||
|
|
216ca7cbc9 | ||
|
|
549e440f7c | ||
|
|
45c02c31f8 | ||
|
|
b6b092d124 | ||
|
|
d346d4a9b6 | ||
|
|
c84e98774a | ||
|
|
e1f4187430 | ||
|
|
3af93ada6f | ||
|
|
fa4dee8cfd | ||
|
|
3888492f0d | ||
|
|
dc16928f74 | ||
|
|
a4e440527b | ||
|
|
80ff146620 | ||
|
|
d4fe9d8166 | ||
|
|
85a0af03c1 | ||
|
|
dc43f5605b | ||
|
|
e0d2baae58 | ||
|
|
437de19ecb | ||
|
|
fab064641f | ||
|
|
cc69d01bdc | ||
|
|
461a95d7ff | ||
|
|
316e9681cc | ||
|
|
4181313cc6 | ||
|
|
aa1665acce | ||
|
|
6aabc5e7b0 | ||
|
|
343a8e0192 | ||
|
|
2707887a65 | ||
|
|
ef87d09cfa | ||
|
|
d21c67f237 | ||
|
|
de0432b317 | ||
|
|
de40fed248 | ||
|
|
76d7e0e1f8 | ||
|
|
bfa551ec08 | ||
|
|
740e564bc7 | ||
|
|
1f585d67b6 | ||
|
|
5b22e94a4b | ||
|
|
7749aef6b6 | ||
|
|
5de8fb0d08 | ||
|
|
19f7335926 | ||
|
|
9b61830a55 | ||
|
|
99f4cd867d | ||
|
|
f29fec33a2 | ||
|
|
93fe3459fd | ||
|
|
1abd3bd7f3 | ||
|
|
5509f20025 | ||
|
|
fc4fd41be4 | ||
|
|
3ffca20001 | ||
|
|
7c29305788 | ||
|
|
41fb18e573 | ||
|
|
e4c6251ef5 | ||
|
|
7aa250eaf7 | ||
|
|
ff380b686a | ||
|
|
d2452f4b68 | ||
|
|
deb9c32a38 | ||
|
|
440f45b996 | ||
|
|
7d64c4ec66 | ||
|
|
7fe0d530c1 | ||
|
|
c944767554 | ||
|
|
fde5a1c507 | ||
|
|
0fbfb41fa8 | ||
|
|
1991ed0804 | ||
|
|
e782b92a80 | ||
|
|
07635ea2be | ||
|
|
fb3de9ce9d | ||
|
|
efff91ea3d | ||
|
|
1916bd3bd0 | ||
|
|
4eb752b000 | ||
|
|
bfb29a58f3 | ||
|
|
f86e455a87 | ||
|
|
faa35fe9fc | ||
|
|
89b8b59658 | ||
|
|
de55a1adc4 | ||
|
|
d1613025ee | ||
|
|
cc4431c409 | ||
|
|
3d5986c55d | ||
|
|
9aeb6ee532 | ||
|
|
e7f6cc598d | ||
|
|
cd465dd121 | ||
|
|
174b48a14a | ||
|
|
bca18e7aba | ||
|
|
17e761d6c6 | ||
|
|
c50556dde4 | ||
|
|
dd5bdd67d7 | ||
|
|
21ac9363e9 | ||
|
|
8e3cccf4d6 | ||
|
|
945e935312 | ||
|
|
bb5cf570e5 | ||
|
|
a5ed288db2 | ||
|
|
7002d6d306 | ||
|
|
1b8d8f3a04 | ||
|
|
284440336d | ||
|
|
140ae7a513 | ||
|
|
21328d9e37 | ||
|
|
5177fe1db7 | ||
|
|
7de50b5e2e | ||
|
|
4652a84b43 | ||
|
|
9e0755bc86 | ||
|
|
da0f7d7907 | ||
|
|
88d72bf31d | ||
|
|
aac2f7dd73 | ||
|
|
1f44ad1723 | ||
|
|
4ab1857a11 | ||
|
|
d23c714ec7 | ||
|
|
ac524532e7 | ||
|
|
59a1fde2a1 | ||
|
|
31276de5c3 | ||
|
|
c581aef11d | ||
|
|
7f6a955a71 | ||
|
|
125d97cc41 | ||
|
|
de7d9ba471 | ||
|
|
ad54b44235 | ||
|
|
42532ec0f5 | ||
|
|
b84fa619da | ||
|
|
8a1409184f | ||
|
|
8a3c16a5bc | ||
|
|
6343c65ce2 | ||
|
|
20b4736a1f | ||
|
|
d5967f7834 | ||
|
|
d5f7650ac1 | ||
|
|
6330caffde | ||
|
|
8f580c256c | ||
|
|
4671b8db5c | ||
|
|
7c8f044380 | ||
|
|
8efd506471 | ||
|
|
e83267751e | ||
|
|
a3b110aee5 | ||
|
|
84f0b0a84c | ||
|
|
c9c5adc650 | ||
|
|
52e7226655 | ||
|
|
b89c8decd4 | ||
|
|
d783975597 | ||
|
|
5ec291df5c | ||
|
|
0a45355055 | ||
|
|
e696624da0 | ||
|
|
99ad40f3e0 | ||
|
|
b9c8016aca | ||
|
|
8ad1f2d4f5 | ||
|
|
dc30581be0 | ||
|
|
2e56b606fa | ||
|
|
d84c72afe5 | ||
|
|
2d69896f64 | ||
|
|
b3dd2db815 | ||
|
|
290dd3993b | ||
|
|
4f6a9917c6 | ||
|
|
3d48183753 | ||
|
|
33c31eb798 | ||
|
|
73ae7ad82f | ||
|
|
ee6470708b | ||
|
|
61f25926b5 | ||
|
|
1a5d3bb69c | ||
|
|
7d4fe55d63 | ||
|
|
089e03afe8 | ||
|
|
8e019f01ab | ||
|
|
77bdaf3c78 | ||
|
|
0b6828c895 | ||
|
|
d4704c656f | ||
|
|
c01192c784 | ||
|
|
8adb611edf | ||
|
|
e5af5b57ad | ||
|
|
f05d3e6af3 | ||
|
|
5963d038ef | ||
|
|
bfd28a04ba | ||
|
|
359ec257c0 | ||
|
|
88767e402c | ||
|
|
88c7a6d053 | ||
|
|
e698cbf092 | ||
|
|
f2ce646d8d | ||
|
|
cbf9b345ff | ||
|
|
1725894fe9 | ||
|
|
fd4f85eb19 | ||
|
|
f1c4864016 | ||
|
|
e6bd265729 | ||
|
|
c22e8112e7 | ||
|
|
44252984c2 | ||
|
|
4b4f92780e | ||
|
|
f694620087 | ||
|
|
dc1d1f132e | ||
|
|
9b4048ec6e | ||
|
|
727342134c | ||
|
|
c744a97e3c | ||
|
|
40cafb95ed | ||
|
|
91d75d7704 | ||
|
|
dc8cff364f | ||
|
|
572dc40e6b | ||
|
|
f92ffddb82 | ||
|
|
641e0c1afc | ||
|
|
bf7faa80c1 | ||
|
|
a2ae3771eb | ||
|
|
673ffc50da | ||
|
|
6dc9973754 | ||
|
|
cf6a910630 | ||
|
|
520baa6ec8 | ||
|
|
c1cc4f96df | ||
|
|
bbf925a27e | ||
|
|
381fd5dbe4 | ||
|
|
ead8b7437e | ||
|
|
9f2d18b7ba | ||
|
|
acd9df6745 | ||
|
|
f96c051932 | ||
|
|
67b2e40fae | ||
|
|
081a2acd61 | ||
|
|
de79acc790 | ||
|
|
a125bc7242 | ||
|
|
ebed4cd728 | ||
|
|
21d4838322 | ||
|
|
926a373591 | ||
|
|
0cbb481fa4 | ||
|
|
a954f23642 | ||
|
|
41a104cc10 | ||
|
|
f0b4971c7b | ||
|
|
8e669a32a3 | ||
|
|
0e16e7935e | ||
|
|
7ea84d9a4e | ||
|
|
7313c89dec | ||
|
|
35a66c03c2 | ||
|
|
bbb3168bae | ||
|
|
1ea9d3faf8 | ||
|
|
4479be4fd0 | ||
|
|
e7aaf4dd2e | ||
|
|
91a6bf671d | ||
|
|
49b5889d9e | ||
|
|
ede61ae130 | ||
|
|
7a79111767 | ||
|
|
6432521b9d | ||
|
|
65f578fe61 | ||
|
|
3a8eb4a4f0 | ||
|
|
eb180656bb | ||
|
|
1afcbba218 | ||
|
|
8a0902a83b | ||
|
|
dfb312fee6 | ||
|
|
11bb594fa2 | ||
|
|
8e3ae2c78f | ||
|
|
8080d1d961 | ||
|
|
4b4135e35a | ||
|
|
d1148c4cab | ||
|
|
8ee62b4789 | ||
|
|
5e7a142ff1 | ||
|
|
2c816db45e | ||
|
|
b920507f34 | ||
|
|
d8601ef01f | ||
|
|
afbc57cc0c | ||
|
|
9f12c009d6 | ||
|
|
84ac68a58b | ||
|
|
27d1df4699 | ||
|
|
0d7a7dc7c9 | ||
|
|
b8bff0e7f5 | ||
|
|
60bf1ef7ea | ||
|
|
dc37b692cf | ||
|
|
95976d8b58 | ||
|
|
ecb20cc29b | ||
|
|
b6183e86eb | ||
|
|
229af0380c | ||
|
|
b968a662d3 | ||
|
|
159e869fe6 | ||
|
|
76814588b8 | ||
|
|
1934c7faa2 | ||
|
|
9e9e160c43 | ||
|
|
546b4edbf1 | ||
|
|
63d8a88e2f | ||
|
|
75d2d64e7c | ||
|
|
a5113998e2 | ||
|
|
4d2e8cd71d | ||
|
|
30b355fd6f | ||
|
|
c257b91552 | ||
|
|
12df7112da | ||
|
|
cd5ca3f65b | ||
|
|
0bd63cf00f | ||
|
|
7ceb3369eb | ||
|
|
ae21726287 | ||
|
|
a4da1b6eb0 | ||
|
|
85bfd2eba3 | ||
|
|
2d543590dc | ||
|
|
18b6f17e86 | ||
|
|
f37179d9f2 | ||
|
|
3e0b7d71d4 | ||
|
|
58d10943ed | ||
|
|
dc920a04f6 | ||
|
|
d031381e70 | ||
|
|
ed1b0b90f7 | ||
|
|
38dd3c5c60 | ||
|
|
d3189acaa6 | ||
|
|
350c98ab4d | ||
|
|
4f3c754771 | ||
|
|
dc994f001d | ||
|
|
9b6ccbcc95 | ||
|
|
9d3cf9c73c | ||
|
|
28572d4392 | ||
|
|
0433db0370 | ||
|
|
a6b326da48 | ||
|
|
e457ce66ea | ||
|
|
c118dd8afe | ||
|
|
dba3a3d942 | ||
|
|
6c606b5506 | ||
|
|
55dbeefeb2 | ||
|
|
4d9589af7c | ||
|
|
ee625cb8a8 | ||
|
|
f20940a37b | ||
|
|
32e0a66610 | ||
|
|
d9598b35c2 | ||
|
|
acba357df6 | ||
|
|
7ce335d9da | ||
|
|
3caf9ca914 | ||
|
|
fd569201ef | ||
|
|
f094aa946a | ||
|
|
a17c192a9e | ||
|
|
1916a9b99c | ||
|
|
9796b308dc | ||
|
|
bdf0a1941c | ||
|
|
d0e3024bec | ||
|
|
d2ad149e56 | ||
|
|
ad602a4722 | ||
|
|
348840a2aa | ||
|
|
3a719633eb | ||
|
|
bd69948d37 | ||
|
|
54aa211f56 | ||
|
|
f118891970 | ||
|
|
c4055fde97 | ||
|
|
dbae3fc9a5 | ||
|
|
7c73ed7ed5 | ||
|
|
c834960bfb | ||
|
|
600abc55b5 | ||
|
|
f3ec7d54bb | ||
|
|
090760e526 | ||
|
|
cccde7dc89 | ||
|
|
849e48f519 | ||
|
|
1c3935eb40 | ||
|
|
466bed3163 | ||
|
|
641a9c72e9 | ||
|
|
5138216ba1 | ||
|
|
de1f5686a8 | ||
|
|
c983678fcd | ||
|
|
2b0415d552 | ||
|
|
066e4421e8 | ||
|
|
f02a241249 | ||
|
|
a5fe1e4259 | ||
|
|
9b80563443 | ||
|
|
91b5da06e3 | ||
|
|
7320f96ae7 | ||
|
|
fdf2b9cd7b | ||
|
|
bfc70a1b60 | ||
|
|
aff7a93106 | ||
|
|
3b027c4593 | ||
|
|
42d3bdd373 | ||
|
|
57789092c1 | ||
|
|
6bc5268cbd | ||
|
|
887ab705d1 | ||
|
|
ff6ddaed2e | ||
|
|
e6fe36f45c | ||
|
|
3a26f6b8bf | ||
|
|
06b6f85d55 | ||
|
|
d6f1ea50a6 | ||
|
|
9554869886 | ||
|
|
364059551c | ||
|
|
06340b1ad7 | ||
|
|
d23511860d | ||
|
|
a8dd4660b2 | ||
|
|
eb3a0eb3c7 | ||
|
|
cd0148e0bc | ||
|
|
8584ee609f | ||
|
|
373847e2b7 | ||
|
|
84f7d8dfc2 | ||
|
|
e057a7d0dd | ||
|
|
7bab15bf99 | ||
|
|
dac40630fd | ||
|
|
33bfa1b81c | ||
|
|
8fc27dc292 | ||
|
|
f8e8f18be5 | ||
|
|
8b38c567d2 | ||
|
|
cd53e42d79 | ||
|
|
bea6cf2038 | ||
|
|
eca80f1425 | ||
|
|
1c0962e53c | ||
|
|
4b21549ff4 | ||
|
|
30d7b0129b | ||
|
|
ce6877d6e4 | ||
|
|
0ab5177637 | ||
|
|
276cbfa505 | ||
|
|
610c85a354 | ||
|
|
012084b37b | ||
|
|
55af6681af | ||
|
|
2a7a42ff8f | ||
|
|
7af5737ec5 | ||
|
|
0ad1529f3f | ||
|
|
0df8173536 | ||
|
|
b517811e2f | ||
|
|
83d3a9c6dd | ||
|
|
d0402261e6 | ||
|
|
6f6e09d200 | ||
|
|
24e2fd4184 | ||
|
|
1aada996dc | ||
|
|
f5d3d6bcea | ||
|
|
a8a99f647f | ||
|
|
ff68b26c44 | ||
|
|
a94b4f62fd | ||
|
|
bcc959d938 | ||
|
|
cf25d8a378 | ||
|
|
c750bdafd5 | ||
|
|
693d46f8ea | ||
|
|
3496ebd1d7 | ||
|
|
be763cf7fe | ||
|
|
c3b4bee210 | ||
|
|
6df0227ab1 | ||
|
|
2a3a7fa8a0 | ||
|
|
50a6496399 | ||
|
|
c99dee86dd | ||
|
|
0c5bb9626b | ||
|
|
a9971917f5 | ||
|
|
4c62611da3 | ||
|
|
47f36f08fb | ||
|
|
f906bab1a5 | ||
|
|
fffc03c4e4 | ||
|
|
0f6934a131 | ||
|
|
0a94184d6b | ||
|
|
7be3578497 | ||
|
|
eeaccb8ac0 | ||
|
|
8ef9a932aa | ||
|
|
31e22e4cfb | ||
|
|
4fc25fb798 | ||
|
|
941d9a46a2 | ||
|
|
ecfe68d70f | ||
|
|
c0c2b5e3a9 | ||
|
|
f102d8a4b4 | ||
|
|
471e13efbc | ||
|
|
6d34432988 | ||
|
|
d3f0d15732 | ||
|
|
b827b9e33e | ||
|
|
29e5ecc282 | ||
|
|
c9bf2dda16 | ||
|
|
3ceb378b9b | ||
|
|
1cf1c90511 | ||
|
|
491b9f83f2 | ||
|
|
d989825216 | ||
|
|
3976420b88 | ||
|
|
2f1ce5fe43 | ||
|
|
42145a5b8a | ||
|
|
04f4536cb2 | ||
|
|
4e66017205 | ||
|
|
2c1f2edcf2 | ||
|
|
299d517449 | ||
|
|
561e73dbd7 | ||
|
|
9e6ffaad7d | ||
|
|
9cded1e92c | ||
|
|
4c1ab6ff25 | ||
|
|
16f31cab6a | ||
|
|
02c88e6826 | ||
|
|
9ecd43238f | ||
|
|
5ffe71346c | ||
|
|
d25804f4a2 | ||
|
|
edb75e69cb | ||
|
|
f3e895f17c | ||
|
|
b952d73e83 | ||
|
|
07facc0636 | ||
|
|
da1a69be27 | ||
|
|
7e31658932 | ||
|
|
5ebc59dd1f | ||
|
|
b10f5ab110 | ||
|
|
b4286bb42b | ||
|
|
4d7ce3792f | ||
|
|
76767da300 | ||
|
|
dc8701a929 | ||
|
|
139d35c6f9 | ||
|
|
cb24457b4a | ||
|
|
9f3efb7f05 | ||
|
|
e6001e0f22 | ||
|
|
c6535bf035 | ||
|
|
7118a515e0 | ||
|
|
952451c9b8 | ||
|
|
610327a04e | ||
|
|
2121e32409 | ||
|
|
7ec21edc2f | ||
|
|
003162f710 | ||
|
|
040ac93042 | ||
|
|
b489ba3d0d | ||
|
|
c5e8b547af | ||
|
|
e67de90ad0 | ||
|
|
c3c84c88a1 | ||
|
|
0dc9c4cee1 | ||
|
|
544c137cb0 | ||
|
|
b312a61a81 | ||
|
|
4917556a99 | ||
|
|
15ed4a0d09 | ||
|
|
aa6b0f07b7 | ||
|
|
d9d20d9d30 | ||
|
|
689bfbbdb3 | ||
|
|
e27a10bde4 | ||
|
|
253a199f27 | ||
|
|
61e5702520 | ||
|
|
b12c640807 | ||
|
|
9be23ecc34 | ||
|
|
8960f471a0 | ||
|
|
955cb6411c | ||
|
|
fc4ca4f8e3 | ||
|
|
eec068914e | ||
|
|
a1f02d0cd8 | ||
|
|
39b8285ba5 | ||
|
|
7733fef3bd | ||
|
|
6acddfdb98 | ||
|
|
ec3d5c0b32 | ||
|
|
99492c2ec2 | ||
|
|
addf9f9af4 | ||
|
|
846b505d27 | ||
|
|
c4cfcfab8e | ||
|
|
5e083426c5 | ||
|
|
8d43b4a98d | ||
|
|
aeaea073c6 | ||
|
|
6b0dd19442 | ||
|
|
9336ffe216 | ||
|
|
eb157f15f3 | ||
|
|
d6e2a3f425 | ||
|
|
b47ca13ed3 | ||
|
|
67546c4d6e | ||
|
|
f72deb0a5c | ||
|
|
616ccbb878 | ||
|
|
5899af0038 | ||
|
|
ed303310bb | ||
|
|
33ff4f3b5c | ||
|
|
20bad38d42 | ||
|
|
92a07398cd | ||
|
|
ce8f782577 | ||
|
|
e961d0b4a3 | ||
|
|
2253ff656a | ||
|
|
18631399ad | ||
|
|
ad4afcdcd5 | ||
|
|
2cf5bcc5db | ||
|
|
1180ad7662 | ||
|
|
5463cd1ae3 | ||
|
|
647ec770ce | ||
|
|
e47bec2e65 | ||
|
|
6566936be9 | ||
|
|
674941abdf | ||
|
|
b3f0ca39ed | ||
|
|
5ccb512883 | ||
|
|
da286d5ae8 | ||
|
|
73e45511dc | ||
|
|
a282a51673 | ||
|
|
b7b13e20d1 | ||
|
|
ad90c6b6ce | ||
|
|
402fa41bc0 | ||
|
|
0b9ebafc0f | ||
|
|
140e24ef15 | ||
|
|
0c998d60cb | ||
|
|
ffcd2ea10c | ||
|
|
cb460de94d | ||
|
|
f6624bf776 | ||
|
|
b4b6c4d86f | ||
|
|
759689ff31 | ||
|
|
1dfc36f311 | ||
|
|
1c03ff1d37 | ||
|
|
19dd2f92bd | ||
|
|
acfaa016a0 | ||
|
|
732761433a | ||
|
|
9012a7f5e1 | ||
|
|
e957b471b2 | ||
|
|
e5a5faa417 | ||
|
|
313dbe05e0 | ||
|
|
adf7124e2c | ||
|
|
c4ab2bbeed | ||
|
|
42ef459e20 | ||
|
|
cad1a9e0f1 | ||
|
|
f1d514470d | ||
|
|
9a7a54f22f | ||
|
|
137d1c61bd | ||
|
|
adc071ed7a | ||
|
|
e06f470044 | ||
|
|
ab69fe56c9 | ||
|
|
60bad22a91 | ||
|
|
7092429f7c | ||
|
|
fa800bb809 | ||
|
|
e15f1103a0 | ||
|
|
a4263b5a8c | ||
|
|
3d85f820f4 | ||
|
|
245b7baa61 | ||
|
|
0eeaaa150a | ||
|
|
692d87f446 | ||
|
|
6572efe2a7 | ||
|
|
8aac2bd029 | ||
|
|
add11db369 | ||
|
|
e47eab1d40 | ||
|
|
2f86dfdf2b | ||
|
|
fa71ae3174 | ||
|
|
dfcd1508c9 | ||
|
|
0ca4631279 | ||
|
|
7e5fc4444a | ||
|
|
a6221ca322 | ||
|
|
d8e42c4379 | ||
|
|
3bf109ae0b | ||
|
|
dd37fa49a0 | ||
|
|
3227ec72a2 | ||
|
|
ee324c3d89 | ||
|
|
863971f944 | ||
|
|
fd70f7ad43 | ||
|
|
6e034c9b7f | ||
|
|
52e375a985 | ||
|
|
635c1eacd5 | ||
|
|
f49ba18627 | ||
|
|
6dbce96781 | ||
|
|
9ec42f0f8f | ||
|
|
10a5e7313f | ||
|
|
ec9cb21fae | ||
|
|
fdd02ad6a6 | ||
|
|
76e9fcc94a | ||
|
|
e412927415 | ||
|
|
dda154c7c6 | ||
|
|
9215535bee | ||
|
|
27726fd2d1 | ||
|
|
77befb7f8e | ||
|
|
86c6248b48 | ||
|
|
f2af8ff25d | ||
|
|
7d8894415c | ||
|
|
f8380d2d4c | ||
|
|
5cc25d0846 | ||
|
|
1502c4530e | ||
|
|
c1df4d1c0b | ||
|
|
1f9e41e9cb | ||
|
|
e402e690b0 | ||
|
|
6a15bb15ca | ||
|
|
3255fc91fa | ||
|
|
7f2610c4fc | ||
|
|
79bd3eb6ae | ||
|
|
b11dd6950c | ||
|
|
98bd6fc240 | ||
|
|
8be053fd35 | ||
|
|
99fee22a9f | ||
|
|
084d002353 | ||
|
|
dcbc9847a3 | ||
|
|
db3c158215 | ||
|
|
25e2bd307a | ||
|
|
b9f78f5d33 | ||
|
|
b4ec9d70da | ||
|
|
738999a8b7 | ||
|
|
dd91d793d9 | ||
|
|
1f0bf1b32d | ||
|
|
8e51e8eb77 | ||
|
|
6210605bc7 | ||
|
|
0245b040b0 | ||
|
|
34c1cc5693 | ||
|
|
8795719c18 | ||
|
|
6bbbf43341 | ||
|
|
f0ef45f0ca | ||
|
|
ee6039bfa5 | ||
|
|
ef58ce6277 | ||
|
|
15de5e98c4 | ||
|
|
38848ca2db | ||
|
|
77c627e822 | ||
|
|
c640132699 | ||
|
|
60b09d9bb0 | ||
|
|
57dd38aef2 | ||
|
|
460a6cb6fe | ||
|
|
fdb676da4e | ||
|
|
26aaddaa33 | ||
|
|
e51151e558 | ||
|
|
f576baf214 | ||
|
|
5c1ac05170 | ||
|
|
1bae4973bc | ||
|
|
3d9f86c584 | ||
|
|
3514e537ca | ||
|
|
3d160ce85f | ||
|
|
b78090ec76 | ||
|
|
759007ffc1 | ||
|
|
37a55c3a77 | ||
|
|
69ae9d72c8 | ||
|
|
604232acd9 | ||
|
|
82205d71cc | ||
|
|
402eab10f8 | ||
|
|
b6bf4d73ad | ||
|
|
5425b5c423 | ||
|
|
29cd8504ca | ||
|
|
3544746934 | ||
|
|
d8f814f1c4 | ||
|
|
a43175125a | ||
|
|
1d03bc560a | ||
|
|
3832acf6e3 | ||
|
|
7894b50321 | ||
|
|
ffded619e6 | ||
|
|
bcb7bb5cce | ||
|
|
87dcd82f69 | ||
|
|
e671cc6056 | ||
|
|
5da89b88a6 | ||
|
|
5d60c1f20b | ||
|
|
7fd00165c9 | ||
|
|
34d4420e8c | ||
|
|
20da194fab | ||
|
|
8d2d4c850f | ||
|
|
b7bed027d7 | ||
|
|
fcd6b7b0ea | ||
|
|
ceca32ceb3 | ||
|
|
e3bb9fc1d7 | ||
|
|
77a8ddb95c | ||
|
|
c733a4dbf8 | ||
|
|
d898a43dff | ||
|
|
6216d53b1a | ||
|
|
86c30769d9 | ||
|
|
956a6dbd64 | ||
|
|
68fe19818e | ||
|
|
de208ead4e | ||
|
|
69d62560b4 | ||
|
|
87d2fc1491 | ||
|
|
2bc9af09e1 | ||
|
|
26f4758523 | ||
|
|
6123349b79 | ||
|
|
d1ac54fe92 | ||
|
|
9468adf737 | ||
|
|
e85db40b0f | ||
|
|
b3d55cc16d | ||
|
|
56b62a5e49 | ||
|
|
3ee1fc544f | ||
|
|
5401744dc0 | ||
|
|
fe10a10ac2 | ||
|
|
ba2e5a97a9 | ||
|
|
4515d1220c | ||
|
|
486959bce8 | ||
|
|
e1a410bf3d | ||
|
|
3767cc7c0b | ||
|
|
96b0ce9ef2 | ||
|
|
038ed0551e | ||
|
|
cfaf4a8a65 | ||
|
|
22dd8a8847 | ||
|
|
b2ae8e7a4a | ||
|
|
3e2bac8129 | ||
|
|
50b9d0e86d | ||
|
|
a030d9935e | ||
|
|
c425dec4d5 | ||
|
|
67d53601d5 | ||
|
|
622cca0acf | ||
|
|
48999c03a5 | ||
|
|
377cc7bdcd | ||
|
|
a5d0976c2d | ||
|
|
ae05010255 | ||
|
|
66cacbd0e0 | ||
|
|
b1616be4b8 | ||
|
|
a0a9a72d8f | ||
|
|
0cfc7f732c | ||
|
|
f7de6f790c | ||
|
|
d1f3b5ed80 | ||
|
|
7925dcc5a2 | ||
|
|
6ade36bf09 | ||
|
|
c52945aab5 | ||
|
|
2b0a4055f7 | ||
|
|
7cb16a3fc5 | ||
|
|
0b80c1988b | ||
|
|
eab9bc1503 | ||
|
|
5bfedff8d1 | ||
|
|
c8638c0ffb | ||
|
|
8a95b91e2a | ||
|
|
c226be612f | ||
|
|
c8699d9770 | ||
|
|
a0799e14cc | ||
|
|
dea6048849 | ||
|
|
813e252539 | ||
|
|
b41e29a83b | ||
|
|
d35c7ad127 | ||
|
|
ea63415d0e | ||
|
|
52ea3b741c | ||
|
|
2731ca8c92 | ||
|
|
af1ade9433 | ||
|
|
fc248951cc | ||
|
|
84547ee1c1 | ||
|
|
a42848c62f | ||
|
|
c7b5d69431 | ||
|
|
81374b70b5 | ||
|
|
47a530fd5c | ||
|
|
58451d7c0c | ||
|
|
5c8f8c76fe | ||
|
|
ae1d1bdb5b | ||
|
|
33cc1154a2 | ||
|
|
4bc0b75c30 | ||
|
|
eb8ec1efb1 | ||
|
|
616f8efc47 | ||
|
|
29e4369420 | ||
|
|
bd7f7bc8d7 | ||
|
|
e689ca92c4 | ||
|
|
4ef3005072 | ||
|
|
174c837767 | ||
|
|
486bb911a9 | ||
|
|
754221d697 | ||
|
|
3c36c90729 | ||
|
|
3d1d15a25b | ||
|
|
000d99f26c | ||
|
|
524e2abc8c | ||
|
|
00bab98e09 | ||
|
|
6d98349be1 | ||
|
|
d24d153c08 | ||
|
|
b01561712c | ||
|
|
324edcb391 | ||
|
|
6e62e4e296 | ||
|
|
f81ecbf4a0 | ||
|
|
4370456323 | ||
|
|
a424ed7c00 | ||
|
|
a2065f59a1 | ||
|
|
c1bd7f5c67 | ||
|
|
5810a1a98e | ||
|
|
a4c011e3c0 | ||
|
|
337fd15dc0 | ||
|
|
9bc94f4536 | ||
|
|
3f4cf35384 | ||
|
|
4dd7f2cc09 | ||
|
|
1b29cc34c4 | ||
|
|
53c3c1f5ab | ||
|
|
6225abd751 | ||
|
|
c6fcd9a1eb | ||
|
|
30fbb6ea53 | ||
|
|
0e49258546 | ||
|
|
264b8dfb28 | ||
|
|
6a15b8f695 | ||
|
|
5167d256cc | ||
|
|
16bd826491 | ||
|
|
55af8fa5d9 | ||
|
|
1ec8ff20af | ||
|
|
99a65d3297 | ||
|
|
94907b51aa | ||
|
|
0085265d13 | ||
|
|
8e0893bd42 | ||
|
|
704dc9bdcb | ||
|
|
7a673a2448 | ||
|
|
33e2a4b21c | ||
|
|
3e6b804896 | ||
|
|
e98165a657 | ||
|
|
2a7727d12b | ||
|
|
c20e8f4062 | ||
|
|
4ca9db7d49 | ||
|
|
4add48cffb | ||
|
|
adbfb009f8 | ||
|
|
43ceca8711 | ||
|
|
3ef28a4f03 | ||
|
|
adcd580d5b | ||
|
|
5715c9183f | ||
|
|
ceb62ac7f9 | ||
|
|
bda0756620 | ||
|
|
6b47fb38c6 | ||
|
|
38bf8a06a7 | ||
|
|
196651d9aa | ||
|
|
6b46212a4e | ||
|
|
2a6fff2008 | ||
|
|
c5944efe50 | ||
|
|
f384370b18 | ||
|
|
0c09275a9f | ||
|
|
278671cdb9 | ||
|
|
964d2d4fa4 | ||
|
|
f371221dba | ||
|
|
27b0579ec6 | ||
|
|
283092cfbc | ||
|
|
614953a222 | ||
|
|
4fffb3cf19 | ||
|
|
850aa2b23a | ||
|
|
d715e5fd1d | ||
|
|
7826a26c7b | ||
|
|
dc0a82cf9a | ||
|
|
2e60c81bd6 | ||
|
|
763b9ba0ec | ||
|
|
bae8bb0c00 | ||
|
|
bcf483fb7e | ||
|
|
a5b7d819a7 | ||
|
|
fe07a0b1d8 | ||
|
|
d9231e5d4a | ||
|
|
b7aa1a1c84 | ||
|
|
32e144115d | ||
|
|
177cc96f49 | ||
|
|
51d98ef9ab | ||
|
|
2327c48cc4 | ||
|
|
742d44a532 | ||
|
|
52b96db2b9 | ||
|
|
0b9de78c38 | ||
|
|
2c28cb8c57 | ||
|
|
483fe82e9d | ||
|
|
29492d6138 | ||
|
|
19310e32c4 | ||
|
|
c04a395499 | ||
|
|
1c424833a9 | ||
|
|
a46ff5590d | ||
|
|
ab059b63fd | ||
|
|
3d8fc9952d | ||
|
|
8ce8fbd977 | ||
|
|
7f08218b28 | ||
|
|
2c139ad931 | ||
|
|
1119779c8b | ||
|
|
5351ac560f | ||
|
|
49f0ab0f15 | ||
|
|
a5c57e777e | ||
|
|
3c59042388 | ||
|
|
919e211bc4 | ||
|
|
daa0737ce4 | ||
|
|
36805cb120 | ||
|
|
7de69e9874 | ||
|
|
b93575bbcc | ||
|
|
116e0f0105 | ||
|
|
e4a650aaff | ||
|
|
b5312b9ba0 | ||
|
|
6afee7bb9b | ||
|
|
5729e6e13a | ||
|
|
2f53b105bb | ||
|
|
b698056f78 | ||
|
|
95c906f03d | ||
|
|
be19fa9dde | ||
|
|
81e9ba5608 | ||
|
|
f2d7b9f6a9 | ||
|
|
1ea034310a | ||
|
|
bdcab447f9 | ||
|
|
10bf6744aa | ||
|
|
895d98e266 | ||
|
|
903e343895 | ||
|
|
f8b7c59616 | ||
|
|
fcd267a3f9 | ||
|
|
f8bb66d2a0 | ||
|
|
90782d3c27 | ||
|
|
f2336d2efc | ||
|
|
c2d093fa3c | ||
|
|
1a97cc8a91 | ||
|
|
c34a548fa0 | ||
|
|
d1b89392a2 | ||
|
|
ed734754e5 | ||
|
|
520c3c9218 | ||
|
|
9230cf1726 | ||
|
|
6e616972a5 | ||
|
|
f98888824d | ||
|
|
6c8b23e708 | ||
|
|
2c2bb3765f | ||
|
|
0d165740ea | ||
|
|
88f0f2b623 | ||
|
|
0afa143375 | ||
|
|
8319aca351 | ||
|
|
a66734883a | ||
|
|
d2ab0dd839 | ||
|
|
2574407afb | ||
|
|
83a54fd6d2 | ||
|
|
e062780968 | ||
|
|
3acd0be1f7 | ||
|
|
69c0734975 | ||
|
|
c1678d7be7 | ||
|
|
117f9a9794 | ||
|
|
b49cc407c6 | ||
|
|
954386f1cc | ||
|
|
d7ff6bd04d | ||
|
|
6025516f9f | ||
|
|
d8b9cdf7a2 | ||
|
|
09dbff39f2 | ||
|
|
2fe15a6168 | ||
|
|
07dc26f8fa | ||
|
|
a08d65b1ff | ||
|
|
199621db08 | ||
|
|
0e1e8c7faa | ||
|
|
42a98e1676 | ||
|
|
23e26e0333 | ||
|
|
fadb04f3f3 | ||
|
|
4968ccf46d | ||
|
|
1dcac304d3 | ||
|
|
1651efe4fc | ||
|
|
8f24aed43e | ||
|
|
a381374e31 | ||
|
|
9411c37d23 | ||
|
|
6af6f21868 | ||
|
|
9a0022cfcb | ||
|
|
266310d9c2 | ||
|
|
fbf1adef05 | ||
|
|
311ddfb05a | ||
|
|
2fd8a8aa66 | ||
|
|
0c3e9dca28 | ||
|
|
c331d15429 | ||
|
|
4414e96710 | ||
|
|
7161783a4f | ||
|
|
cbac48da86 | ||
|
|
d9142d5427 | ||
|
|
e5e988b28f | ||
|
|
e94e051c87 | ||
|
|
5fc91effb5 | ||
|
|
6c9dacbe89 | ||
|
|
6a7eb832cc | ||
|
|
60cf8486bb | ||
|
|
90b8163d54 | ||
|
|
a1e4389f63 | ||
|
|
440b11708b | ||
|
|
f90dce5c54 | ||
|
|
606c7709cf | ||
|
|
1d1e6d1820 | ||
|
|
3eb4dd74a2 | ||
|
|
853914480c | ||
|
|
fe04410681 | ||
|
|
1f686c4e6b | ||
|
|
2a2ac1227b | ||
|
|
b5340c8f74 | ||
|
|
196c4dcdd9 | ||
|
|
c5a86f0ef7 | ||
|
|
88f2a2940b | ||
|
|
26b019a4d4 | ||
|
|
5f7b3ae313 | ||
|
|
61c127ed2e | ||
|
|
333981e2a7 | ||
|
|
423fbc9ac7 | ||
|
|
1c1719e561 | ||
|
|
56c30e1651 | ||
|
|
1ea4130035 | ||
|
|
57713d63fa | ||
|
|
d18a537509 | ||
|
|
8e0a6df03b | ||
|
|
95a52a9f62 | ||
|
|
ae2993625c | ||
|
|
0982141442 | ||
|
|
85fab2abc4 | ||
|
|
de3b37799c | ||
|
|
70851f3b2d | ||
|
|
462bbf2e40 | ||
|
|
778b9ef683 | ||
|
|
96e7eb1bed | ||
|
|
05671f3553 | ||
|
|
6e4832f999 | ||
|
|
54e3332673 | ||
|
|
6c559d7556 | ||
|
|
9165a85484 | ||
|
|
98ada2588a | ||
|
|
43f686c22d | ||
|
|
4a2673d757 | ||
|
|
f27e331462 | ||
|
|
dd64aef910 | ||
|
|
95971f39f1 | ||
|
|
83beb3c0e6 | ||
|
|
76335e5cf2 | ||
|
|
4494320238 | ||
|
|
5acd97c860 | ||
|
|
b0f551c307 | ||
|
|
b6b3d845a3 | ||
|
|
505d84f336 | ||
|
|
1d5144b912 | ||
|
|
deff91e460 | ||
|
|
afd8dc0915 | ||
|
|
fbee74e1fe | ||
|
|
ccd82591aa | ||
|
|
64931e476d | ||
|
|
604a715a49 | ||
|
|
24757ef20c | ||
|
|
e36cc9e777 | ||
|
|
2e999889bd | ||
|
|
f4db4c3a73 | ||
|
|
d923fe72c0 | ||
|
|
f05cdd5e34 | ||
|
|
f9954619d4 | ||
|
|
0aa8c3c40d | ||
|
|
a30eeaab6a | ||
|
|
3858e79579 | ||
|
|
b4a5fa33b0 | ||
|
|
2a6e9c5e8a | ||
|
|
488c2aed51 | ||
|
|
5483f979dc | ||
|
|
ea11f3826a | ||
|
|
ceae81a332 | ||
|
|
50ea56e908 | ||
|
|
bfb2f79cff | ||
|
|
8268e8ee4c | ||
|
|
cb31e22f59 | ||
|
|
6752f4fd73 | ||
|
|
22c31e4f55 | ||
|
|
c2ff64c1e0 | ||
|
|
4db792591a | ||
|
|
1290a8e32b | ||
|
|
8ae38991b0 | ||
|
|
6d40549c0c | ||
|
|
93d5c9a3c7 | ||
|
|
9af6c0b37a | ||
|
|
7e3528c692 | ||
|
|
41f2fc51be | ||
|
|
11228dc265 | ||
|
|
ef50967793 | ||
|
|
5f6c08b7e0 | ||
|
|
6cb23ec5be | ||
|
|
1bae70bcf8 | ||
|
|
9820591ba4 | ||
|
|
77071b3c69 | ||
|
|
335e839b31 | ||
|
|
6fe947b8b9 | ||
|
|
22b29e77a7 | ||
|
|
4858cfce6b | ||
|
|
8da3e91f5e | ||
|
|
012235bfeb | ||
|
|
052e284c33 | ||
|
|
32e3dd71b1 | ||
|
|
95f4272919 | ||
|
|
00679b6135 | ||
|
|
2c18bb4508 | ||
|
|
0cf1c9040a | ||
|
|
9196341482 | ||
|
|
685140a4c2 | ||
|
|
1465b0ee4d | ||
|
|
0bf6b765d3 | ||
|
|
4774676e2a | ||
|
|
9c29655da2 | ||
|
|
c8ab18f2b6 | ||
|
|
8ebce466db | ||
|
|
1b39b17125 | ||
|
|
5a46853075 | ||
|
|
48ad4d4c4c | ||
|
|
056a036712 | ||
|
|
70eaa79108 | ||
|
|
20c814a4dd | ||
|
|
6a052e1900 | ||
|
|
cecdf8584a | ||
|
|
4758bc8615 | ||
|
|
c906dc3c0a | ||
|
|
d1dcb41b6f | ||
|
|
96ac86a757 | ||
|
|
4919786825 | ||
|
|
24b4185714 | ||
|
|
ad10d0037a | ||
|
|
b6554c8255 | ||
|
|
01dc83d0d6 | ||
|
|
2fd08789ab | ||
|
|
bc9e529995 | ||
|
|
708c24cc57 | ||
|
|
7fb3048257 | ||
|
|
9319f0525a | ||
|
|
b7a62e0121 | ||
|
|
bd5dd9b9a3 | ||
|
|
3348167c46 | ||
|
|
700c505974 | ||
|
|
d403036d86 | ||
|
|
5e08d7db39 | ||
|
|
c34cb310a8 | ||
|
|
8d86aa69bc | ||
|
|
cc41ccc5f1 | ||
|
|
e6252fe0ed | ||
|
|
03577de675 | ||
|
|
205518ba75 | ||
|
|
2510064218 | ||
|
|
0ef2806970 | ||
|
|
d80f03e369 | ||
|
|
fd271d920b | ||
|
|
2bbf8bc9fa | ||
|
|
9b65d56ed0 | ||
|
|
a5098a60ec | ||
|
|
0ebd900e40 | ||
|
|
7aeb17ac92 | ||
|
|
cc78bfb229 | ||
|
|
485c2a866c | ||
|
|
5b419ca5bf | ||
|
|
14ae579fca | ||
|
|
1c2ea0d7fe | ||
|
|
e7a9ae18a1 | ||
|
|
d61f478a39 | ||
|
|
9cc747b3e2 | ||
|
|
2f223f7db2 | ||
|
|
17f11a3be3 | ||
|
|
37dcf61130 | ||
|
|
856ebfacca | ||
|
|
9731fdd33b | ||
|
|
5ea605ccf7 | ||
|
|
d0c789ff9a | ||
|
|
9baa861742 | ||
|
|
30a1a53c97 | ||
|
|
bdb1b7e77c | ||
|
|
9293bcbc88 | ||
|
|
c481f475e7 | ||
|
|
ef01471e17 | ||
|
|
73c8157197 | ||
|
|
af1dc2d3b2 | ||
|
|
8f6b3feee1 | ||
|
|
a20f5528b7 | ||
|
|
f48876d80e | ||
|
|
db52f13c32 | ||
|
|
2590769d3f | ||
|
|
5667dcac36 | ||
|
|
bec71ead39 | ||
|
|
e4d9022d37 | ||
|
|
572be48f38 | ||
|
|
6f4ccebfa1 | ||
|
|
77fcf52d27 | ||
|
|
79c2bc1fd7 | ||
|
|
76370d9418 | ||
|
|
7bac18bd65 | ||
|
|
704737144a | ||
|
|
2a9c73a1d3 | ||
|
|
e87e851401 | ||
|
|
80d4846a27 | ||
|
|
9fd53c9c91 | ||
|
|
53eae873d8 | ||
|
|
93422f4b1c | ||
|
|
06cedb2e50 | ||
|
|
7fdb1d848b | ||
|
|
246fd9442f | ||
|
|
eb99a64b29 | ||
|
|
d7954a4cb1 | ||
|
|
ef636da866 | ||
|
|
fa18b06dbf | ||
|
|
349b9ce502 | ||
|
|
b2cf121410 | ||
|
|
71cf63bd35 | ||
|
|
d1bb3aada4 | ||
|
|
b4214c6e08 | ||
|
|
f5c7746493 | ||
|
|
f10ec80153 | ||
|
|
0af405aa46 | ||
|
|
cf481effa6 | ||
|
|
a1511f9600 | ||
|
|
325e2b3941 | ||
|
|
7017324d60 | ||
|
|
deb5d69ac7 | ||
|
|
68a04f4e6a | ||
|
|
0d61902b10 | ||
|
|
3eec210b30 | ||
|
|
5998f3b35b | ||
|
|
869567fdd9 | ||
|
|
2e70b5eb9f | ||
|
|
8a3bfb8672 | ||
|
|
06f1e64177 | ||
|
|
b42780173a | ||
|
|
36c8821c4c | ||
|
|
947de2d54a | ||
|
|
9347fe5f44 | ||
|
|
e82367def3 | ||
|
|
9cde7c12ba | ||
|
|
015556cc91 | ||
|
|
47c5a243aa | ||
|
|
070e359d82 | ||
|
|
b397059d5e | ||
|
|
400f54e508 | ||
|
|
e0736435f8 | ||
|
|
b09c5538c6 | ||
|
|
ce3d2913bf | ||
|
|
87202a2a27 | ||
|
|
818a4dff25 | ||
|
|
eacffa49f5 | ||
|
|
9e506c3206 | ||
|
|
29cf80339a | ||
|
|
50f53f7d97 | ||
|
|
73fbd89c85 | ||
|
|
f74fa06f2d | ||
|
|
ee989ab762 | ||
|
|
818655a9b6 | ||
|
|
57a7e0834f | ||
|
|
cd787486d2 | ||
|
|
67fd6787a6 | ||
|
|
627b96f73c | ||
|
|
8a6985c2e8 | ||
|
|
60e8273de2 | ||
|
|
aa8ce5c1ac | ||
|
|
dd28246f9f | ||
|
|
dc25a60b9b | ||
|
|
094d623485 | ||
|
|
1266bbb224 | ||
|
|
bd1ea5740a | ||
|
|
3e04b51122 | ||
|
|
76f2aba51a | ||
|
|
fd88071c0a | ||
|
|
16bfe1a55c | ||
|
|
90c3d6a1e8 | ||
|
|
18d6197d6c | ||
|
|
27eddf6dff | ||
|
|
57b32d9537 | ||
|
|
837b9499d5 | ||
|
|
c2fde2b147 | ||
|
|
f26bf4b9e4 | ||
|
|
1da51bee6c | ||
|
|
5a66956221 | ||
|
|
91d973c4a9 | ||
|
|
fa79589db8 | ||
|
|
e52649f74d | ||
|
|
d77ddaf4fa | ||
|
|
9ff392279a | ||
|
|
448d9dc3e1 | ||
|
|
afb4e6d37d | ||
|
|
158122fbf4 | ||
|
|
417ece2386 | ||
|
|
77b241af4f | ||
|
|
25b8c4c062 | ||
|
|
1be88a5308 | ||
|
|
294280a94e | ||
|
|
32aebfebe0 | ||
|
|
14663bd06b | ||
|
|
68abd197aa | ||
|
|
18fd21eae7 | ||
|
|
3296347370 | ||
|
|
28c9463e0d | ||
|
|
044ac949ba | ||
|
|
87317f5673 | ||
|
|
5e21a49841 | ||
|
|
687c05365e | ||
|
|
4f80523828 | ||
|
|
76299a2add | ||
|
|
48f794dc2d | ||
|
|
51b8dcd011 | ||
|
|
acdbd88b9e | ||
|
|
00a3a3c724 | ||
|
|
729edeac6c | ||
|
|
faaa4961ed | ||
|
|
7937cc2d0f | ||
|
|
8a11a5832c | ||
|
|
53ba0e67d1 | ||
|
|
e8825aeada | ||
|
|
e90e30e766 | ||
|
|
9f6bb325e6 | ||
|
|
6e2c65435a | ||
|
|
052ab44f1c | ||
|
|
daa5679241 | ||
|
|
e055668554 | ||
|
|
c96829c29e | ||
|
|
c88abed2dc | ||
|
|
e42b6cb3c8 | ||
|
|
465ecc4a78 | ||
|
|
ae4ccdf5e6 | ||
|
|
6bdaa54aaf | ||
|
|
3543a25168 | ||
|
|
03ef81b07c | ||
|
|
0ac11fc39e | ||
|
|
3d0503a35e | ||
|
|
ad8cb52f11 | ||
|
|
496a294c71 | ||
|
|
465c74ab86 | ||
|
|
4e8f82a39c | ||
|
|
584a5ad7fb | ||
|
|
0ab85cce20 | ||
|
|
44e5caf803 | ||
|
|
04291e9a86 | ||
|
|
d0776b58cf | ||
|
|
6da099d7e1 | ||
|
|
60e77785e8 | ||
|
|
19cd6a55d3 | ||
|
|
08432dd94b | ||
|
|
cc3c3663f6 | ||
|
|
b76c923ff4 | ||
|
|
3c1131a84b | ||
|
|
a3cd953415 | ||
|
|
c0abdf1b86 | ||
|
|
3ef2715eee | ||
|
|
4a12d7086d | ||
|
|
a6b75b8637 | ||
|
|
bdb3bce8d6 | ||
|
|
a26716919c | ||
|
|
8dbc7649aa | ||
|
|
42a9dc7c2b | ||
|
|
7965772745 | ||
|
|
f37f89a7d3 | ||
|
|
d987e5a9d7 | ||
|
|
fcba0cc3d6 | ||
|
|
c097ed348a | ||
|
|
0f9ab53ea0 | ||
|
|
21b1dab4a5 | ||
|
|
dd7419282d | ||
|
|
7562917740 | ||
|
|
3925eee575 | ||
|
|
6482303063 | ||
|
|
388b136980 | ||
|
|
9ce1dbaebb | ||
|
|
064667c0c3 | ||
|
|
58be770eaa | ||
|
|
1b0f45649e | ||
|
|
42bfabbe8c | ||
|
|
986c4006a6 | ||
|
|
07a63d62dd | ||
|
|
26911a16e8 | ||
|
|
cf9a5d595b | ||
|
|
09a6a1905b | ||
|
|
2ad2b4384b | ||
|
|
7729f1f3d0 | ||
|
|
7d59ff6d8f | ||
|
|
2ee478b4c4 | ||
|
|
bb0d35e3d0 | ||
|
|
84774a7910 | ||
|
|
a482ce1546 | ||
|
|
a35e1f4fbe | ||
|
|
2371048ad1 | ||
|
|
93b9ea67e6 | ||
|
|
60a0f8e824 | ||
|
|
b3fc64d4f2 | ||
|
|
650b9a139b | ||
|
|
5758693b7d | ||
|
|
f8c9ef2950 | ||
|
|
69ca2e8803 | ||
|
|
87fac15cc4 | ||
|
|
2d51924a3c | ||
|
|
c3d96b30d7 | ||
|
|
44240773ef | ||
|
|
ed587a4db5 | ||
|
|
020a04006e | ||
|
|
622a8abf7f | ||
|
|
871bac6c8a | ||
|
|
fe3e8f87e7 | ||
|
|
87fc7c02e8 | ||
|
|
f2620e6afb | ||
|
|
ab2ad70885 | ||
|
|
135134acfd | ||
|
|
5664e81d48 | ||
|
|
c353923557 | ||
|
|
b830d62850 | ||
|
|
17f551e89d | ||
|
|
4a4da90d56 | ||
|
|
404c1f06e6 | ||
|
|
730bfcd1fd | ||
|
|
97249b0edd | ||
|
|
5a1bda1d82 | ||
|
|
b7d6b8efcf | ||
|
|
9bec91c2b9 | ||
|
|
3d1775d853 | ||
|
|
814c057570 | ||
|
|
b63ca16ce2 | ||
|
|
0ddf09ac0f | ||
|
|
e53586df1d | ||
|
|
54491b35ef | ||
|
|
b447f5f174 | ||
|
|
39a105b48a | ||
|
|
cdc19c6990 | ||
|
|
397704a1e6 | ||
|
|
1a5dafae00 | ||
|
|
d368dae94a | ||
|
|
54e2eb0948 | ||
|
|
7d778bc328 | ||
|
|
7a8317ad81 | ||
|
|
a32a2f36be | ||
|
|
064fe7658c | ||
|
|
cd215ef521 | ||
|
|
14c5e038e2 | ||
|
|
82717b39bb | ||
|
|
f190a1395a | ||
|
|
4eaf3440bd | ||
|
|
f985248902 | ||
|
|
5c90744f0c | ||
|
|
e9177bbb2a | ||
|
|
ab5e4ca9c7 | ||
|
|
40516c9cec | ||
|
|
d93d380c88 | ||
|
|
8a1c6978de | ||
|
|
6839e9e3b3 | ||
|
|
83cbbe09c6 | ||
|
|
166ddab5e0 | ||
|
|
67408521cd | ||
|
|
f05260b839 | ||
|
|
62949d2f8b | ||
|
|
2f18f40697 | ||
|
|
eea4c1f148 | ||
|
|
63a792f434 | ||
|
|
7b164de6fd | ||
|
|
26ad760904 | ||
|
|
24e68166c6 | ||
|
|
b72474f418 | ||
|
|
38046d49aa | ||
|
|
4601421aa6 | ||
|
|
86fd47545d | ||
|
|
c8471eb993 | ||
|
|
83d0cfc24e | ||
|
|
cbf5a79ee8 | ||
|
|
2f45e07d82 | ||
|
|
496b6b5cfc | ||
|
|
8604b1786e | ||
|
|
267e28e012 | ||
|
|
631a8a7421 | ||
|
|
7dcb0553e4 | ||
|
|
2a7ea9f57c | ||
|
|
e2b20568c6 | ||
|
|
4f5eb4d71b | ||
|
|
a1df8452ce | ||
|
|
9781460c41 | ||
|
|
55c9d152e9 | ||
|
|
71a107fe75 | ||
|
|
6cf9099ce1 | ||
|
|
e6dc39f6f0 | ||
|
|
f6466fd657 | ||
|
|
28ce675c96 | ||
|
|
3d91b0a31b | ||
|
|
5d1970d201 | ||
|
|
72d7901c88 | ||
|
|
60cfec6a65 | ||
|
|
2e9065b34c | ||
|
|
992ee6d631 | ||
|
|
772093c311 | ||
|
|
e42843cca0 | ||
|
|
3336a123f8 | ||
|
|
bd54e30748 | ||
|
|
35be402354 | ||
|
|
28bd620e7f | ||
|
|
96f2d802d9 | ||
|
|
b117df3367 | ||
|
|
fa8236741d | ||
|
|
e16d5f33d1 | ||
|
|
2a45e7a8d4 | ||
|
|
f8f0ff0fae | ||
|
|
f5dcff2f29 | ||
|
|
e773b331cd | ||
|
|
99c21925f4 | ||
|
|
eccf5ca043 | ||
|
|
24af62a3e5 | ||
|
|
52cf15c3e6 | ||
|
|
a791680e6f | ||
|
|
a3e98907ca | ||
|
|
6e53b4c507 | ||
|
|
52c38e72f6 | ||
|
|
a51d143c35 | ||
|
|
17e9305282 | ||
|
|
c284b34003 | ||
|
|
2ab3bba695 | ||
|
|
2c4dcf8843 | ||
|
|
ea40b2c982 | ||
|
|
adfdfa205f | ||
|
|
e83b2120ce | ||
|
|
33abdc95aa | ||
|
|
6ca8aa99fc | ||
|
|
17bac4c8cf | ||
|
|
46bd20b5e0 | ||
|
|
3c7f9a43ad | ||
|
|
82312d3b59 | ||
|
|
93a80a30d3 | ||
|
|
77b1efd176 | ||
|
|
acfab1dfb3 | ||
|
|
819e9039ab | ||
|
|
6526c645a5 | ||
|
|
3d2490b774 | ||
|
|
1e041f1adf | ||
|
|
4fdf01a1a8 | ||
|
|
beb514b231 | ||
|
|
f57e897085 | ||
|
|
2a8e8a4982 | ||
|
|
9f202d4238 | ||
|
|
1a40cc048e | ||
|
|
53514c7fdc | ||
|
|
274b3c7d24 | ||
|
|
07df7572b3 | ||
|
|
906b6ccdb7 | ||
|
|
f1ba040dd8 | ||
|
|
8db289e229 | ||
|
|
8142487d57 | ||
|
|
2860be7068 | ||
|
|
b5ecd5f7ef | ||
|
|
7e720e754b | ||
|
|
41a618c957 | ||
|
|
3d85e6bb97 | ||
|
|
d54085c7fd | ||
|
|
0bb8bdf938 | ||
|
|
865058b8d6 | ||
|
|
b6bc0a21fb | ||
|
|
8311ac4a7c | ||
|
|
4636d8dfb7 | ||
|
|
ac95e4d758 | ||
|
|
b8c6d4b153 | ||
|
|
5eddc92846 | ||
|
|
f50e8b5106 | ||
|
|
dcc2fe0990 | ||
|
|
56111c75ae | ||
|
|
cc90935abd | ||
|
|
413e42e1b6 | ||
|
|
fc4bda0047 | ||
|
|
c8beb59172 | ||
|
|
8789ffda15 | ||
|
|
e8e604dc3c | ||
|
|
57e0fdfadc | ||
|
|
7f62732476 | ||
|
|
36aebe0ff9 | ||
|
|
051d2b83f4 | ||
|
|
17b12120eb | ||
|
|
6e9ce50569 | ||
|
|
adef2e9b4e | ||
|
|
0fafbf5092 | ||
|
|
3c887aff95 | ||
|
|
e5076b295b | ||
|
|
c10c161d39 | ||
|
|
04024ca159 | ||
|
|
64d556f60f | ||
|
|
8564e7406b | ||
|
|
ebdb58d790 | ||
|
|
cf8afc70b2 | ||
|
|
4f02e8fbaf | ||
|
|
6e618a6bb7 | ||
|
|
df1bc18fb3 | ||
|
|
9f12ce2fb8 | ||
|
|
b9672c0669 | ||
|
|
e58608b25a | ||
|
|
e502d76371 | ||
|
|
b0c790f3c6 | ||
|
|
aa478cd222 | ||
|
|
c78c121159 | ||
|
|
e71e506883 | ||
|
|
a601ac0cab | ||
|
|
9b92753e0a | ||
|
|
ec0018df79 | ||
|
|
8b19c523cf | ||
|
|
5ace61f9b9 | ||
|
|
8a74f5911c | ||
|
|
4982430a29 | ||
|
|
dea79c6dea | ||
|
|
ad03858c6e | ||
|
|
54b26c7991 | ||
|
|
17c3a3eb4b | ||
|
|
5f413a38df | ||
|
|
8860d0ff51 | ||
|
|
8bd471fa3c | ||
|
|
cd6ac51aa6 | ||
|
|
10caa1a1fb | ||
|
|
722e0068ca | ||
|
|
8f2eea8819 | ||
|
|
3b2d65fa16 | ||
|
|
3dc36b704a | ||
|
|
37a20e125c | ||
|
|
2910faf963 | ||
|
|
321e10fffb | ||
|
|
1acb8c3c42 | ||
|
|
f667dd223f | ||
|
|
e0d90f69ec | ||
|
|
d82187bee2 | ||
|
|
3c20e1f037 | ||
|
|
15bedc74d4 | ||
|
|
4bd6ffa9e4 | ||
|
|
9c2c918760 | ||
|
|
47d20699d8 | ||
|
|
e8ce70dccb | ||
|
|
fa4938f29c | ||
|
|
ddb4bb1421 | ||
|
|
ca94e9038e | ||
|
|
2c72a77a25 | ||
|
|
8c0e06e645 | ||
|
|
a24ae727a7 | ||
|
|
5058a8b96a | ||
|
|
762ecab3aa | ||
|
|
9ba5b7c1d4 | ||
|
|
5f807b6e47 | ||
|
|
718f950071 | ||
|
|
68fe16a092 | ||
|
|
97a64db5e0 | ||
|
|
86577b772b | ||
|
|
306df7554e | ||
|
|
30c2c0f050 | ||
|
|
205649cac2 | ||
|
|
fd49b72e31 | ||
|
|
995904993d | ||
|
|
17cbba85fc | ||
|
|
9d7d45338f | ||
|
|
3b55d3f158 | ||
|
|
fda2293d6b | ||
|
|
da814c62bc | ||
|
|
d4095b1b3b | ||
|
|
ed41154338 | ||
|
|
38bca5f0f0 | ||
|
|
a8738b533a | ||
|
|
29cf96c703 | ||
|
|
782dc3d046 | ||
|
|
0ae217f51d | ||
|
|
adcb2e03e8 | ||
|
|
11b6c1d4b5 | ||
|
|
367cb1789d | ||
|
|
adf1484ecc | ||
|
|
5401ff6c78 | ||
|
|
eb8d0eefd5 | ||
|
|
c934e22cee | ||
|
|
1a3effc692 | ||
|
|
32c942d154 | ||
|
|
9c5dc0ed29 | ||
|
|
290972cedf | ||
|
|
dc9d370952 | ||
|
|
a41be61f99 | ||
|
|
3d1783ddae | ||
|
|
8151c8e409 | ||
|
|
0ef42f93ff | ||
|
|
d318ab4e70 | ||
|
|
ebfa35c2c7 | ||
|
|
db50b0fe23 | ||
|
|
233a69a1d8 | ||
|
|
3749b7b776 | ||
|
|
ed63e7ea75 | ||
|
|
31d68622c8 | ||
|
|
ee5f45c979 | ||
|
|
3d79b11f92 | ||
|
|
dfe4e49110 | ||
|
|
12784a71e2 | ||
|
|
e0b36c9c3d | ||
|
|
c5c56f9d05 | ||
|
|
9f0129cab8 | ||
|
|
5a48e50355 | ||
|
|
86283b1815 | ||
|
|
a38d964f62 | ||
|
|
114d48b076 | ||
|
|
6e9d517c26 | ||
|
|
3b2e97e77c | ||
|
|
159924dcc0 | ||
|
|
5d8f284757 | ||
|
|
c978a95463 | ||
|
|
fe4caf7a41 | ||
|
|
4bf85abf30 | ||
|
|
49cee90b4d | ||
|
|
394f6b58d8 | ||
|
|
dbdea95241 | ||
|
|
1928c955d9 | ||
|
|
a91a13b46b | ||
|
|
2f86d5ebaf | ||
|
|
b589d6e3ef | ||
|
|
db8b265e80 | ||
|
|
8560b38ffa | ||
|
|
049a78c667 | ||
|
|
574a37814c | ||
|
|
94eb17db0c | ||
|
|
9577c8e27f | ||
|
|
c72bdd776e | ||
|
|
d35def4bbc | ||
|
|
d5f209366a | ||
|
|
9062e80e9d | ||
|
|
fd3760cedc | ||
|
|
9b73331ee9 | ||
|
|
65ca931e83 | ||
|
|
6cb71eb11b | ||
|
|
43251193ee | ||
|
|
55de98fb46 | ||
|
|
1422d43c35 | ||
|
|
6273ef8ba2 | ||
|
|
3c6f09a898 | ||
|
|
24fcb0c24b | ||
|
|
3162873a9c | ||
|
|
03e2b6a265 | ||
|
|
ee22cf7ca1 | ||
|
|
187f507532 | ||
|
|
6000bd3a5e | ||
|
|
87069da3dd | ||
|
|
5cb4077576 | ||
|
|
e9c7e0b9dd | ||
|
|
35aa7612bb | ||
|
|
acaa841822 | ||
|
|
46c1c9b5ee | ||
|
|
4bdbca64b2 | ||
|
|
3da6b4709c | ||
|
|
11fe8ab6db | ||
|
|
a9ce43d244 | ||
|
|
091bce9350 | ||
|
|
32ccce3040 | ||
|
|
ab3fcb3ea0 | ||
|
|
9610672615 | ||
|
|
5ee9630624 | ||
|
|
1b3836eb1c | ||
|
|
1302a046e9 | ||
|
|
33dec3c220 | ||
|
|
7c29c3a944 | ||
|
|
c9ca1fc7a0 | ||
|
|
a965c8de9f | ||
|
|
0b4b271e3d | ||
|
|
5fc6dd1a4d | ||
|
|
79ef026b93 | ||
|
|
a4ab5b0b49 | ||
|
|
310282b7c9 | ||
|
|
af667c718e | ||
|
|
950f5b1691 | ||
|
|
f54a3f8619 | ||
|
|
cbc0d848ad | ||
|
|
f4d13d1f6f | ||
|
|
6808ad6f5d | ||
|
|
7a8920ee38 | ||
|
|
4870506f6e | ||
|
|
6f47f9d67c | ||
|
|
8093f67173 | ||
|
|
72884f37c3 | ||
|
|
8edb3fcd5f | ||
|
|
b0efc647f1 | ||
|
|
fdd102df52 | ||
|
|
73d28838c0 | ||
|
|
03a893dc74 | ||
|
|
56de2512ae | ||
|
|
cdc2311045 | ||
|
|
c6c12209e8 | ||
|
|
eec27c3406 | ||
|
|
2ac6f96806 | ||
|
|
0bd3103949 | ||
|
|
098a22aa95 | ||
|
|
9a819d6ca0 | ||
|
|
b4bf541eec | ||
|
|
7ede3d2b9e | ||
|
|
e7160fe3c3 | ||
|
|
9d61665014 | ||
|
|
d2938ad7c8 | ||
|
|
9e0e063f8a | ||
|
|
46f7ff07f7 | ||
|
|
8ace258fbc | ||
|
|
4359fb1746 | ||
|
|
a34f294ba8 | ||
|
|
cd7d080b7a | ||
|
|
b0936b6ef4 | ||
|
|
8fae74f93e | ||
|
|
fca48e4b66 | ||
|
|
dd816c5a0a | ||
|
|
3b2ea37428 | ||
|
|
8a805b6ba1 | ||
|
|
3cc89cb4d2 | ||
|
|
9b45c5a1cd | ||
|
|
3cba3a5ac0 | ||
|
|
4b024c5787 | ||
|
|
4a42de4f18 | ||
|
|
d00e5d23ef | ||
|
|
2c9ce116a2 | ||
|
|
3512352c32 | ||
|
|
4d9372c52f | ||
|
|
1d288b08b6 | ||
|
|
f3c7c11772 | ||
|
|
4b9fe805e9 | ||
|
|
a7051e4e42 | ||
|
|
34794223b4 | ||
|
|
96cf617ee6 | ||
|
|
69dddf34b9 | ||
|
|
8f4597f742 | ||
|
|
98347cb1c3 | ||
|
|
c7ab3d4075 | ||
|
|
cddd72876f | ||
|
|
62f936128d | ||
|
|
bb80e53021 | ||
|
|
952891d1b6 | ||
|
|
6dfad6a44b | ||
|
|
e4c5bfdd5c | ||
|
|
da8563733b | ||
|
|
e41faeb557 | ||
|
|
9a55eb56ea | ||
|
|
9206ab5dc3 | ||
|
|
7e39550fc0 | ||
|
|
96e79301f3 | ||
|
|
c3f5fbd300 | ||
|
|
1db713fec1 | ||
|
|
68ba73bee0 | ||
|
|
cdacf280e1 | ||
|
|
1538a02e18 | ||
|
|
f9cec9a102 | ||
|
|
adda3d8f42 | ||
|
|
ec3ff0da12 | ||
|
|
73c38b3b0d | ||
|
|
edc8050b36 | ||
|
|
37815a982a | ||
|
|
bd8af25294 | ||
|
|
3207183f05 | ||
|
|
e803f993b7 | ||
|
|
5dbc87caf0 | ||
|
|
4862ccc947 | ||
|
|
e1ecf66485 | ||
|
|
2c71ba0744 | ||
|
|
a7aeb779e9 | ||
|
|
e72cfbf447 | ||
|
|
0c04a376c4 | ||
|
|
3c6dc4c448 | ||
|
|
5d154e3d0c | ||
|
|
86a24cc928 | ||
|
|
e8b52d20e9 | ||
|
|
b0fc2f6ecf | ||
|
|
715a1b9cd6 | ||
|
|
81969bbea9 | ||
|
|
86310849eb | ||
|
|
a2a928e262 | ||
|
|
ffc9e229b6 | ||
|
|
3813e00ca3 | ||
|
|
5698aa6499 | ||
|
|
1f5908dc51 | ||
|
|
72884c3ead | ||
|
|
80358cf5bd | ||
|
|
a15af1df5e | ||
|
|
6d511f01a4 | ||
|
|
da9e378ab1 | ||
|
|
6d3d7c6006 | ||
|
|
8024bbd721 | ||
|
|
ece9382a4e | ||
|
|
6ba517a4c1 | ||
|
|
20fd5adb24 | ||
|
|
abb350ff5b | ||
|
|
dc8d4d49f5 | ||
|
|
54352cb1cb | ||
|
|
7e106c6add | ||
|
|
0ae49b356a | ||
|
|
32374444ba | ||
|
|
287bfeb924 | ||
|
|
b5fa574686 | ||
|
|
7aea3dc124 | ||
|
|
81c38c7200 | ||
|
|
3bb3d8c5c1 | ||
|
|
b57a2bfec9 | ||
|
|
93968d267d | ||
|
|
d27fb5f199 | ||
|
|
a51f4122f0 | ||
|
|
35ba5fc894 | ||
|
|
228d901253 | ||
|
|
d37ba62343 | ||
|
|
699fb0aa4b | ||
|
|
613d4b7c8b | ||
|
|
4f9d06d8c7 | ||
|
|
5149e4364a | ||
|
|
6b29e1f598 | ||
|
|
6c9edbb7a2 | ||
|
|
282d0f1ebb | ||
|
|
f466cbadec | ||
|
|
189a468ad4 | ||
|
|
a3414c2673 | ||
|
|
5126163c5d | ||
|
|
46ee98639e | ||
|
|
cc6c0d535c | ||
|
|
78b57e73d5 | ||
|
|
9e2a6526d1 | ||
|
|
d3c7253981 | ||
|
|
e3147b6b45 | ||
|
|
d50b059a17 | ||
|
|
cc5ec78156 | ||
|
|
ddc44ce0d1 | ||
|
|
5cbb91f352 | ||
|
|
91ea2eff4c | ||
|
|
bf85d71674 | ||
|
|
426e90eebf | ||
|
|
3889646d6b | ||
|
|
0178aaee2b | ||
|
|
53f60f7c87 | ||
|
|
2da71acefd | ||
|
|
45f5896b76 | ||
|
|
531a3bb7e6 | ||
|
|
1b28d929e4 | ||
|
|
e8943618dc | ||
|
|
1ae2f6f449 | ||
|
|
88e26b42f5 | ||
|
|
03d1aff6c0 | ||
|
|
e4459b6256 | ||
|
|
2be817a6a1 | ||
|
|
a833bb892b | ||
|
|
7f3f6c339f | ||
|
|
0d562699a2 | ||
|
|
034056d0cd | ||
|
|
1249fb598b | ||
|
|
5a8b8478d2 | ||
|
|
6c54699c44 | ||
|
|
266022b193 | ||
|
|
94a6da6b7d | ||
|
|
885fae1534 | ||
|
|
1df2ce513a | ||
|
|
1e4679ae14 | ||
|
|
267dd59a59 | ||
|
|
0a91ac5af5 | ||
|
|
ad93ad6018 | ||
|
|
0c700094ea | ||
|
|
20631a157b | ||
|
|
bdda84dfde | ||
|
|
e44f95a882 | ||
|
|
31cd45f8b5 | ||
|
|
74f9f6ad3b | ||
|
|
1dfdb51e61 | ||
|
|
18832dc19d | ||
|
|
3dee0666cb | ||
|
|
f830f6a57a | ||
|
|
82c733c68c | ||
|
|
ed510409c4 | ||
|
|
7614eba4bf | ||
|
|
13c8032465 | ||
|
|
44fc08cd5b | ||
|
|
7631b11c55 | ||
|
|
726b5f62bb | ||
|
|
ddd84db510 | ||
|
|
966241b4cc | ||
|
|
9371a8993f | ||
|
|
410c99de54 | ||
|
|
817f93a490 | ||
|
|
43611792ac | ||
|
|
62231708d7 | ||
|
|
a5dcab4092 | ||
|
|
8bde2e5f4c | ||
|
|
5287c57ee0 | ||
|
|
3aa47f9c68 | ||
|
|
ab07814614 | ||
|
|
1653abdf88 | ||
|
|
b3ab9fff9b | ||
|
|
14718b93a4 | ||
|
|
69450e27ad | ||
|
|
0cd08aa79d | ||
|
|
1fa94e1b08 | ||
|
|
76d9893866 | ||
|
|
c3f8982c62 | ||
|
|
99eba2f8ba | ||
|
|
69509f6502 | ||
|
|
c3187fdbe1 | ||
|
|
42228ea955 | ||
|
|
e5f57ea743 | ||
|
|
3b398f7a9a | ||
|
|
096add7551 | ||
|
|
334e0666b7 | ||
|
|
98c81749c8 | ||
|
|
5dcf720bb5 | ||
|
|
9c0c0255f6 | ||
|
|
68c15bd605 | ||
|
|
9a2f32795f | ||
|
|
7aa6cf4c6b | ||
|
|
dfda2adf0d | ||
|
|
c0a1c34012 | ||
|
|
3c6adc1ff4 | ||
|
|
e511d33a7c | ||
|
|
c35969d677 | ||
|
|
27afb8f0a7 | ||
|
|
327ab81436 | ||
|
|
db7178495f | ||
|
|
979186e71d | ||
|
|
f05e0d956b | ||
|
|
b22aa5d699 | ||
|
|
3e6a2adaaf | ||
|
|
8f5537aaaa | ||
|
|
a15d4a156b | ||
|
|
6a47571d17 | ||
|
|
7479dc74ed | ||
|
|
28da1a724a | ||
|
|
f529eadbec | ||
|
|
5dc3cd3a2f | ||
|
|
3039a445f0 | ||
|
|
82797fd395 | ||
|
|
a0885ab7d0 | ||
|
|
8eaf1303a3 | ||
|
|
20cbe72985 | ||
|
|
071ad6b767 | ||
|
|
0619e49eac | ||
|
|
b8848d8580 | ||
|
|
aface1f8be | ||
|
|
ae87728770 | ||
|
|
28c8ba70c1 | ||
|
|
486324ecab | ||
|
|
6892ac13e8 | ||
|
|
340ad093a6 | ||
|
|
0fe09cd1e4 | ||
|
|
da4702851f | ||
|
|
09fba72d58 | ||
|
|
d17c90edf7 | ||
|
|
7966592fae | ||
|
|
6efe4e1753 | ||
|
|
536c4d45c1 | ||
|
|
a02f88fe7c | ||
|
|
d9be6ab806 | ||
|
|
290598429a | ||
|
|
92e72959c3 | ||
|
|
776f014dbe | ||
|
|
c01bc784b9 | ||
|
|
abcd86a294 | ||
|
|
451f83ba51 | ||
|
|
b439f40fe2 | ||
|
|
968166b06d | ||
|
|
88293909f4 | ||
|
|
9b6c48631d | ||
|
|
0ed98cbfac | ||
|
|
7dde7cc743 | ||
|
|
755627f12d | ||
|
|
f8004d7096 | ||
|
|
0418f51ef2 | ||
|
|
054e0af071 | ||
|
|
907c3374c3 | ||
|
|
b578240993 | ||
|
|
f83ee97439 | ||
|
|
19aea85184 | ||
|
|
1ba0a117e7 | ||
|
|
b510b9d337 | ||
|
|
b608e11965 | ||
|
|
e68b3a2f32 | ||
|
|
f7b119ffe1 | ||
|
|
a4cec95db1 | ||
|
|
84c4fa197b | ||
|
|
eac722cf59 | ||
|
|
7439a326a6 | ||
|
|
5ca1c0747f | ||
|
|
466ca38dfa | ||
|
|
93b0839036 | ||
|
|
e068cbc103 | ||
|
|
5c809e5fbf | ||
|
|
3933bf49cf | ||
|
|
7065ba4857 | ||
|
|
ebff83018e | ||
|
|
9ce9167e3c | ||
|
|
993eff1d3d | ||
|
|
7be983ec00 | ||
|
|
18e8d6ce06 | ||
|
|
b7ba0d4327 | ||
|
|
825201f4f2 | ||
|
|
9a05c68ce7 | ||
|
|
d8dccf2500 | ||
|
|
b416aa640f | ||
|
|
4ebf594b3b | ||
|
|
8a83024962 | ||
|
|
bdc1136b96 | ||
|
|
da78dea98f | ||
|
|
dcf8cb14e2 | ||
|
|
38912859e1 | ||
|
|
b83d93abc2 | ||
|
|
36f843bc6e | ||
|
|
15c87e02e9 | ||
|
|
00923eac7c | ||
|
|
a72ac8294c | ||
|
|
4f03bf754d | ||
|
|
78b3ec4b10 | ||
|
|
ef1a514785 | ||
|
|
6635876e7e | ||
|
|
5645f90abe | ||
|
|
b96cd4d18b | ||
|
|
ad8a2e2cb9 | ||
|
|
fa438e5113 | ||
|
|
8641494809 | ||
|
|
f4a23af5d6 | ||
|
|
5449e90b34 | ||
|
|
1cd664ad85 | ||
|
|
e680022b1f | ||
|
|
67c2ce2174 | ||
|
|
596e700b60 | ||
|
|
4a53b6e538 | ||
|
|
687f4bb3bb | ||
|
|
473799cb62 | ||
|
|
ce0536cdfa | ||
|
|
3dc22a9fd5 | ||
|
|
f54b655606 | ||
|
|
d2e868ea2b | ||
|
|
3fc649359a | ||
|
|
1512ac11da | ||
|
|
5039cc7bb2 | ||
|
|
5360a7b4ce | ||
|
|
2957a31f40 | ||
|
|
8c11df52bf | ||
|
|
2b7ffcd48f | ||
|
|
7980a9033e | ||
|
|
125ddfa513 | ||
|
|
636e929607 | ||
|
|
22c792dc46 | ||
|
|
95af1815c8 | ||
|
|
d707c5ac95 | ||
|
|
5c9192e5e6 | ||
|
|
72b5584042 | ||
|
|
f9045b5352 | ||
|
|
f87fe92bc8 | ||
|
|
669d8e64ab | ||
|
|
9447aa38be | ||
|
|
a781c3eb4d | ||
|
|
c0b1308dfd | ||
|
|
2d9dd6704a | ||
|
|
94dba70bbe | ||
|
|
022ec20e75 | ||
|
|
41f69405d8 | ||
|
|
5741e22e29 | ||
|
|
8e242eea54 | ||
|
|
703065a0a5 | ||
|
|
291aa42fe1 | ||
|
|
8fc3496cc9 | ||
|
|
e807a462a1 | ||
|
|
18790a90ae | ||
|
|
21afc70261 | ||
|
|
7bb74af478 | ||
|
|
894269aa06 | ||
|
|
fbe479c43f |
13
.github/FUNDING.yml
vendored
Normal file
13
.github/FUNDING.yml
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
# These are supported funding model platforms
|
||||
|
||||
github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
|
||||
patreon: # Replace with a single Patreon username
|
||||
open_collective: # Replace with a single Open Collective username
|
||||
ko_fi: # Replace with a single Ko-fi username
|
||||
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
|
||||
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
|
||||
liberapay: # Replace with a single Liberapay username
|
||||
issuehunt: # Replace with a single IssueHunt username
|
||||
otechie: # Replace with a single Otechie username
|
||||
custom: ['https://www.amazon.com/hz/wishlist/ls/8WPVFLQQDPTA']
|
||||
# Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
|
||||
@@ -23,10 +23,19 @@ namespace Activity {
|
||||
*/
|
||||
class Observer {
|
||||
public:
|
||||
/// Provides hints as to the sort of information presented on an LED.
|
||||
enum LEDPresentation: uint8_t {
|
||||
/// This LED informs the user of some sort of persistent state, e.g. scroll lock.
|
||||
/// If this flag is absent then the LED describes an ephemeral state, such as media access.
|
||||
Persistent = (1 << 0),
|
||||
};
|
||||
|
||||
/// Announces to the receiver that there is an LED of name @c name.
|
||||
virtual void register_led([[maybe_unused]] const std::string &name) {}
|
||||
virtual void register_led([[maybe_unused]] const std::string &name, [[maybe_unused]] uint8_t presentation = 0) {}
|
||||
|
||||
/// Announces to the receiver that there is a drive of name @c name.
|
||||
///
|
||||
/// If a drive has the same name as an LED, that LED goes with this drive.
|
||||
virtual void register_drive([[maybe_unused]] const std::string &name) {}
|
||||
|
||||
/// Informs the receiver of the new state of the LED with name @c name.
|
||||
|
||||
@@ -14,16 +14,20 @@ namespace Analyser {
|
||||
enum class Machine {
|
||||
AmstradCPC,
|
||||
AppleII,
|
||||
AppleIIgs,
|
||||
Atari2600,
|
||||
AtariST,
|
||||
Amiga,
|
||||
ColecoVision,
|
||||
Electron,
|
||||
Enterprise,
|
||||
Macintosh,
|
||||
MasterSystem,
|
||||
MSX,
|
||||
Oric,
|
||||
Vic20,
|
||||
ZX8081
|
||||
ZX8081,
|
||||
ZXSpectrum,
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -50,7 +50,10 @@ std::unique_ptr<Catalogue> Analyser::Static::Acorn::GetDFSCatalogue(const std::s
|
||||
new_file.name = name;
|
||||
new_file.load_address = uint32_t(details->samples[0][file_offset] | (details->samples[0][file_offset+1] << 8) | ((details->samples[0][file_offset+6]&0x0c) << 14));
|
||||
new_file.execution_address = uint32_t(details->samples[0][file_offset+2] | (details->samples[0][file_offset+3] << 8) | ((details->samples[0][file_offset+6]&0xc0) << 10));
|
||||
new_file.is_protected = names->samples[0][file_offset + 7] & 0x80;
|
||||
if(names->samples[0][file_offset + 7] & 0x80) {
|
||||
// File is locked; it may not be altered or deleted.
|
||||
new_file.flags |= File::Flags::Locked;
|
||||
}
|
||||
|
||||
long data_length = long(details->samples[0][file_offset+4] | (details->samples[0][file_offset+5] << 8) | ((details->samples[0][file_offset+6]&0x30) << 12));
|
||||
int start_sector = details->samples[0][file_offset+7] | ((details->samples[0][file_offset+6]&0x03) << 8);
|
||||
@@ -69,11 +72,16 @@ std::unique_ptr<Catalogue> Analyser::Static::Acorn::GetDFSCatalogue(const std::s
|
||||
new_file.data.insert(new_file.data.end(), next_sector->samples[0].begin(), next_sector->samples[0].begin() + length_from_sector);
|
||||
data_length -= length_from_sector;
|
||||
}
|
||||
if(!data_length) catalogue->files.push_back(new_file);
|
||||
if(!data_length) catalogue->files.push_back(std::move(new_file));
|
||||
}
|
||||
|
||||
return catalogue;
|
||||
}
|
||||
|
||||
/*
|
||||
Primary resource used: "Acorn 8-Bit ADFS Filesystem Structure";
|
||||
http://mdfs.net/Docs/Comp/Disk/Format/ADFS
|
||||
*/
|
||||
std::unique_ptr<Catalogue> Analyser::Static::Acorn::GetADFSCatalogue(const std::shared_ptr<Storage::Disk::Disk> &disk) {
|
||||
auto catalogue = std::make_unique<Catalogue>();
|
||||
Storage::Encodings::MFM::Parser parser(true, disk);
|
||||
@@ -101,5 +109,73 @@ std::unique_ptr<Catalogue> Analyser::Static::Acorn::GetADFSCatalogue(const std::
|
||||
case 3: catalogue->bootOption = Catalogue::BootOption::ExecBOOT; break;
|
||||
}
|
||||
|
||||
// Parse the root directory, at least.
|
||||
for(std::size_t file_offset = 0x005; file_offset < 0x4cb; file_offset += 0x1a) {
|
||||
// Obtain the name, which will be at most ten characters long, and will
|
||||
// be terminated by either a NULL character or a \r.
|
||||
char name[11];
|
||||
std::size_t c = 0;
|
||||
for(; c < 10; c++) {
|
||||
const char next = root_directory[file_offset + c] & 0x7f;
|
||||
name[c] = next;
|
||||
if(next == '\0' || next == '\r') break;
|
||||
}
|
||||
name[c] = '\0';
|
||||
|
||||
// Skip if the name is empty.
|
||||
if(name[0] == '\0') continue;
|
||||
|
||||
// Populate a file then.
|
||||
File new_file;
|
||||
new_file.name = name;
|
||||
new_file.flags =
|
||||
(root_directory[file_offset + 0] & 0x80 ? File::Flags::Readable : 0) |
|
||||
(root_directory[file_offset + 1] & 0x80 ? File::Flags::Writable : 0) |
|
||||
(root_directory[file_offset + 2] & 0x80 ? File::Flags::Locked : 0) |
|
||||
(root_directory[file_offset + 3] & 0x80 ? File::Flags::IsDirectory : 0) |
|
||||
(root_directory[file_offset + 4] & 0x80 ? File::Flags::ExecuteOnly : 0) |
|
||||
(root_directory[file_offset + 5] & 0x80 ? File::Flags::PubliclyReadable : 0) |
|
||||
(root_directory[file_offset + 6] & 0x80 ? File::Flags::PubliclyWritable : 0) |
|
||||
(root_directory[file_offset + 7] & 0x80 ? File::Flags::PubliclyExecuteOnly : 0) |
|
||||
(root_directory[file_offset + 8] & 0x80 ? File::Flags::IsPrivate : 0);
|
||||
|
||||
new_file.load_address =
|
||||
(uint32_t(root_directory[file_offset + 0x0a]) << 0) |
|
||||
(uint32_t(root_directory[file_offset + 0x0b]) << 8) |
|
||||
(uint32_t(root_directory[file_offset + 0x0c]) << 16) |
|
||||
(uint32_t(root_directory[file_offset + 0x0d]) << 24);
|
||||
|
||||
new_file.execution_address =
|
||||
(uint32_t(root_directory[file_offset + 0x0e]) << 0) |
|
||||
(uint32_t(root_directory[file_offset + 0x0f]) << 8) |
|
||||
(uint32_t(root_directory[file_offset + 0x10]) << 16) |
|
||||
(uint32_t(root_directory[file_offset + 0x11]) << 24);
|
||||
|
||||
new_file.sequence_number = root_directory[file_offset + 0x19];
|
||||
|
||||
const uint32_t size =
|
||||
(uint32_t(root_directory[file_offset + 0x12]) << 0) |
|
||||
(uint32_t(root_directory[file_offset + 0x13]) << 8) |
|
||||
(uint32_t(root_directory[file_offset + 0x14]) << 16) |
|
||||
(uint32_t(root_directory[file_offset + 0x15]) << 24);
|
||||
|
||||
uint32_t start_sector =
|
||||
(uint32_t(root_directory[file_offset + 0x16]) << 0) |
|
||||
(uint32_t(root_directory[file_offset + 0x17]) << 8) |
|
||||
(uint32_t(root_directory[file_offset + 0x18]) << 16);
|
||||
|
||||
new_file.data.reserve(size);
|
||||
while(new_file.data.size() < size) {
|
||||
const Storage::Encodings::MFM::Sector *const sector = parser.get_sector(start_sector / (80 * 16), (start_sector / 16) % 80, start_sector % 16);
|
||||
if(!sector) break;
|
||||
|
||||
const auto length_from_sector = std::min(size - new_file.data.size(), sector->samples[0].size());
|
||||
new_file.data.insert(new_file.data.end(), sector->samples[0].begin(), sector->samples[0].begin() + ssize_t(length_from_sector));
|
||||
++start_sector;
|
||||
}
|
||||
|
||||
catalogue->files.push_back(std::move(new_file));
|
||||
}
|
||||
|
||||
return catalogue;
|
||||
}
|
||||
|
||||
@@ -19,19 +19,38 @@ namespace Acorn {
|
||||
|
||||
struct File {
|
||||
std::string name;
|
||||
uint32_t load_address;
|
||||
uint32_t execution_address;
|
||||
bool is_protected;
|
||||
uint32_t load_address = 0;
|
||||
uint32_t execution_address = 0;
|
||||
|
||||
enum Flags: uint16_t {
|
||||
Readable = 1 << 0,
|
||||
Writable = 1 << 1,
|
||||
Locked = 1 << 2,
|
||||
IsDirectory = 1 << 3,
|
||||
ExecuteOnly = 1 << 4,
|
||||
PubliclyReadable = 1 << 5,
|
||||
PubliclyWritable = 1 << 6,
|
||||
PubliclyExecuteOnly = 1 << 7,
|
||||
IsPrivate = 1 << 8,
|
||||
};
|
||||
uint16_t flags = Flags::Readable | Flags::Readable | Flags::PubliclyReadable | Flags::PubliclyWritable;
|
||||
uint8_t sequence_number = 0;
|
||||
|
||||
std::vector<uint8_t> data;
|
||||
|
||||
/// Describes a single chunk of file data; these relate to the tape and ROM filing system.
|
||||
/// The File-level fields contain a 'definitive' version of the load and execution addresses,
|
||||
/// but both of those filing systems also store them per chunk.
|
||||
///
|
||||
/// Similarly, the file-level data will contain the aggregate data of all chunks.
|
||||
struct Chunk {
|
||||
std::string name;
|
||||
uint32_t load_address;
|
||||
uint32_t execution_address;
|
||||
uint16_t block_number;
|
||||
uint16_t block_length;
|
||||
uint8_t block_flag;
|
||||
uint32_t next_address;
|
||||
uint32_t load_address = 0;
|
||||
uint32_t execution_address = 0;
|
||||
uint16_t block_number = 0;
|
||||
uint16_t block_length = 0;
|
||||
uint32_t next_address = 0;
|
||||
uint8_t block_flag = 0;
|
||||
|
||||
bool header_crc_matched;
|
||||
bool data_crc_matched;
|
||||
|
||||
@@ -12,6 +12,8 @@
|
||||
#include "Tape.hpp"
|
||||
#include "Target.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
using namespace Analyser::Static::Acorn;
|
||||
|
||||
static std::vector<std::shared_ptr<Storage::Cartridge::Cartridge>>
|
||||
@@ -59,10 +61,6 @@ static std::vector<std::shared_ptr<Storage::Cartridge::Cartridge>>
|
||||
|
||||
Analyser::Static::TargetList Analyser::Static::Acorn::GetTargets(const Media &media, const std::string &, TargetPlatform::IntType) {
|
||||
auto target = std::make_unique<Target>();
|
||||
target->confidence = 0.5; // TODO: a proper estimation
|
||||
target->has_dfs = false;
|
||||
target->has_adfs = false;
|
||||
target->should_shift_restart = false;
|
||||
|
||||
// strip out inappropriate cartridges
|
||||
target->media.cartridges = AcornCartridgesFrom(media.cartridges);
|
||||
@@ -77,8 +75,8 @@ Analyser::Static::TargetList Analyser::Static::Acorn::GetTargets(const Media &me
|
||||
if(!files.empty()) {
|
||||
bool is_basic = true;
|
||||
|
||||
// protected files are always for *RUNning only
|
||||
if(files.front().is_protected) is_basic = false;
|
||||
// If a file is execute-only, that means *RUN.
|
||||
if(files.front().flags & File::Flags::ExecuteOnly) is_basic = false;
|
||||
|
||||
// check also for a continuous threading of BASIC lines; if none then this probably isn't BASIC code,
|
||||
// so that's also justification to *RUN
|
||||
@@ -108,15 +106,60 @@ Analyser::Static::TargetList Analyser::Static::Acorn::GetTargets(const Media &me
|
||||
dfs_catalogue = GetDFSCatalogue(disk);
|
||||
if(dfs_catalogue == nullptr) adfs_catalogue = GetADFSCatalogue(disk);
|
||||
if(dfs_catalogue || adfs_catalogue) {
|
||||
// Accept the disk and determine whether DFS or ADFS ROMs are implied.
|
||||
// Use the Pres ADFS if using an ADFS, as it leaves Page at &EOO.
|
||||
target->media.disks = media.disks;
|
||||
target->has_dfs = !!dfs_catalogue;
|
||||
target->has_adfs = !!adfs_catalogue;
|
||||
target->has_dfs = bool(dfs_catalogue);
|
||||
target->has_pres_adfs = bool(adfs_catalogue);
|
||||
|
||||
// Check whether a simple shift+break will do for loading this disk.
|
||||
Catalogue::BootOption bootOption = (dfs_catalogue ?: adfs_catalogue)->bootOption;
|
||||
if(bootOption != Catalogue::BootOption::None)
|
||||
if(bootOption != Catalogue::BootOption::None) {
|
||||
target->should_shift_restart = true;
|
||||
else
|
||||
} else {
|
||||
target->loading_command = "*CAT\n";
|
||||
}
|
||||
|
||||
// Check whether adding the AP6 ROM is justified.
|
||||
// For now this is an incredibly dense text search;
|
||||
// if any of the commands that aren't usually present
|
||||
// on a stock Electron are here, add the AP6 ROM and
|
||||
// some sideways RAM such that the SR commands are useful.
|
||||
for(const auto &file: dfs_catalogue ? dfs_catalogue->files : adfs_catalogue->files) {
|
||||
for(const auto &command: {
|
||||
"AQRPAGE", "BUILD", "DUMP", "FORMAT", "INSERT", "LANG", "LIST", "LOADROM",
|
||||
"LOCK", "LROMS", "RLOAD", "ROMS", "RSAVE", "SAVEROM", "SRLOAD", "SRPAGE",
|
||||
"SRUNLOCK", "SRWIPE", "TUBE", "TYPE", "UNLOCK", "UNPLUG", "UROMS",
|
||||
"VERIFY", "ZERO"
|
||||
}) {
|
||||
if(std::search(file.data.begin(), file.data.end(), command, command+strlen(command)) != file.data.end()) {
|
||||
target->has_ap6_rom = true;
|
||||
target->has_sideways_ram = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Enable the Acorn ADFS if a mass-storage device is attached;
|
||||
// unlike the Pres ADFS it retains SCSI logic.
|
||||
if(!media.mass_storage_devices.empty()) {
|
||||
target->has_pres_adfs = false; // To override a floppy selection, if one was made.
|
||||
target->has_acorn_adfs = true;
|
||||
|
||||
// Assume some sort of later-era Acorn work is likely to happen;
|
||||
// so ensure *TYPE, etc are present.
|
||||
target->has_ap6_rom = true;
|
||||
target->has_sideways_ram = true;
|
||||
|
||||
target->media.mass_storage_devices = media.mass_storage_devices;
|
||||
|
||||
// Check for a boot option.
|
||||
const auto sector = target->media.mass_storage_devices.front()->get_block(1);
|
||||
if(sector[0xfd]) {
|
||||
target->should_shift_restart = true;
|
||||
} else {
|
||||
target->loading_command = "*CAT\n";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -109,7 +109,12 @@ static std::unique_ptr<File> GetNextFile(std::deque<File::Chunk> &chunks) {
|
||||
file->name = file->chunks.front().name;
|
||||
file->load_address = file->chunks.front().load_address;
|
||||
file->execution_address = file->chunks.front().execution_address;
|
||||
file->is_protected = !!(file->chunks.back().block_flag & 0x01); // I think the last flags are the ones that count; TODO: check.
|
||||
// I think the final chunk's flags are the ones that count; TODO: check.
|
||||
if(file->chunks.back().block_flag & 0x01) {
|
||||
// File is locked, which in more generalised terms means it is
|
||||
// for execution only.
|
||||
file->flags |= File::Flags::ExecuteOnly;
|
||||
}
|
||||
|
||||
// copy all data into a single big block
|
||||
for(File::Chunk chunk : file->chunks) {
|
||||
|
||||
@@ -18,15 +18,21 @@ namespace Static {
|
||||
namespace Acorn {
|
||||
|
||||
struct Target: public ::Analyser::Static::Target, public Reflection::StructImpl<Target> {
|
||||
bool has_adfs = false;
|
||||
bool has_acorn_adfs = false;
|
||||
bool has_pres_adfs = false;
|
||||
bool has_dfs = false;
|
||||
bool has_ap6_rom = false;
|
||||
bool has_sideways_ram = false;
|
||||
bool should_shift_restart = false;
|
||||
std::string loading_command;
|
||||
|
||||
Target() : Analyser::Static::Target(Machine::Electron) {
|
||||
if(needs_declare()) {
|
||||
DeclareField(has_adfs);
|
||||
DeclareField(has_pres_adfs);
|
||||
DeclareField(has_acorn_adfs);
|
||||
DeclareField(has_dfs);
|
||||
DeclareField(has_ap6_rom);
|
||||
DeclareField(has_sideways_ram);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
25
Analyser/Static/Amiga/StaticAnalyser.cpp
Normal file
25
Analyser/Static/Amiga/StaticAnalyser.cpp
Normal file
@@ -0,0 +1,25 @@
|
||||
//
|
||||
// StaticAnalyser.cpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 16/07/2021.
|
||||
// Copyright © 2021 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#include "StaticAnalyser.hpp"
|
||||
#include "Target.hpp"
|
||||
|
||||
Analyser::Static::TargetList Analyser::Static::Amiga::GetTargets(const Media &media, const std::string &, TargetPlatform::IntType) {
|
||||
// This analyser can comprehend disks and mass-storage devices only.
|
||||
if(media.disks.empty()) return {};
|
||||
|
||||
// As there is at least one usable media image, wave it through.
|
||||
Analyser::Static::TargetList targets;
|
||||
|
||||
using Target = Analyser::Static::Amiga::Target;
|
||||
auto *const target = new Target();
|
||||
target->media = media;
|
||||
targets.push_back(std::unique_ptr<Analyser::Static::Target>(target));
|
||||
|
||||
return targets;
|
||||
}
|
||||
27
Analyser/Static/Amiga/StaticAnalyser.hpp
Normal file
27
Analyser/Static/Amiga/StaticAnalyser.hpp
Normal file
@@ -0,0 +1,27 @@
|
||||
//
|
||||
// StaticAnalyser.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 16/07/2021.
|
||||
// Copyright © 2021 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef Analyser_Static_Amiga_StaticAnalyser_hpp
|
||||
#define Analyser_Static_Amiga_StaticAnalyser_hpp
|
||||
|
||||
#include "../StaticAnalyser.hpp"
|
||||
#include "../../../Storage/TargetPlatforms.hpp"
|
||||
#include <string>
|
||||
|
||||
namespace Analyser {
|
||||
namespace Static {
|
||||
namespace Amiga {
|
||||
|
||||
TargetList GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#endif /* Analyser_Static_Amiga_StaticAnalyser_hpp */
|
||||
48
Analyser/Static/Amiga/Target.hpp
Normal file
48
Analyser/Static/Amiga/Target.hpp
Normal file
@@ -0,0 +1,48 @@
|
||||
//
|
||||
// Target.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 16/07/2021.
|
||||
// Copyright © 2021 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef Analyser_Static_Amiga_Target_h
|
||||
#define Analyser_Static_Amiga_Target_h
|
||||
|
||||
#include "../../../Reflection/Struct.hpp"
|
||||
#include "../StaticAnalyser.hpp"
|
||||
|
||||
namespace Analyser {
|
||||
namespace Static {
|
||||
namespace Amiga {
|
||||
|
||||
struct Target: public Analyser::Static::Target, public Reflection::StructImpl<Target> {
|
||||
ReflectableEnum(ChipRAM,
|
||||
FiveHundredAndTwelveKilobytes,
|
||||
OneMegabyte,
|
||||
TwoMegabytes);
|
||||
ReflectableEnum(FastRAM,
|
||||
None,
|
||||
OneMegabyte,
|
||||
TwoMegabytes,
|
||||
FourMegabytes,
|
||||
EightMegabytes);
|
||||
|
||||
ChipRAM chip_ram = ChipRAM::FiveHundredAndTwelveKilobytes;
|
||||
FastRAM fast_ram = FastRAM::EightMegabytes;
|
||||
|
||||
Target() : Analyser::Static::Target(Machine::Amiga) {
|
||||
if(needs_declare()) {
|
||||
DeclareField(fast_ram);
|
||||
DeclareField(chip_ram);
|
||||
AnnounceEnum(FastRAM);
|
||||
AnnounceEnum(ChipRAM);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* Analyser_Static_Amiga_Target_h */
|
||||
@@ -11,12 +11,15 @@
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
|
||||
#include "Target.hpp"
|
||||
|
||||
#include "../../../Storage/Disk/Parsers/CPM.hpp"
|
||||
#include "../../../Storage/Disk/Encodings/MFM/Parser.hpp"
|
||||
#include "../../../Storage/Tape/Parsers/Spectrum.hpp"
|
||||
|
||||
static bool strcmp_insensitive(const char *a, const char *b) {
|
||||
#include "Target.hpp"
|
||||
|
||||
namespace {
|
||||
|
||||
bool strcmp_insensitive(const char *a, const char *b) {
|
||||
if(std::strlen(a) != std::strlen(b)) return false;
|
||||
while(*a) {
|
||||
if(std::tolower(*a) != std::tolower(*b)) return false;
|
||||
@@ -26,20 +29,20 @@ static bool strcmp_insensitive(const char *a, const char *b) {
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool is_implied_extension(const std::string &extension) {
|
||||
bool is_implied_extension(const std::string &extension) {
|
||||
return
|
||||
extension == " " ||
|
||||
strcmp_insensitive(extension.c_str(), "BAS") ||
|
||||
strcmp_insensitive(extension.c_str(), "BIN");
|
||||
}
|
||||
|
||||
static void right_trim(std::string &string) {
|
||||
void right_trim(std::string &string) {
|
||||
string.erase(std::find_if(string.rbegin(), string.rend(), [](int ch) {
|
||||
return !std::isspace(ch);
|
||||
}).base(), string.end());
|
||||
}
|
||||
|
||||
static std::string RunCommandFor(const Storage::Disk::CPM::File &file) {
|
||||
std::string RunCommandFor(const Storage::Disk::CPM::File &file) {
|
||||
// Trim spaces from the name.
|
||||
std::string name = file.name;
|
||||
right_trim(name);
|
||||
@@ -58,7 +61,7 @@ static std::string RunCommandFor(const Storage::Disk::CPM::File &file) {
|
||||
return command + "\n";
|
||||
}
|
||||
|
||||
static void InspectCatalogue(
|
||||
void InspectCatalogue(
|
||||
const Storage::Disk::CPM::Catalogue &catalogue,
|
||||
const std::unique_ptr<Analyser::Static::AmstradCPC::Target> &target) {
|
||||
|
||||
@@ -155,7 +158,7 @@ static void InspectCatalogue(
|
||||
target->loading_command = "cat\n";
|
||||
}
|
||||
|
||||
static bool CheckBootSector(const std::shared_ptr<Storage::Disk::Disk> &disk, const std::unique_ptr<Analyser::Static::AmstradCPC::Target> &target) {
|
||||
bool CheckBootSector(const std::shared_ptr<Storage::Disk::Disk> &disk, const std::unique_ptr<Analyser::Static::AmstradCPC::Target> &target) {
|
||||
Storage::Encodings::MFM::Parser parser(true, disk);
|
||||
Storage::Encodings::MFM::Sector *boot_sector = parser.get_sector(0, 0, 0x41);
|
||||
if(boot_sector != nullptr && !boot_sector->samples.empty() && boot_sector->samples[0].size() == 512) {
|
||||
@@ -179,6 +182,28 @@ static bool CheckBootSector(const std::shared_ptr<Storage::Disk::Disk> &disk, co
|
||||
return false;
|
||||
}
|
||||
|
||||
bool IsAmstradTape(const std::shared_ptr<Storage::Tape::Tape> &tape) {
|
||||
// Limited sophistication here; look for a CPC-style file header, that is
|
||||
// any Spectrum-esque block with a synchronisation character of 0x2c.
|
||||
//
|
||||
// More could be done here: parse the header, look for 0x16 data records.
|
||||
using Parser = Storage::Tape::ZXSpectrum::Parser;
|
||||
Parser parser(Parser::MachineType::AmstradCPC);
|
||||
|
||||
while(true) {
|
||||
const auto block = parser.find_block(tape);
|
||||
if(!block) break;
|
||||
|
||||
if(block->type == 0x2c) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
Analyser::Static::TargetList Analyser::Static::AmstradCPC::GetTargets(const Media &media, const std::string &, TargetPlatform::IntType) {
|
||||
TargetList destination;
|
||||
auto target = std::make_unique<Target>();
|
||||
@@ -187,13 +212,19 @@ Analyser::Static::TargetList Analyser::Static::AmstradCPC::GetTargets(const Medi
|
||||
target->model = Target::Model::CPC6128;
|
||||
|
||||
if(!media.tapes.empty()) {
|
||||
// TODO: which of these are actually potentially CPC tapes?
|
||||
target->media.tapes = media.tapes;
|
||||
bool has_cpc_tape = false;
|
||||
for(auto &tape: media.tapes) {
|
||||
has_cpc_tape |= IsAmstradTape(tape);
|
||||
}
|
||||
|
||||
// Ugliness flows here: assume the CPC isn't smart enough to pause between pressing
|
||||
// enter and responding to the follow-on prompt to press a key, so just type for
|
||||
// a while. Yuck!
|
||||
target->loading_command = "|tape\nrun\"\n1234567890";
|
||||
if(has_cpc_tape) {
|
||||
target->media.tapes = media.tapes;
|
||||
|
||||
// Ugliness flows here: assume the CPC isn't smart enough to pause between pressing
|
||||
// enter and responding to the follow-on prompt to press a key, so just type for
|
||||
// a while. Yuck!
|
||||
target->loading_command = "|tape\nrun\"\n123";
|
||||
}
|
||||
}
|
||||
|
||||
if(!media.disks.empty()) {
|
||||
|
||||
@@ -6,8 +6,8 @@
|
||||
// Copyright 2018 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef Target_h
|
||||
#define Target_h
|
||||
#ifndef Analyser_Static_AppleII_Target_h
|
||||
#define Analyser_Static_AppleII_Target_h
|
||||
|
||||
#include "../../../Reflection/Enum.hpp"
|
||||
#include "../../../Reflection/Struct.hpp"
|
||||
@@ -47,4 +47,4 @@ struct Target: public Analyser::Static::Target, public Reflection::StructImpl<Ta
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* Target_h */
|
||||
#endif /* Analyser_Static_AppleII_Target_h */
|
||||
|
||||
19
Analyser/Static/AppleIIgs/StaticAnalyser.cpp
Normal file
19
Analyser/Static/AppleIIgs/StaticAnalyser.cpp
Normal file
@@ -0,0 +1,19 @@
|
||||
//
|
||||
// StaticAnalyser.cpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 20/10/2020.
|
||||
// Copyright 2018 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#include "StaticAnalyser.hpp"
|
||||
#include "Target.hpp"
|
||||
|
||||
Analyser::Static::TargetList Analyser::Static::AppleIIgs::GetTargets(const Media &media, const std::string &, TargetPlatform::IntType) {
|
||||
auto target = std::make_unique<Target>();
|
||||
target->media = media;
|
||||
|
||||
TargetList targets;
|
||||
targets.push_back(std::move(target));
|
||||
return targets;
|
||||
}
|
||||
26
Analyser/Static/AppleIIgs/StaticAnalyser.hpp
Normal file
26
Analyser/Static/AppleIIgs/StaticAnalyser.hpp
Normal file
@@ -0,0 +1,26 @@
|
||||
//
|
||||
// StaticAnalyser.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 20/10/2020.
|
||||
// Copyright 2018 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef Analyser_Static_AppleIIgs_StaticAnalyser_hpp
|
||||
#define Analyser_Static_AppleIIgs_StaticAnalyser_hpp
|
||||
|
||||
#include "../StaticAnalyser.hpp"
|
||||
#include "../../../Storage/TargetPlatforms.hpp"
|
||||
#include <string>
|
||||
|
||||
namespace Analyser {
|
||||
namespace Static {
|
||||
namespace AppleIIgs {
|
||||
|
||||
TargetList GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* Analyser_Static_AppleIIgs_StaticAnalyser_hpp */
|
||||
49
Analyser/Static/AppleIIgs/Target.hpp
Normal file
49
Analyser/Static/AppleIIgs/Target.hpp
Normal file
@@ -0,0 +1,49 @@
|
||||
//
|
||||
// Target.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 20/10/2020.
|
||||
// Copyright 2018 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef Analyser_Static_AppleIIgs_Target_h
|
||||
#define Analyser_Static_AppleIIgs_Target_h
|
||||
|
||||
#include "../../../Reflection/Enum.hpp"
|
||||
#include "../../../Reflection/Struct.hpp"
|
||||
#include "../StaticAnalyser.hpp"
|
||||
|
||||
namespace Analyser {
|
||||
namespace Static {
|
||||
namespace AppleIIgs {
|
||||
|
||||
struct Target: public Analyser::Static::Target, public Reflection::StructImpl<Target> {
|
||||
ReflectableEnum(Model,
|
||||
ROM00,
|
||||
ROM01,
|
||||
ROM03
|
||||
);
|
||||
ReflectableEnum(MemoryModel,
|
||||
TwoHundredAndFiftySixKB,
|
||||
OneMB,
|
||||
EightMB
|
||||
);
|
||||
|
||||
Model model = Model::ROM01;
|
||||
MemoryModel memory_model = MemoryModel::EightMB;
|
||||
|
||||
Target() : Analyser::Static::Target(Machine::AppleIIgs) {
|
||||
if(needs_declare()) {
|
||||
DeclareField(model);
|
||||
DeclareField(memory_model);
|
||||
AnnounceEnum(Model);
|
||||
AnnounceEnum(MemoryModel);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* Analyser_Static_AppleIIgs_Target_h */
|
||||
@@ -9,6 +9,7 @@
|
||||
#include "StaticAnalyser.hpp"
|
||||
|
||||
#include "../AppleII/Target.hpp"
|
||||
#include "../AppleIIgs/Target.hpp"
|
||||
#include "../Oric/Target.hpp"
|
||||
#include "../Disassembler/6502.hpp"
|
||||
#include "../Disassembler/AddressMapper.hpp"
|
||||
@@ -18,7 +19,7 @@
|
||||
|
||||
namespace {
|
||||
|
||||
Analyser::Static::Target *AppleTarget(const Storage::Encodings::AppleGCR::Sector *sector_zero) {
|
||||
Analyser::Static::Target *AppleIITarget(const Storage::Encodings::AppleGCR::Sector *sector_zero) {
|
||||
using Target = Analyser::Static::AppleII::Target;
|
||||
auto *const target = new Target;
|
||||
|
||||
@@ -31,6 +32,10 @@ Analyser::Static::Target *AppleTarget(const Storage::Encodings::AppleGCR::Sector
|
||||
return target;
|
||||
}
|
||||
|
||||
Analyser::Static::Target *AppleIIgsTarget() {
|
||||
return new Analyser::Static::AppleIIgs::Target();
|
||||
}
|
||||
|
||||
Analyser::Static::Target *OricTarget(const Storage::Encodings::AppleGCR::Sector *) {
|
||||
using Target = Analyser::Static::Oric::Target;
|
||||
auto *const target = new Target;
|
||||
@@ -46,8 +51,18 @@ Analyser::Static::TargetList Analyser::Static::DiskII::GetTargets(const Media &m
|
||||
// This analyser can comprehend disks only.
|
||||
if(media.disks.empty()) return {};
|
||||
|
||||
auto &disk = media.disks.front();
|
||||
TargetList targets;
|
||||
|
||||
// If the disk image is too large for a 5.25" disk, map this to the IIgs.
|
||||
if(disk->get_maximum_head_position() > Storage::Disk::HeadPosition(40)) {
|
||||
targets.push_back(std::unique_ptr<Analyser::Static::Target>(AppleIIgsTarget()));
|
||||
targets.back()->media = media;
|
||||
return targets;
|
||||
}
|
||||
|
||||
// Grab track 0, sector 0: the boot sector.
|
||||
const auto track_zero = media.disks.front()->get_track_at_position(Storage::Disk::Track::Address(0, Storage::Disk::HeadPosition(0)));
|
||||
const auto track_zero = disk->get_track_at_position(Storage::Disk::Track::Address(0, Storage::Disk::HeadPosition(0)));
|
||||
const auto sector_map = Storage::Encodings::AppleGCR::sectors_from_segment(
|
||||
Storage::Disk::track_serialisation(*track_zero, Storage::Time(1, 50000)));
|
||||
|
||||
@@ -61,12 +76,11 @@ Analyser::Static::TargetList Analyser::Static::DiskII::GetTargets(const Media &m
|
||||
|
||||
// If there's no boot sector then if there are also no sectors at all,
|
||||
// decline to nominate a machine. Otherwise go with an Apple as the default.
|
||||
TargetList targets;
|
||||
if(!sector_zero) {
|
||||
if(sector_map.empty()) {
|
||||
return targets;
|
||||
} else {
|
||||
targets.push_back(std::unique_ptr<Analyser::Static::Target>(AppleTarget(nullptr)));
|
||||
targets.push_back(std::unique_ptr<Analyser::Static::Target>(AppleIITarget(nullptr)));
|
||||
targets.back()->media = media;
|
||||
return targets;
|
||||
}
|
||||
@@ -116,7 +130,7 @@ Analyser::Static::TargetList Analyser::Static::DiskII::GetTargets(const Media &m
|
||||
if(is_oric) {
|
||||
targets.push_back(std::unique_ptr<Analyser::Static::Target>(OricTarget(sector_zero)));
|
||||
} else {
|
||||
targets.push_back(std::unique_ptr<Analyser::Static::Target>(AppleTarget(sector_zero)));
|
||||
targets.push_back(std::unique_ptr<Analyser::Static::Target>(AppleIITarget(sector_zero)));
|
||||
}
|
||||
targets.back()->media = media;
|
||||
return targets;
|
||||
|
||||
84
Analyser/Static/Enterprise/StaticAnalyser.cpp
Normal file
84
Analyser/Static/Enterprise/StaticAnalyser.cpp
Normal file
@@ -0,0 +1,84 @@
|
||||
//
|
||||
// StaticAnalyser.cpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 24/06/2021.
|
||||
// Copyright 2021 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#include "StaticAnalyser.hpp"
|
||||
#include "Target.hpp"
|
||||
|
||||
#include "../../../Storage/Disk/Parsers/FAT.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
namespace {
|
||||
|
||||
bool insensitive_equal(const std::string &lhs, const std::string &rhs) {
|
||||
return std::equal(
|
||||
lhs.begin(), lhs.end(),
|
||||
rhs.begin(), rhs.end(),
|
||||
[] (char l, char r) {
|
||||
return tolower(l) == tolower(r);
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Analyser::Static::TargetList Analyser::Static::Enterprise::GetTargets(const Media &media, const std::string &, TargetPlatform::IntType) {
|
||||
// This analyser can comprehend disks only.
|
||||
if(media.disks.empty()) return {};
|
||||
|
||||
// Otherwise, assume a return will happen.
|
||||
Analyser::Static::TargetList targets;
|
||||
using Target = Analyser::Static::Enterprise::Target;
|
||||
auto *const target = new Target;
|
||||
target->media = media;
|
||||
|
||||
// Always require a BASIC.
|
||||
target->basic_version = Target::BASICVersion::Any;
|
||||
|
||||
// Inspect any supplied disks.
|
||||
if(!media.disks.empty()) {
|
||||
// DOS will be needed.
|
||||
target->dos = Target::DOS::EXDOS;
|
||||
|
||||
// Grab the volume information, which includes the root directory.
|
||||
auto volume = Storage::Disk::FAT::GetVolume(media.disks.front());
|
||||
if(volume) {
|
||||
// If there's an EXDOS.INI then this disk should be able to boot itself.
|
||||
// If not but if there's only one visible .COM or .BAS, automatically load
|
||||
// that. Otherwise, issue a :DIR.
|
||||
using File = Storage::Disk::FAT::File;
|
||||
const File *selected_file = nullptr;
|
||||
bool has_exdos_ini = false;
|
||||
bool did_pick_file = false;
|
||||
for(const auto &file: (*volume).root_directory) {
|
||||
if(insensitive_equal(file.name, "exdos") && insensitive_equal(file.extension, "ini")) {
|
||||
has_exdos_ini = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if(!(file.attributes & File::Attribute::Hidden) &&
|
||||
(insensitive_equal(file.extension, "com") || insensitive_equal(file.extension, "bas"))
|
||||
) {
|
||||
did_pick_file = !selected_file;
|
||||
selected_file = &file;
|
||||
}
|
||||
}
|
||||
|
||||
if(!has_exdos_ini) {
|
||||
if(did_pick_file) {
|
||||
target->loading_command = std::string("run \"") + selected_file->name + "." + selected_file->extension + "\"\n";
|
||||
} else {
|
||||
target->loading_command = ":dir\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
targets.push_back(std::unique_ptr<Analyser::Static::Target>(target));
|
||||
|
||||
return targets;
|
||||
}
|
||||
27
Analyser/Static/Enterprise/StaticAnalyser.hpp
Normal file
27
Analyser/Static/Enterprise/StaticAnalyser.hpp
Normal file
@@ -0,0 +1,27 @@
|
||||
//
|
||||
// StaticAnalyser.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 24/06/2021.
|
||||
// Copyright 2018 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef Analyser_Static_Enterprise_StaticAnalyser_hpp
|
||||
#define Analyser_Static_Enterprise_StaticAnalyser_hpp
|
||||
|
||||
#include "../StaticAnalyser.hpp"
|
||||
#include "../../../Storage/TargetPlatforms.hpp"
|
||||
#include <string>
|
||||
|
||||
namespace Analyser {
|
||||
namespace Static {
|
||||
namespace Enterprise {
|
||||
|
||||
TargetList GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#endif /* Analyser_Static_Enterprise_StaticAnalyser_hpp */
|
||||
57
Analyser/Static/Enterprise/Target.hpp
Normal file
57
Analyser/Static/Enterprise/Target.hpp
Normal file
@@ -0,0 +1,57 @@
|
||||
//
|
||||
// Target.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 14/06/2021.
|
||||
// Copyright © 2021 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef Analyser_Static_Enterprise_Target_h
|
||||
#define Analyser_Static_Enterprise_Target_h
|
||||
|
||||
#include "../../../Reflection/Enum.hpp"
|
||||
#include "../../../Reflection/Struct.hpp"
|
||||
#include "../StaticAnalyser.hpp"
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace Analyser {
|
||||
namespace Static {
|
||||
namespace Enterprise {
|
||||
|
||||
struct Target: public Analyser::Static::Target, public Reflection::StructImpl<Target> {
|
||||
ReflectableEnum(Model, Enterprise64, Enterprise128, Enterprise256);
|
||||
ReflectableEnum(EXOSVersion, v10, v20, v21, v23, Any);
|
||||
ReflectableEnum(BASICVersion, v10, v11, v21, Any, None);
|
||||
ReflectableEnum(DOS, EXDOS, None);
|
||||
ReflectableEnum(Speed, FourMHz, SixMHz);
|
||||
|
||||
Model model = Model::Enterprise128;
|
||||
EXOSVersion exos_version = EXOSVersion::Any;
|
||||
BASICVersion basic_version = BASICVersion::None;
|
||||
DOS dos = DOS::None;
|
||||
Speed speed = Speed::FourMHz;
|
||||
std::string loading_command;
|
||||
|
||||
Target() : Analyser::Static::Target(Machine::Enterprise) {
|
||||
if(needs_declare()) {
|
||||
AnnounceEnum(Model);
|
||||
AnnounceEnum(EXOSVersion);
|
||||
AnnounceEnum(BASICVersion);
|
||||
AnnounceEnum(DOS);
|
||||
AnnounceEnum(Speed);
|
||||
|
||||
DeclareField(model);
|
||||
DeclareField(exos_version);
|
||||
DeclareField(basic_version);
|
||||
DeclareField(dos);
|
||||
DeclareField(speed);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* Analyser_Static_Enterprise_Target_h */
|
||||
@@ -19,6 +19,19 @@ Analyser::Static::TargetList Analyser::Static::Macintosh::GetTargets(const Media
|
||||
using Target = Analyser::Static::Macintosh::Target;
|
||||
auto *const target = new Target;
|
||||
target->media = media;
|
||||
|
||||
// If this is a single-sided floppy disk, guess the Macintosh 512kb.
|
||||
if(media.mass_storage_devices.empty()) {
|
||||
bool has_800kb_disks = false;
|
||||
for(const auto &disk: media.disks) {
|
||||
has_800kb_disks |= disk->get_head_count() > 1;
|
||||
}
|
||||
|
||||
if(!has_800kb_disks) {
|
||||
target->model = Target::Model::Mac512k;
|
||||
}
|
||||
}
|
||||
|
||||
targets.push_back(std::unique_ptr<Analyser::Static::Target>(target));
|
||||
|
||||
return targets;
|
||||
|
||||
@@ -33,8 +33,14 @@ struct Target: public Analyser::Static::Target, public Reflection::StructImpl<Ta
|
||||
BD500
|
||||
);
|
||||
|
||||
ReflectableEnum(Processor,
|
||||
MOS6502,
|
||||
WDC65816
|
||||
);
|
||||
|
||||
ROM rom = ROM::BASIC11;
|
||||
DiskInterface disk_interface = DiskInterface::None;
|
||||
Processor processor = Processor::MOS6502;
|
||||
std::string loading_command;
|
||||
bool should_start_jasmin = false;
|
||||
|
||||
@@ -42,8 +48,10 @@ struct Target: public Analyser::Static::Target, public Reflection::StructImpl<Ta
|
||||
if(needs_declare()) {
|
||||
DeclareField(rom);
|
||||
DeclareField(disk_interface);
|
||||
DeclareField(processor);
|
||||
AnnounceEnum(ROM);
|
||||
AnnounceEnum(DiskInterface);
|
||||
AnnounceEnum(Processor);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -15,34 +15,41 @@
|
||||
|
||||
// Analysers
|
||||
#include "Acorn/StaticAnalyser.hpp"
|
||||
#include "Amiga/StaticAnalyser.hpp"
|
||||
#include "AmstradCPC/StaticAnalyser.hpp"
|
||||
#include "AppleII/StaticAnalyser.hpp"
|
||||
#include "AppleIIgs/StaticAnalyser.hpp"
|
||||
#include "Atari2600/StaticAnalyser.hpp"
|
||||
#include "AtariST/StaticAnalyser.hpp"
|
||||
#include "Coleco/StaticAnalyser.hpp"
|
||||
#include "Commodore/StaticAnalyser.hpp"
|
||||
#include "DiskII/StaticAnalyser.hpp"
|
||||
#include "Enterprise/StaticAnalyser.hpp"
|
||||
#include "Macintosh/StaticAnalyser.hpp"
|
||||
#include "MSX/StaticAnalyser.hpp"
|
||||
#include "Oric/StaticAnalyser.hpp"
|
||||
#include "Sega/StaticAnalyser.hpp"
|
||||
#include "ZX8081/StaticAnalyser.hpp"
|
||||
#include "ZXSpectrum/StaticAnalyser.hpp"
|
||||
|
||||
// Cartridges
|
||||
#include "../../Storage/Cartridge/Formats/BinaryDump.hpp"
|
||||
#include "../../Storage/Cartridge/Formats/PRG.hpp"
|
||||
|
||||
// Disks
|
||||
#include "../../Storage/Disk/DiskImage/Formats/2MG.hpp"
|
||||
#include "../../Storage/Disk/DiskImage/Formats/AcornADF.hpp"
|
||||
#include "../../Storage/Disk/DiskImage/Formats/AmigaADF.hpp"
|
||||
#include "../../Storage/Disk/DiskImage/Formats/AppleDSK.hpp"
|
||||
#include "../../Storage/Disk/DiskImage/Formats/CPCDSK.hpp"
|
||||
#include "../../Storage/Disk/DiskImage/Formats/D64.hpp"
|
||||
#include "../../Storage/Disk/DiskImage/Formats/MacintoshIMG.hpp"
|
||||
#include "../../Storage/Disk/DiskImage/Formats/G64.hpp"
|
||||
#include "../../Storage/Disk/DiskImage/Formats/DMK.hpp"
|
||||
#include "../../Storage/Disk/DiskImage/Formats/FAT12.hpp"
|
||||
#include "../../Storage/Disk/DiskImage/Formats/HFE.hpp"
|
||||
#include "../../Storage/Disk/DiskImage/Formats/IPF.hpp"
|
||||
#include "../../Storage/Disk/DiskImage/Formats/MacintoshIMG.hpp"
|
||||
#include "../../Storage/Disk/DiskImage/Formats/MSA.hpp"
|
||||
#include "../../Storage/Disk/DiskImage/Formats/MSXDSK.hpp"
|
||||
#include "../../Storage/Disk/DiskImage/Formats/NIB.hpp"
|
||||
#include "../../Storage/Disk/DiskImage/Formats/OricMFMDSK.hpp"
|
||||
#include "../../Storage/Disk/DiskImage/Formats/SSD.hpp"
|
||||
@@ -51,8 +58,15 @@
|
||||
#include "../../Storage/Disk/DiskImage/Formats/WOZ.hpp"
|
||||
|
||||
// Mass Storage Devices (i.e. usually, hard disks)
|
||||
#include "../../Storage/MassStorage/Formats/DAT.hpp"
|
||||
#include "../../Storage/MassStorage/Formats/DSK.hpp"
|
||||
#include "../../Storage/MassStorage/Formats/HFV.hpp"
|
||||
|
||||
// State Snapshots
|
||||
#include "../../Storage/State/SNA.hpp"
|
||||
#include "../../Storage/State/SZX.hpp"
|
||||
#include "../../Storage/State/Z80.hpp"
|
||||
|
||||
// Tapes
|
||||
#include "../../Storage/Tape/Formats/CAS.hpp"
|
||||
#include "../../Storage/Tape/Formats/CommodoreTAP.hpp"
|
||||
@@ -62,80 +76,118 @@
|
||||
#include "../../Storage/Tape/Formats/TapeUEF.hpp"
|
||||
#include "../../Storage/Tape/Formats/TZX.hpp"
|
||||
#include "../../Storage/Tape/Formats/ZX80O81P.hpp"
|
||||
#include "../../Storage/Tape/Formats/ZXSpectrumTAP.hpp"
|
||||
|
||||
// Target Platform Types
|
||||
#include "../../Storage/TargetPlatforms.hpp"
|
||||
|
||||
using namespace Analyser::Static;
|
||||
|
||||
static Media GetMediaAndPlatforms(const std::string &file_name, TargetPlatform::IntType &potential_platforms) {
|
||||
Media result;
|
||||
namespace {
|
||||
|
||||
std::string get_extension(const std::string &name) {
|
||||
// Get the extension, if any; it will be assumed that extensions are reliable, so an extension is a broad-phase
|
||||
// test as to file format.
|
||||
std::string::size_type final_dot = file_name.find_last_of(".");
|
||||
if(final_dot == std::string::npos) return result;
|
||||
std::string extension = file_name.substr(final_dot + 1);
|
||||
std::string::size_type final_dot = name.find_last_of(".");
|
||||
if(final_dot == std::string::npos) return name;
|
||||
std::string extension = name.substr(final_dot + 1);
|
||||
std::transform(extension.begin(), extension.end(), extension.begin(), ::tolower);
|
||||
return extension;
|
||||
}
|
||||
|
||||
#define Insert(list, class, platforms) \
|
||||
list.emplace_back(new Storage::class(file_name));\
|
||||
}
|
||||
|
||||
static Media GetMediaAndPlatforms(const std::string &file_name, TargetPlatform::IntType &potential_platforms) {
|
||||
Media result;
|
||||
const std::string extension = get_extension(file_name);
|
||||
|
||||
#define InsertInstance(list, instance, platforms) \
|
||||
list.emplace_back(instance);\
|
||||
potential_platforms |= platforms;\
|
||||
TargetPlatform::TypeDistinguisher *distinguisher = dynamic_cast<TargetPlatform::TypeDistinguisher *>(list.back().get());\
|
||||
TargetPlatform::TypeDistinguisher *const distinguisher = dynamic_cast<TargetPlatform::TypeDistinguisher *>(list.back().get());\
|
||||
if(distinguisher) potential_platforms &= distinguisher->target_platform_type();
|
||||
|
||||
#define TryInsert(list, class, platforms) \
|
||||
#define Insert(list, class, platforms, ...) \
|
||||
InsertInstance(list, new Storage::class(__VA_ARGS__), platforms);
|
||||
|
||||
#define TryInsert(list, class, platforms, ...) \
|
||||
try {\
|
||||
Insert(list, class, platforms) \
|
||||
Insert(list, class, platforms, __VA_ARGS__) \
|
||||
} catch(...) {}
|
||||
|
||||
#define Format(ext, list, class, platforms) \
|
||||
if(extension == ext) { \
|
||||
TryInsert(list, class, platforms) \
|
||||
TryInsert(list, class, platforms, file_name) \
|
||||
}
|
||||
|
||||
// 2MG
|
||||
if(extension == "2mg") {
|
||||
// 2MG uses a factory method; defer to it.
|
||||
try {
|
||||
InsertInstance(result.disks, Storage::Disk::Disk2MG::open(file_name), TargetPlatform::DiskII)
|
||||
} catch(...) {}
|
||||
}
|
||||
|
||||
Format("80", result.tapes, Tape::ZX80O81P, TargetPlatform::ZX8081) // 80
|
||||
Format("81", result.tapes, Tape::ZX80O81P, TargetPlatform::ZX8081) // 81
|
||||
Format("a26", result.cartridges, Cartridge::BinaryDump, TargetPlatform::Atari2600) // A26
|
||||
Format("adf", result.disks, Disk::DiskImageHolder<Storage::Disk::AcornADF>, TargetPlatform::Acorn) // ADF
|
||||
Format("adf", result.disks, Disk::DiskImageHolder<Storage::Disk::AcornADF>, TargetPlatform::Acorn) // ADF (Acorn)
|
||||
Format("adf", result.disks, Disk::DiskImageHolder<Storage::Disk::AmigaADF>, TargetPlatform::Amiga) // ADF (Amiga)
|
||||
Format("adl", result.disks, Disk::DiskImageHolder<Storage::Disk::AcornADF>, TargetPlatform::Acorn) // ADL
|
||||
Format("bin", result.cartridges, Cartridge::BinaryDump, TargetPlatform::AllCartridge) // BIN (cartridge dump)
|
||||
Format("cas", result.tapes, Tape::CAS, TargetPlatform::MSX) // CAS
|
||||
Format("cdt", result.tapes, Tape::TZX, TargetPlatform::AmstradCPC) // CDT
|
||||
Format("col", result.cartridges, Cartridge::BinaryDump, TargetPlatform::ColecoVision) // COL
|
||||
Format("col", result.cartridges, Cartridge::BinaryDump, TargetPlatform::Coleco) // COL
|
||||
Format("csw", result.tapes, Tape::CSW, TargetPlatform::AllTape) // CSW
|
||||
Format("d64", result.disks, Disk::DiskImageHolder<Storage::Disk::D64>, TargetPlatform::Commodore) // D64
|
||||
Format("dat", result.mass_storage_devices, MassStorage::DAT, TargetPlatform::Acorn) // DAT
|
||||
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::CPCDSK>,
|
||||
TargetPlatform::AmstradCPC | TargetPlatform::Oric | TargetPlatform::ZXSpectrum) // DSK (Amstrad CPC, etc)
|
||||
Format("dsk", result.disks, Disk::DiskImageHolder<Storage::Disk::AppleDSK>, TargetPlatform::DiskII) // DSK (Apple II)
|
||||
Format("dsk", result.disks, Disk::DiskImageHolder<Storage::Disk::MacintoshIMG>, TargetPlatform::Macintosh) // DSK (Macintosh, floppy disk)
|
||||
Format("dsk", result.mass_storage_devices, MassStorage::HFV, TargetPlatform::Macintosh) // DSK (Macintosh, hard disk)
|
||||
Format("dsk", result.disks, Disk::DiskImageHolder<Storage::Disk::MSXDSK>, TargetPlatform::MSX) // DSK (MSX)
|
||||
Format("dsk", result.mass_storage_devices, MassStorage::HFV, TargetPlatform::Macintosh) // DSK (Macintosh, hard disk, single volume image)
|
||||
Format("dsk", result.mass_storage_devices, MassStorage::DSK, TargetPlatform::Macintosh) // DSK (Macintosh, hard disk, full device image)
|
||||
Format("dsk", result.disks, Disk::DiskImageHolder<Storage::Disk::FAT12>, TargetPlatform::MSX) // DSK (MSX)
|
||||
Format("dsk", result.disks, Disk::DiskImageHolder<Storage::Disk::OricMFMDSK>, TargetPlatform::Oric) // DSK (Oric)
|
||||
Format("g64", result.disks, Disk::DiskImageHolder<Storage::Disk::G64>, TargetPlatform::Commodore) // G64
|
||||
Format( "hfe",
|
||||
result.disks,
|
||||
Disk::DiskImageHolder<Storage::Disk::HFE>,
|
||||
TargetPlatform::Acorn | TargetPlatform::AmstradCPC | TargetPlatform::Commodore | TargetPlatform::Oric)
|
||||
TargetPlatform::Acorn | TargetPlatform::AmstradCPC | TargetPlatform::Commodore | TargetPlatform::Oric | TargetPlatform::ZXSpectrum)
|
||||
// HFE (TODO: switch to AllDisk once the MSX stops being so greedy)
|
||||
Format("img", result.disks, Disk::DiskImageHolder<Storage::Disk::MacintoshIMG>, TargetPlatform::Macintosh) // IMG (DiskCopy 4.2)
|
||||
Format("image", result.disks, Disk::DiskImageHolder<Storage::Disk::MacintoshIMG>, TargetPlatform::Macintosh) // IMG (DiskCopy 4.2)
|
||||
Format("img", result.disks, Disk::DiskImageHolder<Storage::Disk::FAT12>, TargetPlatform::Enterprise) // IMG (Enterprise/MS-DOS style)
|
||||
Format( "ipf",
|
||||
result.disks,
|
||||
Disk::DiskImageHolder<Storage::Disk::IPF>,
|
||||
TargetPlatform::Amiga | TargetPlatform::AtariST | TargetPlatform::AmstradCPC | TargetPlatform::ZXSpectrum) // IPF
|
||||
Format("msa", result.disks, Disk::DiskImageHolder<Storage::Disk::MSA>, TargetPlatform::AtariST) // MSA
|
||||
Format("nib", result.disks, Disk::DiskImageHolder<Storage::Disk::NIB>, TargetPlatform::DiskII) // NIB
|
||||
Format("o", result.tapes, Tape::ZX80O81P, TargetPlatform::ZX8081) // O
|
||||
Format("p", result.tapes, Tape::ZX80O81P, TargetPlatform::ZX8081) // P
|
||||
Format("po", result.disks, Disk::DiskImageHolder<Storage::Disk::AppleDSK>, TargetPlatform::DiskII) // PO
|
||||
Format("po", result.disks, Disk::DiskImageHolder<Storage::Disk::AppleDSK>, TargetPlatform::DiskII) // PO (original Apple II kind)
|
||||
|
||||
// PO (Apple IIgs kind)
|
||||
if(extension == "po") {
|
||||
TryInsert(result.disks, Disk::DiskImageHolder<Storage::Disk::MacintoshIMG>, TargetPlatform::AppleIIgs, file_name, Storage::Disk::MacintoshIMG::FixedType::GCR)
|
||||
}
|
||||
|
||||
Format("p81", result.tapes, Tape::ZX80O81P, TargetPlatform::ZX8081) // P81
|
||||
|
||||
// PRG
|
||||
if(extension == "prg") {
|
||||
// try instantiating as a ROM; failing that accept as a tape
|
||||
try {
|
||||
Insert(result.cartridges, Cartridge::PRG, TargetPlatform::Commodore)
|
||||
Insert(result.cartridges, Cartridge::PRG, TargetPlatform::Commodore, file_name)
|
||||
} catch(...) {
|
||||
try {
|
||||
Insert(result.tapes, Tape::PRG, TargetPlatform::Commodore)
|
||||
Insert(result.tapes, Tape::PRG, TargetPlatform::Commodore, file_name)
|
||||
} catch(...) {}
|
||||
}
|
||||
}
|
||||
@@ -143,7 +195,7 @@ static Media GetMediaAndPlatforms(const std::string &file_name, TargetPlatform::
|
||||
Format( "rom",
|
||||
result.cartridges,
|
||||
Cartridge::BinaryDump,
|
||||
TargetPlatform::AcornElectron | TargetPlatform::ColecoVision | TargetPlatform::MSX) // ROM
|
||||
TargetPlatform::AcornElectron | TargetPlatform::Coleco | 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
|
||||
@@ -151,14 +203,16 @@ static Media GetMediaAndPlatforms(const std::string &file_name, TargetPlatform::
|
||||
Format("stx", result.disks, Disk::DiskImageHolder<Storage::Disk::STX>, TargetPlatform::AtariST) // STX
|
||||
Format("tap", result.tapes, Tape::CommodoreTAP, TargetPlatform::Commodore) // TAP (Commodore)
|
||||
Format("tap", result.tapes, Tape::OricTAP, TargetPlatform::Oric) // TAP (Oric)
|
||||
Format("tap", result.tapes, Tape::ZXSpectrumTAP, TargetPlatform::ZXSpectrum) // TAP (ZX Spectrum)
|
||||
Format("tsx", result.tapes, Tape::TZX, TargetPlatform::MSX) // TSX
|
||||
Format("tzx", result.tapes, Tape::TZX, TargetPlatform::ZX8081) // TZX
|
||||
Format("tzx", result.tapes, Tape::TZX, TargetPlatform::ZX8081 | TargetPlatform::ZXSpectrum) // 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
|
||||
#undef TryInsert
|
||||
#undef InsertInstance
|
||||
|
||||
return result;
|
||||
}
|
||||
@@ -170,34 +224,59 @@ Media Analyser::Static::GetMedia(const std::string &file_name) {
|
||||
|
||||
TargetList Analyser::Static::GetTargets(const std::string &file_name) {
|
||||
TargetList targets;
|
||||
const std::string extension = get_extension(file_name);
|
||||
|
||||
// Check whether the file directly identifies a target; if so then just return that.
|
||||
#define Format(ext, class) \
|
||||
if(extension == ext) { \
|
||||
try { \
|
||||
auto target = Storage::State::class::load(file_name); \
|
||||
if(target) { \
|
||||
targets.push_back(std::move(target)); \
|
||||
return targets; \
|
||||
} \
|
||||
} catch(...) {} \
|
||||
}
|
||||
|
||||
Format("sna", SNA);
|
||||
Format("szx", SZX);
|
||||
Format("z80", Z80);
|
||||
|
||||
#undef TryInsert
|
||||
|
||||
// Otherwise:
|
||||
//
|
||||
// Collect all disks, tapes ROMs, etc as can be extrapolated from this file, forming the
|
||||
// union of all platforms this file might be a target for.
|
||||
TargetPlatform::IntType potential_platforms = 0;
|
||||
Media media = GetMediaAndPlatforms(file_name, potential_platforms);
|
||||
|
||||
// Hand off to platform-specific determination of whether these things are actually compatible and,
|
||||
// if so, how to load them.
|
||||
#define Append(x) {\
|
||||
auto new_targets = x::GetTargets(media, file_name, potential_platforms);\
|
||||
std::move(new_targets.begin(), new_targets.end(), std::back_inserter(targets));\
|
||||
}
|
||||
if(potential_platforms & TargetPlatform::Acorn) Append(Acorn);
|
||||
if(potential_platforms & TargetPlatform::AmstradCPC) Append(AmstradCPC);
|
||||
if(potential_platforms & TargetPlatform::AppleII) Append(AppleII);
|
||||
if(potential_platforms & TargetPlatform::Atari2600) Append(Atari2600);
|
||||
if(potential_platforms & TargetPlatform::AtariST) Append(AtariST);
|
||||
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::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
|
||||
// Hand off to platform-specific determination of whether these
|
||||
// things are actually compatible and, if so, how to load them.
|
||||
#define Append(x) if(potential_platforms & TargetPlatform::x) {\
|
||||
auto new_targets = x::GetTargets(media, file_name, potential_platforms);\
|
||||
std::move(new_targets.begin(), new_targets.end(), std::back_inserter(targets));\
|
||||
}
|
||||
Append(Acorn);
|
||||
Append(AmstradCPC);
|
||||
Append(AppleII);
|
||||
Append(AppleIIgs);
|
||||
Append(Amiga);
|
||||
Append(Atari2600);
|
||||
Append(AtariST);
|
||||
Append(Coleco);
|
||||
Append(Commodore);
|
||||
Append(DiskII);
|
||||
Append(Enterprise);
|
||||
Append(Macintosh);
|
||||
Append(MSX);
|
||||
Append(Oric);
|
||||
Append(Sega);
|
||||
Append(ZX8081);
|
||||
Append(ZXSpectrum);
|
||||
#undef Append
|
||||
|
||||
// Reset any tapes to their initial position
|
||||
// Reset any tapes to their initial position.
|
||||
for(const auto &target : targets) {
|
||||
for(auto &tape : target->media.tapes) {
|
||||
tape->reset();
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
#include "../../Storage/Disk/Disk.hpp"
|
||||
#include "../../Storage/MassStorage/MassStorageDevice.hpp"
|
||||
#include "../../Storage/Tape/Tape.hpp"
|
||||
#include "../../Reflection/Struct.hpp"
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
@@ -23,8 +24,10 @@
|
||||
namespace Analyser {
|
||||
namespace Static {
|
||||
|
||||
struct State;
|
||||
|
||||
/*!
|
||||
A list of disks, tapes and cartridges.
|
||||
A list of disks, tapes and cartridges, and possibly a state snapshot.
|
||||
*/
|
||||
struct Media {
|
||||
std::vector<std::shared_ptr<Storage::Disk::Disk>> disks;
|
||||
@@ -48,13 +51,16 @@ struct Media {
|
||||
};
|
||||
|
||||
/*!
|
||||
A list of disks, tapes and cartridges plus information about the machine to which to attach them and its configuration,
|
||||
and instructions on how to launch the software attached, plus a measure of confidence in this target's correctness.
|
||||
Describes a machine and possibly its state; conventionally subclassed to add other machine-specific configuration fields and any
|
||||
necessary instructions on how to launch any software provided, plus a measure of confidence in this target's correctness.
|
||||
*/
|
||||
struct Target {
|
||||
Target(Machine machine) : machine(machine) {}
|
||||
virtual ~Target() {}
|
||||
|
||||
// This field is entirely optional.
|
||||
std::unique_ptr<Reflection::Struct> state;
|
||||
|
||||
Machine machine;
|
||||
Media media;
|
||||
float confidence = 0.0f;
|
||||
|
||||
95
Analyser/Static/ZXSpectrum/StaticAnalyser.cpp
Normal file
95
Analyser/Static/ZXSpectrum/StaticAnalyser.cpp
Normal file
@@ -0,0 +1,95 @@
|
||||
//
|
||||
// StaticAnalyser.cpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 17/03/2021.
|
||||
// Copyright © 2021 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#include "StaticAnalyser.hpp"
|
||||
|
||||
#include "../../../Storage/Disk/Encodings/MFM/Parser.hpp"
|
||||
#include "../../../Storage/Tape/Parsers/Spectrum.hpp"
|
||||
|
||||
#include "Target.hpp"
|
||||
|
||||
namespace {
|
||||
|
||||
bool IsSpectrumTape(const std::shared_ptr<Storage::Tape::Tape> &tape) {
|
||||
using Parser = Storage::Tape::ZXSpectrum::Parser;
|
||||
Parser parser(Parser::MachineType::ZXSpectrum);
|
||||
|
||||
while(true) {
|
||||
const auto block = parser.find_block(tape);
|
||||
if(!block) break;
|
||||
|
||||
// Check for a Spectrum header block.
|
||||
if(block->type == 0x00) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool IsSpectrumDisk(const std::shared_ptr<Storage::Disk::Disk> &disk) {
|
||||
Storage::Encodings::MFM::Parser parser(true, disk);
|
||||
|
||||
// Get logical sector 1; the Spectrum appears to support various physical
|
||||
// sectors as sector 1.
|
||||
Storage::Encodings::MFM::Sector *boot_sector = nullptr;
|
||||
uint8_t sector_mask = 0;
|
||||
while(!boot_sector) {
|
||||
boot_sector = parser.get_sector(0, 0, sector_mask + 1);
|
||||
sector_mask += 0x40;
|
||||
if(!sector_mask) break;
|
||||
}
|
||||
if(!boot_sector) return false;
|
||||
|
||||
// Test that the contents of the boot sector sum to 3, modulo 256.
|
||||
uint8_t byte_sum = 0;
|
||||
for(auto byte: boot_sector->samples[0]) {
|
||||
byte_sum += byte;
|
||||
}
|
||||
return byte_sum == 3;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Analyser::Static::TargetList Analyser::Static::ZXSpectrum::GetTargets(const Media &media, const std::string &, TargetPlatform::IntType) {
|
||||
TargetList destination;
|
||||
auto target = std::make_unique<Target>();
|
||||
target->confidence = 0.5;
|
||||
|
||||
if(!media.tapes.empty()) {
|
||||
bool has_spectrum_tape = false;
|
||||
for(auto &tape: media.tapes) {
|
||||
has_spectrum_tape |= IsSpectrumTape(tape);
|
||||
}
|
||||
|
||||
if(has_spectrum_tape) {
|
||||
target->media.tapes = media.tapes;
|
||||
}
|
||||
}
|
||||
|
||||
if(!media.disks.empty()) {
|
||||
bool has_spectrum_disk = false;
|
||||
|
||||
for(auto &disk: media.disks) {
|
||||
has_spectrum_disk |= IsSpectrumDisk(disk);
|
||||
}
|
||||
|
||||
if(has_spectrum_disk) {
|
||||
target->media.disks = media.disks;
|
||||
target->model = Target::Model::Plus3;
|
||||
}
|
||||
}
|
||||
|
||||
// If any media survived, add the target.
|
||||
if(!target->media.empty()) {
|
||||
target->should_hold_enter = true; // To force entry into the 'loader' and thereby load the media.
|
||||
destination.push_back(std::move(target));
|
||||
}
|
||||
|
||||
return destination;
|
||||
}
|
||||
26
Analyser/Static/ZXSpectrum/StaticAnalyser.hpp
Normal file
26
Analyser/Static/ZXSpectrum/StaticAnalyser.hpp
Normal file
@@ -0,0 +1,26 @@
|
||||
//
|
||||
// StaticAnalyser.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 17/03/2021.
|
||||
// Copyright © 2021 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef Analyser_Static_ZXSpectrum_StaticAnalyser_hpp
|
||||
#define Analyser_Static_ZXSpectrum_StaticAnalyser_hpp
|
||||
|
||||
#include "../StaticAnalyser.hpp"
|
||||
#include "../../../Storage/TargetPlatforms.hpp"
|
||||
#include <string>
|
||||
|
||||
namespace Analyser {
|
||||
namespace Static {
|
||||
namespace ZXSpectrum {
|
||||
|
||||
TargetList GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* StaticAnalyser_hpp */
|
||||
45
Analyser/Static/ZXSpectrum/Target.hpp
Normal file
45
Analyser/Static/ZXSpectrum/Target.hpp
Normal file
@@ -0,0 +1,45 @@
|
||||
//
|
||||
// Target.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 18/03/2021.
|
||||
// Copyright © 2021 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef Analyser_Static_ZXSpectrum_Target_h
|
||||
#define Analyser_Static_ZXSpectrum_Target_h
|
||||
|
||||
#include "../../../Reflection/Enum.hpp"
|
||||
#include "../../../Reflection/Struct.hpp"
|
||||
#include "../StaticAnalyser.hpp"
|
||||
|
||||
namespace Analyser {
|
||||
namespace Static {
|
||||
namespace ZXSpectrum {
|
||||
|
||||
struct Target: public ::Analyser::Static::Target, public Reflection::StructImpl<Target> {
|
||||
ReflectableEnum(Model,
|
||||
SixteenK,
|
||||
FortyEightK,
|
||||
OneTwoEightK,
|
||||
Plus2,
|
||||
Plus2a,
|
||||
Plus3,
|
||||
);
|
||||
|
||||
Model model = Model::Plus2;
|
||||
bool should_hold_enter = false;
|
||||
|
||||
Target(): Analyser::Static::Target(Machine::ZXSpectrum) {
|
||||
if(needs_declare()) {
|
||||
DeclareField(model);
|
||||
AnnounceEnum(Model);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* Target_h */
|
||||
@@ -10,7 +10,10 @@
|
||||
#define ClockReceiver_hpp
|
||||
|
||||
#include "ForceInline.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdint>
|
||||
#include <limits>
|
||||
|
||||
/*
|
||||
Informal pattern for all classes that run from a clock cycle:
|
||||
@@ -136,8 +139,11 @@ template <class T> class WrappedInt {
|
||||
forceinline constexpr bool operator !() const { return !length_; }
|
||||
// bool operator () is not supported because it offers an implicit cast to int, which is prone silently to permit misuse
|
||||
|
||||
/// @returns The underlying int, cast to an integral type of your choosing.
|
||||
template<typename Type = IntType> forceinline constexpr Type as() const { return Type(length_); }
|
||||
/// @returns The underlying int, converted to an integral type of your choosing, clamped to that int's range.
|
||||
template<typename Type = IntType> forceinline constexpr Type as() const {
|
||||
const auto clamped = std::clamp(length_, IntType(std::numeric_limits<Type>::min()), IntType(std::numeric_limits<Type>::max()));
|
||||
return Type(clamped);
|
||||
}
|
||||
|
||||
/// @returns The underlying int, in its native form.
|
||||
forceinline constexpr IntType as_integral() const { return length_; }
|
||||
@@ -176,6 +182,9 @@ class Cycles: public WrappedInt<Cycles> {
|
||||
public:
|
||||
forceinline constexpr Cycles(IntType l) noexcept : WrappedInt<Cycles>(l) {}
|
||||
forceinline constexpr Cycles() noexcept : WrappedInt<Cycles>() {}
|
||||
forceinline static constexpr Cycles max() {
|
||||
return Cycles(std::numeric_limits<IntType>::max());
|
||||
}
|
||||
|
||||
private:
|
||||
friend WrappedInt;
|
||||
@@ -195,6 +204,9 @@ class HalfCycles: public WrappedInt<HalfCycles> {
|
||||
public:
|
||||
forceinline constexpr HalfCycles(IntType l) noexcept : WrappedInt<HalfCycles>(l) {}
|
||||
forceinline constexpr HalfCycles() noexcept : WrappedInt<HalfCycles>() {}
|
||||
forceinline static constexpr HalfCycles max() {
|
||||
return HalfCycles(std::numeric_limits<IntType>::max());
|
||||
}
|
||||
|
||||
forceinline constexpr HalfCycles(const Cycles &cycles) noexcept : WrappedInt<HalfCycles>(cycles.as_integral() * 2) {}
|
||||
|
||||
@@ -205,7 +217,7 @@ class HalfCycles: public WrappedInt<HalfCycles> {
|
||||
|
||||
/*!
|
||||
Severs from @c this the effect of dividing by @c divisor; @c this will end up with
|
||||
the value of @c this modulo @c divisor and @c divided by @c divisor is returned.
|
||||
the value of @c this modulo @c divisor . @c this divided by @c divisor is returned.
|
||||
*/
|
||||
forceinline Cycles divide_cycles(const Cycles &divisor) {
|
||||
const HalfCycles half_divisor = HalfCycles(divisor);
|
||||
@@ -214,6 +226,15 @@ class HalfCycles: public WrappedInt<HalfCycles> {
|
||||
return result;
|
||||
}
|
||||
|
||||
/*!
|
||||
Equivalent to @c divide_cycles(Cycles(1)) but faster.
|
||||
*/
|
||||
forceinline Cycles divide_cycles() {
|
||||
const Cycles result(length_ >> 1);
|
||||
length_ &= 1;
|
||||
return result;
|
||||
}
|
||||
|
||||
private:
|
||||
friend WrappedInt;
|
||||
void fill(Cycles &result) {
|
||||
|
||||
48
ClockReceiver/DeferredValue.hpp
Normal file
48
ClockReceiver/DeferredValue.hpp
Normal file
@@ -0,0 +1,48 @@
|
||||
//
|
||||
// DeferredValue.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 07/08/2021.
|
||||
// Copyright © 2021 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef DeferredValue_h
|
||||
#define DeferredValue_h
|
||||
|
||||
/*!
|
||||
Provides storage for a single deferred value: one with a current value and a certain number
|
||||
of future values.
|
||||
*/
|
||||
template <int DeferredDepth, typename ValueT> class DeferredValue {
|
||||
private:
|
||||
static_assert(sizeof(ValueT) <= 4);
|
||||
|
||||
constexpr int elements_per_uint32 = sizeof(uint32_t) / sizeof(ValueT);
|
||||
constexpr int unit_shift = sizeof(ValueT) * 8;
|
||||
constexpr int insert_shift = (DeferredDepth & (elements_per_uint32 - 1)) * unit_shift;
|
||||
constexpr uint32_t insert_mask = ~(0xffff'ffff << insert_shift);
|
||||
|
||||
std::array<uint32_t, (DeferredDepth + elements_per_uint32 - 1) / elements_per_uint32> backlog;
|
||||
|
||||
public:
|
||||
/// @returns the current value.
|
||||
ValueT value() const {
|
||||
return uint8_t(backlog[0]);
|
||||
}
|
||||
|
||||
/// Advances to the next enqueued value.
|
||||
void advance() {
|
||||
for(size_t c = 0; c < backlog.size() - 1; c--) {
|
||||
backlog[c] = (backlog[c] >> unit_shift) | (backlog[c+1] << (32 - unit_shift));
|
||||
}
|
||||
backlog[backlog.size() - 1] >>= unit_shift;
|
||||
}
|
||||
|
||||
/// Inserts a new value, replacing whatever is currently at the end of the queue.
|
||||
void insert(ValueT value) {
|
||||
backlog[DeferredDepth / elements_per_uint32] =
|
||||
(backlog[DeferredDepth / elements_per_uint32] & insert_mask) | (value << insert_shift);
|
||||
}
|
||||
};
|
||||
|
||||
#endif /* DeferredValue_h */
|
||||
@@ -9,7 +9,9 @@
|
||||
#ifndef JustInTime_h
|
||||
#define JustInTime_h
|
||||
|
||||
#include "ClockReceiver.hpp"
|
||||
#include "../Concurrency/AsyncTaskQueue.hpp"
|
||||
#include "ClockingHintSource.hpp"
|
||||
#include "ForceInline.hpp"
|
||||
|
||||
/*!
|
||||
@@ -21,44 +23,150 @@
|
||||
|
||||
Machines that accumulate HalfCycle time but supply to a Cycle-counted device may supply a
|
||||
separate @c TargetTimeScale at template declaration.
|
||||
|
||||
If the held object implements get_next_sequence_point() then it'll be used to flush implicitly
|
||||
as and when sequence points are hit. Callers can use will_flush() to predict these.
|
||||
|
||||
If the held object is a subclass of ClockingHint::Source, this template will register as an
|
||||
observer and potentially stop clocking or stop delaying clocking until just-in-time references
|
||||
as directed.
|
||||
|
||||
TODO: incorporate and codify AsyncJustInTimeActor.
|
||||
*/
|
||||
template <class T, int multiplier = 1, int divider = 1, class LocalTimeScale = HalfCycles, class TargetTimeScale = LocalTimeScale> class JustInTimeActor {
|
||||
template <class T, class LocalTimeScale = HalfCycles, int multiplier = 1, int divider = 1> class JustInTimeActor:
|
||||
public ClockingHint::Observer {
|
||||
private:
|
||||
/*!
|
||||
A std::unique_ptr deleter which causes an update_sequence_point to occur on the actor supplied
|
||||
to it at construction if it implements get_next_sequence_point(). Otherwise destruction is a no-op.
|
||||
|
||||
**Does not delete the object.**
|
||||
|
||||
This is used by the -> operators below, which provide a unique pointer to the enclosed object and
|
||||
update their sequence points upon its destruction — i.e. after the caller has made whatever call
|
||||
or calls as were relevant to the enclosed object.
|
||||
*/
|
||||
class SequencePointAwareDeleter {
|
||||
public:
|
||||
explicit SequencePointAwareDeleter(JustInTimeActor<T, LocalTimeScale, multiplier, divider> *actor) noexcept
|
||||
: actor_(actor) {}
|
||||
|
||||
forceinline void operator ()(const T *const) const {
|
||||
if constexpr (has_sequence_points<T>::value) {
|
||||
actor_->update_sequence_point();
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
JustInTimeActor<T, LocalTimeScale, multiplier, divider> *const actor_;
|
||||
};
|
||||
|
||||
// This block of SFINAE determines whether objects of type T accepts Cycles or HalfCycles.
|
||||
using HalfRunFor = void (T::*const)(HalfCycles);
|
||||
static uint8_t half_sig(...);
|
||||
static uint16_t half_sig(HalfRunFor);
|
||||
using TargetTimeScale =
|
||||
std::conditional_t<
|
||||
sizeof(half_sig(&T::run_for)) == sizeof(uint16_t),
|
||||
HalfCycles,
|
||||
Cycles>;
|
||||
|
||||
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)...) {}
|
||||
template<typename... Args> JustInTimeActor(Args&&... args) : object_(std::forward<Args>(args)...) {
|
||||
if constexpr (std::is_base_of<ClockingHint::Source, T>::value) {
|
||||
object_.set_clocking_hint_observer(this);
|
||||
}
|
||||
}
|
||||
|
||||
/// Adds time to the actor.
|
||||
forceinline void operator += (const LocalTimeScale &rhs) {
|
||||
///
|
||||
/// @returns @c true if adding time caused a flush; @c false otherwise.
|
||||
forceinline bool operator += (LocalTimeScale rhs) {
|
||||
if constexpr (std::is_base_of<ClockingHint::Source, T>::value) {
|
||||
if(clocking_preference_ == ClockingHint::Preference::None) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if constexpr (multiplier != 1) {
|
||||
time_since_update_ += rhs * multiplier;
|
||||
} else {
|
||||
time_since_update_ += rhs;
|
||||
}
|
||||
is_flushed_ = false;
|
||||
|
||||
if constexpr (std::is_base_of<ClockingHint::Source, T>::value) {
|
||||
if (clocking_preference_ == ClockingHint::Preference::RealTime) {
|
||||
flush();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if constexpr (has_sequence_points<T>::value) {
|
||||
time_until_event_ -= rhs * multiplier;
|
||||
if(time_until_event_ <= LocalTimeScale(0)) {
|
||||
time_overrun_ = time_until_event_ / divider;
|
||||
flush();
|
||||
update_sequence_point();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// Flushes all accumulated time and returns a pointer to the included object.
|
||||
forceinline T *operator->() {
|
||||
///
|
||||
/// If this object provides sequence points, checks for changes to the next
|
||||
/// sequence point upon deletion of the pointer.
|
||||
[[nodiscard]] forceinline auto operator->() {
|
||||
flush();
|
||||
return &object_;
|
||||
return std::unique_ptr<T, SequencePointAwareDeleter>(&object_, SequencePointAwareDeleter(this));
|
||||
}
|
||||
|
||||
/// Acts exactly as per the standard ->, but preserves constness.
|
||||
forceinline const T *operator->() const {
|
||||
auto non_const_this = const_cast<JustInTimeActor<T, multiplier, divider, LocalTimeScale, TargetTimeScale> *>(this);
|
||||
///
|
||||
/// Despite being const, this will flush the object and, if relevant, update the next sequence point.
|
||||
[[nodiscard]] forceinline auto operator -> () const {
|
||||
auto non_const_this = const_cast<JustInTimeActor<T, LocalTimeScale, multiplier, divider> *>(this);
|
||||
non_const_this->flush();
|
||||
return std::unique_ptr<const T, SequencePointAwareDeleter>(&object_, SequencePointAwareDeleter(non_const_this));
|
||||
}
|
||||
|
||||
/// @returns a pointer to the included object, without flushing time.
|
||||
[[nodiscard]] forceinline T *last_valid() {
|
||||
return &object_;
|
||||
}
|
||||
|
||||
/// Returns a pointer to the included object without flushing time.
|
||||
forceinline T *last_valid() {
|
||||
/// @returns a const pointer to the included object, without flushing time.
|
||||
[[nodiscard]] forceinline const T *last_valid() const {
|
||||
return &object_;
|
||||
}
|
||||
|
||||
/// @returns the amount of time since the object was last flushed, in the target time scale.
|
||||
[[nodiscard]] forceinline TargetTimeScale time_since_flush() const {
|
||||
if constexpr (divider == 1) {
|
||||
return time_since_update_;
|
||||
}
|
||||
return TargetTimeScale(time_since_update_.as_integral() / divider);
|
||||
}
|
||||
|
||||
/// @returns the amount of time since the object was last flushed, plus the local time scale @c offset,
|
||||
/// converted to the target time scale.
|
||||
[[nodiscard]] forceinline TargetTimeScale time_since_flush(LocalTimeScale offset) const {
|
||||
if constexpr (divider == 1) {
|
||||
return time_since_update_ + offset;
|
||||
}
|
||||
return TargetTimeScale((time_since_update_ + offset).as_integral() / divider);
|
||||
}
|
||||
|
||||
/// Flushes all accumulated time.
|
||||
///
|
||||
/// This does not affect this actor's record of when the next sequence point will occur.
|
||||
forceinline void flush() {
|
||||
if(!is_flushed_) {
|
||||
is_flushed_ = true;
|
||||
did_flush_ = is_flushed_ = true;
|
||||
if constexpr (divider == 1) {
|
||||
const auto duration = time_since_update_.template flush<TargetTimeScale>();
|
||||
object_.run_for(duration);
|
||||
@@ -70,56 +178,96 @@ template <class T, int multiplier = 1, int divider = 1, class LocalTimeScale = H
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
T object_;
|
||||
LocalTimeScale time_since_update_;
|
||||
bool is_flushed_ = true;
|
||||
};
|
||||
/// Indicates whether a flush has occurred since the last call to did_flush().
|
||||
[[nodiscard]] forceinline bool did_flush() {
|
||||
const bool did_flush = did_flush_;
|
||||
did_flush_ = false;
|
||||
return did_flush;
|
||||
}
|
||||
|
||||
/*!
|
||||
A RealTimeActor presents the same interface as a JustInTimeActor but doesn't defer work.
|
||||
Time added will be performed immediately.
|
||||
/// @returns a number in the range [-max, 0] indicating the offset of the most recent sequence
|
||||
/// point from the final time at the end of the += that triggered the sequence point.
|
||||
[[nodiscard]] forceinline LocalTimeScale last_sequence_point_overrun() {
|
||||
return time_overrun_;
|
||||
}
|
||||
|
||||
Its primary purpose is to allow consumers to remain flexible in their scheduling.
|
||||
*/
|
||||
template <class T, int multiplier = 1, int divider = 1, class LocalTimeScale = HalfCycles, class TargetTimeScale = LocalTimeScale> class RealTimeActor {
|
||||
public:
|
||||
template<typename... Args> RealTimeActor(Args&&... args) : object_(std::forward<Args>(args)...) {}
|
||||
/// @returns the number of cycles until the next sequence-point-based flush, if the embedded object
|
||||
/// supports sequence points; @c LocalTimeScale() otherwise.
|
||||
[[nodiscard]] LocalTimeScale cycles_until_implicit_flush() const {
|
||||
return time_until_event_ / divider;
|
||||
}
|
||||
|
||||
forceinline void operator += (const LocalTimeScale &rhs) {
|
||||
/// Indicates whether a sequence-point-caused flush will occur if the specified period is added.
|
||||
[[nodiscard]] forceinline bool will_flush(LocalTimeScale rhs) const {
|
||||
if constexpr (!has_sequence_points<T>::value) {
|
||||
return false;
|
||||
}
|
||||
return rhs >= time_until_event_;
|
||||
}
|
||||
|
||||
/// Indicates the amount of time, in the local time scale, until the first local slot that falls wholly
|
||||
/// after @c duration, if that delay were to occur in @c offset units of time from now.
|
||||
[[nodiscard]] forceinline LocalTimeScale back_map(TargetTimeScale duration, TargetTimeScale offset) const {
|
||||
// A 1:1 mapping is easy.
|
||||
if constexpr (multiplier == 1 && divider == 1) {
|
||||
object_.run_for(TargetTimeScale(rhs));
|
||||
return;
|
||||
return duration;
|
||||
}
|
||||
|
||||
if constexpr (multiplier == 1) {
|
||||
accumulated_time_ += rhs;
|
||||
} else {
|
||||
accumulated_time_ += rhs * multiplier;
|
||||
}
|
||||
// Work out when this query is placed, and the time to which it relates
|
||||
const auto base = time_since_update_ + offset * divider;
|
||||
const auto target = base + duration * divider;
|
||||
|
||||
if constexpr (divider == 1) {
|
||||
const auto duration = accumulated_time_.template flush<TargetTimeScale>();
|
||||
object_.run_for(duration);
|
||||
} else {
|
||||
const auto duration = accumulated_time_.template divide<TargetTimeScale>(LocalTimeScale(divider));
|
||||
if(duration > TargetTimeScale(0))
|
||||
object_.run_for(duration);
|
||||
// Figure out the number of whole input steps that is required to get
|
||||
// past target, and subtract the number of whole input steps necessary
|
||||
// to get to base.
|
||||
const auto steps_to_base = base.as_integral() / multiplier;
|
||||
const auto steps_to_target = (target.as_integral() + divider - 1) / multiplier;
|
||||
|
||||
return LocalTimeScale(steps_to_target - steps_to_base);
|
||||
}
|
||||
|
||||
/// Updates this template's record of the next sequence point.
|
||||
void update_sequence_point() {
|
||||
if constexpr (has_sequence_points<T>::value) {
|
||||
// Keep a fast path where no conversions will be applied; if conversions are
|
||||
// going to be applied then do a direct max -> max translation rather than
|
||||
// allowing the arithmetic to overflow.
|
||||
if constexpr (divider == 1 && std::is_same_v<LocalTimeScale, TargetTimeScale>) {
|
||||
time_until_event_ = object_.get_next_sequence_point();
|
||||
} else {
|
||||
const auto time = object_.get_next_sequence_point();
|
||||
if(time == TargetTimeScale::max()) {
|
||||
time_until_event_ = LocalTimeScale::max();
|
||||
} else {
|
||||
time_until_event_ = time * divider;
|
||||
}
|
||||
}
|
||||
assert(time_until_event_ > LocalTimeScale(0));
|
||||
}
|
||||
}
|
||||
|
||||
forceinline T *operator->() { return &object_; }
|
||||
forceinline const T *operator->() const { return &object_; }
|
||||
forceinline T *last_valid() { return &object_; }
|
||||
forceinline void flush() {}
|
||||
/// @returns A cached copy of the object's clocking preference.
|
||||
ClockingHint::Preference clocking_preference() const {
|
||||
return clocking_preference_;
|
||||
}
|
||||
|
||||
private:
|
||||
T object_;
|
||||
LocalTimeScale accumulated_time_;
|
||||
LocalTimeScale time_since_update_, time_until_event_, time_overrun_;
|
||||
bool is_flushed_ = true;
|
||||
bool did_flush_ = false;
|
||||
|
||||
template <typename S, typename = void> struct has_sequence_points : std::false_type {};
|
||||
template <typename S> struct has_sequence_points<S, decltype(void(std::declval<S &>().get_next_sequence_point()))> : std::true_type {};
|
||||
|
||||
ClockingHint::Preference clocking_preference_ = ClockingHint::Preference::JustInTime;
|
||||
void set_component_prefers_clocking(ClockingHint::Source *, ClockingHint::Preference clocking) {
|
||||
clocking_preference_ = clocking;
|
||||
}
|
||||
};
|
||||
|
||||
/*!
|
||||
A AsyncJustInTimeActor acts like a JustInTimeActor but additionally contains an AsyncTaskQueue.
|
||||
An 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.
|
||||
*/
|
||||
|
||||
@@ -276,7 +276,10 @@ void WD1770::posit_event(int new_event_type) {
|
||||
goto test_type1_type;
|
||||
|
||||
begin_type1_spin_up:
|
||||
if((command_&0x08) || get_drive().get_motor_on()) goto test_type1_type;
|
||||
if((command_&0x08) || get_drive().get_motor_on()) {
|
||||
set_motor_on(true);
|
||||
goto test_type1_type;
|
||||
}
|
||||
SPIN_UP();
|
||||
|
||||
test_type1_type:
|
||||
@@ -387,7 +390,10 @@ void WD1770::posit_event(int new_event_type) {
|
||||
distance_into_section_ = 0;
|
||||
|
||||
if((command_&0x08) && has_motor_on_line()) goto test_type2_delay;
|
||||
if(!has_motor_on_line() && !has_head_load_line()) goto test_type2_delay;
|
||||
if(!has_motor_on_line() && !has_head_load_line()) {
|
||||
if(has_motor_on_line()) set_motor_on(true);
|
||||
goto test_type2_delay;
|
||||
}
|
||||
|
||||
if(has_motor_on_line()) goto begin_type2_spin_up;
|
||||
goto begin_type2_load_head;
|
||||
|
||||
@@ -10,8 +10,6 @@
|
||||
#define _522_hpp
|
||||
|
||||
#include <cstdint>
|
||||
#include <typeinfo>
|
||||
#include <cstdio>
|
||||
|
||||
#include "Implementation/6522Storage.hpp"
|
||||
|
||||
@@ -37,22 +35,24 @@ enum Line {
|
||||
class PortHandler {
|
||||
public:
|
||||
/// Requests the current input value of @c port from the port handler.
|
||||
uint8_t get_port_input([[maybe_unused]] Port port) { return 0xff; }
|
||||
uint8_t get_port_input([[maybe_unused]] Port port) {
|
||||
return 0xff;
|
||||
}
|
||||
|
||||
/// Sets the current output value of @c port and provides @c direction_mask, indicating which pins are marked as output.
|
||||
void set_port_output([[maybe_unused]] Port port, [[maybe_unused]] uint8_t value, [[maybe_unused]] uint8_t direction_mask) {}
|
||||
void set_port_output([[maybe_unused]] Port port, [[maybe_unused]] uint8_t value, [[maybe_unused]] uint8_t direction_mask) {}
|
||||
|
||||
/// Sets the current logical output level for line @c line on port @c port.
|
||||
void set_control_line_output([[maybe_unused]] Port port, [[maybe_unused]] Line line, [[maybe_unused]] bool value) {}
|
||||
void set_control_line_output([[maybe_unused]] Port port, [[maybe_unused]] Line line, [[maybe_unused]] bool value) {}
|
||||
|
||||
/// Sets the current logical value of the interrupt line.
|
||||
void set_interrupt_status([[maybe_unused]] bool status) {}
|
||||
void set_interrupt_status([[maybe_unused]] bool status) {}
|
||||
|
||||
/// Provides a measure of time elapsed between other calls.
|
||||
void run_for([[maybe_unused]] HalfCycles duration) {}
|
||||
void run_for([[maybe_unused]] HalfCycles duration) {}
|
||||
|
||||
/// Receives passed-on flush() calls from the 6522.
|
||||
void flush() {}
|
||||
void flush() {}
|
||||
};
|
||||
|
||||
/*!
|
||||
@@ -88,9 +88,9 @@ class IRQDelegatePortHandler: public PortHandler {
|
||||
Consumers should derive their own curiously-recurring-template-pattern subclass,
|
||||
implementing bus communications as required.
|
||||
*/
|
||||
template <class T> class MOS6522: public MOS6522Storage {
|
||||
template <class BusHandlerT> class MOS6522: public MOS6522Storage {
|
||||
public:
|
||||
MOS6522(T &bus_handler) noexcept : bus_handler_(bus_handler) {}
|
||||
MOS6522(BusHandlerT &bus_handler) noexcept : bus_handler_(bus_handler) {}
|
||||
MOS6522(const MOS6522 &) = delete;
|
||||
|
||||
/*! Sets a register value. */
|
||||
@@ -100,7 +100,7 @@ template <class T> class MOS6522: public MOS6522Storage {
|
||||
uint8_t read(int address);
|
||||
|
||||
/*! @returns the bus handler. */
|
||||
T &bus_handler();
|
||||
BusHandlerT &bus_handler();
|
||||
|
||||
/// Sets the input value of line @c line on port @c port.
|
||||
void set_control_line_input(Port port, Line line, bool value);
|
||||
@@ -123,18 +123,19 @@ template <class T> class MOS6522: public MOS6522Storage {
|
||||
void shift_in();
|
||||
void shift_out();
|
||||
|
||||
T &bus_handler_;
|
||||
BusHandlerT &bus_handler_;
|
||||
HalfCycles time_since_bus_handler_call_;
|
||||
|
||||
void access(int address);
|
||||
|
||||
uint8_t get_port_input(Port port, uint8_t output_mask, uint8_t output);
|
||||
uint8_t get_port_input(Port port, uint8_t output_mask, uint8_t output, uint8_t timer_mask);
|
||||
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();
|
||||
void evaluate_port_b_output();
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -8,6 +8,10 @@
|
||||
|
||||
#include "../../../Outputs/Log.hpp"
|
||||
|
||||
// As-yet unimplemented (incomplete list):
|
||||
//
|
||||
// PB6 count-down mode for timer 2.
|
||||
|
||||
namespace MOS {
|
||||
namespace MOS6522 {
|
||||
|
||||
@@ -34,18 +38,18 @@ template <typename T> void MOS6522<T>::write(int address, uint8_t value) {
|
||||
address &= 0xf;
|
||||
access(address);
|
||||
switch(address) {
|
||||
case 0x0: // Write Port B.
|
||||
case 0x0: // Write Port B. ('ORB')
|
||||
// Store locally and communicate outwards.
|
||||
registers_.output[1] = value;
|
||||
|
||||
bus_handler_.run_for(time_since_bus_handler_call_.flush<HalfCycles>());
|
||||
bus_handler_.set_port_output(Port::B, value, registers_.data_direction[1]);
|
||||
evaluate_port_b_output();
|
||||
|
||||
registers_.interrupt_flags &= ~(InterruptFlag::CB1ActiveEdge | ((registers_.peripheral_control&0x20) ? 0 : InterruptFlag::CB2ActiveEdge));
|
||||
reevaluate_interrupts();
|
||||
break;
|
||||
case 0xf:
|
||||
case 0x1: // Write Port A.
|
||||
case 0x1: // Write Port A. ('ORA')
|
||||
registers_.output[0] = value;
|
||||
|
||||
bus_handler_.run_for(time_since_bus_handler_call_.flush<HalfCycles>());
|
||||
@@ -59,28 +63,44 @@ template <typename T> void MOS6522<T>::write(int address, uint8_t value) {
|
||||
reevaluate_interrupts();
|
||||
break;
|
||||
|
||||
case 0x2: // Port B direction.
|
||||
case 0x2: // Port B direction ('DDRB').
|
||||
registers_.data_direction[1] = value;
|
||||
break;
|
||||
case 0x3: // Port A direction.
|
||||
case 0x3: // Port A direction ('DDRA').
|
||||
registers_.data_direction[0] = value;
|
||||
break;
|
||||
|
||||
// Timer 1
|
||||
case 0x6: case 0x4: registers_.timer_latch[0] = (registers_.timer_latch[0]&0xff00) | value; break;
|
||||
case 0x5: case 0x7:
|
||||
case 0x6: case 0x4: // ('T1L-L' and 'T1C-L')
|
||||
registers_.timer_latch[0] = (registers_.timer_latch[0]&0xff00) | value;
|
||||
break;
|
||||
case 0x7: // Timer 1 latch, high ('T1L-H').
|
||||
registers_.timer_latch[0] = (registers_.timer_latch[0]&0x00ff) | uint16_t(value << 8);
|
||||
registers_.interrupt_flags &= ~InterruptFlag::Timer1;
|
||||
if(address == 0x05) {
|
||||
registers_.next_timer[0] = registers_.timer_latch[0];
|
||||
timer_is_running_[0] = true;
|
||||
break;
|
||||
case 0x5: // Timer 1 counter, high ('T1C-H').
|
||||
// Fill latch.
|
||||
registers_.timer_latch[0] = (registers_.timer_latch[0]&0x00ff) | uint16_t(value << 8);
|
||||
|
||||
// Restart timer.
|
||||
registers_.next_timer[0] = registers_.timer_latch[0];
|
||||
timer_is_running_[0] = true;
|
||||
|
||||
// If PB7 output mode is active, set it low.
|
||||
if(timer1_is_controlling_pb7()) {
|
||||
registers_.timer_port_b_output &= 0x7f;
|
||||
evaluate_port_b_output();
|
||||
}
|
||||
|
||||
// Clear existing interrupt flag.
|
||||
registers_.interrupt_flags &= ~InterruptFlag::Timer1;
|
||||
reevaluate_interrupts();
|
||||
break;
|
||||
|
||||
// Timer 2
|
||||
case 0x8: registers_.timer_latch[1] = value; break;
|
||||
case 0x9:
|
||||
case 0x8: // ('T2C-L')
|
||||
registers_.timer_latch[1] = value;
|
||||
break;
|
||||
case 0x9: // ('T2C-H')
|
||||
registers_.interrupt_flags &= ~InterruptFlag::Timer2;
|
||||
registers_.next_timer[1] = registers_.timer_latch[1] | uint16_t(value << 8);
|
||||
timer_is_running_[1] = true;
|
||||
@@ -88,7 +108,7 @@ template <typename T> void MOS6522<T>::write(int address, uint8_t value) {
|
||||
break;
|
||||
|
||||
// Shift
|
||||
case 0xa:
|
||||
case 0xa: // ('SR')
|
||||
registers_.shift = value;
|
||||
shift_bits_remaining_ = 8;
|
||||
registers_.interrupt_flags &= ~InterruptFlag::ShiftRegister;
|
||||
@@ -96,11 +116,18 @@ template <typename T> void MOS6522<T>::write(int address, uint8_t value) {
|
||||
break;
|
||||
|
||||
// Control
|
||||
case 0xb:
|
||||
case 0xb: // Auxiliary control ('ACR').
|
||||
registers_.auxiliary_control = value;
|
||||
evaluate_cb2_output();
|
||||
|
||||
// This is a bit of a guess: reset the timer-based PB7 output to its default high level
|
||||
// any timer that timer-linked PB7 output is disabled.
|
||||
if(!timer1_is_controlling_pb7()) {
|
||||
registers_.timer_port_b_output |= 0x80;
|
||||
}
|
||||
evaluate_port_b_output();
|
||||
break;
|
||||
case 0xc: {
|
||||
case 0xc: { // Peripheral control ('PCR').
|
||||
// const auto old_peripheral_control = registers_.peripheral_control;
|
||||
registers_.peripheral_control = value;
|
||||
|
||||
@@ -141,11 +168,11 @@ template <typename T> void MOS6522<T>::write(int address, uint8_t value) {
|
||||
} break;
|
||||
|
||||
// Interrupt control
|
||||
case 0xd:
|
||||
case 0xd: // Interrupt flag regiser ('IFR').
|
||||
registers_.interrupt_flags &= ~value;
|
||||
reevaluate_interrupts();
|
||||
break;
|
||||
case 0xe:
|
||||
case 0xe: // Interrupt enable register ('IER').
|
||||
if(value&0x80)
|
||||
registers_.interrupt_enable |= value;
|
||||
else
|
||||
@@ -159,54 +186,55 @@ template <typename T> uint8_t MOS6522<T>::read(int address) {
|
||||
address &= 0xf;
|
||||
access(address);
|
||||
switch(address) {
|
||||
case 0x0:
|
||||
case 0x0: // Read Port B ('IRB').
|
||||
registers_.interrupt_flags &= ~(InterruptFlag::CB1ActiveEdge | InterruptFlag::CB2ActiveEdge);
|
||||
reevaluate_interrupts();
|
||||
return get_port_input(Port::B, registers_.data_direction[1], registers_.output[1]);
|
||||
return get_port_input(Port::B, registers_.data_direction[1], registers_.output[1], registers_.auxiliary_control & 0x80);
|
||||
case 0xf:
|
||||
case 0x1:
|
||||
case 0x1: // Read Port A ('IRA').
|
||||
registers_.interrupt_flags &= ~(InterruptFlag::CA1ActiveEdge | InterruptFlag::CA2ActiveEdge);
|
||||
reevaluate_interrupts();
|
||||
return get_port_input(Port::A, registers_.data_direction[0], registers_.output[0]);
|
||||
return get_port_input(Port::A, registers_.data_direction[0], registers_.output[0], 0);
|
||||
|
||||
case 0x2: return registers_.data_direction[1];
|
||||
case 0x3: return registers_.data_direction[0];
|
||||
case 0x2: return registers_.data_direction[1]; // Port B direction ('DDRB').
|
||||
case 0x3: return registers_.data_direction[0]; // Port A direction ('DDRA').
|
||||
|
||||
// Timer 1
|
||||
case 0x4:
|
||||
case 0x4: // Timer 1 low-order latches ('T1L-L').
|
||||
registers_.interrupt_flags &= ~InterruptFlag::Timer1;
|
||||
reevaluate_interrupts();
|
||||
return registers_.timer[0] & 0x00ff;
|
||||
case 0x5: return registers_.timer[0] >> 8;
|
||||
case 0x6: return registers_.timer_latch[0] & 0x00ff;
|
||||
case 0x7: return registers_.timer_latch[0] >> 8;
|
||||
case 0x5: return registers_.timer[0] >> 8; // Timer 1 high-order counter ('T1C-H')
|
||||
case 0x6: return registers_.timer_latch[0] & 0x00ff; // Timer 1 low-order latches ('T1L-L').
|
||||
case 0x7: return registers_.timer_latch[0] >> 8; // Timer 1 high-order latches ('T1L-H').
|
||||
|
||||
// Timer 2
|
||||
case 0x8:
|
||||
case 0x8: // Timer 2 low-order counter ('T2C-L').
|
||||
registers_.interrupt_flags &= ~InterruptFlag::Timer2;
|
||||
reevaluate_interrupts();
|
||||
return registers_.timer[1] & 0x00ff;
|
||||
case 0x9: return registers_.timer[1] >> 8;
|
||||
case 0x9: return registers_.timer[1] >> 8; // Timer 2 high-order counter ('T2C-H').
|
||||
|
||||
case 0xa:
|
||||
case 0xa: // Shift register ('SR').
|
||||
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;
|
||||
case 0xb: return registers_.auxiliary_control; // Auxiliary control ('ACR').
|
||||
case 0xc: return registers_.peripheral_control; // Peripheral control ('PCR').
|
||||
|
||||
case 0xd: return registers_.interrupt_flags | (get_interrupt_line() ? 0x80 : 0x00);
|
||||
case 0xe: return registers_.interrupt_enable | 0x80;
|
||||
case 0xd: return registers_.interrupt_flags | (get_interrupt_line() ? 0x80 : 0x00); // Interrupt flag register ('IFR').
|
||||
case 0xe: return registers_.interrupt_enable | 0x80; // Interrupt enable register ('IER').
|
||||
}
|
||||
|
||||
return 0xff;
|
||||
}
|
||||
|
||||
template <typename T> uint8_t MOS6522<T>::get_port_input(Port port, uint8_t output_mask, uint8_t output) {
|
||||
template <typename T> uint8_t MOS6522<T>::get_port_input(Port port, uint8_t output_mask, uint8_t output, uint8_t timer_mask) {
|
||||
bus_handler_.run_for(time_since_bus_handler_call_.flush<HalfCycles>());
|
||||
const uint8_t input = bus_handler_.get_port_input(port);
|
||||
output = (output & ~timer_mask) | (registers_.timer_port_b_output & timer_mask);
|
||||
return (input & ~output_mask) | (output & output_mask);
|
||||
}
|
||||
|
||||
@@ -276,10 +304,13 @@ template <typename T> void MOS6522<T>::do_phase2() {
|
||||
registers_.timer_needs_reload = false;
|
||||
registers_.timer[0] = registers_.timer_latch[0];
|
||||
} else {
|
||||
registers_.timer[0] --;
|
||||
-- registers_.timer[0];
|
||||
}
|
||||
|
||||
registers_.timer[1] --;
|
||||
// Count down timer 2 if it is in timed interrupt mode (i.e. auxiliary control bit 5 is clear).
|
||||
registers_.timer[1] -= timer2_clock_decrement();
|
||||
|
||||
// TODO: can eliminate conditional branches here.
|
||||
if(registers_.next_timer[0] >= 0) {
|
||||
registers_.timer[0] = uint16_t(registers_.next_timer[0]);
|
||||
registers_.next_timer[0] = -1;
|
||||
@@ -330,20 +361,29 @@ template <typename T> void MOS6522<T>::do_phase1() {
|
||||
reevaluate_interrupts();
|
||||
|
||||
// Determine whether to reload.
|
||||
if(registers_.auxiliary_control&0x40)
|
||||
if(timer1_is_continuous())
|
||||
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;
|
||||
if(timer1_is_controlling_pb7()) {
|
||||
registers_.timer_port_b_output ^= 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]);
|
||||
evaluate_port_b_output();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T> void MOS6522<T>::evaluate_port_b_output() {
|
||||
// Apply current timer-linked PB7 output if any atop the stated output.
|
||||
const uint8_t timer_control_bit = registers_.auxiliary_control & 0x80;
|
||||
bus_handler_.set_port_output(
|
||||
Port::B,
|
||||
(registers_.output[1] & (0xff ^ timer_control_bit)) | timer_control_bit,
|
||||
registers_.data_direction[1] | timer_control_bit);
|
||||
}
|
||||
|
||||
/*! Runs for a specified number of half cycles. */
|
||||
template <typename T> void MOS6522<T>::run_for(const HalfCycles half_cycles) {
|
||||
auto number_of_half_cycles = half_cycles.as_integral();
|
||||
@@ -438,10 +478,11 @@ template <typename T> void MOS6522<T>::shift_in() {
|
||||
}
|
||||
|
||||
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));
|
||||
const bool is_free_running = shift_mode() == ShiftMode::OutUnderT2FreeRunning;
|
||||
if(is_free_running || shift_bits_remaining_) {
|
||||
// Recirculate bits only if in free-running mode (?)
|
||||
const uint8_t incoming_bit = (registers_.shift >> 7) * is_free_running;
|
||||
registers_.shift = uint8_t(registers_.shift << 1) | incoming_bit;
|
||||
evaluate_cb2_output();
|
||||
|
||||
--shift_bits_remaining_;
|
||||
|
||||
@@ -34,7 +34,9 @@ class MOS6522Storage {
|
||||
uint8_t peripheral_control = 0;
|
||||
uint8_t interrupt_flags = 0;
|
||||
uint8_t interrupt_enable = 0;
|
||||
|
||||
bool timer_needs_reload = false;
|
||||
uint8_t timer_port_b_output = 0xff;
|
||||
} registers_;
|
||||
|
||||
// Control state.
|
||||
@@ -79,12 +81,30 @@ class MOS6522Storage {
|
||||
OutUnderPhase2 = 6,
|
||||
OutUnderCB1 = 7
|
||||
};
|
||||
ShiftMode shift_mode() const {
|
||||
return ShiftMode((registers_.auxiliary_control >> 2) & 7);
|
||||
bool timer1_is_controlling_pb7() const {
|
||||
return registers_.auxiliary_control & 0x80;
|
||||
}
|
||||
bool timer1_is_continuous() const {
|
||||
return registers_.auxiliary_control & 0x40;
|
||||
}
|
||||
bool is_shifting_out() const {
|
||||
return registers_.auxiliary_control & 0x10;
|
||||
}
|
||||
int timer2_clock_decrement() const {
|
||||
return 1 ^ ((registers_.auxiliary_control >> 5)&1);
|
||||
}
|
||||
int timer2_pb6_decrement() const {
|
||||
return (registers_.auxiliary_control >> 5)&1;
|
||||
}
|
||||
ShiftMode shift_mode() const {
|
||||
return ShiftMode((registers_.auxiliary_control >> 2) & 7);
|
||||
}
|
||||
bool portb_is_latched() const {
|
||||
return registers_.auxiliary_control & 0x02;
|
||||
}
|
||||
bool port1_is_latched() const {
|
||||
return registers_.auxiliary_control & 0x01;
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
94
Components/6526/6526.hpp
Normal file
94
Components/6526/6526.hpp
Normal file
@@ -0,0 +1,94 @@
|
||||
//
|
||||
// 6526.h
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 18/07/2021.
|
||||
// Copyright © 2021 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef _526_h
|
||||
#define _526_h
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
#include "Implementation/6526Storage.hpp"
|
||||
#include "../Serial/Line.hpp"
|
||||
|
||||
namespace MOS {
|
||||
namespace MOS6526 {
|
||||
|
||||
enum Port {
|
||||
A = 0,
|
||||
B = 1
|
||||
};
|
||||
|
||||
struct PortHandler {
|
||||
/// Requests the current input value of @c port from the port handler.
|
||||
uint8_t get_port_input([[maybe_unused]] Port port) {
|
||||
return 0xff;
|
||||
}
|
||||
|
||||
/// Sets the current output value of @c port; any bits marked as input will be supplied as 1s.
|
||||
void set_port_output([[maybe_unused]] Port port, [[maybe_unused]] uint8_t value) {}
|
||||
};
|
||||
|
||||
enum class Personality {
|
||||
// The 6526, used in machines such as the C64, has a BCD time-of-day clock.
|
||||
P6526,
|
||||
// The 8250, used in the Amiga, provides a binary time-of-day clock.
|
||||
P8250,
|
||||
};
|
||||
|
||||
template <typename PortHandlerT, Personality personality> class MOS6526:
|
||||
private MOS6526Storage,
|
||||
private Serial::Line<true>::ReadDelegate
|
||||
{
|
||||
public:
|
||||
MOS6526(PortHandlerT &port_handler) noexcept : port_handler_(port_handler) {
|
||||
serial_input.set_read_delegate(this);
|
||||
}
|
||||
MOS6526(const MOS6526 &) = delete;
|
||||
|
||||
/// Writes @c value to the register at @c address. Only the low two bits of the address are decoded.
|
||||
void write(int address, uint8_t value);
|
||||
|
||||
/// Fetches the value of the register @c address. Only the low two bits of the address are decoded.
|
||||
uint8_t read(int address);
|
||||
|
||||
/// Pulses Phi2 to advance by the specified number of half cycles.
|
||||
void run_for(const HalfCycles half_cycles);
|
||||
|
||||
/// Pulses the TOD input the specified number of times.
|
||||
void advance_tod(int count);
|
||||
|
||||
/// @returns @c true if the interrupt output is active, @c false otherwise.
|
||||
bool get_interrupt_line();
|
||||
|
||||
/// Sets the current state of the CNT input.
|
||||
void set_cnt_input(bool active);
|
||||
|
||||
/// Provides both the serial input bit and an additional source of CNT.
|
||||
Serial::Line<true> serial_input;
|
||||
|
||||
/// Sets the current state of the FLG input.
|
||||
void set_flag_input(bool low);
|
||||
|
||||
private:
|
||||
PortHandlerT &port_handler_;
|
||||
TODStorage<personality == Personality::P8250> tod_;
|
||||
|
||||
template <int port> void set_port_output();
|
||||
template <int port> uint8_t get_port_input();
|
||||
void update_interrupts();
|
||||
void posit_interrupt(uint8_t mask);
|
||||
void advance_counters(int);
|
||||
|
||||
bool serial_line_did_produce_bit(Serial::Line<true> *line, int bit) final;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#include "Implementation/6526Implementation.hpp"
|
||||
|
||||
#endif /* _526_h */
|
||||
244
Components/6526/Implementation/6526Implementation.hpp
Normal file
244
Components/6526/Implementation/6526Implementation.hpp
Normal file
@@ -0,0 +1,244 @@
|
||||
//
|
||||
// 6526Implementation.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 18/07/2021.
|
||||
// Copyright © 2021 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef _526Implementation_h
|
||||
#define _526Implementation_h
|
||||
|
||||
#include <cassert>
|
||||
#include <cstdio>
|
||||
|
||||
namespace MOS {
|
||||
namespace MOS6526 {
|
||||
|
||||
enum Interrupts: uint8_t {
|
||||
TimerA = 1 << 0,
|
||||
TimerB = 1 << 1,
|
||||
Alarm = 1 << 2,
|
||||
SerialPort = 1 << 3,
|
||||
Flag = 1 << 4,
|
||||
};
|
||||
|
||||
template <typename BusHandlerT, Personality personality>
|
||||
template <int port> void MOS6526<BusHandlerT, personality>::set_port_output() {
|
||||
const uint8_t output = output_[port] | (~data_direction_[port]);
|
||||
port_handler_.set_port_output(Port(port), output);
|
||||
}
|
||||
|
||||
template <typename BusHandlerT, Personality personality>
|
||||
template <int port> uint8_t MOS6526<BusHandlerT, personality>::get_port_input() {
|
||||
// Avoid bothering the port handler if there's no input active.
|
||||
const uint8_t input_mask = ~data_direction_[port];
|
||||
const uint8_t input = input_mask ? port_handler_.get_port_input(Port(port)) : 0x00;
|
||||
return (input & input_mask) | (output_[port] & data_direction_[port]);
|
||||
}
|
||||
|
||||
template <typename BusHandlerT, Personality personality>
|
||||
void MOS6526<BusHandlerT, personality>::posit_interrupt(uint8_t mask) {
|
||||
if(!mask) {
|
||||
return;
|
||||
}
|
||||
interrupt_state_ |= mask;
|
||||
update_interrupts();
|
||||
}
|
||||
|
||||
template <typename BusHandlerT, Personality personality>
|
||||
void MOS6526<BusHandlerT, personality>::update_interrupts() {
|
||||
if(interrupt_state_ & interrupt_control_) {
|
||||
pending_ |= InterruptInOne;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename BusHandlerT, Personality personality>
|
||||
bool MOS6526<BusHandlerT, personality>::get_interrupt_line() {
|
||||
return interrupt_state_ & 0x80;
|
||||
}
|
||||
|
||||
template <typename BusHandlerT, Personality personality>
|
||||
void MOS6526<BusHandlerT, personality>::set_cnt_input(bool active) {
|
||||
cnt_edge_ = active && !cnt_state_;
|
||||
cnt_state_ = active;
|
||||
}
|
||||
|
||||
template <typename BusHandlerT, Personality personality>
|
||||
void MOS6526<BusHandlerT, personality>::set_flag_input(bool low) {
|
||||
if(low && !flag_state_) {
|
||||
posit_interrupt(Interrupts::Flag);
|
||||
}
|
||||
flag_state_ = low;
|
||||
}
|
||||
|
||||
template <typename BusHandlerT, Personality personality>
|
||||
void MOS6526<BusHandlerT, personality>::write(int address, uint8_t value) {
|
||||
address &= 0xf;
|
||||
switch(address) {
|
||||
// Port output.
|
||||
case 0:
|
||||
output_[0] = value;
|
||||
set_port_output<0>();
|
||||
break;
|
||||
case 1:
|
||||
output_[1] = value;
|
||||
set_port_output<1>();
|
||||
break;
|
||||
|
||||
// Port direction.
|
||||
case 2:
|
||||
data_direction_[0] = value;
|
||||
set_port_output<0>();
|
||||
break;
|
||||
case 3:
|
||||
data_direction_[1] = value;
|
||||
set_port_output<1>();
|
||||
break;
|
||||
|
||||
// Counters; writes set the reload values.
|
||||
case 4: counter_[0].template set_reload<0, personality == Personality::P8250>(value); break;
|
||||
case 5: counter_[0].template set_reload<8, personality == Personality::P8250>(value); break;
|
||||
case 6: counter_[1].template set_reload<0, personality == Personality::P8250>(value); break;
|
||||
case 7: counter_[1].template set_reload<8, personality == Personality::P8250>(value); break;
|
||||
|
||||
// Time-of-day clock.
|
||||
case 8: tod_.template write<0>(value); break;
|
||||
case 9: tod_.template write<1>(value); break;
|
||||
case 10: tod_.template write<2>(value); break;
|
||||
case 11: tod_.template write<3>(value); break;
|
||||
|
||||
// Interrupt control.
|
||||
case 13: {
|
||||
if(value & 0x80) {
|
||||
interrupt_control_ |= value & 0x7f;
|
||||
} else {
|
||||
interrupt_control_ &= ~(value & 0x7f);
|
||||
}
|
||||
update_interrupts();
|
||||
} break;
|
||||
|
||||
// Control. Posted to both the counters and the clock as it affects both.
|
||||
case 14:
|
||||
counter_[0].template set_control<false>(value);
|
||||
tod_.template set_control<false>(value);
|
||||
if(shifter_is_output_ != bool(value & 0x40)) {
|
||||
shifter_is_output_ = value & 0x40;
|
||||
shift_bits_ = 0;
|
||||
}
|
||||
break;
|
||||
case 15:
|
||||
counter_[1].template set_control<true>(value);
|
||||
tod_.template set_control<true>(value);
|
||||
break;
|
||||
|
||||
// Shift control.
|
||||
case 12:
|
||||
printf("TODO: write to shift register\n");
|
||||
break;
|
||||
|
||||
default:
|
||||
printf("Unhandled 6526 write: %02x to %d\n", value, address);
|
||||
assert(false);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename BusHandlerT, Personality personality>
|
||||
uint8_t MOS6526<BusHandlerT, personality>::read(int address) {
|
||||
address &= 0xf;
|
||||
switch(address) {
|
||||
case 0: return get_port_input<0>();
|
||||
case 1: return get_port_input<1>();
|
||||
|
||||
case 2: case 3:
|
||||
return data_direction_[address - 2];
|
||||
|
||||
// Counters; reads obtain the current values.
|
||||
case 4: return uint8_t(counter_[0].value >> 0);
|
||||
case 5: return uint8_t(counter_[0].value >> 8);
|
||||
case 6: return uint8_t(counter_[1].value >> 0);
|
||||
case 7: return uint8_t(counter_[1].value >> 8);
|
||||
|
||||
// Interrupt state.
|
||||
case 13: {
|
||||
const uint8_t result = interrupt_state_;
|
||||
interrupt_state_ = 0;
|
||||
pending_ &= ~(InterruptNow | InterruptInOne);
|
||||
update_interrupts();
|
||||
return result;
|
||||
} break;
|
||||
|
||||
case 14: case 15:
|
||||
return counter_[address - 14].control;
|
||||
|
||||
// Time-of-day clock.
|
||||
case 8: return tod_.template read<0>();
|
||||
case 9: return tod_.template read<1>();
|
||||
case 10: return tod_.template read<2>();
|
||||
case 11: return tod_.template read<3>();
|
||||
|
||||
// Shift register.
|
||||
case 12: return shift_data_;
|
||||
|
||||
default:
|
||||
printf("Unhandled 6526 read from %d\n", address);
|
||||
assert(false);
|
||||
break;
|
||||
}
|
||||
return 0xff;
|
||||
}
|
||||
|
||||
template <typename BusHandlerT, Personality personality>
|
||||
void MOS6526<BusHandlerT, personality>::run_for(const HalfCycles half_cycles) {
|
||||
half_divider_ += half_cycles;
|
||||
int sub = half_divider_.divide_cycles().template as<int>();
|
||||
|
||||
while(sub--) {
|
||||
pending_ <<= 1;
|
||||
if(pending_ & InterruptNow) {
|
||||
interrupt_state_ |= 0x80;
|
||||
}
|
||||
pending_ &= PendingClearMask;
|
||||
|
||||
// TODO: use CNT potentially to clock timer A, elimiante conditional above.
|
||||
const bool timer1_did_reload = counter_[0].template advance<false>(false, cnt_state_, cnt_edge_);
|
||||
|
||||
const bool timer1_carry = timer1_did_reload && (counter_[1].control & 0x60) == 0x40;
|
||||
const bool timer2_did_reload = counter_[1].template advance<true>(timer1_carry, cnt_state_, cnt_edge_);
|
||||
posit_interrupt((timer1_did_reload ? Interrupts::TimerA : 0x00) | (timer2_did_reload ? Interrupts::TimerB : 0x00));
|
||||
|
||||
cnt_edge_ = false;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename BusHandlerT, Personality personality>
|
||||
void MOS6526<BusHandlerT, personality>::advance_tod(int count) {
|
||||
if(!count) return;
|
||||
if(tod_.advance(count)) {
|
||||
posit_interrupt(Interrupts::Alarm);
|
||||
}
|
||||
}
|
||||
|
||||
template <typename BusHandlerT, Personality personality>
|
||||
bool MOS6526<BusHandlerT, personality>::serial_line_did_produce_bit(Serial::Line<true> *, int bit) {
|
||||
// TODO: post CNT change; might affect timer.
|
||||
|
||||
if(!shifter_is_output_) {
|
||||
shift_register_ = uint8_t((shift_register_ << 1) | bit);
|
||||
++shift_bits_;
|
||||
|
||||
if(shift_bits_ == 8) {
|
||||
shift_bits_ = 0;
|
||||
shift_data_ = shift_register_;
|
||||
posit_interrupt(Interrupts::SerialPort);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* _526Implementation_h */
|
||||
339
Components/6526/Implementation/6526Storage.hpp
Normal file
339
Components/6526/Implementation/6526Storage.hpp
Normal file
@@ -0,0 +1,339 @@
|
||||
//
|
||||
// 6526Storage.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 18/07/2021.
|
||||
// Copyright © 2021 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef _526Storage_h
|
||||
#define _526Storage_h
|
||||
|
||||
#include <array>
|
||||
|
||||
#include "../../../ClockReceiver/ClockReceiver.hpp"
|
||||
|
||||
namespace MOS {
|
||||
namespace MOS6526 {
|
||||
|
||||
class TODBase {
|
||||
public:
|
||||
template <bool is_timer2> void set_control(uint8_t value) {
|
||||
if constexpr (is_timer2) {
|
||||
write_alarm = value & 0x80;
|
||||
} else {
|
||||
is_50Hz = value & 0x80;
|
||||
}
|
||||
}
|
||||
|
||||
protected:
|
||||
bool write_alarm = false, is_50Hz = false;
|
||||
};
|
||||
|
||||
template <bool is_8250> class TODStorage {};
|
||||
|
||||
template <> class TODStorage<false>: public TODBase {
|
||||
private:
|
||||
bool increment_ = true, latched_ = false;
|
||||
int divider_ = 0;
|
||||
std::array<uint8_t, 4> value_;
|
||||
std::array<uint8_t, 4> latch_;
|
||||
std::array<uint8_t, 4> alarm_;
|
||||
|
||||
static constexpr uint8_t masks[4] = {0xf, 0x3f, 0x3f, 0x1f};
|
||||
|
||||
void bcd_increment(uint8_t &value) {
|
||||
++value;
|
||||
if((value&0x0f) > 0x09) value += 0x06;
|
||||
}
|
||||
|
||||
public:
|
||||
template <int byte> void write(uint8_t v) {
|
||||
if(write_alarm) {
|
||||
alarm_[byte] = v & masks[byte];
|
||||
} else {
|
||||
value_[byte] = v & masks[byte];
|
||||
|
||||
if constexpr (byte == 0) {
|
||||
increment_ = true;
|
||||
}
|
||||
if constexpr (byte == 3) {
|
||||
increment_ = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <int byte> uint8_t read() {
|
||||
if(latched_) {
|
||||
const uint8_t result = latch_[byte];
|
||||
if constexpr (byte == 0) {
|
||||
latched_ = false;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
if constexpr (byte == 3) {
|
||||
latched_ = true;
|
||||
latch_ = value_;
|
||||
}
|
||||
return value_[byte];
|
||||
}
|
||||
|
||||
bool advance(int count) {
|
||||
if(!increment_) {
|
||||
return false;
|
||||
}
|
||||
|
||||
while(count--) {
|
||||
// Increment the pre-10ths divider.
|
||||
++divider_;
|
||||
if(divider_ < 5) continue;
|
||||
if(divider_ < 6 && !is_50Hz) continue;
|
||||
divider_ = 0;
|
||||
|
||||
// Increments 10ths of a second. One BCD digit.
|
||||
++value_[0];
|
||||
if(value_[0] < 10) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Increment seconds. Actual BCD needed from here onwards.
|
||||
bcd_increment(value_[1]);
|
||||
if(value_[1] != 60) {
|
||||
continue;
|
||||
}
|
||||
value_[1] = 0;
|
||||
|
||||
// Increment minutes.
|
||||
bcd_increment(value_[2]);
|
||||
if(value_[2] != 60) {
|
||||
continue;
|
||||
}
|
||||
value_[2] = 0;
|
||||
|
||||
// TODO: increment hours, keeping AM/PM separate?
|
||||
}
|
||||
|
||||
return false; // TODO: test against alarm.
|
||||
}
|
||||
};
|
||||
|
||||
template <> class TODStorage<true>: public TODBase {
|
||||
private:
|
||||
uint32_t increment_mask_ = uint32_t(~0);
|
||||
uint32_t latch_ = 0;
|
||||
uint32_t value_ = 0;
|
||||
uint32_t alarm_ = 0xff'ffff;
|
||||
|
||||
public:
|
||||
template <int byte> void write(uint8_t v) {
|
||||
if constexpr (byte == 3) {
|
||||
return;
|
||||
}
|
||||
constexpr int shift = byte << 3;
|
||||
|
||||
// Write to either the alarm or the current value as directed;
|
||||
// writing to any part of the current value other than the LSB
|
||||
// pauses incrementing until the LSB is written.
|
||||
const uint32_t mask = uint32_t(~(0xff << shift));
|
||||
if(write_alarm) {
|
||||
alarm_ = (alarm_ & mask) | uint32_t(v << shift);
|
||||
} else {
|
||||
value_ = (value_ & mask) | uint32_t(v << shift);
|
||||
increment_mask_ = (byte == 0) ? uint32_t(~0) : 0;
|
||||
}
|
||||
}
|
||||
|
||||
template <int byte> uint8_t read() {
|
||||
if constexpr (byte == 3) {
|
||||
return 0xff; // Assumed. Just a guess.
|
||||
}
|
||||
constexpr int shift = byte << 3;
|
||||
|
||||
if constexpr (byte == 2) {
|
||||
latch_ = value_ | 0xff00'0000;
|
||||
}
|
||||
|
||||
const uint32_t source = latch_ ? latch_ : value_;
|
||||
const uint8_t result = uint8_t((source >> shift) & 0xff);
|
||||
|
||||
if constexpr (byte == 0) {
|
||||
latch_ = 0;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
bool advance(int count) {
|
||||
// The 8250 uses a simple binary counter to replace the
|
||||
// 6526's time-of-day clock. So this is easy.
|
||||
const uint32_t distance_to_alarm = (alarm_ - value_) & 0xff'ffff;
|
||||
const auto increment = uint32_t(count) & increment_mask_;
|
||||
value_ = (value_ + increment) & 0xff'ffff;
|
||||
return distance_to_alarm <= increment;
|
||||
}
|
||||
};
|
||||
|
||||
struct MOS6526Storage {
|
||||
bool cnt_state_ = false; // Inactive by default.
|
||||
bool cnt_edge_ = false;
|
||||
bool flag_state_ = false;
|
||||
HalfCycles half_divider_;
|
||||
|
||||
uint8_t output_[2] = {0, 0};
|
||||
uint8_t data_direction_[2] = {0, 0};
|
||||
|
||||
uint8_t interrupt_control_ = 0;
|
||||
uint8_t interrupt_state_ = 0;
|
||||
|
||||
uint8_t shift_data_ = 0;
|
||||
uint8_t shift_register_ = 0;
|
||||
int shift_bits_ = 0;
|
||||
bool shifter_is_output_ = false;
|
||||
|
||||
struct Counter {
|
||||
uint16_t reload = 0;
|
||||
uint16_t value = 0;
|
||||
uint8_t control = 0;
|
||||
|
||||
template <int shift, bool is_8250> void set_reload(uint8_t v) {
|
||||
reload = (reload & (0xff00 >> shift)) | uint16_t(v << shift);
|
||||
|
||||
if constexpr (shift == 8) {
|
||||
// This seems to be a special 8250 feature per the Amiga
|
||||
// Hardware Reference Manual; cf. Appendix F.
|
||||
if(is_8250) {
|
||||
control |= 1;
|
||||
pending |= ReloadInOne;
|
||||
} else {
|
||||
if(!(control&1)) {
|
||||
pending |= ReloadInOne;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If this write has hit during a reload cycle, reload.
|
||||
if(pending & ReloadNow) {
|
||||
value = reload;
|
||||
}
|
||||
}
|
||||
|
||||
template <bool is_counter_2> void set_control(uint8_t v) {
|
||||
control = v;
|
||||
|
||||
if(v&2) {
|
||||
printf("UNIMPLEMENTED: PB strobe\n");
|
||||
}
|
||||
}
|
||||
|
||||
template <bool is_counter_2> bool advance(bool chained_input, bool cnt_state, bool cnt_edge) {
|
||||
// TODO: remove most of the conditionals here in favour of bit shuffling.
|
||||
|
||||
pending = (pending & PendingClearMask) << 1;
|
||||
|
||||
//
|
||||
// Apply feeder states inputs: anything that
|
||||
// will take effect in the future.
|
||||
//
|
||||
|
||||
// Schedule a force reload if requested.
|
||||
if(control & 0x10) {
|
||||
pending |= ReloadInOne;
|
||||
control &= ~0x10;
|
||||
}
|
||||
|
||||
// Keep a history of the one-shot bit.
|
||||
if(control & 0x08) {
|
||||
pending |= OneShotInOne;
|
||||
}
|
||||
|
||||
// Determine whether an input clock is applicable.
|
||||
if constexpr(is_counter_2) {
|
||||
switch(control&0x60) {
|
||||
case 0x00: // Count Phi2 pulses.
|
||||
pending |= TestInputNow;
|
||||
break;
|
||||
case 0x20: // Count negative CNTs, with an extra cycle of delay.
|
||||
pending |= cnt_edge ? TestInputInOne : 0;
|
||||
break;
|
||||
case 0x40: // Count timer A reloads.
|
||||
pending |= chained_input ? TestInputNow : 0;
|
||||
break;
|
||||
case 0x60: // Count timer A transitions when CNT is low.
|
||||
pending |= chained_input && cnt_state ? TestInputNow : 0;
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
if(!(control&0x20)) {
|
||||
pending |= TestInputNow;
|
||||
} else if (cnt_edge) {
|
||||
pending |= TestInputInOne;
|
||||
}
|
||||
}
|
||||
if(pending&TestInputNow && control&1) {
|
||||
pending |= ApplyClockInTwo;
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// Perform a timer tick and decide whether a reload is prompted.
|
||||
//
|
||||
if(pending & ApplyClockNow) {
|
||||
--value;
|
||||
}
|
||||
|
||||
const bool should_reload = !value && (pending & ApplyClockInOne);
|
||||
|
||||
// Schedule a reload if so ordered.
|
||||
if(should_reload) {
|
||||
pending |= ReloadNow; // Combine this decision with a deferred
|
||||
// input from the force-reoad test above.
|
||||
|
||||
// If this was one-shot, stop.
|
||||
if(pending&(OneShotInOne | OneShotNow)) {
|
||||
control &= ~1;
|
||||
pending &= ~(ApplyClockInOne|ApplyClockInTwo); // Cancel scheduled ticks.
|
||||
}
|
||||
}
|
||||
|
||||
// Reload if scheduled.
|
||||
if(pending & ReloadNow) {
|
||||
value = reload;
|
||||
pending &= ~ApplyClockInOne; // Skip next decrement.
|
||||
}
|
||||
|
||||
|
||||
return should_reload;
|
||||
}
|
||||
|
||||
private:
|
||||
int pending = 0;
|
||||
|
||||
static constexpr int ReloadInOne = 1 << 0;
|
||||
static constexpr int ReloadNow = 1 << 1;
|
||||
|
||||
static constexpr int OneShotInOne = 1 << 2;
|
||||
static constexpr int OneShotNow = 1 << 3;
|
||||
|
||||
static constexpr int ApplyClockInTwo = 1 << 4;
|
||||
static constexpr int ApplyClockInOne = 1 << 5;
|
||||
static constexpr int ApplyClockNow = 1 << 6;
|
||||
|
||||
static constexpr int TestInputInOne = 1 << 7;
|
||||
static constexpr int TestInputNow = 1 << 8;
|
||||
|
||||
static constexpr int PendingClearMask = ~(ReloadNow | OneShotNow | ApplyClockNow);
|
||||
|
||||
bool active_ = false;
|
||||
} counter_[2];
|
||||
|
||||
static constexpr int InterruptInOne = 1 << 0;
|
||||
static constexpr int InterruptNow = 1 << 1;
|
||||
static constexpr int PendingClearMask = ~(InterruptNow);
|
||||
int pending_ = 0;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* _526Storage_h */
|
||||
@@ -435,7 +435,7 @@ template <class BusHandler> class MOS6560 {
|
||||
|
||||
Concurrency::DeferringAsyncTaskQueue audio_queue_;
|
||||
AudioGenerator audio_generator_;
|
||||
Outputs::Speaker::LowpassSpeaker<AudioGenerator> speaker_;
|
||||
Outputs::Speaker::PullLowpass<AudioGenerator> speaker_;
|
||||
|
||||
Cycles cycles_since_speaker_update_;
|
||||
void update_audio() {
|
||||
|
||||
@@ -129,8 +129,8 @@ ClockingHint::Preference ACIA::preferred_clocking() const {
|
||||
// because it's unclear when the interrupt might come.
|
||||
if(bits_incoming_ && receive_interrupt_enabled_) return ClockingHint::Preference::RealTime;
|
||||
|
||||
// No clocking required then.
|
||||
return ClockingHint::Preference::None;
|
||||
// Real-time clocking not required then.
|
||||
return ClockingHint::Preference::JustInTime;
|
||||
}
|
||||
|
||||
bool ACIA::get_interrupt_line() const {
|
||||
@@ -148,7 +148,7 @@ uint8_t ACIA::parity(uint8_t value) {
|
||||
return value ^ (parity_ == Parity::Even);
|
||||
}
|
||||
|
||||
bool ACIA::serial_line_did_produce_bit(Serial::Line *, int bit) {
|
||||
bool ACIA::serial_line_did_produce_bit(Serial::Line<false> *, int bit) {
|
||||
// Shift this bit into the 11-bit input register; this is big enough to hold
|
||||
// the largest transmission symbol.
|
||||
++bits_received_;
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
namespace Motorola {
|
||||
namespace ACIA {
|
||||
|
||||
class ACIA: public ClockingHint::Source, private Serial::Line::ReadDelegate {
|
||||
class ACIA: public ClockingHint::Source, private Serial::Line<false>::ReadDelegate {
|
||||
public:
|
||||
static constexpr const HalfCycles SameAsTransmit = HalfCycles(0);
|
||||
|
||||
@@ -77,13 +77,13 @@ class ACIA: public ClockingHint::Source, private Serial::Line::ReadDelegate {
|
||||
void reset();
|
||||
|
||||
// Input lines.
|
||||
Serial::Line receive;
|
||||
Serial::Line clear_to_send;
|
||||
Serial::Line data_carrier_detect;
|
||||
Serial::Line<false> receive;
|
||||
Serial::Line<false> clear_to_send;
|
||||
Serial::Line<false> data_carrier_detect;
|
||||
|
||||
// Output lines.
|
||||
Serial::Line transmit;
|
||||
Serial::Line request_to_send;
|
||||
Serial::Line<false> transmit;
|
||||
Serial::Line<false> request_to_send;
|
||||
|
||||
// ClockingHint::Source.
|
||||
ClockingHint::Preference preferred_clocking() const final;
|
||||
@@ -118,7 +118,7 @@ class ACIA: public ClockingHint::Source, private Serial::Line::ReadDelegate {
|
||||
HalfCycles transmit_clock_rate_;
|
||||
HalfCycles receive_clock_rate_;
|
||||
|
||||
bool serial_line_did_produce_bit(Serial::Line *line, int bit) final;
|
||||
bool serial_line_did_produce_bit(Serial::Line<false> *line, int bit) final;
|
||||
|
||||
bool interrupt_line_ = false;
|
||||
void update_interrupt_line();
|
||||
|
||||
@@ -208,7 +208,7 @@ void MFP68901::run_for(HalfCycles time) {
|
||||
}
|
||||
|
||||
HalfCycles MFP68901::get_next_sequence_point() {
|
||||
return HalfCycles(-1);
|
||||
return HalfCycles::max();
|
||||
}
|
||||
|
||||
// MARK: - Timers
|
||||
|
||||
@@ -8,6 +8,10 @@
|
||||
|
||||
#include "z8530.hpp"
|
||||
|
||||
#ifndef NDEBUG
|
||||
#define NDEBUG
|
||||
#endif
|
||||
|
||||
#define LOG_PREFIX "[SCC] "
|
||||
#include "../../Outputs/Log.hpp"
|
||||
|
||||
|
||||
@@ -708,9 +708,9 @@ HalfCycles Base::half_cycles_before_internal_cycles(int internal_cycles) {
|
||||
return HalfCycles(((internal_cycles << 2) + (2 - cycles_error_)) / 3);
|
||||
}
|
||||
|
||||
HalfCycles TMS9918::get_time_until_interrupt() {
|
||||
if(!generate_interrupts_ && !enable_line_interrupts_) return HalfCycles(-1);
|
||||
if(get_interrupt_line()) return HalfCycles(0);
|
||||
HalfCycles TMS9918::get_next_sequence_point() {
|
||||
if(!generate_interrupts_ && !enable_line_interrupts_) return HalfCycles::max();
|
||||
if(get_interrupt_line()) return HalfCycles::max();
|
||||
|
||||
// Calculate the amount of time until the next end-of-frame interrupt.
|
||||
const int frame_length = 342 * mode_timing_.total_lines;
|
||||
@@ -750,7 +750,7 @@ HalfCycles TMS9918::get_time_until_interrupt() {
|
||||
if(next_line_interrupt_row == -1) {
|
||||
return generate_interrupts_ ?
|
||||
half_cycles_before_internal_cycles(time_until_frame_interrupt) :
|
||||
HalfCycles(-1);
|
||||
HalfCycles::max();
|
||||
}
|
||||
|
||||
// Figure out the number of internal cycles until the next line interrupt, which is the amount
|
||||
|
||||
@@ -75,13 +75,13 @@ class TMS9918: public Base {
|
||||
void latch_horizontal_counter();
|
||||
|
||||
/*!
|
||||
Returns the amount of time until @c get_interrupt_line would next return true if
|
||||
Returns the amount of time until @c get_interrupt_line would next change if
|
||||
there are no interceding calls to @c write or to @c read.
|
||||
|
||||
If get_interrupt_line is true now, returns zero. If get_interrupt_line would
|
||||
never return true, returns -1.
|
||||
If get_interrupt_line is true now of if get_interrupt_line would
|
||||
never return true, returns HalfCycles::max().
|
||||
*/
|
||||
HalfCycles get_time_until_interrupt();
|
||||
HalfCycles get_next_sequence_point();
|
||||
|
||||
/*!
|
||||
Returns the amount of time until the nominated line interrupt position is
|
||||
|
||||
@@ -350,11 +350,14 @@ class Base {
|
||||
|
||||
case MemoryAccess::Write:
|
||||
if(master_system_.cram_is_selected) {
|
||||
// Adjust the palette.
|
||||
// Adjust the palette. In a Master System blue has a slightly different
|
||||
// scale; cf. https://www.retrorgb.com/sega-master-system-non-linear-blue-channel-findings.html
|
||||
constexpr uint8_t rg_scale[] = {0, 85, 170, 255};
|
||||
constexpr uint8_t b_scale[] = {0, 104, 170, 255};
|
||||
master_system_.colour_ram[ram_pointer_ & 0x1f] = palette_pack(
|
||||
uint8_t(((read_ahead_buffer_ >> 0) & 3) * 255 / 3),
|
||||
uint8_t(((read_ahead_buffer_ >> 2) & 3) * 255 / 3),
|
||||
uint8_t(((read_ahead_buffer_ >> 4) & 3) * 255 / 3)
|
||||
rg_scale[(read_ahead_buffer_ >> 0) & 3],
|
||||
rg_scale[(read_ahead_buffer_ >> 2) & 3],
|
||||
b_scale[(read_ahead_buffer_ >> 4) & 3]
|
||||
);
|
||||
|
||||
// Schedule a CRAM dot; this is scheduled for wherever it should appear
|
||||
|
||||
@@ -12,6 +12,8 @@
|
||||
#include "../../Outputs/Speaker/Implementation/SampleSource.hpp"
|
||||
#include "../../Concurrency/AsyncTaskQueue.hpp"
|
||||
|
||||
#include "../../Reflection/Struct.hpp"
|
||||
|
||||
namespace GI {
|
||||
namespace AY38910 {
|
||||
|
||||
@@ -162,8 +164,60 @@ template <bool is_stereo> class AY38910: public ::Outputs::Speaker::SampleSource
|
||||
uint8_t a_left_ = 255, a_right_ = 255;
|
||||
uint8_t b_left_ = 255, b_right_ = 255;
|
||||
uint8_t c_left_ = 255, c_right_ = 255;
|
||||
|
||||
friend struct State;
|
||||
};
|
||||
|
||||
/*!
|
||||
Provides helper code, to provide something closer to the interface exposed by many
|
||||
AY-deploying machines of the era.
|
||||
*/
|
||||
struct Utility {
|
||||
template <typename AY> static void write(AY &ay, bool is_data_write, uint8_t data) {
|
||||
ay.set_control_lines(GI::AY38910::ControlLines(GI::AY38910::BDIR | GI::AY38910::BC2 | (is_data_write ? 0 : GI::AY38910::BC1)));
|
||||
ay.set_data_input(data);
|
||||
ay.set_control_lines(GI::AY38910::ControlLines(0));
|
||||
}
|
||||
|
||||
template <typename AY> static void select_register(AY &ay, uint8_t reg) {
|
||||
write(ay, false, reg);
|
||||
}
|
||||
|
||||
template <typename AY> static void write_data(AY &ay, uint8_t reg) {
|
||||
write(ay, true, reg);
|
||||
}
|
||||
|
||||
template <typename AY> static uint8_t read(AY &ay) {
|
||||
ay.set_control_lines(GI::AY38910::ControlLines(GI::AY38910::BC2 | GI::AY38910::BC1));
|
||||
const uint8_t result = ay.get_data_output();
|
||||
ay.set_control_lines(GI::AY38910::ControlLines(0));
|
||||
return result;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
struct State: public Reflection::StructImpl<State> {
|
||||
uint8_t registers[16]{};
|
||||
uint8_t selected_register = 0;
|
||||
|
||||
// TODO: all audio-production thread state.
|
||||
|
||||
State() {
|
||||
if(needs_declare()) {
|
||||
DeclareField(registers);
|
||||
DeclareField(selected_register);
|
||||
}
|
||||
}
|
||||
|
||||
template <typename AY> void apply(AY &target) {
|
||||
// Establish emulator-thread state
|
||||
for(uint8_t c = 0; c < 16; c++) {
|
||||
target.select_register(c);
|
||||
target.set_register_value(registers[c]);
|
||||
}
|
||||
target.select_register(selected_register);
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
299
Components/AppleClock/AppleClock.hpp
Normal file
299
Components/AppleClock/AppleClock.hpp
Normal file
@@ -0,0 +1,299 @@
|
||||
//
|
||||
// RealTimeClock.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 07/05/2019.
|
||||
// Copyright © 2019 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef Apple_RealTimeClock_hpp
|
||||
#define Apple_RealTimeClock_hpp
|
||||
|
||||
#include <array>
|
||||
|
||||
namespace Apple {
|
||||
namespace Clock {
|
||||
|
||||
/*!
|
||||
Models Apple's real-time clocks, as contained in the Macintosh and IIgs.
|
||||
|
||||
Since tracking of time is pushed to this class, it is assumed
|
||||
that whomever is translating real time into emulated time
|
||||
will also signal interrupts — this is just the storage and time counting.
|
||||
*/
|
||||
class ClockStorage {
|
||||
public:
|
||||
ClockStorage() {}
|
||||
|
||||
/*!
|
||||
Advances the clock by 1 second.
|
||||
|
||||
The caller should also signal an interrupt if applicable.
|
||||
*/
|
||||
void update() {
|
||||
for(size_t c = 0; c < 4; ++c) {
|
||||
++seconds_[c];
|
||||
if(seconds_[c]) break;
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets the current [P/B]RAM contents.
|
||||
*/
|
||||
template <typename CollectionT> void set_data(const CollectionT &collection) {
|
||||
set_data(collection.begin(), collection.end());
|
||||
}
|
||||
|
||||
template <typename IteratorT> void set_data(IteratorT begin, const IteratorT end) {
|
||||
size_t c = 0;
|
||||
while(begin != end && c < 256) {
|
||||
data_[c] = *begin;
|
||||
++begin;
|
||||
++c;
|
||||
}
|
||||
}
|
||||
|
||||
protected:
|
||||
static constexpr uint16_t NoResult = 0x100;
|
||||
static constexpr uint16_t DidComplete = 0x101;
|
||||
uint16_t perform(uint8_t command) {
|
||||
/*
|
||||
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
|
||||
|
||||
z0111abc, followed by 0defgh00
|
||||
RAM address abcdefgh
|
||||
|
||||
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.
|
||||
*/
|
||||
switch(phase_) {
|
||||
case Phase::Command:
|
||||
// Decode an address.
|
||||
switch(command & 0x70) {
|
||||
default:
|
||||
if(command & 0x40) {
|
||||
// RAM addresses 0x00 – 0x0f.
|
||||
address_ = (command >> 2) & 0xf;
|
||||
} else return DidComplete; // Unrecognised.
|
||||
break;
|
||||
|
||||
case 0x00:
|
||||
// A time access.
|
||||
address_ = SecondsBuffer + ((command >> 2)&3);
|
||||
break;
|
||||
case 0x30:
|
||||
// Either a register access or an extended instruction.
|
||||
if(command & 0x08) {
|
||||
address_ = unsigned((command & 0x7) << 5);
|
||||
phase_ = (command & 0x80) ? Phase::SecondAddressByteRead : Phase::SecondAddressByteWrite;
|
||||
return NoResult;
|
||||
} else {
|
||||
address_ = (command & 4) ? RegisterWriteProtect : RegisterTest;
|
||||
}
|
||||
break;
|
||||
case 0x20:
|
||||
// RAM addresses 0x10 – 0x13.
|
||||
address_ = 0x10 + ((command >> 2) & 0x3);
|
||||
break;
|
||||
}
|
||||
|
||||
// If this is a read, return a result; otherwise prepare to write.
|
||||
if(command & 0x80) {
|
||||
// The two registers are write-only.
|
||||
if(address_ == RegisterTest || address_ == RegisterWriteProtect) {
|
||||
return DidComplete;
|
||||
}
|
||||
return (address_ >= SecondsBuffer) ? seconds_[address_ & 0xff] : data_[address_];
|
||||
}
|
||||
phase_ = Phase::WriteData;
|
||||
return NoResult;
|
||||
|
||||
case Phase::SecondAddressByteRead:
|
||||
case Phase::SecondAddressByteWrite:
|
||||
if(command & 0x83) {
|
||||
return DidComplete;
|
||||
}
|
||||
address_ |= command >> 2;
|
||||
|
||||
if(phase_ == Phase::SecondAddressByteRead) {
|
||||
phase_ = Phase::Command;
|
||||
return data_[address_]; // Only RAM accesses can get this far.
|
||||
} else {
|
||||
phase_ = Phase::WriteData;
|
||||
}
|
||||
return NoResult;
|
||||
|
||||
case Phase::WriteData:
|
||||
// First test: is this to the write-protect register?
|
||||
if(address_ == RegisterWriteProtect) {
|
||||
write_protect_ = command;
|
||||
return DidComplete;
|
||||
}
|
||||
|
||||
if(address_ == RegisterTest) {
|
||||
// No documentation here.
|
||||
return DidComplete;
|
||||
}
|
||||
|
||||
// No other writing is permitted if the write protect
|
||||
// register won't allow it.
|
||||
if(!(write_protect_ & 0x80)) {
|
||||
if(address_ >= SecondsBuffer) {
|
||||
seconds_[address_ & 0xff] = command;
|
||||
} else {
|
||||
data_[address_] = command;
|
||||
}
|
||||
}
|
||||
|
||||
phase_ = Phase::Command;
|
||||
return DidComplete;
|
||||
}
|
||||
|
||||
return NoResult;
|
||||
}
|
||||
|
||||
|
||||
private:
|
||||
std::array<uint8_t, 256> data_{0xff};
|
||||
std::array<uint8_t, 4> seconds_{};
|
||||
uint8_t write_protect_ = 0;
|
||||
unsigned int address_ = 0;
|
||||
|
||||
static constexpr int SecondsBuffer = 0x100;
|
||||
static constexpr int RegisterTest = 0x200;
|
||||
static constexpr int RegisterWriteProtect = 0x201;
|
||||
|
||||
enum class Phase {
|
||||
Command,
|
||||
SecondAddressByteRead,
|
||||
SecondAddressByteWrite,
|
||||
WriteData
|
||||
};
|
||||
Phase phase_ = Phase::Command;
|
||||
|
||||
};
|
||||
|
||||
/*!
|
||||
Provides the serial interface implemented by the Macintosh.
|
||||
*/
|
||||
class SerialClock: public ClockStorage {
|
||||
public:
|
||||
/*!
|
||||
Sets the current clock and data inputs to the clock.
|
||||
*/
|
||||
void set_input(bool clock, bool data) {
|
||||
// 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_;
|
||||
|
||||
// If a whole byte has been collected, push it onwards.
|
||||
if(!(phase_&7)) {
|
||||
// Begin pessimistically.
|
||||
const auto effect = perform(uint8_t(command_));
|
||||
|
||||
switch(effect) {
|
||||
case ClockStorage::NoResult:
|
||||
break;
|
||||
default:
|
||||
result_ = uint8_t(effect);
|
||||
break;
|
||||
case ClockStorage::DidComplete:
|
||||
abort();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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:
|
||||
int phase_ = 0;
|
||||
uint16_t command_;
|
||||
uint8_t result_ = 0;
|
||||
|
||||
bool previous_clock_ = false;
|
||||
};
|
||||
|
||||
/*!
|
||||
Provides the parallel interface implemented by the IIgs.
|
||||
*/
|
||||
class ParallelClock: public ClockStorage {
|
||||
public:
|
||||
void set_control(uint8_t control) {
|
||||
if(!(control&0x80)) return;
|
||||
|
||||
if(control & 0x40) {
|
||||
// Read from the RTC.
|
||||
// A no-op for now.
|
||||
} else {
|
||||
// Write to the RTC. Which in this implementation also sets up a future read.
|
||||
const auto result = perform(data_);
|
||||
if(result < 0x100) {
|
||||
data_ = uint8_t(result);
|
||||
}
|
||||
}
|
||||
|
||||
// MAGIC! The transaction took 0 seconds.
|
||||
// TODO: no magic.
|
||||
control_ = control & 0x7f;
|
||||
|
||||
// Bit 5 is also meant to be 1 or 0 to indicate the final byte.
|
||||
}
|
||||
|
||||
uint8_t get_control() {
|
||||
return control_;
|
||||
}
|
||||
|
||||
void set_data(uint8_t data) {
|
||||
data_ = data;
|
||||
}
|
||||
|
||||
uint8_t get_data() {
|
||||
return data_;
|
||||
}
|
||||
|
||||
private:
|
||||
uint8_t data_;
|
||||
uint8_t control_;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* Apple_RealTimeClock_hpp */
|
||||
@@ -22,7 +22,10 @@ namespace {
|
||||
DiskII::DiskII(int clock_rate) :
|
||||
clock_rate_(clock_rate),
|
||||
inputs_(input_command),
|
||||
drives_{{clock_rate, 300, 1}, {clock_rate, 300, 1}}
|
||||
drives_{
|
||||
Storage::Disk::Drive{clock_rate, 300, 1},
|
||||
Storage::Disk::Drive{clock_rate, 300, 1}
|
||||
}
|
||||
{
|
||||
drives_[0].set_clocking_hint_observer(this);
|
||||
drives_[1].set_clocking_hint_observer(this);
|
||||
@@ -137,10 +140,15 @@ void DiskII::decide_clocking_preference() {
|
||||
|
||||
// If in read mode, clocking is either:
|
||||
//
|
||||
// just-in-time, if drives are running or the shift register has any 1s in it or a flux event hasn't yet passed; or
|
||||
// none, given that drives are not running, the shift register has already emptied and there's no flux about to be received.
|
||||
// just-in-time, if drives are running or the shift register has any 1s in it and shifting may occur, or a flux event hasn't yet passed; or
|
||||
// none, given that drives are not running, the shift register has already emptied or stopped and there's no flux about to be received.
|
||||
if(!(inputs_ & ~input_flux)) {
|
||||
clocking_preference_ = (!motor_is_enabled_ && !shift_register_ && (inputs_&input_flux)) ? ClockingHint::Preference::None : ClockingHint::Preference::JustInTime;
|
||||
const bool is_stuck_at_nop =
|
||||
!flux_duration_ && state_machine_[(state_ & 0xf0) | inputs_ | ((shift_register_&0x80) >> 6)] == state_ && (state_ &0xf) == 0x8;
|
||||
|
||||
clocking_preference_ =
|
||||
(drive_is_sleeping_[0] && drive_is_sleeping_[1] && (!shift_register_ || is_stuck_at_nop) && (inputs_&input_flux))
|
||||
? ClockingHint::Preference::None : ClockingHint::Preference::JustInTime;
|
||||
}
|
||||
|
||||
// If in writing mode, clocking is real time.
|
||||
@@ -180,29 +188,40 @@ void DiskII::set_state_machine(const std::vector<uint8_t> &state_machine) {
|
||||
if(state_machine[0] != 0x18) {
|
||||
for(size_t source_address = 0; source_address < 256; ++source_address) {
|
||||
// Remap into Beneath Apple Pro-DOS address form.
|
||||
size_t destination_address =
|
||||
((source_address&0x80) ? 0x10 : 0x00) |
|
||||
((source_address&0x01) ? 0x20 : 0x00) |
|
||||
((source_address&0x40) ? 0x40 : 0x00) |
|
||||
const size_t destination_address =
|
||||
((source_address&0x20) ? 0x80 : 0x00) |
|
||||
((source_address&0x10) ? 0x01 : 0x00) |
|
||||
((source_address&0x40) ? 0x40 : 0x00) |
|
||||
((source_address&0x01) ? 0x20 : 0x00) |
|
||||
((source_address&0x80) ? 0x10 : 0x00) |
|
||||
((source_address&0x08) ? 0x08 : 0x00) |
|
||||
((source_address&0x04) ? 0x04 : 0x00) |
|
||||
((source_address&0x02) ? 0x02 : 0x00);
|
||||
uint8_t source_value = state_machine[source_address];
|
||||
((source_address&0x02) ? 0x02 : 0x00) |
|
||||
((source_address&0x10) ? 0x01 : 0x00);
|
||||
|
||||
source_value =
|
||||
// Store.
|
||||
const uint8_t source_value = state_machine[source_address];
|
||||
state_machine_[destination_address] =
|
||||
((source_value & 0x80) ? 0x10 : 0x0) |
|
||||
((source_value & 0x40) ? 0x20 : 0x0) |
|
||||
((source_value & 0x20) ? 0x40 : 0x0) |
|
||||
((source_value & 0x10) ? 0x80 : 0x0) |
|
||||
(source_value & 0x0f);
|
||||
|
||||
// Store.
|
||||
state_machine_[destination_address] = source_value;
|
||||
}
|
||||
} else {
|
||||
memcpy(&state_machine_[0], &state_machine[0], 128);
|
||||
for(size_t source_address = 0; source_address < 256; ++source_address) {
|
||||
// Reshuffle ordering of bytes only, to retain indexing by the high nibble.
|
||||
const size_t destination_address =
|
||||
((source_address&0x80) ? 0x80 : 0x00) |
|
||||
((source_address&0x40) ? 0x40 : 0x00) |
|
||||
((source_address&0x01) ? 0x20 : 0x00) |
|
||||
((source_address&0x20) ? 0x10 : 0x00) |
|
||||
((source_address&0x08) ? 0x08 : 0x00) |
|
||||
((source_address&0x04) ? 0x04 : 0x00) |
|
||||
((source_address&0x02) ? 0x02 : 0x00) |
|
||||
((source_address&0x10) ? 0x01 : 0x00);
|
||||
|
||||
state_machine_[destination_address] = state_machine[source_address];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
45
Components/DiskII/DiskIIDrive.cpp
Normal file
45
Components/DiskII/DiskIIDrive.cpp
Normal file
@@ -0,0 +1,45 @@
|
||||
//
|
||||
// DiskIIDrive.cpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 20/11/2020.
|
||||
// Copyright © 2020 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#include "DiskIIDrive.hpp"
|
||||
|
||||
using namespace Apple::Disk;
|
||||
|
||||
DiskIIDrive::DiskIIDrive(int input_clock_rate) :
|
||||
IWMDrive(input_clock_rate, 1) {
|
||||
Drive::set_rotation_speed(300.0f);
|
||||
}
|
||||
|
||||
void DiskIIDrive::set_enabled(bool enabled) {
|
||||
set_motor_on(enabled);
|
||||
}
|
||||
|
||||
void DiskIIDrive::set_control_lines(int lines) {
|
||||
// If the stepper magnet selections have changed, and any is on, see how
|
||||
// that moves the head.
|
||||
if(lines ^ stepper_mask_ && lines) {
|
||||
// Convert from a representation of bits set to the centre of pull.
|
||||
int direction = 0;
|
||||
if(lines&1) direction += (((stepper_position_ - 0) + 4)&7) - 4;
|
||||
if(lines&2) direction += (((stepper_position_ - 2) + 4)&7) - 4;
|
||||
if(lines&4) direction += (((stepper_position_ - 4) + 4)&7) - 4;
|
||||
if(lines&8) direction += (((stepper_position_ - 6) + 4)&7) - 4;
|
||||
const int bits_set = (lines&1) + ((lines >> 1)&1) + ((lines >> 2)&1) + ((lines >> 3)&1);
|
||||
direction /= bits_set;
|
||||
|
||||
// Compare to the stepper position to decide whether that pulls in the
|
||||
// current cog notch, or grabs a later one.
|
||||
step(Storage::Disk::HeadPosition(-direction, 4));
|
||||
stepper_position_ = (stepper_position_ - direction + 8) & 7;
|
||||
}
|
||||
stepper_mask_ = lines;
|
||||
}
|
||||
|
||||
bool DiskIIDrive::read() {
|
||||
return !!(stepper_mask_ & 2) || get_is_read_only();
|
||||
}
|
||||
33
Components/DiskII/DiskIIDrive.hpp
Normal file
33
Components/DiskII/DiskIIDrive.hpp
Normal file
@@ -0,0 +1,33 @@
|
||||
//
|
||||
// DiskIIDrive.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 20/11/2020.
|
||||
// Copyright © 2020 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef DiskIIDrive_hpp
|
||||
#define DiskIIDrive_hpp
|
||||
|
||||
#include "IWM.hpp"
|
||||
|
||||
namespace Apple {
|
||||
namespace Disk {
|
||||
|
||||
class DiskIIDrive: public IWMDrive {
|
||||
public:
|
||||
DiskIIDrive(int input_clock_rate);
|
||||
|
||||
private:
|
||||
void set_enabled(bool) final;
|
||||
void set_control_lines(int) final;
|
||||
bool read() final;
|
||||
|
||||
int stepper_mask_ = 0;
|
||||
int stepper_position_ = 0;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* DiskIIDrive_hpp */
|
||||
@@ -8,6 +8,11 @@
|
||||
|
||||
#include "IWM.hpp"
|
||||
|
||||
#ifndef NDEBUG
|
||||
#define NDEBUG
|
||||
#endif
|
||||
|
||||
#define LOG_PREFIX "[IWM] "
|
||||
#include "../../Outputs/Log.hpp"
|
||||
|
||||
using namespace Apple;
|
||||
@@ -50,7 +55,7 @@ uint8_t IWM::read(int address) {
|
||||
|
||||
switch(state_ & (Q6 | Q7 | ENABLE)) {
|
||||
default:
|
||||
LOG("[IWM] Invalid read\n");
|
||||
LOG("Invalid read\n");
|
||||
return 0xff;
|
||||
|
||||
// "Read all 1s".
|
||||
@@ -62,9 +67,8 @@ uint8_t IWM::read(int address) {
|
||||
const auto result = data_register_;
|
||||
|
||||
if(data_register_ & 0x80) {
|
||||
// printf("\n\nIWM:%02x\n\n", data_register_);
|
||||
// printf(".");
|
||||
data_register_ = 0;
|
||||
// LOG("Reading data: " << PADHEX(2) << int(result));
|
||||
}
|
||||
// LOG("Reading data register: " << PADHEX(2) << int(result));
|
||||
|
||||
@@ -99,7 +103,7 @@ uint8_t IWM::read(int address) {
|
||||
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_));
|
||||
// LOG("Reading write handshake: " << PADHEX(2) << int(0x3f | write_handshake_));
|
||||
return 0x3f | write_handshake_;
|
||||
}
|
||||
|
||||
@@ -128,13 +132,21 @@ void IWM::write(int address, uint8_t input) {
|
||||
|
||||
mode_ = input;
|
||||
|
||||
// TEMPORARY. To test for the unimplemented mode.
|
||||
if(input&0x2) {
|
||||
LOG("Switched to asynchronous mode");
|
||||
} else {
|
||||
LOG("Switched to synchronous mode");
|
||||
}
|
||||
|
||||
switch(mode_ & 0x18) {
|
||||
case 0x00: bit_length_ = Cycles(24); break; // slow mode, 7Mhz
|
||||
case 0x08: bit_length_ = Cycles(12); break; // fast mode, 7Mhz
|
||||
case 0x00: bit_length_ = Cycles(28); break; // slow mode, 7Mhz
|
||||
case 0x08: bit_length_ = Cycles(14); 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_));
|
||||
LOG("Mode is now " << PADHEX(2) << int(mode_));
|
||||
LOG("New bit length is " << std::dec << bit_length_.as_integral());
|
||||
break;
|
||||
|
||||
case Q7|Q6|ENABLE: // Write data register.
|
||||
@@ -248,6 +260,7 @@ void IWM::run_for(const Cycles cycles) {
|
||||
drives_[active_drive_]->run_for(Cycles(1));
|
||||
++cycles_since_shift_;
|
||||
if(cycles_since_shift_ == bit_length_ + error_margin) {
|
||||
// LOG("Shifting 0 at " << std::dec << cycles_since_shift_.as_integral());
|
||||
propose_shift(0);
|
||||
}
|
||||
}
|
||||
@@ -263,41 +276,45 @@ void IWM::run_for(const Cycles 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_;
|
||||
while(cycles_since_shift_ + integer_cycles >= bit_length_) {
|
||||
const auto cycles_until_write = bit_length_ - cycles_since_shift_;
|
||||
if(drives_[active_drive_]) {
|
||||
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;
|
||||
}
|
||||
shift_register_ <<= 1;
|
||||
|
||||
integer_cycles -= cycles_until_write.as_integral();
|
||||
cycles_since_shift_ = Cycles(0);
|
||||
integer_cycles -= cycles_until_write.as_integral();
|
||||
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();
|
||||
}
|
||||
--output_bits_remaining_;
|
||||
if(!output_bits_remaining_) {
|
||||
if(!(write_handshake_ & 0x80)) {
|
||||
shift_register_ = next_output_;
|
||||
output_bits_remaining_ = 8;
|
||||
// LOG("Next byte: " << PADHEX(2) << int(shift_register_));
|
||||
} else {
|
||||
write_handshake_ &= ~0x40;
|
||||
if(drives_[active_drive_]) drives_[active_drive_]->end_writing();
|
||||
LOG("Overrun; done.");
|
||||
output_bits_remaining_ = 1;
|
||||
}
|
||||
}
|
||||
|
||||
cycles_since_shift_ = integer_cycles;
|
||||
if(integer_cycles) {
|
||||
drives_[active_drive_]->run_for(cycles_since_shift_);
|
||||
// Either way, the IWM is ready for more data.
|
||||
write_handshake_ |= 0x80;
|
||||
}
|
||||
} else {
|
||||
drives_[active_drive_]->run_for(cycles);
|
||||
}
|
||||
|
||||
// Either some bits were output, in which case cycles_since_shift_ is no 0 and
|
||||
// integer_cycles is some number less than bit_length_, or none were and
|
||||
// cycles_since_shift_ + integer_cycles is less than bit_length, and the new
|
||||
// part should be accumulated.
|
||||
cycles_since_shift_ += integer_cycles;
|
||||
|
||||
if(drives_[active_drive_] && integer_cycles) {
|
||||
drives_[active_drive_]->run_for(cycles_since_shift_);
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -332,12 +349,12 @@ void IWM::select_shift_mode() {
|
||||
}
|
||||
|
||||
// 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_integral()), false);
|
||||
if(old_shift_mode != ShiftMode::Writing && shift_mode_ == ShiftMode::Writing) {
|
||||
if(drives_[active_drive_]) drives_[active_drive_]->begin_writing(Storage::Time(1, clock_rate_ / bit_length_.as_integral()), false);
|
||||
shift_register_ = next_output_;
|
||||
write_handshake_ |= 0x80 | 0x40;
|
||||
output_bits_remaining_ = 8;
|
||||
LOG("Seeding output with " << PADHEX(2) << shift_register_);
|
||||
LOG("Seeding output with " << PADHEX(2) << int(shift_register_));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -351,6 +368,7 @@ void IWM::process_event(const Storage::Disk::Drive::Event &event) {
|
||||
switch(event.type) {
|
||||
case Storage::Disk::Track::Event::IndexHole: return;
|
||||
case Storage::Disk::Track::Event::FluxTransition:
|
||||
// LOG("Shifting 1 at " << std::dec << cycles_since_shift_.as_integral());
|
||||
propose_shift(1);
|
||||
break;
|
||||
}
|
||||
@@ -359,12 +377,13 @@ void IWM::process_event(const Storage::Disk::Drive::Event &event) {
|
||||
void IWM::propose_shift(uint8_t bit) {
|
||||
// TODO: synchronous mode.
|
||||
|
||||
// LOG("Shifting at " << std::dec << cycles_since_shift_.as_integral());
|
||||
// LOG("Shifting input");
|
||||
|
||||
// See above for text from the IWM patent, column 7, around line 35 onwards.
|
||||
// The error_margin here implements the 'before' part of that contract.
|
||||
//
|
||||
// Basic effective logic: if at least 1 is fozund in the bit_length_ cycles centred
|
||||
// Basic effective logic: if at least 1 is found in the bit_length_ cycles centred
|
||||
// on the current expected bit delivery time as implied by cycles_since_shift_,
|
||||
// shift in a 1 and start a new window wherever the first found 1 was.
|
||||
//
|
||||
@@ -374,6 +393,7 @@ void IWM::propose_shift(uint8_t bit) {
|
||||
|
||||
shift_register_ = uint8_t((shift_register_ << 1) | bit);
|
||||
if(shift_register_ & 0x80) {
|
||||
// if(data_register_ & 0x80) LOG("Byte missed");
|
||||
data_register_ = shift_register_;
|
||||
shift_register_ = 0;
|
||||
}
|
||||
@@ -386,16 +406,20 @@ void IWM::propose_shift(uint8_t bit) {
|
||||
|
||||
void IWM::set_drive(int slot, IWMDrive *drive) {
|
||||
drives_[slot] = drive;
|
||||
drive->set_event_delegate(this);
|
||||
drive->set_clocking_hint_observer(this);
|
||||
if(drive) {
|
||||
drive->set_event_delegate(this);
|
||||
drive->set_clocking_hint_observer(this);
|
||||
} else {
|
||||
drive_is_rotating_[slot] = false;
|
||||
}
|
||||
}
|
||||
|
||||
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])) {
|
||||
if(drives_[0] && component == static_cast<ClockingHint::Source *>(drives_[0])) {
|
||||
drive_is_rotating_[0] = is_rotating;
|
||||
} else {
|
||||
} else if(drives_[1] && component == static_cast<ClockingHint::Source *>(drives_[1])) {
|
||||
drive_is_rotating_[1] = is_rotating;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -142,8 +142,11 @@ bool DoubleDensityDrive::read() {
|
||||
return !get_is_track_zero();
|
||||
|
||||
case CA1|CA0: // Disk has been ejected.
|
||||
// (0 = user has ejected disk)
|
||||
return !has_new_disk_;
|
||||
// (1 = user has ejected disk)
|
||||
//
|
||||
// TODO: does this really mean _user_ has ejected disk? If so then I should avoid
|
||||
// changing the flag upon a programmatic eject.
|
||||
return has_new_disk_;
|
||||
|
||||
case CA1|CA0|SEL: // Tachometer.
|
||||
// (arbitrary)
|
||||
@@ -170,12 +173,10 @@ bool DoubleDensityDrive::read() {
|
||||
|
||||
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;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void DoubleDensityDrive::did_set_disk() {
|
||||
has_new_disk_ = true;
|
||||
void DoubleDensityDrive::did_set_disk(bool did_replace) {
|
||||
has_new_disk_ = did_replace;
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ class DoubleDensityDrive: public IWMDrive {
|
||||
/*!
|
||||
@returns @c true if this is an 800kb drive; @c false otherwise.
|
||||
*/
|
||||
bool is_800k() {
|
||||
bool is_800k() const {
|
||||
return is_800k_;
|
||||
}
|
||||
|
||||
@@ -39,7 +39,7 @@ class DoubleDensityDrive: public IWMDrive {
|
||||
|
||||
// To receive the proper notifications from Storage::Disk::Drive.
|
||||
void did_step(Storage::Disk::HeadPosition to_position) final;
|
||||
void did_set_disk() final;
|
||||
void did_set_disk(bool) final;
|
||||
|
||||
const bool is_800k_;
|
||||
bool has_new_disk_ = false;
|
||||
|
||||
@@ -8,13 +8,18 @@
|
||||
|
||||
#include "Line.hpp"
|
||||
|
||||
#include <cassert>
|
||||
#include <limits>
|
||||
|
||||
using namespace Serial;
|
||||
|
||||
void Line::set_writer_clock_rate(HalfCycles clock_rate) {
|
||||
template <bool include_clock>
|
||||
void Line<include_clock>::set_writer_clock_rate(HalfCycles clock_rate) {
|
||||
clock_rate_ = clock_rate;
|
||||
}
|
||||
|
||||
void Line::advance_writer(HalfCycles cycles) {
|
||||
template <bool include_clock>
|
||||
void Line<include_clock>::advance_writer(HalfCycles cycles) {
|
||||
if(cycles == HalfCycles(0)) return;
|
||||
|
||||
const auto integral_cycles = cycles.as_integral();
|
||||
@@ -25,7 +30,9 @@ void Line::advance_writer(HalfCycles cycles) {
|
||||
transmission_extra_ -= integral_cycles;
|
||||
if(transmission_extra_ <= 0) {
|
||||
transmission_extra_ = 0;
|
||||
update_delegate(level_);
|
||||
if constexpr (!include_clock) {
|
||||
update_delegate(level_);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -38,12 +45,17 @@ void Line::advance_writer(HalfCycles cycles) {
|
||||
auto iterator = events_.begin() + 1;
|
||||
while(iterator != events_.end() && iterator->type != Event::Delay) {
|
||||
level_ = iterator->type == Event::SetHigh;
|
||||
if constexpr(include_clock) {
|
||||
update_delegate(level_);
|
||||
}
|
||||
++iterator;
|
||||
}
|
||||
events_.erase(events_.begin(), iterator);
|
||||
|
||||
if(old_level != level_) {
|
||||
update_delegate(old_level);
|
||||
if constexpr (!include_clock) {
|
||||
if(old_level != level_) {
|
||||
update_delegate(old_level);
|
||||
}
|
||||
}
|
||||
|
||||
// Book enough extra time for the read delegate to be posted
|
||||
@@ -60,7 +72,8 @@ void Line::advance_writer(HalfCycles cycles) {
|
||||
}
|
||||
}
|
||||
|
||||
void Line::write(bool level) {
|
||||
template <bool include_clock>
|
||||
void Line<include_clock>::write(bool level) {
|
||||
if(!events_.empty()) {
|
||||
events_.emplace_back();
|
||||
events_.back().type = level ? Event::SetHigh : Event::SetLow;
|
||||
@@ -70,7 +83,8 @@ void Line::write(bool level) {
|
||||
}
|
||||
}
|
||||
|
||||
void Line::write(HalfCycles cycles, int count, int levels) {
|
||||
template <bool include_clock>
|
||||
template <bool lsb_first, typename IntT> void Line<include_clock>::write_internal(HalfCycles cycles, int count, IntT levels) {
|
||||
remaining_delays_ += count * cycles.as_integral();
|
||||
|
||||
auto event = events_.size();
|
||||
@@ -78,63 +92,122 @@ void Line::write(HalfCycles cycles, int count, int levels) {
|
||||
while(count--) {
|
||||
events_[event].type = Event::Delay;
|
||||
events_[event].delay = int(cycles.as_integral());
|
||||
events_[event+1].type = (levels&1) ? Event::SetHigh : Event::SetLow;
|
||||
levels >>= 1;
|
||||
IntT bit;
|
||||
if constexpr (lsb_first) {
|
||||
bit = levels & 1;
|
||||
levels >>= 1;
|
||||
} else {
|
||||
constexpr auto top_bit = IntT(0x80) << ((sizeof(IntT) - 1) * 8);
|
||||
bit = levels & top_bit;
|
||||
levels <<= 1;
|
||||
}
|
||||
|
||||
events_[event+1].type = bit ? Event::SetHigh : Event::SetLow;
|
||||
event += 2;
|
||||
}
|
||||
}
|
||||
|
||||
void Line::reset_writing() {
|
||||
template <bool include_clock>
|
||||
void Line<include_clock>::write(HalfCycles cycles, int count, int levels) {
|
||||
write_internal<true, int>(cycles, count, levels);
|
||||
}
|
||||
|
||||
template <bool include_clock>
|
||||
template <bool lsb_first, typename IntT> void Line<include_clock>::write(HalfCycles cycles, IntT value) {
|
||||
write_internal<lsb_first, IntT>(cycles, 8 * sizeof(IntT), value);
|
||||
}
|
||||
|
||||
template <bool include_clock>
|
||||
void Line<include_clock>::reset_writing() {
|
||||
remaining_delays_ = 0;
|
||||
events_.clear();
|
||||
}
|
||||
|
||||
bool Line::read() const {
|
||||
template <bool include_clock>
|
||||
bool Line<include_clock>::read() const {
|
||||
return level_;
|
||||
}
|
||||
|
||||
void Line::set_read_delegate(ReadDelegate *delegate, Storage::Time bit_length) {
|
||||
template <bool include_clock>
|
||||
void Line<include_clock>::set_read_delegate(ReadDelegate *delegate, Storage::Time bit_length) {
|
||||
read_delegate_ = delegate;
|
||||
read_delegate_bit_length_ = bit_length;
|
||||
read_delegate_bit_length_.simplify();
|
||||
write_cycles_since_delegate_call_ = 0;
|
||||
if constexpr (!include_clock) {
|
||||
assert(bit_length > Storage::Time(0));
|
||||
read_delegate_bit_length_ = bit_length;
|
||||
read_delegate_bit_length_.simplify();
|
||||
write_cycles_since_delegate_call_ = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void Line::update_delegate(bool level) {
|
||||
template <bool include_clock>
|
||||
void Line<include_clock>::update_delegate(bool level) {
|
||||
// Exit early if there's no delegate, or if the delegate is waiting for
|
||||
// zero and this isn't zero.
|
||||
if(!read_delegate_) return;
|
||||
|
||||
const int cycles_to_forward = write_cycles_since_delegate_call_;
|
||||
write_cycles_since_delegate_call_ = 0;
|
||||
if(level && read_delegate_phase_ == ReadDelegatePhase::WaitingForZero) return;
|
||||
if constexpr (!include_clock) {
|
||||
const int cycles_to_forward = write_cycles_since_delegate_call_;
|
||||
write_cycles_since_delegate_call_ = 0;
|
||||
if(level && read_delegate_phase_ == ReadDelegatePhase::WaitingForZero) return;
|
||||
|
||||
// Deal with a transition out of waiting-for-zero mode by seeding time left
|
||||
// in bit at half a bit.
|
||||
if(read_delegate_phase_ == ReadDelegatePhase::WaitingForZero) {
|
||||
time_left_in_bit_ = read_delegate_bit_length_;
|
||||
time_left_in_bit_.clock_rate <<= 1;
|
||||
read_delegate_phase_ = ReadDelegatePhase::Serialising;
|
||||
}
|
||||
|
||||
// Forward as many bits as occur.
|
||||
Storage::Time time_left(cycles_to_forward, int(clock_rate_.as_integral()));
|
||||
const int bit = level ? 1 : 0;
|
||||
int bits = 0;
|
||||
while(time_left >= time_left_in_bit_) {
|
||||
++bits;
|
||||
if(!read_delegate_->serial_line_did_produce_bit(this, bit)) {
|
||||
read_delegate_phase_ = ReadDelegatePhase::WaitingForZero;
|
||||
if(bit) return;
|
||||
// Deal with a transition out of waiting-for-zero mode by seeding time left
|
||||
// in bit at half a bit.
|
||||
if(read_delegate_phase_ == ReadDelegatePhase::WaitingForZero) {
|
||||
time_left_in_bit_ = read_delegate_bit_length_;
|
||||
time_left_in_bit_.clock_rate <<= 1;
|
||||
read_delegate_phase_ = ReadDelegatePhase::Serialising;
|
||||
}
|
||||
|
||||
time_left -= time_left_in_bit_;
|
||||
time_left_in_bit_ = read_delegate_bit_length_;
|
||||
// Forward as many bits as occur.
|
||||
Storage::Time time_left(cycles_to_forward, int(clock_rate_.as_integral()));
|
||||
const int bit = level ? 1 : 0;
|
||||
int bits = 0;
|
||||
while(time_left >= time_left_in_bit_) {
|
||||
++bits;
|
||||
if(!read_delegate_->serial_line_did_produce_bit(this, bit)) {
|
||||
read_delegate_phase_ = ReadDelegatePhase::WaitingForZero;
|
||||
if(bit) return;
|
||||
}
|
||||
|
||||
time_left -= time_left_in_bit_;
|
||||
time_left_in_bit_ = read_delegate_bit_length_;
|
||||
}
|
||||
time_left_in_bit_ -= time_left;
|
||||
} else {
|
||||
read_delegate_->serial_line_did_produce_bit(this, level);
|
||||
}
|
||||
time_left_in_bit_ -= time_left;
|
||||
}
|
||||
|
||||
Cycles::IntType Line::minimum_write_cycles_for_read_delegate_bit() {
|
||||
template <bool include_clock>
|
||||
Cycles::IntType Line<include_clock>::minimum_write_cycles_for_read_delegate_bit() {
|
||||
if(!read_delegate_) return 0;
|
||||
return 1 + (read_delegate_bit_length_ * unsigned(clock_rate_.as_integral())).get<int>();
|
||||
return 1 + (read_delegate_bit_length_ * unsigned(clock_rate_.as_integral())).template get<int>();
|
||||
}
|
||||
|
||||
//
|
||||
// Explicitly instantiate the meaningful instances of templates above;
|
||||
// this class uses templates primarily to keep the interface compact and
|
||||
// to take advantage of constexpr functionality selection, not so as
|
||||
// to be generic.
|
||||
//
|
||||
|
||||
template class Serial::Line<true>;
|
||||
template class Serial::Line<false>;
|
||||
|
||||
template void Line<true>::write<true, uint8_t>(HalfCycles, uint8_t);
|
||||
template void Line<true>::write<false, uint8_t>(HalfCycles, uint8_t);
|
||||
template void Line<true>::write<true, uint16_t>(HalfCycles, uint16_t);
|
||||
template void Line<true>::write<false, uint16_t>(HalfCycles, uint16_t);
|
||||
template void Line<true>::write<true, uint32_t>(HalfCycles, uint32_t);
|
||||
template void Line<true>::write<false, uint32_t>(HalfCycles, uint32_t);
|
||||
template void Line<true>::write<true, uint64_t>(HalfCycles, uint64_t);
|
||||
template void Line<true>::write<false, uint64_t>(HalfCycles, uint64_t);
|
||||
|
||||
template void Line<false>::write<true, uint8_t>(HalfCycles, uint8_t);
|
||||
template void Line<false>::write<false, uint8_t>(HalfCycles, uint8_t);
|
||||
template void Line<false>::write<true, uint16_t>(HalfCycles, uint16_t);
|
||||
template void Line<false>::write<false, uint16_t>(HalfCycles, uint16_t);
|
||||
template void Line<false>::write<true, uint32_t>(HalfCycles, uint32_t);
|
||||
template void Line<false>::write<false, uint32_t>(HalfCycles, uint32_t);
|
||||
template void Line<false>::write<true, uint64_t>(HalfCycles, uint64_t);
|
||||
template void Line<false>::write<false, uint64_t>(HalfCycles, uint64_t);
|
||||
|
||||
@@ -17,25 +17,42 @@
|
||||
namespace Serial {
|
||||
|
||||
/*!
|
||||
@c Line connects a single reader and a single writer, allowing timestamped events to be
|
||||
published and consumed, potentially with a clock conversion in between. It allows line
|
||||
levels to be written and read in larger collections.
|
||||
Models one of two connections, either:
|
||||
|
||||
It is assumed that the owner of the reader and writer will ensure that the reader will never
|
||||
get ahead of the writer. If the writer posts events behind the reader they will simply be
|
||||
given instanteous effect.
|
||||
(i) a plain single-line serial; or
|
||||
(ii) a two-line data + clock.
|
||||
|
||||
In both cases connects a single reader to a single writer.
|
||||
|
||||
When operating as a single-line serial connection:
|
||||
|
||||
Provides a mechanism for the writer to enqueue levels arbitrarily far
|
||||
ahead of the current time, which are played back only as the
|
||||
write queue advances. Permits the reader and writer to work at
|
||||
different clock rates, and provides a virtual delegate protocol with
|
||||
start bit detection.
|
||||
|
||||
Can alternatively be used by reader and/or writer only in immediate
|
||||
mode, getting or setting the current level now, without the actor on
|
||||
the other end having to have made the same decision.
|
||||
|
||||
When operating as a two-line connection:
|
||||
|
||||
Implies a clock over enqueued data and provides the reader with
|
||||
all enqueued bits at appropriate times.
|
||||
*/
|
||||
class Line {
|
||||
template <bool include_clock> class Line {
|
||||
public:
|
||||
void set_writer_clock_rate(HalfCycles clock_rate);
|
||||
|
||||
/// Advances the read position by @c cycles relative to the writer's
|
||||
/// clock rate.
|
||||
void advance_writer(HalfCycles cycles);
|
||||
|
||||
/// Sets the line to @c level.
|
||||
/// Sets the line to @c level instantaneously.
|
||||
void write(bool level);
|
||||
|
||||
/// @returns The instantaneous level of this line.
|
||||
bool read() const;
|
||||
|
||||
/// Sets the denominator for the between levels for any data enqueued
|
||||
/// via an @c write.
|
||||
void set_writer_clock_rate(HalfCycles clock_rate);
|
||||
|
||||
/// Enqueues @c count level changes, the first occurring immediately
|
||||
/// after the final event currently posted and each subsequent event
|
||||
/// occurring @c cycles after the previous. An additional gap of @c cycles
|
||||
@@ -44,6 +61,10 @@ class Line {
|
||||
/// relative to the writer's clock rate.
|
||||
void write(HalfCycles cycles, int count, int levels);
|
||||
|
||||
/// Enqueus every bit from @c value as per the rules of write(HalfCycles, int, int),
|
||||
/// either in LSB or MSB order as per the @c lsb_first template flag.
|
||||
template <bool lsb_first, typename IntT> void write(HalfCycles cycles, IntT value);
|
||||
|
||||
/// @returns the number of cycles until currently enqueued write data is exhausted.
|
||||
forceinline HalfCycles write_data_time_remaining() const {
|
||||
return HalfCycles(remaining_delays_);
|
||||
@@ -55,25 +76,36 @@ class Line {
|
||||
return HalfCycles(remaining_delays_ + transmission_extra_);
|
||||
}
|
||||
|
||||
/// Advances the read position by @c cycles relative to the writer's
|
||||
/// clock rate.
|
||||
void advance_writer(HalfCycles cycles);
|
||||
|
||||
/// Eliminates all future write states, leaving the output at whatever it is now.
|
||||
void reset_writing();
|
||||
|
||||
/// @returns The instantaneous level of this line.
|
||||
bool read() const;
|
||||
|
||||
struct ReadDelegate {
|
||||
virtual bool serial_line_did_produce_bit(Line *line, int bit) = 0;
|
||||
};
|
||||
/*!
|
||||
Sets a read delegate, which will receive samples of the output level every
|
||||
@c bit_lengths of a second apart subject to a state machine:
|
||||
Sets a read delegate.
|
||||
|
||||
* initially no bits will be delivered;
|
||||
* when a zero level is first detected, the line will wait half a bit's length, then start
|
||||
sampling at single-bit intervals, passing each bit to the delegate while it returns @c true;
|
||||
* as soon as the delegate returns @c false, the line will return to the initial state.
|
||||
Single line serial connection:
|
||||
|
||||
The delegate will receive samples of the output level every
|
||||
@c bit_lengths of a second apart subject to a state machine:
|
||||
|
||||
* initially no bits will be delivered;
|
||||
* when a zero level is first detected, the line will wait half a bit's length, then start
|
||||
sampling at single-bit intervals, passing each bit to the delegate while it returns @c true;
|
||||
* as soon as the delegate returns @c false, the line will return to the initial state.
|
||||
|
||||
Two-line clock + data connection:
|
||||
|
||||
The delegate will receive every bit that has been enqueued, spaced as nominated
|
||||
by the writer. @c bit_length is ignored, as is the return result of
|
||||
@c ReadDelegate::serial_line_did_produce_bit.
|
||||
*/
|
||||
void set_read_delegate(ReadDelegate *delegate, Storage::Time bit_length);
|
||||
void set_read_delegate(ReadDelegate *delegate, Storage::Time bit_length = Storage::Time());
|
||||
|
||||
private:
|
||||
struct Event {
|
||||
@@ -98,6 +130,9 @@ class Line {
|
||||
|
||||
void update_delegate(bool level);
|
||||
HalfCycles::IntType minimum_write_cycles_for_read_delegate_bit();
|
||||
|
||||
template <bool lsb_first, typename IntT> void
|
||||
write_internal(HalfCycles, int, IntT);
|
||||
};
|
||||
|
||||
/*!
|
||||
|
||||
@@ -11,53 +11,53 @@
|
||||
using namespace Concurrency;
|
||||
|
||||
AsyncTaskQueue::AsyncTaskQueue()
|
||||
#ifndef __APPLE__
|
||||
: should_destruct_(false)
|
||||
#endif
|
||||
{
|
||||
#ifdef __APPLE__
|
||||
serial_dispatch_queue_ = dispatch_queue_create("com.thomasharte.clocksignal.asyntaskqueue", DISPATCH_QUEUE_SERIAL);
|
||||
#ifndef USE_GCD
|
||||
:
|
||||
should_destruct_(false),
|
||||
thread_([this] () {
|
||||
while(!should_destruct_) {
|
||||
std::function<void(void)> next_function;
|
||||
|
||||
// Take lock, check for a new task.
|
||||
std::unique_lock lock(queue_mutex_);
|
||||
if(!pending_tasks_.empty()) {
|
||||
next_function = pending_tasks_.front();
|
||||
pending_tasks_.pop_front();
|
||||
}
|
||||
|
||||
if(next_function) {
|
||||
// If there is a task, release lock and perform it.
|
||||
lock.unlock();
|
||||
next_function();
|
||||
} else {
|
||||
// If there isn't a task, atomically block on the processing condition and release the lock
|
||||
// until there's something pending (and then release it again via scope).
|
||||
processing_condition_.wait(lock);
|
||||
}
|
||||
}
|
||||
})
|
||||
#else
|
||||
thread_ = std::make_unique<std::thread>([this]() {
|
||||
while(!should_destruct_) {
|
||||
std::function<void(void)> next_function;
|
||||
|
||||
// Take lock, check for a new task
|
||||
std::unique_lock lock(queue_mutex_);
|
||||
if(!pending_tasks_.empty()) {
|
||||
next_function = pending_tasks_.front();
|
||||
pending_tasks_.pop_front();
|
||||
}
|
||||
|
||||
if(next_function) {
|
||||
// If there is a task, release lock and perform it
|
||||
lock.unlock();
|
||||
next_function();
|
||||
} else {
|
||||
// If there isn't a task, atomically block on the processing condition and release the lock
|
||||
// until there's something pending (and then release it again via scope)
|
||||
processing_condition_.wait(lock);
|
||||
}
|
||||
}
|
||||
});
|
||||
: serial_dispatch_queue_(dispatch_queue_create("com.thomasharte.clocksignal.asyntaskqueue", DISPATCH_QUEUE_SERIAL))
|
||||
#endif
|
||||
}
|
||||
{}
|
||||
|
||||
AsyncTaskQueue::~AsyncTaskQueue() {
|
||||
#ifdef __APPLE__
|
||||
#ifdef USE_GCD
|
||||
flush();
|
||||
dispatch_release(serial_dispatch_queue_);
|
||||
serial_dispatch_queue_ = nullptr;
|
||||
#else
|
||||
// Set should destruct, and then give the thread a bit of a nudge
|
||||
// via an empty enqueue.
|
||||
should_destruct_ = true;
|
||||
enqueue([](){});
|
||||
thread_->join();
|
||||
thread_.reset();
|
||||
|
||||
// Wait for the thread safely to terminate.
|
||||
thread_.join();
|
||||
#endif
|
||||
}
|
||||
|
||||
void AsyncTaskQueue::enqueue(std::function<void(void)> function) {
|
||||
#ifdef __APPLE__
|
||||
#ifdef USE_GCD
|
||||
dispatch_async(serial_dispatch_queue_, ^{function();});
|
||||
#else
|
||||
std::lock_guard lock(queue_mutex_);
|
||||
@@ -67,7 +67,7 @@ void AsyncTaskQueue::enqueue(std::function<void(void)> function) {
|
||||
}
|
||||
|
||||
void AsyncTaskQueue::flush() {
|
||||
#ifdef __APPLE__
|
||||
#ifdef USE_GCD
|
||||
dispatch_sync(serial_dispatch_queue_, ^{});
|
||||
#else
|
||||
auto flush_mutex = std::make_shared<std::mutex>();
|
||||
@@ -88,18 +88,22 @@ DeferringAsyncTaskQueue::~DeferringAsyncTaskQueue() {
|
||||
|
||||
void DeferringAsyncTaskQueue::defer(std::function<void(void)> function) {
|
||||
if(!deferred_tasks_) {
|
||||
deferred_tasks_ = std::make_shared<std::list<std::function<void(void)>>>();
|
||||
deferred_tasks_ = std::make_unique<TaskList>();
|
||||
}
|
||||
deferred_tasks_->push_back(function);
|
||||
}
|
||||
|
||||
void DeferringAsyncTaskQueue::perform() {
|
||||
if(!deferred_tasks_) return;
|
||||
std::shared_ptr<std::list<std::function<void(void)>>> deferred_tasks = deferred_tasks_;
|
||||
deferred_tasks_.reset();
|
||||
enqueue([deferred_tasks] {
|
||||
enqueue([deferred_tasks_raw = deferred_tasks_.release()] {
|
||||
std::unique_ptr<TaskList> deferred_tasks(deferred_tasks_raw);
|
||||
for(const auto &function : *deferred_tasks) {
|
||||
function();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void DeferringAsyncTaskQueue::flush() {
|
||||
perform();
|
||||
AsyncTaskQueue::flush();
|
||||
}
|
||||
|
||||
@@ -16,12 +16,15 @@
|
||||
#include <memory>
|
||||
#include <thread>
|
||||
|
||||
#ifdef __APPLE__
|
||||
#if defined(__APPLE__) && !defined(IGNORE_APPLE)
|
||||
#include <dispatch/dispatch.h>
|
||||
#define USE_GCD
|
||||
#endif
|
||||
|
||||
namespace Concurrency {
|
||||
|
||||
using TaskList = std::list<std::function<void(void)>>;
|
||||
|
||||
/*!
|
||||
An async task queue allows a caller to enqueue void(void) functions. Those functions are guaranteed
|
||||
to be performed serially and asynchronously from the caller. A caller may also request to flush,
|
||||
@@ -47,15 +50,15 @@ class AsyncTaskQueue {
|
||||
void flush();
|
||||
|
||||
private:
|
||||
#ifdef __APPLE__
|
||||
#ifdef USE_GCD
|
||||
dispatch_queue_t serial_dispatch_queue_;
|
||||
#else
|
||||
std::unique_ptr<std::thread> thread_;
|
||||
|
||||
std::mutex queue_mutex_;
|
||||
std::list<std::function<void(void)>> pending_tasks_;
|
||||
std::condition_variable processing_condition_;
|
||||
std::atomic_bool should_destruct_;
|
||||
std::condition_variable processing_condition_;
|
||||
std::mutex queue_mutex_;
|
||||
TaskList pending_tasks_;
|
||||
|
||||
std::thread thread_;
|
||||
#endif
|
||||
};
|
||||
|
||||
@@ -86,10 +89,13 @@ class DeferringAsyncTaskQueue: public AsyncTaskQueue {
|
||||
*/
|
||||
void perform();
|
||||
|
||||
/*!
|
||||
Blocks the caller until all previously-enqueud functions have completed.
|
||||
*/
|
||||
void flush();
|
||||
|
||||
private:
|
||||
// TODO: this is a shared_ptr because of the issues capturing moveables in C++11;
|
||||
// switch to a unique_ptr if/when adapting to C++14
|
||||
std::shared_ptr<std::list<std::function<void(void)>>> deferred_tasks_;
|
||||
std::unique_ptr<TaskList> deferred_tasks_;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#ifndef Joystick_hpp
|
||||
#define Joystick_hpp
|
||||
|
||||
#include <cstddef>
|
||||
#include <vector>
|
||||
|
||||
namespace Inputs {
|
||||
@@ -35,7 +36,10 @@ class Joystick {
|
||||
// Fire buttons.
|
||||
Fire,
|
||||
// Other labelled keys.
|
||||
Key
|
||||
Key,
|
||||
|
||||
// The maximum value this enum can contain.
|
||||
Max = Key
|
||||
};
|
||||
const Type type;
|
||||
|
||||
|
||||
@@ -8,6 +8,8 @@
|
||||
|
||||
#include "Keyboard.hpp"
|
||||
|
||||
#include <cstddef>
|
||||
|
||||
using namespace Inputs;
|
||||
|
||||
Keyboard::Keyboard(const std::set<Key> &essential_modifiers) : essential_modifiers_(essential_modifiers) {
|
||||
|
||||
24
InstructionSets/AccessType.hpp
Normal file
24
InstructionSets/AccessType.hpp
Normal file
@@ -0,0 +1,24 @@
|
||||
//
|
||||
// AccessType.h
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 16/01/21.
|
||||
// Copyright © 2021 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef AccessType_h
|
||||
#define AccessType_h
|
||||
|
||||
namespace InstructionSet {
|
||||
|
||||
enum class AccessType {
|
||||
None,
|
||||
Read,
|
||||
Write,
|
||||
ReadModifyWrite
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
|
||||
#endif /* AccessType_h */
|
||||
200
InstructionSets/CachingExecutor.hpp
Normal file
200
InstructionSets/CachingExecutor.hpp
Normal file
@@ -0,0 +1,200 @@
|
||||
//
|
||||
// CachingExecutor.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 16/01/2021.
|
||||
// Copyright © 2021 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef CachingExecutor_hpp
|
||||
#define CachingExecutor_hpp
|
||||
|
||||
#include "../Numeric/Sizes.hpp"
|
||||
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
#include <limits>
|
||||
#include <list>
|
||||
#include <map>
|
||||
#include <queue>
|
||||
#include <unordered_map>
|
||||
|
||||
namespace InstructionSet {
|
||||
|
||||
/*!
|
||||
A caching executor makes use of an instruction set-specific executor to cache 'performers' (i.e. function pointers)
|
||||
that result from decoding.
|
||||
|
||||
In other words, it's almost a JIT compiler, but producing threaded code (in the Forth sense) and then incurring whatever
|
||||
costs sit behind using the C ABI for calling. Since there'll always be exactly one parameter, being the specific executor,
|
||||
hopefully the calling costs are acceptable.
|
||||
|
||||
Intended usage is for specific executors to subclass from this and declare it a friend.
|
||||
|
||||
TODO: determine promises re: interruption, amongst other things.
|
||||
*/
|
||||
template <
|
||||
/// Indicates the Executor for this platform.
|
||||
typename Executor,
|
||||
/// Indicates the greatest value the program counter might take.
|
||||
uint64_t max_address,
|
||||
/// Indicates the maximum number of potential performers that will be provided.
|
||||
uint64_t max_performer_count,
|
||||
/// Provides the type of Instruction to expect.
|
||||
typename InstructionType,
|
||||
/// Indicates whether instructions should be treated as ephemeral or included in the cache.
|
||||
bool retain_instructions
|
||||
> class CachingExecutor {
|
||||
public:
|
||||
using Performer = void (Executor::*)();
|
||||
using PerformerIndex = typename MinIntTypeValue<max_performer_count>::type;
|
||||
using ProgramCounterType = typename MinIntTypeValue<max_address>::type;
|
||||
|
||||
// MARK: - Parser call-ins.
|
||||
|
||||
void announce_overflow(ProgramCounterType) {
|
||||
/*
|
||||
Should be impossible for now; this is intended to provide information
|
||||
when page caching.
|
||||
*/
|
||||
}
|
||||
void announce_instruction(ProgramCounterType, InstructionType instruction) {
|
||||
// Dutifully map the instruction to a performer and keep it.
|
||||
program_.push_back(static_cast<Executor *>(this)->action_for(instruction));
|
||||
|
||||
if constexpr (retain_instructions) {
|
||||
// TODO.
|
||||
}
|
||||
}
|
||||
|
||||
protected:
|
||||
|
||||
// Storage for the statically-allocated list of performers. It's a bit more
|
||||
// work for executors to fill this array, but subsequently performers can be
|
||||
// indexed by array position, which is a lot more compact than a generic pointer.
|
||||
std::array<Performer, max_performer_count+1> performers_;
|
||||
ProgramCounterType program_counter_;
|
||||
|
||||
/*!
|
||||
Moves the current point of execution to @c address, updating necessary performer caches
|
||||
and doing any translation as is necessary.
|
||||
*/
|
||||
void set_program_counter(ProgramCounterType address) {
|
||||
// Set flag to terminate any inner loop currently running through
|
||||
// previously-parsed content.
|
||||
has_branched_ = true;
|
||||
program_counter_ = address;
|
||||
|
||||
// Temporary implementation: just interpret.
|
||||
program_.clear();
|
||||
program_index_ = 0;
|
||||
static_cast<Executor *>(this)->parse(address, ProgramCounterType(max_address));
|
||||
|
||||
// const auto page = find_page(address);
|
||||
// const auto entry = page->entry_points.find(address);
|
||||
// if(entry == page->entry_points.end()) {
|
||||
// // Requested segment wasn't found; check whether it was
|
||||
// // within the recently translated list and otherwise
|
||||
// // translate it.
|
||||
// }
|
||||
}
|
||||
|
||||
/*!
|
||||
Indicates whether the processor is currently 'stopped', i.e. whether all attempts to run
|
||||
should produce no activity. Some processors have such a state when waiting for
|
||||
interrupts or for a reset.
|
||||
*/
|
||||
void set_is_stopped(bool) {}
|
||||
|
||||
/*!
|
||||
Executes up to the next branch.
|
||||
*/
|
||||
void run_to_branch() {
|
||||
has_branched_ = false;
|
||||
for(auto index: program_) {
|
||||
const auto performer = performers_[index];
|
||||
(static_cast<Executor *>(this)->*performer)();
|
||||
if(has_branched_) break;
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
Runs for @c duration; the intention is that subclasses provide a method
|
||||
that is clear about units, and call this to count down in whatever units they
|
||||
count down in.
|
||||
*/
|
||||
void run_for(int duration) {
|
||||
remaining_duration_ += duration;
|
||||
|
||||
while(remaining_duration_ > 0) {
|
||||
has_branched_ = false;
|
||||
Executor *const executor = static_cast<Executor *>(this);
|
||||
while(remaining_duration_ > 0 && !has_branched_) {
|
||||
const auto performer = performers_[program_[program_index_]];
|
||||
++program_index_;
|
||||
|
||||
(executor->*performer)();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
Should be called by a specific executor to subtract from the remaining
|
||||
running duration.
|
||||
*/
|
||||
inline void subtract_duration(int duration) {
|
||||
remaining_duration_ -= duration;
|
||||
}
|
||||
|
||||
private:
|
||||
bool has_branched_ = false;
|
||||
int remaining_duration_ = 0;
|
||||
std::vector<PerformerIndex> program_;
|
||||
size_t program_index_ = 0;
|
||||
|
||||
/* TODO: almost below here can be shoved off into an LRUCache object, or similar. */
|
||||
|
||||
// static constexpr size_t max_cached_pages = 64;
|
||||
|
||||
// struct Page {
|
||||
// std::map<ProgramCounterType, PerformerIndex> entry_points;
|
||||
|
||||
// TODO: can I statically these two? Should I?
|
||||
// std::vector<PerformerIndex> actions_;
|
||||
// std::vector<typename std::enable_if<!std::is_same<InstructionType, void>::value, InstructionType>::type> instructions_;
|
||||
// };
|
||||
// std::array<Page, max_cached_pages> pages_;
|
||||
|
||||
// Maps from page numbers to pages.
|
||||
// std::unordered_map<ProgramCounterType, Page *> cached_pages_;
|
||||
|
||||
// Maintains an LRU of recently-used pages in case of a need for reuse.
|
||||
// std::list<ProgramCounterType> touched_pages_;
|
||||
|
||||
/*!
|
||||
Finds or creates the page that contains @c address.
|
||||
*/
|
||||
/* Page *find_page(ProgramCounterType address) {
|
||||
// TODO: are 1kb pages always appropriate? Is 64 the correct amount to keep?
|
||||
const auto page_address = ProgramCounterType(address >> 10);
|
||||
|
||||
auto page = cached_pages_.find(page_address);
|
||||
if(page == cached_pages_.end()) {
|
||||
// Page wasn't found; either allocate a new one or
|
||||
// reuse one that already exists.
|
||||
if(cached_pages_.size() == max_cached_pages) {
|
||||
|
||||
} else {
|
||||
|
||||
}
|
||||
} else {
|
||||
// Page was found; LRU shuffle it.
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}*/
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif /* CachingExecutor_hpp */
|
||||
89
InstructionSets/Disassembler.hpp
Normal file
89
InstructionSets/Disassembler.hpp
Normal file
@@ -0,0 +1,89 @@
|
||||
//
|
||||
// Disassembler.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 26/01/2021.
|
||||
// Copyright © 2021 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef Disassembler_hpp
|
||||
#define Disassembler_hpp
|
||||
|
||||
#include "../Numeric/Sizes.hpp"
|
||||
|
||||
#include <list>
|
||||
#include <map>
|
||||
#include <set>
|
||||
|
||||
namespace InstructionSet {
|
||||
|
||||
template <
|
||||
/// Indicates the Parser for this platform.
|
||||
template<typename, bool> class ParserType,
|
||||
/// Indicates the greatest value the program counter might take.
|
||||
uint64_t max_address,
|
||||
/// Provides the type of Instruction to expect.
|
||||
typename InstructionType,
|
||||
/// Provides the storage size used for memory.
|
||||
typename MemoryWord,
|
||||
/// Provides the addressing range of memory.
|
||||
typename AddressType
|
||||
> class Disassembler {
|
||||
public:
|
||||
using ProgramCounterType = typename MinIntTypeValue<max_address>::type;
|
||||
|
||||
/*!
|
||||
Adds the result of disassembling @c memory which is @c length @c MemoryWords long from @c start_address
|
||||
to the current net total of instructions and recorded memory accesses.
|
||||
*/
|
||||
void disassemble(const MemoryWord *memory, ProgramCounterType location, ProgramCounterType length, ProgramCounterType start_address) {
|
||||
// TODO: possibly, move some of this stuff to instruction-set specific disassemblers, analogous to
|
||||
// the Executor's ownership of the Parser. That would allow handling of stateful parsing.
|
||||
ParserType<decltype(*this), true> parser;
|
||||
pending_entry_points_.push_back(start_address);
|
||||
entry_points_.insert(start_address);
|
||||
|
||||
while(!pending_entry_points_.empty()) {
|
||||
const auto next_entry_point = pending_entry_points_.front();
|
||||
pending_entry_points_.pop_front();
|
||||
|
||||
if(next_entry_point >= location) {
|
||||
parser.parse(*this, memory - location, next_entry_point & max_address, length + location);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const std::map<ProgramCounterType, InstructionType> &instructions() const {
|
||||
return instructions_;
|
||||
}
|
||||
|
||||
const std::set<ProgramCounterType> &entry_points() const {
|
||||
return entry_points_;
|
||||
}
|
||||
|
||||
void announce_overflow(ProgramCounterType) {}
|
||||
void announce_instruction(ProgramCounterType address, InstructionType instruction) {
|
||||
instructions_[address] = instruction;
|
||||
}
|
||||
void add_entry(ProgramCounterType address) {
|
||||
if(entry_points_.find(address) == entry_points_.end()) {
|
||||
pending_entry_points_.push_back(address);
|
||||
entry_points_.insert(address);
|
||||
}
|
||||
}
|
||||
void add_access(AddressType address, AccessType access_type) {
|
||||
// TODO.
|
||||
(void)address;
|
||||
(void)access_type;
|
||||
}
|
||||
|
||||
private:
|
||||
std::map<ProgramCounterType, InstructionType> instructions_;
|
||||
std::set<ProgramCounterType> entry_points_;
|
||||
|
||||
std::list<ProgramCounterType> pending_entry_points_;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif /* Disassembler_h */
|
||||
282
InstructionSets/M50740/Decoder.cpp
Normal file
282
InstructionSets/M50740/Decoder.cpp
Normal file
@@ -0,0 +1,282 @@
|
||||
//
|
||||
// Decoder.cpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 15/01/21.
|
||||
// Copyright © 2021 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#include "Decoder.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
namespace InstructionSet {
|
||||
namespace M50740 {
|
||||
|
||||
Instruction Decoder::instrucion_for_opcode(uint8_t opcode) {
|
||||
switch(opcode) {
|
||||
default: return Instruction(opcode);
|
||||
|
||||
#define Map(opcode, operation, addressing_mode) case opcode: return Instruction(Operation::operation, AddressingMode::addressing_mode, opcode);
|
||||
|
||||
/* 0x00 – 0x0f */
|
||||
Map(0x00, BRK, Implied); Map(0x01, ORA, XIndirect);
|
||||
Map(0x02, JSR, ZeroPageIndirect); Map(0x03, BBS0, AccumulatorRelative);
|
||||
|
||||
Map(0x05, ORA, ZeroPage);
|
||||
Map(0x06, ASL, ZeroPage); Map(0x07, BBS0, ZeroPageRelative);
|
||||
|
||||
Map(0x08, PHP, Implied); Map(0x09, ORA, Immediate);
|
||||
Map(0x0a, ASL, Accumulator); Map(0x0b, SEB0, Accumulator);
|
||||
|
||||
Map(0x0d, ORA, Absolute);
|
||||
Map(0x0e, ASL, Absolute); Map(0x0f, SEB0, ZeroPage);
|
||||
|
||||
/* 0x10 – 0x1f */
|
||||
Map(0x10, BPL, Relative); Map(0x11, ORA, IndirectY);
|
||||
Map(0x12, CLT, Implied); Map(0x13, BBC0, AccumulatorRelative);
|
||||
|
||||
Map(0x15, ORA, ZeroPageX);
|
||||
Map(0x16, ASL, ZeroPageX); Map(0x17, BBC0, ZeroPageRelative);
|
||||
|
||||
Map(0x18, CLC, Implied); Map(0x19, ORA, AbsoluteY);
|
||||
Map(0x1a, DEC, Accumulator); Map(0x1b, CLB0, Accumulator);
|
||||
|
||||
Map(0x1d, ORA, AbsoluteX);
|
||||
Map(0x1e, ASL, AbsoluteX); Map(0x1f, CLB0, ZeroPage);
|
||||
|
||||
/* 0x20 – 0x2f */
|
||||
Map(0x20, JSR, Absolute); Map(0x21, AND, XIndirect);
|
||||
Map(0x22, JSR, SpecialPage); Map(0x23, BBS1, AccumulatorRelative);
|
||||
|
||||
Map(0x24, BIT, ZeroPage); Map(0x25, AND, ZeroPage);
|
||||
Map(0x26, ROL, ZeroPage); Map(0x27, BBS1, ZeroPageRelative);
|
||||
|
||||
Map(0x28, PLP, Implied); Map(0x29, AND, Immediate);
|
||||
Map(0x2a, ROL, Accumulator); Map(0x2b, SEB1, Accumulator);
|
||||
|
||||
Map(0x2c, BIT, Absolute); Map(0x2d, AND, Absolute);
|
||||
Map(0x2e, ROL, Absolute); Map(0x2f, SEB1, ZeroPage);
|
||||
|
||||
/* 0x30 – 0x3f */
|
||||
Map(0x30, BMI, Relative); Map(0x31, AND, IndirectY);
|
||||
Map(0x32, SET, Implied); Map(0x33, BBC1, AccumulatorRelative);
|
||||
|
||||
Map(0x35, AND, ZeroPageX);
|
||||
Map(0x36, ROL, ZeroPageX); Map(0x37, BBC1, ZeroPageRelative);
|
||||
|
||||
Map(0x38, SEC, Implied); Map(0x39, AND, AbsoluteY);
|
||||
Map(0x3a, INC, Accumulator); Map(0x3b, CLB1, Accumulator);
|
||||
|
||||
Map(0x3c, LDM, ImmediateZeroPage); Map(0x3d, AND, AbsoluteX);
|
||||
Map(0x3e, ROL, AbsoluteX); Map(0x3f, CLB1, ZeroPage);
|
||||
|
||||
/* 0x40 – 0x4f */
|
||||
Map(0x40, RTI, Implied); Map(0x41, EOR, XIndirect);
|
||||
Map(0x42, STP, Implied); Map(0x43, BBS2, AccumulatorRelative);
|
||||
|
||||
Map(0x44, COM, ZeroPage); Map(0x45, EOR, ZeroPage);
|
||||
Map(0x46, LSR, ZeroPage); Map(0x47, BBS2, ZeroPageRelative);
|
||||
|
||||
Map(0x48, PHA, Implied); Map(0x49, EOR, Immediate);
|
||||
Map(0x4a, LSR, Accumulator); Map(0x4b, SEB2, Accumulator);
|
||||
|
||||
Map(0x4c, JMP, Absolute); Map(0x4d, EOR, Absolute);
|
||||
Map(0x4e, LSR, Absolute); Map(0x4f, SEB2, ZeroPage);
|
||||
|
||||
/* 0x50 – 0x5f */
|
||||
Map(0x50, BVC, Relative); Map(0x51, EOR, IndirectY);
|
||||
Map(0x53, BBC2, AccumulatorRelative);
|
||||
|
||||
Map(0x55, EOR, ZeroPageX);
|
||||
Map(0x56, LSR, ZeroPageX); Map(0x57, BBC2, ZeroPageRelative);
|
||||
|
||||
Map(0x58, CLI, Implied); Map(0x59, EOR, AbsoluteY);
|
||||
Map(0x5b, CLB2, Accumulator);
|
||||
|
||||
Map(0x5d, EOR, AbsoluteX);
|
||||
Map(0x5e, LSR, AbsoluteX); Map(0x5f, CLB2, ZeroPage);
|
||||
|
||||
/* 0x60 – 0x6f */
|
||||
Map(0x60, RTS, Implied); Map(0x61, ADC, XIndirect);
|
||||
Map(0x63, BBS3, AccumulatorRelative);
|
||||
|
||||
Map(0x64, TST, ZeroPage); Map(0x65, ADC, ZeroPage);
|
||||
Map(0x66, ROR, ZeroPage); Map(0x67, BBS3, ZeroPageRelative);
|
||||
|
||||
Map(0x68, PLA, Implied); Map(0x69, ADC, Immediate);
|
||||
Map(0x6a, ROR, Accumulator); Map(0x6b, SEB3, Accumulator);
|
||||
|
||||
Map(0x6c, JMP, AbsoluteIndirect); Map(0x6d, ADC, Absolute);
|
||||
Map(0x6e, ROR, Absolute); Map(0x6f, SEB3, ZeroPage);
|
||||
|
||||
/* 0x70 – 0x7f */
|
||||
Map(0x70, BVS, Relative); Map(0x71, ADC, IndirectY);
|
||||
Map(0x73, BBC3, AccumulatorRelative);
|
||||
|
||||
Map(0x75, ADC, ZeroPageX);
|
||||
Map(0x76, ROR, ZeroPageX); Map(0x77, BBC3, ZeroPageRelative);
|
||||
|
||||
Map(0x78, SEI, Implied); Map(0x79, ADC, AbsoluteY);
|
||||
Map(0x7b, CLB3, Accumulator);
|
||||
|
||||
Map(0x7d, ADC, AbsoluteX);
|
||||
Map(0x7e, ROR, AbsoluteX); Map(0x7f, CLB3, ZeroPage);
|
||||
|
||||
/* 0x80 – 0x8f */
|
||||
Map(0x80, BRA, Relative); Map(0x81, STA, XIndirect);
|
||||
Map(0x82, RRF, ZeroPage); Map(0x83, BBS4, AccumulatorRelative);
|
||||
|
||||
Map(0x84, STY, ZeroPage); Map(0x85, STA, ZeroPage);
|
||||
Map(0x86, STX, ZeroPage); Map(0x87, BBS4, ZeroPageRelative);
|
||||
|
||||
Map(0x88, DEY, Implied);
|
||||
Map(0x8a, TXA, Implied); Map(0x8b, SEB4, Accumulator);
|
||||
|
||||
Map(0x8c, STY, Absolute); Map(0x8d, STA, Absolute);
|
||||
Map(0x8e, STX, Absolute); Map(0x8f, SEB4, ZeroPage);
|
||||
|
||||
/* 0x90 – 0x9f */
|
||||
Map(0x90, BCC, Relative); Map(0x91, STA, IndirectY);
|
||||
Map(0x93, BBC4, AccumulatorRelative);
|
||||
|
||||
Map(0x94, STY, ZeroPageX); Map(0x95, STA, ZeroPageX);
|
||||
Map(0x96, STX, ZeroPageX); Map(0x97, BBC4, ZeroPageRelative);
|
||||
|
||||
Map(0x98, TYA, Implied); Map(0x99, STA, AbsoluteY);
|
||||
Map(0x9a, TXS, Implied); Map(0x9b, CLB4, Accumulator);
|
||||
|
||||
Map(0x9d, ADC, AbsoluteX);
|
||||
Map(0x9f, CLB4, ZeroPage);
|
||||
|
||||
/* 0xa0 – 0xaf */
|
||||
Map(0xa0, LDY, Immediate); Map(0xa1, LDA, XIndirect);
|
||||
Map(0xa2, LDX, Immediate); Map(0xa3, BBS5, AccumulatorRelative);
|
||||
|
||||
Map(0xa4, LDY, ZeroPage); Map(0xa5, LDA, ZeroPage);
|
||||
Map(0xa6, LDX, ZeroPage); Map(0xa7, BBS5, ZeroPageRelative);
|
||||
|
||||
Map(0xa8, TAY, Implied); Map(0xa9, LDA, Immediate);
|
||||
Map(0xaa, TAX, Implied); Map(0xab, SEB5, Accumulator);
|
||||
|
||||
Map(0xac, LDY, Absolute); Map(0xad, LDA, Absolute);
|
||||
Map(0xae, LDX, Absolute); Map(0xaf, SEB5, ZeroPage);
|
||||
|
||||
/* 0xb0 – 0xbf */
|
||||
Map(0xb0, BCS, Relative); Map(0xb1, STA, IndirectY);
|
||||
Map(0xb2, JMP, ZeroPageIndirect); Map(0xb3, BBC5, AccumulatorRelative);
|
||||
|
||||
Map(0xb4, LDY, ZeroPageX); Map(0xb5, LDA, ZeroPageX);
|
||||
Map(0xb6, LDX, ZeroPageY); Map(0xb7, BBC5, ZeroPageRelative);
|
||||
|
||||
Map(0xb8, CLV, Implied); Map(0xb9, LDA, AbsoluteY);
|
||||
Map(0xba, TSX, Implied); Map(0xbb, CLB5, Accumulator);
|
||||
|
||||
Map(0xbc, LDY, AbsoluteX); Map(0xbd, LDA, AbsoluteX);
|
||||
Map(0xbe, LDX, AbsoluteY); Map(0xbf, CLB5, ZeroPage);
|
||||
|
||||
/* 0xc0 – 0xcf */
|
||||
Map(0xc0, CPY, Immediate); Map(0xc1, CMP, XIndirect);
|
||||
Map(0xc2, SLW, Implied); Map(0xc3, BBS6, AccumulatorRelative);
|
||||
|
||||
Map(0xc4, CPY, ZeroPage); Map(0xc5, CMP, ZeroPage);
|
||||
Map(0xc6, DEC, ZeroPage); Map(0xc7, BBS6, ZeroPageRelative);
|
||||
|
||||
Map(0xc8, INY, Implied); Map(0xc9, CMP, Immediate);
|
||||
Map(0xca, DEX, Implied); Map(0xcb, SEB6, Accumulator);
|
||||
|
||||
Map(0xcc, CPY, Absolute); Map(0xcd, CMP, Absolute);
|
||||
Map(0xce, DEC, Absolute); Map(0xcf, SEB6, ZeroPage);
|
||||
|
||||
/* 0xd0 – 0xdf */
|
||||
Map(0xd0, BNE, Relative); Map(0xd1, CMP, IndirectY);
|
||||
Map(0xd3, BBC6, AccumulatorRelative);
|
||||
|
||||
Map(0xd5, CMP, ZeroPageX);
|
||||
Map(0xd6, DEC, ZeroPageX); Map(0xd7, BBC6, ZeroPageRelative);
|
||||
|
||||
Map(0xd8, CLD, Implied); Map(0xd9, CMP, AbsoluteY);
|
||||
Map(0xdb, CLB6, Accumulator);
|
||||
|
||||
Map(0xdd, CMP, AbsoluteX);
|
||||
Map(0xde, DEC, AbsoluteX); Map(0xdf, CLB6, ZeroPage);
|
||||
|
||||
/* 0xe0 – 0xef */
|
||||
Map(0xe0, CPX, Immediate); Map(0xe1, SBC, XIndirect);
|
||||
Map(0xe2, FST, Implied); Map(0xe3, BBS7, AccumulatorRelative);
|
||||
|
||||
Map(0xe4, CPX, ZeroPage); Map(0xe5, SBC, ZeroPage);
|
||||
Map(0xe6, INC, ZeroPage); Map(0xe7, BBS7, ZeroPageRelative);
|
||||
|
||||
Map(0xe8, INX, Implied); Map(0xe9, SBC, Immediate);
|
||||
Map(0xea, NOP, Implied); Map(0xeb, SEB7, Accumulator);
|
||||
|
||||
Map(0xec, CPX, Absolute); Map(0xed, SBC, Absolute);
|
||||
Map(0xee, INC, Absolute); Map(0xef, SEB7, ZeroPage);
|
||||
|
||||
/* 0xf0 – 0xff */
|
||||
Map(0xf0, BEQ, Relative); Map(0xf1, SBC, IndirectY);
|
||||
Map(0xf3, BBC7, AccumulatorRelative);
|
||||
|
||||
Map(0xf5, SBC, ZeroPageX);
|
||||
Map(0xf6, INC, ZeroPageX); Map(0xf7, BBC7, ZeroPageRelative);
|
||||
|
||||
Map(0xf8, SED, Implied); Map(0xf9, SBC, AbsoluteY);
|
||||
Map(0xfb, CLB7, Accumulator);
|
||||
|
||||
Map(0xfd, SBC, AbsoluteX);
|
||||
Map(0xfe, INC, AbsoluteX); Map(0xff, CLB7, ZeroPage);
|
||||
|
||||
#undef Map
|
||||
}
|
||||
}
|
||||
|
||||
std::pair<int, InstructionSet::M50740::Instruction> Decoder::decode(const uint8_t *source, size_t length) {
|
||||
const uint8_t *const end = source + length;
|
||||
|
||||
if(phase_ == Phase::Instruction && source != end) {
|
||||
const uint8_t instruction = *source;
|
||||
++source;
|
||||
++consumed_;
|
||||
|
||||
// Determine the instruction in hand, and finish now if its undefined.
|
||||
instr_ = instrucion_for_opcode(instruction);
|
||||
if(instr_.operation == Operation::Invalid) {
|
||||
consumed_ = 0;
|
||||
return std::make_pair(1, instr_);
|
||||
}
|
||||
|
||||
// Obtain an operand size and roll onto the correct phase.
|
||||
operand_size_ = size(instr_.addressing_mode);
|
||||
phase_ = operand_size_ ? Phase::AwaitingOperand : Phase::ReadyToPost;
|
||||
operand_bytes_ = 0;
|
||||
}
|
||||
|
||||
if(phase_ == Phase::AwaitingOperand && source != end) {
|
||||
const int outstanding_bytes = operand_size_ - operand_bytes_;
|
||||
const int bytes_to_consume = std::min(int(end - source), outstanding_bytes);
|
||||
|
||||
consumed_ += bytes_to_consume;
|
||||
source += bytes_to_consume;
|
||||
operand_bytes_ += bytes_to_consume;
|
||||
|
||||
if(operand_size_ == operand_bytes_) {
|
||||
phase_ = Phase::ReadyToPost;
|
||||
} else {
|
||||
return std::make_pair(-(operand_size_ - operand_bytes_), Instruction());
|
||||
}
|
||||
}
|
||||
|
||||
if(phase_ == Phase::ReadyToPost) {
|
||||
const auto result = std::make_pair(consumed_, instr_);
|
||||
consumed_ = 0;
|
||||
phase_ = Phase::Instruction;
|
||||
return result;
|
||||
}
|
||||
|
||||
// Decoding didn't complete, without it being clear how many more bytes are required.
|
||||
return std::make_pair(0, Instruction());
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
39
InstructionSets/M50740/Decoder.hpp
Normal file
39
InstructionSets/M50740/Decoder.hpp
Normal file
@@ -0,0 +1,39 @@
|
||||
//
|
||||
// Decoder.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 15/01/21.
|
||||
// Copyright © 2021 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef InstructionSets_M50740_Decoder_hpp
|
||||
#define InstructionSets_M50740_Decoder_hpp
|
||||
|
||||
#include "Instruction.hpp"
|
||||
|
||||
#include <cstddef>
|
||||
#include <utility>
|
||||
|
||||
namespace InstructionSet {
|
||||
namespace M50740 {
|
||||
|
||||
class Decoder {
|
||||
public:
|
||||
std::pair<int, Instruction> decode(const uint8_t *source, size_t length);
|
||||
Instruction instrucion_for_opcode(uint8_t opcode);
|
||||
|
||||
private:
|
||||
enum class Phase {
|
||||
Instruction,
|
||||
AwaitingOperand,
|
||||
ReadyToPost
|
||||
} phase_ = Phase::Instruction;
|
||||
int operand_size_ = 0, operand_bytes_ = 0;
|
||||
int consumed_ = 0;
|
||||
Instruction instr_;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* InstructionSets_M50740_Decoder_hpp */
|
||||
838
InstructionSets/M50740/Executor.cpp
Normal file
838
InstructionSets/M50740/Executor.cpp
Normal file
@@ -0,0 +1,838 @@
|
||||
//
|
||||
// Executor.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 16/1/21.
|
||||
// Copyright © 2021 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#include "Executor.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cassert>
|
||||
#include <cstring>
|
||||
|
||||
#include "../../Machines/Utility/MemoryFuzzer.hpp"
|
||||
|
||||
#define LOG_PREFIX "[M50740] "
|
||||
#include "../../Outputs/Log.hpp"
|
||||
|
||||
using namespace InstructionSet::M50740;
|
||||
|
||||
namespace {
|
||||
constexpr int port_remap[] = {0, 1, 2, 0, 3};
|
||||
}
|
||||
|
||||
Executor::Executor(PortHandler &port_handler) : port_handler_(port_handler) {
|
||||
// Cut down the list of all generated performers to those the processor actually uses, and install that
|
||||
// for future referencing by action_for.
|
||||
Decoder decoder;
|
||||
for(size_t c = 0; c < 256; c++) {
|
||||
const auto instruction = decoder.instrucion_for_opcode(uint8_t(c));
|
||||
|
||||
// Treat invalid as NOP, because I've got to do _something_.
|
||||
if(instruction.operation == Operation::Invalid) {
|
||||
performers_[c] = performer_lookup_.performer(Operation::NOP, instruction.addressing_mode);
|
||||
} else {
|
||||
performers_[c] = performer_lookup_.performer(instruction.operation, instruction.addressing_mode);
|
||||
}
|
||||
}
|
||||
|
||||
// Fuzz RAM; then set anything that may be replaced by ROM to FF.
|
||||
Memory::Fuzz(memory_);
|
||||
memset(&memory_[0x100], 0xff, memory_.size() - 0x100);
|
||||
}
|
||||
|
||||
void Executor::set_rom(const std::vector<uint8_t> &rom) {
|
||||
// Copy into place, and reset.
|
||||
const auto length = std::min(size_t(0x1000), rom.size());
|
||||
memcpy(&memory_[0x2000 - length], rom.data(), length);
|
||||
reset();
|
||||
}
|
||||
|
||||
void Executor::run_for(Cycles cycles) {
|
||||
// The incoming clock is divided by four; the local cycles_ count
|
||||
// ensures that fractional parts are kept track of.
|
||||
cycles_ += cycles;
|
||||
CachingExecutor::run_for(cycles_.divide(Cycles(4)).as<int>());
|
||||
}
|
||||
|
||||
void Executor::reset() {
|
||||
// Just jump to the reset vector.
|
||||
set_program_counter(uint16_t(memory_[0x1ffe] | (memory_[0x1fff] << 8)));
|
||||
}
|
||||
|
||||
void Executor::set_interrupt_line(bool line) {
|
||||
// Super hack: interrupt now, if permitted. Otherwise do nothing.
|
||||
// So this will fail to catch enabling of interrupts while the line
|
||||
// is active, amongst other things.
|
||||
if(interrupt_line_ != line) {
|
||||
interrupt_line_ = line;
|
||||
|
||||
// TODO: verify interrupt connection. Externally, but stubbed off here.
|
||||
// if(!interrupt_disable_ && line) {
|
||||
// perform_interrupt<false>(0x1ff4);
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t Executor::read(uint16_t address) {
|
||||
address &= 0x1fff;
|
||||
|
||||
// Deal with RAM and ROM accesses quickly.
|
||||
if(address < 0x60 || address >= 0x100) return memory_[address];
|
||||
|
||||
port_handler_.run_ports_for(cycles_since_port_handler_.flush<Cycles>());
|
||||
switch(address) {
|
||||
default:
|
||||
LOG("Unrecognised read from " << PADHEX(4) << address);
|
||||
return 0xff;
|
||||
|
||||
// "Port R"; sixteen four-bit ports
|
||||
case 0xd0: case 0xd1: case 0xd2: case 0xd3: case 0xd4: case 0xd5: case 0xd6: case 0xd7:
|
||||
case 0xd8: case 0xd9: case 0xda: case 0xdb: case 0xdc: case 0xdd: case 0xde: case 0xdf:
|
||||
LOG("Unimplemented Port R read from " << PADHEX(4) << address);
|
||||
return 0x00;
|
||||
|
||||
// Ports P0–P3.
|
||||
case 0xe0: case 0xe2:
|
||||
case 0xe4: case 0xe8: {
|
||||
const int port = port_remap[(address - 0xe0) >> 1];
|
||||
const uint8_t input = port_handler_.get_port_input(port);
|
||||
|
||||
// In the direction registers, a 0 indicates input, a 1 indicates output.
|
||||
return (input &~ port_directions_[port]) | (port_outputs_[port] & port_directions_[port]);
|
||||
}
|
||||
|
||||
case 0xe1: case 0xe3:
|
||||
case 0xe5: case 0xe9:
|
||||
return port_directions_[port_remap[(address - 0xe0) >> 1]];
|
||||
|
||||
// Timers.
|
||||
case 0xf9: return prescalers_[0].value;
|
||||
case 0xfa: return timers_[0].value;
|
||||
case 0xfb: return timers_[1].value;
|
||||
case 0xfc: return prescalers_[1].value;
|
||||
case 0xfd: return timers_[2].value;
|
||||
|
||||
case 0xfe: return interrupt_control_;
|
||||
case 0xff: return timer_control_;
|
||||
}
|
||||
}
|
||||
|
||||
void Executor::set_port_output(int port) {
|
||||
// Force 'output' to a 1 anywhere that a bit is set as input.
|
||||
port_handler_.set_port_output(port, port_outputs_[port] | ~port_directions_[port]);
|
||||
}
|
||||
|
||||
void Executor::write(uint16_t address, uint8_t value) {
|
||||
address &= 0x1fff;
|
||||
|
||||
// RAM writes are easy.
|
||||
if(address < 0x60) {
|
||||
memory_[address] = value;
|
||||
return;
|
||||
}
|
||||
|
||||
// ROM 'writes' are almost as easy (albeit unexpected).
|
||||
if(address >= 0x100) {
|
||||
LOG("Attempted ROM write of " << PADHEX(2) << value << " to " << PADHEX(4) << address);
|
||||
return;
|
||||
}
|
||||
|
||||
// Push time to the port handler.
|
||||
port_handler_.run_ports_for(cycles_since_port_handler_.flush<Cycles>());
|
||||
|
||||
switch(address) {
|
||||
default:
|
||||
LOG("Unrecognised write of " << PADHEX(2) << value << " to " << PADHEX(4) << address);
|
||||
break;
|
||||
|
||||
// "Port R"; sixteen four-bit ports
|
||||
case 0xd0: case 0xd1: case 0xd2: case 0xd3: case 0xd4: case 0xd5: case 0xd6: case 0xd7:
|
||||
case 0xd8: case 0xd9: case 0xda: case 0xdb: case 0xdc: case 0xdd: case 0xde: case 0xdf:
|
||||
LOG("Unimplemented Port R write of " << PADHEX(2) << value << " from " << PADHEX(4) << address);
|
||||
break;
|
||||
|
||||
// Ports P0–P3.
|
||||
case 0xe0: case 0xe2:
|
||||
case 0xe4: case 0xe8: {
|
||||
const int port = port_remap[(address - 0xe0) >> 1];
|
||||
port_outputs_[port] = value;
|
||||
set_port_output(port);
|
||||
}
|
||||
break;
|
||||
|
||||
case 0xe1: case 0xe3:
|
||||
case 0xe5: case 0xe9: {
|
||||
const int port = port_remap[(address - 0xe0) >> 1];
|
||||
port_directions_[port] = value;
|
||||
set_port_output(port);
|
||||
} break;
|
||||
|
||||
// Timers.
|
||||
//
|
||||
// Reloading of value with the reload value is a guess, based upon what I take
|
||||
// to be the intended usage of timer 2 in handling key repeat on the Apple IIgs.
|
||||
case 0xf9: prescalers_[0].value = prescalers_[0].reload_value = value; break;
|
||||
case 0xfa: timers_[0].value = timers_[0].reload_value = value; break;
|
||||
case 0xfb: timers_[1].value = timers_[1].reload_value = value; break;
|
||||
case 0xfc: prescalers_[1].value = prescalers_[1].reload_value = value; break;
|
||||
case 0xfd: timers_[2].value = timers_[2].reload_value = value; break;
|
||||
|
||||
case 0xfe: interrupt_control_ = value; break;
|
||||
case 0xff: timer_control_ = value; break;
|
||||
}
|
||||
}
|
||||
|
||||
void Executor::push(uint8_t value) {
|
||||
write(s_, value);
|
||||
--s_;
|
||||
}
|
||||
|
||||
uint8_t Executor::pull() {
|
||||
++s_;
|
||||
return read(s_);
|
||||
}
|
||||
|
||||
void Executor::set_flags(uint8_t flags) {
|
||||
negative_result_ = flags;
|
||||
overflow_result_ = uint8_t(flags << 1);
|
||||
index_mode_ = flags & 0x20;
|
||||
decimal_mode_ = flags & 0x08;
|
||||
interrupt_disable_ = flags & 0x04;
|
||||
zero_result_ = !(flags & 0x02);
|
||||
carry_flag_ = flags & 0x01;
|
||||
}
|
||||
|
||||
uint8_t Executor::flags() {
|
||||
return
|
||||
(negative_result_ & 0x80) |
|
||||
((overflow_result_ & 0x80) >> 1) |
|
||||
(index_mode_ ? 0x20 : 0x00) |
|
||||
(decimal_mode_ ? 0x08 : 0x00) |
|
||||
interrupt_disable_ |
|
||||
(zero_result_ ? 0x00 : 0x02) |
|
||||
carry_flag_;
|
||||
}
|
||||
|
||||
template<bool is_brk> inline void Executor::perform_interrupt(uint16_t vector) {
|
||||
// BRK has an unused operand.
|
||||
++program_counter_;
|
||||
push(uint8_t(program_counter_ >> 8));
|
||||
push(uint8_t(program_counter_ & 0xff));
|
||||
push(flags() | (is_brk ? 0x10 : 0x00));
|
||||
set_program_counter(uint16_t(memory_[vector] | (memory_[vector+1] << 8)));
|
||||
}
|
||||
|
||||
template <Operation operation, AddressingMode addressing_mode> void Executor::perform() {
|
||||
// Post cycle cost; this emulation _does not provide accurate timing_.
|
||||
#define TLength(mode, base) case AddressingMode::mode: subtract_duration(base + t_lengths[index_mode_]); break;
|
||||
#define Length(mode, base) case AddressingMode::mode: subtract_duration(base); break;
|
||||
|
||||
switch(operation) {
|
||||
case Operation::ADC: case Operation::AND: case Operation::CMP: case Operation::EOR:
|
||||
case Operation::LDA: case Operation::ORA: case Operation::SBC:
|
||||
{
|
||||
constexpr int t_lengths[] = {
|
||||
0,
|
||||
operation == Operation::LDA ? 2 : (operation == Operation::CMP ? 1 : 3)
|
||||
};
|
||||
switch(addressing_mode) {
|
||||
TLength(XIndirect, 6);
|
||||
TLength(ZeroPage, 3);
|
||||
TLength(Immediate, 2);
|
||||
TLength(Absolute, 4);
|
||||
TLength(IndirectY, 6);
|
||||
TLength(ZeroPageX, 4);
|
||||
TLength(AbsoluteY, 5);
|
||||
TLength(AbsoluteX, 5);
|
||||
default: assert(false);
|
||||
}
|
||||
} break;
|
||||
|
||||
case Operation::ASL: case Operation::DEC: case Operation::INC: case Operation::LSR:
|
||||
case Operation::ROL: case Operation::ROR:
|
||||
switch(addressing_mode) {
|
||||
Length(ZeroPage, 5);
|
||||
Length(Accumulator, 2);
|
||||
Length(Absolute, 6);
|
||||
Length(ZeroPageX, 6);
|
||||
Length(AbsoluteX, 7);
|
||||
default: assert(false);
|
||||
}
|
||||
break;
|
||||
|
||||
case Operation::BBC0: case Operation::BBC1: case Operation::BBC2: case Operation::BBC3:
|
||||
case Operation::BBC4: case Operation::BBC5: case Operation::BBC6: case Operation::BBC7:
|
||||
case Operation::BBS0: case Operation::BBS1: case Operation::BBS2: case Operation::BBS3:
|
||||
case Operation::BBS4: case Operation::BBS5: case Operation::BBS6: case Operation::BBS7:
|
||||
switch(addressing_mode) {
|
||||
Length(AccumulatorRelative, 4);
|
||||
Length(ZeroPageRelative, 5);
|
||||
default: assert(false);
|
||||
}
|
||||
break;
|
||||
case Operation::BPL: case Operation::BMI: case Operation::BEQ: case Operation::BNE:
|
||||
case Operation::BCS: case Operation::BCC: case Operation::BVS: case Operation::BVC:
|
||||
case Operation::INX: case Operation::INY:
|
||||
subtract_duration(2);
|
||||
break;
|
||||
|
||||
case Operation::CPX: case Operation::CPY: case Operation::BIT: case Operation::LDX:
|
||||
case Operation::LDY:
|
||||
switch(addressing_mode) {
|
||||
Length(Immediate, 2);
|
||||
Length(ZeroPage, 3);
|
||||
Length(Absolute, 4);
|
||||
Length(ZeroPageX, 4);
|
||||
Length(ZeroPageY, 4);
|
||||
Length(AbsoluteX, 5);
|
||||
Length(AbsoluteY, 5);
|
||||
default: assert(false);
|
||||
}
|
||||
break;
|
||||
|
||||
case Operation::BRA: subtract_duration(4); break;
|
||||
case Operation::BRK: subtract_duration(7); break;
|
||||
|
||||
case Operation::CLB0: case Operation::CLB1: case Operation::CLB2: case Operation::CLB3:
|
||||
case Operation::CLB4: case Operation::CLB5: case Operation::CLB6: case Operation::CLB7:
|
||||
case Operation::SEB0: case Operation::SEB1: case Operation::SEB2: case Operation::SEB3:
|
||||
case Operation::SEB4: case Operation::SEB5: case Operation::SEB6: case Operation::SEB7:
|
||||
switch(addressing_mode) {
|
||||
Length(Accumulator, 2);
|
||||
Length(ZeroPage, 5);
|
||||
default: assert(false);
|
||||
}
|
||||
break;
|
||||
|
||||
case Operation::CLC: case Operation::CLD: case Operation::CLT: case Operation::CLV:
|
||||
case Operation::CLI:
|
||||
case Operation::DEX: case Operation::DEY: case Operation::FST: case Operation::NOP:
|
||||
case Operation::SEC: case Operation::SED: case Operation::SEI: case Operation::SET:
|
||||
case Operation::SLW: case Operation::STP: case Operation::TAX: case Operation::TAY:
|
||||
case Operation::TSX: case Operation::TXA: case Operation::TXS: case Operation::TYA:
|
||||
subtract_duration(2);
|
||||
break;
|
||||
|
||||
case Operation::COM: subtract_duration(5); break;
|
||||
|
||||
case Operation::JMP:
|
||||
switch(addressing_mode) {
|
||||
Length(Absolute, 3);
|
||||
Length(AbsoluteIndirect, 5);
|
||||
Length(ZeroPageIndirect, 4);
|
||||
default: assert(false);
|
||||
}
|
||||
break;
|
||||
|
||||
case Operation::JSR:
|
||||
switch(addressing_mode) {
|
||||
Length(ZeroPageIndirect, 7);
|
||||
Length(Absolute, 6);
|
||||
Length(SpecialPage, 5);
|
||||
default: assert(false);
|
||||
}
|
||||
break;
|
||||
|
||||
case Operation::LDM: subtract_duration(4); break;
|
||||
|
||||
case Operation::PHA: case Operation::PHP: case Operation::TST:
|
||||
subtract_duration(3);
|
||||
break;
|
||||
|
||||
case Operation::PLA: case Operation::PLP:
|
||||
subtract_duration(4);
|
||||
break;
|
||||
|
||||
case Operation::RRF: subtract_duration(8); break;
|
||||
case Operation::RTI: subtract_duration(6); break;
|
||||
case Operation::RTS: subtract_duration(6); break;
|
||||
|
||||
case Operation::STA: case Operation::STX: case Operation::STY:
|
||||
switch(addressing_mode) {
|
||||
Length(XIndirect, 7);
|
||||
Length(ZeroPage, 4);
|
||||
Length(Absolute, 5);
|
||||
Length(IndirectY, 7);
|
||||
Length(ZeroPageX, 5);
|
||||
Length(ZeroPageY, 5);
|
||||
Length(AbsoluteY, 6);
|
||||
Length(AbsoluteX, 6);
|
||||
default: assert(false);
|
||||
}
|
||||
break;
|
||||
|
||||
default: assert(false);
|
||||
}
|
||||
|
||||
// Deal with all modes that don't access memory up here;
|
||||
// those that access memory will go through a slightly longer
|
||||
// sequence below that wraps the address and checks whether
|
||||
// a write is valid [if required].
|
||||
|
||||
unsigned int address;
|
||||
#define next8() memory_[(program_counter_ + 1) & 0x1fff]
|
||||
#define next16() uint16_t(memory_[(program_counter_ + 1) & 0x1fff] | (memory_[(program_counter_ + 2) & 0x1fff] << 8))
|
||||
|
||||
// Underlying assumption below: the instruction stream will never
|
||||
// overlap with IO ports.
|
||||
switch(addressing_mode) {
|
||||
|
||||
// Addressing modes with no further memory access.
|
||||
|
||||
case AddressingMode::Implied:
|
||||
perform<operation>(nullptr);
|
||||
++program_counter_;
|
||||
return;
|
||||
|
||||
case AddressingMode::Accumulator:
|
||||
perform<operation>(&a_);
|
||||
++program_counter_;
|
||||
return;
|
||||
|
||||
case AddressingMode::Immediate:
|
||||
perform<operation>(&next8());
|
||||
program_counter_ += 2;
|
||||
return;
|
||||
|
||||
// Special-purpose addressing modes.
|
||||
|
||||
case AddressingMode::Relative:
|
||||
address = unsigned(program_counter_ + 1 + size(addressing_mode) + int8_t(next8()));
|
||||
break;
|
||||
|
||||
case AddressingMode::SpecialPage: address = 0x1f00 | next8(); break;
|
||||
|
||||
case AddressingMode::ImmediateZeroPage:
|
||||
// LDM only...
|
||||
write(memory_[(program_counter_+2)&0x1fff], memory_[(program_counter_+1)&0x1fff]);
|
||||
program_counter_ += 1 + size(addressing_mode);
|
||||
return;
|
||||
|
||||
case AddressingMode::AccumulatorRelative:
|
||||
case AddressingMode::ZeroPageRelative: {
|
||||
// Order of bytes is: (i) zero page address; (ii) relative jump.
|
||||
uint8_t value;
|
||||
if constexpr (addressing_mode == AddressingMode::AccumulatorRelative) {
|
||||
value = a_;
|
||||
address = unsigned(program_counter_ + 1 + size(addressing_mode) + int8_t(next8()));
|
||||
} else {
|
||||
value = read(next8());
|
||||
address = unsigned(program_counter_ + 1 + size(addressing_mode) + int8_t(memory_[(program_counter_+2)&0x1fff]));
|
||||
}
|
||||
program_counter_ += 1 + size(addressing_mode);
|
||||
switch(operation) {
|
||||
case Operation::BBS0: case Operation::BBS1: case Operation::BBS2: case Operation::BBS3:
|
||||
case Operation::BBS4: case Operation::BBS5: case Operation::BBS6: case Operation::BBS7: {
|
||||
if constexpr (operation >= Operation::BBS0 && operation <= Operation::BBS7) {
|
||||
constexpr uint8_t mask = 1 << (int(operation) - int(Operation::BBS0));
|
||||
if(value & mask) {
|
||||
set_program_counter(uint16_t(address));
|
||||
subtract_duration(2);
|
||||
}
|
||||
}
|
||||
} return;
|
||||
case Operation::BBC0: case Operation::BBC1: case Operation::BBC2: case Operation::BBC3:
|
||||
case Operation::BBC4: case Operation::BBC5: case Operation::BBC6: case Operation::BBC7: {
|
||||
if constexpr (operation >= Operation::BBC0 && operation <= Operation::BBC7) {
|
||||
constexpr uint8_t mask = 1 << (int(operation) - int(Operation::BBC0));
|
||||
if(!(value & mask)) {
|
||||
set_program_counter(uint16_t(address));
|
||||
subtract_duration(2);
|
||||
}
|
||||
}
|
||||
} return;
|
||||
default: assert(false);
|
||||
}
|
||||
} break;
|
||||
|
||||
// Addressing modes with a memory access.
|
||||
|
||||
case AddressingMode::Absolute: address = next16(); break;
|
||||
case AddressingMode::AbsoluteX: address = next16() + x_; break;
|
||||
case AddressingMode::AbsoluteY: address = next16() + y_; break;
|
||||
case AddressingMode::ZeroPage: address = next8(); break;
|
||||
case AddressingMode::ZeroPageX: address = (next8() + x_) & 0xff; break;
|
||||
case AddressingMode::ZeroPageY: address = (next8() + y_) & 0xff; break;
|
||||
|
||||
case AddressingMode::ZeroPageIndirect:
|
||||
address = next8();
|
||||
address = unsigned(memory_[address] | (memory_[(address + 1) & 0xff] << 8));
|
||||
break;
|
||||
|
||||
case AddressingMode::XIndirect:
|
||||
address = (next8() + x_) & 0xff;
|
||||
address = unsigned(memory_[address] | (memory_[(address + 1)&0xff] << 8));
|
||||
break;
|
||||
|
||||
case AddressingMode::IndirectY:
|
||||
address = unsigned((memory_[next8()] | (memory_[(next8()+1)&0xff] << 8)) + y_);
|
||||
break;
|
||||
|
||||
case AddressingMode::AbsoluteIndirect:
|
||||
address = next16();
|
||||
address = unsigned(memory_[address & 0x1fff] | (memory_[(address + 1) & 0x1fff] << 8));
|
||||
break;
|
||||
|
||||
default:
|
||||
assert(false);
|
||||
}
|
||||
|
||||
#undef next16
|
||||
#undef next8
|
||||
program_counter_ += 1 + size(addressing_mode);
|
||||
|
||||
// Check for a branch; those don't go through the memory accesses below.
|
||||
switch(operation) {
|
||||
case Operation::BRA: case Operation::JMP:
|
||||
set_program_counter(uint16_t(address));
|
||||
return;
|
||||
|
||||
case Operation::JSR: {
|
||||
// Push one less than the actual return address.
|
||||
const auto return_address = program_counter_ - 1;
|
||||
push(uint8_t(return_address >> 8));
|
||||
push(uint8_t(return_address & 0xff));
|
||||
set_program_counter(uint16_t(address));
|
||||
} return;
|
||||
|
||||
#define Bcc(c) if(c) { set_program_counter(uint16_t(address)); subtract_duration(2); } return
|
||||
case Operation::BPL: Bcc(!(negative_result_&0x80));
|
||||
case Operation::BMI: Bcc(negative_result_&0x80);
|
||||
case Operation::BEQ: Bcc(!zero_result_);
|
||||
case Operation::BNE: Bcc(zero_result_);
|
||||
case Operation::BCS: Bcc(carry_flag_);
|
||||
case Operation::BCC: Bcc(!carry_flag_);
|
||||
case Operation::BVS: Bcc(overflow_result_ & 0x80);
|
||||
case Operation::BVC: Bcc(!(overflow_result_ & 0x80));
|
||||
#undef Bcc
|
||||
|
||||
default: break;
|
||||
}
|
||||
|
||||
assert(access_type(operation) != AccessType::None);
|
||||
|
||||
if constexpr(access_type(operation) == AccessType::Read) {
|
||||
uint8_t source = read(uint16_t(address));
|
||||
perform<operation>(&source);
|
||||
return;
|
||||
}
|
||||
|
||||
uint8_t value;
|
||||
if constexpr(access_type(operation) == AccessType::ReadModifyWrite) {
|
||||
value = read(uint16_t(address));
|
||||
} else {
|
||||
value = 0xff;
|
||||
}
|
||||
perform<operation>(&value);
|
||||
write(uint16_t(address), value);
|
||||
}
|
||||
|
||||
template <Operation operation> void Executor::perform(uint8_t *operand [[maybe_unused]]) {
|
||||
|
||||
#define set_nz(a) negative_result_ = zero_result_ = (a)
|
||||
switch(operation) {
|
||||
case Operation::LDA:
|
||||
if(index_mode_) {
|
||||
write(x_, *operand);
|
||||
set_nz(*operand);
|
||||
} else {
|
||||
set_nz(a_ = *operand);
|
||||
}
|
||||
break;
|
||||
case Operation::LDX: set_nz(x_ = *operand); break;
|
||||
case Operation::LDY: set_nz(y_ = *operand); break;
|
||||
|
||||
case Operation::STA: *operand = a_; break;
|
||||
case Operation::STX: *operand = x_; break;
|
||||
case Operation::STY: *operand = y_; break;
|
||||
|
||||
case Operation::TXA: set_nz(a_ = x_); break;
|
||||
case Operation::TYA: set_nz(a_ = y_); break;
|
||||
case Operation::TXS: s_ = x_; break;
|
||||
case Operation::TAX: set_nz(x_ = a_); break;
|
||||
case Operation::TAY: set_nz(y_ = a_); break;
|
||||
case Operation::TSX: set_nz(x_ = s_); break;
|
||||
|
||||
case Operation::SEB0: case Operation::SEB1: case Operation::SEB2: case Operation::SEB3:
|
||||
case Operation::SEB4: case Operation::SEB5: case Operation::SEB6: case Operation::SEB7:
|
||||
if constexpr(operation >= Operation::SEB0 && operation <= Operation::SEB7) {
|
||||
*operand |= 1 << (int(operation) - int(Operation::SEB0));
|
||||
}
|
||||
break;
|
||||
case Operation::CLB0: case Operation::CLB1: case Operation::CLB2: case Operation::CLB3:
|
||||
case Operation::CLB4: case Operation::CLB5: case Operation::CLB6: case Operation::CLB7:
|
||||
if constexpr(operation >= Operation::CLB0 && operation <= Operation::CLB7) {
|
||||
*operand &= ~(1 << (int(operation) - int(Operation::CLB0)));
|
||||
}
|
||||
break;
|
||||
|
||||
case Operation::CLI: interrupt_disable_ = 0x00; break;
|
||||
case Operation::SEI: interrupt_disable_ = 0x04; break;
|
||||
case Operation::CLT: index_mode_ = false; break;
|
||||
case Operation::SET: index_mode_ = true; break;
|
||||
case Operation::CLD: decimal_mode_ = false; break;
|
||||
case Operation::SED: decimal_mode_ = true; break;
|
||||
case Operation::CLC: carry_flag_ = 0; break;
|
||||
case Operation::SEC: carry_flag_ = 1; break;
|
||||
case Operation::CLV: overflow_result_ = 0; break;
|
||||
|
||||
case Operation::DEX: --x_; set_nz(x_); break;
|
||||
case Operation::INX: ++x_; set_nz(x_); break;
|
||||
case Operation::DEY: --y_; set_nz(y_); break;
|
||||
case Operation::INY: ++y_; set_nz(y_); break;
|
||||
case Operation::DEC: --*operand; set_nz(*operand); break;
|
||||
case Operation::INC: ++*operand; set_nz(*operand); break;
|
||||
|
||||
case Operation::RTS: {
|
||||
uint16_t target = pull();
|
||||
target |= pull() << 8;
|
||||
set_program_counter(target+1);
|
||||
--program_counter_; // To undo the unavoidable increment
|
||||
// after exiting from here.
|
||||
} break;
|
||||
|
||||
case Operation::RTI: {
|
||||
set_flags(pull());
|
||||
uint16_t target = pull();
|
||||
target |= pull() << 8;
|
||||
set_program_counter(target);
|
||||
--program_counter_; // To undo the unavoidable increment
|
||||
// after exiting from here.
|
||||
} break;
|
||||
|
||||
case Operation::BRK:
|
||||
perform_interrupt<true>(0x1ff4);
|
||||
--program_counter_; // To undo the unavoidable increment
|
||||
// after exiting from here.
|
||||
break;
|
||||
|
||||
case Operation::STP: set_is_stopped(true); break;
|
||||
|
||||
case Operation::COM: set_nz(*operand ^= 0xff); break;
|
||||
|
||||
case Operation::FST: case Operation::SLW: case Operation::NOP:
|
||||
// TODO: communicate FST and SLW onwards, I imagine. Find out what they interface with.
|
||||
break;
|
||||
|
||||
case Operation::PHA: push(a_); break;
|
||||
case Operation::PHP: push(flags()); break;
|
||||
case Operation::PLA: set_nz(a_ = pull()); break;
|
||||
case Operation::PLP: set_flags(pull()); break;
|
||||
|
||||
case Operation::ASL:
|
||||
carry_flag_ = *operand >> 7;
|
||||
*operand <<= 1;
|
||||
set_nz(*operand);
|
||||
break;
|
||||
|
||||
case Operation::LSR:
|
||||
carry_flag_ = *operand & 1;
|
||||
*operand >>= 1;
|
||||
set_nz(*operand);
|
||||
break;
|
||||
|
||||
case Operation::ROL: {
|
||||
const uint8_t temp8 = uint8_t((*operand << 1) | carry_flag_);
|
||||
carry_flag_ = *operand >> 7;
|
||||
set_nz(*operand = temp8);
|
||||
} break;
|
||||
|
||||
case Operation::ROR: {
|
||||
const uint8_t temp8 = uint8_t((*operand >> 1) | (carry_flag_ << 7));
|
||||
carry_flag_ = *operand & 1;
|
||||
set_nz(*operand = temp8);
|
||||
} break;
|
||||
|
||||
case Operation::RRF:
|
||||
*operand = uint8_t((*operand >> 4) | (*operand << 4));
|
||||
break;
|
||||
|
||||
case Operation::BIT:
|
||||
zero_result_ = *operand & a_;
|
||||
negative_result_ = *operand;
|
||||
overflow_result_ = uint8_t(*operand << 1);
|
||||
break;
|
||||
|
||||
case Operation::TST:
|
||||
set_nz(*operand);
|
||||
break;
|
||||
|
||||
/*
|
||||
Operations affected by the index mode flag: ADC, AND, CMP, EOR, LDA, ORA, and SBC.
|
||||
*/
|
||||
|
||||
#define index(op) \
|
||||
if(index_mode_) { \
|
||||
uint8_t t = read(x_); \
|
||||
op(t); \
|
||||
write(x_, t); \
|
||||
} else { \
|
||||
op(a_); \
|
||||
}
|
||||
|
||||
#define op_ora(x) set_nz(x |= *operand)
|
||||
#define op_and(x) set_nz(x &= *operand)
|
||||
#define op_eor(x) set_nz(x ^= *operand)
|
||||
case Operation::ORA: index(op_ora); break;
|
||||
case Operation::AND: index(op_and); break;
|
||||
case Operation::EOR: index(op_eor); break;
|
||||
#undef op_eor
|
||||
#undef op_and
|
||||
#undef op_ora
|
||||
#undef index
|
||||
|
||||
#define op_cmp(x) { \
|
||||
const uint16_t temp16 = x - *operand; \
|
||||
set_nz(uint8_t(temp16)); \
|
||||
carry_flag_ = (~temp16 >> 8)&1; \
|
||||
}
|
||||
case Operation::CMP:
|
||||
if(index_mode_) {
|
||||
op_cmp(read(x_));
|
||||
} else {
|
||||
op_cmp(a_);
|
||||
}
|
||||
break;
|
||||
case Operation::CPX: op_cmp(x_); break;
|
||||
case Operation::CPY: op_cmp(y_); break;
|
||||
#undef op_cmp
|
||||
|
||||
case Operation::SBC:
|
||||
case Operation::ADC: {
|
||||
const uint8_t a = index_mode_ ? read(x_) : a_;
|
||||
|
||||
if(decimal_mode_) {
|
||||
if(operation == Operation::ADC) {
|
||||
uint16_t partials = 0;
|
||||
int result = carry_flag_;
|
||||
|
||||
#define nibble(mask, limit, adjustment, carry) \
|
||||
result += (a & mask) + (*operand & mask); \
|
||||
partials += result & mask; \
|
||||
if(result >= limit) result = ((result + (adjustment)) & (carry - 1)) + carry;
|
||||
|
||||
nibble(0x000f, 0x000a, 0x0006, 0x00010);
|
||||
nibble(0x00f0, 0x00a0, 0x0060, 0x00100);
|
||||
|
||||
#undef nibble
|
||||
|
||||
overflow_result_ = uint8_t((partials ^ a) & (partials ^ *operand));
|
||||
set_nz(uint8_t(result));
|
||||
carry_flag_ = (result >> 8) & 1;
|
||||
} else {
|
||||
unsigned int result = 0;
|
||||
unsigned int borrow = carry_flag_ ^ 1;
|
||||
const uint16_t decimal_result = uint16_t(a - *operand - borrow);
|
||||
|
||||
#define nibble(mask, adjustment, carry) \
|
||||
result += (a & mask) - (*operand & mask) - borrow; \
|
||||
if(result > mask) result -= adjustment; \
|
||||
borrow = (result > mask) ? carry : 0; \
|
||||
result &= (carry - 1);
|
||||
|
||||
nibble(0x000f, 0x0006, 0x00010);
|
||||
nibble(0x00f0, 0x0060, 0x00100);
|
||||
|
||||
#undef nibble
|
||||
|
||||
overflow_result_ = uint8_t((decimal_result ^ a) & (~decimal_result ^ *operand));
|
||||
set_nz(uint8_t(result));
|
||||
carry_flag_ = ((borrow >> 8)&1)^1;
|
||||
}
|
||||
} else {
|
||||
int result;
|
||||
if(operation == Operation::ADC) {
|
||||
result = int(a + *operand + carry_flag_);
|
||||
overflow_result_ = uint8_t((result ^ a) & (result ^ *operand));
|
||||
} else {
|
||||
result = int(a + ~*operand + carry_flag_);
|
||||
overflow_result_ = uint8_t((result ^ a) & (result ^ ~*operand));
|
||||
}
|
||||
set_nz(uint8_t(result));
|
||||
carry_flag_ = (result >> 8) & 1;
|
||||
}
|
||||
|
||||
if(index_mode_) {
|
||||
write(x_, a);
|
||||
} else {
|
||||
a_ = a;
|
||||
}
|
||||
} break;
|
||||
|
||||
|
||||
/*
|
||||
Already removed from the instruction stream:
|
||||
|
||||
* all branches and jumps;
|
||||
* LDM.
|
||||
*/
|
||||
|
||||
default:
|
||||
LOG("Unimplemented operation: " << operation);
|
||||
assert(false);
|
||||
}
|
||||
#undef set_nz
|
||||
}
|
||||
|
||||
inline void Executor::subtract_duration(int duration) {
|
||||
// Pass along.
|
||||
CachingExecutor::subtract_duration(duration);
|
||||
|
||||
// Update count for potential port accesses.
|
||||
cycles_since_port_handler_ += Cycles(duration);
|
||||
|
||||
// Update timer 1 and 2 prescaler.
|
||||
constexpr int t12_divider = 4; // A divide by 4 has already been applied before counting instruction lengths; therefore
|
||||
// this additional divide by 4 produces the correct net divide by 16.
|
||||
|
||||
timer_divider_ += duration;
|
||||
const int t12_ticks = update_timer(prescalers_[0], timer_divider_ / t12_divider);
|
||||
timer_divider_ &= (t12_divider-1);
|
||||
|
||||
// Update timers 1 and 2. TODO: interrupts (elsewhere?).
|
||||
if(update_timer(timers_[0], t12_ticks)) interrupt_control_ |= 0x20;
|
||||
if(update_timer(timers_[1], t12_ticks)) interrupt_control_ |= 0x08;
|
||||
|
||||
// If timer X is disabled, stop.
|
||||
if(timer_control_&0x20) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Update timer X prescaler.
|
||||
switch(timer_control_ & 0x0c) {
|
||||
default: {
|
||||
const int tx_ticks = update_timer(prescalers_[1], t12_ticks); // TODO: don't hard code this. And is this even right?
|
||||
if(update_timer(timers_[2], tx_ticks))
|
||||
timer_control_ |= 0x80; // TODO: interrupt result of this.
|
||||
} break;
|
||||
case 0x04:
|
||||
LOG("TODO: Timer X; Pulse output mode");
|
||||
break;
|
||||
case 0x08:
|
||||
LOG("TODO: Timer X; Event counter mode");
|
||||
break;
|
||||
case 0x0c:
|
||||
LOG("TODO: Timer X; Pulse width measurement mode");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
inline int Executor::update_timer(Timer &timer, int count) {
|
||||
const int next_value = timer.value - count;
|
||||
if(next_value < 0) {
|
||||
// Determine how many reloads were required to get above zero.
|
||||
const int reload_value = timer.reload_value ? timer.reload_value : 256;
|
||||
const int underflow_count = 1 - next_value / reload_value;
|
||||
timer.value = uint8_t((next_value % reload_value) + timer.reload_value);
|
||||
return underflow_count;
|
||||
}
|
||||
timer.value = uint8_t(next_value);
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint8_t Executor::get_output_mask(int port) {
|
||||
return port_directions_[port];
|
||||
}
|
||||
180
InstructionSets/M50740/Executor.hpp
Normal file
180
InstructionSets/M50740/Executor.hpp
Normal file
@@ -0,0 +1,180 @@
|
||||
//
|
||||
// Executor.h
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 16/01/21.
|
||||
// Copyright © 2021 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef Executor_h
|
||||
#define Executor_h
|
||||
|
||||
#include "Instruction.hpp"
|
||||
#include "Parser.hpp"
|
||||
#include "../CachingExecutor.hpp"
|
||||
#include "../../ClockReceiver/ClockReceiver.hpp"
|
||||
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
|
||||
namespace InstructionSet {
|
||||
namespace M50740 {
|
||||
|
||||
class Executor;
|
||||
using CachingExecutor = CachingExecutor<Executor, 0x1fff, 255, Instruction, false>;
|
||||
|
||||
struct PortHandler {
|
||||
virtual void run_ports_for(Cycles) = 0;
|
||||
virtual void set_port_output(int port, uint8_t value) = 0;
|
||||
virtual uint8_t get_port_input(int port) = 0;
|
||||
};
|
||||
|
||||
/*!
|
||||
Executes M50740 code subject to heavy limitations:
|
||||
|
||||
* the instruction stream cannot run across any of the specialised IO addresses; and
|
||||
* timing is correct to whole-opcode boundaries only.
|
||||
*/
|
||||
class Executor: public CachingExecutor {
|
||||
public:
|
||||
Executor(PortHandler &);
|
||||
void set_rom(const std::vector<uint8_t> &rom);
|
||||
|
||||
void reset();
|
||||
void set_interrupt_line(bool);
|
||||
|
||||
uint8_t get_output_mask(int port);
|
||||
|
||||
/*!
|
||||
Runs, in discrete steps, the minimum number of instructions as it takes to complete at least @c cycles.
|
||||
*/
|
||||
void run_for(Cycles cycles);
|
||||
|
||||
private:
|
||||
// MARK: - CachingExecutor-facing interface.
|
||||
|
||||
friend CachingExecutor;
|
||||
|
||||
/*!
|
||||
Maps instructions to performers; called by the CachingExecutor and for this instruction set, extremely trivial.
|
||||
*/
|
||||
inline PerformerIndex action_for(Instruction instruction) {
|
||||
// This is a super-simple processor, so the opcode can be used directly to index the performers.
|
||||
return instruction.opcode;
|
||||
}
|
||||
|
||||
/*!
|
||||
Parses from @c start and no later than @c max_address, using the CachingExecutor as a target.
|
||||
*/
|
||||
inline void parse(uint16_t start, uint16_t closing_bound) {
|
||||
Parser<Executor, false> parser;
|
||||
parser.parse(*this, &memory_[0], start & 0x1fff, closing_bound);
|
||||
}
|
||||
|
||||
private:
|
||||
// MARK: - Internal framework for generator performers.
|
||||
|
||||
/*!
|
||||
Provides dynamic lookup of @c perform(Executor*).
|
||||
*/
|
||||
class PerformerLookup {
|
||||
public:
|
||||
PerformerLookup() {
|
||||
fill<int(MinOperation)>(performers_);
|
||||
}
|
||||
|
||||
Performer performer(Operation operation, AddressingMode addressing_mode) {
|
||||
const auto index =
|
||||
(int(operation) - MinOperation) * (1 + MaxAddressingMode - MinAddressingMode) +
|
||||
(int(addressing_mode) - MinAddressingMode);
|
||||
return performers_[index];
|
||||
}
|
||||
|
||||
private:
|
||||
Performer performers_[(1 + MaxAddressingMode - MinAddressingMode) * (1 + MaxOperation - MinOperation)];
|
||||
|
||||
template<int operation, int addressing_mode> void fill_operation(Performer *target) {
|
||||
*target = &Executor::perform<Operation(operation), AddressingMode(addressing_mode)>;
|
||||
|
||||
if constexpr (addressing_mode+1 <= MaxAddressingMode) {
|
||||
fill_operation<operation, addressing_mode+1>(target + 1);
|
||||
}
|
||||
}
|
||||
|
||||
template<int operation> void fill(Performer *target) {
|
||||
fill_operation<operation, int(MinAddressingMode)>(target);
|
||||
target += 1 + MaxAddressingMode - MinAddressingMode;
|
||||
|
||||
if constexpr (operation+1 <= MaxOperation) {
|
||||
fill<operation+1>(target);
|
||||
}
|
||||
}
|
||||
};
|
||||
inline static PerformerLookup performer_lookup_;
|
||||
|
||||
/*!
|
||||
Performs @c operation using @c operand as the value fetched from memory, if any.
|
||||
*/
|
||||
template <Operation operation> void perform(uint8_t *operand);
|
||||
|
||||
/*!
|
||||
Performs @c operation in @c addressing_mode.
|
||||
*/
|
||||
template <Operation operation, AddressingMode addressing_mode> void perform();
|
||||
|
||||
private:
|
||||
// MARK: - Instruction set state.
|
||||
|
||||
// Memory.
|
||||
std::array<uint8_t, 0x2000> memory_;
|
||||
|
||||
// Registers.
|
||||
uint8_t a_ = 0, x_ = 0, y_ = 0, s_ = 0;
|
||||
|
||||
uint8_t negative_result_ = 0;
|
||||
uint8_t zero_result_ = 0;
|
||||
uint8_t interrupt_disable_ = 0x04;
|
||||
uint8_t carry_flag_ = 0;
|
||||
uint8_t overflow_result_ = 0;
|
||||
bool index_mode_ = false;
|
||||
bool decimal_mode_ = false;
|
||||
|
||||
// IO ports.
|
||||
uint8_t port_directions_[4] = {0x00, 0x00, 0x00, 0x00};
|
||||
uint8_t port_outputs_[4] = {0xff, 0xff, 0xff, 0xff};
|
||||
|
||||
// Timers.
|
||||
struct Timer {
|
||||
uint8_t value = 0xff, reload_value = 0xff;
|
||||
};
|
||||
int timer_divider_ = 0;
|
||||
Timer timers_[3], prescalers_[2];
|
||||
inline int update_timer(Timer &timer, int count);
|
||||
|
||||
// Interrupt and timer control.
|
||||
uint8_t interrupt_control_ = 0, timer_control_ = 0;
|
||||
bool interrupt_line_ = false;
|
||||
|
||||
// Access helpers.
|
||||
inline uint8_t read(uint16_t address);
|
||||
inline void write(uint16_t address, uint8_t value);
|
||||
inline void push(uint8_t value);
|
||||
inline uint8_t pull();
|
||||
inline void set_flags(uint8_t);
|
||||
inline uint8_t flags();
|
||||
template<bool is_brk> inline void perform_interrupt(uint16_t vector);
|
||||
inline void set_port_output(int port);
|
||||
|
||||
// MARK: - Execution time
|
||||
|
||||
Cycles cycles_;
|
||||
Cycles cycles_since_port_handler_;
|
||||
PortHandler &port_handler_;
|
||||
inline void subtract_duration(int duration);
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* Executor_h */
|
||||
242
InstructionSets/M50740/Instruction.hpp
Normal file
242
InstructionSets/M50740/Instruction.hpp
Normal file
@@ -0,0 +1,242 @@
|
||||
//
|
||||
// Instruction.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 15/01/21.
|
||||
// Copyright © 2021 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef InstructionSets_M50740_Instruction_h
|
||||
#define InstructionSets_M50740_Instruction_h
|
||||
|
||||
#include <cstdint>
|
||||
#include <iomanip>
|
||||
#include <string>
|
||||
#include <sstream>
|
||||
#include "../AccessType.hpp"
|
||||
|
||||
namespace InstructionSet {
|
||||
namespace M50740 {
|
||||
|
||||
enum class AddressingMode {
|
||||
Implied, Accumulator, Immediate,
|
||||
Absolute, AbsoluteX, AbsoluteY,
|
||||
ZeroPage, ZeroPageX, ZeroPageY,
|
||||
XIndirect, IndirectY,
|
||||
Relative,
|
||||
AbsoluteIndirect, ZeroPageIndirect,
|
||||
SpecialPage,
|
||||
ImmediateZeroPage,
|
||||
AccumulatorRelative, ZeroPageRelative
|
||||
};
|
||||
|
||||
static constexpr auto MaxAddressingMode = int(AddressingMode::ZeroPageRelative);
|
||||
static constexpr auto MinAddressingMode = int(AddressingMode::Implied);
|
||||
|
||||
constexpr int size(AddressingMode mode) {
|
||||
// This is coupled to the AddressingMode list above; be careful!
|
||||
constexpr int sizes[] = {
|
||||
0, 0, 1,
|
||||
2, 2, 2,
|
||||
1, 1, 1,
|
||||
1, 1,
|
||||
1,
|
||||
2, 1,
|
||||
1,
|
||||
2,
|
||||
1, 2
|
||||
};
|
||||
static_assert(sizeof(sizes)/sizeof(*sizes) == int(MaxAddressingMode) + 1);
|
||||
return sizes[int(mode)];
|
||||
}
|
||||
|
||||
enum class Operation: uint8_t {
|
||||
Invalid,
|
||||
|
||||
// Operations that don't access memory.
|
||||
BBC0, BBC1, BBC2, BBC3, BBC4, BBC5, BBC6, BBC7,
|
||||
BBS0, BBS1, BBS2, BBS3, BBS4, BBS5, BBS6, BBS7,
|
||||
BCC, BCS,
|
||||
BEQ, BMI, BNE, BPL,
|
||||
BVC, BVS, BRA, BRK,
|
||||
JMP, JSR,
|
||||
RTI, RTS,
|
||||
CLC, CLD, CLI, CLT, CLV,
|
||||
SEC, SED, SEI, SET,
|
||||
INX, INY, DEX, DEY,
|
||||
FST, SLW,
|
||||
NOP,
|
||||
PHA, PHP, PLA, PLP,
|
||||
STP,
|
||||
TAX, TAY, TSX, TXA,
|
||||
TXS, TYA,
|
||||
|
||||
// Read operations.
|
||||
ADC, SBC,
|
||||
AND, ORA, EOR, BIT,
|
||||
CMP, CPX, CPY,
|
||||
LDA, LDX, LDY,
|
||||
TST,
|
||||
|
||||
// Read-modify-write operations.
|
||||
ASL, LSR,
|
||||
CLB0, CLB1, CLB2, CLB3, CLB4, CLB5, CLB6, CLB7,
|
||||
SEB0, SEB1, SEB2, SEB3, SEB4, SEB5, SEB6, SEB7,
|
||||
COM,
|
||||
DEC, INC,
|
||||
ROL, ROR, RRF,
|
||||
|
||||
// Write operations.
|
||||
LDM,
|
||||
STA, STX, STY,
|
||||
};
|
||||
|
||||
static constexpr auto MaxOperation = int(Operation::STY);
|
||||
static constexpr auto MinOperation = int(Operation::BBC0);
|
||||
|
||||
constexpr AccessType access_type(Operation operation) {
|
||||
if(operation < Operation::ADC) return AccessType::None;
|
||||
if(operation < Operation::ASL) return AccessType::Read;
|
||||
if(operation < Operation::LDM) return AccessType::ReadModifyWrite;
|
||||
return AccessType::Write;
|
||||
}
|
||||
|
||||
constexpr bool uses_index_mode(Operation operation) {
|
||||
return
|
||||
operation == Operation::ADC || operation == Operation::AND ||
|
||||
operation == Operation::CMP || operation == Operation::EOR ||
|
||||
operation == Operation::LDA || operation == Operation::ORA ||
|
||||
operation == Operation::SBC;
|
||||
}
|
||||
|
||||
/*!
|
||||
@returns The name of @c operation.
|
||||
*/
|
||||
inline constexpr const char *operation_name(Operation operation) {
|
||||
#define MAP(x) case Operation::x: return #x;
|
||||
switch(operation) {
|
||||
default: break;
|
||||
MAP(BBC0); MAP(BBC1); MAP(BBC2); MAP(BBC3); MAP(BBC4); MAP(BBC5); MAP(BBC6); MAP(BBC7);
|
||||
MAP(BBS0); MAP(BBS1); MAP(BBS2); MAP(BBS3); MAP(BBS4); MAP(BBS5); MAP(BBS6); MAP(BBS7);
|
||||
MAP(BCC); MAP(BCS); MAP(BEQ); MAP(BMI); MAP(BNE); MAP(BPL); MAP(BVC); MAP(BVS);
|
||||
MAP(BRA); MAP(BRK); MAP(JMP); MAP(JSR); MAP(RTI); MAP(RTS); MAP(CLC); MAP(CLD);
|
||||
MAP(CLI); MAP(CLT); MAP(CLV); MAP(SEC); MAP(SED); MAP(SEI); MAP(SET); MAP(INX);
|
||||
MAP(INY); MAP(DEX); MAP(DEY); MAP(FST); MAP(SLW); MAP(NOP); MAP(PHA); MAP(PHP);
|
||||
MAP(PLA); MAP(PLP); MAP(STP); MAP(TAX); MAP(TAY); MAP(TSX); MAP(TXA); MAP(TXS);
|
||||
MAP(TYA); MAP(ADC); MAP(SBC); MAP(AND); MAP(ORA); MAP(EOR); MAP(BIT); MAP(CMP);
|
||||
MAP(CPX); MAP(CPY); MAP(LDA); MAP(LDX); MAP(LDY); MAP(TST); MAP(ASL); MAP(LSR);
|
||||
MAP(CLB0); MAP(CLB1); MAP(CLB2); MAP(CLB3); MAP(CLB4); MAP(CLB5); MAP(CLB6); MAP(CLB7);
|
||||
MAP(SEB0); MAP(SEB1); MAP(SEB2); MAP(SEB3); MAP(SEB4); MAP(SEB5); MAP(SEB6); MAP(SEB7);
|
||||
MAP(COM); MAP(DEC); MAP(INC); MAP(ROL); MAP(ROR); MAP(RRF); MAP(LDM); MAP(STA);
|
||||
MAP(STX); MAP(STY);
|
||||
}
|
||||
#undef MAP
|
||||
|
||||
return "???";
|
||||
}
|
||||
|
||||
inline std::ostream &operator <<(std::ostream &stream, Operation operation) {
|
||||
stream << operation_name(operation);
|
||||
return stream;
|
||||
}
|
||||
|
||||
/*!
|
||||
@returns The name of @c addressing_mode.
|
||||
*/
|
||||
inline constexpr const char *addressing_mode_name(AddressingMode addressing_mode) {
|
||||
switch(addressing_mode) {
|
||||
default: break;
|
||||
case AddressingMode::Implied: return "";
|
||||
case AddressingMode::Accumulator: return "A";
|
||||
case AddressingMode::Immediate: return "#";
|
||||
case AddressingMode::Absolute: return "abs";
|
||||
case AddressingMode::AbsoluteX: return "abs, x";
|
||||
case AddressingMode::AbsoluteY: return "abs, y";
|
||||
case AddressingMode::ZeroPage: return "zp";
|
||||
case AddressingMode::ZeroPageX: return "zp, x";
|
||||
case AddressingMode::ZeroPageY: return "zp, y";
|
||||
case AddressingMode::XIndirect: return "((zp, x))";
|
||||
case AddressingMode::IndirectY: return "((zp), y)";
|
||||
case AddressingMode::Relative: return "rel";
|
||||
case AddressingMode::AbsoluteIndirect: return "(abs)";
|
||||
case AddressingMode::ZeroPageIndirect: return "(zp)";
|
||||
case AddressingMode::SpecialPage: return "\\sp";
|
||||
case AddressingMode::ImmediateZeroPage: return "#, zp";
|
||||
case AddressingMode::AccumulatorRelative: return "A, rel";
|
||||
case AddressingMode::ZeroPageRelative: return "zp, rel";
|
||||
}
|
||||
|
||||
return "???";
|
||||
}
|
||||
|
||||
inline std::ostream &operator <<(std::ostream &stream, AddressingMode mode) {
|
||||
stream << addressing_mode_name(mode);
|
||||
return stream;
|
||||
}
|
||||
|
||||
/*!
|
||||
@returns The way that the address for an operation with @c addressing_mode and encoded starting from @c operation
|
||||
would appear in an assembler. E.g. '$5a' for that zero page address, or '$5a, x' for zero-page indexed from $5a. This function
|
||||
may access up to three bytes from @c operation onwards.
|
||||
*/
|
||||
inline std::string address(AddressingMode addressing_mode, const uint8_t *operation, uint16_t program_counter) {
|
||||
std::stringstream output;
|
||||
output << std::hex;
|
||||
|
||||
#define NUM(x) std::setfill('0') << std::setw(2) << int(x)
|
||||
#define NUM4(x) std::setfill('0') << std::setw(4) << int(x)
|
||||
switch(addressing_mode) {
|
||||
default: return "???";
|
||||
case AddressingMode::Implied: return "";
|
||||
case AddressingMode::Accumulator: return "A ";
|
||||
case AddressingMode::Immediate: output << "#$" << NUM(operation[1]); break;
|
||||
case AddressingMode::Absolute: output << "$" << NUM(operation[2]) << NUM(operation[1]); break;
|
||||
case AddressingMode::AbsoluteX: output << "$" << NUM(operation[2]) << NUM(operation[1]) << ", x"; break;
|
||||
case AddressingMode::AbsoluteY: output << "$" << NUM(operation[2]) << NUM(operation[1]) << ", y"; break;
|
||||
case AddressingMode::ZeroPage: output << "$" << NUM(operation[1]); break;
|
||||
case AddressingMode::ZeroPageX: output << "$" << NUM(operation[1]) << ", x"; break;
|
||||
case AddressingMode::ZeroPageY: output << "$" << NUM(operation[1]) << ", y"; break;
|
||||
case AddressingMode::XIndirect: output << "(($" << NUM(operation[1]) << ", x))"; break;
|
||||
case AddressingMode::IndirectY: output << "(($" << NUM(operation[1]) << "), y)"; break;
|
||||
case AddressingMode::Relative: output << "#$" << NUM4(2 + program_counter + int8_t(operation[1])); break;
|
||||
case AddressingMode::AbsoluteIndirect: output << "($" << NUM(operation[2]) << NUM(operation[1]) << ") "; break;
|
||||
case AddressingMode::ZeroPageIndirect: output << "($" << NUM(operation[1]) << ")"; break;
|
||||
case AddressingMode::SpecialPage: output << "$1f" << NUM(operation[1]); break;
|
||||
case AddressingMode::ImmediateZeroPage: output << "#$" << NUM(operation[1]) << ", $" << NUM(operation[2]); break;
|
||||
case AddressingMode::AccumulatorRelative: output << "A, $" << NUM4(2 + program_counter + int8_t(operation[1])); break;
|
||||
case AddressingMode::ZeroPageRelative:
|
||||
output << "$" << NUM(operation[1]) << ", $" << NUM4(3 + program_counter + int8_t(operation[2]));
|
||||
break;
|
||||
}
|
||||
#undef NUM4
|
||||
#undef NUM
|
||||
|
||||
return output.str();
|
||||
}
|
||||
|
||||
/*!
|
||||
Models a complete M50740-style instruction, including its operation, addressing mode and opcode.
|
||||
*/
|
||||
struct Instruction {
|
||||
Operation operation = Operation::Invalid;
|
||||
AddressingMode addressing_mode = AddressingMode::Implied;
|
||||
uint8_t opcode = 0;
|
||||
|
||||
Instruction(Operation operation, AddressingMode addressing_mode, uint8_t opcode) : operation(operation), addressing_mode(addressing_mode), opcode(opcode) {}
|
||||
Instruction(uint8_t opcode) : opcode(opcode) {}
|
||||
Instruction() {}
|
||||
};
|
||||
|
||||
/*!
|
||||
Outputs a description of @c instruction to @c stream.
|
||||
*/
|
||||
inline std::ostream &operator <<(std::ostream &stream, const Instruction &instruction) {
|
||||
stream << operation_name(instruction.operation) << " " << addressing_mode_name(instruction.addressing_mode);
|
||||
return stream;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#endif /* InstructionSets_M50740_Instruction_h */
|
||||
125
InstructionSets/M50740/Parser.hpp
Normal file
125
InstructionSets/M50740/Parser.hpp
Normal file
@@ -0,0 +1,125 @@
|
||||
//
|
||||
// Parser.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 16/01/21.
|
||||
// Copyright © 2021 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef InstructionSets_M50740_Parser_hpp
|
||||
#define InstructionSets_M50740_Parser_hpp
|
||||
|
||||
#include <cstdint>
|
||||
#include "Decoder.hpp"
|
||||
#include "../AccessType.hpp"
|
||||
|
||||
namespace InstructionSet {
|
||||
namespace M50740 {
|
||||
|
||||
template<typename Target, bool include_entries_and_accesses> struct Parser {
|
||||
void parse(Target &target, const uint8_t *storage, uint16_t start, uint16_t closing_bound) {
|
||||
Decoder decoder;
|
||||
|
||||
while(start <= closing_bound) {
|
||||
const auto next = decoder.decode(&storage[start], 1 + closing_bound - start);
|
||||
if(next.first <= 0) {
|
||||
// If there weren't enough bytes left before the closing bound to complete
|
||||
// an instruction, but implicitly there were some bytes left, announce overflow
|
||||
// and terminate.
|
||||
target.announce_overflow(start);
|
||||
return;
|
||||
} else {
|
||||
// Pass on the instruction.
|
||||
target.announce_instruction(start, next.second);
|
||||
|
||||
if constexpr(!include_entries_and_accesses) {
|
||||
// Do a simplified test: is this a terminating operation?
|
||||
switch(next.second.operation) {
|
||||
case Operation::RTS: case Operation::RTI: case Operation::BRK:
|
||||
case Operation::JMP: case Operation::BRA:
|
||||
return;
|
||||
|
||||
default: break;
|
||||
}
|
||||
} else {
|
||||
// Check for end of stream and potential new entry points.
|
||||
switch(next.second.operation) {
|
||||
// Terminating instructions.
|
||||
case Operation::RTS: case Operation::RTI: case Operation::BRK:
|
||||
return;
|
||||
|
||||
// Terminating operations, possibly with implied additional entry point.
|
||||
case Operation::JMP:
|
||||
if(next.second.addressing_mode == AddressingMode::Absolute) {
|
||||
target.add_entry(uint16_t(storage[start + 1] | (storage[start + 2] << 8)));
|
||||
}
|
||||
return;
|
||||
case Operation::BRA:
|
||||
target.add_entry(uint16_t(start + 2 + int8_t(storage[start + 1])));
|
||||
return;
|
||||
|
||||
// Instructions that suggest another entry point but don't terminate parsing.
|
||||
case Operation::BCC: case Operation::BCS:
|
||||
case Operation::BVC: case Operation::BVS:
|
||||
case Operation::BMI: case Operation::BPL:
|
||||
case Operation::BNE: case Operation::BEQ:
|
||||
target.add_entry(uint16_t(start + 2 + int8_t(storage[start + 1])));
|
||||
break;
|
||||
case Operation::JSR:
|
||||
switch(next.second.addressing_mode) {
|
||||
default: break;
|
||||
case AddressingMode::Absolute:
|
||||
target.add_entry(uint16_t(storage[start + 1] | (storage[start + 2] << 8)));
|
||||
break;
|
||||
case AddressingMode::SpecialPage:
|
||||
target.add_entry(uint16_t(storage[start + 1] | 0x1f00));
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
case Operation::BBS0: case Operation::BBS1: case Operation::BBS2: case Operation::BBS3:
|
||||
case Operation::BBS4: case Operation::BBS5: case Operation::BBS6: case Operation::BBS7:
|
||||
case Operation::BBC0: case Operation::BBC1: case Operation::BBC2: case Operation::BBC3:
|
||||
case Operation::BBC4: case Operation::BBC5: case Operation::BBC6: case Operation::BBC7:
|
||||
switch(next.second.addressing_mode) {
|
||||
default: break;
|
||||
case AddressingMode::AccumulatorRelative:
|
||||
target.add_entry(uint16_t(start + 2 + int8_t(storage[start + 1])));
|
||||
break;
|
||||
case AddressingMode::ZeroPageRelative:
|
||||
target.add_entry(uint16_t(start + 3 + int8_t(storage[start + 2])));
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
|
||||
default: break;
|
||||
}
|
||||
|
||||
// Provide any fixed address accesses.
|
||||
switch(next.second.addressing_mode) {
|
||||
case AddressingMode::Absolute:
|
||||
target.add_access(uint16_t(storage[start + 1] | (storage[start + 2] << 8)), access_type(next.second.operation));
|
||||
break;
|
||||
case AddressingMode::ZeroPage: case AddressingMode::ZeroPageRelative:
|
||||
target.add_access(storage[start + 1], access_type(next.second.operation));
|
||||
break;
|
||||
case AddressingMode::ImmediateZeroPage:
|
||||
target.add_access(storage[start + 2], access_type(next.second.operation));
|
||||
break;
|
||||
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
|
||||
// Advance.
|
||||
start += next.first;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* InstructionSets_M50740_Parser_hpp */
|
||||
1492
InstructionSets/M68k/Decoder.cpp
Normal file
1492
InstructionSets/M68k/Decoder.cpp
Normal file
File diff suppressed because it is too large
Load Diff
113
InstructionSets/M68k/Decoder.hpp
Normal file
113
InstructionSets/M68k/Decoder.hpp
Normal file
@@ -0,0 +1,113 @@
|
||||
//
|
||||
// Decoder.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 10/04/2022.
|
||||
// Copyright © 2022 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef InstructionSets_M68k_Decoder_hpp
|
||||
#define InstructionSets_M68k_Decoder_hpp
|
||||
|
||||
#include "Instruction.hpp"
|
||||
#include "Model.hpp"
|
||||
|
||||
namespace InstructionSet {
|
||||
namespace M68k {
|
||||
|
||||
/*!
|
||||
A stateless decoder that can map from instruction words to preinstructions
|
||||
(i.e. enough to know the operation and size, and either know the addressing mode
|
||||
and registers or else know how many further extension words are needed).
|
||||
|
||||
WARNING: at present this handles the original 68000 instruction set only. It
|
||||
requires a model only for the sake of not baking in assumptions about MOVE SR, etc,
|
||||
and supporting extended addressing modes in some cases.
|
||||
|
||||
But it does not yet decode any operations which were not present on the 68000.
|
||||
*/
|
||||
template <Model model> class Predecoder {
|
||||
public:
|
||||
Preinstruction decode(uint16_t instruction);
|
||||
|
||||
private:
|
||||
// Page by page decoders; each gets a bit ad hoc so
|
||||
// it is neater to separate them.
|
||||
Preinstruction decode0(uint16_t instruction);
|
||||
Preinstruction decode1(uint16_t instruction);
|
||||
Preinstruction decode2(uint16_t instruction);
|
||||
Preinstruction decode3(uint16_t instruction);
|
||||
Preinstruction decode4(uint16_t instruction);
|
||||
Preinstruction decode5(uint16_t instruction);
|
||||
Preinstruction decode6(uint16_t instruction);
|
||||
Preinstruction decode7(uint16_t instruction);
|
||||
Preinstruction decode8(uint16_t instruction);
|
||||
Preinstruction decode9(uint16_t instruction);
|
||||
Preinstruction decodeA(uint16_t instruction);
|
||||
Preinstruction decodeB(uint16_t instruction);
|
||||
Preinstruction decodeC(uint16_t instruction);
|
||||
Preinstruction decodeD(uint16_t instruction);
|
||||
Preinstruction decodeE(uint16_t instruction);
|
||||
Preinstruction decodeF(uint16_t instruction);
|
||||
|
||||
using OpT = uint8_t;
|
||||
|
||||
// Specific instruction decoders.
|
||||
template <OpT operation, bool validate = true> Preinstruction decode(uint16_t instruction);
|
||||
template <OpT operation, bool validate> Preinstruction validated(
|
||||
AddressingMode op1_mode = AddressingMode::None, int op1_reg = 0,
|
||||
AddressingMode op2_mode = AddressingMode::None, int op2_reg = 0,
|
||||
Condition condition = Condition::True
|
||||
);
|
||||
template <uint8_t op> uint32_t invalid_operands();
|
||||
|
||||
// Extended operation list; collapses into a single byte enough information to
|
||||
// know both the type of operation and how to decode the operands. Most of the
|
||||
// time that's knowable from the Operation alone, hence the rather awkward
|
||||
// extension of @c Operation.
|
||||
enum ExtendedOperation: OpT {
|
||||
MOVEPtoRl = uint8_t(Operation::Max) + 1, MOVEPtoRw,
|
||||
MOVEPtoMl, MOVEPtoMw,
|
||||
|
||||
MOVEQ,
|
||||
|
||||
ADDQb, ADDQw, ADDQl,
|
||||
ADDQAw, ADDQAl,
|
||||
SUBQb, SUBQw, SUBQl,
|
||||
SUBQAw, SUBQAl,
|
||||
|
||||
ADDIb, ADDIw, ADDIl,
|
||||
ORIb, ORIw, ORIl,
|
||||
SUBIb, SUBIw, SUBIl,
|
||||
ANDIb, ANDIw, ANDIl,
|
||||
EORIb, EORIw, EORIl,
|
||||
CMPIb, CMPIw, CMPIl,
|
||||
|
||||
BTSTI, BCHGI, BCLRI, BSETI,
|
||||
|
||||
CMPMb, CMPMw, CMPMl,
|
||||
|
||||
MOVEq,
|
||||
|
||||
ADDtoMb, ADDtoMw, ADDtoMl,
|
||||
ADDtoRb, ADDtoRw, ADDtoRl,
|
||||
|
||||
SUBtoMb, SUBtoMw, SUBtoMl,
|
||||
SUBtoRb, SUBtoRw, SUBtoRl,
|
||||
|
||||
ANDtoMb, ANDtoMw, ANDtoMl,
|
||||
ANDtoRb, ANDtoRw, ANDtoRl,
|
||||
|
||||
ORtoMb, ORtoMw, ORtoMl,
|
||||
ORtoRb, ORtoRw, ORtoRl,
|
||||
|
||||
EXGRtoR, EXGAtoA, EXGRtoA,
|
||||
};
|
||||
|
||||
static constexpr Operation operation(OpT op);
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* InstructionSets_M68k_Decoder_hpp */
|
||||
50
InstructionSets/M68k/ExceptionVectors.hpp
Normal file
50
InstructionSets/M68k/ExceptionVectors.hpp
Normal file
@@ -0,0 +1,50 @@
|
||||
//
|
||||
// ExceptionVectors.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 10/05/2022.
|
||||
// Copyright © 2022 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef InstructionSets_M68k_ExceptionVectors_hpp
|
||||
#define InstructionSets_M68k_ExceptionVectors_hpp
|
||||
|
||||
namespace InstructionSet {
|
||||
namespace M68k {
|
||||
|
||||
enum Exception {
|
||||
InitialStackPointer = 0,
|
||||
InitialProgramCounter = 1,
|
||||
AccessFault = 2,
|
||||
AddressError = 3,
|
||||
IllegalInstruction = 4,
|
||||
IntegerDivideByZero = 5,
|
||||
CHK = 6,
|
||||
TRAPV = 7,
|
||||
PrivilegeViolation = 8,
|
||||
Trace = 9,
|
||||
Line1010 = 10,
|
||||
Line1111 = 11,
|
||||
CoprocessorProtocolViolation = 13,
|
||||
FormatError = 14,
|
||||
UninitialisedInterrupt = 15,
|
||||
SpuriousInterrupt = 24,
|
||||
InterruptAutovectorBase = 25, // This is the vector for interrupt level _1_.
|
||||
TrapBase = 32,
|
||||
FPBranchOrSetOnUnorderedCondition = 48,
|
||||
FPInexactResult = 49,
|
||||
FPDivideByZero = 50,
|
||||
FPUnderflow = 51,
|
||||
FPOperandError = 52,
|
||||
FPOverflow = 53,
|
||||
FPSignallingNAN = 54,
|
||||
FPUnimplementedDataType = 55,
|
||||
MMUConfigurationError = 56,
|
||||
MMUIllegalOperationError = 57,
|
||||
MMUAccessLevelViolationError = 58,
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* InstructionSets_M68k_ExceptionVectors_hpp */
|
||||
171
InstructionSets/M68k/Executor.hpp
Normal file
171
InstructionSets/M68k/Executor.hpp
Normal file
@@ -0,0 +1,171 @@
|
||||
//
|
||||
// Executor.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 29/04/2022.
|
||||
// Copyright © 2022 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef InstructionSets_M68k_Executor_hpp
|
||||
#define InstructionSets_M68k_Executor_hpp
|
||||
|
||||
#include "Decoder.hpp"
|
||||
#include "Instruction.hpp"
|
||||
#include "Model.hpp"
|
||||
#include "Perform.hpp"
|
||||
#include "RegisterSet.hpp"
|
||||
#include "Status.hpp"
|
||||
|
||||
namespace InstructionSet {
|
||||
namespace M68k {
|
||||
|
||||
/// Maps the 68k function codes such that bits 0, 1 and 2 represent
|
||||
/// FC0, FC1 and FC2 respectively.
|
||||
enum class FunctionCode {
|
||||
UserData = 0b001,
|
||||
UserProgram = 0b010,
|
||||
SupervisorData = 0b101,
|
||||
SupervisorProgram = 0b110,
|
||||
InterruptAcknowledge = 0b111,
|
||||
};
|
||||
|
||||
/// The Executor is templated on a class that implements bus handling as defined below;
|
||||
/// the bus handler is responsible for all reads and writes, and will also receive resets and
|
||||
/// interrupt acknowledgements.
|
||||
///
|
||||
/// The executor will provide 32-bit addresses and act as if it had a 32-bit data bus, even
|
||||
/// if interpretting the original 68000 instruction set.
|
||||
struct BusHandler {
|
||||
/// Write @c value of type/size @c IntT to @c address with the processor signalling
|
||||
/// a FunctionCode of @c function. @c IntT will be one of @c uint8_t, @c uint16_t
|
||||
/// or @c uint32_t.
|
||||
template <typename IntT> void write(uint32_t address, IntT value, FunctionCode function);
|
||||
|
||||
/// Read and return a value of type/size @c IntT from @c address with the processor signalling
|
||||
/// a FunctionCode of @c function. @c IntT will be one of @c uint8_t, @c uint16_t
|
||||
/// or @c uint32_t.
|
||||
template <typename IntT> IntT read(uint32_t address, FunctionCode function);
|
||||
|
||||
/// React to the processor programmatically strobing its RESET output.
|
||||
void reset();
|
||||
|
||||
/// Respond to an interrupt acknowledgement at @c interrupt_level from the processor.
|
||||
/// Should return @c -1 in order to trigger autovectoring, or the appropriate exception vector
|
||||
/// number otherwise.
|
||||
///
|
||||
/// It is undefined behaviour to return a number greater than 255.
|
||||
int acknowlege_interrupt(int interrupt_level);
|
||||
};
|
||||
|
||||
/// Ties together the decoder, sequencer and performer to provide an executor for 680x0 instruction streams.
|
||||
/// As is standard for these executors, no bus- or cache-level fidelity to any real 680x0 is attempted. This is
|
||||
/// simply an executor of 680x0 code.
|
||||
template <Model model, typename BusHandler> class Executor {
|
||||
public:
|
||||
Executor(BusHandler &);
|
||||
|
||||
/// Reset the processor, back to a state as if just externally reset.
|
||||
void reset();
|
||||
|
||||
/// Executes the number of instructions specified;
|
||||
/// other events — such as initial reset or branching
|
||||
/// to exceptions — may be zero costed, and interrupts
|
||||
/// will not necessarily take effect immediately when signalled.
|
||||
void run_for_instructions(int);
|
||||
|
||||
/// Call this at any time to interrupt processing with a bus error;
|
||||
/// the function code and address must be provided. Internally
|
||||
/// this will raise a C++ exception, and therefore doesn't return.
|
||||
[[noreturn]] void signal_bus_error(FunctionCode, uint32_t address);
|
||||
|
||||
/// Sets the current input interrupt level.
|
||||
void set_interrupt_level(int);
|
||||
|
||||
// State for the executor is just the register set.
|
||||
RegisterSet get_state();
|
||||
void set_state(const RegisterSet &);
|
||||
|
||||
private:
|
||||
class State: public NullFlowController {
|
||||
public:
|
||||
State(BusHandler &handler) : bus_handler_(handler) {}
|
||||
|
||||
void run(int &);
|
||||
bool stopped = false;
|
||||
|
||||
void read(DataSize size, uint32_t address, CPU::SlicedInt32 &value);
|
||||
void write(DataSize size, uint32_t address, CPU::SlicedInt32 value);
|
||||
template <typename IntT> IntT read(uint32_t address, bool is_from_pc = false);
|
||||
template <typename IntT> void write(uint32_t address, IntT value);
|
||||
|
||||
template <typename IntT> IntT read_pc();
|
||||
|
||||
// Processor state.
|
||||
Status status;
|
||||
CPU::SlicedInt32 program_counter;
|
||||
CPU::SlicedInt32 registers[16]; // D0–D7 followed by A0–A7.
|
||||
CPU::SlicedInt32 stack_pointers[2];
|
||||
uint32_t instruction_address;
|
||||
uint16_t instruction_opcode;
|
||||
|
||||
// Things that are ephemerally duplicative of Status.
|
||||
int active_stack_pointer = 0;
|
||||
Status::FlagT should_trace = 0;
|
||||
|
||||
// Bus state.
|
||||
int interrupt_input = 0;
|
||||
|
||||
// A lookup table to ensure that A7 is adjusted by 2 rather than 1 in
|
||||
// postincrement and predecrement mode.
|
||||
static constexpr uint32_t byte_increments[] = {
|
||||
1, 1, 1, 1, 1, 1, 1, 2
|
||||
};
|
||||
|
||||
// Flow control; Cf. Perform.hpp.
|
||||
template <bool use_current_instruction_pc = true> void raise_exception(int);
|
||||
|
||||
void did_update_status();
|
||||
|
||||
template <typename IntT> void complete_bcc(bool matched_condition, IntT offset);
|
||||
void complete_dbcc(bool matched_condition, bool overflowed, int16_t offset);
|
||||
void bsr(uint32_t offset);
|
||||
void jmp(uint32_t);
|
||||
void jsr(uint32_t offset);
|
||||
void rtr();
|
||||
void rts();
|
||||
void rte();
|
||||
void stop();
|
||||
void reset();
|
||||
|
||||
void link(Preinstruction instruction, uint32_t offset);
|
||||
void unlink(uint32_t &address);
|
||||
void pea(uint32_t address);
|
||||
|
||||
void move_to_usp(uint32_t address);
|
||||
void move_from_usp(uint32_t &address);
|
||||
|
||||
template <typename IntT> void movep(Preinstruction instruction, uint32_t source, uint32_t dest);
|
||||
template <typename IntT> void movem_toM(Preinstruction instruction, uint32_t source, uint32_t dest);
|
||||
template <typename IntT> void movem_toR(Preinstruction instruction, uint32_t source, uint32_t dest);
|
||||
|
||||
void tas(Preinstruction instruction, uint32_t address);
|
||||
|
||||
private:
|
||||
BusHandler &bus_handler_;
|
||||
Predecoder<model> decoder_;
|
||||
|
||||
struct EffectiveAddress {
|
||||
CPU::SlicedInt32 value;
|
||||
bool requires_fetch;
|
||||
};
|
||||
EffectiveAddress calculate_effective_address(Preinstruction instruction, uint16_t opcode, int index);
|
||||
uint32_t index_8bitdisplacement();
|
||||
} state_;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#include "Implementation/ExecutorImplementation.hpp"
|
||||
|
||||
#endif /* InstructionSets_M68k_Executor_hpp */
|
||||
695
InstructionSets/M68k/Implementation/ExecutorImplementation.hpp
Normal file
695
InstructionSets/M68k/Implementation/ExecutorImplementation.hpp
Normal file
@@ -0,0 +1,695 @@
|
||||
//
|
||||
// ExecutorImplementation.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 01/05/2022.
|
||||
// Copyright © 2022 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef InstructionSets_M68k_ExecutorImplementation_hpp
|
||||
#define InstructionSets_M68k_ExecutorImplementation_hpp
|
||||
|
||||
#include "../Perform.hpp"
|
||||
#include "../ExceptionVectors.hpp"
|
||||
|
||||
#include <cassert>
|
||||
|
||||
namespace InstructionSet {
|
||||
namespace M68k {
|
||||
|
||||
#define An(x) state_.registers[8 + x]
|
||||
#define Dn(x) state_.registers[x]
|
||||
#define sp An(7)
|
||||
|
||||
#define AccessException(code, address, vector) \
|
||||
uint64_t(((vector) << 8) | uint64_t(code) | ((address) << 16))
|
||||
|
||||
// MARK: - Executor itself.
|
||||
|
||||
template <Model model, typename BusHandler>
|
||||
Executor<model, BusHandler>::Executor(BusHandler &handler) : state_(handler) {
|
||||
reset();
|
||||
}
|
||||
|
||||
template <Model model, typename BusHandler>
|
||||
void Executor<model, BusHandler>::reset() {
|
||||
// Establish: supervisor state, all interrupts blocked.
|
||||
state_.status.set_status(0b0010'0011'1000'0000);
|
||||
state_.did_update_status();
|
||||
|
||||
// Clear the STOPped state, if currently active.
|
||||
state_.stopped = false;
|
||||
|
||||
// Seed stack pointer and program counter.
|
||||
sp.l = state_.template read<uint32_t>(0) & 0xffff'fffe;
|
||||
state_.program_counter.l = state_.template read<uint32_t>(4);
|
||||
}
|
||||
|
||||
template <Model model, typename BusHandler>
|
||||
void Executor<model, BusHandler>::signal_bus_error(FunctionCode code, uint32_t address) {
|
||||
throw AccessException(code, address, Exception::AccessFault);
|
||||
}
|
||||
|
||||
template <Model model, typename BusHandler>
|
||||
void Executor<model, BusHandler>::set_interrupt_level(int level) {
|
||||
state_.interrupt_input_ = level;
|
||||
state_.stopped &= !state_.status.would_accept_interrupt(level);
|
||||
}
|
||||
|
||||
template <Model model, typename BusHandler>
|
||||
void Executor<model, BusHandler>::run_for_instructions(int count) {
|
||||
if(state_.stopped) return;
|
||||
|
||||
while(count > 0) {
|
||||
try {
|
||||
state_.run(count);
|
||||
} catch (uint64_t exception) {
|
||||
// Potiental source of an exception #1: STOP. Check for that first.
|
||||
if(state_.stopped) return;
|
||||
|
||||
// Unpack the exception; this is the converse of the AccessException macro.
|
||||
const int vector_address = (exception >> 6) & 0xfc;
|
||||
const uint16_t code = uint16_t(exception & 0xff);
|
||||
const uint32_t faulting_address = uint32_t(exception >> 16);
|
||||
|
||||
// Grab the status to store, then switch into supervisor mode.
|
||||
const uint16_t status = state_.status.status();
|
||||
state_.status.is_supervisor = true;
|
||||
state_.status.trace_flag = 0;
|
||||
state_.did_update_status();
|
||||
|
||||
// Ensure no tracing occurs into the exception.
|
||||
state_.should_trace = 0;
|
||||
|
||||
// Push status and the program counter at instruction start.
|
||||
state_.template write<uint16_t>(sp.l - 14, code);
|
||||
state_.template write<uint32_t>(sp.l - 12, faulting_address);
|
||||
state_.template write<uint16_t>(sp.l - 8, state_.instruction_opcode);
|
||||
state_.template write<uint16_t>(sp.l - 6, status);
|
||||
state_.template write<uint16_t>(sp.l - 4, state_.instruction_address);
|
||||
sp.l -= 14;
|
||||
|
||||
// Fetch the new program counter; reset on a double fault.
|
||||
try {
|
||||
state_.program_counter.l = state_.template read<uint32_t>(vector_address);
|
||||
} catch (uint64_t) {
|
||||
// TODO: I think this is incorrect, but need to verify consistency
|
||||
// across different 680x0s.
|
||||
reset();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <Model model, typename BusHandler>
|
||||
RegisterSet Executor<model, BusHandler>::get_state() {
|
||||
RegisterSet result;
|
||||
|
||||
for(int c = 0; c < 8; c++) {
|
||||
result.data[c] = Dn(c).l;
|
||||
}
|
||||
for(int c = 0; c < 7; c++) {
|
||||
result.address[c] = An(c).l;
|
||||
}
|
||||
result.status = state_.status.status();
|
||||
result.program_counter = state_.program_counter.l;
|
||||
|
||||
state_.stack_pointers[state_.active_stack_pointer] = sp;
|
||||
result.user_stack_pointer = state_.stack_pointers[0].l;
|
||||
result.supervisor_stack_pointer = state_.stack_pointers[1].l;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
template <Model model, typename BusHandler>
|
||||
void Executor<model, BusHandler>::set_state(const RegisterSet &state) {
|
||||
for(int c = 0; c < 8; c++) {
|
||||
Dn(c).l = state.data[c];
|
||||
}
|
||||
for(int c = 0; c < 7; c++) {
|
||||
An(c).l = state.address[c];
|
||||
}
|
||||
state_.status.set_status(state.status);
|
||||
state_.did_update_status();
|
||||
state_.program_counter.l = state.program_counter;
|
||||
|
||||
state_.stack_pointers[0].l = state.user_stack_pointer;
|
||||
state_.stack_pointers[1].l = state.supervisor_stack_pointer;
|
||||
sp = state_.stack_pointers[state_.active_stack_pointer];
|
||||
}
|
||||
|
||||
#undef Dn
|
||||
#undef An
|
||||
|
||||
// MARK: - State.
|
||||
|
||||
#define An(x) registers[8 + x]
|
||||
#define Dn(x) registers[x]
|
||||
|
||||
template <Model model, typename BusHandler>
|
||||
template <typename IntT>
|
||||
IntT Executor<model, BusHandler>::State::read(uint32_t address, bool is_from_pc) {
|
||||
const auto code = FunctionCode((active_stack_pointer << 2) | 1 << int(is_from_pc));
|
||||
if(model == Model::M68000 && sizeof(IntT) > 1 && address & 1) {
|
||||
throw AccessException(code, address, Exception::AddressError | (int(is_from_pc) << 3) | (1 << 4));
|
||||
}
|
||||
|
||||
return bus_handler_.template read<IntT>(address, code);
|
||||
}
|
||||
|
||||
template <Model model, typename BusHandler>
|
||||
template <typename IntT>
|
||||
void Executor<model, BusHandler>::State::write(uint32_t address, IntT value) {
|
||||
const auto code = FunctionCode((active_stack_pointer << 2) | 1);
|
||||
if(model == Model::M68000 && sizeof(IntT) > 1 && address & 1) {
|
||||
throw AccessException(code, address, Exception::AddressError);
|
||||
}
|
||||
|
||||
bus_handler_.template write<IntT>(address, value, code);
|
||||
}
|
||||
|
||||
template <Model model, typename BusHandler>
|
||||
void Executor<model, BusHandler>::State::read(DataSize size, uint32_t address, CPU::SlicedInt32 &value) {
|
||||
switch(size) {
|
||||
case DataSize::Byte: value.b = read<uint8_t>(address); break;
|
||||
case DataSize::Word: value.w = read<uint16_t>(address); break;
|
||||
case DataSize::LongWord: value.l = read<uint32_t>(address); break;
|
||||
}
|
||||
}
|
||||
|
||||
template <Model model, typename BusHandler>
|
||||
void Executor<model, BusHandler>::State::write(DataSize size, uint32_t address, CPU::SlicedInt32 value) {
|
||||
switch(size) {
|
||||
case DataSize::Byte: write<uint8_t>(address, value.b); break;
|
||||
case DataSize::Word: write<uint16_t>(address, value.w); break;
|
||||
case DataSize::LongWord: write<uint32_t>(address, value.l); break;
|
||||
}
|
||||
}
|
||||
|
||||
template <Model model, typename BusHandler>
|
||||
template <typename IntT> IntT Executor<model, BusHandler>::State::read_pc() {
|
||||
const IntT result = read<IntT>(program_counter.l, true);
|
||||
|
||||
if constexpr (sizeof(IntT) == 4) {
|
||||
program_counter.l += 4;
|
||||
} else {
|
||||
program_counter.l += 2;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
template <Model model, typename BusHandler>
|
||||
uint32_t Executor<model, BusHandler>::State::index_8bitdisplacement() {
|
||||
// TODO: if not a 68000, check bit 8 for whether this should be a full extension word;
|
||||
// also include the scale field even if not.
|
||||
const auto extension = read_pc<uint16_t>();
|
||||
const auto offset = int8_t(extension);
|
||||
const int register_index = (extension >> 12) & 15;
|
||||
const uint32_t displacement = registers[register_index].l;
|
||||
const uint32_t sized_displacement = (extension & 0x800) ? displacement : int16_t(displacement);
|
||||
return offset + sized_displacement;
|
||||
}
|
||||
|
||||
template <Model model, typename BusHandler>
|
||||
typename Executor<model, BusHandler>::State::EffectiveAddress
|
||||
Executor<model, BusHandler>::State::calculate_effective_address(Preinstruction instruction, uint16_t opcode, int index) {
|
||||
EffectiveAddress ea;
|
||||
|
||||
switch(instruction.mode(index)) {
|
||||
case AddressingMode::None:
|
||||
// Permit an uninitialised effective address to be returned;
|
||||
// this value shouldn't be used.
|
||||
break;
|
||||
|
||||
//
|
||||
// Operands that don't have effective addresses, which are returned as values.
|
||||
//
|
||||
case AddressingMode::DataRegisterDirect:
|
||||
case AddressingMode::AddressRegisterDirect:
|
||||
ea.value = registers[instruction.lreg(index)];
|
||||
ea.requires_fetch = false;
|
||||
break;
|
||||
case AddressingMode::Quick:
|
||||
ea.value.l = quick(opcode, instruction.operation);
|
||||
ea.requires_fetch = false;
|
||||
break;
|
||||
case AddressingMode::ImmediateData:
|
||||
switch(instruction.operand_size()) {
|
||||
case DataSize::Byte:
|
||||
ea.value.l = read_pc<uint16_t>() & 0xff;
|
||||
break;
|
||||
case DataSize::Word:
|
||||
ea.value.l = read_pc<uint16_t>();
|
||||
break;
|
||||
case DataSize::LongWord:
|
||||
ea.value.l = read_pc<uint32_t>();
|
||||
break;
|
||||
}
|
||||
ea.requires_fetch = false;
|
||||
break;
|
||||
|
||||
//
|
||||
// Absolute addresses.
|
||||
//
|
||||
case AddressingMode::AbsoluteShort:
|
||||
ea.value.l = int16_t(read_pc<uint16_t>());
|
||||
ea.requires_fetch = true;
|
||||
break;
|
||||
case AddressingMode::AbsoluteLong:
|
||||
ea.value.l = read_pc<uint32_t>();
|
||||
ea.requires_fetch = true;
|
||||
break;
|
||||
|
||||
//
|
||||
// Address register indirects.
|
||||
//
|
||||
case AddressingMode::AddressRegisterIndirect:
|
||||
ea.value = An(instruction.reg(index));
|
||||
ea.requires_fetch = true;
|
||||
break;
|
||||
case AddressingMode::AddressRegisterIndirectWithPostincrement: {
|
||||
const auto reg = instruction.reg(index);
|
||||
|
||||
ea.value = An(reg);
|
||||
ea.requires_fetch = true;
|
||||
|
||||
switch(instruction.operand_size()) {
|
||||
case DataSize::Byte: An(reg).l += byte_increments[reg]; break;
|
||||
case DataSize::Word: An(reg).l += 2; break;
|
||||
case DataSize::LongWord: An(reg).l += 4; break;
|
||||
}
|
||||
} break;
|
||||
case AddressingMode::AddressRegisterIndirectWithPredecrement: {
|
||||
const auto reg = instruction.reg(index);
|
||||
|
||||
switch(instruction.operand_size()) {
|
||||
case DataSize::Byte: An(reg).l -= byte_increments[reg]; break;
|
||||
case DataSize::Word: An(reg).l -= 2; break;
|
||||
case DataSize::LongWord: An(reg).l -= 4; break;
|
||||
}
|
||||
|
||||
ea.value = An(reg);
|
||||
ea.requires_fetch = true;
|
||||
} break;
|
||||
case AddressingMode::AddressRegisterIndirectWithDisplacement:
|
||||
ea.value.l = An(instruction.reg(index)).l + int16_t(read_pc<uint16_t>());
|
||||
ea.requires_fetch = true;
|
||||
break;
|
||||
case AddressingMode::AddressRegisterIndirectWithIndex8bitDisplacement:
|
||||
ea.value.l = An(instruction.reg(index)).l + index_8bitdisplacement();
|
||||
ea.requires_fetch = true;
|
||||
break;
|
||||
|
||||
//
|
||||
// PC-relative addresses.
|
||||
//
|
||||
case AddressingMode::ProgramCounterIndirectWithDisplacement:
|
||||
ea.value.l = program_counter.l + int16_t(read_pc<uint16_t>());
|
||||
ea.requires_fetch = true;
|
||||
break;
|
||||
case AddressingMode::ProgramCounterIndirectWithIndex8bitDisplacement:
|
||||
ea.value.l = program_counter.l + index_8bitdisplacement();
|
||||
ea.requires_fetch = true;
|
||||
break;
|
||||
|
||||
default:
|
||||
assert(false);
|
||||
}
|
||||
|
||||
return ea;
|
||||
}
|
||||
|
||||
template <Model model, typename BusHandler>
|
||||
void Executor<model, BusHandler>::State::run(int &count) {
|
||||
while(count--) {
|
||||
// Check for a new interrupt.
|
||||
if(status.would_accept_interrupt(interrupt_input)) {
|
||||
const int vector = bus_handler_.acknowlege_interrupt(interrupt_input);
|
||||
if(vector >= 0) {
|
||||
raise_exception<false>(vector);
|
||||
} else {
|
||||
raise_exception<false>(Exception::InterruptAutovectorBase - 1 + interrupt_input);
|
||||
}
|
||||
status.interrupt_level = interrupt_input;
|
||||
}
|
||||
|
||||
// Capture the trace bit, indicating whether to trace
|
||||
// after this instruction.
|
||||
//
|
||||
// If an exception occurs, this value will be cleared, but
|
||||
// it'll persist across mere status register changes for
|
||||
// one instruction's duration.
|
||||
should_trace = status.trace_flag;
|
||||
|
||||
// Read the next instruction.
|
||||
instruction_address = program_counter.l;
|
||||
instruction_opcode = read_pc<uint16_t>();
|
||||
const Preinstruction instruction = decoder_.decode(instruction_opcode);
|
||||
|
||||
if(instruction.requires_supervisor() && !status.is_supervisor) {
|
||||
raise_exception(Exception::PrivilegeViolation);
|
||||
continue;
|
||||
}
|
||||
if(instruction.operation == Operation::Undefined) {
|
||||
switch(instruction_opcode & 0xf000) {
|
||||
default:
|
||||
raise_exception(Exception::IllegalInstruction);
|
||||
continue;
|
||||
case 0xa000:
|
||||
raise_exception(Exception::Line1010);
|
||||
continue;
|
||||
case 0xf000:
|
||||
raise_exception(Exception::Line1111);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// Temporary storage.
|
||||
CPU::SlicedInt32 operand_[2];
|
||||
EffectiveAddress effective_address_[2];
|
||||
|
||||
// Calculate effective addresses; copy 'addresses' into the
|
||||
// operands by default both: (i) because they might be values,
|
||||
// rather than addresses; and (ii) then they'll be there for use
|
||||
// by LEA and PEA.
|
||||
effective_address_[0] = calculate_effective_address(instruction, instruction_opcode, 0);
|
||||
effective_address_[1] = calculate_effective_address(instruction, instruction_opcode, 1);
|
||||
operand_[0] = effective_address_[0].value;
|
||||
operand_[1] = effective_address_[1].value;
|
||||
|
||||
// Obtain the appropriate sequence.
|
||||
const auto flags = operand_flags<model>(instruction.operation);
|
||||
|
||||
#define fetch_operand(n) \
|
||||
if(effective_address_[n].requires_fetch) { \
|
||||
read(instruction.operand_size(), effective_address_[n].value.l, operand_[n]); \
|
||||
}
|
||||
|
||||
if(flags & FetchOp1) { fetch_operand(0); }
|
||||
if(flags & FetchOp2) { fetch_operand(1); }
|
||||
|
||||
#undef fetch_operand
|
||||
|
||||
perform<model>(instruction, operand_[0], operand_[1], status, *this);
|
||||
|
||||
#define store_operand(n) \
|
||||
if(!effective_address_[n].requires_fetch) { \
|
||||
registers[instruction.lreg(n)] = operand_[n]; \
|
||||
} else { \
|
||||
write(instruction.operand_size(), effective_address_[n].value.l, operand_[n]); \
|
||||
}
|
||||
|
||||
if(flags & StoreOp1) { store_operand(0); }
|
||||
if(flags & StoreOp2) { store_operand(1); }
|
||||
|
||||
#undef store_operand
|
||||
|
||||
// If the trace bit was set, trigger the trace exception.
|
||||
if(should_trace) {
|
||||
raise_exception<false>(Exception::Trace);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Flow Control.
|
||||
|
||||
template <Model model, typename BusHandler>
|
||||
template <bool use_current_instruction_pc>
|
||||
void Executor<model, BusHandler>::State::raise_exception(int index) {
|
||||
const uint32_t address = index << 2;
|
||||
|
||||
// Grab the status to store, then switch into supervisor mode
|
||||
// and disable tracing.
|
||||
const uint16_t previous_status = status.status();
|
||||
status.is_supervisor = true;
|
||||
status.trace_flag = 0;
|
||||
did_update_status();
|
||||
|
||||
// Push status and the program counter at instruction start.
|
||||
write<uint32_t>(sp.l - 4, use_current_instruction_pc ? instruction_address : program_counter.l);
|
||||
write<uint16_t>(sp.l - 6, previous_status);
|
||||
sp.l -= 6;
|
||||
|
||||
// Ensure no tracing occurs into the exception.
|
||||
should_trace = 0;
|
||||
|
||||
// Fetch the new program counter.
|
||||
program_counter.l = read<uint32_t>(address);
|
||||
}
|
||||
|
||||
template <Model model, typename BusHandler>
|
||||
void Executor<model, BusHandler>::State::did_update_status() {
|
||||
// Shuffle the stack pointers.
|
||||
stack_pointers[active_stack_pointer] = sp;
|
||||
sp = stack_pointers[int(status.is_supervisor)];
|
||||
active_stack_pointer = int(status.is_supervisor);
|
||||
}
|
||||
|
||||
template <Model model, typename BusHandler>
|
||||
void Executor<model, BusHandler>::State::stop() {
|
||||
stopped = true;
|
||||
|
||||
// Raise an exception to exit the run loop; it doesn't matter
|
||||
// what value is used as long as it is a uint64_t, so 0 will do.
|
||||
throw uint64_t();
|
||||
}
|
||||
|
||||
template <Model model, typename BusHandler>
|
||||
void Executor<model, BusHandler>::State::reset() {
|
||||
bus_handler_.reset();
|
||||
}
|
||||
|
||||
template <Model model, typename BusHandler>
|
||||
void Executor<model, BusHandler>::State::jmp(uint32_t address) {
|
||||
program_counter.l = address;
|
||||
}
|
||||
|
||||
template <Model model, typename BusHandler>
|
||||
template <typename IntT> void Executor<model, BusHandler>::State::complete_bcc(bool branch, IntT offset) {
|
||||
if(branch) {
|
||||
program_counter.l = instruction_address + offset + 2;
|
||||
}
|
||||
}
|
||||
|
||||
template <Model model, typename BusHandler>
|
||||
void Executor<model, BusHandler>::State::complete_dbcc(bool matched_condition, bool overflowed, int16_t offset) {
|
||||
if(!matched_condition && !overflowed) {
|
||||
program_counter.l = instruction_address + offset + 2;
|
||||
}
|
||||
}
|
||||
|
||||
template <Model model, typename BusHandler>
|
||||
void Executor<model, BusHandler>::State::bsr(uint32_t offset) {
|
||||
sp.l -= 4;
|
||||
write<uint32_t>(sp.l, program_counter.l);
|
||||
program_counter.l = instruction_address + offset + 2;
|
||||
}
|
||||
|
||||
template <Model model, typename BusHandler>
|
||||
void Executor<model, BusHandler>::State::jsr(uint32_t address) {
|
||||
sp.l -= 4;
|
||||
write<uint32_t>(sp.l, program_counter.l);
|
||||
program_counter.l = address;
|
||||
}
|
||||
|
||||
template <Model model, typename BusHandler>
|
||||
void Executor<model, BusHandler>::State::link(Preinstruction instruction, uint32_t offset) {
|
||||
const auto reg = 8 + instruction.reg<0>();
|
||||
|
||||
sp.l -= 4;
|
||||
write<uint32_t>(sp.l, Dn(reg).l);
|
||||
Dn(reg) = sp;
|
||||
sp.l += offset;
|
||||
}
|
||||
|
||||
template <Model model, typename BusHandler>
|
||||
void Executor<model, BusHandler>::State::unlink(uint32_t &address) {
|
||||
sp.l = address;
|
||||
address = read<uint32_t>(sp.l);
|
||||
sp.l += 4;
|
||||
}
|
||||
|
||||
template <Model model, typename BusHandler>
|
||||
void Executor<model, BusHandler>::State::pea(uint32_t address) {
|
||||
sp.l -= 4;
|
||||
write<uint32_t>(sp.l, address);
|
||||
}
|
||||
|
||||
template <Model model, typename BusHandler>
|
||||
void Executor<model, BusHandler>::State::rtr() {
|
||||
status.set_ccr(read<uint16_t>(sp.l));
|
||||
sp.l += 2;
|
||||
rts();
|
||||
}
|
||||
|
||||
template <Model model, typename BusHandler>
|
||||
void Executor<model, BusHandler>::State::rte() {
|
||||
status.set_status(read<uint16_t>(sp.l));
|
||||
sp.l += 2;
|
||||
rts();
|
||||
}
|
||||
|
||||
template <Model model, typename BusHandler>
|
||||
void Executor<model, BusHandler>::State::rts() {
|
||||
program_counter.l = read<uint32_t>(sp.l);
|
||||
sp.l += 4;
|
||||
}
|
||||
|
||||
template <Model model, typename BusHandler>
|
||||
void Executor<model, BusHandler>::State::tas(Preinstruction instruction, uint32_t address) {
|
||||
uint8_t value;
|
||||
if(instruction.mode<0>() != AddressingMode::DataRegisterDirect) {
|
||||
value = read<uint8_t>(address);
|
||||
write<uint8_t>(address, value | 0x80);
|
||||
} else {
|
||||
value = uint8_t(address);
|
||||
Dn(instruction.reg<0>()).b = uint8_t(address | 0x80);
|
||||
}
|
||||
|
||||
status.overflow_flag = status.carry_flag = 0;
|
||||
status.zero_result = value;
|
||||
status.negative_flag = value & 0x80;
|
||||
}
|
||||
|
||||
template <Model model, typename BusHandler>
|
||||
void Executor<model, BusHandler>::State::move_to_usp(uint32_t address) {
|
||||
stack_pointers[0].l = address;
|
||||
}
|
||||
|
||||
template <Model model, typename BusHandler>
|
||||
void Executor<model, BusHandler>::State::move_from_usp(uint32_t &address) {
|
||||
address = stack_pointers[0].l;
|
||||
}
|
||||
|
||||
template <Model model, typename BusHandler>
|
||||
template <typename IntT>
|
||||
void Executor<model, BusHandler>::State::movep(Preinstruction instruction, uint32_t source, uint32_t dest) {
|
||||
if(instruction.mode<0>() == AddressingMode::DataRegisterDirect) {
|
||||
// Move register to memory.
|
||||
const uint32_t reg = source;
|
||||
uint32_t address = dest;
|
||||
|
||||
if constexpr (sizeof(IntT) == 4) {
|
||||
write<uint8_t>(address, uint8_t(reg >> 24));
|
||||
address += 2;
|
||||
|
||||
write<uint8_t>(address, uint8_t(reg >> 16));
|
||||
address += 2;
|
||||
}
|
||||
|
||||
write<uint8_t>(address, uint8_t(reg >> 8));
|
||||
address += 2;
|
||||
|
||||
write<uint8_t>(address, uint8_t(reg));
|
||||
} else {
|
||||
// Move memory to register.
|
||||
uint32_t ® = Dn(instruction.reg<1>()).l;
|
||||
uint32_t address = source;
|
||||
|
||||
if constexpr (sizeof(IntT) == 4) {
|
||||
reg = read<uint8_t>(address) << 24;
|
||||
address += 2;
|
||||
|
||||
reg |= read<uint8_t>(address) << 16;
|
||||
address += 2;
|
||||
} else {
|
||||
reg &= 0xffff0000;
|
||||
}
|
||||
|
||||
reg |= read<uint8_t>(address) << 8;
|
||||
address += 2;
|
||||
|
||||
reg |= read<uint8_t>(address);
|
||||
}
|
||||
}
|
||||
|
||||
template <Model model, typename BusHandler>
|
||||
template <typename IntT>
|
||||
void Executor<model, BusHandler>::State::movem_toM(Preinstruction instruction, uint32_t source, uint32_t dest) {
|
||||
// Move registers to memory. This is the only permitted use of the predecrement mode,
|
||||
// which reverses output order.
|
||||
|
||||
if(instruction.mode<1>() == AddressingMode::AddressRegisterIndirectWithPredecrement) {
|
||||
// The structure of the code in the mainline part of the executor is such
|
||||
// that the address register will already have been predecremented before
|
||||
// reaching here, and it'll have been by two bytes per the operand size
|
||||
// rather than according to the instruction size. That's not wanted, so undo it.
|
||||
//
|
||||
// TODO: with the caveat that the 68020+ have different behaviour:
|
||||
//
|
||||
// "For the MC68020, MC68030, MC68040, and CPU32, if the addressing register is also
|
||||
// moved to memory, the value written is the initial register value decremented by the
|
||||
// size of the operation. The MC68000 and MC68010 write the initial register value
|
||||
// (not decremented)."
|
||||
An(instruction.reg<1>()).l += 2;
|
||||
|
||||
uint32_t address = An(instruction.reg<1>()).l;
|
||||
int index = 15;
|
||||
|
||||
while(source) {
|
||||
if(source & 1) {
|
||||
address -= sizeof(IntT);
|
||||
write<IntT>(address, IntT(registers[index].l));
|
||||
}
|
||||
--index;
|
||||
source >>= 1;
|
||||
}
|
||||
|
||||
An(instruction.reg<1>()).l = address;
|
||||
return;
|
||||
}
|
||||
|
||||
int index = 0;
|
||||
while(source) {
|
||||
if(source & 1) {
|
||||
write<IntT>(dest, IntT(registers[index].l));
|
||||
dest += sizeof(IntT);
|
||||
}
|
||||
++index;
|
||||
source >>= 1;
|
||||
}
|
||||
}
|
||||
|
||||
template <Model model, typename BusHandler>
|
||||
template <typename IntT>
|
||||
void Executor<model, BusHandler>::State::movem_toR(Preinstruction instruction, uint32_t source, uint32_t dest) {
|
||||
// Move memory to registers.
|
||||
//
|
||||
// A 68000 convention has been broken here; the instruction form is:
|
||||
// MOVEM <ea>, #
|
||||
// ... but the instruction is encoded as [MOVEM] [#] [ea].
|
||||
//
|
||||
// This project's decoder decodes as #, <ea>.
|
||||
int index = 0;
|
||||
while(source) {
|
||||
if(source & 1) {
|
||||
if constexpr (sizeof(IntT) == 2) {
|
||||
registers[index].l = int16_t(read<uint16_t>(dest));
|
||||
} else {
|
||||
registers[index].l = read<uint32_t>(dest);
|
||||
}
|
||||
dest += sizeof(IntT);
|
||||
}
|
||||
++index;
|
||||
source >>= 1;
|
||||
}
|
||||
|
||||
if(instruction.mode<1>() == AddressingMode::AddressRegisterIndirectWithPostincrement) {
|
||||
// "If the effective address is specified by the postincrement mode ...
|
||||
// [i]f the addressing register is also loaded from memory, the memory value is
|
||||
// ignored and the register is written with the postincremented effective address."
|
||||
|
||||
An(instruction.reg<1>()).l = dest;
|
||||
}
|
||||
}
|
||||
|
||||
#undef sp
|
||||
#undef Dn
|
||||
#undef An
|
||||
#undef AccessException
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* InstructionSets_M68k_ExecutorImplementation_hpp */
|
||||
146
InstructionSets/M68k/Implementation/InstructionOperandFlags.hpp
Normal file
146
InstructionSets/M68k/Implementation/InstructionOperandFlags.hpp
Normal file
@@ -0,0 +1,146 @@
|
||||
//
|
||||
// InstructionOperandFlags.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 09/05/2022.
|
||||
// Copyright © 2022 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef InstructionSets_68k_InstructionOperandFlags_hpp
|
||||
#define InstructionSets_68k_InstructionOperandFlags_hpp
|
||||
|
||||
namespace InstructionSet {
|
||||
namespace M68k {
|
||||
|
||||
template <Model model, Operation t_operation> constexpr uint8_t operand_flags(Operation r_operation) {
|
||||
switch((t_operation != Operation::Undefined) ? t_operation : r_operation) {
|
||||
default:
|
||||
assert(false);
|
||||
|
||||
//
|
||||
// No operands are fetched or stored.
|
||||
// (which means that source and destination will appear as their effective addresses)
|
||||
//
|
||||
case Operation::PEA:
|
||||
case Operation::JMP: case Operation::JSR:
|
||||
case Operation::MOVEPw: case Operation::MOVEPl:
|
||||
case Operation::TAS:
|
||||
case Operation::RTR: case Operation::RTS: case Operation::RTE:
|
||||
return 0;
|
||||
|
||||
//
|
||||
// Single-operand read.
|
||||
//
|
||||
case Operation::MOVEtoSR: case Operation::MOVEtoCCR: case Operation::MOVEtoUSP:
|
||||
case Operation::ORItoSR: case Operation::ORItoCCR:
|
||||
case Operation::ANDItoSR: case Operation::ANDItoCCR:
|
||||
case Operation::EORItoSR: case Operation::EORItoCCR:
|
||||
case Operation::Bccb: case Operation::Bccw: case Operation::Bccl:
|
||||
case Operation::BSRb: case Operation::BSRw: case Operation::BSRl:
|
||||
case Operation::TSTb: case Operation::TSTw: case Operation::TSTl:
|
||||
case Operation::MOVEMtoMw: case Operation::MOVEMtoMl:
|
||||
case Operation::MOVEMtoRw: case Operation::MOVEMtoRl:
|
||||
return FetchOp1;
|
||||
|
||||
//
|
||||
// Single-operand write.
|
||||
//
|
||||
case Operation::MOVEfromUSP:
|
||||
return StoreOp1;
|
||||
|
||||
//
|
||||
// Single-operand read-modify-write.
|
||||
//
|
||||
case Operation::NBCD:
|
||||
case Operation::NOTb: case Operation::NOTw: case Operation::NOTl:
|
||||
case Operation::NEGb: case Operation::NEGw: case Operation::NEGl:
|
||||
case Operation::NEGXb: case Operation::NEGXw: case Operation::NEGXl:
|
||||
case Operation::EXTbtow: case Operation::EXTwtol:
|
||||
case Operation::SWAP:
|
||||
case Operation::UNLINK:
|
||||
case Operation::ASLm: case Operation::ASRm:
|
||||
case Operation::LSLm: case Operation::LSRm:
|
||||
case Operation::ROLm: case Operation::RORm:
|
||||
case Operation::ROXLm: case Operation::ROXRm:
|
||||
case Operation::Scc:
|
||||
return FetchOp1 | StoreOp1;
|
||||
|
||||
//
|
||||
// CLR and MOVE SR, which are model-dependent.
|
||||
//
|
||||
case Operation::MOVEfromSR:
|
||||
case Operation::CLRb: case Operation::CLRw: case Operation::CLRl:
|
||||
if constexpr (model == Model::M68000) {
|
||||
return FetchOp1 | StoreOp1;
|
||||
} else {
|
||||
return StoreOp1;
|
||||
}
|
||||
|
||||
//
|
||||
// Two-operand; read both.
|
||||
//
|
||||
case Operation::CMPb: case Operation::CMPw: case Operation::CMPl:
|
||||
case Operation::CMPAw: case Operation::CMPAl:
|
||||
case Operation::CHK:
|
||||
case Operation::BTST:
|
||||
case Operation::LINKw:
|
||||
return FetchOp1 | FetchOp2;
|
||||
|
||||
//
|
||||
// Two-operand; read source, write dest.
|
||||
//
|
||||
case Operation::MOVEb: case Operation::MOVEw: case Operation::MOVEl:
|
||||
case Operation::MOVEAw: case Operation::MOVEAl:
|
||||
return FetchOp1 | StoreOp2;
|
||||
|
||||
//
|
||||
// Two-operand; read both, write dest.
|
||||
//
|
||||
case Operation::ABCD: case Operation::SBCD:
|
||||
case Operation::ADDb: case Operation::ADDw: case Operation::ADDl:
|
||||
case Operation::ADDAw: case Operation::ADDAl:
|
||||
case Operation::ADDXb: case Operation::ADDXw: case Operation::ADDXl:
|
||||
case Operation::SUBb: case Operation::SUBw: case Operation::SUBl:
|
||||
case Operation::SUBAw: case Operation::SUBAl:
|
||||
case Operation::SUBXb: case Operation::SUBXw: case Operation::SUBXl:
|
||||
case Operation::ORb: case Operation::ORw: case Operation::ORl:
|
||||
case Operation::ANDb: case Operation::ANDw: case Operation::ANDl:
|
||||
case Operation::EORb: case Operation::EORw: case Operation::EORl:
|
||||
case Operation::DIVU: case Operation::DIVS:
|
||||
case Operation::MULU: case Operation::MULS:
|
||||
case Operation::ASLb: case Operation::ASLw: case Operation::ASLl:
|
||||
case Operation::ASRb: case Operation::ASRw: case Operation::ASRl:
|
||||
case Operation::LSLb: case Operation::LSLw: case Operation::LSLl:
|
||||
case Operation::LSRb: case Operation::LSRw: case Operation::LSRl:
|
||||
case Operation::ROLb: case Operation::ROLw: case Operation::ROLl:
|
||||
case Operation::RORb: case Operation::RORw: case Operation::RORl:
|
||||
case Operation::ROXLb: case Operation::ROXLw: case Operation::ROXLl:
|
||||
case Operation::ROXRb: case Operation::ROXRw: case Operation::ROXRl:
|
||||
case Operation::BCHG:
|
||||
case Operation::BCLR: case Operation::BSET:
|
||||
return FetchOp1 | FetchOp2 | StoreOp2;
|
||||
|
||||
//
|
||||
// Two-operand; read both, write source.
|
||||
//
|
||||
case Operation::DBcc:
|
||||
return FetchOp1 | FetchOp2 | StoreOp1;
|
||||
|
||||
//
|
||||
// Two-operand; read both, write both.
|
||||
//
|
||||
case Operation::EXG:
|
||||
return FetchOp1 | FetchOp2 | StoreOp1 | StoreOp2;
|
||||
|
||||
//
|
||||
// Two-operand; just write destination.
|
||||
//
|
||||
case Operation::LEA:
|
||||
return StoreOp2;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* InstructionSets_68k_InstructionOperandFlags_hpp */
|
||||
121
InstructionSets/M68k/Implementation/InstructionOperandSize.hpp
Normal file
121
InstructionSets/M68k/Implementation/InstructionOperandSize.hpp
Normal file
@@ -0,0 +1,121 @@
|
||||
//
|
||||
// InstructionOperandSize.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 09/05/2022.
|
||||
// Copyright © 2022 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef InstructionSets_68k_InstructionOperandSize_hpp
|
||||
#define InstructionSets_68k_InstructionOperandSize_hpp
|
||||
|
||||
namespace InstructionSet {
|
||||
namespace M68k {
|
||||
|
||||
template <Operation t_operation>
|
||||
constexpr DataSize operand_size(Operation r_operation) {
|
||||
switch((t_operation == Operation::Undefined) ? r_operation : t_operation) {
|
||||
// These are given a value arbitrarily, to
|
||||
// complete the switch statement.
|
||||
case Operation::Undefined:
|
||||
case Operation::NOP:
|
||||
case Operation::STOP:
|
||||
case Operation::RESET:
|
||||
case Operation::RTE: case Operation::RTR:
|
||||
case Operation::TRAP:
|
||||
case Operation::TRAPV:
|
||||
|
||||
case Operation::ABCD: case Operation::SBCD:
|
||||
case Operation::NBCD:
|
||||
case Operation::ADDb: case Operation::ADDXb:
|
||||
case Operation::SUBb: case Operation::SUBXb:
|
||||
case Operation::MOVEb:
|
||||
case Operation::ORItoCCR:
|
||||
case Operation::ANDItoCCR:
|
||||
case Operation::EORItoCCR:
|
||||
case Operation::BTST: case Operation::BCLR:
|
||||
case Operation::BCHG: case Operation::BSET:
|
||||
case Operation::CMPb: case Operation::TSTb:
|
||||
case Operation::Bccb: case Operation::BSRb:
|
||||
case Operation::CLRb:
|
||||
case Operation::Scc:
|
||||
case Operation::NEGXb: case Operation::NEGb:
|
||||
case Operation::ASLb: case Operation::ASRb:
|
||||
case Operation::LSLb: case Operation::LSRb:
|
||||
case Operation::ROLb: case Operation::RORb:
|
||||
case Operation::ROXLb: case Operation::ROXRb:
|
||||
case Operation::ANDb: case Operation::EORb:
|
||||
case Operation::NOTb: case Operation::ORb:
|
||||
case Operation::TAS:
|
||||
return DataSize::Byte;
|
||||
|
||||
case Operation::ADDw: case Operation::ADDAw:
|
||||
case Operation::ADDXw: case Operation::SUBw:
|
||||
case Operation::SUBAw: case Operation::SUBXw:
|
||||
case Operation::MOVEw: case Operation::MOVEAw:
|
||||
case Operation::ORItoSR:
|
||||
case Operation::ANDItoSR:
|
||||
case Operation::EORItoSR:
|
||||
case Operation::MOVEtoSR:
|
||||
case Operation::MOVEfromSR:
|
||||
case Operation::MOVEtoCCR:
|
||||
case Operation::CMPw: case Operation::CMPAw:
|
||||
case Operation::TSTw:
|
||||
case Operation::DBcc:
|
||||
case Operation::Bccw: case Operation::BSRw:
|
||||
case Operation::CLRw:
|
||||
case Operation::NEGXw: case Operation::NEGw:
|
||||
case Operation::ASLw: case Operation::ASLm:
|
||||
case Operation::ASRw: case Operation::ASRm:
|
||||
case Operation::LSLw: case Operation::LSLm:
|
||||
case Operation::LSRw: case Operation::LSRm:
|
||||
case Operation::ROLw: case Operation::ROLm:
|
||||
case Operation::RORw: case Operation::RORm:
|
||||
case Operation::ROXLw: case Operation::ROXLm:
|
||||
case Operation::ROXRw: case Operation::ROXRm:
|
||||
case Operation::MOVEMtoRw:
|
||||
case Operation::MOVEMtoRl:
|
||||
case Operation::MOVEMtoMw:
|
||||
case Operation::MOVEMtoMl:
|
||||
case Operation::MOVEPw:
|
||||
case Operation::ANDw: case Operation::EORw:
|
||||
case Operation::NOTw: case Operation::ORw:
|
||||
case Operation::DIVU: case Operation::DIVS:
|
||||
case Operation::MULU: case Operation::MULS:
|
||||
case Operation::EXTbtow:
|
||||
case Operation::LINKw:
|
||||
case Operation::CHK:
|
||||
return DataSize::Word;
|
||||
|
||||
case Operation::ADDl: case Operation::ADDAl:
|
||||
case Operation::ADDXl: case Operation::SUBl:
|
||||
case Operation::SUBAl: case Operation::SUBXl:
|
||||
case Operation::MOVEl: case Operation::MOVEAl:
|
||||
case Operation::LEA: case Operation::PEA:
|
||||
case Operation::EXG: case Operation::SWAP:
|
||||
case Operation::MOVEtoUSP:
|
||||
case Operation::MOVEfromUSP:
|
||||
case Operation::CMPl: case Operation::CMPAl:
|
||||
case Operation::TSTl:
|
||||
case Operation::JMP: case Operation::JSR:
|
||||
case Operation::RTS:
|
||||
case Operation::Bccl: case Operation::BSRl:
|
||||
case Operation::CLRl:
|
||||
case Operation::NEGXl: case Operation::NEGl:
|
||||
case Operation::ASLl: case Operation::ASRl:
|
||||
case Operation::LSLl: case Operation::LSRl:
|
||||
case Operation::ROLl: case Operation::RORl:
|
||||
case Operation::ROXLl: case Operation::ROXRl:
|
||||
case Operation::MOVEPl:
|
||||
case Operation::ANDl: case Operation::EORl:
|
||||
case Operation::NOTl: case Operation::ORl:
|
||||
case Operation::EXTwtol:
|
||||
case Operation::UNLINK:
|
||||
return DataSize::LongWord;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* InstructionSets_68k_InstructionOperandSize_hpp */
|
||||
1167
InstructionSets/M68k/Implementation/PerformImplementation.hpp
Normal file
1167
InstructionSets/M68k/Implementation/PerformImplementation.hpp
Normal file
File diff suppressed because it is too large
Load Diff
295
InstructionSets/M68k/Instruction.cpp
Normal file
295
InstructionSets/M68k/Instruction.cpp
Normal file
@@ -0,0 +1,295 @@
|
||||
//
|
||||
// Instruction.cpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 12/05/2022.
|
||||
// Copyright © 2022 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#include "Instruction.hpp"
|
||||
|
||||
#include <cassert>
|
||||
|
||||
using namespace InstructionSet::M68k;
|
||||
|
||||
std::string Preinstruction::operand_description(int index, int opcode) const {
|
||||
switch(mode(index)) {
|
||||
default: assert(false);
|
||||
|
||||
case AddressingMode::None:
|
||||
return "";
|
||||
|
||||
case AddressingMode::DataRegisterDirect:
|
||||
return std::string("D") + std::to_string(reg(index));
|
||||
|
||||
case AddressingMode::AddressRegisterDirect:
|
||||
return std::string("A") + std::to_string(reg(index));
|
||||
case AddressingMode::AddressRegisterIndirect:
|
||||
return std::string("(A") + std::to_string(reg(index)) + ")";
|
||||
case AddressingMode::AddressRegisterIndirectWithPostincrement:
|
||||
return std::string("(A") + std::to_string(reg(index)) + ")+";
|
||||
case AddressingMode::AddressRegisterIndirectWithPredecrement:
|
||||
return std::string("-(A") + std::to_string(reg(index)) + ")";
|
||||
case AddressingMode::AddressRegisterIndirectWithDisplacement:
|
||||
return std::string("(d16, A") + std::to_string(reg(index)) + ")";
|
||||
case AddressingMode::AddressRegisterIndirectWithIndex8bitDisplacement:
|
||||
return std::string("(d8, A") + std::to_string(reg(index)) + ", Xn)";
|
||||
|
||||
case AddressingMode::ProgramCounterIndirectWithDisplacement:
|
||||
return "(d16, PC)";
|
||||
case AddressingMode::ProgramCounterIndirectWithIndex8bitDisplacement:
|
||||
return "(d8, PC, Xn)";
|
||||
|
||||
case AddressingMode::AbsoluteShort:
|
||||
return "(xxx).w";
|
||||
case AddressingMode::AbsoluteLong:
|
||||
return "(xxx).l";
|
||||
|
||||
case AddressingMode::ImmediateData:
|
||||
return "#";
|
||||
|
||||
case AddressingMode::Quick:
|
||||
if(opcode == -1) {
|
||||
return "Q";
|
||||
}
|
||||
return std::to_string(int(quick(uint16_t(opcode), operation)));
|
||||
}
|
||||
}
|
||||
|
||||
std::string Preinstruction::to_string(int opcode) const {
|
||||
bool flip_operands = false;
|
||||
const char *instruction;
|
||||
|
||||
switch(operation) {
|
||||
case Operation::Undefined: return "None";
|
||||
case Operation::NOP: instruction = "NOP"; break;
|
||||
case Operation::ABCD: instruction = "ABCD"; break;
|
||||
case Operation::SBCD: instruction = "SBCD"; break;
|
||||
case Operation::NBCD: instruction = "NBCD"; break;
|
||||
|
||||
case Operation::ADDb: instruction = "ADD.b"; break;
|
||||
case Operation::ADDw: instruction = "ADD.w"; break;
|
||||
case Operation::ADDl: instruction = "ADD.l"; break;
|
||||
|
||||
case Operation::ADDAw:
|
||||
if(mode<0>() == AddressingMode::Quick) {
|
||||
instruction = "ADD.w";
|
||||
} else {
|
||||
instruction = "ADDA.w";
|
||||
}
|
||||
break;
|
||||
case Operation::ADDAl:
|
||||
if(mode<0>() == AddressingMode::Quick) {
|
||||
instruction = "ADD.l";
|
||||
} else {
|
||||
instruction = "ADDA.l";
|
||||
}
|
||||
break;
|
||||
|
||||
case Operation::ADDXb: instruction = "ADDX.b"; break;
|
||||
case Operation::ADDXw: instruction = "ADDX.w"; break;
|
||||
case Operation::ADDXl: instruction = "ADDX.l"; break;
|
||||
|
||||
case Operation::SUBb: instruction = "SUB.b"; break;
|
||||
case Operation::SUBw: instruction = "SUB.w"; break;
|
||||
case Operation::SUBl: instruction = "SUB.l"; break;
|
||||
|
||||
case Operation::SUBAw:
|
||||
if(mode<0>() == AddressingMode::Quick) {
|
||||
instruction = "SUB.w";
|
||||
} else {
|
||||
instruction = "SUBA.w";
|
||||
}
|
||||
break;
|
||||
case Operation::SUBAl:
|
||||
if(mode<0>() == AddressingMode::Quick) {
|
||||
instruction = "SUB.l";
|
||||
} else {
|
||||
instruction = "SUBA.l";
|
||||
}
|
||||
break;
|
||||
|
||||
case Operation::SUBXb: instruction = "SUBX.b"; break;
|
||||
case Operation::SUBXw: instruction = "SUBX.w"; break;
|
||||
case Operation::SUBXl: instruction = "SUBX.l"; break;
|
||||
|
||||
case Operation::MOVEb: instruction = "MOVE.b"; break;
|
||||
case Operation::MOVEw: instruction = "MOVE.w"; break;
|
||||
case Operation::MOVEl:
|
||||
if(mode<0>() == AddressingMode::Quick) {
|
||||
instruction = "MOVE.q";
|
||||
} else {
|
||||
instruction = "MOVE.l";
|
||||
}
|
||||
break;
|
||||
|
||||
case Operation::MOVEAw: instruction = "MOVEA.w"; break;
|
||||
case Operation::MOVEAl: instruction = "MOVEA.l"; break;
|
||||
|
||||
case Operation::LEA: instruction = "LEA"; break;
|
||||
case Operation::PEA: instruction = "PEA"; break;
|
||||
|
||||
case Operation::MOVEtoSR: instruction = "MOVEtoSR"; break;
|
||||
case Operation::MOVEfromSR: instruction = "MOVEfromSR"; break;
|
||||
case Operation::MOVEtoCCR: instruction = "MOVEtoCCR"; break;
|
||||
case Operation::MOVEtoUSP: instruction = "MOVEtoUSP"; break;
|
||||
case Operation::MOVEfromUSP: instruction = "MOVEfromUSP"; break;
|
||||
|
||||
case Operation::ORItoSR: instruction = "ORItoSR"; break;
|
||||
case Operation::ORItoCCR: instruction = "ORItoCCR"; break;
|
||||
case Operation::ANDItoSR: instruction = "ANDItoSR"; break;
|
||||
case Operation::ANDItoCCR: instruction = "ANDItoCCR"; break;
|
||||
case Operation::EORItoSR: instruction = "EORItoSR"; break;
|
||||
case Operation::EORItoCCR: instruction = "EORItoCCR"; break;
|
||||
|
||||
case Operation::BTST: instruction = "BTST"; break;
|
||||
case Operation::BCLR: instruction = "BCLR"; break;
|
||||
case Operation::BCHG: instruction = "BCHG"; break;
|
||||
case Operation::BSET: instruction = "BSET"; break;
|
||||
|
||||
case Operation::CMPb: instruction = "CMP.b"; break;
|
||||
case Operation::CMPw: instruction = "CMP.w"; break;
|
||||
case Operation::CMPl: instruction = "CMP.l"; break;
|
||||
|
||||
case Operation::CMPAw: instruction = "CMPA.w"; break;
|
||||
case Operation::CMPAl: instruction = "CMPA.l"; break;
|
||||
|
||||
case Operation::TSTb: instruction = "TST.b"; break;
|
||||
case Operation::TSTw: instruction = "TST.w"; break;
|
||||
case Operation::TSTl: instruction = "TST.l"; break;
|
||||
|
||||
case Operation::JMP: instruction = "JMP"; break;
|
||||
case Operation::JSR: instruction = "JSR"; break;
|
||||
case Operation::RTS: instruction = "RTS"; break;
|
||||
case Operation::DBcc: instruction = "DBcc"; break;
|
||||
case Operation::Scc: instruction = "Scc"; break;
|
||||
|
||||
case Operation::Bccb:
|
||||
case Operation::Bccl:
|
||||
case Operation::Bccw: instruction = "Bcc"; break;
|
||||
|
||||
case Operation::BSRb:
|
||||
case Operation::BSRl:
|
||||
case Operation::BSRw: instruction = "BSR"; break;
|
||||
|
||||
case Operation::CLRb: instruction = "CLR.b"; break;
|
||||
case Operation::CLRw: instruction = "CLR.w"; break;
|
||||
case Operation::CLRl: instruction = "CLR.l"; break;
|
||||
|
||||
case Operation::NEGXb: instruction = "NEGX.b"; break;
|
||||
case Operation::NEGXw: instruction = "NEGX.w"; break;
|
||||
case Operation::NEGXl: instruction = "NEGX.l"; break;
|
||||
|
||||
case Operation::NEGb: instruction = "NEG.b"; break;
|
||||
case Operation::NEGw: instruction = "NEG.w"; break;
|
||||
case Operation::NEGl: instruction = "NEG.l"; break;
|
||||
|
||||
case Operation::ASLb: instruction = "ASL.b"; break;
|
||||
case Operation::ASLw: instruction = "ASL.w"; break;
|
||||
case Operation::ASLl: instruction = "ASL.l"; break;
|
||||
case Operation::ASLm: instruction = "ASL.w"; break;
|
||||
|
||||
case Operation::ASRb: instruction = "ASR.b"; break;
|
||||
case Operation::ASRw: instruction = "ASR.w"; break;
|
||||
case Operation::ASRl: instruction = "ASR.l"; break;
|
||||
case Operation::ASRm: instruction = "ASR.w"; break;
|
||||
|
||||
case Operation::LSLb: instruction = "LSL.b"; break;
|
||||
case Operation::LSLw: instruction = "LSL.w"; break;
|
||||
case Operation::LSLl: instruction = "LSL.l"; break;
|
||||
case Operation::LSLm: instruction = "LSL.w"; break;
|
||||
|
||||
case Operation::LSRb: instruction = "LSR.b"; break;
|
||||
case Operation::LSRw: instruction = "LSR.w"; break;
|
||||
case Operation::LSRl: instruction = "LSR.l"; break;
|
||||
case Operation::LSRm: instruction = "LSR.w"; break;
|
||||
|
||||
case Operation::ROLb: instruction = "ROL.b"; break;
|
||||
case Operation::ROLw: instruction = "ROL.w"; break;
|
||||
case Operation::ROLl: instruction = "ROL.l"; break;
|
||||
case Operation::ROLm: instruction = "ROL.w"; break;
|
||||
|
||||
case Operation::RORb: instruction = "ROR.b"; break;
|
||||
case Operation::RORw: instruction = "ROR.w"; break;
|
||||
case Operation::RORl: instruction = "ROR.l"; break;
|
||||
case Operation::RORm: instruction = "ROR.w"; break;
|
||||
|
||||
case Operation::ROXLb: instruction = "ROXL.b"; break;
|
||||
case Operation::ROXLw: instruction = "ROXL.w"; break;
|
||||
case Operation::ROXLl: instruction = "ROXL.l"; break;
|
||||
case Operation::ROXLm: instruction = "ROXL.w"; break;
|
||||
|
||||
case Operation::ROXRb: instruction = "ROXR.b"; break;
|
||||
case Operation::ROXRw: instruction = "ROXR.w"; break;
|
||||
case Operation::ROXRl: instruction = "ROXR.l"; break;
|
||||
case Operation::ROXRm: instruction = "ROXR.w"; break;
|
||||
|
||||
case Operation::MOVEMtoMl: instruction = "MOVEM.l"; break;
|
||||
case Operation::MOVEMtoMw: instruction = "MOVEM.w"; break;
|
||||
case Operation::MOVEMtoRl:
|
||||
instruction = "MOVEM.l";
|
||||
flip_operands = true;
|
||||
break;
|
||||
case Operation::MOVEMtoRw:
|
||||
instruction = "MOVEM.w";
|
||||
flip_operands = true;
|
||||
break;
|
||||
|
||||
case Operation::MOVEPl: instruction = "MOVEP.l"; break;
|
||||
case Operation::MOVEPw: instruction = "MOVEP.w"; break;
|
||||
|
||||
case Operation::ANDb: instruction = "AND.b"; break;
|
||||
case Operation::ANDw: instruction = "AND.w"; break;
|
||||
case Operation::ANDl: instruction = "AND.l"; break;
|
||||
|
||||
case Operation::EORb: instruction = "EOR.b"; break;
|
||||
case Operation::EORw: instruction = "EOR.w"; break;
|
||||
case Operation::EORl: instruction = "EOR.l"; break;
|
||||
|
||||
case Operation::NOTb: instruction = "NOT.b"; break;
|
||||
case Operation::NOTw: instruction = "NOT.w"; break;
|
||||
case Operation::NOTl: instruction = "NOT.l"; break;
|
||||
|
||||
case Operation::ORb: instruction = "OR.b"; break;
|
||||
case Operation::ORw: instruction = "OR.w"; break;
|
||||
case Operation::ORl: instruction = "OR.l"; break;
|
||||
|
||||
case Operation::MULU: instruction = "MULU"; break;
|
||||
case Operation::MULS: instruction = "MULS"; break;
|
||||
case Operation::DIVU: instruction = "DIVU"; break;
|
||||
case Operation::DIVS: instruction = "DIVS"; break;
|
||||
|
||||
case Operation::RTE: instruction = "RTE"; break;
|
||||
case Operation::RTR: instruction = "RTR"; break;
|
||||
|
||||
case Operation::TRAP: instruction = "TRAP"; break;
|
||||
case Operation::TRAPV: instruction = "TRAPV"; break;
|
||||
case Operation::CHK: instruction = "CHK"; break;
|
||||
|
||||
case Operation::EXG: instruction = "EXG"; break;
|
||||
case Operation::SWAP: instruction = "SWAP"; break;
|
||||
|
||||
case Operation::TAS: instruction = "TAS"; break;
|
||||
|
||||
case Operation::EXTbtow: instruction = "EXT.w"; break;
|
||||
case Operation::EXTwtol: instruction = "EXT.l"; break;
|
||||
|
||||
case Operation::LINKw: instruction = "LINK"; break;
|
||||
case Operation::UNLINK: instruction = "UNLINK"; break;
|
||||
|
||||
case Operation::STOP: instruction = "STOP"; break;
|
||||
case Operation::RESET: instruction = "RESET"; break;
|
||||
|
||||
default:
|
||||
assert(false);
|
||||
}
|
||||
|
||||
const std::string operand1 = operand_description(0 ^ int(flip_operands), opcode);
|
||||
const std::string operand2 = operand_description(1 ^ int(flip_operands), opcode);
|
||||
|
||||
std::string result = instruction;
|
||||
if(!operand1.empty()) result += std::string(" ") + operand1;
|
||||
if(!operand2.empty()) result += std::string(", ") + operand2;
|
||||
|
||||
return result;
|
||||
}
|
||||
357
InstructionSets/M68k/Instruction.hpp
Normal file
357
InstructionSets/M68k/Instruction.hpp
Normal file
@@ -0,0 +1,357 @@
|
||||
//
|
||||
// Instruction.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 10/04/2022.
|
||||
// Copyright © 2022 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef InstructionSets_68k_Instruction_hpp
|
||||
#define InstructionSets_68k_Instruction_hpp
|
||||
|
||||
#include "Model.hpp"
|
||||
|
||||
#include <cassert>
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
|
||||
namespace InstructionSet {
|
||||
namespace M68k {
|
||||
|
||||
enum class Operation: uint8_t {
|
||||
Undefined,
|
||||
|
||||
NOP,
|
||||
|
||||
ABCD, SBCD, NBCD,
|
||||
|
||||
ADDb, ADDw, ADDl,
|
||||
ADDAw, ADDAl,
|
||||
ADDXb, ADDXw, ADDXl,
|
||||
|
||||
SUBb, SUBw, SUBl,
|
||||
SUBAw, SUBAl,
|
||||
SUBXb, SUBXw, SUBXl,
|
||||
|
||||
MOVEb, MOVEw, MOVEl,
|
||||
MOVEAw, MOVEAl,
|
||||
LEA, PEA,
|
||||
|
||||
MOVEtoSR, MOVEfromSR,
|
||||
MOVEtoCCR,
|
||||
MOVEtoUSP, MOVEfromUSP,
|
||||
|
||||
ORItoSR, ORItoCCR,
|
||||
ANDItoSR, ANDItoCCR,
|
||||
EORItoSR, EORItoCCR,
|
||||
|
||||
BTST, BCLR,
|
||||
BCHG, BSET,
|
||||
|
||||
CMPb, CMPw, CMPl,
|
||||
CMPAw, CMPAl,
|
||||
TSTb, TSTw, TSTl,
|
||||
|
||||
JMP,
|
||||
JSR, RTS,
|
||||
DBcc,
|
||||
Scc,
|
||||
|
||||
Bccb, Bccw, Bccl,
|
||||
BSRb, BSRw, BSRl,
|
||||
|
||||
CLRb, CLRw, CLRl,
|
||||
NEGXb, NEGXw, NEGXl,
|
||||
NEGb, NEGw, NEGl,
|
||||
|
||||
ASLb, ASLw, ASLl, ASLm,
|
||||
ASRb, ASRw, ASRl, ASRm,
|
||||
LSLb, LSLw, LSLl, LSLm,
|
||||
LSRb, LSRw, LSRl, LSRm,
|
||||
ROLb, ROLw, ROLl, ROLm,
|
||||
RORb, RORw, RORl, RORm,
|
||||
ROXLb, ROXLw, ROXLl, ROXLm,
|
||||
ROXRb, ROXRw, ROXRl, ROXRm,
|
||||
|
||||
MOVEMtoRl, MOVEMtoRw,
|
||||
MOVEMtoMl, MOVEMtoMw,
|
||||
|
||||
MOVEPl, MOVEPw,
|
||||
|
||||
ANDb, ANDw, ANDl,
|
||||
EORb, EORw, EORl,
|
||||
NOTb, NOTw, NOTl,
|
||||
ORb, ORw, ORl,
|
||||
|
||||
MULU, MULS,
|
||||
DIVU, DIVS,
|
||||
|
||||
RTE, RTR,
|
||||
|
||||
TRAP, TRAPV,
|
||||
CHK,
|
||||
|
||||
EXG, SWAP,
|
||||
|
||||
TAS,
|
||||
|
||||
EXTbtow, EXTwtol,
|
||||
|
||||
LINKw, UNLINK,
|
||||
|
||||
STOP, RESET,
|
||||
|
||||
Max = RESET
|
||||
};
|
||||
|
||||
template <Model model>
|
||||
constexpr bool requires_supervisor(Operation op) {
|
||||
switch(op) {
|
||||
case Operation::MOVEfromSR:
|
||||
if constexpr (model == Model::M68000) {
|
||||
return false;
|
||||
}
|
||||
[[fallthrough]];
|
||||
case Operation::ORItoSR: case Operation::ANDItoSR:
|
||||
case Operation::EORItoSR: case Operation::RTE:
|
||||
case Operation::RESET: case Operation::STOP:
|
||||
case Operation::MOVEtoUSP: case Operation::MOVEfromUSP:
|
||||
case Operation::MOVEtoSR:
|
||||
return true;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
enum class DataSize {
|
||||
Byte = 0,
|
||||
Word = 1,
|
||||
LongWord = 2,
|
||||
};
|
||||
|
||||
/// Classifies operations by the size of their memory accesses, if any.
|
||||
///
|
||||
/// For any operations that don't fit the neat model of reading one or two operands,
|
||||
/// then writing zero or one, the size determines the data size of the operands only,
|
||||
/// not any other accesses.
|
||||
template <Operation t_operation = Operation::Undefined>
|
||||
constexpr DataSize operand_size(Operation operation = Operation::Undefined);
|
||||
|
||||
template <Operation t_op = Operation::Undefined>
|
||||
constexpr uint32_t quick(uint16_t instruction, Operation r_op = Operation::Undefined) {
|
||||
switch((t_op != Operation::Undefined) ? t_op : r_op) {
|
||||
case Operation::Bccb:
|
||||
case Operation::BSRb:
|
||||
case Operation::MOVEl: return uint32_t(int8_t(instruction));
|
||||
case Operation::TRAP: return uint32_t(instruction & 15);
|
||||
default: {
|
||||
uint32_t value = (instruction >> 9) & 7;
|
||||
value |= (value - 1)&8;
|
||||
return value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static constexpr uint8_t FetchOp1 = (1 << 0);
|
||||
static constexpr uint8_t FetchOp2 = (1 << 1);
|
||||
static constexpr uint8_t StoreOp1 = (1 << 2);
|
||||
static constexpr uint8_t StoreOp2 = (1 << 3);
|
||||
|
||||
/*!
|
||||
Provides a bitfield with a value in the range 0–15 indicating which of the provided operation's
|
||||
operands are accessed via standard fetch and store cycles; the bitfield is composted of
|
||||
[Fetch/Store]Op[1/2] as defined above.
|
||||
|
||||
Unusual bus sequences, such as TAS or MOVEM, are not described here.
|
||||
*/
|
||||
template <Model model, Operation t_operation = Operation::Undefined>
|
||||
constexpr uint8_t operand_flags(Operation r_operation = Operation::Undefined);
|
||||
|
||||
/// Lists the various condition codes used by the 680x0.
|
||||
enum class Condition {
|
||||
True = 0x00, False = 0x01,
|
||||
High = 0x02, LowOrSame = 0x03,
|
||||
CarryClear = 0x04, CarrySet = 0x05,
|
||||
NotEqual = 0x06, Equal = 0x07,
|
||||
OverflowClear = 0x08, OverflowSet = 0x09,
|
||||
Positive = 0x0a, Negative = 0x0b,
|
||||
GreaterThanOrEqual = 0x0c, LessThan = 0x0d,
|
||||
GreaterThan = 0x0e, LessThanOrEqual = 0x0f,
|
||||
};
|
||||
|
||||
/// Indicates the addressing mode applicable to an operand.
|
||||
///
|
||||
/// Implementation notes:
|
||||
///
|
||||
/// Those entries starting 0b00 or 0b01 are mapped as per the 68000's native encoding;
|
||||
/// those starting 0b00 are those which are indicated directly by a mode field and those starting
|
||||
/// 0b01 are those which are indicated by a register field given a mode of 0b111. The only minor
|
||||
/// exception is AddressRegisterDirect, which exists on a 68000 but isn't specifiable by a
|
||||
/// mode and register, it's contextual based on the instruction.
|
||||
///
|
||||
/// Those modes starting in 0b10 are the various extended addressing modes introduced as
|
||||
/// of the 68020, which can be detected only after interpreting an extension word. At the
|
||||
/// Preinstruction stage:
|
||||
///
|
||||
/// * AddressRegisterIndirectWithIndexBaseDisplacement, MemoryIndirectPostindexed
|
||||
/// and MemoryIndirectPreindexed will have been partially decoded as
|
||||
/// AddressRegisterIndirectWithIndex8bitDisplacement; and
|
||||
/// * ProgramCounterIndirectWithIndexBaseDisplacement,
|
||||
/// ProgramCounterMemoryIndirectPostindexed and
|
||||
/// ProgramCounterMemoryIndirectPreindexed will have been partially decoded
|
||||
/// as ProgramCounterIndirectWithIndex8bitDisplacement.
|
||||
enum class AddressingMode: uint8_t {
|
||||
/// No adddressing mode; this operand doesn't exist.
|
||||
None = 0b01'101,
|
||||
|
||||
/// Dn
|
||||
DataRegisterDirect = 0b00'000,
|
||||
|
||||
/// An
|
||||
AddressRegisterDirect = 0b00'001,
|
||||
/// (An)
|
||||
AddressRegisterIndirect = 0b00'010,
|
||||
/// (An)+
|
||||
AddressRegisterIndirectWithPostincrement = 0b00'011,
|
||||
/// -(An)
|
||||
AddressRegisterIndirectWithPredecrement = 0b00'100,
|
||||
/// (d16, An)
|
||||
AddressRegisterIndirectWithDisplacement = 0b00'101,
|
||||
/// (d8, An, Xn)
|
||||
AddressRegisterIndirectWithIndex8bitDisplacement = 0b00'110,
|
||||
/// (bd, An, Xn) [68020+]
|
||||
AddressRegisterIndirectWithIndexBaseDisplacement = 0b10'000,
|
||||
|
||||
/// ([bd, An, Xn], od) [68020+]
|
||||
MemoryIndirectPostindexed = 0b10'001,
|
||||
/// ([bd, An], Xn, od) [68020+]
|
||||
MemoryIndirectPreindexed = 0b10'010,
|
||||
|
||||
/// (d16, PC)
|
||||
ProgramCounterIndirectWithDisplacement = 0b01'010,
|
||||
/// (d8, PC, Xn)
|
||||
ProgramCounterIndirectWithIndex8bitDisplacement = 0b01'011,
|
||||
/// (bd, PC, Xn) [68020+]
|
||||
ProgramCounterIndirectWithIndexBaseDisplacement = 0b10'011,
|
||||
/// ([bd, PC, Xn], od) [68020+]
|
||||
ProgramCounterMemoryIndirectPostindexed = 0b10'100,
|
||||
/// ([bc, PC], Xn, od) [68020+]
|
||||
ProgramCounterMemoryIndirectPreindexed = 0b10'101,
|
||||
|
||||
/// (xxx).W
|
||||
AbsoluteShort = 0b01'000,
|
||||
/// (xxx).L
|
||||
AbsoluteLong = 0b01'001,
|
||||
|
||||
/// #
|
||||
ImmediateData = 0b01'100,
|
||||
|
||||
/// .q; value is embedded in the opcode.
|
||||
Quick = 0b01'110,
|
||||
};
|
||||
/// Guaranteed to be 1+[largest value used by AddressingMode].
|
||||
static constexpr int AddressingModeCount = 0b10'110;
|
||||
|
||||
/*!
|
||||
A preinstruction is as much of an instruction as can be decoded with
|
||||
only the first instruction word — i.e. an operation, and:
|
||||
|
||||
* on the 68000 and 68010, the complete addressing modes;
|
||||
* on subsequent, a decent proportion of the addressing mode. See
|
||||
the notes on @c AddressingMode for potential aliasing.
|
||||
*/
|
||||
class Preinstruction {
|
||||
public:
|
||||
Operation operation = Operation::Undefined;
|
||||
|
||||
// Instructions come with 0, 1 or 2 operands;
|
||||
// the getters below act to provide a list of operands
|
||||
// that is terminated by an AddressingMode::None.
|
||||
//
|
||||
// For two-operand instructions, argument 0 is a source
|
||||
// and argument 1 is a destination.
|
||||
//
|
||||
// For one-operand instructions, only argument 0 will
|
||||
// be provided, and will be a source and/or destination as
|
||||
// per the semantics of the operation.
|
||||
//
|
||||
// The versions templated on index do a range check;
|
||||
// if using the runtime versions then results for indices
|
||||
// other than 0 and 1 are undefined.
|
||||
|
||||
AddressingMode mode(int index) const {
|
||||
return AddressingMode(operands_[index] >> 3);
|
||||
}
|
||||
template <int index> AddressingMode mode() const {
|
||||
if constexpr (index > 1) {
|
||||
return AddressingMode::None;
|
||||
}
|
||||
return mode(index);
|
||||
}
|
||||
int reg(int index) const {
|
||||
return operands_[index] & 7;
|
||||
}
|
||||
template <int index> int reg() const {
|
||||
if constexpr (index > 1) {
|
||||
return 0;
|
||||
}
|
||||
return reg(index);
|
||||
}
|
||||
|
||||
/// @returns 0–7 to indicate data registers 0 to 7, or 8–15 to indicate address registers 0 to 7 respectively.
|
||||
/// Provides undefined results if the addressing mode is not either @c DataRegisterDirect or
|
||||
/// @c AddressRegisterDirect.
|
||||
int lreg(int index) const {
|
||||
return operands_[index] & 0xf;
|
||||
}
|
||||
|
||||
bool requires_supervisor() const {
|
||||
return flags_ & 0x80;
|
||||
}
|
||||
DataSize operand_size() const {
|
||||
return DataSize(flags_ & 0x03);
|
||||
}
|
||||
Condition condition() const {
|
||||
return Condition((flags_ >> 2) & 0x0f);
|
||||
}
|
||||
|
||||
private:
|
||||
uint8_t operands_[2] = { uint8_t(AddressingMode::None), uint8_t(AddressingMode::None)};
|
||||
uint8_t flags_ = 0;
|
||||
|
||||
std::string operand_description(int index, int opcode) const;
|
||||
|
||||
public:
|
||||
Preinstruction(
|
||||
Operation operation,
|
||||
AddressingMode op1_mode, int op1_reg,
|
||||
AddressingMode op2_mode, int op2_reg,
|
||||
bool is_supervisor,
|
||||
DataSize size,
|
||||
Condition condition) : operation(operation)
|
||||
{
|
||||
operands_[0] = uint8_t((uint8_t(op1_mode) << 3) | op1_reg);
|
||||
operands_[1] = uint8_t((uint8_t(op2_mode) << 3) | op2_reg);
|
||||
flags_ = uint8_t(
|
||||
(is_supervisor ? 0x80 : 0x00) |
|
||||
(int(condition) << 2) |
|
||||
int(size)
|
||||
);
|
||||
}
|
||||
|
||||
Preinstruction() {}
|
||||
|
||||
/// Produces a string description of this instruction; if @c opcode
|
||||
/// is supplied then any quick fields in this instruction will be decoded;
|
||||
/// otherwise they'll be printed as just 'Q'.
|
||||
std::string to_string(int opcode = -1) const;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#include "Implementation/InstructionOperandSize.hpp"
|
||||
#include "Implementation/InstructionOperandFlags.hpp"
|
||||
|
||||
#endif /* InstructionSets_68k_Instruction_hpp */
|
||||
26
InstructionSets/M68k/Model.hpp
Normal file
26
InstructionSets/M68k/Model.hpp
Normal file
@@ -0,0 +1,26 @@
|
||||
//
|
||||
// Model.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 15/04/2022.
|
||||
// Copyright © 2022 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef InstructionSets_M68k_Model_hpp
|
||||
#define InstructionSets_M68k_Model_hpp
|
||||
|
||||
namespace InstructionSet {
|
||||
namespace M68k {
|
||||
|
||||
enum class Model {
|
||||
M68000,
|
||||
M68010,
|
||||
M68020,
|
||||
M68030,
|
||||
M68040,
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* InstructionSets_M68k_Model_hpp */
|
||||
175
InstructionSets/M68k/Perform.hpp
Normal file
175
InstructionSets/M68k/Perform.hpp
Normal file
@@ -0,0 +1,175 @@
|
||||
//
|
||||
// Perform.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 28/04/2022.
|
||||
// Copyright © 2022 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef InstructionSets_M68k_Perform_h
|
||||
#define InstructionSets_M68k_Perform_h
|
||||
|
||||
#include "Model.hpp"
|
||||
#include "Instruction.hpp"
|
||||
#include "Status.hpp"
|
||||
#include "../../Numeric/RegisterSizes.hpp"
|
||||
|
||||
namespace InstructionSet {
|
||||
namespace M68k {
|
||||
|
||||
struct NullFlowController {
|
||||
//
|
||||
// Various operation-specific did-perform notfications; these all relate to operations
|
||||
// with variable timing on a 68000, providing the fields that contribute to that timing.
|
||||
//
|
||||
|
||||
/// Indicates that a @c MULU was performed, providing the @c source operand.
|
||||
template <typename IntT> void did_mulu(IntT) {}
|
||||
|
||||
/// Indicates that a @c MULS was performed, providing the @c source operand.
|
||||
template <typename IntT> void did_muls(IntT) {}
|
||||
|
||||
/// Indicates that a @c CHK was performed, along with whether the result @c was_under zero or @c was_over the source operand.
|
||||
void did_chk([[maybe_unused]] bool was_under, [[maybe_unused]] bool was_over) {}
|
||||
|
||||
/// Indicates an in-register shift or roll occurred, providing the number of bits shifted by
|
||||
/// and the type shifted.
|
||||
///
|
||||
/// @c IntT may be uint8_t, uint16_t or uint32_t.
|
||||
template <typename IntT> void did_shift([[maybe_unused]] int bit_count) {}
|
||||
|
||||
/// Indicates that a @c DIVU was performed, providing the @c dividend and @c divisor.
|
||||
/// If @c did_overflow is @c true then the divide ended in overflow.
|
||||
template <bool did_overflow> void did_divu([[maybe_unused]] uint32_t dividend, [[maybe_unused]] uint32_t divisor) {}
|
||||
|
||||
/// Indicates that a @c DIVS was performed, providing the @c dividend and @c divisor.
|
||||
/// If @c did_overflow is @c true then the divide ended in overflow.
|
||||
template <bool did_overflow> void did_divs([[maybe_unused]] int32_t dividend, [[maybe_unused]] int32_t divisor) {}
|
||||
|
||||
/// Indicates that a bit-manipulation operation (i.e. BTST, BCHG or BSET) was performed, affecting the bit at posiition @c bit_position.
|
||||
void did_bit_op([[maybe_unused]] int bit_position) {}
|
||||
|
||||
/// Indicates that an @c Scc was performed; if @c did_set_ff is true then the condition was true and FF
|
||||
/// written to the operand; otherwise 00 was written.
|
||||
void did_scc([[maybe_unused]] bool did_set_ff) {}
|
||||
|
||||
/// Provides a notification that the upper byte of the status register has been affected by the current instruction;
|
||||
/// this gives an opportunity to track the supervisor flag.
|
||||
void did_update_status() {}
|
||||
|
||||
//
|
||||
// Operations that don't fit the reductive load-modify-store pattern; these are requests from perform
|
||||
// that the flow controller do something (and, correspondingly, do not have empty implementations).
|
||||
//
|
||||
// All offsets are the native values as encoded in the corresponding operations.
|
||||
//
|
||||
|
||||
/// If @c matched_condition is @c true, apply the @c offset to the PC.
|
||||
template <typename IntT> void complete_bcc(bool matched_condition, IntT offset);
|
||||
|
||||
/// If both @c matched_condition and @c overflowed are @c false, apply @c offset to the PC.
|
||||
void complete_dbcc(bool matched_condition, bool overflowed, int16_t offset);
|
||||
|
||||
/// Push the program counter of the next instruction to the stack, and add @c offset to the PC.
|
||||
void bsr(uint32_t offset);
|
||||
|
||||
/// Push the program counter of the next instruction to the stack, and load @c offset to the PC.
|
||||
void jsr(uint32_t address);
|
||||
|
||||
/// Set the program counter to @c address.
|
||||
void jmp(uint32_t address);
|
||||
|
||||
/// Pop a word from the stack and use that to set the status condition codes. Then pop a new value for the PC.
|
||||
void rtr();
|
||||
|
||||
/// Pop a word from the stack and use that to set the entire status register. Then pop a new value for the PC.
|
||||
void rte();
|
||||
|
||||
/// Pop a new value for the PC from the stack.
|
||||
void rts();
|
||||
|
||||
/// Put the processor into the stopped state, waiting for interrupts.
|
||||
void stop();
|
||||
|
||||
/// Assert the reset output.
|
||||
void reset();
|
||||
|
||||
/// Perform LINK using the address register identified by @c instruction and the specified @c offset.
|
||||
void link(Preinstruction instruction, uint32_t offset);
|
||||
|
||||
/// Perform unlink, with @c address being the target address register.
|
||||
void unlink(uint32_t &address);
|
||||
|
||||
/// Push @c address to the stack.
|
||||
void pea(uint32_t address);
|
||||
|
||||
/// Replace the current user stack pointer with @c address.
|
||||
/// The processor is guranteed to be in supervisor mode.
|
||||
void move_to_usp(uint32_t address);
|
||||
|
||||
/// Put the value of the user stack pointer into @c address.
|
||||
/// The processor is guranteed to be in supervisor mode.
|
||||
void move_from_usp(uint32_t &address);
|
||||
|
||||
/// Perform an atomic TAS cycle; if @c instruction indicates that this is a TAS Dn then
|
||||
/// perform the TAS directly upon that register; otherwise perform it on the memory at
|
||||
/// @c address. If this is a TAS Dn then @c address will contain the initial value of
|
||||
/// the register.
|
||||
void tas(Preinstruction instruction, uint32_t address);
|
||||
|
||||
/// Use @c instruction to determine the direction of this MOVEP and perform it;
|
||||
/// @c source is the first operand provided to the MOVEP — either an address or register
|
||||
/// contents — and @c dest is the second.
|
||||
///
|
||||
/// @c IntT may be either uint16_t or uint32_t.
|
||||
template <typename IntT> void movep(Preinstruction instruction, uint32_t source, uint32_t dest);
|
||||
|
||||
/// Perform a MOVEM to memory, from registers. @c instruction will indicate the mask as the first operand,
|
||||
/// and the target address and addressing mode as the second; the mask and address are also supplied
|
||||
/// as @c mask and @c address. If the addressing mode is -(An) then the address register will have
|
||||
/// been decremented already.
|
||||
///
|
||||
/// The receiver is responsible for updating the address register if applicable.
|
||||
///
|
||||
/// @c IntT may be either uint16_t or uint32_t.
|
||||
template <typename IntT> void movem_toM(Preinstruction instruction, uint32_t mask, uint32_t address);
|
||||
|
||||
/// Perform a MOVEM to registers, from memory. @c instruction will indicate the mask as the first operand,
|
||||
/// and the target address and addressing mode as the second; the mask and address are also supplied
|
||||
/// as @c mask and @c address. If the addressing mode is (An)+ then the address register will have been
|
||||
/// incremented, but @c address will be its value before that occurred.
|
||||
///
|
||||
/// The receiver is responsible for updating the address register if applicable.
|
||||
///
|
||||
/// @c IntT may be either uint16_t or uint32_t.
|
||||
template <typename IntT> void movem_toR(Preinstruction instruction, uint32_t mask, uint32_t address);
|
||||
|
||||
/// Raises a short-form exception using @c vector. If @c use_current_instruction_pc is @c true,
|
||||
/// the program counter for the current instruction is included in the resulting stack frame. Otherwise the program
|
||||
/// counter for the next instruction is used.
|
||||
template <bool use_current_instruction_pc = true>
|
||||
void raise_exception([[maybe_unused]] int vector);
|
||||
};
|
||||
|
||||
/// Performs @c instruction using @c source and @c dest (one or both of which may be ignored as per
|
||||
/// the semantics of the operation).
|
||||
///
|
||||
/// Any change in processor status will be applied to @c status. If this operation does not fit the reductive model
|
||||
/// of being a read and possibly a modify and possibly a write of up to two operands then the @c flow_controller
|
||||
/// will be asked to fill in the gaps.
|
||||
///
|
||||
/// If the template parameter @c operation is not @c Operation::Undefined then that operation will be performed, ignoring
|
||||
/// whatever is specifed in @c instruction. This allows selection either at compile time or at run time; per Godbolt all modern
|
||||
/// compilers seem to be smart enough fully to optimise the compile-time case.
|
||||
template <
|
||||
Model model,
|
||||
typename FlowController,
|
||||
Operation operation = Operation::Undefined
|
||||
> void perform(Preinstruction instruction, CPU::RegisterPair32 &source, CPU::RegisterPair32 &dest, Status &status, FlowController &flow_controller);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#include "Implementation/PerformImplementation.hpp"
|
||||
|
||||
#endif /* InstructionSets_M68k_Perform_h */
|
||||
31
InstructionSets/M68k/RegisterSet.hpp
Normal file
31
InstructionSets/M68k/RegisterSet.hpp
Normal file
@@ -0,0 +1,31 @@
|
||||
//
|
||||
// RegisterSet.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 16/05/2022.
|
||||
// Copyright © 2022 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef InstructionSets_M68k_RegisterSet_h
|
||||
#define InstructionSets_M68k_RegisterSet_h
|
||||
|
||||
namespace InstructionSet {
|
||||
namespace M68k {
|
||||
|
||||
struct RegisterSet {
|
||||
uint32_t data[8], address[7];
|
||||
uint32_t user_stack_pointer;
|
||||
uint32_t supervisor_stack_pointer;
|
||||
uint16_t status;
|
||||
uint32_t program_counter;
|
||||
|
||||
/// @returns The active stack pointer, whichever it may be.
|
||||
uint32_t stack_pointer() const {
|
||||
return (status & 0x2000) ? supervisor_stack_pointer : user_stack_pointer;
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* InstructionSets_M68k_RegisterSet_h */
|
||||
153
InstructionSets/M68k/Status.hpp
Normal file
153
InstructionSets/M68k/Status.hpp
Normal file
@@ -0,0 +1,153 @@
|
||||
//
|
||||
// Status.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 28/04/2022.
|
||||
// Copyright © 2022 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef InstructionSets_M68k_Status_h
|
||||
#define InstructionSets_M68k_Status_h
|
||||
|
||||
#include "Instruction.hpp"
|
||||
|
||||
namespace InstructionSet {
|
||||
namespace M68k {
|
||||
|
||||
namespace ConditionCode {
|
||||
|
||||
static constexpr uint16_t Carry = 1 << 0;
|
||||
static constexpr uint16_t Overflow = 1 << 1;
|
||||
static constexpr uint16_t Zero = 1 << 2;
|
||||
static constexpr uint16_t Negative = 1 << 3;
|
||||
static constexpr uint16_t Extend = 1 << 4;
|
||||
|
||||
static constexpr uint16_t AllConditions = Carry | Overflow | Zero | Negative | Extend;
|
||||
|
||||
static constexpr uint16_t Supervisor = 1 << 13;
|
||||
static constexpr uint16_t Trace = 1 << 15;
|
||||
|
||||
static constexpr uint16_t InterruptPriorityMask = 0b111 << 8;
|
||||
|
||||
}
|
||||
|
||||
|
||||
struct Status {
|
||||
/// Generally holds an unevaluated flag for potential later lazy evaluation; it'll be zero for one outcome,
|
||||
/// non-zero for the other, but no guarantees are made about the potential range of non-zero values.
|
||||
using FlagT = uint_fast32_t;
|
||||
|
||||
/* b15 */
|
||||
FlagT trace_flag = 0; // The trace flag is set if and only if this value is non-zero.
|
||||
|
||||
/* b13 */
|
||||
bool is_supervisor = false; // true => processor is in supervisor mode; false => it isn't.
|
||||
|
||||
/* b7–b9 */
|
||||
int interrupt_level = 0; // The direct integer value of the current interrupt level.
|
||||
// Values of 8 or greater have undefined meaning.
|
||||
|
||||
/* b0–b4 */
|
||||
FlagT zero_result = 0; // The zero flag is set if and only if this value is zero.
|
||||
FlagT carry_flag = 0; // The carry flag is set if and only if this value is non-zero.
|
||||
FlagT extend_flag = 0; // The extend flag is set if and only if this value is non-zero.
|
||||
FlagT overflow_flag = 0; // The overflow flag is set if and only if this value is non-zero.
|
||||
FlagT negative_flag = 0; // The negative flag is set if and only this value is non-zero.
|
||||
|
||||
/// Gets the current condition codes.
|
||||
constexpr uint16_t ccr() const {
|
||||
return
|
||||
(carry_flag ? ConditionCode::Carry : 0) |
|
||||
(overflow_flag ? ConditionCode::Overflow : 0) |
|
||||
(!zero_result ? ConditionCode::Zero : 0) |
|
||||
(negative_flag ? ConditionCode::Negative : 0) |
|
||||
(extend_flag ? ConditionCode::Extend : 0);
|
||||
}
|
||||
|
||||
/// Sets the current condition codes.
|
||||
constexpr void set_ccr(uint16_t ccr) {
|
||||
carry_flag = ccr & ConditionCode::Carry;
|
||||
overflow_flag = ccr & ConditionCode::Overflow;
|
||||
zero_result = ~ccr & ConditionCode::Zero;
|
||||
negative_flag = ccr & ConditionCode::Negative;
|
||||
extend_flag = ccr & ConditionCode::Extend;
|
||||
}
|
||||
|
||||
/// Gets the current value of the status register.
|
||||
constexpr uint16_t status() const {
|
||||
return uint16_t(
|
||||
ccr() |
|
||||
(interrupt_level << 8) |
|
||||
(trace_flag ? ConditionCode::Trace : 0) |
|
||||
(is_supervisor ? ConditionCode::Supervisor : 0)
|
||||
);
|
||||
}
|
||||
|
||||
/// Sets the current value of the status register;
|
||||
/// @returns @c true if the processor finishes in supervisor mode; @c false otherwise.
|
||||
constexpr bool set_status(uint16_t status) {
|
||||
set_ccr(status);
|
||||
|
||||
interrupt_level = (status >> 8) & 7;
|
||||
trace_flag = status & ConditionCode::Trace;
|
||||
is_supervisor = status & ConditionCode::Supervisor;
|
||||
|
||||
return is_supervisor;
|
||||
}
|
||||
|
||||
/// Adjusts the status for exception processing — sets supervisor mode, disables trace,
|
||||
/// and if @c new_interrupt_level is greater than or equal to 0 sets that as the new
|
||||
/// interrupt level.
|
||||
///
|
||||
/// @returns The status prior to those changes.
|
||||
uint16_t begin_exception(int new_interrupt_level = -1) {
|
||||
const uint16_t initial_status = status();
|
||||
|
||||
if(new_interrupt_level >= 0) {
|
||||
interrupt_level = new_interrupt_level;
|
||||
}
|
||||
is_supervisor = true;
|
||||
trace_flag = 0;
|
||||
|
||||
return initial_status;
|
||||
}
|
||||
|
||||
/// Evaluates @c condition.
|
||||
constexpr bool evaluate_condition(Condition condition) const {
|
||||
switch(condition) {
|
||||
default:
|
||||
case Condition::True: return true;
|
||||
case Condition::False: return false;
|
||||
case Condition::High: return zero_result && !carry_flag;
|
||||
case Condition::LowOrSame: return !zero_result || carry_flag;
|
||||
case Condition::CarryClear: return !carry_flag;
|
||||
case Condition::CarrySet: return carry_flag;
|
||||
case Condition::NotEqual: return zero_result;
|
||||
case Condition::Equal: return !zero_result;
|
||||
case Condition::OverflowClear: return !overflow_flag;
|
||||
case Condition::OverflowSet: return overflow_flag;
|
||||
case Condition::Positive: return !negative_flag;
|
||||
case Condition::Negative: return negative_flag;
|
||||
case Condition::GreaterThanOrEqual:
|
||||
return (negative_flag && overflow_flag) || (!negative_flag && !overflow_flag);
|
||||
case Condition::LessThan:
|
||||
return (negative_flag && !overflow_flag) || (!negative_flag && overflow_flag);
|
||||
case Condition::GreaterThan:
|
||||
return zero_result && ((negative_flag && overflow_flag) || (!negative_flag && !overflow_flag));
|
||||
case Condition::LessThanOrEqual:
|
||||
return !zero_result || (negative_flag && !overflow_flag) || (!negative_flag && overflow_flag);
|
||||
}
|
||||
}
|
||||
|
||||
/// @returns @c true if an interrupt at level @c level should be accepted; @c false otherwise.
|
||||
constexpr bool would_accept_interrupt(int level) const {
|
||||
// TODO: is level seven really non-maskable? If so then what mechanism prevents
|
||||
// rapid stack overflow upon a level-seven interrupt?
|
||||
return level > interrupt_level;
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* InstructionSets_M68k_Status_h */
|
||||
604
InstructionSets/PowerPC/Decoder.cpp
Normal file
604
InstructionSets/PowerPC/Decoder.cpp
Normal file
@@ -0,0 +1,604 @@
|
||||
//
|
||||
// Decoder.cpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 30/12/20.
|
||||
// Copyright © 2020 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#include "Decoder.hpp"
|
||||
|
||||
using namespace InstructionSet::PowerPC;
|
||||
|
||||
namespace {
|
||||
|
||||
template <Model model, bool validate_reserved_bits, Operation operation> Instruction instruction(uint32_t opcode, bool is_supervisor = false) {
|
||||
// If validation isn't required, there's nothing to do here.
|
||||
if constexpr (!validate_reserved_bits) {
|
||||
return Instruction(operation, opcode, is_supervisor);
|
||||
}
|
||||
|
||||
// Otherwise, validation depends on operation
|
||||
// (and, in principle, processor model).
|
||||
switch(operation) {
|
||||
case Operation::absx: case Operation::clcs:
|
||||
case Operation::nabsx:
|
||||
case Operation::addmex: case Operation::addzex:
|
||||
case Operation::bcctrx: case Operation::bclrx:
|
||||
case Operation::cntlzdx: case Operation::cntlzwx:
|
||||
case Operation::extsbx: case Operation::extshx: case Operation::extswx:
|
||||
case Operation::fmulx: case Operation::fmulsx:
|
||||
case Operation::negx:
|
||||
case Operation::subfmex: case Operation::subfzex:
|
||||
if(opcode & 0b000000'00000'00000'11111'0000000000'0) return Instruction(opcode);
|
||||
break;
|
||||
|
||||
case Operation::cmp: case Operation::cmpl:
|
||||
if(opcode & 0b000000'00010'00000'00000'0000000000'1) return Instruction(opcode);
|
||||
break;
|
||||
|
||||
case Operation::cmpi: case Operation::cmpli:
|
||||
if(opcode & 0b000000'00010'00000'00000'0000000000'0) return Instruction(opcode);
|
||||
break;
|
||||
|
||||
case Operation::dcbf: case Operation::dcbi: case Operation::dcbst:
|
||||
case Operation::dcbt: case Operation::dcbtst: case Operation::dcbz:
|
||||
if(opcode & 0b000000'11111'00000'00000'0000000000'0) return Instruction(opcode);
|
||||
break;
|
||||
|
||||
case Operation::crand: case Operation::crandc: case Operation::creqv:
|
||||
case Operation::crnand: case Operation::crnor: case Operation::cror:
|
||||
case Operation::crorc: case Operation::crxor:
|
||||
case Operation::eciwx: case Operation::ecowx:
|
||||
case Operation::lbzux: case Operation::lbzx:
|
||||
case Operation::ldarx:
|
||||
case Operation::ldux: case Operation::ldx:
|
||||
case Operation::lfdux: case Operation::lfdx:
|
||||
case Operation::lfsux: case Operation::lfsx:
|
||||
case Operation::lhaux: case Operation::lhax: case Operation::lhbrx:
|
||||
case Operation::lhzux: case Operation::lhzx:
|
||||
case Operation::lswi: case Operation::lswx:
|
||||
case Operation::lwarx: case Operation::lwaux: case Operation::lwax: case Operation::lwbrx:
|
||||
case Operation::lwzux: case Operation::lwzx:
|
||||
case Operation::mfspr: case Operation::mftb:
|
||||
case Operation::mtspr:
|
||||
case Operation::stbux: case Operation::stbx:
|
||||
case Operation::stdux: case Operation::stdx:
|
||||
case Operation::stfdux: case Operation::stfdx:
|
||||
case Operation::stfiwx:
|
||||
case Operation::stfsux: case Operation::stfsx:
|
||||
case Operation::sthbrx:
|
||||
case Operation::sthux: case Operation::sthx:
|
||||
case Operation::stswi: case Operation::stswx:
|
||||
case Operation::stwbrx:
|
||||
case Operation::stwux: case Operation::stwx:
|
||||
case Operation::td: case Operation::tw:
|
||||
if(opcode & 0b000000'00000'00000'00000'0000000000'1) return Instruction(opcode);
|
||||
break;
|
||||
|
||||
case Operation::fabsx: case Operation::fcfidx:
|
||||
case Operation::fctidx: case Operation::fctidzx:
|
||||
case Operation::fctiwx: case Operation::fctiwzx:
|
||||
case Operation::fmrx: case Operation::fnabsx:
|
||||
case Operation::fnegx: case Operation::frspx:
|
||||
if(opcode & 0b000000'00000'11111'00000'0000000000'0) return Instruction(opcode);
|
||||
break;
|
||||
|
||||
case Operation::faddx: case Operation::faddsx:
|
||||
case Operation::fdivx: case Operation::fdivsx:
|
||||
case Operation::fsubx: case Operation::fsubsx:
|
||||
if(opcode & 0b000000'00000'00000'00000'1111100000'0) return Instruction(opcode);
|
||||
break;
|
||||
|
||||
case Operation::fcmpo: case Operation::fcmpu:
|
||||
if(opcode & 0b000000'00011'00000'00000'0000000000'1) return Instruction(opcode);
|
||||
break;
|
||||
|
||||
case Operation::fresx: case Operation::frsqrtex:
|
||||
case Operation::fsqrtx: case Operation::fsqrtsx:
|
||||
if(opcode & 0b000000'00000'11111'00000'1111100000'1) return Instruction(opcode);
|
||||
break;
|
||||
|
||||
case Operation::icbi:
|
||||
if(opcode & 0b000000'11111'00000'00000'0000000000'1) return Instruction(opcode);
|
||||
break;
|
||||
|
||||
case Operation::eieio:
|
||||
case Operation::isync:
|
||||
case Operation::rfi:
|
||||
case Operation::slbia:
|
||||
case Operation::sync:
|
||||
case Operation::tlbia:
|
||||
case Operation::tlbsync:
|
||||
if(opcode & 0b000000'11111'11111'11111'0000000000'1) return Instruction(opcode);
|
||||
break;
|
||||
|
||||
case Operation::mcrf: case Operation::mcrfs:
|
||||
if(opcode & 0b000000'00011'00011'11111'0000000000'1) return Instruction(opcode);
|
||||
break;
|
||||
|
||||
case Operation::mcrxr:
|
||||
if(opcode & 0b000000'00011'11111'11111'0000000000'1) return Instruction(opcode);
|
||||
break;
|
||||
|
||||
case Operation::mfcr:
|
||||
case Operation::mfmsr:
|
||||
case Operation::mtmsr:
|
||||
if(opcode & 0b000000'00000'11111'11111'0000000000'1) return Instruction(opcode);
|
||||
break;
|
||||
|
||||
case Operation::mffsx:
|
||||
case Operation::mtfsb0x:
|
||||
case Operation::mtfsb1x:
|
||||
if(opcode & 0b000000'00000'11111'11111'0000000000'0) return Instruction(opcode);
|
||||
break;
|
||||
|
||||
case Operation::mtfsfx:
|
||||
if(opcode & 0b000000'10000'00001'00000'0000000000'0) return Instruction(opcode);
|
||||
break;
|
||||
|
||||
case Operation::mtfsfix:
|
||||
if(opcode & 0b000000'00011'11111'00001'0000000000'0) return Instruction(opcode);
|
||||
break;
|
||||
|
||||
case Operation::mtsr:
|
||||
if(opcode & 0b000000'00000'10000'11111'0000000000'1) return Instruction(opcode);
|
||||
break;
|
||||
|
||||
case Operation::mtsrin: case Operation::mfsrin:
|
||||
if(opcode & 0b000000'00000'11111'00000'0000000000'1) return Instruction(opcode);
|
||||
break;
|
||||
|
||||
case Operation::mfsr:
|
||||
if(opcode & 0b000000'00000'10000'11111'0000000000'1) return Instruction(opcode);
|
||||
break;
|
||||
|
||||
case Operation::mtcrf:
|
||||
if(opcode & 0b000000'00000'10000'00001'0000000000'1) return Instruction(opcode);
|
||||
break;
|
||||
|
||||
case Operation::mulhdx: case Operation::mulhdux:
|
||||
case Operation::mulhwx: case Operation::mulhwux:
|
||||
if(opcode & 0b000000'00000'00000'00000'1000000000'0) return Instruction(opcode);
|
||||
break;
|
||||
|
||||
case Operation::sc:
|
||||
if(opcode & 0b000000'11111'11111'11111'1111111110'1) return Instruction(opcode);
|
||||
break;
|
||||
|
||||
case Operation::slbie:
|
||||
case Operation::tlbie:
|
||||
if(opcode & 0b000000'11111'11111'00000'0000000000'1) return Instruction(opcode);
|
||||
break;
|
||||
|
||||
case Operation::stwcx_:
|
||||
if(!(opcode & 0b000000'00000'00000'00000'0000000000'1)) return Instruction(opcode);
|
||||
break;
|
||||
|
||||
case Operation::divx: case Operation::divsx:
|
||||
case Operation::dozx: case Operation::dozi:
|
||||
case Operation::lscbxx:
|
||||
case Operation::maskgx: case Operation::maskirx:
|
||||
case Operation::mulx:
|
||||
case Operation::rlmix: case Operation::rribx:
|
||||
case Operation::slex: case Operation::sleqx: case Operation::sliqx:
|
||||
case Operation::slliqx: case Operation::sllqx: case Operation::slqx:
|
||||
case Operation::sraiqx: case Operation::sraqx:
|
||||
case Operation::srex: case Operation::sreqx:
|
||||
case Operation::sriqx: case Operation::srliqx:
|
||||
case Operation::srlqx: case Operation::srqx:
|
||||
case Operation::sreax:
|
||||
case Operation::addx: case Operation::addcx: case Operation::addex:
|
||||
case Operation::addi: case Operation::addic: case Operation::addic_:
|
||||
case Operation::addis:
|
||||
case Operation::andx: case Operation::andcx:
|
||||
case Operation::andi_: case Operation::andis_:
|
||||
case Operation::bx: case Operation::bcx:
|
||||
case Operation::divdx: case Operation::divdux:
|
||||
case Operation::divwx: case Operation::divwux:
|
||||
case Operation::eqvx:
|
||||
case Operation::fmaddx: case Operation::fmaddsx:
|
||||
case Operation::fmsubx: case Operation::fmsubsx:
|
||||
case Operation::fnmaddx: case Operation::fnmaddsx:
|
||||
case Operation::fnmsubx: case Operation::fnmsubsx:
|
||||
case Operation::fselx:
|
||||
case Operation::lbz: case Operation::lbzu:
|
||||
case Operation::lfd: case Operation::lfdu:
|
||||
case Operation::lfs: case Operation::lfsu:
|
||||
case Operation::lha: case Operation::lhau:
|
||||
case Operation::lhz: case Operation::lhzu:
|
||||
case Operation::lmw: case Operation::lwa:
|
||||
case Operation::lwz: case Operation::lwzu:
|
||||
case Operation::mulldx: case Operation::mulli: case Operation::mullwx:
|
||||
case Operation::nandx: case Operation::norx:
|
||||
case Operation::orx: case Operation::orcx:
|
||||
case Operation::ori: case Operation::oris:
|
||||
case Operation::rlwimix: case Operation::rlwinmx: case Operation::rlwnmx:
|
||||
case Operation::sldx: case Operation::slwx:
|
||||
case Operation::sradx: case Operation::sradix:
|
||||
case Operation::srawx: case Operation::srawix:
|
||||
case Operation::srdx: case Operation::srwx:
|
||||
case Operation::stb: case Operation::stbu:
|
||||
case Operation::std: case Operation::stdcx_: case Operation::stdu:
|
||||
case Operation::stfd: case Operation::stfdu:
|
||||
case Operation::stfs: case Operation::stfsu:
|
||||
case Operation::sth: case Operation::sthu:
|
||||
case Operation::stmw:
|
||||
case Operation::stw: case Operation::stwu:
|
||||
case Operation::subfx: case Operation::subfcx: case Operation::subfex:
|
||||
case Operation::subfic:
|
||||
case Operation::tdi: case Operation::twi:
|
||||
case Operation::xorx: case Operation::xori: case Operation::xoris:
|
||||
case Operation::ld: case Operation::ldu:
|
||||
case Operation::rldclx: case Operation::rldcrx:
|
||||
case Operation::rldicx: case Operation::rldiclx:
|
||||
case Operation::rldicrx: case Operation::rldimix:
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
return Instruction(operation, opcode, is_supervisor);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
template <Model model, bool validate_reserved_bits>
|
||||
Instruction Decoder<model, validate_reserved_bits>::decode(uint32_t opcode) {
|
||||
// Quick bluffer's guide to PowerPC instruction encoding:
|
||||
//
|
||||
// There is a six-bit field at the very top of the instruction.
|
||||
// Sometimes that fully identifies an instruction, but usually
|
||||
// it doesn't.
|
||||
//
|
||||
// There is an addition 9- or 10-bit field starting one bit above
|
||||
// least significant that disambiguates the rest. Strictly speaking
|
||||
// it's a 10-bit field, but the mnemonics for many instructions treat
|
||||
// it as a 9-bit field with a flag at the top.
|
||||
//
|
||||
// I've decided to hew directly to the mnemonics.
|
||||
//
|
||||
// Various opcodes in the 1995 documentation define reserved bits,
|
||||
// which are given the nominal value of 0. It does not give a formal
|
||||
// definition of a reserved bit. As a result this code does not
|
||||
// currently check the value of reserved bits. That may need to change
|
||||
// if/when I add support for extended instruction sets.
|
||||
|
||||
#define Bind(mask, operation) case mask: return instruction<model, validate_reserved_bits, Operation::operation>(opcode);
|
||||
#define BindSupervisor(mask, operation) case mask: return instruction<model, validate_reserved_bits, Operation::operation>(opcode, true);
|
||||
#define BindConditional(condition, mask, operation) \
|
||||
case mask: \
|
||||
if(condition(model)) return instruction<model, validate_reserved_bits, Operation::operation>(opcode); \
|
||||
return instruction<model, validate_reserved_bits, Operation::operation>(opcode);
|
||||
#define BindSupervisorConditional(condition, mask, operation) \
|
||||
case mask: \
|
||||
if(condition(model)) return instruction<model, validate_reserved_bits, Operation::operation>(opcode, true); \
|
||||
return instruction<model, validate_reserved_bits, Operation::operation>(opcode);
|
||||
|
||||
#define Six(x) (unsigned(x) << 26)
|
||||
#define SixTen(x, y) (Six(x) | ((y) << 1))
|
||||
|
||||
// First pass: weed out all those instructions identified entirely by the
|
||||
// top six bits.
|
||||
switch(opcode & Six(0b111111)) {
|
||||
default: break;
|
||||
|
||||
BindConditional(is64bit, Six(0b000010), tdi);
|
||||
|
||||
Bind(Six(0b000011), twi);
|
||||
Bind(Six(0b000111), mulli);
|
||||
Bind(Six(0b001000), subfic);
|
||||
Bind(Six(0b001100), addic); Bind(Six(0b001101), addic_);
|
||||
Bind(Six(0b001110), addi); Bind(Six(0b001111), addis);
|
||||
case Six(0b010000): {
|
||||
// This might be a bcx, but check for a valid bo field.
|
||||
switch((opcode >> 21) & 0x1f) {
|
||||
case 0: case 1: case 2: case 3: case 4: case 5:
|
||||
case 8: case 9: case 10: case 11: case 12: case 13:
|
||||
case 16: case 17: case 18: case 19: case 20:
|
||||
return instruction<model, validate_reserved_bits, Operation::bcx>(opcode);
|
||||
|
||||
default: return Instruction(opcode);
|
||||
}
|
||||
} break;
|
||||
Bind(Six(0b010010), bx);
|
||||
Bind(Six(0b010100), rlwimix);
|
||||
Bind(Six(0b010101), rlwinmx);
|
||||
Bind(Six(0b010111), rlwnmx);
|
||||
|
||||
Bind(Six(0b011000), ori); Bind(Six(0b011001), oris);
|
||||
Bind(Six(0b011010), xori); Bind(Six(0b011011), xoris);
|
||||
Bind(Six(0b011100), andi_); Bind(Six(0b011101), andis_);
|
||||
Bind(Six(0b100000), lwz); Bind(Six(0b100001), lwzu);
|
||||
Bind(Six(0b100010), lbz); Bind(Six(0b100011), lbzu);
|
||||
Bind(Six(0b100100), stw); Bind(Six(0b100101), stwu);
|
||||
Bind(Six(0b100110), stb); Bind(Six(0b100111), stbu);
|
||||
Bind(Six(0b101000), lhz); Bind(Six(0b101001), lhzu);
|
||||
Bind(Six(0b101010), lha); Bind(Six(0b101011), lhau);
|
||||
Bind(Six(0b101100), sth); Bind(Six(0b101101), sthu);
|
||||
Bind(Six(0b101110), lmw); Bind(Six(0b101111), stmw);
|
||||
Bind(Six(0b110000), lfs); Bind(Six(0b110001), lfsu);
|
||||
Bind(Six(0b110010), lfd); Bind(Six(0b110011), lfdu);
|
||||
Bind(Six(0b110100), stfs); Bind(Six(0b110101), stfsu);
|
||||
Bind(Six(0b110110), stfd); Bind(Six(0b110111), stfdu);
|
||||
|
||||
BindConditional(is601, Six(9), dozi);
|
||||
BindConditional(is601, Six(22), rlmix);
|
||||
|
||||
Bind(Six(0b001010), cmpli); Bind(Six(0b001011), cmpi);
|
||||
}
|
||||
|
||||
// Second pass: all those with a top six bits and a bottom nine or ten.
|
||||
switch(opcode & SixTen(0b111111, 0b1111111111)) {
|
||||
default: break;
|
||||
|
||||
// 64-bit instructions.
|
||||
BindConditional(is64bit, SixTen(0b011111, 0b0000001001), mulhdux); BindConditional(is64bit, SixTen(0b011111, 0b1000001001), mulhdux);
|
||||
BindConditional(is64bit, SixTen(0b011111, 0b0000010101), ldx);
|
||||
BindConditional(is64bit, SixTen(0b011111, 0b0000011011), sldx);
|
||||
BindConditional(is64bit, SixTen(0b011111, 0b0000110101), ldux);
|
||||
BindConditional(is64bit, SixTen(0b011111, 0b0000111010), cntlzdx);
|
||||
BindConditional(is64bit, SixTen(0b011111, 0b0001000100), td);
|
||||
BindConditional(is64bit, SixTen(0b011111, 0b0001001001), mulhdx); BindConditional(is64bit, SixTen(0b011111, 0b1001001001), mulhdx);
|
||||
BindConditional(is64bit, SixTen(0b011111, 0b0001010100), ldarx);
|
||||
BindConditional(is64bit, SixTen(0b011111, 0b0010010101), stdx);
|
||||
BindConditional(is64bit, SixTen(0b011111, 0b0010110101), stdux);
|
||||
BindConditional(is64bit, SixTen(0b011111, 0b0011101001), mulldx); BindConditional(is64bit, SixTen(0b011111, 0b1011101001), mulldx);
|
||||
BindConditional(is64bit, SixTen(0b011111, 0b0101010101), lwax);
|
||||
BindConditional(is64bit, SixTen(0b011111, 0b0101110101), lwaux);
|
||||
BindConditional(is64bit, SixTen(0b011111, 0b1100111011), sradix); BindConditional(is64bit, SixTen(0b011111, 0b1100111010), sradix);
|
||||
BindConditional(is64bit, SixTen(0b011111, 0b0110110010), slbie);
|
||||
BindConditional(is64bit, SixTen(0b011111, 0b0111001001), divdux); BindConditional(is64bit, SixTen(0b011111, 0b1111001001), divdux);
|
||||
BindConditional(is64bit, SixTen(0b011111, 0b0111101001), divdx); BindConditional(is64bit, SixTen(0b011111, 0b1111101001), divdx);
|
||||
BindConditional(is64bit, SixTen(0b011111, 0b1000011011), srdx);
|
||||
BindConditional(is64bit, SixTen(0b011111, 0b1100011010), sradx);
|
||||
BindConditional(is64bit, SixTen(0b111111, 0b1111011010), extswx);
|
||||
|
||||
// Power instructions; these are all taken from the MPC601 manual rather than
|
||||
// the PowerPC Programmer's Reference Guide, hence the decimal encoding of the
|
||||
// ten-bit field.
|
||||
BindConditional(is601, SixTen(0b011111, 360), absx); BindConditional(is601, SixTen(0b011111, 512 + 360), absx);
|
||||
BindConditional(is601, SixTen(0b011111, 531), clcs);
|
||||
BindConditional(is601, SixTen(0b011111, 331), divx); BindConditional(is601, SixTen(0b011111, 512 + 331), divx);
|
||||
BindConditional(is601, SixTen(0b011111, 363), divsx); BindConditional(is601, SixTen(0b011111, 512 + 363), divsx);
|
||||
BindConditional(is601, SixTen(0b011111, 264), dozx); BindConditional(is601, SixTen(0b011111, 512 + 264), dozx);
|
||||
BindConditional(is601, SixTen(0b011111, 277), lscbxx);
|
||||
BindConditional(is601, SixTen(0b011111, 29), maskgx);
|
||||
BindConditional(is601, SixTen(0b011111, 541), maskirx);
|
||||
BindConditional(is601, SixTen(0b011111, 107), mulx); BindConditional(is601, SixTen(0b011111, 512 + 107), mulx);
|
||||
BindConditional(is601, SixTen(0b011111, 488), nabsx); BindConditional(is601, SixTen(0b011111, 512 + 488), nabsx);
|
||||
BindConditional(is601, SixTen(0b011111, 537), rribx);
|
||||
BindConditional(is601, SixTen(0b011111, 153), slex);
|
||||
BindConditional(is601, SixTen(0b011111, 217), sleqx);
|
||||
BindConditional(is601, SixTen(0b011111, 184), sliqx);
|
||||
BindConditional(is601, SixTen(0b011111, 248), slliqx);
|
||||
BindConditional(is601, SixTen(0b011111, 216), sllqx);
|
||||
BindConditional(is601, SixTen(0b011111, 152), slqx);
|
||||
BindConditional(is601, SixTen(0b011111, 952), sraiqx);
|
||||
BindConditional(is601, SixTen(0b011111, 920), sraqx);
|
||||
BindConditional(is601, SixTen(0b011111, 665), srex);
|
||||
BindConditional(is601, SixTen(0b011111, 921), sreax);
|
||||
BindConditional(is601, SixTen(0b011111, 729), sreqx);
|
||||
BindConditional(is601, SixTen(0b011111, 696), sriqx);
|
||||
BindConditional(is601, SixTen(0b011111, 760), srliqx);
|
||||
BindConditional(is601, SixTen(0b011111, 728), srlqx);
|
||||
BindConditional(is601, SixTen(0b011111, 664), srqx);
|
||||
|
||||
// 32-bit instructions.
|
||||
Bind(SixTen(0b010011, 0b0000000000), mcrf);
|
||||
Bind(SixTen(0b010011, 0b0000010000), bclrx);
|
||||
Bind(SixTen(0b010011, 0b0000100001), crnor);
|
||||
Bind(SixTen(0b010011, 0b0000110010), rfi);
|
||||
Bind(SixTen(0b010011, 0b0010000001), crandc);
|
||||
Bind(SixTen(0b010011, 0b0010010110), isync);
|
||||
Bind(SixTen(0b010011, 0b0011000001), crxor);
|
||||
Bind(SixTen(0b010011, 0b0011100001), crnand);
|
||||
Bind(SixTen(0b010011, 0b0100000001), crand);
|
||||
Bind(SixTen(0b010011, 0b0100100001), creqv);
|
||||
Bind(SixTen(0b010011, 0b0110100001), crorc);
|
||||
Bind(SixTen(0b010011, 0b0111000001), cror);
|
||||
Bind(SixTen(0b010011, 0b1000010000), bcctrx);
|
||||
Bind(SixTen(0b011111, 0b0000000000), cmp);
|
||||
Bind(SixTen(0b011111, 0b0000000100), tw);
|
||||
Bind(SixTen(0b011111, 0b0000001000), subfcx); Bind(SixTen(0b011111, 0b1000001000), subfcx);
|
||||
Bind(SixTen(0b011111, 0b0000001010), addcx); Bind(SixTen(0b011111, 0b1000001010), addcx);
|
||||
Bind(SixTen(0b011111, 0b0000001011), mulhwux); Bind(SixTen(0b011111, 0b1000001011), mulhwux);
|
||||
Bind(SixTen(0b011111, 0b0000010011), mfcr);
|
||||
Bind(SixTen(0b011111, 0b0000010100), lwarx);
|
||||
Bind(SixTen(0b011111, 0b0000010111), lwzx);
|
||||
Bind(SixTen(0b011111, 0b0000011000), slwx);
|
||||
Bind(SixTen(0b011111, 0b0000011010), cntlzwx);
|
||||
Bind(SixTen(0b011111, 0b0000011100), andx);
|
||||
Bind(SixTen(0b011111, 0b0000100000), cmpl);
|
||||
Bind(SixTen(0b011111, 0b0000101000), subfx); Bind(SixTen(0b011111, 0b1000101000), subfx);
|
||||
Bind(SixTen(0b011111, 0b0000110110), dcbst);
|
||||
Bind(SixTen(0b011111, 0b0000110111), lwzux);
|
||||
Bind(SixTen(0b011111, 0b0000111100), andcx);
|
||||
Bind(SixTen(0b011111, 0b0001001011), mulhwx); Bind(SixTen(0b011111, 0b1001001011), mulhwx);
|
||||
Bind(SixTen(0b011111, 0b0001010011), mfmsr);
|
||||
Bind(SixTen(0b011111, 0b0001010110), dcbf);
|
||||
Bind(SixTen(0b011111, 0b0001010111), lbzx);
|
||||
Bind(SixTen(0b011111, 0b0001101000), negx); Bind(SixTen(0b011111, 0b1001101000), negx);
|
||||
Bind(SixTen(0b011111, 0b0001110111), lbzux);
|
||||
Bind(SixTen(0b011111, 0b0001111100), norx);
|
||||
Bind(SixTen(0b011111, 0b0010001000), subfex); Bind(SixTen(0b011111, 0b1010001000), subfex);
|
||||
Bind(SixTen(0b011111, 0b0010001010), addex); Bind(SixTen(0b011111, 0b1010001010), addex);
|
||||
Bind(SixTen(0b011111, 0b0010010000), mtcrf);
|
||||
Bind(SixTen(0b011111, 0b0010010010), mtmsr);
|
||||
Bind(SixTen(0b011111, 0b0010010111), stwx);
|
||||
Bind(SixTen(0b011111, 0b0010110111), stwux);
|
||||
Bind(SixTen(0b011111, 0b0011001000), subfzex); Bind(SixTen(0b011111, 0b1011001000), subfzex);
|
||||
Bind(SixTen(0b011111, 0b0011001010), addzex); Bind(SixTen(0b011111, 0b1011001010), addzex);
|
||||
Bind(SixTen(0b011111, 0b0011010111), stbx);
|
||||
Bind(SixTen(0b011111, 0b0011101000), subfmex); Bind(SixTen(0b011111, 0b1011101000), subfmex);
|
||||
Bind(SixTen(0b011111, 0b0011101010), addmex); Bind(SixTen(0b011111, 0b1011101010), addmex);
|
||||
Bind(SixTen(0b011111, 0b0011101011), mullwx); Bind(SixTen(0b011111, 0b1011101011), mullwx);
|
||||
Bind(SixTen(0b011111, 0b0011110110), dcbtst);
|
||||
Bind(SixTen(0b011111, 0b0011110111), stbux);
|
||||
Bind(SixTen(0b011111, 0b0100001010), addx); Bind(SixTen(0b011111, 0b1100001010), addx);
|
||||
Bind(SixTen(0b011111, 0b0100010110), dcbt);
|
||||
Bind(SixTen(0b011111, 0b0100010111), lhzx);
|
||||
Bind(SixTen(0b011111, 0b0100011100), eqvx);
|
||||
Bind(SixTen(0b011111, 0b0100110110), eciwx);
|
||||
Bind(SixTen(0b011111, 0b0100110111), lhzux);
|
||||
Bind(SixTen(0b011111, 0b0100111100), xorx);
|
||||
Bind(SixTen(0b011111, 0b0101010111), lhax);
|
||||
Bind(SixTen(0b011111, 0b0101110011), mftb);
|
||||
Bind(SixTen(0b011111, 0b0101110111), lhaux);
|
||||
Bind(SixTen(0b011111, 0b0110010111), sthx);
|
||||
Bind(SixTen(0b011111, 0b0110011100), orcx);
|
||||
Bind(SixTen(0b011111, 0b0110110110), ecowx);
|
||||
Bind(SixTen(0b011111, 0b0110110111), sthux);
|
||||
Bind(SixTen(0b011111, 0b0110111100), orx);
|
||||
Bind(SixTen(0b011111, 0b0111001011), divwux); Bind(SixTen(0b011111, 0b1111001011), divwux);
|
||||
Bind(SixTen(0b011111, 0b0111010110), dcbi);
|
||||
Bind(SixTen(0b011111, 0b0111011100), nandx);
|
||||
Bind(SixTen(0b011111, 0b0111101011), divwx); Bind(SixTen(0b011111, 0b1111101011), divwx);
|
||||
Bind(SixTen(0b011111, 0b1000000000), mcrxr);
|
||||
Bind(SixTen(0b011111, 0b1000010101), lswx);
|
||||
Bind(SixTen(0b011111, 0b1000010110), lwbrx);
|
||||
Bind(SixTen(0b011111, 0b1000010111), lfsx);
|
||||
Bind(SixTen(0b011111, 0b1000011000), srwx);
|
||||
Bind(SixTen(0b011111, 0b1000110111), lfsux);
|
||||
Bind(SixTen(0b011111, 0b1001010101), lswi);
|
||||
Bind(SixTen(0b011111, 0b1001010110), sync);
|
||||
Bind(SixTen(0b011111, 0b1001010111), lfdx);
|
||||
Bind(SixTen(0b011111, 0b1001110111), lfdux);
|
||||
Bind(SixTen(0b011111, 0b1010010101), stswx);
|
||||
Bind(SixTen(0b011111, 0b1010010110), stwbrx);
|
||||
Bind(SixTen(0b011111, 0b1010010111), stfsx);
|
||||
Bind(SixTen(0b011111, 0b1010110111), stfsux);
|
||||
Bind(SixTen(0b011111, 0b1011010101), stswi);
|
||||
Bind(SixTen(0b011111, 0b1011010111), stfdx);
|
||||
Bind(SixTen(0b011111, 0b1011110111), stfdux);
|
||||
Bind(SixTen(0b011111, 0b1100010110), lhbrx);
|
||||
Bind(SixTen(0b011111, 0b1100011000), srawx);
|
||||
Bind(SixTen(0b011111, 0b1100111000), srawix);
|
||||
Bind(SixTen(0b011111, 0b1101010110), eieio);
|
||||
Bind(SixTen(0b011111, 0b1110010110), sthbrx);
|
||||
Bind(SixTen(0b011111, 0b1110011010), extshx);
|
||||
Bind(SixTen(0b011111, 0b1110111010), extsbx);
|
||||
Bind(SixTen(0b011111, 0b1111010110), icbi);
|
||||
Bind(SixTen(0b011111, 0b1111010111), stfiwx);
|
||||
Bind(SixTen(0b011111, 0b1111110110), dcbz);
|
||||
Bind(SixTen(0b111111, 0b0000000000), fcmpu);
|
||||
Bind(SixTen(0b111111, 0b0000001100), frspx);
|
||||
Bind(SixTen(0b111111, 0b0000001110), fctiwx);
|
||||
Bind(SixTen(0b111111, 0b0000001111), fctiwzx);
|
||||
Bind(SixTen(0b111111, 0b0000100000), fcmpo);
|
||||
Bind(SixTen(0b111111, 0b0000100110), mtfsb1x);
|
||||
Bind(SixTen(0b111111, 0b0000101000), fnegx);
|
||||
Bind(SixTen(0b111111, 0b0001000000), mcrfs);
|
||||
Bind(SixTen(0b111111, 0b0001000110), mtfsb0x);
|
||||
Bind(SixTen(0b111111, 0b0001001000), fmrx);
|
||||
Bind(SixTen(0b111111, 0b0010000110), mtfsfix);
|
||||
Bind(SixTen(0b111111, 0b0010001000), fnabsx);
|
||||
Bind(SixTen(0b111111, 0b0100001000), fabsx);
|
||||
Bind(SixTen(0b111111, 0b1001000111), mffsx);
|
||||
Bind(SixTen(0b111111, 0b1011000111), mtfsfx);
|
||||
Bind(SixTen(0b111111, 0b1100101110), fctidx);
|
||||
Bind(SixTen(0b111111, 0b1100101111), fctidzx);
|
||||
Bind(SixTen(0b111111, 0b1101001110), fcfidx);
|
||||
|
||||
Bind(SixTen(0b011111, 0b0101010011), mfspr); // Flagged as "supervisor and user"?
|
||||
Bind(SixTen(0b011111, 0b0111010011), mtspr); // Flagged as "supervisor and user"?
|
||||
|
||||
BindSupervisorConditional(is32bit, SixTen(0b011111, 0b0011010010), mtsr);
|
||||
BindSupervisorConditional(is32bit, SixTen(0b011111, 0b0011110010), mtsrin);
|
||||
BindSupervisorConditional(is32bit, SixTen(0b011111, 0b1001010011), mfsr);
|
||||
BindSupervisorConditional(is32bit, SixTen(0b011111, 0b1010010011), mfsrin);
|
||||
|
||||
BindSupervisorConditional(is64bit, SixTen(0b011111, 0b0111110010), slbia); // optional
|
||||
|
||||
// The following are all optional; should I record that?
|
||||
BindSupervisor(SixTen(0b011111, 0b0100110010), tlbie);
|
||||
BindSupervisor(SixTen(0b011111, 0b0101110010), tlbia);
|
||||
BindSupervisor(SixTen(0b011111, 0b1000110110), tlbsync);
|
||||
}
|
||||
|
||||
// Third pass: like six-ten except that the top five of the final ten
|
||||
// are reserved (i.e. ignored here).
|
||||
switch(opcode & SixTen(0b111111, 0b11111)) {
|
||||
default: break;
|
||||
|
||||
Bind(SixTen(0b111011, 0b10010), fdivsx);
|
||||
Bind(SixTen(0b111011, 0b10100), fsubsx);
|
||||
Bind(SixTen(0b111011, 0b10101), faddsx);
|
||||
Bind(SixTen(0b111011, 0b11001), fmulsx);
|
||||
Bind(SixTen(0b111011, 0b11100), fmsubsx);
|
||||
Bind(SixTen(0b111011, 0b11101), fmaddsx);
|
||||
Bind(SixTen(0b111011, 0b11110), fnmsubsx);
|
||||
Bind(SixTen(0b111011, 0b11111), fnmaddsx);
|
||||
|
||||
Bind(SixTen(0b111111, 0b10010), fdivx);
|
||||
Bind(SixTen(0b111111, 0b10100), fsubx);
|
||||
Bind(SixTen(0b111111, 0b10101), faddx);
|
||||
Bind(SixTen(0b111111, 0b11001), fmulx);
|
||||
Bind(SixTen(0b111111, 0b11100), fmsubx);
|
||||
Bind(SixTen(0b111111, 0b11101), fmaddx);
|
||||
Bind(SixTen(0b111111, 0b11110), fnmsubx);
|
||||
Bind(SixTen(0b111111, 0b11111), fnmaddx);
|
||||
|
||||
BindConditional(is64bit, SixTen(0b111011, 0b10110), fsqrtsx);
|
||||
BindConditional(is64bit, SixTen(0b111011, 0b11000), fresx);
|
||||
BindConditional(is64bit, SixTen(0b011110, 0b01000), rldclx);
|
||||
BindConditional(is64bit, SixTen(0b011110, 0b01001), rldcrx);
|
||||
|
||||
// Optional...
|
||||
Bind(SixTen(0b111111, 0b10110), fsqrtx);
|
||||
Bind(SixTen(0b111111, 0b10111), fselx);
|
||||
Bind(SixTen(0b111111, 0b11010), frsqrtex);
|
||||
}
|
||||
|
||||
// rldicx, rldiclx, rldicrx, rldimix
|
||||
if(is64bit(model)) {
|
||||
switch(opcode & 0b111111'00000'00000'00000'000000'111'00) {
|
||||
default: break;
|
||||
case 0b011110'00000'00000'00000'000000'000'00: return instruction<model, validate_reserved_bits, Operation::rldiclx>(opcode);
|
||||
case 0b011110'00000'00000'00000'000000'001'00: return instruction<model, validate_reserved_bits, Operation::rldicrx>(opcode);
|
||||
case 0b011110'00000'00000'00000'000000'010'00: return instruction<model, validate_reserved_bits, Operation::rldicx>(opcode);
|
||||
case 0b011110'00000'00000'00000'000000'011'00: return instruction<model, validate_reserved_bits, Operation::rldimix>(opcode);
|
||||
}
|
||||
}
|
||||
|
||||
// stwcx. and stdcx.
|
||||
switch(opcode & 0b111111'0000'0000'0000'0000'111111111'1) {
|
||||
default: break;
|
||||
case 0b011111'0000'0000'0000'0000'010010110'1: return instruction<model, validate_reserved_bits, Operation::stwcx_>(opcode);
|
||||
case 0b011111'0000'0000'0000'0000'011010110'1:
|
||||
if(is64bit(model)) return instruction<model, validate_reserved_bits, Operation::stdcx_>(opcode);
|
||||
return Instruction(opcode);
|
||||
}
|
||||
|
||||
// std, stdu, ld, ldu, lwa
|
||||
if(is64bit(model)) {
|
||||
switch(opcode & 0b111111'00'00000000'00000000'000000'11) {
|
||||
default: break;
|
||||
case 0b111010'00'00000000'00000000'000000'00: return instruction<model, validate_reserved_bits, Operation::ld>(opcode);
|
||||
case 0b111010'00'00000000'00000000'000000'01: return instruction<model, validate_reserved_bits, Operation::ldu>(opcode);
|
||||
case 0b111010'00'00000000'00000000'000000'10: return instruction<model, validate_reserved_bits, Operation::lwa>(opcode);
|
||||
case 0b111110'00'00000000'00000000'000000'00: return instruction<model, validate_reserved_bits, Operation::std>(opcode);
|
||||
case 0b111110'00'00000000'00000000'000000'01: return instruction<model, validate_reserved_bits, Operation::stdu>(opcode);
|
||||
}
|
||||
}
|
||||
|
||||
// sc
|
||||
if((opcode & 0b111111'00'00000000'00000000'000000'1'0) == 0b010001'00'00000000'00000000'000000'1'0) {
|
||||
return instruction<model, validate_reserved_bits, Operation::sc>(opcode);
|
||||
}
|
||||
|
||||
#undef Six
|
||||
#undef SixTen
|
||||
|
||||
#undef Bind
|
||||
#undef BindConditional
|
||||
|
||||
return Instruction(opcode);
|
||||
}
|
||||
|
||||
template struct InstructionSet::PowerPC::Decoder<InstructionSet::PowerPC::Model::MPC601, true>;
|
||||
template struct InstructionSet::PowerPC::Decoder<InstructionSet::PowerPC::Model::MPC603, true>;
|
||||
template struct InstructionSet::PowerPC::Decoder<InstructionSet::PowerPC::Model::MPC620, true>;
|
||||
|
||||
template struct InstructionSet::PowerPC::Decoder<InstructionSet::PowerPC::Model::MPC601, false>;
|
||||
template struct InstructionSet::PowerPC::Decoder<InstructionSet::PowerPC::Model::MPC603, false>;
|
||||
template struct InstructionSet::PowerPC::Decoder<InstructionSet::PowerPC::Model::MPC620, false>;
|
||||
56
InstructionSets/PowerPC/Decoder.hpp
Normal file
56
InstructionSets/PowerPC/Decoder.hpp
Normal file
@@ -0,0 +1,56 @@
|
||||
//
|
||||
// Decoder.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 30/12/20.
|
||||
// Copyright © 2020 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef InstructionSets_PowerPC_Decoder_hpp
|
||||
#define InstructionSets_PowerPC_Decoder_hpp
|
||||
|
||||
#include "Instruction.hpp"
|
||||
|
||||
namespace InstructionSet {
|
||||
namespace PowerPC {
|
||||
|
||||
enum class Model {
|
||||
/// i.e. 32-bit, with POWER carry-over instructions.
|
||||
MPC601,
|
||||
/// i.e. 32-bit, no POWER instructions.
|
||||
MPC603,
|
||||
/// i.e. 64-bit.
|
||||
MPC620,
|
||||
};
|
||||
|
||||
constexpr bool is64bit(Model model) {
|
||||
return model == Model::MPC620;
|
||||
}
|
||||
|
||||
constexpr bool is32bit(Model model) {
|
||||
return !is64bit(model);
|
||||
}
|
||||
|
||||
constexpr bool is601(Model model) {
|
||||
return model == Model::MPC601;
|
||||
}
|
||||
|
||||
/*!
|
||||
Implements PowerPC instruction decoding.
|
||||
|
||||
@c model Indicates the instruction set to decode.
|
||||
|
||||
@c validate_reserved_bits If set to @c true, check that all
|
||||
reserved bits are 0 or 1 as required and produce an invalid opcode if not.
|
||||
Otherwise does no inspection of reserved bits.
|
||||
|
||||
TODO: determine what specific models of PowerPC do re: reserved bits.
|
||||
*/
|
||||
template <Model model, bool validate_reserved_bits = false> struct Decoder {
|
||||
Instruction decode(uint32_t opcode);
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* InstructionSets_PowerPC_Decoder_hpp */
|
||||
1536
InstructionSets/PowerPC/Instruction.hpp
Normal file
1536
InstructionSets/PowerPC/Instruction.hpp
Normal file
File diff suppressed because it is too large
Load Diff
86
InstructionSets/README.md
Normal file
86
InstructionSets/README.md
Normal file
@@ -0,0 +1,86 @@
|
||||
# Instruction Sets
|
||||
|
||||
Code in here provides the means to disassemble, and to execute code for certain instruction sets.
|
||||
|
||||
It **does not seek to emulate specific processors** other than in terms of implementing their instruction sets. So:
|
||||
* it doesn't involve itself in the actual bus signalling of real processors; and
|
||||
* instruction-level timing (e.g. total cycle counts) may be unimplemented, and is likely to be incomplete.
|
||||
|
||||
This part of CLK is intended primarily to provide disassembly services for static analysis, and processing for machines where timing is not part of the specification — i.e. anything that's an instruction set and a HAL.
|
||||
|
||||
## Decoders
|
||||
|
||||
A decoder extracts fully-decoded instructions from a data stream for its associated architecture.
|
||||
|
||||
The meaning of 'fully-decoded' is flexible but it means that a caller can easily discern at least:
|
||||
* the operation in use;
|
||||
* its addressing mode; and
|
||||
* relevant registers.
|
||||
|
||||
It may be assumed that callers will have access to the original data stream for immediate values, if it is sensible to do so.
|
||||
|
||||
In deciding what to expose, what to store ahead of time and what to obtain just-in-time a decoder should have an eye on two principal consumers:
|
||||
1. disassemblers; and
|
||||
2. instruction executors.
|
||||
|
||||
It may also be reasonable to make allowances for bus-centric CPU emulators, but those will be tightly coupled to specific decoders so no general rules need apply.
|
||||
|
||||
Disassemblers are likely to decode an instruction, output it, and then immediately forget about it.
|
||||
|
||||
Instruction executors may opt to cache decoded instructions to reduce recurrent costs, but will always be dealing with an actual instruction stream. The chance of caching means that decoded instructions should seek to be small. If helpful then a decoder might prefer to return a `std::pair` or similar of ephemeral information and stuff that it is meaningful to store.
|
||||
|
||||
### Likely Interfaces
|
||||
|
||||
These examples assume that the processor itself doesn't hold any state that affects instruction parsing. Whether processors with such state offer more than one decoder or take state as an argument will be a question of measure and effect.
|
||||
|
||||
#### Fixed-size instruction words
|
||||
|
||||
If the instructions are a fixed size, the decoder can provide what is functionally a simple lookup, whether implemented as such or not:
|
||||
|
||||
Instruction decode(word_type instruction) { ... }
|
||||
|
||||
For now I have preferred not to make this a simple constructor on `Instruction` because I'm reserving the option of switching to an ephemeral/permanent split in what's returned. More consideration needs to be applied here.
|
||||
|
||||
#### Variable-size instruction words
|
||||
|
||||
If instructions are a variable size, the decoder should maintain internal state such that it can be provided with fragments of instructions until a full decoding has occurred — this avoids an assumption that all source bytes will always be laid out linearly in memory.
|
||||
|
||||
A sample interface:
|
||||
|
||||
std::pair<int, Instruction> decode(word_type *stream, size_t length) { ... }
|
||||
|
||||
In this sample the returned pair provides an `int` size that is one of:
|
||||
* a positive number, indicating a completed decoding that consumed that many `word_type`s; or
|
||||
* a negative number, indicating the [negatived] minimum number of `word_type`s that the caller should try to get hold of before calling `decode` again.
|
||||
|
||||
A caller is permitted to react in any way it prefers to negative numbers; they're a hint potentially to reduce calling overhead only. A size of `0` would be taken to have the same meaning as a size of `-1`.
|
||||
|
||||
## Parsers
|
||||
|
||||
A parser sits one level above a decoder; it is handed:
|
||||
* a start address;
|
||||
* a closing bound; and
|
||||
* a target.
|
||||
|
||||
It is responsible for parsing the instruction stream from the start address up to and not beyond the closing bound, and no further than any unconditional branches.
|
||||
|
||||
It should post to the target:
|
||||
* any instructions fully decoded;
|
||||
* any conditional branch destinations encountered;
|
||||
* any immediately-knowable accessed addresses; and
|
||||
* if a final instruction exists but runs beyond the closing bound, notification of that fact.
|
||||
|
||||
So a parser has the same two primary potential recipients as a decoder: diassemblers, and executors.
|
||||
|
||||
## Executors
|
||||
|
||||
An executor is responsible for only one thing:
|
||||
* mapping from decoded instructions to objects that can perform those instructions.
|
||||
|
||||
An executor is assumed to bundle all the things that go into instruction set execution: processor state and memory, alongside a parser.
|
||||
|
||||
## Caching Executor
|
||||
|
||||
The caching executor is a generic class templated on a specific executor. It will use an executor to cache the results of parsing.
|
||||
|
||||
Idiomatically, the objects that perform instructions will expect to receive an appropriate executor as an argument. If they require other information, such as a copy of the decoded instruction, it should be built into the classes.
|
||||
320
InstructionSets/x86/DataPointerResolver.hpp
Normal file
320
InstructionSets/x86/DataPointerResolver.hpp
Normal file
@@ -0,0 +1,320 @@
|
||||
//
|
||||
// DataPointerResolver.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 24/02/2022.
|
||||
// Copyright © 2022 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef DataPointerResolver_hpp
|
||||
#define DataPointerResolver_hpp
|
||||
|
||||
#include "Instruction.hpp"
|
||||
#include "Model.hpp"
|
||||
|
||||
#include <cassert>
|
||||
|
||||
namespace InstructionSet {
|
||||
namespace x86 {
|
||||
|
||||
/// Unlike source, describes only registers, and breaks
|
||||
/// them down by conventional name — so AL, AH, AX and EAX are all
|
||||
/// listed separately and uniquely, rather than being eAX+size or
|
||||
/// eSPorAH with a size of 1.
|
||||
enum class Register: uint8_t {
|
||||
// 8-bit registers.
|
||||
AL, AH,
|
||||
CL, CH,
|
||||
DL, DH,
|
||||
BL, BH,
|
||||
|
||||
// 16-bit registers.
|
||||
AX, CX, DX, BX,
|
||||
SP, BP, SI, DI,
|
||||
ES, CS, SS, DS,
|
||||
FS, GS,
|
||||
|
||||
// 32-bit registers.
|
||||
EAX, ECX, EDX, EBX,
|
||||
ESP, EBP, ESI, EDI,
|
||||
|
||||
//
|
||||
None
|
||||
};
|
||||
|
||||
/// @returns @c true if @c r is the same size as @c DataT; @c false otherwise.
|
||||
/// @discussion Provided primarily to aid in asserts; if the decoder and resolver are both
|
||||
/// working then it shouldn't be necessary to test this in register files.
|
||||
template <typename DataT> constexpr bool is_sized(Register r) {
|
||||
static_assert(sizeof(DataT) == 4 || sizeof(DataT) == 2 || sizeof(DataT) == 1);
|
||||
|
||||
if constexpr (sizeof(DataT) == 4) {
|
||||
return r >= Register::EAX && r < Register::None;
|
||||
}
|
||||
|
||||
if constexpr (sizeof(DataT) == 2) {
|
||||
return r >= Register::AX && r < Register::EAX;
|
||||
}
|
||||
|
||||
if constexpr (sizeof(DataT) == 1) {
|
||||
return r >= Register::AL && r < Register::AX;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// @returns the proper @c Register given @c source and data of size @c sizeof(DataT),
|
||||
/// or Register::None if no such register exists (e.g. asking for a 32-bit version of CS).
|
||||
template <typename DataT> constexpr Register register_for_source(Source source) {
|
||||
static_assert(sizeof(DataT) == 4 || sizeof(DataT) == 2 || sizeof(DataT) == 1);
|
||||
|
||||
if constexpr (sizeof(DataT) == 4) {
|
||||
switch(source) {
|
||||
case Source::eAX: return Register::EAX;
|
||||
case Source::eCX: return Register::ECX;
|
||||
case Source::eDX: return Register::EDX;
|
||||
case Source::eBX: return Register::EBX;
|
||||
case Source::eSPorAH: return Register::ESP;
|
||||
case Source::eBPorCH: return Register::EBP;
|
||||
case Source::eSIorDH: return Register::ESI;
|
||||
case Source::eDIorBH: return Register::EDI;
|
||||
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
|
||||
if constexpr (sizeof(DataT) == 2) {
|
||||
switch(source) {
|
||||
case Source::eAX: return Register::AX;
|
||||
case Source::eCX: return Register::CX;
|
||||
case Source::eDX: return Register::DX;
|
||||
case Source::eBX: return Register::BX;
|
||||
case Source::eSPorAH: return Register::SP;
|
||||
case Source::eBPorCH: return Register::BP;
|
||||
case Source::eSIorDH: return Register::SI;
|
||||
case Source::eDIorBH: return Register::DI;
|
||||
case Source::ES: return Register::ES;
|
||||
case Source::CS: return Register::CS;
|
||||
case Source::SS: return Register::SS;
|
||||
case Source::DS: return Register::DS;
|
||||
case Source::FS: return Register::FS;
|
||||
case Source::GS: return Register::GS;
|
||||
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
|
||||
if constexpr (sizeof(DataT) == 1) {
|
||||
switch(source) {
|
||||
case Source::eAX: return Register::AL;
|
||||
case Source::eCX: return Register::CL;
|
||||
case Source::eDX: return Register::DL;
|
||||
case Source::eBX: return Register::BL;
|
||||
case Source::eSPorAH: return Register::AH;
|
||||
case Source::eBPorCH: return Register::CH;
|
||||
case Source::eSIorDH: return Register::DH;
|
||||
case Source::eDIorBH: return Register::BH;
|
||||
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
|
||||
return Register::None;
|
||||
}
|
||||
|
||||
/// Reads from or writes to the source or target identified by a DataPointer, relying upon two user-supplied classes:
|
||||
///
|
||||
/// * a register bank; and
|
||||
/// * a memory pool.
|
||||
///
|
||||
/// The register bank should implement `template<typename DataT, Register> DataT read()` and `template<typename DataT, Register> void write(DataT)`.
|
||||
/// Those functions will be called only with registers and data types that are appropriate to the @c model.
|
||||
///
|
||||
/// The memory pool should implement `template<typename DataT> DataT read(Source segment, uint32_t address)` and
|
||||
/// `template<typename DataT> void write(Source segment, uint32_t address, DataT value)`.
|
||||
template <Model model, typename RegistersT, typename MemoryT> class DataPointerResolver {
|
||||
public:
|
||||
public:
|
||||
/// Reads the data pointed to by @c pointer, referencing @c instruction, @c memory and @c registers as necessary.
|
||||
template <typename DataT> static DataT read(
|
||||
RegistersT ®isters,
|
||||
MemoryT &memory,
|
||||
const Instruction<is_32bit(model)> &instruction,
|
||||
DataPointer pointer);
|
||||
|
||||
/// Writes @c value to the data pointed to by @c pointer, referencing @c instruction, @c memory and @c registers as necessary.
|
||||
template <typename DataT> static void write(
|
||||
RegistersT ®isters,
|
||||
MemoryT &memory,
|
||||
const Instruction<is_32bit(model)> &instruction,
|
||||
DataPointer pointer,
|
||||
DataT value);
|
||||
|
||||
/// Computes the effective address of @c pointer including any displacement applied by @c instruction.
|
||||
/// @c pointer must be of type Source::Indirect.
|
||||
template <bool obscured_indirectNoBase = true, bool has_base = true>
|
||||
static uint32_t effective_address(
|
||||
RegistersT ®isters,
|
||||
const Instruction<is_32bit(model)> &instruction,
|
||||
DataPointer pointer);
|
||||
|
||||
private:
|
||||
template <bool is_write, typename DataT> static void access(
|
||||
RegistersT ®isters,
|
||||
MemoryT &memory,
|
||||
const Instruction<is_32bit(model)> &instruction,
|
||||
DataPointer pointer,
|
||||
DataT &value);
|
||||
};
|
||||
|
||||
|
||||
//
|
||||
// Implementation begins here.
|
||||
//
|
||||
|
||||
template <Model model, typename RegistersT, typename MemoryT>
|
||||
template <typename DataT> DataT DataPointerResolver<model, RegistersT, MemoryT>::read(
|
||||
RegistersT ®isters,
|
||||
MemoryT &memory,
|
||||
const Instruction<is_32bit(model)> &instruction,
|
||||
DataPointer pointer) {
|
||||
DataT result;
|
||||
access<false>(registers, memory, instruction, pointer, result);
|
||||
return result;
|
||||
}
|
||||
|
||||
template <Model model, typename RegistersT, typename MemoryT>
|
||||
template <typename DataT> void DataPointerResolver<model, RegistersT, MemoryT>::write(
|
||||
RegistersT ®isters,
|
||||
MemoryT &memory,
|
||||
const Instruction<is_32bit(model)> &instruction,
|
||||
DataPointer pointer,
|
||||
DataT value) {
|
||||
access<true>(registers, memory, instruction, pointer, value);
|
||||
}
|
||||
|
||||
#define rw(v, r, is_write) \
|
||||
case Source::r: \
|
||||
using VType = typename std::remove_reference<decltype(v)>::type; \
|
||||
if constexpr (is_write) { \
|
||||
registers.template write<VType, register_for_source<VType>(Source::r)>(v); \
|
||||
} else { \
|
||||
v = registers.template read<VType, register_for_source<VType>(Source::r)>(); \
|
||||
} \
|
||||
break;
|
||||
|
||||
#define ALLREGS(v, i) rw(v, eAX, i); rw(v, eCX, i); \
|
||||
rw(v, eDX, i); rw(v, eBX, i); \
|
||||
rw(v, eSPorAH, i); rw(v, eBPorCH, i); \
|
||||
rw(v, eSIorDH, i); rw(v, eDIorBH, i); \
|
||||
rw(v, ES, i); rw(v, CS, i); \
|
||||
rw(v, SS, i); rw(v, DS, i); \
|
||||
rw(v, FS, i); rw(v, GS, i);
|
||||
|
||||
template <Model model, typename RegistersT, typename MemoryT>
|
||||
template <bool obscured_indirectNoBase, bool has_base>
|
||||
uint32_t DataPointerResolver<model, RegistersT, MemoryT>::effective_address(
|
||||
RegistersT ®isters,
|
||||
const Instruction<is_32bit(model)> &instruction,
|
||||
DataPointer pointer) {
|
||||
using AddressT = typename Instruction<is_32bit(model)>::AddressT;
|
||||
AddressT base = 0, index = 0;
|
||||
|
||||
if constexpr (has_base) {
|
||||
switch(pointer.base<obscured_indirectNoBase>()) {
|
||||
default: break;
|
||||
ALLREGS(base, false);
|
||||
}
|
||||
}
|
||||
|
||||
switch(pointer.index()) {
|
||||
default: break;
|
||||
ALLREGS(index, false);
|
||||
}
|
||||
|
||||
uint32_t address = index;
|
||||
if constexpr (model >= Model::i80386) {
|
||||
address <<= pointer.scale();
|
||||
} else {
|
||||
assert(!pointer.scale());
|
||||
}
|
||||
|
||||
// Always compute address as 32-bit.
|
||||
// TODO: verify use of memory_mask around here.
|
||||
// Also I think possibly an exception is supposed to be generated
|
||||
// if the programmer is in 32-bit mode and has asked for 16-bit
|
||||
// address computation but generated e.g. a 17-bit result. Look into
|
||||
// that when working on execution. For now the goal is merely decoding
|
||||
// and this code exists both to verify the presence of all necessary
|
||||
// fields and to help to explore the best breakdown of storage
|
||||
// within Instruction.
|
||||
constexpr uint32_t memory_masks[] = {0x0000'ffff, 0xffff'ffff};
|
||||
const uint32_t memory_mask = memory_masks[int(instruction.address_size())];
|
||||
address = (address & memory_mask) + (base & memory_mask) + instruction.displacement();
|
||||
return address;
|
||||
}
|
||||
|
||||
template <Model model, typename RegistersT, typename MemoryT>
|
||||
template <bool is_write, typename DataT> void DataPointerResolver<model, RegistersT, MemoryT>::access(
|
||||
RegistersT ®isters,
|
||||
MemoryT &memory,
|
||||
const Instruction<is_32bit(model)> &instruction,
|
||||
DataPointer pointer,
|
||||
DataT &value) {
|
||||
const Source source = pointer.source<false>();
|
||||
|
||||
switch(source) {
|
||||
default:
|
||||
if constexpr (!is_write) {
|
||||
value = 0;
|
||||
}
|
||||
return;
|
||||
|
||||
ALLREGS(value, is_write);
|
||||
|
||||
case Source::DirectAddress:
|
||||
if constexpr(is_write) {
|
||||
memory.template write(instruction.data_segment(), instruction.displacement(), value);
|
||||
} else {
|
||||
value = memory.template read<DataT>(instruction.data_segment(), instruction.displacement());
|
||||
}
|
||||
break;
|
||||
case Source::Immediate:
|
||||
value = DataT(instruction.operand());
|
||||
break;
|
||||
|
||||
#define indirect(has_base) { \
|
||||
const auto address = effective_address<false, has_base> \
|
||||
(registers, instruction, pointer); \
|
||||
\
|
||||
if constexpr (is_write) { \
|
||||
memory.template write( \
|
||||
instruction.data_segment(), \
|
||||
address, \
|
||||
value \
|
||||
); \
|
||||
} else { \
|
||||
value = memory.template read<DataT>( \
|
||||
instruction.data_segment(), \
|
||||
address \
|
||||
); \
|
||||
} \
|
||||
}
|
||||
case Source::IndirectNoBase:
|
||||
indirect(false);
|
||||
break;
|
||||
|
||||
case Source::Indirect:
|
||||
indirect(true);
|
||||
break;
|
||||
#undef indirect
|
||||
|
||||
}
|
||||
}
|
||||
#undef ALLREGS
|
||||
#undef rw
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* DataPointerResolver_hpp */
|
||||
981
InstructionSets/x86/Decoder.cpp
Normal file
981
InstructionSets/x86/Decoder.cpp
Normal file
@@ -0,0 +1,981 @@
|
||||
//
|
||||
// x86.cpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 01/01/21.
|
||||
// Copyright © 2021 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#include "Decoder.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cassert>
|
||||
#include <utility>
|
||||
|
||||
using namespace InstructionSet::x86;
|
||||
|
||||
template <Model model>
|
||||
std::pair<int, typename Decoder<model>::InstructionT> Decoder<model>::decode(const uint8_t *source, size_t length) {
|
||||
// Instruction length limits:
|
||||
//
|
||||
// 8086/80186: none*
|
||||
// 80286: 10 bytes
|
||||
// 80386: 15 bytes
|
||||
//
|
||||
// * but, can treat internally as a limit of 65536 bytes — after that distance the IP will
|
||||
// be back to wherever it started, so it's safe to spit out a NOP and reset parsing
|
||||
// without any loss of context. This reduces the risk of the decoder tricking a caller into
|
||||
// an infinite loop.
|
||||
constexpr int max_instruction_length = model >= Model::i80386 ? 15 : (model == Model::i80286 ? 10 : 65536);
|
||||
const uint8_t *const end = source + std::min(length, size_t(max_instruction_length - consumed_));
|
||||
|
||||
// MARK: - Prefixes (if present) and the opcode.
|
||||
|
||||
/// Helper macro for those that follow.
|
||||
#define SetOpSrcDestSize(op, src, dest, size) \
|
||||
operation_ = Operation::op; \
|
||||
source_ = Source::src; \
|
||||
destination_ = Source::dest; \
|
||||
operation_size_ = size
|
||||
|
||||
/// Covers anything which is complete as soon as the opcode is encountered.
|
||||
#define Complete(op, src, dest, size) \
|
||||
SetOpSrcDestSize(op, src, dest, size); \
|
||||
phase_ = Phase::ReadyToPost
|
||||
|
||||
/// Handles instructions of the form rr, kk and rr, jjkk, i.e. a destination register plus an operand.
|
||||
#define RegData(op, dest, size) \
|
||||
SetOpSrcDestSize(op, DirectAddress, dest, size); \
|
||||
source_ = Source::Immediate; \
|
||||
operand_size_ = size; \
|
||||
phase_ = Phase::DisplacementOrOperand
|
||||
|
||||
/// Handles instructions of the form Ax, jjkk where the latter is implicitly an address.
|
||||
#define RegAddr(op, dest, op_size, addr_size) \
|
||||
SetOpSrcDestSize(op, DirectAddress, dest, op_size); \
|
||||
operand_size_ = addr_size; \
|
||||
phase_ = Phase::DisplacementOrOperand
|
||||
|
||||
/// Handles instructions of the form jjkk, Ax where the former is implicitly an address.
|
||||
#define AddrReg(op, source, op_size, addr_size) \
|
||||
SetOpSrcDestSize(op, source, DirectAddress, op_size); \
|
||||
operand_size_ = addr_size; \
|
||||
destination_ = Source::DirectAddress; \
|
||||
phase_ = Phase::DisplacementOrOperand
|
||||
|
||||
/// Covers both `mem/reg, reg` and `reg, mem/reg`.
|
||||
#define MemRegReg(op, format, size) \
|
||||
operation_ = Operation::op; \
|
||||
phase_ = Phase::ModRegRM; \
|
||||
modregrm_format_ = ModRegRMFormat::format; \
|
||||
operand_size_ = DataSize::None; \
|
||||
operation_size_ = size
|
||||
|
||||
/// Handles JO, JNO, JB, etc — anything with only a displacement.
|
||||
#define Displacement(op, size) \
|
||||
operation_ = Operation::op; \
|
||||
phase_ = Phase::DisplacementOrOperand; \
|
||||
displacement_size_ = size
|
||||
|
||||
/// Handles PUSH [immediate], etc — anything with only an immediate operand.
|
||||
#define Immediate(op, size) \
|
||||
operation_ = Operation::op; \
|
||||
source_ = Source::Immediate; \
|
||||
phase_ = Phase::DisplacementOrOperand; \
|
||||
operand_size_ = size
|
||||
|
||||
/// Handles far CALL and far JMP — fixed four or six byte operand operations.
|
||||
#define Far(op) \
|
||||
operation_ = Operation::op; \
|
||||
phase_ = Phase::DisplacementOrOperand; \
|
||||
operand_size_ = DataSize::Word; \
|
||||
displacement_size_ = data_size(default_address_size_)
|
||||
|
||||
/// Handles ENTER — a fixed three-byte operation.
|
||||
#define Displacement16Operand8(op) \
|
||||
operation_ = Operation::op; \
|
||||
phase_ = Phase::DisplacementOrOperand; \
|
||||
displacement_size_ = DataSize::Word; \
|
||||
operand_size_ = DataSize::Byte
|
||||
|
||||
/// Sets up the operation size, oncoming phase and modregrm format for a member of the shift group (i.e. 'group 2').
|
||||
#define ShiftGroup() { \
|
||||
const DataSize sizes[] = {DataSize::Byte, data_size_}; \
|
||||
phase_ = Phase::ModRegRM; \
|
||||
modregrm_format_ = ModRegRMFormat::MemRegROL_to_SAR; \
|
||||
operation_size_ = sizes[instr & 1]; \
|
||||
}
|
||||
|
||||
#define undefined() { \
|
||||
const auto result = std::make_pair(consumed_, InstructionT()); \
|
||||
reset_parsing(); \
|
||||
return result; \
|
||||
}
|
||||
|
||||
#define Requires(x) if constexpr (model != Model::x) undefined();
|
||||
#define RequiresMin(x) if constexpr (model < Model::x) undefined();
|
||||
|
||||
while(phase_ == Phase::Instruction && source != end) {
|
||||
const uint8_t instr = *source;
|
||||
++source;
|
||||
++consumed_;
|
||||
|
||||
switch(instr) {
|
||||
default: undefined();
|
||||
|
||||
#define PartialBlock(start, operation) \
|
||||
case start + 0x00: MemRegReg(operation, MemReg_Reg, DataSize::Byte); break; \
|
||||
case start + 0x01: MemRegReg(operation, MemReg_Reg, data_size_); break; \
|
||||
case start + 0x02: MemRegReg(operation, Reg_MemReg, DataSize::Byte); break; \
|
||||
case start + 0x03: MemRegReg(operation, Reg_MemReg, data_size_); break; \
|
||||
case start + 0x04: RegData(operation, eAX, DataSize::Byte); break; \
|
||||
case start + 0x05: RegData(operation, eAX, data_size_)
|
||||
|
||||
PartialBlock(0x00, ADD); break;
|
||||
case 0x06: Complete(PUSH, ES, None, data_size_); break;
|
||||
case 0x07: Complete(POP, None, ES, data_size_); break;
|
||||
|
||||
PartialBlock(0x08, OR); break;
|
||||
case 0x0e: Complete(PUSH, CS, None, data_size_); break;
|
||||
|
||||
// The 286 onwards have a further set of instructions
|
||||
// prefixed with $0f.
|
||||
case 0x0f:
|
||||
RequiresMin(i80286);
|
||||
phase_ = Phase::InstructionPageF;
|
||||
break;
|
||||
|
||||
PartialBlock(0x10, ADC); break;
|
||||
case 0x16: Complete(PUSH, SS, None, DataSize::Word); break;
|
||||
case 0x17: Complete(POP, None, SS, DataSize::Word); break;
|
||||
|
||||
PartialBlock(0x18, SBB); break;
|
||||
case 0x1e: Complete(PUSH, DS, None, DataSize::Word); break;
|
||||
case 0x1f: Complete(POP, None, DS, DataSize::Word); break;
|
||||
|
||||
PartialBlock(0x20, AND); break;
|
||||
case 0x26: segment_override_ = Source::ES; break;
|
||||
case 0x27: Complete(DAA, eAX, eAX, DataSize::Byte); break;
|
||||
|
||||
PartialBlock(0x28, SUB); break;
|
||||
case 0x2e: segment_override_ = Source::CS; break;
|
||||
case 0x2f: Complete(DAS, eAX, eAX, DataSize::Byte); break;
|
||||
|
||||
PartialBlock(0x30, XOR); break;
|
||||
case 0x36: segment_override_ = Source::SS; break;
|
||||
case 0x37: Complete(AAA, eAX, eAX, DataSize::Word); break;
|
||||
|
||||
PartialBlock(0x38, CMP); break;
|
||||
case 0x3e: segment_override_ = Source::DS; break;
|
||||
case 0x3f: Complete(AAS, eAX, eAX, DataSize::Word); break;
|
||||
|
||||
#undef PartialBlock
|
||||
|
||||
#define RegisterBlock(start, operation) \
|
||||
case start + 0x00: Complete(operation, eAX, eAX, data_size_); break; \
|
||||
case start + 0x01: Complete(operation, eCX, eCX, data_size_); break; \
|
||||
case start + 0x02: Complete(operation, eDX, eDX, data_size_); break; \
|
||||
case start + 0x03: Complete(operation, eBX, eBX, data_size_); break; \
|
||||
case start + 0x04: Complete(operation, eSP, eSP, data_size_); break; \
|
||||
case start + 0x05: Complete(operation, eBP, eBP, data_size_); break; \
|
||||
case start + 0x06: Complete(operation, eSI, eSI, data_size_); break; \
|
||||
case start + 0x07: Complete(operation, eDI, eDI, data_size_)
|
||||
|
||||
RegisterBlock(0x40, INC); break;
|
||||
RegisterBlock(0x48, DEC); break;
|
||||
RegisterBlock(0x50, PUSH); break;
|
||||
RegisterBlock(0x58, POP); break;
|
||||
|
||||
#undef RegisterBlock
|
||||
|
||||
case 0x60:
|
||||
RequiresMin(i80186);
|
||||
Complete(PUSHA, None, None, data_size_);
|
||||
break;
|
||||
case 0x61:
|
||||
RequiresMin(i80186);
|
||||
Complete(POPA, None, None, data_size_);
|
||||
break;
|
||||
case 0x62:
|
||||
RequiresMin(i80186);
|
||||
MemRegReg(BOUND, Reg_MemReg, data_size_);
|
||||
break;
|
||||
case 0x63:
|
||||
RequiresMin(i80286);
|
||||
MemRegReg(ARPL, MemReg_Reg, DataSize::Word);
|
||||
break;
|
||||
case 0x64:
|
||||
RequiresMin(i80386);
|
||||
segment_override_ = Source::FS;
|
||||
break;
|
||||
case 0x65:
|
||||
RequiresMin(i80386);
|
||||
segment_override_ = Source::GS;
|
||||
break;
|
||||
case 0x66:
|
||||
RequiresMin(i80386);
|
||||
data_size_ = DataSize(int(default_data_size_) ^ int(DataSize::Word) ^ int(DataSize::DWord));
|
||||
break;
|
||||
case 0x67:
|
||||
RequiresMin(i80386);
|
||||
address_size_ = AddressSize(int(default_address_size_) ^ int(AddressSize::b16) ^ int(AddressSize::b32));
|
||||
break;
|
||||
case 0x68:
|
||||
RequiresMin(i80286);
|
||||
Immediate(PUSH, data_size_);
|
||||
operation_size_ = data_size_;
|
||||
break;
|
||||
case 0x69:
|
||||
RequiresMin(i80286);
|
||||
MemRegReg(IMUL_3, Reg_MemReg, data_size_);
|
||||
operand_size_ = data_size_;
|
||||
break;
|
||||
case 0x6a:
|
||||
RequiresMin(i80286);
|
||||
Immediate(PUSH, DataSize::Byte);
|
||||
break;
|
||||
case 0x6b:
|
||||
RequiresMin(i80286);
|
||||
MemRegReg(IMUL_3, Reg_MemReg, data_size_);
|
||||
operand_size_ = DataSize::Byte;
|
||||
sign_extend_ = true;
|
||||
break;
|
||||
case 0x6c: // INSB
|
||||
RequiresMin(i80186);
|
||||
Complete(INS, None, None, DataSize::Byte);
|
||||
break;
|
||||
case 0x6d: // INSW/INSD
|
||||
RequiresMin(i80186);
|
||||
Complete(INS, None, None, data_size_);
|
||||
break;
|
||||
case 0x6e: // OUTSB
|
||||
RequiresMin(i80186);
|
||||
Complete(OUTS, None, None, DataSize::Byte);
|
||||
break;
|
||||
case 0x6f: // OUTSW/OUSD
|
||||
RequiresMin(i80186);
|
||||
Complete(OUTS, None, None, data_size_);
|
||||
break;
|
||||
|
||||
case 0x70: Displacement(JO, DataSize::Byte); break;
|
||||
case 0x71: Displacement(JNO, DataSize::Byte); break;
|
||||
case 0x72: Displacement(JB, DataSize::Byte); break;
|
||||
case 0x73: Displacement(JNB, DataSize::Byte); break;
|
||||
case 0x74: Displacement(JE, DataSize::Byte); break;
|
||||
case 0x75: Displacement(JNE, DataSize::Byte); break;
|
||||
case 0x76: Displacement(JBE, DataSize::Byte); break;
|
||||
case 0x77: Displacement(JNBE, DataSize::Byte); break;
|
||||
case 0x78: Displacement(JS, DataSize::Byte); break;
|
||||
case 0x79: Displacement(JNS, DataSize::Byte); break;
|
||||
case 0x7a: Displacement(JP, DataSize::Byte); break;
|
||||
case 0x7b: Displacement(JNP, DataSize::Byte); break;
|
||||
case 0x7c: Displacement(JL, DataSize::Byte); break;
|
||||
case 0x7d: Displacement(JNL, DataSize::Byte); break;
|
||||
case 0x7e: Displacement(JLE, DataSize::Byte); break;
|
||||
case 0x7f: Displacement(JNLE, DataSize::Byte); break;
|
||||
|
||||
case 0x80: MemRegReg(Invalid, MemRegADD_to_CMP, DataSize::Byte); break;
|
||||
case 0x81: MemRegReg(Invalid, MemRegADD_to_CMP, data_size_); break;
|
||||
case 0x82: MemRegReg(Invalid, MemRegADD_to_CMP_SignExtend, DataSize::Byte); break;
|
||||
case 0x83: MemRegReg(Invalid, MemRegADD_to_CMP_SignExtend, data_size_); break;
|
||||
|
||||
case 0x84: MemRegReg(TEST, MemReg_Reg, DataSize::Byte); break;
|
||||
case 0x85: MemRegReg(TEST, MemReg_Reg, data_size_); break;
|
||||
case 0x86: MemRegReg(XCHG, Reg_MemReg, DataSize::Byte); break;
|
||||
case 0x87: MemRegReg(XCHG, Reg_MemReg, data_size_); break;
|
||||
case 0x88: MemRegReg(MOV, MemReg_Reg, DataSize::Byte); break;
|
||||
case 0x89: MemRegReg(MOV, MemReg_Reg, data_size_); break;
|
||||
case 0x8a: MemRegReg(MOV, Reg_MemReg, DataSize::Byte); break;
|
||||
case 0x8b: MemRegReg(MOV, Reg_MemReg, data_size_); break;
|
||||
case 0x8c: MemRegReg(MOV, MemReg_Seg, DataSize::Word); break;
|
||||
case 0x8d: MemRegReg(LEA, Reg_MemReg, data_size_); break;
|
||||
case 0x8e: MemRegReg(MOV, Seg_MemReg, DataSize::Word); break;
|
||||
case 0x8f: MemRegReg(POP, MemRegSingleOperand, data_size_); break;
|
||||
|
||||
case 0x90: Complete(NOP, None, None, DataSize::None); break; // Or XCHG AX, AX?
|
||||
case 0x91: Complete(XCHG, eAX, eCX, data_size_); break;
|
||||
case 0x92: Complete(XCHG, eAX, eDX, data_size_); break;
|
||||
case 0x93: Complete(XCHG, eAX, eBX, data_size_); break;
|
||||
case 0x94: Complete(XCHG, eAX, eSP, data_size_); break;
|
||||
case 0x95: Complete(XCHG, eAX, eBP, data_size_); break;
|
||||
case 0x96: Complete(XCHG, eAX, eSI, data_size_); break;
|
||||
case 0x97: Complete(XCHG, eAX, eDI, data_size_); break;
|
||||
|
||||
case 0x98: Complete(CBW, eAX, AH, data_size_); break;
|
||||
case 0x99: Complete(CWD, eAX, eDX, data_size_); break;
|
||||
case 0x9a: Far(CALLfar); break;
|
||||
case 0x9b: Complete(WAIT, None, None, DataSize::None); break;
|
||||
case 0x9c: Complete(PUSHF, None, None, data_size_); break;
|
||||
case 0x9d: Complete(POPF, None, None, data_size_); break;
|
||||
case 0x9e: Complete(SAHF, None, None, DataSize::Byte); break;
|
||||
case 0x9f: Complete(LAHF, None, None, DataSize::Byte); break;
|
||||
|
||||
case 0xa0: RegAddr(MOV, eAX, DataSize::Byte, data_size(address_size_)); break;
|
||||
case 0xa1: RegAddr(MOV, eAX, data_size_, data_size(address_size_)); break;
|
||||
case 0xa2: AddrReg(MOV, eAX, DataSize::Byte, data_size(address_size_)); break;
|
||||
case 0xa3: AddrReg(MOV, eAX, data_size_, data_size(address_size_)); break;
|
||||
|
||||
case 0xa4: Complete(MOVS, None, None, DataSize::Byte); break;
|
||||
case 0xa5: Complete(MOVS, None, None, data_size_); break;
|
||||
case 0xa6: Complete(CMPS, None, None, DataSize::Byte); break;
|
||||
case 0xa7: Complete(CMPS, None, None, data_size_); break;
|
||||
case 0xa8: RegData(TEST, eAX, DataSize::Byte); break;
|
||||
case 0xa9: RegData(TEST, eAX, data_size_); break;
|
||||
case 0xaa: Complete(STOS, None, None, DataSize::Byte); break;
|
||||
case 0xab: Complete(STOS, None, None, data_size_); break;
|
||||
case 0xac: Complete(LODS, None, None, DataSize::Byte); break;
|
||||
case 0xad: Complete(LODS, None, None, data_size_); break;
|
||||
case 0xae: Complete(SCAS, None, None, DataSize::Byte); break;
|
||||
case 0xaf: Complete(SCAS, None, None, data_size_); break;
|
||||
|
||||
case 0xb0: RegData(MOV, eAX, DataSize::Byte); break;
|
||||
case 0xb1: RegData(MOV, eCX, DataSize::Byte); break;
|
||||
case 0xb2: RegData(MOV, eDX, DataSize::Byte); break;
|
||||
case 0xb3: RegData(MOV, eBX, DataSize::Byte); break;
|
||||
case 0xb4: RegData(MOV, AH, DataSize::Byte); break;
|
||||
case 0xb5: RegData(MOV, CH, DataSize::Byte); break;
|
||||
case 0xb6: RegData(MOV, DH, DataSize::Byte); break;
|
||||
case 0xb7: RegData(MOV, BH, DataSize::Byte); break;
|
||||
case 0xb8: RegData(MOV, eAX, data_size_); break;
|
||||
case 0xb9: RegData(MOV, eCX, data_size_); break;
|
||||
case 0xba: RegData(MOV, eDX, data_size_); break;
|
||||
case 0xbb: RegData(MOV, eBX, data_size_); break;
|
||||
case 0xbc: RegData(MOV, eSP, data_size_); break;
|
||||
case 0xbd: RegData(MOV, eBP, data_size_); break;
|
||||
case 0xbe: RegData(MOV, eSI, data_size_); break;
|
||||
case 0xbf: RegData(MOV, eDI, data_size_); break;
|
||||
|
||||
case 0xc0: case 0xc1:
|
||||
RequiresMin(i80186);
|
||||
ShiftGroup();
|
||||
source_ = Source::Immediate;
|
||||
operand_size_ = DataSize::Byte;
|
||||
break;
|
||||
case 0xc2: RegData(RETnear, None, data_size_); break;
|
||||
case 0xc3: Complete(RETnear, None, None, DataSize::None); break;
|
||||
case 0xc4: MemRegReg(LES, Reg_MemReg, data_size_); break;
|
||||
case 0xc5: MemRegReg(LDS, Reg_MemReg, data_size_); break;
|
||||
case 0xc6: MemRegReg(MOV, MemRegMOV, DataSize::Byte); break;
|
||||
case 0xc7: MemRegReg(MOV, MemRegMOV, data_size_); break;
|
||||
|
||||
case 0xc8:
|
||||
RequiresMin(i80186);
|
||||
Displacement16Operand8(ENTER);
|
||||
break;
|
||||
case 0xc9:
|
||||
RequiresMin(i80186);
|
||||
Complete(LEAVE, None, None, DataSize::None);
|
||||
break;
|
||||
|
||||
case 0xca: RegData(RETfar, None, data_size_); break;
|
||||
case 0xcb: Complete(RETfar, None, None, DataSize::DWord); break;
|
||||
|
||||
case 0xcc:
|
||||
// Encode INT3 as though it were INT with an
|
||||
// immediate operand of 3.
|
||||
Complete(INT, Immediate, None, DataSize::Byte);
|
||||
operand_ = 3;
|
||||
break;
|
||||
case 0xcd: RegData(INT, None, DataSize::Byte); break;
|
||||
case 0xce: Complete(INTO, None, None, DataSize::None); break;
|
||||
case 0xcf: Complete(IRET, None, None, DataSize::None); break;
|
||||
|
||||
case 0xd0: case 0xd1:
|
||||
ShiftGroup();
|
||||
source_ = Source::Immediate;
|
||||
operand_ = 1;
|
||||
break;
|
||||
case 0xd2: case 0xd3:
|
||||
ShiftGroup();
|
||||
source_ = Source::eCX;
|
||||
break;
|
||||
case 0xd4: RegData(AAM, eAX, DataSize::Byte); break;
|
||||
case 0xd5: RegData(AAD, eAX, DataSize::Byte); break;
|
||||
// Unused: 0xd6.
|
||||
case 0xd7: Complete(XLAT, None, None, DataSize::Byte); break;
|
||||
|
||||
case 0xd8: MemRegReg(ESC, MemReg_Reg, DataSize::None); break;
|
||||
case 0xd9: MemRegReg(ESC, MemReg_Reg, DataSize::None); break;
|
||||
case 0xda: MemRegReg(ESC, MemReg_Reg, DataSize::None); break;
|
||||
case 0xdb: MemRegReg(ESC, MemReg_Reg, DataSize::None); break;
|
||||
case 0xdc: MemRegReg(ESC, MemReg_Reg, DataSize::None); break;
|
||||
case 0xdd: MemRegReg(ESC, MemReg_Reg, DataSize::None); break;
|
||||
case 0xde: MemRegReg(ESC, MemReg_Reg, DataSize::None); break;
|
||||
case 0xdf: MemRegReg(ESC, MemReg_Reg, DataSize::None); break;
|
||||
|
||||
case 0xe0: Displacement(LOOPNE, DataSize::Byte); break;
|
||||
case 0xe1: Displacement(LOOPE, DataSize::Byte); break;
|
||||
case 0xe2: Displacement(LOOP, DataSize::Byte); break;
|
||||
case 0xe3: Displacement(JPCX, DataSize::Byte); break;
|
||||
|
||||
case 0xe4: RegAddr(IN, eAX, DataSize::Byte, DataSize::Byte); break;
|
||||
case 0xe5: RegAddr(IN, eAX, data_size_, DataSize::Byte); break;
|
||||
case 0xe6: AddrReg(OUT, eAX, DataSize::Byte, DataSize::Byte); break;
|
||||
case 0xe7: AddrReg(OUT, eAX, data_size_, DataSize::Byte); break;
|
||||
|
||||
case 0xe8: Displacement(CALLrel, data_size_); break;
|
||||
case 0xe9: Displacement(JMPrel, data_size_); break;
|
||||
case 0xea: Far(JMPfar); break;
|
||||
case 0xeb: Displacement(JMPrel, DataSize::Byte); break;
|
||||
|
||||
case 0xec: Complete(IN, eDX, eAX, DataSize::Byte); break;
|
||||
case 0xed: Complete(IN, eDX, eAX, data_size_); break;
|
||||
case 0xee: Complete(OUT, eAX, eDX, DataSize::Byte); break;
|
||||
case 0xef: Complete(OUT, eAX, eDX, data_size_); break;
|
||||
|
||||
case 0xf0: lock_ = true; break;
|
||||
// Unused: 0xf1
|
||||
case 0xf2: repetition_ = Repetition::RepNE; break;
|
||||
case 0xf3: repetition_ = Repetition::RepE; break;
|
||||
|
||||
case 0xf4: Complete(HLT, None, None, DataSize::None); break;
|
||||
case 0xf5: Complete(CMC, None, None, DataSize::None); break;
|
||||
case 0xf6: MemRegReg(Invalid, MemRegTEST_to_IDIV, DataSize::Byte); break;
|
||||
case 0xf7: MemRegReg(Invalid, MemRegTEST_to_IDIV, data_size_); break;
|
||||
|
||||
case 0xf8: Complete(CLC, None, None, DataSize::None); break;
|
||||
case 0xf9: Complete(STC, None, None, DataSize::None); break;
|
||||
case 0xfa: Complete(CLI, None, None, DataSize::None); break;
|
||||
case 0xfb: Complete(STI, None, None, DataSize::None); break;
|
||||
case 0xfc: Complete(CLD, None, None, DataSize::None); break;
|
||||
case 0xfd: Complete(STD, None, None, DataSize::None); break;
|
||||
|
||||
case 0xfe: MemRegReg(Invalid, MemRegINC_DEC, DataSize::Byte); break;
|
||||
case 0xff: MemRegReg(Invalid, MemRegINC_to_PUSH, data_size_); break;
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Additional F page of instructions.
|
||||
|
||||
if(phase_ == Phase::InstructionPageF && source != end) {
|
||||
// Update the instruction acquired.
|
||||
const uint8_t instr = *source;
|
||||
++source;
|
||||
++consumed_;
|
||||
|
||||
// NB: to reach here, the instruction set must be at least
|
||||
// that of an 80286.
|
||||
switch(instr) {
|
||||
default: undefined();
|
||||
|
||||
case 0x00: MemRegReg(Invalid, MemRegSLDT_to_VERW, data_size_); break;
|
||||
case 0x01: MemRegReg(Invalid, MemRegSGDT_to_LMSW, data_size_); break;
|
||||
case 0x02: MemRegReg(LAR, Reg_MemReg, data_size_); break;
|
||||
case 0x03: MemRegReg(LSL, Reg_MemReg, data_size_); break;
|
||||
case 0x05:
|
||||
Requires(i80286);
|
||||
Complete(LOADALL, None, None, DataSize::None);
|
||||
break;
|
||||
case 0x06: Complete(CLTS, None, None, DataSize::Byte); break;
|
||||
|
||||
case 0x20:
|
||||
RequiresMin(i80386);
|
||||
MemRegReg(MOVfromCr, Reg_MemReg, DataSize::DWord);
|
||||
break;
|
||||
case 0x21:
|
||||
RequiresMin(i80386);
|
||||
MemRegReg(MOVfromDr, Reg_MemReg, DataSize::DWord);
|
||||
break;
|
||||
case 0x22:
|
||||
RequiresMin(i80386);
|
||||
MemRegReg(MOVtoCr, Reg_MemReg, DataSize::DWord);
|
||||
break;
|
||||
case 0x23:
|
||||
RequiresMin(i80386);
|
||||
MemRegReg(MOVtoDr, Reg_MemReg, DataSize::DWord);
|
||||
break;
|
||||
case 0x24:
|
||||
RequiresMin(i80386);
|
||||
MemRegReg(MOVfromTr, Reg_MemReg, DataSize::DWord);
|
||||
break;
|
||||
case 0x26:
|
||||
RequiresMin(i80386);
|
||||
MemRegReg(MOVtoTr, Reg_MemReg, DataSize::DWord);
|
||||
break;
|
||||
|
||||
case 0x70: RequiresMin(i80386); Displacement(JO, data_size_); break;
|
||||
case 0x71: RequiresMin(i80386); Displacement(JNO, data_size_); break;
|
||||
case 0x72: RequiresMin(i80386); Displacement(JB, data_size_); break;
|
||||
case 0x73: RequiresMin(i80386); Displacement(JNB, data_size_); break;
|
||||
case 0x74: RequiresMin(i80386); Displacement(JE, data_size_); break;
|
||||
case 0x75: RequiresMin(i80386); Displacement(JNE, data_size_); break;
|
||||
case 0x76: RequiresMin(i80386); Displacement(JBE, data_size_); break;
|
||||
case 0x77: RequiresMin(i80386); Displacement(JNBE, data_size_); break;
|
||||
case 0x78: RequiresMin(i80386); Displacement(JS, data_size_); break;
|
||||
case 0x79: RequiresMin(i80386); Displacement(JNS, data_size_); break;
|
||||
case 0x7a: RequiresMin(i80386); Displacement(JP, data_size_); break;
|
||||
case 0x7b: RequiresMin(i80386); Displacement(JNP, data_size_); break;
|
||||
case 0x7c: RequiresMin(i80386); Displacement(JL, data_size_); break;
|
||||
case 0x7d: RequiresMin(i80386); Displacement(JNL, data_size_); break;
|
||||
case 0x7e: RequiresMin(i80386); Displacement(JLE, data_size_); break;
|
||||
case 0x7f: RequiresMin(i80386); Displacement(JNLE, data_size_); break;
|
||||
|
||||
#define Set(x) \
|
||||
RequiresMin(i80386); \
|
||||
MemRegReg(SET##x, MemRegSingleOperand, DataSize::Byte);
|
||||
|
||||
case 0x90: Set(O); break;
|
||||
case 0x91: Set(NO); break;
|
||||
case 0x92: Set(B); break;
|
||||
case 0x93: Set(NB); break;
|
||||
case 0x94: Set(Z); break;
|
||||
case 0x95: Set(NZ); break;
|
||||
case 0x96: Set(BE); break;
|
||||
case 0x97: Set(NBE); break;
|
||||
case 0x98: Set(S); break;
|
||||
case 0x99: Set(NS); break;
|
||||
case 0x9a: Set(P); break;
|
||||
case 0x9b: Set(NP); break;
|
||||
case 0x9c: Set(L); break;
|
||||
case 0x9d: Set(NL); break;
|
||||
case 0x9e: Set(LE); break;
|
||||
case 0x9f: Set(NLE); break;
|
||||
|
||||
#undef Set
|
||||
|
||||
case 0xa0: RequiresMin(i80386); Complete(PUSH, FS, None, data_size_); break;
|
||||
case 0xa1: RequiresMin(i80386); Complete(POP, FS, None, data_size_); break;
|
||||
case 0xa3: RequiresMin(i80386); MemRegReg(BT, MemReg_Reg, data_size_); break;
|
||||
case 0xa4:
|
||||
RequiresMin(i80386);
|
||||
MemRegReg(SHLDimm, Reg_MemReg, data_size_);
|
||||
operand_size_ = DataSize::Byte;
|
||||
break;
|
||||
case 0xa5:
|
||||
RequiresMin(i80386);
|
||||
MemRegReg(SHLDCL, MemReg_Reg, data_size_);
|
||||
break;
|
||||
case 0xa8: RequiresMin(i80386); Complete(PUSH, GS, None, data_size_); break;
|
||||
case 0xa9: RequiresMin(i80386); Complete(POP, GS, None, data_size_); break;
|
||||
case 0xab: RequiresMin(i80386); MemRegReg(BTS, MemReg_Reg, data_size_); break;
|
||||
case 0xac:
|
||||
RequiresMin(i80386);
|
||||
MemRegReg(SHRDimm, Reg_MemReg, data_size_);
|
||||
operand_size_ = DataSize::Byte;
|
||||
break;
|
||||
case 0xad:
|
||||
RequiresMin(i80386);
|
||||
MemRegReg(SHRDCL, MemReg_Reg, data_size_);
|
||||
break;
|
||||
case 0xaf:
|
||||
RequiresMin(i80386);
|
||||
MemRegReg(IMUL_2, Reg_MemReg, data_size_);
|
||||
break;
|
||||
|
||||
case 0xb2: RequiresMin(i80386); MemRegReg(LSS, Reg_MemReg, data_size_); break;
|
||||
case 0xb3: RequiresMin(i80386); MemRegReg(BTR, MemReg_Reg, data_size_); break;
|
||||
case 0xb4: RequiresMin(i80386); MemRegReg(LFS, Reg_MemReg, data_size_); break;
|
||||
case 0xb5: RequiresMin(i80386); MemRegReg(LGS, Reg_MemReg, data_size_); break;
|
||||
case 0xb6:
|
||||
RequiresMin(i80386);
|
||||
MemRegReg(MOVZX, Reg_MemReg, DataSize::Byte);
|
||||
break;
|
||||
case 0xb7:
|
||||
RequiresMin(i80386);
|
||||
MemRegReg(MOVZX, Reg_MemReg, DataSize::Word);
|
||||
break;
|
||||
case 0xba: RequiresMin(i80386); MemRegReg(Invalid, MemRegBT_to_BTC, data_size_); break;
|
||||
case 0xbb: RequiresMin(i80386); MemRegReg(BTC, MemReg_Reg, data_size_); break;
|
||||
case 0xbc: RequiresMin(i80386); MemRegReg(BSF, MemReg_Reg, data_size_); break;
|
||||
case 0xbd: RequiresMin(i80386); MemRegReg(BSR, MemReg_Reg, data_size_); break;
|
||||
case 0xbe:
|
||||
RequiresMin(i80386);
|
||||
MemRegReg(MOVSX, Reg_MemReg, DataSize::Byte);
|
||||
break;
|
||||
case 0xbf:
|
||||
RequiresMin(i80386);
|
||||
MemRegReg(MOVSX, Reg_MemReg, DataSize::Word);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
#undef Requires
|
||||
#undef RequiresMin
|
||||
#undef ShiftGroup
|
||||
#undef Displacement16Operand8
|
||||
#undef Far
|
||||
#undef Immediate
|
||||
#undef Displacement
|
||||
#undef MemRegReg
|
||||
#undef AddrReg
|
||||
#undef RegAddr
|
||||
#undef RegData
|
||||
#undef Complete
|
||||
#undef SetOpSrcDestSize
|
||||
|
||||
// MARK: - ModRegRM byte, if any.
|
||||
|
||||
if(phase_ == Phase::ModRegRM && source != end) {
|
||||
const uint8_t mod = *source >> 6; // i.e. mode.
|
||||
const uint8_t reg = (*source >> 3) & 7; // i.e. register.
|
||||
const uint8_t rm = *source & 7; // i.e. register/memory.
|
||||
bool expects_sib = false;
|
||||
++source;
|
||||
++consumed_;
|
||||
|
||||
Source memreg;
|
||||
|
||||
// These tables are fairly redundant due to the register ordering within
|
||||
// Source, but act to improve readability and permit further Source
|
||||
// reordering in the future.
|
||||
constexpr Source reg_table[8] = {
|
||||
Source::eAX, Source::eCX, Source::eDX, Source::eBX,
|
||||
Source::eSPorAH, Source::eBPorCH, Source::eSIorDH, Source::eDIorBH,
|
||||
};
|
||||
constexpr Source seg_table[6] = {
|
||||
Source::ES, Source::CS, Source::SS, Source::DS, Source::FS, Source::GS
|
||||
};
|
||||
|
||||
// Mode 3 is the same regardless of 16/32-bit mode. So deal with that up front.
|
||||
if(mod == 3) {
|
||||
// Other operand is just a register.
|
||||
memreg = reg_table[rm];
|
||||
|
||||
// LES, LDS, etc accept a memory argument only, not a register.
|
||||
if(
|
||||
operation_ == Operation::LES ||
|
||||
operation_ == Operation::LDS ||
|
||||
operation_ == Operation::LGS ||
|
||||
operation_ == Operation::LSS ||
|
||||
operation_ == Operation::LFS) {
|
||||
undefined();
|
||||
}
|
||||
} else {
|
||||
const DataSize sizes[] = {
|
||||
DataSize::None,
|
||||
DataSize::Byte,
|
||||
data_size(address_size_)
|
||||
};
|
||||
displacement_size_ = sizes[mod];
|
||||
memreg = Source::Indirect;
|
||||
|
||||
if(address_size_ == AddressSize::b32) {
|
||||
// 32-bit decoding: the range of potential indirections is expanded,
|
||||
// and may segue into obtaining a SIB.
|
||||
sib_ = ScaleIndexBase(0, Source::None, reg_table[rm]);
|
||||
expects_sib = rm == 4; // Indirect via eSP isn't directly supported; it's the
|
||||
// escape indicator for reading a SIB.
|
||||
} else {
|
||||
// Classic 16-bit decoding: mode picks a displacement size,
|
||||
// and a few fixed index+base pairs are defined.
|
||||
constexpr ScaleIndexBase rm_table[8] = {
|
||||
ScaleIndexBase(0, Source::eBX, Source::eSI),
|
||||
ScaleIndexBase(0, Source::eBX, Source::eDI),
|
||||
ScaleIndexBase(0, Source::eBP, Source::eSI),
|
||||
ScaleIndexBase(0, Source::eBP, Source::eDI),
|
||||
ScaleIndexBase(0, Source::None, Source::eSI),
|
||||
ScaleIndexBase(0, Source::None, Source::eDI),
|
||||
ScaleIndexBase(0, Source::None, Source::eBP),
|
||||
ScaleIndexBase(0, Source::None, Source::eBX),
|
||||
};
|
||||
|
||||
sib_ = rm_table[rm];
|
||||
}
|
||||
}
|
||||
|
||||
switch(modregrm_format_) {
|
||||
case ModRegRMFormat::Reg_MemReg:
|
||||
case ModRegRMFormat::MemReg_Reg: {
|
||||
if(modregrm_format_ == ModRegRMFormat::Reg_MemReg) {
|
||||
source_ = memreg;
|
||||
destination_ = reg_table[reg];
|
||||
} else {
|
||||
source_ = reg_table[reg];
|
||||
destination_ = memreg;
|
||||
}
|
||||
} break;
|
||||
|
||||
case ModRegRMFormat::MemRegTEST_to_IDIV:
|
||||
source_ = destination_ = memreg;
|
||||
|
||||
switch(reg) {
|
||||
default: undefined();
|
||||
|
||||
case 0: operation_ = Operation::TEST; break;
|
||||
case 2: operation_ = Operation::NOT; break;
|
||||
case 3: operation_ = Operation::NEG; break;
|
||||
case 4: operation_ = Operation::MUL; break;
|
||||
case 5: operation_ = Operation::IMUL_1; break;
|
||||
case 6: operation_ = Operation::DIV; break;
|
||||
case 7: operation_ = Operation::IDIV; break;
|
||||
}
|
||||
break;
|
||||
|
||||
case ModRegRMFormat::Seg_MemReg:
|
||||
case ModRegRMFormat::MemReg_Seg:
|
||||
// The 16-bit chips have four segment registers;
|
||||
// the 80386 onwards has six.
|
||||
if(!is_32bit(model) && reg > 3) {
|
||||
undefined();
|
||||
} else if(reg > 5) {
|
||||
undefined();
|
||||
}
|
||||
|
||||
if(modregrm_format_ == ModRegRMFormat::Seg_MemReg) {
|
||||
source_ = memreg;
|
||||
destination_ = seg_table[reg];
|
||||
|
||||
// 80286 and later disallow MOV to CS.
|
||||
if(model >= Model::i80286 && destination_ == Source::CS) {
|
||||
undefined();
|
||||
}
|
||||
} else {
|
||||
source_ = seg_table[reg];
|
||||
destination_ = memreg;
|
||||
}
|
||||
break;
|
||||
|
||||
case ModRegRMFormat::MemRegROL_to_SAR:
|
||||
destination_ = memreg;
|
||||
|
||||
switch(reg) {
|
||||
default: undefined();
|
||||
|
||||
case 0: operation_ = Operation::ROL; break;
|
||||
case 1: operation_ = Operation::ROR; break;
|
||||
case 2: operation_ = Operation::RCL; break;
|
||||
case 3: operation_ = Operation::RCR; break;
|
||||
case 4: operation_ = Operation::SAL; break;
|
||||
case 5: operation_ = Operation::SHR; break;
|
||||
case 7: operation_ = Operation::SAR; break;
|
||||
}
|
||||
break;
|
||||
|
||||
case ModRegRMFormat::MemRegINC_DEC:
|
||||
source_ = destination_ = memreg;
|
||||
|
||||
switch(reg) {
|
||||
default: undefined();
|
||||
|
||||
case 0: operation_ = Operation::INC; break;
|
||||
case 1: operation_ = Operation::DEC; break;
|
||||
}
|
||||
break;
|
||||
|
||||
case ModRegRMFormat::MemRegINC_to_PUSH:
|
||||
source_ = destination_ = memreg;
|
||||
|
||||
switch(reg) {
|
||||
default: undefined();
|
||||
|
||||
case 0: operation_ = Operation::INC; break;
|
||||
case 1: operation_ = Operation::DEC; break;
|
||||
case 2: operation_ = Operation::CALLabs; break;
|
||||
case 3: operation_ = Operation::CALLfar; break;
|
||||
case 4: operation_ = Operation::JMPabs; break;
|
||||
case 5: operation_ = Operation::JMPfar; break;
|
||||
case 6: operation_ = Operation::PUSH; break;
|
||||
}
|
||||
break;
|
||||
|
||||
case ModRegRMFormat::MemRegSingleOperand:
|
||||
source_ = destination_ = memreg;
|
||||
|
||||
if(reg != 0) {
|
||||
undefined();
|
||||
}
|
||||
break;
|
||||
|
||||
case ModRegRMFormat::MemRegMOV:
|
||||
source_ = Source::Immediate;
|
||||
destination_ = memreg;
|
||||
operand_size_ = operation_size_;
|
||||
break;
|
||||
|
||||
case ModRegRMFormat::MemRegADD_to_CMP:
|
||||
case ModRegRMFormat::MemRegADD_to_CMP_SignExtend:
|
||||
source_ = Source::Immediate;
|
||||
destination_ = memreg;
|
||||
operand_size_ = (modregrm_format_ == ModRegRMFormat::MemRegADD_to_CMP_SignExtend) ? DataSize::Byte : operation_size_;
|
||||
sign_extend_ = true; // Will be effective only if modregrm_format_ == ModRegRMFormat::MemRegADD_to_CMP_SignExtend.
|
||||
|
||||
switch(reg) {
|
||||
default: operation_ = Operation::ADD; break;
|
||||
case 1: operation_ = Operation::OR; break;
|
||||
case 2: operation_ = Operation::ADC; break;
|
||||
case 3: operation_ = Operation::SBB; break;
|
||||
case 4: operation_ = Operation::AND; break;
|
||||
case 5: operation_ = Operation::SUB; break;
|
||||
case 6: operation_ = Operation::XOR; break;
|
||||
case 7: operation_ = Operation::CMP; break;
|
||||
}
|
||||
break;
|
||||
|
||||
case ModRegRMFormat::MemRegSLDT_to_VERW:
|
||||
destination_ = source_ = memreg;
|
||||
|
||||
switch(reg) {
|
||||
default: undefined();
|
||||
|
||||
case 0: operation_ = Operation::SLDT; break;
|
||||
case 1: operation_ = Operation::STR; break;
|
||||
case 2: operation_ = Operation::LLDT; break;
|
||||
case 3: operation_ = Operation::LTR; break;
|
||||
case 4: operation_ = Operation::VERR; break;
|
||||
case 5: operation_ = Operation::VERW; break;
|
||||
}
|
||||
break;
|
||||
|
||||
case ModRegRMFormat::MemRegSGDT_to_LMSW:
|
||||
destination_ = source_ = memreg;
|
||||
|
||||
switch(reg) {
|
||||
default: undefined();
|
||||
|
||||
case 0: operation_ = Operation::SGDT; break;
|
||||
case 1: operation_ = Operation::SIDT; break;
|
||||
case 2: operation_ = Operation::LGDT; break;
|
||||
case 3: operation_ = Operation::LIDT; break;
|
||||
case 4: operation_ = Operation::SMSW; break;
|
||||
case 6: operation_ = Operation::LMSW; break;
|
||||
}
|
||||
break;
|
||||
|
||||
case ModRegRMFormat::MemRegBT_to_BTC:
|
||||
destination_ = memreg;
|
||||
source_ = Source::Immediate;
|
||||
operand_size_ = DataSize::Byte;
|
||||
|
||||
switch(reg) {
|
||||
default: undefined();
|
||||
|
||||
case 4: operation_ = Operation::BT; break;
|
||||
case 5: operation_ = Operation::BTS; break;
|
||||
case 6: operation_ = Operation::BTR; break;
|
||||
case 7: operation_ = Operation::BTC; break;
|
||||
}
|
||||
break;
|
||||
|
||||
default: assert(false);
|
||||
}
|
||||
|
||||
if(expects_sib && (source_ == Source::Indirect || destination_ == Source::Indirect)) {
|
||||
phase_ = Phase::ScaleIndexBase;
|
||||
} else {
|
||||
phase_ = (displacement_size_ != DataSize::None || operand_size_ != DataSize::None) ? Phase::DisplacementOrOperand : Phase::ReadyToPost;
|
||||
}
|
||||
}
|
||||
|
||||
#undef undefined
|
||||
|
||||
// MARK: - ScaleIndexBase
|
||||
|
||||
if(phase_ == Phase::ScaleIndexBase && source != end) {
|
||||
sib_ = *source;
|
||||
++source;
|
||||
++consumed_;
|
||||
|
||||
// Potentially record the lack of a base.
|
||||
if(displacement_size_ == DataSize::None && (uint8_t(sib_)&7) == 5) {
|
||||
source_ = (source_ == Source::Indirect) ? Source::IndirectNoBase : source_;
|
||||
destination_ = (destination_ == Source::Indirect) ? Source::IndirectNoBase : destination_;
|
||||
}
|
||||
|
||||
phase_ = (displacement_size_ != DataSize::None || operand_size_ != DataSize::None) ? Phase::DisplacementOrOperand : Phase::ReadyToPost;
|
||||
}
|
||||
|
||||
// MARK: - Displacement and operand.
|
||||
|
||||
if(phase_ == Phase::DisplacementOrOperand) {
|
||||
const auto required_bytes = int(byte_size(displacement_size_) + byte_size(operand_size_));
|
||||
|
||||
const int outstanding_bytes = required_bytes - operand_bytes_;
|
||||
const int bytes_to_consume = std::min(int(end - source), outstanding_bytes);
|
||||
|
||||
for(int c = 0; c < bytes_to_consume; c++) {
|
||||
inward_data_ |= decltype(inward_data_)(source[0]) << next_inward_data_shift_;
|
||||
++source;
|
||||
next_inward_data_shift_ += 8;
|
||||
}
|
||||
|
||||
consumed_ += bytes_to_consume;
|
||||
operand_bytes_ += bytes_to_consume;
|
||||
|
||||
if(bytes_to_consume == outstanding_bytes) {
|
||||
phase_ = Phase::ReadyToPost;
|
||||
|
||||
switch(displacement_size_) {
|
||||
case DataSize::None: displacement_ = 0; break;
|
||||
case DataSize::Byte: displacement_ = int8_t(inward_data_); break;
|
||||
case DataSize::Word: displacement_ = int16_t(inward_data_); break;
|
||||
case DataSize::DWord: displacement_ = int32_t(inward_data_); break;
|
||||
}
|
||||
inward_data_ >>= bit_size(displacement_size_);
|
||||
|
||||
// Use inequality of sizes as a test for necessary sign extension.
|
||||
if(operand_size_ == data_size_ || !sign_extend_) {
|
||||
operand_ = decltype(operand_)(inward_data_);
|
||||
} else {
|
||||
switch(operand_size_) {
|
||||
case DataSize::None: operand_ = 0; break;
|
||||
case DataSize::Byte: operand_ = decltype(operand_)(int8_t(inward_data_)); break;
|
||||
case DataSize::Word: operand_ = decltype(operand_)(int16_t(inward_data_)); break;
|
||||
case DataSize::DWord: operand_ = decltype(operand_)(int32_t(inward_data_)); break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Provide a genuine measure of further bytes required.
|
||||
return std::make_pair(-(outstanding_bytes - bytes_to_consume), InstructionT());
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Check for completion.
|
||||
|
||||
if(phase_ == Phase::ReadyToPost) {
|
||||
const auto result = std::make_pair(
|
||||
consumed_,
|
||||
InstructionT(
|
||||
operation_,
|
||||
source_,
|
||||
destination_,
|
||||
sib_,
|
||||
lock_,
|
||||
address_size_,
|
||||
segment_override_,
|
||||
repetition_,
|
||||
DataSize(operation_size_),
|
||||
static_cast<typename InstructionT::DisplacementT>(displacement_),
|
||||
static_cast<typename InstructionT::ImmediateT>(operand_),
|
||||
consumed_
|
||||
)
|
||||
);
|
||||
reset_parsing();
|
||||
return result;
|
||||
}
|
||||
|
||||
// Check for a too-long instruction.
|
||||
if(consumed_ == max_instruction_length) {
|
||||
std::pair<int, InstructionT> result;
|
||||
if(max_instruction_length == 65536) {
|
||||
result = std::make_pair(consumed_, InstructionT(Operation::NOP, consumed_));
|
||||
} else {
|
||||
result = std::make_pair(consumed_, InstructionT());
|
||||
}
|
||||
reset_parsing();
|
||||
return result;
|
||||
}
|
||||
|
||||
// i.e. not done yet.
|
||||
return std::make_pair(0, InstructionT());
|
||||
}
|
||||
|
||||
template <Model model> void Decoder<model>::set_32bit_protected_mode(bool enabled) {
|
||||
if constexpr (!is_32bit(model)) {
|
||||
assert(!enabled);
|
||||
return;
|
||||
}
|
||||
|
||||
if(enabled) {
|
||||
default_address_size_ = address_size_ = AddressSize::b32;
|
||||
default_data_size_ = data_size_ = DataSize::DWord;
|
||||
} else {
|
||||
default_address_size_ = address_size_ = AddressSize::b16;
|
||||
default_data_size_ = data_size_ = DataSize::Word;
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure all possible decoders are built.
|
||||
template class InstructionSet::x86::Decoder<InstructionSet::x86::Model::i8086>;
|
||||
template class InstructionSet::x86::Decoder<InstructionSet::x86::Model::i80186>;
|
||||
template class InstructionSet::x86::Decoder<InstructionSet::x86::Model::i80286>;
|
||||
template class InstructionSet::x86::Decoder<InstructionSet::x86::Model::i80386>;
|
||||
233
InstructionSets/x86/Decoder.hpp
Normal file
233
InstructionSets/x86/Decoder.hpp
Normal file
@@ -0,0 +1,233 @@
|
||||
//
|
||||
// Decoder.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 01/01/21.
|
||||
// Copyright © 2021 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef InstructionSets_x86_Decoder_hpp
|
||||
#define InstructionSets_x86_Decoder_hpp
|
||||
|
||||
#include "Instruction.hpp"
|
||||
#include "Model.hpp"
|
||||
|
||||
#include <cstddef>
|
||||
#include <utility>
|
||||
|
||||
namespace InstructionSet {
|
||||
namespace x86 {
|
||||
|
||||
/*!
|
||||
Implements Intel x86 instruction decoding.
|
||||
|
||||
This is an experimental implementation; it has not yet undergone significant testing.
|
||||
*/
|
||||
template <Model model> class Decoder {
|
||||
public:
|
||||
using InstructionT = Instruction<is_32bit(model)>;
|
||||
|
||||
/*!
|
||||
@returns an @c Instruction plus a size; a positive size indicates successful decoding of
|
||||
an instruction that was that many bytes long in total; a negative size specifies the [negatived]
|
||||
minimum number of further bytes the caller should ideally collect before calling again. The
|
||||
caller is free to call with fewer, but may not get a decoded instruction in response, and the
|
||||
decoder may still not be able to complete decoding even if given that number of bytes.
|
||||
|
||||
Successful decoding is defined to mean that all decoding steps are complete. The output
|
||||
may still be an illegal instruction (indicated by Operation::Invalid), if the byte sequence
|
||||
supplied cannot form a valid instruction.
|
||||
|
||||
@discussion although instructions also contain an indicator of their length, on chips prior
|
||||
to the 80286 there is no limit to instruction length and that could in theory overflow the available
|
||||
storage, which can describe instructions only up to 1kb in size.
|
||||
|
||||
The 80286 and 80386 have instruction length limits of 10 and 15 bytes respectively, so
|
||||
cannot overflow the field.
|
||||
*/
|
||||
std::pair<int, InstructionT> decode(const uint8_t *source, size_t length);
|
||||
|
||||
/*!
|
||||
Enables or disables 32-bit protected mode. Meaningful only if the @c Model supports it.
|
||||
*/
|
||||
void set_32bit_protected_mode(bool);
|
||||
|
||||
private:
|
||||
enum class Phase {
|
||||
/// Captures all prefixes and continues until an instruction byte is encountered.
|
||||
Instruction,
|
||||
/// Having encountered a 0x0f first instruction byte, waits for the next byte fully to determine the instruction.
|
||||
InstructionPageF,
|
||||
/// Receives a ModRegRM byte and either populates the source_ and dest_ fields appropriately
|
||||
/// or completes decoding of the instruction, as per the instruction format.
|
||||
ModRegRM,
|
||||
/// Awaits n 80386+-style scale-index-base byte ('SIB'), indicating the form of indirect addressing.
|
||||
ScaleIndexBase,
|
||||
/// Waits for sufficiently many bytes to pass for the required displacement and operand to be captured.
|
||||
/// Cf. displacement_size_ and operand_size_.
|
||||
DisplacementOrOperand,
|
||||
/// Forms and returns an Instruction, and resets parsing state.
|
||||
ReadyToPost
|
||||
} phase_ = Phase::Instruction;
|
||||
|
||||
/// During the ModRegRM phase, format dictates interpretation of the ModRegRM byte.
|
||||
///
|
||||
/// During the ReadyToPost phase, format determines how transiently-recorded fields
|
||||
/// are packaged into an Instruction.
|
||||
enum class ModRegRMFormat: uint8_t {
|
||||
// Parse the ModRegRM for mode, register and register/memory fields
|
||||
// and populate the source_ and destination_ fields appropriately.
|
||||
MemReg_Reg,
|
||||
Reg_MemReg,
|
||||
|
||||
// Parse for mode and register/memory fields, populating both
|
||||
// source_ and destination_ fields with the single register/memory result.
|
||||
MemRegSingleOperand,
|
||||
|
||||
// Parse for mode and register/memory fields, populating both
|
||||
// the destination_ field with the result and setting source_ to Immediate.
|
||||
MemRegMOV,
|
||||
|
||||
// Parse for mode and register/memory fields, populating the
|
||||
// source_ field with the result. Fills destination_ with a segment
|
||||
// register based on the reg field.
|
||||
Seg_MemReg,
|
||||
MemReg_Seg,
|
||||
|
||||
//
|
||||
// 'Group 1'
|
||||
//
|
||||
|
||||
// Parse for mode and register/memory fields, populating the
|
||||
// destination_ field with the result. Use the 'register' field
|
||||
// to pick an operation from the ADD/OR/ADC/SBB/AND/SUB/XOR/CMP group and
|
||||
// waits for an operand equal to the operation size.
|
||||
MemRegADD_to_CMP,
|
||||
|
||||
// Acts exactly as MemRegADD_to_CMP but the operand is fixed in size
|
||||
// at a single byte, which is sign extended to the operation size.
|
||||
MemRegADD_to_CMP_SignExtend,
|
||||
|
||||
//
|
||||
// 'Group 2'
|
||||
//
|
||||
|
||||
// Parse for mode and register/memory fields, populating the
|
||||
// destination_ field with the result. Use the 'register' field
|
||||
// to pick an operation from the ROL/ROR/RCL/RCR/SAL/SHR/SAR group.
|
||||
MemRegROL_to_SAR,
|
||||
|
||||
//
|
||||
// 'Group 3'
|
||||
//
|
||||
|
||||
// Parse for mode and register/memory fields, populating both
|
||||
// source_ and destination_ fields with the result. Use the 'register'
|
||||
// field to pick an operation from the TEST/NOT/NEG/MUL/IMUL/DIV/IDIV group.
|
||||
MemRegTEST_to_IDIV,
|
||||
|
||||
//
|
||||
// 'Group 4'
|
||||
//
|
||||
|
||||
// Parse for mode and register/memory fields, populating the
|
||||
// source_ and destination_ fields with the result. Uses the
|
||||
// 'register' field to pick INC or DEC.
|
||||
MemRegINC_DEC,
|
||||
|
||||
//
|
||||
// 'Group 5'
|
||||
//
|
||||
|
||||
// Parse for mode and register/memory fields, populating the
|
||||
// source_ and destination_ fields with the result. Uses the
|
||||
// 'register' field to pick from INC/DEC/CALL/JMP/PUSH, altering
|
||||
// the source to ::Immediate and setting an operand size if necessary.
|
||||
MemRegINC_to_PUSH,
|
||||
|
||||
//
|
||||
// 'Group 6'
|
||||
//
|
||||
|
||||
// Parse for mode and register/memory field, populating both source_
|
||||
// and destination_ fields with the result. Uses the 'register' field
|
||||
// to pick from SLDT/STR/LLDT/LTR/VERR/VERW.
|
||||
MemRegSLDT_to_VERW,
|
||||
|
||||
//
|
||||
// 'Group 7'
|
||||
//
|
||||
|
||||
// Parse for mode and register/memory field, populating both source_
|
||||
// and destination_ fields with the result. Uses the 'register' field
|
||||
// to pick from SGDT/LGDT/SMSW/LMSW.
|
||||
MemRegSGDT_to_LMSW,
|
||||
|
||||
//
|
||||
// 'Group 8'
|
||||
//
|
||||
|
||||
// Parse for mode and register/memory field, populating destination,
|
||||
// and prepare to read a single byte as source.
|
||||
MemRegBT_to_BTC,
|
||||
} modregrm_format_ = ModRegRMFormat::MemReg_Reg;
|
||||
|
||||
// Ephemeral decoding state.
|
||||
Operation operation_ = Operation::Invalid;
|
||||
int consumed_ = 0, operand_bytes_ = 0;
|
||||
|
||||
// Source and destination locations.
|
||||
Source source_ = Source::None;
|
||||
Source destination_ = Source::None;
|
||||
|
||||
// Immediate fields.
|
||||
int32_t displacement_ = 0;
|
||||
uint32_t operand_ = 0;
|
||||
uint64_t inward_data_ = 0;
|
||||
int next_inward_data_shift_ = 0;
|
||||
|
||||
// Indirection style.
|
||||
ScaleIndexBase sib_;
|
||||
|
||||
// Facts about the instruction.
|
||||
DataSize displacement_size_ = DataSize::None; // i.e. size of in-stream displacement, if any.
|
||||
DataSize operand_size_ = DataSize::None; // i.e. size of in-stream operand, if any.
|
||||
DataSize operation_size_ = DataSize::None; // i.e. size of data manipulated by the operation.
|
||||
|
||||
bool sign_extend_ = false; // If set then sign extend the operand up to the operation size;
|
||||
// otherwise it'll be zero-padded.
|
||||
|
||||
// Prefix capture fields.
|
||||
Repetition repetition_ = Repetition::None;
|
||||
bool lock_ = false;
|
||||
Source segment_override_ = Source::None;
|
||||
|
||||
// 32-bit/16-bit selection.
|
||||
AddressSize default_address_size_ = AddressSize::b16;
|
||||
DataSize default_data_size_ = DataSize::Word;
|
||||
AddressSize address_size_ = AddressSize::b16;
|
||||
DataSize data_size_ = DataSize::Word;
|
||||
|
||||
/// Resets size capture and all fields with default values.
|
||||
void reset_parsing() {
|
||||
consumed_ = operand_bytes_ = 0;
|
||||
displacement_size_ = operand_size_ = operation_size_ = DataSize::None;
|
||||
displacement_ = operand_ = 0;
|
||||
lock_ = false;
|
||||
address_size_ = default_address_size_;
|
||||
data_size_ = default_data_size_;
|
||||
segment_override_ = Source::None;
|
||||
repetition_ = Repetition::None;
|
||||
phase_ = Phase::Instruction;
|
||||
source_ = destination_ = Source::None;
|
||||
sib_ = ScaleIndexBase();
|
||||
next_inward_data_shift_ = 0;
|
||||
inward_data_ = 0;
|
||||
sign_extend_ = false;
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* InstructionSets_x86_Decoder_hpp */
|
||||
917
InstructionSets/x86/Documentation/80386 opcode map.html
Normal file
917
InstructionSets/x86/Documentation/80386 opcode map.html
Normal file
@@ -0,0 +1,917 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>80386 Opcode Map</title>
|
||||
<style>
|
||||
table, table th, table td {
|
||||
border: 1px solid;
|
||||
border-collapse: collapse;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.codetable, .codetable th, .codetable td {
|
||||
border: 0px;
|
||||
border-collapse: collapse;
|
||||
padding-right: 1em;
|
||||
text-align: left;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
.optable th, .optable td {
|
||||
width: 5em;
|
||||
}
|
||||
.optable tr:nth-child(even) {
|
||||
border-top: 3px solid;
|
||||
}
|
||||
|
||||
.grouptable, .grouptable th, .grouptable td {
|
||||
border-bottom: 3px solid;
|
||||
}
|
||||
.grouptable th, .grouptable td {
|
||||
width: 4em;
|
||||
}
|
||||
|
||||
.skiprow {
|
||||
background-color: darkgray;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Codes for Addressing Method</h1>
|
||||
|
||||
<table class="codetable">
|
||||
<tr>
|
||||
<td>A</td>
|
||||
<td>Direct address; the instruction has no MODRM field; the address of the operand is encoded in the instruction; no base register, index register, or scaling factor can be applied; e.g., far JMP (EA).</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>C</td>
|
||||
<td>The reg field of the MODRM field selects a control register; e.g., MOV (0F20, 0F22).</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>D</td>
|
||||
<td>The reg field of the MODRM field selects a debug register; e.g., MOV (0F21, 0F23).</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>E</td>
|
||||
<td>A MODRM field follows the opcode and specifies the operand. The operand is either a general register or a memory address. If it is a memory address, the address is computed from a segment register and any of the following values: a base register, an index register, a scaling factor, a displacement.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>F</td>
|
||||
<td>Flags register</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>G</td>
|
||||
<td>The reg field of the MODRM field selects a general register; e.g,. ADD (00).</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>I</td>
|
||||
<td>Immediate data. The value of the operand is encoded in subsequent bytes of the instruction.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>J</td>
|
||||
<td>The instruction contains a relative offset to be added to the instruction-pointer register; e.g., JMP short, LOOP.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>M</td>
|
||||
<td>The MODRM field may refer only to memory; e.g., BOUND, LES, LDS, LSS, LFS, LGS.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>O</td>
|
||||
<td>The instruction has no MODRM field; the offset of the operand is coded as a word or dword (depending on address sie attribute) in the instruction. No base register, index register, or scaling factor can be applied; e.g., MOV (A0–A3).</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>R</td>
|
||||
<td>The mod field of the MODRM field may refer only to a general register; e.g., MOV(0F20–0F24, 0F26).</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>S</td>
|
||||
<td>The reg field of the MODRM field selects a segment register; e.g., MOV (8C, 8E).</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>T</td>
|
||||
<td>The reg field of the MODRM field selects a test register; e.g., MOV (0F24, 0F26).</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>X</td>
|
||||
<td>Memory addressed by DS:SI; e.g., MOVS, COMPS, OUTS, LODS, SCAS.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Y</td>
|
||||
<td>Memory addressed by ES:DI; e.g., MOVS, CMPS, INS, STOS.</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<h1>Codes for Operand Type</h1>
|
||||
|
||||
<table class="codetable">
|
||||
<tr>
|
||||
<td>a</td>
|
||||
<td>Two one-word operands in memory or two dword operands in memory, depending on operand size attribute (used only by BOUND).</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>b</td>
|
||||
<td>Byte (regardless of operand size attribute).</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>c</td>
|
||||
<td>Byte or word, depending on operand size attribute.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>d</td>
|
||||
<td>Dword (regardless of operand size attribute).</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>p</td>
|
||||
<td>32-bit or 48-bit pointer, depending on operand size attribute.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>s</td>
|
||||
<td>Six-byte pesudo-descriptor.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>v</td>
|
||||
<td>Word or dword, depending on operand size attribute.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>w</td>
|
||||
<td>Word (regardless of operand size attribute).</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<h1>Register Codes</h1>
|
||||
|
||||
When an operand is a specific register encoded in the opcode, the register is identifed by its name; e.g., AX, CL, or ESI. The name of the register indicates whether the register is 32, 16, or 8 bits wide. A register identifier of the form eXX is used when the width of the register depends on the operand size attribute. For example, eAX indicates that the AX register is used when the operand size attribute is 16, and the EAX register is used when the operand size attribute is 32.
|
||||
|
||||
<h1>One-byte 80386 Opcode Map</h1>
|
||||
<table class="optable">
|
||||
<tr>
|
||||
<th></th>
|
||||
<th>x0</th>
|
||||
<th>x1</th>
|
||||
<th>x2</th>
|
||||
<th>x3</th>
|
||||
<th>x4</th>
|
||||
<th>x5</th>
|
||||
<th>x6</th>
|
||||
<th>x7</th>
|
||||
<th>x8</th>
|
||||
<th>x9</th>
|
||||
<th>xA</th>
|
||||
<th>xB</th>
|
||||
<th>xC</th>
|
||||
<th>xD</th>
|
||||
<th>xE</th>
|
||||
<th>xF</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<th rowspan=2>0x</th>
|
||||
|
||||
<td colspan=6>ADD</td>
|
||||
<td rowspan=2>PUSH ES</td>
|
||||
<td rowspan=2>POP ES</td>
|
||||
<td colspan=6>OR</td>
|
||||
<td rowspan=2>PUSH CS</td>
|
||||
<td rowspan=2>2-byte escape codes</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<!-- ADD -->
|
||||
<td>Eb, Gb</td>
|
||||
<td>Ev, Gv</td>
|
||||
<td>Gb, Eb</td>
|
||||
<td>Gv, Ev</td>
|
||||
<td>AL, Ib</td>
|
||||
<td>eAX, Iv</td>
|
||||
|
||||
<!-- OR -->
|
||||
<td>Eb, Gb</td>
|
||||
<td>Ev, Gv</td>
|
||||
<td>Gb, Eb</td>
|
||||
<td>Gv, Ev</td>
|
||||
<td>AL, Ib</td>
|
||||
<td>eAX, Iv</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th rowspan=2>1x</th>
|
||||
|
||||
<td colspan=6>ADC</td>
|
||||
<td rowspan=2>PUSH SS</td>
|
||||
<td rowspan=2>POP SS</td>
|
||||
<td colspan=6>SBB</td>
|
||||
<td rowspan=2>PUSH DS</td>
|
||||
<td rowspan=2>POP DS</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<!-- ADC -->
|
||||
<td>Eb, Gb</td>
|
||||
<td>Ev, Gv</td>
|
||||
<td>Gb, Eb</td>
|
||||
<td>Gv, Ev</td>
|
||||
<td>AL, Ib</td>
|
||||
<td>eAX, Iv</td>
|
||||
|
||||
<!-- SBB -->
|
||||
<td>Eb, Gb</td>
|
||||
<td>Ev, Gv</td>
|
||||
<td>Gb, Eb</td>
|
||||
<td>Gv, Ev</td>
|
||||
<td>AL, Ib</td>
|
||||
<td>eAX, Iv</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th rowspan=2>2x</th>
|
||||
|
||||
<td colspan=6>AND</td>
|
||||
<td rowspan=2>SEG =ES</td>
|
||||
<td rowspan=2>POP ES</td>
|
||||
<td colspan=6>SUB</td>
|
||||
<td rowspan=2>SEG =CS</td>
|
||||
<td rowspan=2>DAS</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<!-- AND -->
|
||||
<td>Eb, Gb</td>
|
||||
<td>Ev, Gv</td>
|
||||
<td>Gb, Eb</td>
|
||||
<td>Gv, Ev</td>
|
||||
<td>AL, Ib</td>
|
||||
<td>eAX, Iv</td>
|
||||
|
||||
<!-- SUB -->
|
||||
<td>Eb, Gb</td>
|
||||
<td>Ev, Gv</td>
|
||||
<td>Gb, Eb</td>
|
||||
<td>Gv, Ev</td>
|
||||
<td>AL, Ib</td>
|
||||
<td>eAX, Iv</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th rowspan=2>3x</th>
|
||||
|
||||
<td colspan=6>XOR</td>
|
||||
<td rowspan=2>SEG =SS</td>
|
||||
<td rowspan=2>AAA</td>
|
||||
<td colspan=6>CMP</td>
|
||||
<td rowspan=2>SEG =DS</td>
|
||||
<td rowspan=2>AAS</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<!-- XOR -->
|
||||
<td>Eb, Gb</td>
|
||||
<td>Ev, Gv</td>
|
||||
<td>Gb, Eb</td>
|
||||
<td>Gv, Ev</td>
|
||||
<td>AL, Ib</td>
|
||||
<td>eAX, Iv</td>
|
||||
|
||||
<!-- CMP -->
|
||||
<td>Eb, Gb</td>
|
||||
<td>Ev, Gv</td>
|
||||
<td>Gb, Eb</td>
|
||||
<td>Gv, Ev</td>
|
||||
<td>AL, Ib</td>
|
||||
<td>eAX, Iv</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th rowspan=2>4x</th>
|
||||
|
||||
<td colspan=8>INC general register</td>
|
||||
<td colspan=8>DEC general register</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<!-- INC general register -->
|
||||
<td>eAX</td>
|
||||
<td>eCX</td>
|
||||
<td>eDX</td>
|
||||
<td>eBX</td>
|
||||
<td>eSP</td>
|
||||
<td>eBP</td>
|
||||
<td>eSI</td>
|
||||
<td>eDI</td>
|
||||
|
||||
<!-- DEC general register -->
|
||||
<td>eAX</td>
|
||||
<td>eCX</td>
|
||||
<td>eDX</td>
|
||||
<td>eBX</td>
|
||||
<td>eSP</td>
|
||||
<td>eBP</td>
|
||||
<td>eSI</td>
|
||||
<td>eDI</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th rowspan=2>5x</th>
|
||||
|
||||
<td colspan=8>PUSH general register</td>
|
||||
<td colspan=8>POP general register</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<!-- PUSH general register -->
|
||||
<td>eAX</td>
|
||||
<td>eCX</td>
|
||||
<td>eDX</td>
|
||||
<td>eBX</td>
|
||||
<td>eSP</td>
|
||||
<td>eBP</td>
|
||||
<td>eSI</td>
|
||||
<td>eDI</td>
|
||||
|
||||
<!-- POP general register -->
|
||||
<td>eAX</td>
|
||||
<td>eCX</td>
|
||||
<td>eDX</td>
|
||||
<td>eBX</td>
|
||||
<td>eSP</td>
|
||||
<td>eBP</td>
|
||||
<td>eSI</td>
|
||||
<td>eDI</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th rowspan=2>6x</th>
|
||||
|
||||
<td rowspan=2>PUSHA</td>
|
||||
<td rowspan=2>POPA</td>
|
||||
<td rowspan=2>BOUND Gv, Ma</td>
|
||||
<td rowspan=2>ARPL Gv, Ma</td>
|
||||
<td rowspan=2>SEG =FS</td>
|
||||
<td rowspan=2>SEG =GS</td>
|
||||
<td rowspan=2>Operand Size</td>
|
||||
<td rowspan=2>Address Size</td>
|
||||
<td rowspan=2>PUSH Iv</td>
|
||||
<td rowspan=2>IMUL GvEvIv</td>
|
||||
<td rowspan=2>PUSH Ib</td>
|
||||
<td rowspan=2>IMUL GvEvIb</td>
|
||||
<td rowspan=2>INSB Yb, Dx</td>
|
||||
<td rowspan=2>INSW/D Yv, Dx</td>
|
||||
<td rowspan=2>OUTSB Dx, Xb</td>
|
||||
<td rowspan=2>OUTSW/D Dx, Xb</td>
|
||||
</tr>
|
||||
<tr></tr>
|
||||
<tr>
|
||||
<th rowspan=2>7x</th>
|
||||
|
||||
<td colspan=16>Short-displacement jump on condition (Jb)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<!-- Short-displacement jump on condition (Jb) -->
|
||||
<td>JO</td>
|
||||
<td>JNO</td>
|
||||
<td>JB</td>
|
||||
<td>JNB</td>
|
||||
<td>JZ</td>
|
||||
<td>JNZ</td>
|
||||
<td>JBE</td>
|
||||
<td>JNBE</td>
|
||||
<td>JS</td>
|
||||
<td>JNS</td>
|
||||
<td>JP</td>
|
||||
<td>JNP</td>
|
||||
<td>JL</td>
|
||||
<td>JNL</td>
|
||||
<td>JLE</td>
|
||||
<td>JNLE</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th rowspan=2>8x</th>
|
||||
|
||||
<td colspan=2>Immediate Grp1</td>
|
||||
<td rowspan=2></td>
|
||||
<td rowspan=2>Grp1 Ev, Ib</td>
|
||||
<td colspan=2>TEST</td>
|
||||
<td colspan=2>XCHG</td>
|
||||
<td colspan=4>MOV</td>
|
||||
<td rowspan=2>MOV Ew, Sw</td>
|
||||
<td rowspan=2>LEA Gv, M</td>
|
||||
<td rowspan=2>MOV Sw, Ew</td>
|
||||
<td rowspan=2>POP Ev</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<!-- Immediate Grp1 -->
|
||||
<td>Eb, Ib</td>
|
||||
<td>Ev, Iv</td>
|
||||
|
||||
<!-- TEST -->
|
||||
<td>Eb, Gb</td>
|
||||
<td>Ev, Gv</td>
|
||||
|
||||
<!-- XCHG -->
|
||||
<td>Eb, Gb</td>
|
||||
<td>Ev, Gv</td>
|
||||
|
||||
<!-- MOV -->
|
||||
<td>Eb, Gb</td>
|
||||
<td>Ev, Gv</td>
|
||||
<td>Gb, Eb</td>
|
||||
<td>Gv, Ev</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th rowspan=2>9x</th>
|
||||
|
||||
<td rowspan=2>NOP</td>
|
||||
<td colspan=7>XCHG word or double-word register with eAX</td>
|
||||
<td rowspan=2>CBW</td>
|
||||
<td rowspan=2>CWD</td>
|
||||
<td rowspan=2>CALL Ap</td>
|
||||
<td rowspan=2>WAIT</td>
|
||||
<td rowspan=2>PUSHF Fv</td>
|
||||
<td rowspan=2>POPF Fv</td>
|
||||
<td rowspan=2>SAHF</td>
|
||||
<td rowspan=2>LAHF</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<!-- XCHG -->
|
||||
<td>eCX</td>
|
||||
<td>eDX</td>
|
||||
<td>eBX</td>
|
||||
<td>eSP</td>
|
||||
<td>eBP</td>
|
||||
<td>eSI</td>
|
||||
<td>eDI</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th rowspan=2>Ax</th>
|
||||
|
||||
<td colspan=4>MOV</td>
|
||||
<td rowspan=2>MOVSB Xb, Yv</td>
|
||||
<td rowspan=2>MOVSW/D Xv, Yv</td>
|
||||
<td rowspan=2>CMPSB Xb, Yb</td>
|
||||
<td rowspan=2>CMPSW/D Xv, Yv</td>
|
||||
<td colspan=2>TEST</td>
|
||||
<td rowspan=2>STOSB Yb, AL</td>
|
||||
<td rowspan=2>STOSW/D Yv, eAX</td>
|
||||
<td rowspan=2>LDSB AL, Xb</td>
|
||||
<td rowspan=2>LDSW/D eAX, Yv</td>
|
||||
<td rowspan=2>SCASB AL, Xb</td>
|
||||
<td rowspan=2>SCASW/D eAX, Xv</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<!-- MOV -->
|
||||
<td>AL, Ob</td>
|
||||
<td>eAX, Ov</td>
|
||||
<td>Ob, AL</td>
|
||||
<td>Ov, eAX</td>
|
||||
|
||||
<!-- TEST -->
|
||||
<td>AL, Ib</td>
|
||||
<td>eAX, Iv</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th rowspan=2>Bx</th>
|
||||
|
||||
<td colspan=8>MOV immediate byte into byte register</td>
|
||||
<td colspan=8>MOV immediate word or double into word or double register</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>AL</td>
|
||||
<td>CL</td>
|
||||
<td>DL</td>
|
||||
<td>BL</td>
|
||||
<td>AH</td>
|
||||
<td>CH</td>
|
||||
<td>DH</td>
|
||||
<td>BH</td>
|
||||
|
||||
<td>eAX</td>
|
||||
<td>eCX</td>
|
||||
<td>eDX</td>
|
||||
<td>eBX</td>
|
||||
<td>eSP</td>
|
||||
<td>eBP</td>
|
||||
<td>eSI</td>
|
||||
<td>eDI</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th rowspan=2>Cx</th>
|
||||
|
||||
<td colspan=2>Shift Grp2</td>
|
||||
<td colspan=2>RET near</td>
|
||||
<td rowspan=2>LES Gv, Mp</td>
|
||||
<td rowspan=2>LDS Gv, Mp</td>
|
||||
<td colspan=2>MOV</td>
|
||||
<td rowspan=2>ENTER</td>
|
||||
<td rowspan=2>LEAVE</td>
|
||||
<td colspan=2>RET far</td>
|
||||
<td rowspan=2>INT 3</td>
|
||||
<td rowspan=2>INT Ib</td>
|
||||
<td rowspan=2>INTO</td>
|
||||
<td rowspan=2>IRET</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Eb, Ib</td>
|
||||
<td>Ev, Iv</td>
|
||||
<td>Iw</td>
|
||||
<td></td>
|
||||
<td>Eb, Ib</td>
|
||||
<td>Ev, Iv</td>
|
||||
<td>Iw</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th rowspan=2>Dx</th>
|
||||
|
||||
<td colspan=4>Shift Grp2</td>
|
||||
<td rowspan=2>AAM</td>
|
||||
<td rowspan=2>AAD</td>
|
||||
<td rowspan=2></td>
|
||||
<td rowspan=2>XLAT</td>
|
||||
<td colspan=8 rowspan=2>ESC (Escape to coprocessor instruction set)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Eb, 1</td>
|
||||
<td>Ev, 1</td>
|
||||
<td>Eb, CL</td>
|
||||
<td>Ev, CL</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th rowspan=2>Ex</th>
|
||||
|
||||
<td rowspan=2>LOOPNE Jb</td>
|
||||
<td rowspan=2>LOOPE Jb</td>
|
||||
<td rowspan=2>LOOP Jb</td>
|
||||
<td rowspan=2>JCXZ Jb</td>
|
||||
<td colspan=2>IN</td>
|
||||
<td colspan=2>OUT</td>
|
||||
<td rowspan=2>CALL Jv</td>
|
||||
<td colspan=3>JMP</td>
|
||||
<td colspan=2>IN</td>
|
||||
<td colspan=2>OUT</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<!-- IN -->
|
||||
<td>AL, Ib</td>
|
||||
<td>eAX, Ib</td>
|
||||
|
||||
<!-- OUT -->
|
||||
<td>Ib, AL</td>
|
||||
<td>Ib, eAX</td>
|
||||
|
||||
<!-- JMP -->
|
||||
<td>Jv</td>
|
||||
<td>Ap</td>
|
||||
<td>Jb</td>
|
||||
|
||||
<!-- IN -->
|
||||
<td>AL, DX</td>
|
||||
<td>eAX, DX</td>
|
||||
|
||||
<!-- OUT -->
|
||||
<td>DX, AL</td>
|
||||
<td>DX, eAX</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th rowspan=2>Fx</th>
|
||||
|
||||
<td rowspan=2>LOCK</td>
|
||||
<td rowspan=2></td>
|
||||
<td rowspan=2>REPNE</td>
|
||||
<td rowspan=2>REP / REPE</td>
|
||||
<td rowspan=2>HLT</td>
|
||||
<td rowspan=2>CMC</td>
|
||||
<td colspan=2>Unary Grp3</td>
|
||||
<td rowspan=2>CLC</td>
|
||||
<td rowspan=2>STC</td>
|
||||
<td rowspan=2>CLI</td>
|
||||
<td rowspan=2>STI</td>
|
||||
<td rowspan=2>CLD</td>
|
||||
<td rowspan=2>STD</td>
|
||||
<td rowspan=2>INC/DEC Grp4</td>
|
||||
<td rowspan=2>Indirect Grp5</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<!-- Unary Grp3 -->
|
||||
<td>Eb</td>
|
||||
<td>Ev</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<h1>Two-Byte 80386 Opcode Map (First byte is 0FH)</h1>
|
||||
<table class="optable">
|
||||
<tr>
|
||||
<th></th>
|
||||
<th>x0</th>
|
||||
<th>x1</th>
|
||||
<th>x2</th>
|
||||
<th>x3</th>
|
||||
<th>x4</th>
|
||||
<th>x5</th>
|
||||
<th>x6</th>
|
||||
<th>x7</th>
|
||||
<th>x8</th>
|
||||
<th>x9</th>
|
||||
<th>xA</th>
|
||||
<th>xB</th>
|
||||
<th>xC</th>
|
||||
<th>xD</th>
|
||||
<th>xE</th>
|
||||
<th>xF</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<th rowspan=2>0x</th>
|
||||
|
||||
<td rowspan=2>Grp6</td>
|
||||
<td rowspan=2>Grp7</td>
|
||||
<td rowspan=2>LAR Gv, Ew</td>
|
||||
<td rowspan=2>LSL Gv, Ew</td>
|
||||
<td rowspan=2></td>
|
||||
<td rowspan=2></td>
|
||||
<td rowspan=2>CLTS</td>
|
||||
<td rowspan=2></td>
|
||||
<td rowspan=2></td>
|
||||
<td rowspan=2></td>
|
||||
<td rowspan=2></td>
|
||||
<td rowspan=2></td>
|
||||
<td rowspan=2></td>
|
||||
<td rowspan=2></td>
|
||||
<td rowspan=2></td>
|
||||
<td rowspan=2></td>
|
||||
</tr>
|
||||
<tr></tr>
|
||||
<tr>
|
||||
<th rowspan=2>1x</th>
|
||||
|
||||
<td rowspan=2></td>
|
||||
<td rowspan=2></td>
|
||||
<td rowspan=2></td>
|
||||
<td rowspan=2></td>
|
||||
<td rowspan=2></td>
|
||||
<td rowspan=2></td>
|
||||
<td rowspan=2></td>
|
||||
<td rowspan=2></td>
|
||||
<td rowspan=2></td>
|
||||
<td rowspan=2></td>
|
||||
<td rowspan=2></td>
|
||||
<td rowspan=2></td>
|
||||
<td rowspan=2></td>
|
||||
<td rowspan=2></td>
|
||||
<td rowspan=2></td>
|
||||
<td rowspan=2></td>
|
||||
</tr>
|
||||
<tr></tr>
|
||||
<tr>
|
||||
<th rowspan=2>2x</th>
|
||||
|
||||
<td rowspan=2>MOV Cd, Rd</td>
|
||||
<td rowspan=2>MOV Dd, Rd</td>
|
||||
<td rowspan=2>MOV Rd, Cd</td>
|
||||
<td rowspan=2>MOV Rd, Dd</td>
|
||||
<td rowspan=2>MOV Td, Rd</td>
|
||||
<td rowspan=2></td>
|
||||
<td rowspan=2>MOV Rd, Td</td>
|
||||
<td rowspan=2></td>
|
||||
<td rowspan=2></td>
|
||||
<td rowspan=2></td>
|
||||
<td rowspan=2></td>
|
||||
<td rowspan=2></td>
|
||||
<td rowspan=2></td>
|
||||
<td rowspan=2></td>
|
||||
<td rowspan=2></td>
|
||||
<td rowspan=2></td>
|
||||
</tr>
|
||||
<tr></tr>
|
||||
<tr class="skiprow">
|
||||
<th rowspan=2 colspan=17>≈</th>
|
||||
</tr>
|
||||
<tr></tr>
|
||||
<tr>
|
||||
<th rowspan=2>8x</th>
|
||||
|
||||
<td colspan=16>Long-displacement jump on condition (Jv)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<!-- Long-displacement jump on condition (Jv) -->
|
||||
<td>JO</td>
|
||||
<td>JNO</td>
|
||||
<td>JB</td>
|
||||
<td>JNB</td>
|
||||
<td>JZ</td>
|
||||
<td>JNZ</td>
|
||||
<td>JBE</td>
|
||||
<td>JNBE</td>
|
||||
<td>JS</td>
|
||||
<td>JNS</td>
|
||||
<td>JP</td>
|
||||
<td>JNP</td>
|
||||
<td>JL</td>
|
||||
<td>JNL</td>
|
||||
<td>JLE</td>
|
||||
<td>JNLE</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th rowspan=2>9x</th>
|
||||
|
||||
<td colspan=16>Byte set on condition (Eb)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<!-- Byte set on condition (Eb) -->
|
||||
<td>SETO</td>
|
||||
<td>SETNO</td>
|
||||
<td>SETB</td>
|
||||
<td>SETNB</td>
|
||||
<td>SETZ</td>
|
||||
<td>SETNZ</td>
|
||||
<td>SETBE</td>
|
||||
<td>SETNBE</td>
|
||||
<td>SETS</td>
|
||||
<td>SETNS</td>
|
||||
<td>SETP</td>
|
||||
<td>SETNP</td>
|
||||
<td>SETL</td>
|
||||
<td>SETNL</td>
|
||||
<td>SETLE</td>
|
||||
<td>SETNLE</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th rowspan=2>Ax</th>
|
||||
|
||||
<td rowspan=2>PUSH FS</td>
|
||||
<td rowspan=2>POP FS</td>
|
||||
<td rowspan=2></td>
|
||||
<td rowspan=2>BT Ev, Gv</td>
|
||||
<td rowspan=2>SHLD EvGvIb</td>
|
||||
<td rowspan=2>SHLD EvGvCL</td>
|
||||
<td rowspan=2></td>
|
||||
<td rowspan=2></td>
|
||||
<td rowspan=2>PUSH GS</td>
|
||||
<td rowspan=2>POP GS</td>
|
||||
<td rowspan=2></td>
|
||||
<td rowspan=2>BTS Ev, Gv</td>
|
||||
<td rowspan=2>SHRD EvGvIb</td>
|
||||
<td rowspan=2>SHRD EvGvCL</td>
|
||||
<td rowspan=2></td>
|
||||
<td rowspan=2>IMUL Gv, Ev</td>
|
||||
</tr>
|
||||
<tr></tr>
|
||||
<tr>
|
||||
<th rowspan=2>Bx</th>
|
||||
|
||||
<td rowspan=2></td>
|
||||
<td rowspan=2></td>
|
||||
<td rowspan=2>LSS Mp</td>
|
||||
<td rowspan=2>BTR Ev, Gv</td>
|
||||
<td rowspan=2>LFS Mp</td>
|
||||
<td rowspan=2>LGS Mp</td>
|
||||
<td colspan=2>MOVZX</td>
|
||||
<td rowspan=2></td>
|
||||
<td rowspan=2></td>
|
||||
<td rowspan=2>Grp8 Ev, Ib</td>
|
||||
<td rowspan=2>BTC Ev, Gv</td>
|
||||
<td rowspan=2>BSF Gv, Ev</td>
|
||||
<td rowspan=2>BSR Gv, Ev</td>
|
||||
<td colspan=2>MOVSX</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<!-- MOVZX -->
|
||||
<td>Gv, Eb</td>
|
||||
<td>Gv, Ew</td>
|
||||
|
||||
<!-- MOVSX -->
|
||||
<td>Gv, Eb</td>
|
||||
<td>Gv, Ew</td>
|
||||
</tr>
|
||||
<tr class="skiprow">
|
||||
<th rowspan=2 colspan=17>≈</th>
|
||||
</tr>
|
||||
<tr></tr>
|
||||
<tr>
|
||||
<th rowspan=2>Fx</th>
|
||||
|
||||
<td rowspan=2></td>
|
||||
<td rowspan=2></td>
|
||||
<td rowspan=2></td>
|
||||
<td rowspan=2></td>
|
||||
<td rowspan=2></td>
|
||||
<td rowspan=2></td>
|
||||
<td rowspan=2></td>
|
||||
<td rowspan=2></td>
|
||||
<td rowspan=2></td>
|
||||
<td rowspan=2></td>
|
||||
<td rowspan=2></td>
|
||||
<td rowspan=2></td>
|
||||
<td rowspan=2></td>
|
||||
<td rowspan=2></td>
|
||||
<td rowspan=2></td>
|
||||
<td rowspan=2></td>
|
||||
</tr>
|
||||
<tr></tr>
|
||||
</table>
|
||||
<h1>Opcodes Determined by Bits 5, 4, 3 of MODRM Field</h1>
|
||||
<table>
|
||||
<tr>
|
||||
<td>mod</td>
|
||||
<td>nnn</td>
|
||||
<td>R/M</td>
|
||||
</tr>
|
||||
</table>
|
||||
<br />
|
||||
<table class="grouptable">
|
||||
<tr>
|
||||
<th></th>
|
||||
<th>000</th>
|
||||
<th>001</th>
|
||||
<th>010</th>
|
||||
<th>011</th>
|
||||
<th>100</th>
|
||||
<th>101</th>
|
||||
<th>110</th>
|
||||
<th>111</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Group 1</th>
|
||||
|
||||
<td>ADD</td>
|
||||
<td>OR</td>
|
||||
<td>ADC</td>
|
||||
<td>SBB</td>
|
||||
<td>AND</td>
|
||||
<td>SUB</td>
|
||||
<td>XOR</td>
|
||||
<td>CMP</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Group 2</th>
|
||||
|
||||
<td>ROL</td>
|
||||
<td>ROR</td>
|
||||
<td>RCL</td>
|
||||
<td>RCR</td>
|
||||
<td>SHL</td>
|
||||
<td>SHR</td>
|
||||
<td></td>
|
||||
<td>SAR</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Group 3</th>
|
||||
|
||||
<td>TEST Ib/Iv</td>
|
||||
<td></td>
|
||||
<td>NOT</td>
|
||||
<td>NEG</td>
|
||||
<td>MUL AL/eAX</td>
|
||||
<td>IMUL AL/EAX</td>
|
||||
<td>DIV AL/eAX</td>
|
||||
<td>IDIV AL/eAX</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Group 4</th>
|
||||
|
||||
<td>INC Eb</td>
|
||||
<td>DEC Eb</td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Group 5</th>
|
||||
|
||||
<td>INC Ev</td>
|
||||
<td>DEC Ev</td>
|
||||
<td>CALL Ev</td>
|
||||
<td>CALL Ep</td>
|
||||
<td>JMP Ev</td>
|
||||
<td>JMP Ep</td>
|
||||
<td>PUSH Ev</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Group 6</th>
|
||||
|
||||
<td>SLDT Ew</td>
|
||||
<td>STR Ew</td>
|
||||
<td>LLDT Ew</td>
|
||||
<td>LTR Ew</td>
|
||||
<td>VERR Ew</td>
|
||||
<td>VERW Ew</td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Group 7</th>
|
||||
|
||||
<td>SGDT Ms</td>
|
||||
<td>SIDT Ms</td>
|
||||
<td>LGDT Ms</td>
|
||||
<td>LIDT Ms</td>
|
||||
<td>SMSW Ew</td>
|
||||
<td></td>
|
||||
<td>LMSW Ew</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Group 8</th>
|
||||
|
||||
<td></td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
<td>BT</td>
|
||||
<td>BTS</td>
|
||||
<td>BTR</td>
|
||||
<td>BTC</td>
|
||||
</tr>
|
||||
</table>
|
||||
</body>
|
||||
</html>
|
||||
802
InstructionSets/x86/Instruction.hpp
Normal file
802
InstructionSets/x86/Instruction.hpp
Normal file
@@ -0,0 +1,802 @@
|
||||
//
|
||||
// Instruction.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 15/01/21.
|
||||
// Copyright © 2021 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef InstructionSets_x86_Instruction_h
|
||||
#define InstructionSets_x86_Instruction_h
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <type_traits>
|
||||
|
||||
namespace InstructionSet {
|
||||
namespace x86 {
|
||||
|
||||
/*
|
||||
Operations are documented below to establish expectations as to which
|
||||
instruction fields will be meaningful for each; this is a work-in-progress
|
||||
and may currently contain errors in the opcode descriptions — especially
|
||||
where implicit register dependencies are afoot.
|
||||
*/
|
||||
enum class Operation: uint8_t {
|
||||
Invalid,
|
||||
|
||||
//
|
||||
// 8086 instructions.
|
||||
//
|
||||
|
||||
/// ASCII adjust after addition; source will be AL and destination will be AX.
|
||||
AAA,
|
||||
/// ASCII adjust before division; destination will be AX and source will be a multiplier.
|
||||
AAD,
|
||||
/// ASCII adjust after multiplication; destination will be AX and source will be a divider.
|
||||
AAM,
|
||||
/// ASCII adjust after subtraction; source will be AL and destination will be AX.
|
||||
AAS,
|
||||
/// Decimal adjust after addition; source and destination will be AL.
|
||||
DAA,
|
||||
/// Decimal adjust after subtraction; source and destination will be AL.
|
||||
DAS,
|
||||
|
||||
/// If data size is word, convert byte into word; source will be AL, destination will be AH.
|
||||
/// If data size is DWord, convert word to dword; AX will be expanded to fill EAX.
|
||||
/// In both cases, conversion will be by sign extension.
|
||||
CBW,
|
||||
/// If data size is Word, converts word to double word; source will be AX and destination will be DX.
|
||||
/// If data size is DWord, converts double word to quad word (i.e. CDW); source will be EAX and destination will be EDX:EAX.
|
||||
/// In both cases, conversion will be by sign extension.
|
||||
CWD,
|
||||
|
||||
/// Escape, for a coprocessor; perform the bus cycles necessary to read the source and destination and perform a NOP.
|
||||
ESC,
|
||||
|
||||
/// Stops the processor until the next interrupt is fired.
|
||||
HLT,
|
||||
/// Waits until the WAIT input is asserted; if an interrupt occurs then it is serviced but returns to the WAIT.
|
||||
WAIT,
|
||||
|
||||
/// Add with carry; source, destination, operand and displacement will be populated appropriately.
|
||||
ADC,
|
||||
/// Add; source, destination, operand and displacement will be populated appropriately.
|
||||
ADD,
|
||||
/// Subtract with borrow; source, destination, operand and displacement will be populated appropriately.
|
||||
SBB,
|
||||
/// Subtract; source, destination, operand and displacement will be populated appropriately.
|
||||
SUB,
|
||||
/// Unsigned multiply; multiplies the source value by AX or AL, storing the result in DX:AX or AX.
|
||||
MUL,
|
||||
/// Single operand signed multiply; multiplies the source value by AX or AL, storing the result in DX:AX or AX.
|
||||
IMUL_1,
|
||||
/// Unsigned divide; divide the source value by AX or AL, storing the quotient in AL and the remainder in AH.
|
||||
DIV,
|
||||
/// Signed divide; divide the source value by AX or AL, storing the quotient in AL and the remainder in AH.
|
||||
IDIV,
|
||||
|
||||
/// Increment; source, destination, operand and displacement will be populated appropriately.
|
||||
INC,
|
||||
/// Decrement; source, destination, operand and displacement will be populated appropriately.
|
||||
DEC,
|
||||
|
||||
/// Reads from the port specified by source to the destination.
|
||||
IN,
|
||||
/// Writes from the port specified by destination from the source.
|
||||
OUT,
|
||||
|
||||
// Various jumps; see the displacement to calculate targets.
|
||||
JO, JNO, JB, JNB, JE, JNE, JBE, JNBE,
|
||||
JS, JNS, JP, JNP, JL, JNL, JLE, JNLE,
|
||||
|
||||
/// Far call; see the segment() and offset() fields.
|
||||
CALLfar,
|
||||
/// Relative call; see displacement().
|
||||
CALLrel,
|
||||
/// Near call.
|
||||
CALLabs,
|
||||
/// Return from interrupt.
|
||||
IRET,
|
||||
/// Near return; if source is not ::None then it will be an ::Immediate indicating how many additional bytes to remove from the stack.
|
||||
RETfar,
|
||||
/// Far return; if source is not ::None then it will be an ::Immediate indicating how many additional bytes to remove from the stack.
|
||||
RETnear,
|
||||
/// Near jump with an absolute destination.
|
||||
JMPabs,
|
||||
/// Near jump with a relative destination.
|
||||
JMPrel,
|
||||
/// Far jump to the indicated segment and offset.
|
||||
JMPfar,
|
||||
/// Relative jump performed only if CX = 0; see the displacement.
|
||||
JPCX,
|
||||
/// Generates a software interrupt of the level stated in the operand.
|
||||
INT,
|
||||
/// Generates a software interrupt of level 4 if overflow is set.
|
||||
INTO,
|
||||
|
||||
/// Load status flags to AH.
|
||||
LAHF,
|
||||
/// Load status flags from AH.
|
||||
SAHF,
|
||||
/// Load a segment and offset from the source into DS and the destination.
|
||||
LDS,
|
||||
/// Load a segment and offset from the source into ES and the destination.
|
||||
LES,
|
||||
/// Computes the effective address of the source and loads it into the destination.
|
||||
LEA,
|
||||
|
||||
/// Compare [bytes or words, per operation size]; source and destination implied to be DS:[SI] and ES:[DI].
|
||||
CMPS,
|
||||
/// Load string; reads from DS:SI into AL or AX, subject to segment override.
|
||||
LODS,
|
||||
/// Move string; moves a byte or word from DS:SI to ES:DI. If a segment override is provided, it overrides the the source.
|
||||
MOVS,
|
||||
/// Scan string; reads a byte or word from DS:SI and compares it to AL or AX.
|
||||
SCAS,
|
||||
/// Store string; store AL or AX to ES:DI.
|
||||
STOS,
|
||||
|
||||
// Perform a possibly-conditional loop, decrementing CX. See the displacement.
|
||||
LOOP, LOOPE, LOOPNE,
|
||||
|
||||
/// Loads the destination with the source.
|
||||
MOV,
|
||||
/// Negatives; source and destination point to the same thing, to negative.
|
||||
NEG,
|
||||
/// Logical NOT; source and destination point to the same thing, to negative.
|
||||
NOT,
|
||||
/// Logical AND; source, destination, operand and displacement will be populated appropriately.
|
||||
AND,
|
||||
/// Logical OR of source onto destination.
|
||||
OR,
|
||||
/// Logical XOR of source onto destination.
|
||||
XOR,
|
||||
/// NOP; no further fields.
|
||||
NOP,
|
||||
/// POP from the stack to destination.
|
||||
POP,
|
||||
/// POP from the stack to the flags register.
|
||||
POPF,
|
||||
/// PUSH the source to the stack.
|
||||
PUSH,
|
||||
/// PUSH the flags register to the stack.
|
||||
PUSHF,
|
||||
/// Rotate the destination left through carry the number of bits indicated by source; if the source is a register then implicitly its size is 1.
|
||||
RCL,
|
||||
/// Rotate the destination right through carry the number of bits indicated by source; if the source is a register then implicitly its size is 1.
|
||||
RCR,
|
||||
/// Rotate the destination left the number of bits indicated by source; if the source is a register then implicitly its size is 1.
|
||||
ROL,
|
||||
/// Rotate the destination right the number of bits indicated by source; if the source is a register then implicitly its size is 1.
|
||||
ROR,
|
||||
/// Arithmetic shift left the destination by the number of bits indicated by source; if the source is a register then implicitly its size is 1.
|
||||
SAL,
|
||||
/// Arithmetic shift right the destination by the number of bits indicated by source; if the source is a register then implicitly its size is 1.
|
||||
SAR,
|
||||
/// Logical shift right the destination by the number of bits indicated by source; if the source is a register then implicitly its size is 1.
|
||||
SHR,
|
||||
|
||||
/// Clear carry flag; no source or destination provided.
|
||||
CLC,
|
||||
/// Clear direction flag; no source or destination provided.
|
||||
CLD,
|
||||
/// Clear interrupt flag; no source or destination provided.
|
||||
CLI,
|
||||
/// Set carry flag.
|
||||
STC,
|
||||
/// Set decimal flag.
|
||||
STD,
|
||||
/// Set interrupt flag.
|
||||
STI,
|
||||
/// Complement carry flag; no source or destination provided.
|
||||
CMC,
|
||||
|
||||
/// Compare; source, destination, operand and displacement will be populated appropriately.
|
||||
CMP,
|
||||
/// Sets flags based on the result of a logical AND of source and destination.
|
||||
TEST,
|
||||
|
||||
/// Exchanges the contents of the source and destination.
|
||||
XCHG,
|
||||
|
||||
/// Load AL with DS:[AL+BX].
|
||||
XLAT,
|
||||
|
||||
//
|
||||
// 80186 additions.
|
||||
//
|
||||
|
||||
/// Checks whether the signed value in the destination register is within the bounds
|
||||
/// stored at the location indicated by the source register, which will point to two
|
||||
/// 16- or 32-bit words, the first being a signed lower bound and the signed upper.
|
||||
/// Raises a bounds exception if not.
|
||||
BOUND,
|
||||
|
||||
|
||||
/// Create stack frame. See operand() for the nesting level and offset()
|
||||
/// for the dynamic storage size.
|
||||
ENTER,
|
||||
/// Procedure exit; copies BP to SP, then pops a new BP from the stack.
|
||||
LEAVE,
|
||||
|
||||
/// Inputs a byte, word or double word from the port specified by DX, writing it to
|
||||
/// ES:[e]DI and incrementing or decrementing [e]DI as per the
|
||||
/// current EFLAGS DF flag.
|
||||
INS,
|
||||
/// Outputs a byte, word or double word from ES:[e]DI to the port specified by DX,
|
||||
/// incrementing or decrementing [e]DI as per the current EFLAGS DF flag.]
|
||||
OUTS,
|
||||
|
||||
/// Pushes all general purpose registers to the stack, in the order:
|
||||
/// AX, CX, DX, BX, [original] SP, BP, SI, DI.
|
||||
PUSHA,
|
||||
/// Pops all general purpose registers from the stack, in the reverse of
|
||||
/// the PUSHA order, i.e. DI, SI, BP, [final] SP, BX, DX, CX, AX.
|
||||
POPA,
|
||||
|
||||
//
|
||||
// 80286 additions.
|
||||
//
|
||||
|
||||
// TODO: expand detail on all operations below.
|
||||
|
||||
/// Adjusts requested privilege level.
|
||||
ARPL,
|
||||
/// Clears the task-switched flag.
|
||||
CLTS,
|
||||
/// Loads access rights.
|
||||
LAR,
|
||||
|
||||
/// Loads the global descriptor table.
|
||||
LGDT,
|
||||
/// Loads the interrupt descriptor table.
|
||||
LIDT,
|
||||
/// Loads the local descriptor table.
|
||||
LLDT,
|
||||
/// Stores the global descriptor table.
|
||||
SGDT,
|
||||
/// Stores the interrupt descriptor table.
|
||||
SIDT,
|
||||
/// Stores the local descriptor table.
|
||||
SLDT,
|
||||
|
||||
/// Verifies a segment for reading.
|
||||
VERR,
|
||||
/// Verifies a segment for writing.
|
||||
VERW,
|
||||
|
||||
/// Loads the machine status word.
|
||||
LMSW,
|
||||
/// Stores the machine status word.
|
||||
SMSW,
|
||||
/// Loads a segment limit
|
||||
LSL,
|
||||
/// Loads the task register.
|
||||
LTR,
|
||||
/// Stores the task register.
|
||||
STR,
|
||||
|
||||
/// Three-operand form of IMUL; multiply the immediate by the source and write to the destination.
|
||||
IMUL_3,
|
||||
|
||||
/// Undocumented (but used); loads all registers, including internal ones.
|
||||
LOADALL,
|
||||
|
||||
//
|
||||
// 80386 additions.
|
||||
//
|
||||
|
||||
/// Loads a pointer to FS.
|
||||
LFS,
|
||||
/// Loads a pointer to GS.
|
||||
LGS,
|
||||
/// Loads a pointer to SS.
|
||||
LSS,
|
||||
|
||||
/// Shift left double.
|
||||
SHLDimm,
|
||||
SHLDCL,
|
||||
/// Shift right double.
|
||||
SHRDimm,
|
||||
SHRDCL,
|
||||
|
||||
/// Bit scan forwards.
|
||||
BSF,
|
||||
/// Bit scan reverse.
|
||||
BSR,
|
||||
/// Bit test.
|
||||
BT,
|
||||
/// Bit test and complement.
|
||||
BTC,
|
||||
/// Bit test and reset.
|
||||
BTR,
|
||||
/// Bit test and set.
|
||||
BTS,
|
||||
|
||||
/// Move from the source to the destination, extending the source with zeros.
|
||||
/// The instruction data size dictates the size of the source; the destination will
|
||||
/// be either 16- or 32-bit depending on the current processor operating mode.
|
||||
MOVZX,
|
||||
/// Move from the source to the destination, applying a sign extension.
|
||||
/// The instruction data size dictates the size of the source; the destination will
|
||||
/// be either 16- or 32-bit depending on the current processor operating mode.
|
||||
MOVSX,
|
||||
|
||||
/// Two-operand form of IMUL; multiply the source by the destination and write to the destination.
|
||||
IMUL_2,
|
||||
|
||||
// Various conditional sets; each sets the byte at the location given by the operand
|
||||
// to $ff if the condition is met; $00 otherwise.
|
||||
SETO, SETNO, SETB, SETNB, SETZ, SETNZ, SETBE, SETNBE,
|
||||
SETS, SETNS, SETP, SETNP, SETL, SETNL, SETLE, SETNLE,
|
||||
|
||||
// Various special-case moves (i.e. those where it is impractical to extend the
|
||||
// Source enum, so the requirement for special handling is loaded into the operation).
|
||||
// In all cases the Cx, Dx and Tx Source aliases can be used to reinterpret the relevant
|
||||
// source or destination.
|
||||
MOVtoCr, MOVfromCr,
|
||||
MOVtoDr, MOVfromDr,
|
||||
MOVtoTr, MOVfromTr,
|
||||
};
|
||||
|
||||
enum class DataSize: uint8_t {
|
||||
Byte = 0,
|
||||
Word = 1,
|
||||
DWord = 2,
|
||||
None = 3,
|
||||
};
|
||||
|
||||
constexpr int byte_size(DataSize size) {
|
||||
return (1 << int(size)) & 7;
|
||||
}
|
||||
|
||||
constexpr int bit_size(DataSize size) {
|
||||
return (8 << int(size)) & 0x3f;
|
||||
}
|
||||
|
||||
enum class AddressSize: uint8_t {
|
||||
b16 = 0,
|
||||
b32 = 1,
|
||||
};
|
||||
|
||||
constexpr DataSize data_size(AddressSize size) {
|
||||
return DataSize(int(size) + 1);
|
||||
}
|
||||
|
||||
constexpr int byte_size(AddressSize size) {
|
||||
return 2 << int(size);
|
||||
}
|
||||
|
||||
constexpr int bit_size(AddressSize size) {
|
||||
return 16 << int(size);
|
||||
}
|
||||
|
||||
enum class Source: uint8_t {
|
||||
// These are in SIB order; this matters for packing later on.
|
||||
|
||||
/// AL, AX or EAX depending on size.
|
||||
eAX,
|
||||
/// CL, CX or ECX depending on size.
|
||||
eCX,
|
||||
/// DL, DX or EDX depending on size.
|
||||
eDX,
|
||||
/// BL, BX or BDX depending on size.
|
||||
eBX,
|
||||
/// AH if size is 1; SP or ESP otherwise.
|
||||
eSPorAH,
|
||||
/// CH if size is 1; BP or EBP otherwise.
|
||||
eBPorCH,
|
||||
/// DH if size is 1; SI or ESI otherwise.
|
||||
eSIorDH,
|
||||
/// BH if size is 1; DI or EDI otherwise.
|
||||
eDIorBH,
|
||||
|
||||
// Aliases for the dual-purpose enums.
|
||||
eSP = eSPorAH, AH = eSPorAH,
|
||||
eBP = eBPorCH, CH = eBPorCH,
|
||||
eSI = eSIorDH, DH = eSIorDH,
|
||||
eDI = eDIorBH, BH = eDIorBH,
|
||||
|
||||
// Aliases for control, test and debug registers.
|
||||
C0 = 0, C1 = 1, C2 = 2, C3 = 3, C4 = 4, C5 = 5, C6 = 6, C7 = 7,
|
||||
T0 = 0, T1 = 1, T2 = 2, T3 = 3, T4 = 4, T5 = 5, T6 = 6, T7 = 7,
|
||||
D0 = 0, D1 = 1, D2 = 2, D3 = 3, D4 = 4, D5 = 5, D6 = 6, D7 = 7,
|
||||
|
||||
// Selectors.
|
||||
ES, CS, SS, DS, FS, GS,
|
||||
|
||||
/// @c None can be treated as a source that produces 0 when encountered;
|
||||
/// it is semantically valid to receive it with that meaning in some contexts —
|
||||
/// e.g. to indicate no index in indirect addressing.
|
||||
/// It's listed here in order to allow an [optional] segment override to fit into three bits.
|
||||
None,
|
||||
|
||||
/// The address included within this instruction should be used as the source.
|
||||
DirectAddress,
|
||||
|
||||
/// The immediate value included within this instruction should be used as the source.
|
||||
Immediate,
|
||||
|
||||
/// The ScaleIndexBase associated with this source should be used.
|
||||
Indirect = 0b11000,
|
||||
// Elsewhere, as an implementation detail, the low three bits of an indirect source
|
||||
// are reused; (Indirect-1) is also used as a sentinel value but is not a valid member
|
||||
// of the enum and isn't exposed externally.
|
||||
|
||||
/// The ScaleIndexBase associated with this source should be used, but
|
||||
/// its base should be ignored (and is guaranteed to be zero if the default
|
||||
/// getter is used).
|
||||
IndirectNoBase = Indirect - 1,
|
||||
};
|
||||
|
||||
enum class Repetition: uint8_t {
|
||||
None, RepE, RepNE
|
||||
};
|
||||
|
||||
/// Provides a 32-bit-style scale, index and base; to produce the address this represents,
|
||||
/// calcluate base() + (index() << scale()).
|
||||
///
|
||||
/// This form of indirect addressing is used to describe both 16- and 32-bit indirect addresses,
|
||||
/// even though it is a superset of that supported prior to the 80386.
|
||||
///
|
||||
/// This class can represent only exactly what a SIB byte can — a scale of 0 to 3, a base
|
||||
/// that is any one of the eight general purpose registers, and an index that is one of the seven
|
||||
/// general purpose registers excluding eSP or is ::None.
|
||||
///
|
||||
/// It cannot natively describe a base of ::None.
|
||||
class ScaleIndexBase {
|
||||
public:
|
||||
constexpr ScaleIndexBase() noexcept {}
|
||||
constexpr ScaleIndexBase(uint8_t sib) noexcept : sib_(sib) {}
|
||||
constexpr ScaleIndexBase(int scale, Source index, Source base) noexcept :
|
||||
sib_(uint8_t(
|
||||
scale << 6 |
|
||||
(int(index != Source::None ? index : Source::eSI) << 3) |
|
||||
int(base)
|
||||
)) {}
|
||||
constexpr ScaleIndexBase(Source index, Source base) noexcept : ScaleIndexBase(0, index, base) {}
|
||||
constexpr explicit ScaleIndexBase(Source base) noexcept : ScaleIndexBase(0, Source::None, base) {}
|
||||
|
||||
/// @returns the power of two by which to multiply @c index() before adding it to @c base().
|
||||
constexpr int scale() const {
|
||||
return sib_ >> 6;
|
||||
}
|
||||
|
||||
/// @returns the @c index for this address; this is guaranteed to be one of eAX, eBX, eCX, eDX, None, eBP, eSI or eDI.
|
||||
constexpr Source index() const {
|
||||
constexpr Source sources[] = {
|
||||
Source::eAX, Source::eCX, Source::eDX, Source::eBX, Source::None, Source::eBP, Source::eSI, Source::eDI,
|
||||
};
|
||||
static_assert(sizeof(sources) == 8);
|
||||
return sources[(sib_ >> 3) & 0x7];
|
||||
}
|
||||
|
||||
/// @returns the @c base for this address; this is guaranteed to be one of eAX, eBX, eCX, eDX, eSP, eBP, eSI or eDI.
|
||||
constexpr Source base() const {
|
||||
return Source(sib_ & 0x7);
|
||||
}
|
||||
|
||||
constexpr uint8_t without_base() const {
|
||||
return sib_ & ~0x3;
|
||||
}
|
||||
|
||||
bool operator ==(const ScaleIndexBase &rhs) const {
|
||||
// Permit either exact equality or index and base being equal
|
||||
// but transposed with a scale of 1.
|
||||
return
|
||||
(sib_ == rhs.sib_) ||
|
||||
(
|
||||
!scale() && !rhs.scale() &&
|
||||
rhs.index() == base() &&
|
||||
rhs.base() == index()
|
||||
);
|
||||
}
|
||||
|
||||
operator uint8_t() const {
|
||||
return sib_;
|
||||
}
|
||||
|
||||
private:
|
||||
// Data is stored directly as an 80386 SIB byte.
|
||||
uint8_t sib_ = 0;
|
||||
};
|
||||
static_assert(sizeof(ScaleIndexBase) == 1);
|
||||
static_assert(alignof(ScaleIndexBase) == 1);
|
||||
|
||||
/// Provides the location of an operand's source or destination.
|
||||
///
|
||||
/// Callers should use .source() as a first point of entry. If it directly nominates a register
|
||||
/// then use the register contents directly. If it indicates ::DirectAddress or ::Immediate
|
||||
/// then ask the instruction for the address or immediate value that was provided in
|
||||
/// the instruction.
|
||||
///
|
||||
/// If .source() indicates ::Indirect then use base(), index() and scale() to construct an address.
|
||||
///
|
||||
/// In all cases, the applicable segment is indicated by the instruction.
|
||||
class DataPointer {
|
||||
public:
|
||||
/// Constricts a DataPointer referring to the given source; it shouldn't be ::Indirect.
|
||||
constexpr DataPointer(Source source) noexcept : source_(source) {}
|
||||
|
||||
/// Constricts a DataPointer with a source of ::Indirect and the specified sib.
|
||||
constexpr DataPointer(ScaleIndexBase sib) noexcept : sib_(sib) {}
|
||||
|
||||
/// Constructs a DataPointer with a source and SIB; use the source to indicate
|
||||
/// whether the base field of the SIB is effective.
|
||||
constexpr DataPointer(Source source, ScaleIndexBase sib) noexcept : source_(source), sib_(sib) {}
|
||||
|
||||
/// Constructs an indirect DataPointer referencing the given base, index and scale.
|
||||
/// Automatically maps Source::Indirect to Source::IndirectNoBase if base is Source::None.
|
||||
constexpr DataPointer(Source base, Source index, int scale) noexcept :
|
||||
source_(base != Source::None ? Source::Indirect : Source::IndirectNoBase),
|
||||
sib_(scale, index, base) {}
|
||||
|
||||
constexpr bool operator ==(const DataPointer &rhs) const {
|
||||
// Require a SIB match only if source_ is ::Indirect or ::IndirectNoBase.
|
||||
return
|
||||
source_ == rhs.source_ && (
|
||||
source_ < Source::IndirectNoBase ||
|
||||
(source_ == Source::Indirect && sib_ == rhs.sib_) ||
|
||||
(source_ == Source::IndirectNoBase && sib_.without_base() == rhs.sib_.without_base())
|
||||
);
|
||||
}
|
||||
|
||||
template <bool obscure_indirectNoBase = false> constexpr Source source() const {
|
||||
if constexpr (obscure_indirectNoBase) {
|
||||
return (source_ >= Source::IndirectNoBase) ? Source::Indirect : source_;
|
||||
}
|
||||
return source_;
|
||||
}
|
||||
|
||||
constexpr int scale() const {
|
||||
return sib_.scale();
|
||||
}
|
||||
|
||||
constexpr Source index() const {
|
||||
return sib_.index();
|
||||
}
|
||||
|
||||
template <bool obscure_indirectNoBase = false> constexpr Source base() const {
|
||||
if constexpr (obscure_indirectNoBase) {
|
||||
return (source_ <= Source::IndirectNoBase) ? Source::None : sib_.base();
|
||||
}
|
||||
return sib_.base();
|
||||
}
|
||||
|
||||
private:
|
||||
Source source_ = Source::Indirect;
|
||||
ScaleIndexBase sib_;
|
||||
};
|
||||
|
||||
template<bool is_32bit> class Instruction {
|
||||
public:
|
||||
Operation operation = Operation::Invalid;
|
||||
|
||||
bool operator ==(const Instruction<is_32bit> &rhs) const {
|
||||
if( operation != rhs.operation ||
|
||||
mem_exts_source_ != rhs.mem_exts_source_ ||
|
||||
source_data_dest_sib_ != rhs.source_data_dest_sib_) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Have already established above that this and RHS have the
|
||||
// same extensions, if any.
|
||||
const int extension_count = has_length_extension() + has_displacement() + has_operand();
|
||||
for(int c = 0; c < extension_count; c++) {
|
||||
if(extensions_[c] != rhs.extensions_[c]) return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
using DisplacementT = typename std::conditional<is_32bit, int32_t, int16_t>::type;
|
||||
using ImmediateT = typename std::conditional<is_32bit, uint32_t, uint16_t>::type;
|
||||
using AddressT = ImmediateT;
|
||||
|
||||
private:
|
||||
// Packing and encoding of fields is admittedly somewhat convoluted; what this
|
||||
// achieves is that instructions will be sized:
|
||||
//
|
||||
// four bytes + up to three extension words
|
||||
// (two bytes for 16-bit instructions, four for 32)
|
||||
//
|
||||
// Two of the extension words are used to retain an operand and displacement
|
||||
// if the instruction has those. The other can store sizes greater than 15
|
||||
// bytes (for earlier processors), plus any repetition, segment override or
|
||||
// repetition prefixes.
|
||||
|
||||
// b7: address size;
|
||||
// b6: has displacement;
|
||||
// b5: has operand;
|
||||
// [b4, b0]: source.
|
||||
uint8_t mem_exts_source_ = 0;
|
||||
|
||||
bool has_displacement() const {
|
||||
return mem_exts_source_ & (1 << 6);
|
||||
}
|
||||
bool has_operand() const {
|
||||
return mem_exts_source_ & (1 << 5);
|
||||
}
|
||||
|
||||
// [b15, b14]: data size;
|
||||
// [b13, b10]: source length (0 => has length extension);
|
||||
// [b9, b5]: top five of SIB;
|
||||
// [b4, b0]: dest.
|
||||
uint16_t source_data_dest_sib_ = 1 << 10; // So that ::Invalid doesn't seem to have a length extension.
|
||||
|
||||
bool has_length_extension() const {
|
||||
return !((source_data_dest_sib_ >> 10) & 15);
|
||||
}
|
||||
|
||||
// {operand}, {displacement}, {length extension}.
|
||||
//
|
||||
// If length extension is present then:
|
||||
//
|
||||
// [b15, b6]: source length;
|
||||
// [b5, b4]: repetition;
|
||||
// [b3, b1]: segment override;
|
||||
// b0: lock.
|
||||
ImmediateT extensions_[3]{};
|
||||
|
||||
ImmediateT operand_extension() const {
|
||||
return extensions_[0];
|
||||
}
|
||||
ImmediateT displacement_extension() const {
|
||||
return extensions_[(mem_exts_source_ >> 5) & 1];
|
||||
}
|
||||
ImmediateT length_extension() const {
|
||||
return extensions_[((mem_exts_source_ >> 5) & 1) + ((mem_exts_source_ >> 6) & 1)];
|
||||
}
|
||||
|
||||
public:
|
||||
/// @returns The number of bytes used for meaningful content within this class. A receiver must use at least @c sizeof(Instruction) bytes
|
||||
/// to store an @c Instruction but is permitted to reuse the trailing sizeof(Instruction) - packing_size() for any purpose it likes. Teleologically,
|
||||
/// this allows a denser packing of instructions into containers.
|
||||
size_t packing_size() const {
|
||||
return
|
||||
offsetof(Instruction<is_32bit>, extensions) +
|
||||
(has_displacement() + has_operand() + has_length_extension()) * sizeof(ImmediateT);
|
||||
|
||||
// To consider in the future: the length extension is always the last one,
|
||||
// and uses only 8 bits of content within 32-bit instructions, so it'd be
|
||||
// possible further to trim the packing size on little endian machines.
|
||||
//
|
||||
// ... but is that a speed improvement? How much space does it save, and
|
||||
// is it enough to undo the costs of unaligned data?
|
||||
}
|
||||
|
||||
private:
|
||||
// A lookup table to help with stripping parts of the SIB that have been
|
||||
// hidden within the source/destination fields.
|
||||
static constexpr uint8_t sib_masks[] = {
|
||||
0x1f, 0x1f, 0x1f, 0x18
|
||||
};
|
||||
|
||||
public:
|
||||
DataPointer source() const {
|
||||
return DataPointer(
|
||||
Source(mem_exts_source_ & sib_masks[(mem_exts_source_ >> 3) & 3]),
|
||||
((source_data_dest_sib_ >> 2) & 0xf8) | (mem_exts_source_ & 0x07)
|
||||
);
|
||||
}
|
||||
DataPointer destination() const {
|
||||
return DataPointer(
|
||||
Source(source_data_dest_sib_ & sib_masks[(source_data_dest_sib_ >> 3) & 3]),
|
||||
((source_data_dest_sib_ >> 2) & 0xf8) | (source_data_dest_sib_ & 0x07)
|
||||
);
|
||||
}
|
||||
bool lock() const {
|
||||
return has_length_extension() && length_extension()&1;
|
||||
}
|
||||
|
||||
AddressSize address_size() const {
|
||||
return AddressSize(mem_exts_source_ >> 7);
|
||||
}
|
||||
|
||||
/// @returns @c Source::DS if no segment override was found; the overridden segment otherwise.
|
||||
/// On x86 a segment override cannot modify the segment used as a destination in string instructions,
|
||||
/// or that used by stack instructions, but this function does not spend the time necessary to provide
|
||||
/// the correct default for those.
|
||||
Source data_segment() const {
|
||||
if(!has_length_extension()) return Source::DS;
|
||||
return Source(
|
||||
int(Source::ES) +
|
||||
((length_extension() >> 1) & 7)
|
||||
);
|
||||
}
|
||||
|
||||
Repetition repetition() const {
|
||||
if(!has_length_extension()) return Repetition::None;
|
||||
return Repetition((length_extension() >> 4) & 3);
|
||||
}
|
||||
DataSize operation_size() const {
|
||||
return DataSize(source_data_dest_sib_ >> 14);
|
||||
}
|
||||
|
||||
int length() const {
|
||||
const int short_length = (source_data_dest_sib_ >> 10) & 15;
|
||||
if(short_length) return short_length;
|
||||
return length_extension() >> 6;
|
||||
}
|
||||
|
||||
ImmediateT operand() const {
|
||||
const ImmediateT ops[] = {0, operand_extension()};
|
||||
return ops[has_operand()];
|
||||
}
|
||||
DisplacementT displacement() const {
|
||||
return DisplacementT(offset());
|
||||
}
|
||||
|
||||
uint16_t segment() const {
|
||||
return uint16_t(operand());
|
||||
}
|
||||
ImmediateT offset() const {
|
||||
const ImmediateT offsets[] = {0, displacement_extension()};
|
||||
return offsets[has_displacement()];
|
||||
}
|
||||
|
||||
constexpr Instruction() noexcept {}
|
||||
constexpr Instruction(Operation operation, int length) noexcept :
|
||||
Instruction(operation, Source::None, Source::None, ScaleIndexBase(), false, AddressSize::b16, Source::None, Repetition::None, DataSize::None, 0, 0, length) {}
|
||||
constexpr Instruction(
|
||||
Operation operation,
|
||||
Source source,
|
||||
Source destination,
|
||||
ScaleIndexBase sib,
|
||||
bool lock,
|
||||
AddressSize address_size,
|
||||
Source segment_override,
|
||||
Repetition repetition,
|
||||
DataSize data_size,
|
||||
DisplacementT displacement,
|
||||
ImmediateT operand,
|
||||
int length) noexcept :
|
||||
operation(operation),
|
||||
mem_exts_source_(uint8_t(
|
||||
(int(address_size) << 7) |
|
||||
(displacement ? 0x40 : 0x00) |
|
||||
(operand ? 0x20 : 0x00) |
|
||||
int(source) |
|
||||
(source == Source::Indirect ? (uint8_t(sib) & 7) : 0)
|
||||
)),
|
||||
source_data_dest_sib_(uint16_t(
|
||||
(int(data_size) << 14) |
|
||||
((
|
||||
(lock || (segment_override != Source::None) || (length > 15) || (repetition != Repetition::None))
|
||||
) ? 0 : (length << 10)) |
|
||||
((uint8_t(sib) & 0xf8) << 2) |
|
||||
int(destination) |
|
||||
(destination == Source::Indirect ? (uint8_t(sib) & 7) : 0)
|
||||
)) {
|
||||
|
||||
// Decisions on whether to include operand, displacement and/or size extension words
|
||||
// have implicitly been made in the int packing above; honour them here.
|
||||
int extension = 0;
|
||||
if(has_operand()) {
|
||||
extensions_[extension] = operand;
|
||||
++extension;
|
||||
}
|
||||
if(has_displacement()) {
|
||||
extensions_[extension] = ImmediateT(displacement);
|
||||
++extension;
|
||||
}
|
||||
if(has_length_extension()) {
|
||||
// As per the rule stated for segment(), this class provides ::DS for any instruction
|
||||
// that doesn't have a segment override.
|
||||
if(segment_override == Source::None) segment_override = Source::DS;
|
||||
extensions_[extension] = ImmediateT(
|
||||
(length << 6) | (int(repetition) << 4) | ((int(segment_override) & 7) << 1) | int(lock)
|
||||
);
|
||||
++extension;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
static_assert(sizeof(Instruction<true>) <= 16);
|
||||
static_assert(sizeof(Instruction<false>) <= 10);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* InstructionSets_x86_Instruction_h */
|
||||
27
InstructionSets/x86/Model.hpp
Normal file
27
InstructionSets/x86/Model.hpp
Normal file
@@ -0,0 +1,27 @@
|
||||
//
|
||||
// Model.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 27/02/2022.
|
||||
// Copyright © 2022 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef Model_h
|
||||
#define Model_h
|
||||
|
||||
namespace InstructionSet {
|
||||
namespace x86 {
|
||||
|
||||
enum class Model {
|
||||
i8086,
|
||||
i80186,
|
||||
i80286,
|
||||
i80386,
|
||||
};
|
||||
|
||||
static constexpr bool is_32bit(Model model) { return model >= Model::i80386; }
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* Model_h */
|
||||
262
Machines/Amiga/Amiga.cpp
Normal file
262
Machines/Amiga/Amiga.cpp
Normal file
@@ -0,0 +1,262 @@
|
||||
//
|
||||
// Amiga.cpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 16/07/2021.
|
||||
// Copyright © 2021 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#include "Amiga.hpp"
|
||||
|
||||
#include "../../Activity/Source.hpp"
|
||||
#include "../MachineTypes.hpp"
|
||||
|
||||
#include "../../Processors/68000Mk2/68000Mk2.hpp"
|
||||
|
||||
#include "../../Analyser/Static/Amiga/Target.hpp"
|
||||
|
||||
#include "../Utility/MemoryPacker.hpp"
|
||||
#include "../Utility/MemoryFuzzer.hpp"
|
||||
|
||||
//#define NDEBUG
|
||||
#define LOG_PREFIX "[Amiga] "
|
||||
#include "../../Outputs/Log.hpp"
|
||||
|
||||
#include "Chipset.hpp"
|
||||
#include "Keyboard.hpp"
|
||||
#include "MemoryMap.hpp"
|
||||
|
||||
#include <cassert>
|
||||
|
||||
namespace {
|
||||
|
||||
// NTSC clock rate: 2*3.579545 = 7.15909Mhz.
|
||||
// PAL clock rate: 7.09379Mhz; 227 cycles/line.
|
||||
constexpr int PALClockRate = 7'093'790;
|
||||
//constexpr int NTSCClockRate = 7'159'090;
|
||||
|
||||
}
|
||||
|
||||
namespace Amiga {
|
||||
|
||||
class ConcreteMachine:
|
||||
public Activity::Source,
|
||||
public CPU::MC68000Mk2::BusHandler,
|
||||
public MachineTypes::AudioProducer,
|
||||
public MachineTypes::JoystickMachine,
|
||||
public MachineTypes::MappedKeyboardMachine,
|
||||
public MachineTypes::MediaTarget,
|
||||
public MachineTypes::MouseMachine,
|
||||
public MachineTypes::ScanProducer,
|
||||
public MachineTypes::TimedMachine,
|
||||
public Machine {
|
||||
public:
|
||||
ConcreteMachine(const Analyser::Static::Amiga::Target &target, const ROMMachine::ROMFetcher &rom_fetcher) :
|
||||
mc68000_(*this),
|
||||
memory_(target.chip_ram, target.fast_ram),
|
||||
chipset_(memory_, PALClockRate)
|
||||
{
|
||||
// Temporary: use a hard-coded Kickstart selection.
|
||||
constexpr ROM::Name rom_name = ROM::Name::AmigaA500Kickstart13;
|
||||
ROM::Request request(rom_name);
|
||||
auto roms = rom_fetcher(request);
|
||||
if(!request.validate(roms)) {
|
||||
throw ROMMachine::Error::MissingROMs;
|
||||
}
|
||||
Memory::PackBigEndian16(roms.find(rom_name)->second, memory_.kickstart.data());
|
||||
|
||||
// For now, also hard-code assumption of PAL.
|
||||
// (Assumption is both here and in the video timing of the Chipset).
|
||||
set_clock_rate(PALClockRate);
|
||||
|
||||
// Insert supplied media.
|
||||
insert_media(target.media);
|
||||
}
|
||||
|
||||
// MARK: - MediaTarget.
|
||||
|
||||
bool insert_media(const Analyser::Static::Media &media) final {
|
||||
return chipset_.insert(media.disks);
|
||||
}
|
||||
|
||||
// MARK: - MC68000::BusHandler.
|
||||
template <typename Microcycle> HalfCycles perform_bus_operation(const Microcycle &cycle, int) {
|
||||
|
||||
// Do a quick advance check for Chip RAM access; add a suitable delay if required.
|
||||
HalfCycles total_length;
|
||||
if(cycle.operation & Microcycle::NewAddress && *cycle.address < 0x20'0000) {
|
||||
total_length = chipset_.run_until_after_cpu_slot().duration;
|
||||
assert(total_length >= cycle.length);
|
||||
} else {
|
||||
total_length = cycle.length;
|
||||
chipset_.run_for(total_length);
|
||||
}
|
||||
mc68000_.set_interrupt_level(chipset_.get_interrupt_level());
|
||||
|
||||
// Check for assertion of reset.
|
||||
if(cycle.operation & Microcycle::Reset) {
|
||||
memory_.reset();
|
||||
LOG("Reset; PC is around " << PADHEX(8) << mc68000_.get_state().registers.program_counter);
|
||||
}
|
||||
|
||||
// Autovector interrupts.
|
||||
if(cycle.operation & Microcycle::InterruptAcknowledge) {
|
||||
mc68000_.set_is_peripheral_address(true);
|
||||
return total_length - cycle.length;
|
||||
}
|
||||
|
||||
// Do nothing if no address is exposed.
|
||||
if(!(cycle.operation & (Microcycle::NewAddress | Microcycle::SameAddress))) return total_length - cycle.length;
|
||||
|
||||
// Grab the target address to pick a memory source.
|
||||
const uint32_t address = cycle.host_endian_byte_address();
|
||||
|
||||
// Set VPA if this is [going to be] a CIA access.
|
||||
mc68000_.set_is_peripheral_address((address & 0xe0'0000) == 0xa0'0000);
|
||||
|
||||
if(!memory_.regions[address >> 18].read_write_mask) {
|
||||
if((cycle.operation & (Microcycle::SelectByte | Microcycle::SelectWord))) {
|
||||
// Check for various potential chip accesses.
|
||||
|
||||
// Per the manual:
|
||||
//
|
||||
// CIA A is: 101x xxxx xx01 rrrr xxxx xxx0 (i.e. loaded into high byte)
|
||||
// CIA B is: 101x xxxx xx10 rrrr xxxx xxx1 (i.e. loaded into low byte)
|
||||
//
|
||||
// but in order to map 0xbfexxx to CIA A and 0xbfdxxx to CIA B, I think
|
||||
// these might be listed the wrong way around.
|
||||
//
|
||||
// Additional assumption: the relevant CIA select lines are connected
|
||||
// directly to the chip enables.
|
||||
if((address & 0xe0'0000) == 0xa0'0000) {
|
||||
const int reg = address >> 8;
|
||||
const bool select_a = !(address & 0x1000);
|
||||
const bool select_b = !(address & 0x2000);
|
||||
|
||||
if(cycle.operation & Microcycle::Read) {
|
||||
uint16_t result = 0xffff;
|
||||
if(select_a) result &= 0xff00 | (chipset_.cia_a.read(reg) << 0);
|
||||
if(select_b) result &= 0x00ff | (chipset_.cia_b.read(reg) << 8);
|
||||
cycle.set_value16(result);
|
||||
} else {
|
||||
if(select_a) chipset_.cia_a.write(reg, cycle.value8_low());
|
||||
if(select_b) chipset_.cia_b.write(reg, cycle.value8_high());
|
||||
}
|
||||
|
||||
// LOG("CIA " << (((address >> 12) & 3)^3) << " " << (cycle.operation & Microcycle::Read ? "read " : "write ") << std::dec << (reg & 0xf) << " of " << PADHEX(4) << +cycle.value16());
|
||||
} else if(address >= 0xdf'f000 && address <= 0xdf'f1be) {
|
||||
chipset_.perform(cycle);
|
||||
} else if(address >= 0xe8'0000 && address < 0xe9'0000) {
|
||||
// This is the Autoconf space; right now the only
|
||||
// Autoconf device this emulator implements is fast RAM,
|
||||
// which if present is provided as part of the memory map.
|
||||
//
|
||||
// Relevant quote: "The Zorro II configuration space is the 64K memory block $00E8xxxx"
|
||||
memory_.perform(cycle);
|
||||
} else {
|
||||
// This'll do for open bus, for now.
|
||||
if(cycle.operation & Microcycle::Read) {
|
||||
cycle.set_value16(0xffff);
|
||||
}
|
||||
|
||||
// Don't log for the region that is definitely just ROM this machine doesn't have.
|
||||
if(address < 0xf0'0000) {
|
||||
LOG("Unmapped " << (cycle.operation & Microcycle::Read ? "read from " : "write to ") << PADHEX(6) << ((*cycle.address)&0xffffff) << " of " << cycle.value16());
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// A regular memory access.
|
||||
cycle.apply(
|
||||
&memory_.regions[address >> 18].contents[address],
|
||||
memory_.regions[address >> 18].read_write_mask
|
||||
);
|
||||
}
|
||||
|
||||
return total_length - cycle.length;
|
||||
}
|
||||
|
||||
void flush() {
|
||||
chipset_.flush();
|
||||
}
|
||||
|
||||
private:
|
||||
CPU::MC68000Mk2::Processor<ConcreteMachine, true, true> mc68000_;
|
||||
|
||||
// MARK: - Memory map.
|
||||
|
||||
MemoryMap memory_;
|
||||
|
||||
// MARK: - Chipset.
|
||||
|
||||
Chipset chipset_;
|
||||
|
||||
// MARK: - Activity Source
|
||||
|
||||
void set_activity_observer(Activity::Observer *observer) final {
|
||||
chipset_.set_activity_observer(observer);
|
||||
}
|
||||
|
||||
// MARK: - MachineTypes::AudioProducer.
|
||||
|
||||
Outputs::Speaker::Speaker *get_speaker() final {
|
||||
return chipset_.get_speaker();
|
||||
}
|
||||
|
||||
// MARK: - MachineTypes::ScanProducer.
|
||||
|
||||
void set_scan_target(Outputs::Display::ScanTarget *scan_target) final {
|
||||
chipset_.set_scan_target(scan_target);
|
||||
}
|
||||
|
||||
Outputs::Display::ScanStatus get_scaled_scan_status() const {
|
||||
return chipset_.get_scaled_scan_status();
|
||||
}
|
||||
|
||||
// MARK: - MachineTypes::TimedMachine.
|
||||
|
||||
void run_for(const Cycles cycles) {
|
||||
mc68000_.run_for(cycles);
|
||||
flush();
|
||||
}
|
||||
|
||||
// MARK: - MachineTypes::MouseMachine.
|
||||
|
||||
Inputs::Mouse &get_mouse() final {
|
||||
return chipset_.get_mouse();;
|
||||
}
|
||||
|
||||
// MARK: - MachineTypes::JoystickMachine.
|
||||
|
||||
const std::vector<std::unique_ptr<Inputs::Joystick>> &get_joysticks() {
|
||||
return chipset_.get_joysticks();
|
||||
}
|
||||
|
||||
// MARK: - Keyboard.
|
||||
|
||||
Amiga::KeyboardMapper keyboard_mapper_;
|
||||
KeyboardMapper *get_keyboard_mapper() {
|
||||
return &keyboard_mapper_;
|
||||
}
|
||||
|
||||
void set_key_state(uint16_t key, bool is_pressed) {
|
||||
chipset_.get_keyboard().set_key_state(key, is_pressed);
|
||||
}
|
||||
|
||||
void clear_all_keys() {
|
||||
chipset_.get_keyboard().clear_all_keys();
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
|
||||
using namespace Amiga;
|
||||
|
||||
Machine *Machine::Amiga(const Analyser::Static::Target *target, const ROMMachine::ROMFetcher &rom_fetcher) {
|
||||
using Target = Analyser::Static::Amiga::Target;
|
||||
const Target *const amiga_target = dynamic_cast<const Target *>(target);
|
||||
return new Amiga::ConcreteMachine(*amiga_target, rom_fetcher);
|
||||
}
|
||||
|
||||
Machine::~Machine() {}
|
||||
27
Machines/Amiga/Amiga.hpp
Normal file
27
Machines/Amiga/Amiga.hpp
Normal file
@@ -0,0 +1,27 @@
|
||||
//
|
||||
// Amiga.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 16/07/2021.
|
||||
// Copyright © 2021 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef Amiga_hpp
|
||||
#define Amiga_hpp
|
||||
|
||||
#include "../../Analyser/Static/StaticAnalyser.hpp"
|
||||
#include "../ROMMachine.hpp"
|
||||
|
||||
namespace Amiga {
|
||||
|
||||
class Machine {
|
||||
public:
|
||||
virtual ~Machine();
|
||||
|
||||
/// Creates and returns an Amiga.
|
||||
static Machine *Amiga(const Analyser::Static::Target *target, const ROMMachine::ROMFetcher &rom_fetcher);
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif /* Amiga_hpp */
|
||||
678
Machines/Amiga/Audio.cpp
Normal file
678
Machines/Amiga/Audio.cpp
Normal file
@@ -0,0 +1,678 @@
|
||||
//
|
||||
// Audio.cpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 09/11/2021.
|
||||
// Copyright © 2021 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#include "Audio.hpp"
|
||||
|
||||
#include "Flags.hpp"
|
||||
|
||||
#define LOG_PREFIX "[Audio] "
|
||||
#include "../../Outputs/Log.hpp"
|
||||
|
||||
#include <cassert>
|
||||
#include <tuple>
|
||||
|
||||
using namespace Amiga;
|
||||
|
||||
Audio::Audio(Chipset &chipset, uint16_t *ram, size_t word_size, float output_rate) :
|
||||
DMADevice<4>(chipset, ram, word_size) {
|
||||
|
||||
// Mark all buffers as available.
|
||||
for(auto &flag: buffer_available_) {
|
||||
flag.store(true, std::memory_order::memory_order_relaxed);
|
||||
}
|
||||
|
||||
speaker_.set_input_rate(output_rate);
|
||||
speaker_.set_high_frequency_cutoff(7000.0f);
|
||||
}
|
||||
|
||||
// MARK: - Exposed setters.
|
||||
|
||||
void Audio::set_length(int channel, uint16_t length) {
|
||||
assert(channel >= 0 && channel < 4);
|
||||
channels_[channel].length = length;
|
||||
}
|
||||
|
||||
void Audio::set_period(int channel, uint16_t period) {
|
||||
assert(channel >= 0 && channel < 4);
|
||||
channels_[channel].period = period;
|
||||
}
|
||||
|
||||
void Audio::set_volume(int channel, uint16_t volume) {
|
||||
assert(channel >= 0 && channel < 4);
|
||||
channels_[channel].volume = (volume & 0x40) ? 64 : (volume & 0x3f);
|
||||
}
|
||||
|
||||
template <bool is_external> void Audio::set_data(int channel, uint16_t data) {
|
||||
assert(channel >= 0 && channel < 4);
|
||||
channels_[channel].wants_data = false;
|
||||
channels_[channel].data = data;
|
||||
|
||||
// TODO: "the [PWM] counter is reset when ... AUDxDAT is written", but
|
||||
// does that just mean written by the CPU, or does it include DMA?
|
||||
// My guess is the former. But TODO.
|
||||
if constexpr (is_external) {
|
||||
channels_[channel].reset_output_phase();
|
||||
}
|
||||
}
|
||||
|
||||
template void Audio::set_data<false>(int, uint16_t);
|
||||
template void Audio::set_data<true>(int, uint16_t);
|
||||
|
||||
void Audio::set_channel_enables(uint16_t enables) {
|
||||
channels_[0].dma_enabled = enables & 1;
|
||||
channels_[1].dma_enabled = enables & 2;
|
||||
channels_[2].dma_enabled = enables & 4;
|
||||
channels_[3].dma_enabled = enables & 8;
|
||||
}
|
||||
|
||||
void Audio::set_modulation_flags(uint16_t flags) {
|
||||
channels_[3].attach_period = flags & 0x80;
|
||||
channels_[2].attach_period = flags & 0x40;
|
||||
channels_[1].attach_period = flags & 0x20;
|
||||
channels_[0].attach_period = flags & 0x10;
|
||||
|
||||
channels_[3].attach_volume = flags & 0x08;
|
||||
channels_[2].attach_volume = flags & 0x04;
|
||||
channels_[1].attach_volume = flags & 0x02;
|
||||
channels_[0].attach_volume = flags & 0x01;
|
||||
}
|
||||
|
||||
void Audio::set_interrupt_requests(uint16_t requests) {
|
||||
channels_[0].interrupt_pending = requests & uint16_t(InterruptFlag::AudioChannel0);
|
||||
channels_[1].interrupt_pending = requests & uint16_t(InterruptFlag::AudioChannel1);
|
||||
channels_[2].interrupt_pending = requests & uint16_t(InterruptFlag::AudioChannel2);
|
||||
channels_[3].interrupt_pending = requests & uint16_t(InterruptFlag::AudioChannel3);
|
||||
}
|
||||
|
||||
// MARK: - DMA and mixing.
|
||||
|
||||
bool Audio::advance_dma(int channel) {
|
||||
if(!channels_[channel].wants_data) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if(channels_[channel].should_reload_address) {
|
||||
channels_[channel].data_address = pointer_[size_t(channel)];
|
||||
channels_[channel].should_reload_address = false;
|
||||
}
|
||||
|
||||
set_data<false>(channel, ram_[channels_[channel].data_address & ram_mask_]);
|
||||
|
||||
if(channels_[channel].state != Channel::State::WaitingForDummyDMA) {
|
||||
++channels_[channel].data_address;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void Audio::output() {
|
||||
constexpr InterruptFlag interrupts[] = {
|
||||
InterruptFlag::AudioChannel0,
|
||||
InterruptFlag::AudioChannel1,
|
||||
InterruptFlag::AudioChannel2,
|
||||
InterruptFlag::AudioChannel3,
|
||||
};
|
||||
Channel *const modulands[] = {
|
||||
&channels_[1],
|
||||
&channels_[2],
|
||||
&channels_[3],
|
||||
nullptr,
|
||||
};
|
||||
|
||||
for(int c = 0; c < 4; c++) {
|
||||
if(channels_[c].output(modulands[c])) {
|
||||
posit_interrupt(interrupts[c]);
|
||||
}
|
||||
}
|
||||
|
||||
// Spin until the next buffer is available if just entering it for the first time.
|
||||
// Contention here should be essentially non-existent.
|
||||
if(!sample_pointer_) {
|
||||
while(!buffer_available_[buffer_pointer_].load(std::memory_order::memory_order_relaxed));
|
||||
}
|
||||
|
||||
// Left.
|
||||
static_assert(std::tuple_size<AudioBuffer>::value % 2 == 0);
|
||||
buffer_[buffer_pointer_][sample_pointer_] = int16_t(
|
||||
(
|
||||
channels_[1].output_level * channels_[1].output_enabled +
|
||||
channels_[2].output_level * channels_[2].output_enabled
|
||||
) << 7
|
||||
);
|
||||
|
||||
// Right.
|
||||
buffer_[buffer_pointer_][sample_pointer_ + 1] = int16_t(
|
||||
(
|
||||
channels_[0].output_level * channels_[0].output_enabled +
|
||||
channels_[3].output_level * channels_[3].output_enabled
|
||||
) << 7
|
||||
);
|
||||
sample_pointer_ += 2;
|
||||
|
||||
if(sample_pointer_ == buffer_[buffer_pointer_].size()) {
|
||||
const auto &buffer = buffer_[buffer_pointer_];
|
||||
auto &flag = buffer_available_[buffer_pointer_];
|
||||
|
||||
flag.store(false, std::memory_order::memory_order_release);
|
||||
queue_.enqueue([this, &buffer, &flag] {
|
||||
speaker_.push(buffer.data(), buffer.size() >> 1);
|
||||
flag.store(true, std::memory_order::memory_order_relaxed);
|
||||
});
|
||||
|
||||
buffer_pointer_ = (buffer_pointer_ + 1) % BufferCount;
|
||||
sample_pointer_ = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Per-channel logic.
|
||||
|
||||
/*
|
||||
Big spiel on the state machine:
|
||||
|
||||
Commodore's Hardware Rerefence Manual provides the audio subsystem's state
|
||||
machine, so I've just tried to reimplement it verbatim. It's depicted
|
||||
diagrammatically in the original source as a finite state automata, the
|
||||
below is my attempt to translate that into text.
|
||||
|
||||
|
||||
000 State::Disabled:
|
||||
|
||||
-> State::Disabled (000)
|
||||
if: N/A
|
||||
action: percntrld
|
||||
|
||||
-> State::PlayingHigh (010)
|
||||
if: AUDDAT, and not AUDxON, and not AUDxIP
|
||||
action: percntrld, AUDxIR, volcntrld, pbudld1
|
||||
|
||||
-> State::WaitingForDummyDMA (001)
|
||||
if: AUDxON
|
||||
action: percntrld, AUDxDR, lencntrld, dmasen*
|
||||
|
||||
|
||||
* NOTE: except for this case, dmasen is true only when
|
||||
LENFIN = 1. Also, AUDxDSR = (AUDxDR and dmasen).
|
||||
|
||||
|
||||
|
||||
001 State::WaitingForDummyDMA:
|
||||
|
||||
-> State::WaitingForDummyDMA (001)
|
||||
if: N/A
|
||||
action: None
|
||||
|
||||
-> State::Disabled (000)
|
||||
if: not AUDxON
|
||||
action: None
|
||||
|
||||
-> State::WaitingForDMA (101)
|
||||
if: AUDxON, and AUDxDAT
|
||||
action:
|
||||
1. AUDxIR
|
||||
2. if not lenfin, then lencount
|
||||
|
||||
|
||||
|
||||
101 State::WaitingForDMA:
|
||||
|
||||
-> State::WaitingForDMA (101)
|
||||
if: N/A
|
||||
action: None
|
||||
|
||||
-> State:Disabled (000)
|
||||
if: not AUDxON
|
||||
action: None
|
||||
|
||||
-> State::PlayingHigh (010)
|
||||
if: AUDxON, and AUDxDAT
|
||||
action:
|
||||
1. volcntrld, percntrld, pbufld1
|
||||
2. if napnav, then AUDxDR
|
||||
|
||||
|
||||
|
||||
010 State::PlayingHigh
|
||||
|
||||
-> State::PlayingHigh (010)
|
||||
if: N/A
|
||||
action: percount, and penhi
|
||||
|
||||
-> State::PlayingLow (011)
|
||||
if: perfin
|
||||
action:
|
||||
1. if AUDxAP, then pbufld2
|
||||
2. if AUDxAP and AUDxON, then AUDxDR
|
||||
3. percntrld
|
||||
4. if intreq2 and AUDxON and AUDxAP, then AUDxIR
|
||||
5. if AUDxAP and AUDxON, then AUDxIR
|
||||
6. if lenfin and AUDxON and AUDxDAT, then lencntrld
|
||||
7. if (not lenfin) and AUDxON and AUDxDAT, then lencount
|
||||
8. if lenfin and AUDxON and AUDxDAT, then intreq2
|
||||
|
||||
[note that 6–8 are shared with the Low -> High transition]
|
||||
|
||||
|
||||
|
||||
011 State::PlayingLow
|
||||
|
||||
-> State::PlayingLow (011)
|
||||
if: N/A
|
||||
action: percount, and not penhi
|
||||
|
||||
-> State::Disabled (000)
|
||||
if: perfin and not (AUDxON or not AUDxIP)
|
||||
action: None
|
||||
|
||||
-> State::PlayingHigh (010)
|
||||
if: perfin and (AUDxON or not AUDxIP)
|
||||
action:
|
||||
1. pbufld1
|
||||
2. percntrld
|
||||
3. if napnav and AUDxON, then AUDxDR
|
||||
4. if napnav and AUDxON and intreq2, AUDxIR
|
||||
5. if napnav and not AUDxON, AUDxIR
|
||||
6. if lenfin and AUDxON and AUDxDAT, then lencntrld
|
||||
7. if (not lenfin) and AUDxON and AUDxDAT, then lencount
|
||||
8. if lenfin and AUDxON and AUDxDAT, then intreq2
|
||||
|
||||
[note that 6-8 are shared with the High -> Low transition]
|
||||
|
||||
|
||||
|
||||
Definitions:
|
||||
|
||||
AUDxON DMA on "x" indicates channel number (signal from DMACON).
|
||||
|
||||
AUDxIP Audio interrupt pending (input to channel from interrupt circuitry).
|
||||
|
||||
AUDxIR Audio interrupt request (output from channel to interrupt circuitry).
|
||||
|
||||
intreq1 Interrupt request that combines with intreq2 to form AUDxIR.
|
||||
|
||||
intreq2 Prepare for interrupt request. Request comes out after the
|
||||
next 011->010 transition in normal operation.
|
||||
|
||||
AUDxDAT Audio data load signal. Loads 16 bits of data to audio channel.
|
||||
|
||||
AUDxDR Audio DMA request to Agnus for one word of data.
|
||||
|
||||
AUDxDSR Audio DMA request to Agnus to reset pointer to start of block.
|
||||
|
||||
dmasen Restart request enable.
|
||||
|
||||
percntrld Reload period counter from back-up latch typically written
|
||||
by processor with AUDxPER (can also be written by attach mode).
|
||||
|
||||
percount Count period counter down one latch.
|
||||
|
||||
perfin Period counter finished (value = 1).
|
||||
|
||||
lencntrld Reload length counter from back-up latch.
|
||||
|
||||
lencount Count length counter down one notch.
|
||||
|
||||
lenfin Length counter finished (value = 1).
|
||||
|
||||
volcntrld Reload volume counter from back-up latch.
|
||||
|
||||
pbufld1 Load output buffer from holding latch written to by AUDxDAT.
|
||||
|
||||
pbufld2 Like pbufld1, but only during 010->011 with attach period.
|
||||
|
||||
AUDxAV Attach volume. Send data to volume latch of next channel
|
||||
instead of to D->A converter.
|
||||
|
||||
AUDxAP Attach period. Send data to period latch of next channel
|
||||
instead of to the D->A converter.
|
||||
|
||||
penhi Enable the high 8 bits of data to go to the D->A converter.
|
||||
|
||||
napnav /AUDxAV * /AUDxAP + AUDxAV -- no attach stuff or else attach
|
||||
volume. Condition for normal DMA and interrupt requests.
|
||||
*/
|
||||
|
||||
//
|
||||
// Non-action fallback transition and setter, plus specialised begin_state declarations.
|
||||
//
|
||||
|
||||
template <Audio::Channel::State end> void Audio::Channel::begin_state(Channel *) {
|
||||
state = end;
|
||||
}
|
||||
template <> void Audio::Channel::begin_state<Audio::Channel::State::PlayingHigh>(Channel *);
|
||||
template <> void Audio::Channel::begin_state<Audio::Channel::State::PlayingLow>(Channel *);
|
||||
|
||||
template <
|
||||
Audio::Channel::State begin,
|
||||
Audio::Channel::State end> bool Audio::Channel::transit(Channel *moduland) {
|
||||
begin_state<end>(moduland);
|
||||
return false;
|
||||
}
|
||||
|
||||
//
|
||||
// Audio::Channel::State::Disabled
|
||||
//
|
||||
|
||||
template <> bool Audio::Channel::transit<
|
||||
Audio::Channel::State::Disabled,
|
||||
Audio::Channel::State::PlayingHigh>(Channel *moduland) {
|
||||
begin_state<State::PlayingHigh>(moduland);
|
||||
|
||||
// percntrld
|
||||
period_counter = period;
|
||||
|
||||
// [AUDxIR]: see return result.
|
||||
|
||||
// volcntrld
|
||||
volume_latch = volume;
|
||||
reset_output_phase();
|
||||
|
||||
// pbufld1
|
||||
data_latch = data;
|
||||
wants_data = true;
|
||||
if(moduland && attach_volume) moduland->volume = uint8_t(data_latch);
|
||||
|
||||
// AUDxIR.
|
||||
return true;
|
||||
}
|
||||
|
||||
template <> bool Audio::Channel::transit<
|
||||
Audio::Channel::State::Disabled,
|
||||
Audio::Channel::State::WaitingForDummyDMA>(Channel *moduland) {
|
||||
begin_state<State::WaitingForDummyDMA>(moduland);
|
||||
|
||||
// percntrld
|
||||
period_counter = period;
|
||||
|
||||
// AUDxDR
|
||||
wants_data = true;
|
||||
|
||||
// lencntrld
|
||||
length_counter = length;
|
||||
|
||||
// dmasen / AUDxDSR
|
||||
should_reload_address = true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
template <> bool Audio::Channel::output<Audio::Channel::State::Disabled>(Channel *moduland) {
|
||||
// if AUDDAT, and not AUDxON, and not AUDxIP.
|
||||
if(!wants_data && !dma_enabled && !interrupt_pending) {
|
||||
return transit<State::Disabled, State::PlayingHigh>(moduland);
|
||||
}
|
||||
|
||||
// if AUDxON.
|
||||
if(dma_enabled) {
|
||||
return transit<State::Disabled, State::WaitingForDummyDMA>(moduland);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
//
|
||||
// Audio::Channel::State::WaitingForDummyDMA
|
||||
//
|
||||
|
||||
template <> bool Audio::Channel::transit<
|
||||
Audio::Channel::State::WaitingForDummyDMA,
|
||||
Audio::Channel::State::WaitingForDMA>(Channel *moduland) {
|
||||
begin_state<State::WaitingForDMA>(moduland);
|
||||
|
||||
// AUDxDR
|
||||
wants_data = true;
|
||||
|
||||
// if not lenfin, then lencount
|
||||
if(length != 1) {
|
||||
-- length_counter;
|
||||
}
|
||||
|
||||
// AUDxIR
|
||||
return true;
|
||||
}
|
||||
|
||||
template <> bool Audio::Channel::output<Audio::Channel::State::WaitingForDummyDMA>(Channel *moduland) {
|
||||
// if not AUDxON
|
||||
if(!dma_enabled) {
|
||||
return transit<State::WaitingForDummyDMA, State::Disabled>(moduland);
|
||||
}
|
||||
|
||||
// if AUDxON and AUDxDAT
|
||||
if(dma_enabled && !wants_data) {
|
||||
return transit<State::WaitingForDummyDMA, State::WaitingForDMA>(moduland);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
//
|
||||
// Audio::Channel::State::WaitingForDMA
|
||||
//
|
||||
|
||||
template <> bool Audio::Channel::transit<
|
||||
Audio::Channel::State::WaitingForDMA,
|
||||
Audio::Channel::State::PlayingHigh>(Channel *moduland) {
|
||||
begin_state<State::PlayingHigh>(moduland);
|
||||
|
||||
// volcntrld
|
||||
volume_latch = volume;
|
||||
reset_output_phase();
|
||||
|
||||
// percntrld
|
||||
period_counter = period;
|
||||
|
||||
// pbufld1
|
||||
data_latch = data;
|
||||
if(moduland && attach_volume) moduland->volume = uint8_t(data_latch);
|
||||
|
||||
// if napnav
|
||||
if(attach_volume || !(attach_volume || attach_period)) {
|
||||
// AUDxDR
|
||||
wants_data = true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
template <> bool Audio::Channel::output<Audio::Channel::State::WaitingForDMA>(Channel *moduland) {
|
||||
// if: not AUDxON
|
||||
if(!dma_enabled) {
|
||||
return transit<State::WaitingForDummyDMA, State::Disabled>(moduland);
|
||||
}
|
||||
|
||||
// if: AUDxON, and AUDxDAT
|
||||
if(dma_enabled && !wants_data) {
|
||||
return transit<State::WaitingForDummyDMA, State::PlayingHigh>(moduland);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
//
|
||||
// Audio::Channel::State::PlayingHigh
|
||||
//
|
||||
|
||||
void Audio::Channel::decrement_length() {
|
||||
// if lenfin and AUDxON and AUDxDAT, then lencntrld
|
||||
// if (not lenfin) and AUDxON and AUDxDAT, then lencount
|
||||
// if lenfin and AUDxON and AUDxDAT, then intreq2
|
||||
if(dma_enabled && !wants_data) {
|
||||
-- length_counter;
|
||||
|
||||
if(!length_counter) {
|
||||
length_counter = length;
|
||||
will_request_interrupt = true;
|
||||
should_reload_address = true; // This feels logical to me; it's a bit
|
||||
// of a stab in the dark though.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <> bool Audio::Channel::transit<
|
||||
Audio::Channel::State::PlayingHigh,
|
||||
Audio::Channel::State::PlayingLow>(Channel *moduland) {
|
||||
begin_state<State::PlayingLow>(moduland);
|
||||
|
||||
bool wants_interrupt = false;
|
||||
|
||||
// if AUDxAP
|
||||
if(attach_period) {
|
||||
// pbufld2
|
||||
data_latch = data;
|
||||
if(moduland) moduland->period = data_latch;
|
||||
|
||||
// [if AUDxAP] and AUDxON
|
||||
if(dma_enabled) {
|
||||
// AUDxDR
|
||||
wants_data = true;
|
||||
|
||||
// [if AUDxAP and AUDxON] and intreq2
|
||||
if(will_request_interrupt) {
|
||||
will_request_interrupt = false;
|
||||
|
||||
// AUDxIR
|
||||
wants_interrupt = true;
|
||||
}
|
||||
} else {
|
||||
// i.e. if AUDxAP and AUDxON, then AUDxIR
|
||||
wants_interrupt = true;
|
||||
}
|
||||
}
|
||||
|
||||
// percntrld
|
||||
period_counter = period;
|
||||
|
||||
decrement_length();
|
||||
|
||||
return wants_interrupt;
|
||||
}
|
||||
|
||||
template <> void Audio::Channel::begin_state<Audio::Channel::State::PlayingHigh>(Channel *) {
|
||||
state = Audio::Channel::State::PlayingHigh;
|
||||
|
||||
// penhi.
|
||||
output_level = int8_t(data_latch >> 8);
|
||||
}
|
||||
|
||||
template <> bool Audio::Channel::output<Audio::Channel::State::PlayingHigh>(Channel *moduland) {
|
||||
// This is a reasonable guess as to the exit condition for this node;
|
||||
// Commodore doesn't document.
|
||||
if(period_counter == 1) {
|
||||
return transit<State::PlayingHigh, State::PlayingLow>(moduland);
|
||||
}
|
||||
|
||||
// percount.
|
||||
-- period_counter;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
//
|
||||
// Audio::Channel::State::PlayingLow
|
||||
//
|
||||
|
||||
template <> bool Audio::Channel::transit<
|
||||
Audio::Channel::State::PlayingLow,
|
||||
Audio::Channel::State::Disabled>(Channel *moduland) {
|
||||
begin_state<State::Disabled>(moduland);
|
||||
|
||||
// Clear the slightly nebulous 'if intreq2 occurred' state.
|
||||
will_request_interrupt = false;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
template <> bool Audio::Channel::transit<
|
||||
Audio::Channel::State::PlayingLow,
|
||||
Audio::Channel::State::PlayingHigh>(Channel *moduland) {
|
||||
begin_state<State::PlayingHigh>(moduland);
|
||||
|
||||
bool wants_interrupt = false;
|
||||
|
||||
// volcntrld
|
||||
volume_latch = volume;
|
||||
reset_output_phase(); // Is this correct?
|
||||
|
||||
// percntrld
|
||||
period_counter = period;
|
||||
|
||||
// pbufld1
|
||||
data_latch = data;
|
||||
if(moduland && attach_volume) moduland->volume = uint8_t(data_latch);
|
||||
|
||||
// if napnav
|
||||
if(attach_volume || !(attach_volume || attach_period)) {
|
||||
// [if napnav] and AUDxON
|
||||
if(dma_enabled) {
|
||||
// AUDxDR
|
||||
wants_data = true;
|
||||
|
||||
// [if napnav and AUDxON] and intreq2
|
||||
if(will_request_interrupt) {
|
||||
will_request_interrupt = false;
|
||||
wants_interrupt = true;
|
||||
}
|
||||
} else {
|
||||
// AUDxIR
|
||||
wants_interrupt = true;
|
||||
}
|
||||
}
|
||||
|
||||
decrement_length();
|
||||
|
||||
return wants_interrupt;
|
||||
}
|
||||
|
||||
template <> void Audio::Channel::begin_state<Audio::Channel::State::PlayingLow>(Channel *) {
|
||||
state = Audio::Channel::State::PlayingLow;
|
||||
|
||||
// Output low byte.
|
||||
output_level = int8_t(data_latch & 0xff);
|
||||
}
|
||||
|
||||
template <> bool Audio::Channel::output<Audio::Channel::State::PlayingLow>(Channel *moduland) {
|
||||
-- period_counter;
|
||||
|
||||
if(!period_counter) {
|
||||
const bool dma_or_no_interrupt = dma_enabled || !interrupt_pending;
|
||||
if(dma_or_no_interrupt) {
|
||||
return transit<State::PlayingLow, State::PlayingHigh>(moduland);
|
||||
} else {
|
||||
return transit<State::PlayingLow, State::Disabled>(moduland);
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
//
|
||||
// Dispatcher
|
||||
//
|
||||
|
||||
bool Audio::Channel::output(Channel *moduland) {
|
||||
// Update pulse-width modulation.
|
||||
output_phase = output_phase + 1;
|
||||
if(output_phase == 64) {
|
||||
reset_output_phase();
|
||||
} else {
|
||||
output_enabled &= output_phase != volume_latch;
|
||||
}
|
||||
|
||||
switch(state) {
|
||||
case State::Disabled: return output<State::Disabled>(moduland);
|
||||
case State::WaitingForDummyDMA: return output<State::WaitingForDummyDMA>(moduland);
|
||||
case State::WaitingForDMA: return output<State::WaitingForDMA>(moduland);
|
||||
case State::PlayingHigh: return output<State::PlayingHigh>(moduland);
|
||||
case State::PlayingLow: return output<State::PlayingLow>(moduland);
|
||||
|
||||
default:
|
||||
assert(false);
|
||||
break;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
163
Machines/Amiga/Audio.hpp
Normal file
163
Machines/Amiga/Audio.hpp
Normal file
@@ -0,0 +1,163 @@
|
||||
//
|
||||
// Audio.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 09/11/2021.
|
||||
// Copyright © 2021 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef Audio_hpp
|
||||
#define Audio_hpp
|
||||
|
||||
#include <atomic>
|
||||
#include <cstdint>
|
||||
|
||||
#include "DMADevice.hpp"
|
||||
#include "../../ClockReceiver/ClockReceiver.hpp"
|
||||
#include "../../Concurrency/AsyncTaskQueue.hpp"
|
||||
#include "../../Outputs/Speaker/Implementation/LowpassSpeaker.hpp"
|
||||
|
||||
namespace Amiga {
|
||||
|
||||
class Audio: public DMADevice<4> {
|
||||
public:
|
||||
Audio(Chipset &chipset, uint16_t *ram, size_t word_size, float output_rate);
|
||||
|
||||
/// Idiomatic call-in for DMA scheduling; indicates that this class may
|
||||
/// perform a DMA access for the stated channel now.
|
||||
bool advance_dma(int channel);
|
||||
|
||||
/// Advances output by one DMA window, which is implicitly two cycles
|
||||
/// at the output rate that was specified to the constructor.
|
||||
void output();
|
||||
|
||||
/// Sets the total number of words to fetch for the given channel.
|
||||
void set_length(int channel, uint16_t);
|
||||
|
||||
/// Sets the number of DMA windows between each 8-bit output,
|
||||
/// in the same time base as @c ticks_per_line.
|
||||
void set_period(int channel, uint16_t);
|
||||
|
||||
/// Sets the output volume for the given channel; if bit 6 is set
|
||||
/// then output is maximal; otherwise bits 0–5 select
|
||||
/// a volume of [0–63]/64, on a logarithmic scale.
|
||||
void set_volume(int channel, uint16_t);
|
||||
|
||||
/// Sets the next two samples of audio to output.
|
||||
template <bool is_external = true> void set_data(int channel, uint16_t);
|
||||
|
||||
/// Provides a copy of the DMA enable flags, for the purpose of
|
||||
/// determining which channels are enabled for DMA.
|
||||
void set_channel_enables(uint16_t);
|
||||
|
||||
/// Sets which channels, if any, modulate period or volume of
|
||||
/// their neighbours.
|
||||
void set_modulation_flags(uint16_t);
|
||||
|
||||
/// Sets which interrupt requests are currently active.
|
||||
void set_interrupt_requests(uint16_t);
|
||||
|
||||
/// Obtains the output source.
|
||||
Outputs::Speaker::Speaker *get_speaker() {
|
||||
return &speaker_;
|
||||
}
|
||||
|
||||
private:
|
||||
struct Channel {
|
||||
// The data latch plus a count of unused samples
|
||||
// in the latch, which will always be 0, 1 or 2.
|
||||
uint16_t data = 0x0000;
|
||||
bool wants_data = false;
|
||||
uint16_t data_latch = 0x0000;
|
||||
|
||||
// The DMA address; unlike most of the Amiga Chipset,
|
||||
// the user posts a value to feed a pointer, rather
|
||||
// than having access to the pointer itself.
|
||||
bool should_reload_address = false;
|
||||
uint32_t data_address = 0x0000'0000;
|
||||
|
||||
// Number of words remaining in DMA data.
|
||||
uint16_t length = 0;
|
||||
uint16_t length_counter = 0;
|
||||
|
||||
// Number of ticks between each sample, plus the
|
||||
// current counter, which counts downward.
|
||||
uint16_t period = 0;
|
||||
uint16_t period_counter = 0;
|
||||
|
||||
// Modulation / attach flags.
|
||||
bool attach_period = false;
|
||||
bool attach_volume = false;
|
||||
|
||||
// Output volume, [0, 64].
|
||||
uint8_t volume = 0;
|
||||
uint8_t volume_latch = 0;
|
||||
|
||||
// Indicates whether DMA is enabled for this channel.
|
||||
bool dma_enabled = false;
|
||||
|
||||
// Records whether this audio interrupt is pending.
|
||||
bool interrupt_pending = false;
|
||||
bool will_request_interrupt = false;
|
||||
|
||||
// Replicates the Hardware Reference Manual state machine;
|
||||
// comments indicate which of the documented states each
|
||||
// label refers to.
|
||||
enum class State {
|
||||
Disabled, // 000
|
||||
WaitingForDummyDMA, // 001
|
||||
WaitingForDMA, // 101
|
||||
PlayingHigh, // 010
|
||||
PlayingLow, // 011
|
||||
} state = State::Disabled;
|
||||
|
||||
/// Dispatches to the appropriate templatised output for the current state.
|
||||
/// @param moduland The channel to modulate, if modulation is enabled.
|
||||
/// @returns @c true if an interrupt should be posted; @c false otherwise.
|
||||
bool output(Channel *moduland);
|
||||
|
||||
/// Applies dynamic logic for @c state, mostly testing for potential state transitions.
|
||||
/// @param moduland The channel to modulate, if modulation is enabled.
|
||||
/// @returns @c true if an interrupt should be posted; @c false otherwise.
|
||||
template <State state> bool output(Channel *moduland);
|
||||
|
||||
/// Transitions from @c begin to @c end, calling the appropriate @c begin_state
|
||||
/// and taking any steps specific to that particular transition.
|
||||
/// @param moduland The channel to modulate, if modulation is enabled.
|
||||
/// @returns @c true if an interrupt should be posted; @c false otherwise.
|
||||
template <State begin, State end> bool transit(Channel *moduland);
|
||||
|
||||
/// Begins @c state, performing all fixed logic that would otherwise have to be
|
||||
/// repeated endlessly in the relevant @c output.
|
||||
/// @param moduland The channel to modulate, if modulation is enabled.
|
||||
template <State state> void begin_state(Channel *moduland);
|
||||
|
||||
/// Provides the common length-decrementing logic used when transitioning
|
||||
/// between PlayingHigh and PlayingLow in either direction.
|
||||
void decrement_length();
|
||||
|
||||
// Output state.
|
||||
int8_t output_level = 0;
|
||||
uint8_t output_phase = 0;
|
||||
bool output_enabled = false;
|
||||
|
||||
void reset_output_phase() {
|
||||
output_phase = 0;
|
||||
output_enabled = (volume_latch > 0) && !attach_period && !attach_volume;
|
||||
}
|
||||
} channels_[4];
|
||||
|
||||
// Transient output state, and its destination.
|
||||
Outputs::Speaker::PushLowpass<true> speaker_;
|
||||
Concurrency::AsyncTaskQueue queue_;
|
||||
|
||||
using AudioBuffer = std::array<int16_t, 4096>;
|
||||
static constexpr int BufferCount = 3;
|
||||
AudioBuffer buffer_[BufferCount];
|
||||
std::atomic<bool> buffer_available_[BufferCount];
|
||||
size_t buffer_pointer_ = 0, sample_pointer_ = 0;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif /* Audio_hpp */
|
||||
132
Machines/Amiga/Bitplanes.cpp
Normal file
132
Machines/Amiga/Bitplanes.cpp
Normal file
@@ -0,0 +1,132 @@
|
||||
//
|
||||
// Bitplanes.cpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 26/11/2021.
|
||||
// Copyright © 2021 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#include "Bitplanes.hpp"
|
||||
#include "Chipset.hpp"
|
||||
|
||||
using namespace Amiga;
|
||||
|
||||
namespace {
|
||||
|
||||
/// Expands @c source so that b7 is the least-significant bit of the most-significant byte of the result,
|
||||
/// b6 is the least-significant bit of the next most significant byte, etc. b0 stays in place.
|
||||
constexpr uint64_t expand_bitplane_byte(uint8_t source) {
|
||||
uint64_t result = source; // 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 abcd efgh
|
||||
result = (result | (result << 28)) & 0x0000'000f'0000'000f; // 0000 0000 0000 0000 0000 0000 0000 abcd 0000 0000 0000 0000 0000 0000 0000 efgh
|
||||
result = (result | (result << 14)) & 0x0003'0003'0003'0003; // 0000 0000 0000 00ab 0000 0000 0000 00cd 0000 0000 0000 00ef 0000 0000 0000 00gh
|
||||
result = (result | (result << 7)) & 0x0101'0101'0101'0101; // 0000 000a 0000 000b 0000 000c 0000 000d 0000 000e 0000 000f 0000 000g 0000 000h
|
||||
return result;
|
||||
}
|
||||
|
||||
// A very small selection of test cases.
|
||||
static_assert(expand_bitplane_byte(0xff) == 0x01'01'01'01'01'01'01'01);
|
||||
static_assert(expand_bitplane_byte(0x55) == 0x00'01'00'01'00'01'00'01);
|
||||
static_assert(expand_bitplane_byte(0xaa) == 0x01'00'01'00'01'00'01'00);
|
||||
static_assert(expand_bitplane_byte(0x00) == 0x00'00'00'00'00'00'00'00);
|
||||
|
||||
}
|
||||
|
||||
// MARK: - BitplaneShifter.
|
||||
|
||||
void BitplaneShifter::set(const BitplaneData &previous, const BitplaneData &next, int odd_delay, int even_delay) {
|
||||
const uint16_t planes[6] = {
|
||||
uint16_t(((previous[0] << 16) | next[0]) >> even_delay),
|
||||
uint16_t(((previous[1] << 16) | next[1]) >> odd_delay),
|
||||
uint16_t(((previous[2] << 16) | next[2]) >> even_delay),
|
||||
uint16_t(((previous[3] << 16) | next[3]) >> odd_delay),
|
||||
uint16_t(((previous[4] << 16) | next[4]) >> even_delay),
|
||||
uint16_t(((previous[5] << 16) | next[5]) >> odd_delay),
|
||||
};
|
||||
|
||||
// Swizzle bits into the form:
|
||||
//
|
||||
// [b5 b3 b1 b4 b2 b0]
|
||||
//
|
||||
// ... and assume a suitably adjusted palette is in use elsewhere.
|
||||
// This makes dual playfields very easy to separate.
|
||||
data_[0] =
|
||||
(expand_bitplane_byte(uint8_t(planes[0])) << 0) |
|
||||
(expand_bitplane_byte(uint8_t(planes[2])) << 1) |
|
||||
(expand_bitplane_byte(uint8_t(planes[4])) << 2) |
|
||||
(expand_bitplane_byte(uint8_t(planes[1])) << 3) |
|
||||
(expand_bitplane_byte(uint8_t(planes[3])) << 4) |
|
||||
(expand_bitplane_byte(uint8_t(planes[5])) << 5);
|
||||
|
||||
data_[1] =
|
||||
(expand_bitplane_byte(uint8_t(planes[0] >> 8)) << 0) |
|
||||
(expand_bitplane_byte(uint8_t(planes[2] >> 8)) << 1) |
|
||||
(expand_bitplane_byte(uint8_t(planes[4] >> 8)) << 2) |
|
||||
(expand_bitplane_byte(uint8_t(planes[1] >> 8)) << 3) |
|
||||
(expand_bitplane_byte(uint8_t(planes[3] >> 8)) << 4) |
|
||||
(expand_bitplane_byte(uint8_t(planes[5] >> 8)) << 5);
|
||||
}
|
||||
|
||||
// MARK: - Bitplanes.
|
||||
|
||||
bool Bitplanes::advance_dma(int cycle) {
|
||||
#define BIND_CYCLE(offset, plane) \
|
||||
case offset: \
|
||||
if(plane_count_ > plane) { \
|
||||
next[plane] = ram_[pointer_[plane] & ram_mask_]; \
|
||||
++pointer_[plane]; \
|
||||
if constexpr (!plane) { \
|
||||
chipset_.post_bitplanes(next); \
|
||||
} \
|
||||
return true; \
|
||||
} \
|
||||
return false;
|
||||
|
||||
if(is_high_res_) {
|
||||
switch(cycle&3) {
|
||||
default: return false;
|
||||
BIND_CYCLE(0, 3);
|
||||
BIND_CYCLE(1, 1);
|
||||
BIND_CYCLE(2, 2);
|
||||
BIND_CYCLE(3, 0);
|
||||
}
|
||||
} else {
|
||||
switch(cycle&7) {
|
||||
default: return false;
|
||||
/* Omitted: 0. */
|
||||
BIND_CYCLE(1, 3);
|
||||
BIND_CYCLE(2, 5);
|
||||
BIND_CYCLE(3, 1);
|
||||
/* Omitted: 4. */
|
||||
BIND_CYCLE(5, 2);
|
||||
BIND_CYCLE(6, 4);
|
||||
BIND_CYCLE(7, 0);
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
|
||||
#undef BIND_CYCLE
|
||||
}
|
||||
|
||||
void Bitplanes::do_end_of_line() {
|
||||
// Apply modulos here. Posssibly correct?
|
||||
pointer_[0] += modulos_[1];
|
||||
pointer_[2] += modulos_[1];
|
||||
pointer_[4] += modulos_[1];
|
||||
|
||||
pointer_[1] += modulos_[0];
|
||||
pointer_[3] += modulos_[0];
|
||||
pointer_[5] += modulos_[0];
|
||||
}
|
||||
|
||||
void Bitplanes::set_control(uint16_t control) {
|
||||
is_high_res_ = control & 0x8000;
|
||||
plane_count_ = (control >> 12) & 7;
|
||||
|
||||
// TODO: who really has responsibility for clearing the other
|
||||
// bit plane fields?
|
||||
std::fill(next.begin() + plane_count_, next.end(), 0);
|
||||
if(plane_count_ == 7) {
|
||||
plane_count_ = 4;
|
||||
}
|
||||
}
|
||||
96
Machines/Amiga/Bitplanes.hpp
Normal file
96
Machines/Amiga/Bitplanes.hpp
Normal file
@@ -0,0 +1,96 @@
|
||||
//
|
||||
// Bitplanes.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 26/11/2021.
|
||||
// Copyright © 2021 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef Bitplanes_hpp
|
||||
#define Bitplanes_hpp
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
#include "DMADevice.hpp"
|
||||
|
||||
namespace Amiga {
|
||||
|
||||
struct BitplaneData: public std::array<uint16_t, 6> {
|
||||
BitplaneData &operator <<= (int c) {
|
||||
(*this)[0] <<= c;
|
||||
(*this)[1] <<= c;
|
||||
(*this)[2] <<= c;
|
||||
(*this)[3] <<= c;
|
||||
(*this)[4] <<= c;
|
||||
(*this)[5] <<= c;
|
||||
return *this;
|
||||
}
|
||||
|
||||
void clear() {
|
||||
std::fill(begin(), end(), 0);
|
||||
}
|
||||
};
|
||||
|
||||
class Bitplanes: public DMADevice<6, 2> {
|
||||
public:
|
||||
using DMADevice::DMADevice;
|
||||
|
||||
bool advance_dma(int cycle);
|
||||
void do_end_of_line();
|
||||
void set_control(uint16_t);
|
||||
|
||||
private:
|
||||
bool is_high_res_ = false;
|
||||
int plane_count_ = 0;
|
||||
|
||||
BitplaneData next;
|
||||
};
|
||||
|
||||
template <typename SourceT> constexpr SourceT bitplane_swizzle(SourceT value) {
|
||||
return
|
||||
(value&0x21) |
|
||||
((value&0x02) << 2) |
|
||||
((value&0x04) >> 1) |
|
||||
((value&0x08) << 1) |
|
||||
((value&0x10) >> 2);
|
||||
}
|
||||
|
||||
class BitplaneShifter {
|
||||
public:
|
||||
/// Installs a new set of output pixels.
|
||||
void set(
|
||||
const BitplaneData &previous,
|
||||
const BitplaneData &next,
|
||||
int odd_delay,
|
||||
int even_delay);
|
||||
|
||||
/// Shifts either two pixels (in low-res mode) and four pixels (in high-res).
|
||||
void shift(bool high_res) {
|
||||
constexpr int shifts[] = {16, 32};
|
||||
|
||||
data_[1] = (data_[1] << shifts[high_res]) | (data_[0] >> (64 - shifts[high_res]));
|
||||
data_[0] <<= shifts[high_res];
|
||||
}
|
||||
|
||||
/// @returns The next four pixels to output; in low-resolution mode only two
|
||||
/// of them will be unique. The value is arranges so that MSB = first pixel to output,
|
||||
/// LSB = last. Each byte is formed as 00[bitplane 5][bitplane 4]...[bitplane 0].
|
||||
uint32_t get(bool high_res) {
|
||||
if(high_res) {
|
||||
return uint32_t(data_[1] >> 32);
|
||||
} else {
|
||||
uint32_t result = uint16_t(data_[1] >> 48);
|
||||
result = ((result & 0xff00) << 8) | (result & 0x00ff);
|
||||
result |= result << 8;
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
std::array<uint64_t, 2> data_{};
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif /* Bitplanes_hpp */
|
||||
400
Machines/Amiga/Blitter.cpp
Normal file
400
Machines/Amiga/Blitter.cpp
Normal file
@@ -0,0 +1,400 @@
|
||||
//
|
||||
// Blitter.cpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 22/07/2021.
|
||||
// Copyright © 2021 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#include "Blitter.hpp"
|
||||
|
||||
#include "Minterms.hpp"
|
||||
|
||||
#include <cassert>
|
||||
|
||||
#ifndef NDEBUG
|
||||
#define NDEBUG
|
||||
#endif
|
||||
|
||||
#define LOG_PREFIX "[Blitter] "
|
||||
#include "../../Outputs/Log.hpp"
|
||||
|
||||
using namespace Amiga;
|
||||
|
||||
namespace {
|
||||
|
||||
/// @returns Either the final carry flag or the output nibble when using fill mode given that it either @c is_exclusive fill mode, or isn't;
|
||||
/// and the specified initial @c carry and input @c nibble.
|
||||
template <bool wants_carry> constexpr uint32_t fill_nibble(bool is_exclusive, uint8_t carry, uint8_t nibble) {
|
||||
uint8_t fill_output = 0;
|
||||
uint8_t bit = 0x01;
|
||||
while(bit < 0x10) {
|
||||
auto pre_toggle = nibble & bit, post_toggle = pre_toggle;
|
||||
if(!is_exclusive) {
|
||||
pre_toggle &= ~carry; // Accept bits that would transition to set immediately.
|
||||
post_toggle &= carry; // Accept bits that would transition to clear after the fact.
|
||||
} else {
|
||||
post_toggle = 0; // Just do the pre-toggle.
|
||||
}
|
||||
|
||||
carry ^= pre_toggle;
|
||||
fill_output |= carry;
|
||||
carry ^= post_toggle;
|
||||
|
||||
bit <<= 1;
|
||||
carry <<= 1;
|
||||
}
|
||||
|
||||
if constexpr (wants_carry) {
|
||||
return carry >> 4;
|
||||
} else {
|
||||
return fill_output;
|
||||
}
|
||||
}
|
||||
|
||||
// Lookup key for these tables is:
|
||||
//
|
||||
// b0–b3: input nibble
|
||||
// b4: carry
|
||||
// b5: is_exclusive
|
||||
//
|
||||
// i.e. it's in the range [0, 63].
|
||||
//
|
||||
// Tables below are indexed such that the higher-order bits select a table entry, lower-order bits select
|
||||
// a bit or nibble from within the indexed item.
|
||||
|
||||
constexpr uint32_t fill_carries[] = {
|
||||
(fill_nibble<true>(false, 0, 0x0) << 0x0) | (fill_nibble<true>(false, 0, 0x1) << 0x1) | (fill_nibble<true>(false, 0, 0x2) << 0x2) | (fill_nibble<true>(false, 0, 0x3) << 0x3) |
|
||||
(fill_nibble<true>(false, 0, 0x4) << 0x4) | (fill_nibble<true>(false, 0, 0x5) << 0x5) | (fill_nibble<true>(false, 0, 0x6) << 0x6) | (fill_nibble<true>(false, 0, 0x7) << 0x7) |
|
||||
(fill_nibble<true>(false, 0, 0x8) << 0x8) | (fill_nibble<true>(false, 0, 0x9) << 0x9) | (fill_nibble<true>(false, 0, 0xa) << 0xa) | (fill_nibble<true>(false, 0, 0xb) << 0xb) |
|
||||
(fill_nibble<true>(false, 0, 0xc) << 0xc) | (fill_nibble<true>(false, 0, 0xd) << 0xd) | (fill_nibble<true>(false, 0, 0xe) << 0xe) | (fill_nibble<true>(false, 0, 0xf) << 0xf) |
|
||||
|
||||
(fill_nibble<true>(false, 1, 0x0) << 0x10) | (fill_nibble<true>(false, 1, 0x1) << 0x11) | (fill_nibble<true>(false, 1, 0x2) << 0x12) | (fill_nibble<true>(false, 1, 0x3) << 0x13) |
|
||||
(fill_nibble<true>(false, 1, 0x4) << 0x14) | (fill_nibble<true>(false, 1, 0x5) << 0x15) | (fill_nibble<true>(false, 1, 0x6) << 0x16) | (fill_nibble<true>(false, 1, 0x7) << 0x17) |
|
||||
(fill_nibble<true>(false, 1, 0x8) << 0x18) | (fill_nibble<true>(false, 1, 0x9) << 0x19) | (fill_nibble<true>(false, 1, 0xa) << 0x1a) | (fill_nibble<true>(false, 1, 0xb) << 0x1b) |
|
||||
(fill_nibble<true>(false, 1, 0xc) << 0x1c) | (fill_nibble<true>(false, 1, 0xd) << 0x1d) | (fill_nibble<true>(false, 1, 0xe) << 0x1e) | (fill_nibble<true>(false, 1, 0xf) << 0x1f),
|
||||
|
||||
(fill_nibble<true>(true, 0, 0x0) << 0x0) | (fill_nibble<true>(true, 0, 0x1) << 0x1) | (fill_nibble<true>(true, 0, 0x2) << 0x2) | (fill_nibble<true>(true, 0, 0x3) << 0x3) |
|
||||
(fill_nibble<true>(true, 0, 0x4) << 0x4) | (fill_nibble<true>(true, 0, 0x5) << 0x5) | (fill_nibble<true>(true, 0, 0x6) << 0x6) | (fill_nibble<true>(true, 0, 0x7) << 0x7) |
|
||||
(fill_nibble<true>(true, 0, 0x8) << 0x8) | (fill_nibble<true>(true, 0, 0x9) << 0x9) | (fill_nibble<true>(true, 0, 0xa) << 0xa) | (fill_nibble<true>(true, 0, 0xb) << 0xb) |
|
||||
(fill_nibble<true>(true, 0, 0xc) << 0xc) | (fill_nibble<true>(true, 0, 0xd) << 0xd) | (fill_nibble<true>(true, 0, 0xe) << 0xe) | (fill_nibble<true>(true, 0, 0xf) << 0xf) |
|
||||
|
||||
(fill_nibble<true>(true, 1, 0x0) << 0x10) | (fill_nibble<true>(true, 1, 0x1) << 0x11) | (fill_nibble<true>(true, 1, 0x2) << 0x12) | (fill_nibble<true>(true, 1, 0x3) << 0x13) |
|
||||
(fill_nibble<true>(true, 1, 0x4) << 0x14) | (fill_nibble<true>(true, 1, 0x5) << 0x15) | (fill_nibble<true>(true, 1, 0x6) << 0x16) | (fill_nibble<true>(true, 1, 0x7) << 0x17) |
|
||||
(fill_nibble<true>(true, 1, 0x8) << 0x18) | (fill_nibble<true>(true, 1, 0x9) << 0x19) | (fill_nibble<true>(true, 1, 0xa) << 0x1a) | (fill_nibble<true>(true, 1, 0xb) << 0x1b) |
|
||||
(fill_nibble<true>(true, 1, 0xc) << 0x1c) | (fill_nibble<true>(true, 1, 0xd) << 0x1d) | (fill_nibble<true>(true, 1, 0xe) << 0x1e) | (fill_nibble<true>(true, 1, 0xf) << 0x1f),
|
||||
};
|
||||
|
||||
constexpr uint32_t fill_values[] = {
|
||||
(fill_nibble<false>(false, 0, 0x0) << 0) | (fill_nibble<false>(false, 0, 0x1) << 4) | (fill_nibble<false>(false, 0, 0x2) << 8) | (fill_nibble<false>(false, 0, 0x3) << 12) |
|
||||
(fill_nibble<false>(false, 0, 0x4) << 16) | (fill_nibble<false>(false, 0, 0x5) << 20) | (fill_nibble<false>(false, 0, 0x6) << 24) | (fill_nibble<false>(false, 0, 0x7) << 28),
|
||||
|
||||
(fill_nibble<false>(false, 0, 0x8) << 0) | (fill_nibble<false>(false, 0, 0x9) << 4) | (fill_nibble<false>(false, 0, 0xa) << 8) | (fill_nibble<false>(false, 0, 0xb) << 12) |
|
||||
(fill_nibble<false>(false, 0, 0xc) << 16) | (fill_nibble<false>(false, 0, 0xd) << 20) | (fill_nibble<false>(false, 0, 0xe) << 24) | (fill_nibble<false>(false, 0, 0xf) << 28),
|
||||
|
||||
(fill_nibble<false>(false, 1, 0x0) << 0) | (fill_nibble<false>(false, 1, 0x1) << 4) | (fill_nibble<false>(false, 1, 0x2) << 8) | (fill_nibble<false>(false, 1, 0x3) << 12) |
|
||||
(fill_nibble<false>(false, 1, 0x4) << 16) | (fill_nibble<false>(false, 1, 0x5) << 20) | (fill_nibble<false>(false, 1, 0x6) << 24) | (fill_nibble<false>(false, 1, 0x7) << 28),
|
||||
|
||||
(fill_nibble<false>(false, 1, 0x8) << 0) | (fill_nibble<false>(false, 1, 0x9) << 4) | (fill_nibble<false>(false, 1, 0xa) << 8) | (fill_nibble<false>(false, 1, 0xb) << 12) |
|
||||
(fill_nibble<false>(false, 1, 0xc) << 16) | (fill_nibble<false>(false, 1, 0xd) << 20) | (fill_nibble<false>(false, 1, 0xe) << 24) | (fill_nibble<false>(false, 1, 0xf) << 28),
|
||||
|
||||
(fill_nibble<false>(true, 0, 0x0) << 0) | (fill_nibble<false>(true, 0, 0x1) << 4) | (fill_nibble<false>(true, 0, 0x2) << 8) | (fill_nibble<false>(true, 0, 0x3) << 12) |
|
||||
(fill_nibble<false>(true, 0, 0x4) << 16) | (fill_nibble<false>(true, 0, 0x5) << 20) | (fill_nibble<false>(true, 0, 0x6) << 24) | (fill_nibble<false>(true, 0, 0x7) << 28),
|
||||
|
||||
(fill_nibble<false>(true, 0, 0x8) << 0) | (fill_nibble<false>(true, 0, 0x9) << 4) | (fill_nibble<false>(true, 0, 0xa) << 8) | (fill_nibble<false>(true, 0, 0xb) << 12) |
|
||||
(fill_nibble<false>(true, 0, 0xc) << 16) | (fill_nibble<false>(true, 0, 0xd) << 20) | (fill_nibble<false>(true, 0, 0xe) << 24) | (fill_nibble<false>(true, 0, 0xf) << 28),
|
||||
|
||||
(fill_nibble<false>(true, 1, 0x0) << 0) | (fill_nibble<false>(true, 1, 0x1) << 4) | (fill_nibble<false>(true, 1, 0x2) << 8) | (fill_nibble<false>(true, 1, 0x3) << 12) |
|
||||
(fill_nibble<false>(true, 1, 0x4) << 16) | (fill_nibble<false>(true, 1, 0x5) << 20) | (fill_nibble<false>(true, 1, 0x6) << 24) | (fill_nibble<false>(true, 1, 0x7) << 28),
|
||||
|
||||
(fill_nibble<false>(true, 1, 0x8) << 0) | (fill_nibble<false>(true, 1, 0x9) << 4) | (fill_nibble<false>(true, 1, 0xa) << 8) | (fill_nibble<false>(true, 1, 0xb) << 12) |
|
||||
(fill_nibble<false>(true, 1, 0xc) << 16) | (fill_nibble<false>(true, 1, 0xd) << 20) | (fill_nibble<false>(true, 1, 0xe) << 24) | (fill_nibble<false>(true, 1, 0xf) << 28),
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
void Blitter::set_control(int index, uint16_t value) {
|
||||
if(index) {
|
||||
line_mode_ = (value & 0x0001);
|
||||
one_dot_ = value & 0x0002;
|
||||
line_direction_ = (value >> 2) & 7;
|
||||
line_sign_ = (value & 0x0040) ? -1 : 1;
|
||||
|
||||
direction_ = one_dot_ ? uint32_t(-1) : uint32_t(1);
|
||||
exclusive_fill_ = (value & 0x0010);
|
||||
inclusive_fill_ = !exclusive_fill_ && (value & 0x0008); // Exclusive fill takes precedence. Probably? TODO: verify.
|
||||
fill_carry_ = (value & 0x0004);
|
||||
} else {
|
||||
minterms_ = value & 0xff;
|
||||
channel_enables_[3] = value & 0x100;
|
||||
channel_enables_[2] = value & 0x200;
|
||||
channel_enables_[1] = value & 0x400;
|
||||
channel_enables_[0] = value & 0x800;
|
||||
}
|
||||
shifts_[index] = value >> 12;
|
||||
LOG("Set control " << index << " to " << PADHEX(4) << value);
|
||||
}
|
||||
|
||||
void Blitter::set_first_word_mask(uint16_t value) {
|
||||
LOG("Set first word mask: " << PADHEX(4) << value);
|
||||
a_mask_[0] = value;
|
||||
}
|
||||
|
||||
void Blitter::set_last_word_mask(uint16_t value) {
|
||||
LOG("Set last word mask: " << PADHEX(4) << value);
|
||||
a_mask_[1] = value;
|
||||
}
|
||||
|
||||
void Blitter::set_size(uint16_t value) {
|
||||
// width_ = (width_ & ~0x3f) | (value & 0x3f);
|
||||
// height_ = (height_ & ~0x3ff) | (value >> 6);
|
||||
width_ = value & 0x3f;
|
||||
if(!width_) width_ = 0x40;
|
||||
height_ = value >> 6;
|
||||
if(!height_) height_ = 1024;
|
||||
LOG("Set size to " << std::dec << width_ << ", " << height_);
|
||||
|
||||
// Current assumption: writing this register informs the
|
||||
// blitter that it should treat itself as about to start a new line.
|
||||
}
|
||||
|
||||
void Blitter::set_minterms(uint16_t value) {
|
||||
LOG("Set minterms " << PADHEX(4) << value);
|
||||
minterms_ = value & 0xff;
|
||||
}
|
||||
|
||||
//void Blitter::set_vertical_size([[maybe_unused]] uint16_t value) {
|
||||
// LOG("Set vertical size " << PADHEX(4) << value);
|
||||
// // TODO. This is ECS only, I think. Ditto set_horizontal_size.
|
||||
//}
|
||||
//
|
||||
//void Blitter::set_horizontal_size([[maybe_unused]] uint16_t value) {
|
||||
// LOG("Set horizontal size " << PADHEX(4) << value);
|
||||
//}
|
||||
|
||||
void Blitter::set_data(int channel, uint16_t value) {
|
||||
LOG("Set data " << channel << " to " << PADHEX(4) << value);
|
||||
|
||||
// Ugh, backed myself into a corner. TODO: clean.
|
||||
switch(channel) {
|
||||
case 0: a_data_ = value; break;
|
||||
case 1: b_data_ = value; break;
|
||||
case 2: c_data_ = value; break;
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
|
||||
uint16_t Blitter::get_status() {
|
||||
const uint16_t result =
|
||||
(not_zero_flag_ ? 0x0000 : 0x2000) | (height_ ? 0x4000 : 0x0000);
|
||||
LOG("Returned status of " << result);
|
||||
return result;
|
||||
}
|
||||
|
||||
bool Blitter::advance_dma() {
|
||||
if(!height_) return false;
|
||||
|
||||
not_zero_flag_ = false;
|
||||
if(line_mode_) {
|
||||
// As-yet unimplemented:
|
||||
assert(b_data_ == 0xffff);
|
||||
|
||||
//
|
||||
// Line mode.
|
||||
//
|
||||
|
||||
// Bluffer's guide to line mode:
|
||||
//
|
||||
// In Bresenham terms, the following registers have been set up:
|
||||
//
|
||||
// [A modulo] = 4 * (dy - dx)
|
||||
// [B modulo] = 4 * dy
|
||||
// [A pointer] = 4 * dy - 2 * dx, with the sign flag in BLTCON1 indicating sign.
|
||||
//
|
||||
// [A data] = 0x8000
|
||||
// [Both masks] = 0xffff
|
||||
// [A shift] = x1 & 15
|
||||
//
|
||||
// [B data] = texture
|
||||
// [B shift] = bit at which to start the line texture (0 = LSB)
|
||||
//
|
||||
// [C and D pointers] = word containing the first pixel of the line
|
||||
// [C and D modulo] = width of the bitplane in bytes
|
||||
//
|
||||
// height = number of pixels
|
||||
//
|
||||
// If ONEDOT of BLTCON1 is set, plot only a single bit per horizontal row.
|
||||
//
|
||||
// BLTCON1 quadrants are (bits 2–4):
|
||||
//
|
||||
// 110 -> step in x, x positive, y negative
|
||||
// 111 -> step in x, x negative, y negative
|
||||
// 101 -> step in x, x negative, y positive
|
||||
// 100 -> step in x, x positive, y positive
|
||||
//
|
||||
// 001 -> step in y, x positive, y negative
|
||||
// 011 -> step in y, x negative, y negative
|
||||
// 010 -> step in y, x negative, y positive
|
||||
// 000 -> step in y, x positive, y positive
|
||||
//
|
||||
// So that's:
|
||||
//
|
||||
// * bit 4 = x [=1] or y [=0] major;
|
||||
// * bit 3 = 1 => major variable negative; otherwise positive;
|
||||
// * bit 2 = 1 => minor variable negative; otherwise positive.
|
||||
|
||||
//
|
||||
// Implementation below is heavily based on the documentation found
|
||||
// at https://github.com/niklasekstrom/blitter-subpixel-line/blob/master/Drawing%20lines%20using%20the%20Amiga%20blitter.pdf
|
||||
//
|
||||
|
||||
int error = int16_t(pointer_[0] << 1) >> 1; // TODO: what happens if line_sign_ doesn't agree with this?
|
||||
bool draw_ = true;
|
||||
while(height_--) {
|
||||
|
||||
if(draw_) {
|
||||
// TODO: patterned lines. Unclear what to do with the bit that comes out of b.
|
||||
// Probably extend it to a full word?
|
||||
c_data_ = ram_[pointer_[3] & ram_mask_];
|
||||
const uint16_t output =
|
||||
apply_minterm<uint16_t>(a_data_ >> shifts_[0], b_data_, c_data_, minterms_);
|
||||
ram_[pointer_[3] & ram_mask_] = output;
|
||||
not_zero_flag_ |= output;
|
||||
draw_ &= !one_dot_;
|
||||
}
|
||||
|
||||
constexpr int LEFT = 1 << 0;
|
||||
constexpr int RIGHT = 1 << 1;
|
||||
constexpr int UP = 1 << 2;
|
||||
constexpr int DOWN = 1 << 3;
|
||||
int step = (line_direction_ & 4) ?
|
||||
((line_direction_ & 1) ? LEFT : RIGHT) :
|
||||
((line_direction_ & 1) ? UP : DOWN);
|
||||
|
||||
if(error < 0) {
|
||||
error += modulos_[1];
|
||||
} else {
|
||||
step |=
|
||||
(line_direction_ & 4) ?
|
||||
((line_direction_ & 2) ? UP : DOWN) :
|
||||
((line_direction_ & 2) ? LEFT : RIGHT);
|
||||
|
||||
error += modulos_[0];
|
||||
}
|
||||
|
||||
if(step & LEFT) {
|
||||
--shifts_[0];
|
||||
if(shifts_[0] == -1) {
|
||||
--pointer_[3];
|
||||
}
|
||||
} else if(step & RIGHT) {
|
||||
++shifts_[0];
|
||||
if(shifts_[0] == 16) {
|
||||
++pointer_[3];
|
||||
}
|
||||
}
|
||||
shifts_[0] &= 15;
|
||||
|
||||
if(step & UP) {
|
||||
pointer_[3] -= modulos_[2];
|
||||
draw_ = true;
|
||||
} else if(step & DOWN) {
|
||||
pointer_[3] += modulos_[2];
|
||||
draw_ = true;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Copy mode.
|
||||
|
||||
// Quick hack: do the entire action atomically.
|
||||
a32_ = 0;
|
||||
b32_ = 0;
|
||||
|
||||
for(int y = 0; y < height_; y++) {
|
||||
bool fill_carry = fill_carry_;
|
||||
|
||||
for(int x = 0; x < width_; x++) {
|
||||
uint16_t a_mask = 0xffff;
|
||||
if(x == 0) a_mask &= a_mask_[0];
|
||||
if(x == width_ - 1) a_mask &= a_mask_[1];
|
||||
|
||||
if(channel_enables_[0]) {
|
||||
a_data_ = ram_[pointer_[0] & ram_mask_];
|
||||
pointer_[0] += direction_;
|
||||
}
|
||||
a32_ = (a32_ << 16) | (a_data_ & a_mask);
|
||||
|
||||
if(channel_enables_[1]) {
|
||||
b_data_ = ram_[pointer_[1] & ram_mask_];
|
||||
pointer_[1] += direction_;
|
||||
}
|
||||
b32_ = (b32_ << 16) | b_data_;
|
||||
|
||||
if(channel_enables_[2]) {
|
||||
c_data_ = ram_[pointer_[2] & ram_mask_];
|
||||
pointer_[2] += direction_;
|
||||
}
|
||||
|
||||
uint16_t a, b;
|
||||
|
||||
// The barrel shifter shifts to the right in ascending address mode,
|
||||
// but to the left otherwise
|
||||
if(!one_dot_) {
|
||||
a = uint16_t(a32_ >> shifts_[0]);
|
||||
b = uint16_t(b32_ >> shifts_[1]);
|
||||
} else {
|
||||
// TODO: there must be a neater solution than this.
|
||||
a = uint16_t(
|
||||
(a32_ << shifts_[0]) |
|
||||
(a32_ >> (32 - shifts_[0]))
|
||||
);
|
||||
|
||||
b = uint16_t(
|
||||
(b32_ << shifts_[1]) |
|
||||
(b32_ >> (32 - shifts_[1]))
|
||||
);
|
||||
}
|
||||
|
||||
uint16_t output =
|
||||
apply_minterm<uint16_t>(
|
||||
a,
|
||||
b,
|
||||
c_data_,
|
||||
minterms_);
|
||||
|
||||
if(exclusive_fill_ || inclusive_fill_) {
|
||||
// Use the fill tables nibble-by-nibble to figure out the filled word.
|
||||
uint16_t fill_output = 0;
|
||||
int ongoing_carry = fill_carry;
|
||||
const int type_mask = exclusive_fill_ ? (1 << 5) : 0;
|
||||
for(int c = 0; c < 16; c += 4) {
|
||||
const int total_index = (output & 0xf) | (ongoing_carry << 4) | type_mask;
|
||||
fill_output |= ((fill_values[total_index >> 3] >> ((total_index & 7) * 4)) & 0xf) << c;
|
||||
ongoing_carry = (fill_carries[total_index >> 5] >> (total_index & 31)) & 1;
|
||||
output >>= 4;
|
||||
}
|
||||
|
||||
output = fill_output;
|
||||
fill_carry = ongoing_carry;
|
||||
}
|
||||
|
||||
not_zero_flag_ |= output;
|
||||
|
||||
if(channel_enables_[3]) {
|
||||
ram_[pointer_[3] & ram_mask_] = output;
|
||||
pointer_[3] += direction_;
|
||||
}
|
||||
}
|
||||
|
||||
pointer_[0] += modulos_[0] * channel_enables_[0] * direction_;
|
||||
pointer_[1] += modulos_[1] * channel_enables_[1] * direction_;
|
||||
pointer_[2] += modulos_[2] * channel_enables_[2] * direction_;
|
||||
pointer_[3] += modulos_[3] * channel_enables_[3] * direction_;
|
||||
}
|
||||
}
|
||||
|
||||
posit_interrupt(InterruptFlag::Blitter);
|
||||
height_ = 0;
|
||||
|
||||
return true;
|
||||
}
|
||||
69
Machines/Amiga/Blitter.hpp
Normal file
69
Machines/Amiga/Blitter.hpp
Normal file
@@ -0,0 +1,69 @@
|
||||
//
|
||||
// Blitter.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 22/07/2021.
|
||||
// Copyright © 2021 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef Blitter_hpp
|
||||
#define Blitter_hpp
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
|
||||
#include "../../ClockReceiver/ClockReceiver.hpp"
|
||||
#include "DMADevice.hpp"
|
||||
|
||||
namespace Amiga {
|
||||
|
||||
class Blitter: public DMADevice<4, 4> {
|
||||
public:
|
||||
using DMADevice::DMADevice;
|
||||
|
||||
// Various setters; it's assumed that address decoding is handled externally.
|
||||
//
|
||||
// In all cases where a channel is identified numerically, it's taken that
|
||||
// 0 = A, 1 = B, 2 = C, 3 = D.
|
||||
void set_control(int index, uint16_t value);
|
||||
void set_first_word_mask(uint16_t value);
|
||||
void set_last_word_mask(uint16_t value);
|
||||
|
||||
void set_size(uint16_t value);
|
||||
void set_minterms(uint16_t value);
|
||||
// void set_vertical_size(uint16_t value);
|
||||
// void set_horizontal_size(uint16_t value);
|
||||
void set_data(int channel, uint16_t value);
|
||||
|
||||
uint16_t get_status();
|
||||
|
||||
bool advance_dma();
|
||||
|
||||
private:
|
||||
int width_ = 0, height_ = 0;
|
||||
int shifts_[2]{};
|
||||
uint16_t a_mask_[2] = {0xffff, 0xffff};
|
||||
|
||||
bool line_mode_ = false;
|
||||
bool one_dot_ = false;
|
||||
int line_direction_ = 0;
|
||||
int line_sign_ = 1;
|
||||
|
||||
uint32_t direction_ = 1;
|
||||
bool inclusive_fill_ = false;
|
||||
bool exclusive_fill_ = false;
|
||||
bool fill_carry_ = false;
|
||||
|
||||
bool channel_enables_[4]{};
|
||||
|
||||
uint8_t minterms_ = 0;
|
||||
uint32_t a32_ = 0, b32_ = 0;
|
||||
uint16_t a_data_ = 0, b_data_ = 0, c_data_ = 0;
|
||||
|
||||
bool not_zero_flag_ = false;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
|
||||
#endif /* Blitter_hpp */
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user