mirror of
https://github.com/TomHarte/CLK.git
synced 2026-04-26 19:17:52 +00:00
Compare commits
1097 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 4be5ee5b35 | |||
| 92e6dc64d4 | |||
| a9d945d6d2 | |||
| 5e465f1ff4 | |||
| 5359964fef | |||
| fa8be26f9f | |||
| aabfe7c284 | |||
| d011b10b5d | |||
| 332b37063f | |||
| b3a9e39be3 | |||
| 67590cf06b | |||
| 236fdacb36 | |||
| f422cda553 | |||
| 2c44d3a7d3 | |||
| 051ce98ecb | |||
| 33ae24c961 | |||
| 4247d0ef40 | |||
| ffababdb45 | |||
| 176bda9eb8 | |||
| 9f0a0443a8 | |||
| fd1a7e78c5 | |||
| 909fa57b27 | |||
| 5630b1c351 | |||
| c4fe38a61f | |||
| 5b4f303e35 | |||
| c9c1bde6e2 | |||
| d01e1f3bb1 | |||
| fd32e63459 | |||
| dbbb1d60fc | |||
| 1ce013bcf7 | |||
| 86bf019aac | |||
| d00546dd77 | |||
| cf33e17688 | |||
| c5c6c5ff72 | |||
| fa0835abd8 | |||
| f232b179ed | |||
| a4a0026cab | |||
| eac7493180 | |||
| 989fb32fba | |||
| 735afcfabb | |||
| 37152a1fad | |||
| 4e86184955 | |||
| d23dbb96c2 | |||
| 4586e4b4c1 | |||
| de5cdbf18c | |||
| 8c2294fc0d | |||
| b0b82782ad | |||
| b9f5802c89 | |||
| 29235f1276 | |||
| 8c74e2a323 | |||
| ae2936b9c3 | |||
| 0d295a6338 | |||
| 3ebd6c6871 | |||
| 6e2cd0ace6 | |||
| af82a0bcda | |||
| 6fe208ae77 | |||
| f569b86c90 | |||
| b622cc9536 | |||
| 7dfd5ea0d0 | |||
| a81309433c | |||
| 902f388cb1 | |||
| 0cc5a9d74f | |||
| 5e98e6502d | |||
| fe7a206fc5 | |||
| c5704aaaff | |||
| e115f09f51 | |||
| 32cd142629 | |||
| b00be303aa | |||
| 273e23bd98 | |||
| 5063e6943d | |||
| ce32747973 | |||
| a8ef8dfb21 | |||
| 7658edca62 | |||
| 056028e07b | |||
| 34992126a8 | |||
| d30d4e8f89 | |||
| f562deca48 | |||
| b2b7aa221b | |||
| b98a9a8487 | |||
| 7f36a8a746 | |||
| 2fe6e9c7fc | |||
| 62919e77d4 | |||
| 871b724290 | |||
| ca23c04ba1 | |||
| 44b8f75611 | |||
| 301df785fe | |||
| 9657109471 | |||
| 139569e291 | |||
| 1383c1dad4 | |||
| 1d2cdd85a3 | |||
| 76082b1271 | |||
| 25dcbf918d | |||
| 6c72c1842b | |||
| 4e4388dc35 | |||
| 54bff80ecc | |||
| 78073aaa11 | |||
| 4df01a7e0d | |||
| f4d15d0640 | |||
| 64842d4de2 | |||
| f52315ac92 | |||
| cc88877109 | |||
| d7568e57c3 | |||
| d49b301fca | |||
| 0113bcbea7 | |||
| 0332bb4f12 | |||
| 82a5d5116a | |||
| 62a6797ef3 | |||
| bb66033682 | |||
| d4aa0799a9 | |||
| fba2d37714 | |||
| e891697f88 | |||
| ee6ac3b4a9 | |||
| c8130f9d6f | |||
| 8abd837c8b | |||
| 02ad080bb8 | |||
| 5887e3e580 | |||
| 1994b2dc9f | |||
| d7a82d00b1 | |||
| 0017bd6d0f | |||
| 15f30995b1 | |||
| 37ca0e4f81 | |||
| e400aa200c | |||
| e168298aa0 | |||
| c4afbf8f2e | |||
| 112aff9887 | |||
| a8207ded4f | |||
| bafef023a5 | |||
| 9b39eebc2d | |||
| 02fdcd6ece | |||
| 49601e0a78 | |||
| cf10abff5b | |||
| e75c27cb66 | |||
| d27f0e3633 | |||
| e19bd0d517 | |||
| 3427120b3f | |||
| ecc623cd6c | |||
| 3ef615f508 | |||
| 1c4c3a6cae | |||
| fd7142f6a1 | |||
| b30fda3c36 | |||
| 7e43b40415 | |||
| 53501a9443 | |||
| c5dc65fc61 | |||
| b389889e1f | |||
| 02a29056b7 | |||
| 5e789b4b0c | |||
| 9ff09a45be | |||
| fc02a3d34b | |||
| 48f983c040 | |||
| dd25d387fe | |||
| 80a503f317 | |||
| 5aa9168dd6 | |||
| b3f01fe314 | |||
| e688d87c22 | |||
| 332fb2f384 | |||
| 5332bcd6b4 | |||
| 55c59e6164 | |||
| 80bfa1859f | |||
| 58e1880773 | |||
| c8c4c99f09 | |||
| 350f424055 | |||
| bc5b7a6725 | |||
| 7b4e71e6dd | |||
| a32202ab72 | |||
| 193c027c8b | |||
| 3dd07b6ac1 | |||
| 12d912b627 | |||
| f847a72696 | |||
| 967c3f6dba | |||
| 6cf87e3aa9 | |||
| cbc6477431 | |||
| 45d0f101a7 | |||
| cba96aee37 | |||
| 17325834b5 | |||
| 3673144a44 | |||
| 4df49d9f18 | |||
| 8b04608d68 | |||
| 2bac276870 | |||
| 213f9850e7 | |||
| 378bffbf84 | |||
| c291d5313d | |||
| 1f6f665639 | |||
| e81233c586 | |||
| b946029394 | |||
| 6095936354 | |||
| fe79a1231d | |||
| 76a5872d17 | |||
| 0d72c75e15 | |||
| 2e0e89c494 | |||
| 7d6b7a5874 | |||
| 48f8ddf53a | |||
| 9aae07b737 | |||
| d7abdc8017 | |||
| cb81156835 | |||
| 1fd8d94e2e | |||
| e4fe127444 | |||
| aeabd5f113 | |||
| 58f7d4065c | |||
| 60f25a3ba4 | |||
| d267571dc6 | |||
| 3c34aa6696 | |||
| 5dc00a2092 | |||
| b20d489bf0 | |||
| df39870587 | |||
| f742eab4be | |||
| e9c8c61dcf | |||
| e5f09002e9 | |||
| d42f005e17 | |||
| 24e060abee | |||
| 8b6d763442 | |||
| e239745f63 | |||
| cfef2b4e19 | |||
| cf93c39881 | |||
| 5d223bce4c | |||
| b454ebc1c9 | |||
| 7cf9910cae | |||
| 79ab1d8cb1 | |||
| 7cd20f5d12 | |||
| 5396d751e1 | |||
| d23e715650 | |||
| 0791bce338 | |||
| 2bcb74072a | |||
| c5f2f17f33 | |||
| 62a8bf4261 | |||
| ebda18b44e | |||
| a8f41b9017 | |||
| 410c19a7da | |||
| a346e2e04b | |||
| 2d114b6677 | |||
| 02e74ca1f4 | |||
| 69122cdec4 | |||
| d730168631 | |||
| 2f210ebe3b | |||
| 693b53baa2 | |||
| 77554879a5 | |||
| 45363922b5 | |||
| 0463c1ceda | |||
| b35a55a658 | |||
| 4da68c9fa8 | |||
| 72f133f31b | |||
| af4a8f6d9c | |||
| b5899a2e42 | |||
| 4ee8f8564e | |||
| ff08c03bc5 | |||
| 95dd430b0d | |||
| 20eb8b1442 | |||
| 2d6a0b3ed0 | |||
| 80f0ce78e0 | |||
| fde0e2434e | |||
| fe2da7fd95 | |||
| 25e783ff2f | |||
| 2eb94f1b66 | |||
| 2cdf6ac8f9 | |||
| 309c58a93d | |||
| 700bd0ddd4 | |||
| bd5a2f240d | |||
| 73054d971c | |||
| 8c7f2491d7 | |||
| 24fcbea6f2 | |||
| fddc9c8c48 | |||
| 294893b7da | |||
| 564542420b | |||
| 3f7e3e6d75 | |||
| 6521d7d02b | |||
| ad162a4e4a | |||
| 676b1f6fdc | |||
| 406ef4e16c | |||
| 217976350b | |||
| e8f860d6fe | |||
| 859e6e2396 | |||
| 51186e615f | |||
| bd8287fda3 | |||
| 287ff99bbc | |||
| 0bbfcedabb | |||
| 812e1e637d | |||
| f20fd38940 | |||
| b4cfabc005 | |||
| c49e160501 | |||
| a0a24902d5 | |||
| 1047bc8a80 | |||
| 0eed49c4cb | |||
| e7f09e2ece | |||
| 89678f1ea7 | |||
| e43ec7d549 | |||
| 95395132f0 | |||
| 89293d8481 | |||
| e6de24557f | |||
| 66d76dc36a | |||
| 06629def62 | |||
| 97aeb5e930 | |||
| bf45b6e20b | |||
| 6ad41326b0 | |||
| 2bbca3c169 | |||
| ae903b0712 | |||
| a2a7f82716 | |||
| 00456c891a | |||
| afd5faaab1 | |||
| bb33cf0f8d | |||
| edc510572a | |||
| bc6cffa95c | |||
| 48ed2912b0 | |||
| a8af262c41 | |||
| dcf49933bc | |||
| 9c014001da | |||
| 4f410088dd | |||
| e1c1b66dc5 | |||
| 23c3a1fa99 | |||
| ef6e1b2f74 | |||
| e130ae0a8a | |||
| 1a1e3281e4 | |||
| a4e55c9362 | |||
| 0b4c51eebd | |||
| 1107f0d9a3 | |||
| 775819432b | |||
| a71a60937f | |||
| 5e661fe96b | |||
| a9f5b17fcb | |||
| b0c2b55fc9 | |||
| 925832aac5 | |||
| 994131e2ea | |||
| f8d27d0ae0 | |||
| fc50af0e17 | |||
| 087d3535f6 | |||
| e9d310962f | |||
| 0f9c89d259 | |||
| 258c37685b | |||
| 56f092a0c3 | |||
| 6c3048ffbf | |||
| c58eba61de | |||
| 8a54773f1b | |||
| 2c483e7b97 | |||
| 1027e9ffdc | |||
| 85d6957e03 | |||
| c3609b66a9 | |||
| 605f4a92d7 | |||
| d395e2bc75 | |||
| e6ccdc5a97 | |||
| a68c7aa45f | |||
| 66e959ab65 | |||
| d68b172a40 | |||
| d3ee778265 | |||
| da96df7df7 | |||
| 4ea82581ec | |||
| 4473d3400e | |||
| 2f1f843e48 | |||
| 53a3d9042e | |||
| 6eb32f98b2 | |||
| 0fad97ed48 | |||
| 27246247a2 | |||
| cbc96e2223 | |||
| 8fdf32cde8 | |||
| 03a94e59e2 | |||
| 2c0610fef8 | |||
| 60b3c51085 | |||
| d7b5a45417 | |||
| e11060bde8 | |||
| 4653de9161 | |||
| 1926ad9215 | |||
| 33d047c703 | |||
| fadda00246 | |||
| a3fed788d8 | |||
| dde31e8687 | |||
| 190fb009bc | |||
| 62574d04c6 | |||
| 2496257bcf | |||
| ab73b4de6b | |||
| 6c1c32baca | |||
| 239cc15c8f | |||
| 6b437c3907 | |||
| 4756f63169 | |||
| 7229acb34f | |||
| 43cb91760a | |||
| 7bb4d052d1 | |||
| 5885bdf0f8 | |||
| 05042b1859 | |||
| 3ca2f72184 | |||
| b076450b73 | |||
| 7a90662c06 | |||
| eb31aaeb7d | |||
| ebfb215246 | |||
| eb97e4e518 | |||
| 61d3e65c05 | |||
| 0ac5681d13 | |||
| 1e27c5759b | |||
| 5e71aedc99 | |||
| d0d8c2316b | |||
| 8f0a5b2191 | |||
| 242b180862 | |||
| feb4766d7b | |||
| a224eea077 | |||
| ecefbc23ae | |||
| e5e0cbfc53 | |||
| 38781c9395 | |||
| e70d72b614 | |||
| fcf648bbb2 | |||
| a8325b6bce | |||
| 22554a9ba4 | |||
| 02a10ef651 | |||
| e3ca44f3ca | |||
| a9abc0dd5f | |||
| cbcc7c718e | |||
| 4377c79068 | |||
| 514993bc2e | |||
| c182176134 | |||
| abd1f10395 | |||
| f279bebc1a | |||
| 4e3fa5a6ff | |||
| 01d355a247 | |||
| 009f71a186 | |||
| de5c311d84 | |||
| eefe34f99e | |||
| 82e3c870b3 | |||
| d44a1d9761 | |||
| e6fd54c14b | |||
| ccb8e90110 | |||
| a4e66f291a | |||
| 3ab3f34bef | |||
| 99da8c4424 | |||
| 9a1bf1cf74 | |||
| 2256e99157 | |||
| 3a5f8b4987 | |||
| 6de5fcc980 | |||
| a454d0d4b7 | |||
| 67339754e3 | |||
| 7316fe00ee | |||
| feb4a7021c | |||
| 5e84b671b6 | |||
| bcefef62f9 | |||
| cc953dda34 | |||
| f9e5b0f0c7 | |||
| 578654411e | |||
| 247e92cfa2 | |||
| 66f605de0f | |||
| 709b0efc9b | |||
| 622679f4c2 | |||
| fdeb421513 | |||
| 8fe25cde8d | |||
| fbd71451f1 | |||
| 0d91ce8e6a | |||
| d71796c88a | |||
| 277748c8f5 | |||
| 8c1358ace9 | |||
| 1254916058 | |||
| f228bee4b8 | |||
| 8094477b09 | |||
| 32a5bf76cd | |||
| 0375d000e6 | |||
| 141d43d3e5 | |||
| 823f7b1d2e | |||
| 6579f011d0 | |||
| 93f768af9b | |||
| f8c11bf217 | |||
| 26ccd930c3 | |||
| 82211c7312 | |||
| 2015c154fe | |||
| ef17d116a8 | |||
| 46fddc44bf | |||
| 0214a77cd7 | |||
| 425ed658f1 | |||
| a53adb561e | |||
| 3c3c55090a | |||
| ebc04c6520 | |||
| 8b0e8f5b13 | |||
| 16132a007e | |||
| b6e41ceea7 | |||
| 7015e46227 | |||
| cce2607c80 | |||
| 9dd2ec8bda | |||
| 068726e0ab | |||
| 89e86ad9bd | |||
| 2e49bc2044 | |||
| 174c8dafbf | |||
| 90a96293de | |||
| 84877c4fec | |||
| a7cceb5fa9 | |||
| ca6359a597 | |||
| b7c3667be1 | |||
| b6dea59db3 | |||
| aa51f13743 | |||
| f34ec03ff0 | |||
| 1363be59b7 | |||
| 622c24ef24 | |||
| 539b0e49d4 | |||
| 0c42976312 | |||
| 3f6b3a4fa0 | |||
| 67e1773495 | |||
| a199b64aa0 | |||
| ebf09aceb2 | |||
| ca226e4295 | |||
| 9261939f62 | |||
| 0349931953 | |||
| d612a385d2 | |||
| ed4f299d55 | |||
| 7cef789d41 | |||
| 66bfb86d42 | |||
| c4a5bc12ef | |||
| 557631f6ba | |||
| 362ffaff7f | |||
| fb5ef200fb | |||
| 5e78ac3af5 | |||
| 719a090b34 | |||
| 3af85da6e0 | |||
| 8fd62aa525 | |||
| 40747f51bd | |||
| f3cef6bd73 | |||
| eef0ee8180 | |||
| 503e974375 | |||
| c959f2fee5 | |||
| 7d5e434cba | |||
| 2720bcdf18 | |||
| c513b7262b | |||
| 57a795df96 | |||
| 6bdd9e4543 | |||
| ede3def37f | |||
| 87d9022280 | |||
| ff0ba7d48b | |||
| b49c47425f | |||
| 3916ba1a42 | |||
| 0b3d22b97c | |||
| 9b8b0f2023 | |||
| 06e0d17be0 | |||
| 239c485f3c | |||
| 5e5fdda0ca | |||
| 4b2dddf3c6 | |||
| c99ec745ca | |||
| 1ec2e455ec | |||
| 69304737c6 | |||
| fe91670127 | |||
| 7a59f94f3d | |||
| 4efe3a333d | |||
| 421bf28582 | |||
| 4c49ffe3d1 | |||
| 26b1ef247b | |||
| 3aafba707a | |||
| ae774e88fa | |||
| ff56dd53cf | |||
| 12f063c178 | |||
| 888148d282 | |||
| 7bba0b82ef | |||
| 41196a862d | |||
| a99ed0e557 | |||
| 654981fb03 | |||
| c473c36d46 | |||
| e863e61af8 | |||
| efb486dd1b | |||
| 25b15fcdd1 | |||
| 1106fbb5ef | |||
| 9e1a29f0ff | |||
| b3c057f911 | |||
| 1c33e9ead9 | |||
| d78f35b940 | |||
| 506236a5ed | |||
| 18b32dbba3 | |||
| 26e40564dc | |||
| b6e8421a0a | |||
| 03c5a3b325 | |||
| a1f33d3fc6 | |||
| b8fca7db80 | |||
| 6ea70cd245 | |||
| 683fea675e | |||
| 811a010a60 | |||
| 019526332d | |||
| 1e90387198 | |||
| 84d6bb47ea | |||
| 512179d92a | |||
| 04344a3723 | |||
| d032207473 | |||
| b33dc2779d | |||
| 28699a1af5 | |||
| ff3fe135a3 | |||
| ff69709926 | |||
| 2162822cec | |||
| 34330baaa0 | |||
| 28d8aab7e5 | |||
| 6a91c89126 | |||
| c350f6fe5e | |||
| 493bf0a666 | |||
| 0305203e61 | |||
| 71d7b1dfad | |||
| 69748a1f1b | |||
| 39c2db38b7 | |||
| f499de3622 | |||
| e8a16a8fce | |||
| 81f2952c97 | |||
| dcf9de1a01 | |||
| 95f57f4eeb | |||
| 7a794b8b6e | |||
| cbaf841f13 | |||
| 6fdca9e89c | |||
| 8c3b3d98f6 | |||
| 6dd2abfb61 | |||
| 32defde397 | |||
| b1969c9528 | |||
| b6d5af81b5 | |||
| 0358853d5a | |||
| eb60f63223 | |||
| fdbb06436d | |||
| bceaa073cd | |||
| ccace48d5a | |||
| 193203bbf7 | |||
| d0cc4e1557 | |||
| d52ae8c662 | |||
| 6afd40cb39 | |||
| 6713baf86b | |||
| 365145e7c0 | |||
| 0413af79e9 | |||
| 868c498e28 | |||
| c5fbbe8a69 | |||
| b698850ce8 | |||
| 0d1fe03369 | |||
| 98b900e886 | |||
| 8210da9e34 | |||
| fac3d99f64 | |||
| d3c216374f | |||
| 105272630e | |||
| 53cc8ecaf0 | |||
| fb8e8b4b3a | |||
| 035713b4d3 | |||
| 54b7dc56b5 | |||
| 7fd39f44d0 | |||
| 691292501a | |||
| a58158ae08 | |||
| ef09b971fa | |||
| e07dee380d | |||
| 125bc5baa6 | |||
| 995444b11b | |||
| 8f2384dbfc | |||
| 0cdd1c23ce | |||
| 4765a39759 | |||
| 7f4047772c | |||
| 45c4ca6bec | |||
| 4a573a5aae | |||
| 5125ff6a8c | |||
| 482d3301ce | |||
| cdeec8ac47 | |||
| 3cef12b53b | |||
| dd098a16a8 | |||
| 61a175e84a | |||
| a5bcd38fe8 | |||
| cad42beef4 | |||
| 5a57958639 | |||
| 260336c1e5 | |||
| 889cb9c78f | |||
| b90e8f5af3 | |||
| 12361d2854 | |||
| d307ddfa8e | |||
| 96fd0b7892 | |||
| 6f1db15d7c | |||
| 1854296ee8 | |||
| 515cc5f326 | |||
| 091be7eafe | |||
| 27a19ea417 | |||
| 9a5e9af67c | |||
| 3a493f2428 | |||
| ca6e34f4b4 | |||
| e1e68312c4 | |||
| c7ff2cece4 | |||
| 8e6f4fa36f | |||
| 4d302da9fa | |||
| e0917dc734 | |||
| 26f82e8143 | |||
| 6518f08bc7 | |||
| 8599123b30 | |||
| f934a1aa10 | |||
| 81be5f809f | |||
| 8bb94804d4 | |||
| c3f64e85ce | |||
| 53057aff5d | |||
| 8cad5ac7e9 | |||
| b4f643f3dd | |||
| 2d659289e8 | |||
| 9883640b63 | |||
| bc3a0c3c91 | |||
| 4e822347a5 | |||
| d5650da8c0 | |||
| d3c77523c3 | |||
| 787a5ce568 | |||
| 91dfd405d1 | |||
| 08c615c493 | |||
| 5483102276 | |||
| 32686d898b | |||
| 4b50c8d96c | |||
| c1780ee26b | |||
| 46b2db00bc | |||
| 7042e457ab | |||
| faeec1701f | |||
| 2a75a1f9f2 | |||
| 8c65dccc53 | |||
| de7c3ba92f | |||
| ac204aadd2 | |||
| 46fc0d677f | |||
| 2d4f4b0036 | |||
| c1de00bf9d | |||
| 4639e1c47c | |||
| 5d2c156bc9 | |||
| 357f98f015 | |||
| 62f23ac27c | |||
| 0936646ef9 | |||
| 856c12f46f | |||
| 7489783837 | |||
| a4f0a4c310 | |||
| bb1ef114f1 | |||
| f1610b6407 | |||
| 5e48a4c724 | |||
| d825c03372 | |||
| d177549dd6 | |||
| 9d1543401f | |||
| 094eb7e252 | |||
| 95e6726468 | |||
| 8fded8f210 | |||
| 19c4940abd | |||
| 7b1f6b3c53 | |||
| 43042c3737 | |||
| 30b50b8a1b | |||
| 095be3072b | |||
| 91831200d6 | |||
| 8295d4511b | |||
| df589d9588 | |||
| b826e1c661 | |||
| 6727e2fe73 | |||
| ecdcee8d4e | |||
| 8b4a4369c1 | |||
| eeb06de916 | |||
| 5018d7d577 | |||
| 1ca279d99d | |||
| 8a149a188c | |||
| 076334bc4e | |||
| e6b45c978c | |||
| a07615445f | |||
| 41d30c2835 | |||
| 71f1635e23 | |||
| 57df6d9bf7 | |||
| fd670d5175 | |||
| 39d4c315c8 | |||
| 6487086354 | |||
| 7d6e24b8ed | |||
| 4922073300 | |||
| 778a02324e | |||
| 8e89aa97a0 | |||
| dfd521e938 | |||
| d47332adf5 | |||
| 14e7ba8fab | |||
| e68a356fd0 | |||
| 6e77b8659c | |||
| 00fad7e424 | |||
| 0a65248bf7 | |||
| 9cff26b163 | |||
| 9309d8c3f2 | |||
| 07e96c10d2 | |||
| d95abc99d9 | |||
| b83c2615de | |||
| 78a2b27e65 | |||
| bae594e34c | |||
| 4ded6fceea | |||
| 0e498829f7 | |||
| ddd090d581 | |||
| 4cd979e5fb | |||
| 2f7a6bb242 | |||
| 3b3b2e61b0 | |||
| ab4fde9bd7 | |||
| a9996f0b81 | |||
| 246d34e072 | |||
| d35efe3f32 | |||
| ebd1a6b47c | |||
| 83980678a0 | |||
| 201393f87d | |||
| 055eb3873e | |||
| dc94d58148 | |||
| 0adaec1665 | |||
| 4ee30dc36f | |||
| 54ff2fa01f | |||
| 03c70b49ba | |||
| 4b2d8e13d1 | |||
| a0c50f0521 | |||
| b15a865c88 | |||
| 8e5bbbbc71 | |||
| 615ebaf711 | |||
| 0882d2b7ce | |||
| 900195efac | |||
| b58b962ccf | |||
| 5255499445 | |||
| d9a2be4250 | |||
| 256e14a8a6 | |||
| 1ab26d4a2f | |||
| 91b2c751af | |||
| edf7617d1e | |||
| 32666d304f | |||
| b65f7b4a6a | |||
| 7c4df23c1c | |||
| a8e60163e1 | |||
| 02ec1b5da6 | |||
| a9a6aba862 | |||
| 03c6a60f68 | |||
| 8ab688687e | |||
| bdec32722e | |||
| ad50e5c754 | |||
| 9c48e44e9e | |||
| 76284eb462 | |||
| 0745c5128a | |||
| 01fbe2d3de | |||
| 9e14c22259 | |||
| dff0111cd5 | |||
| e7452b0ea1 | |||
| 61a0f892c4 | |||
| 4ceab01ed4 | |||
| 9908969eea | |||
| 19a78ef1ac | |||
| 4785c1ae84 | |||
| ef03841efa | |||
| 4747a70ce7 | |||
| cd986cc2dc | |||
| c29d5ca4a8 | |||
| 56b49011d6 | |||
| 48c55211e6 | |||
| 72f68f3b0b | |||
| 7b6dddc994 | |||
| 51fbe4e8c5 | |||
| c148d9ee6c | |||
| 9dfe59a104 | |||
| b6aae65afd | |||
| 9fed93a771 | |||
| cdfb68f261 | |||
| 46450bd080 | |||
| 9a25d601f1 | |||
| fe0834ecda | |||
| 846f745e2c | |||
| 30d40e6f9b | |||
| f7501b10f7 | |||
| 379c513f8a | |||
| 5a6d77e958 | |||
| 6646039ffe | |||
| 5e0994270f | |||
| 44fc801720 | |||
| 405c61f53d | |||
| c40acb9406 | |||
| 7778d2a47e | |||
| 96afb245a5 | |||
| cf0677c30b | |||
| 667614d9de | |||
| 652ede57b3 | |||
| 09a34f880e | |||
| a9f9be330d | |||
| 39568d2464 | |||
| 10e07a9966 | |||
| fe00a69136 | |||
| 9d0c2cd67f | |||
| b5aab442f8 | |||
| 7c010bd1ef | |||
| 1bf898405f | |||
| c490166b35 | |||
| e6862364ed | |||
| cf20c84edd | |||
| 88e776ad5b | |||
| e79a60f5cd | |||
| fd4a91ba72 | |||
| 5705ece2a3 | |||
| c723f20f39 | |||
| 0f7447d539 | |||
| 1a08944854 | |||
| 7b0b06f6df | |||
| d2ad227a24 | |||
| 71d7982d14 | |||
| a94dcc12ef | |||
| b7bfcfa1e3 | |||
| 416ae0ca04 | |||
| 8d66cd4874 | |||
| a701ba8030 | |||
| 0160908522 | |||
| cdd0d6d127 | |||
| 65ee745d6e | |||
| 4141dfc353 | |||
| fb6cd105c3 | |||
| 6ff9168146 | |||
| 7b5e08aab6 | |||
| c7dd4526c1 | |||
| 066036ccdd | |||
| 7c164453a5 | |||
| 8b31cfeafb | |||
| 2ddaf0afa3 | |||
| a8a97b4606 | |||
| a55b63a210 | |||
| 2e4f7cd667 | |||
| bf257a8d9e | |||
| c4e66f7a35 | |||
| efff433aa0 | |||
| ee60e36a16 | |||
| 841fc3cfaf | |||
| 2a44caea6c | |||
| 0f661928ae | |||
| 0961e5cc2e | |||
| df621a8205 | |||
| bfa416ca99 | |||
| 8041b87317 | |||
| b3000f6350 | |||
| 947baab269 | |||
| a41ea90ca7 | |||
| 8b3f0d8fd6 | |||
| bd9740a9a4 | |||
| 3f735e44f1 | |||
| 9e5235fd30 | |||
| 159f3cb780 | |||
| 61469f8e09 | |||
| 71343b5131 | |||
| 82caee6d7d | |||
| 275e75980c | |||
| 2572da872a | |||
| 6934618589 | |||
| 01fd07c372 | |||
| 02f9cf0318 | |||
| 6bc586025a | |||
| 0d34960d60 | |||
| 99b94a31ea | |||
| b0d4bcd26c | |||
| 248ea52e06 | |||
| 9b51fe3db4 | |||
| 5f95696815 | |||
| 8c9df5556d | |||
| 32495f47b3 | |||
| 23bc561524 | |||
| fa2cc0f62e | |||
| a686a167cc | |||
| 8a2468a4fb | |||
| 4cc21a2c20 | |||
| 5350e41da1 | |||
| e07e6b6954 | |||
| 0a60e38d82 | |||
| f53b40e127 | |||
| 4df51a00ed | |||
| 3981c4d101 | |||
| fc3e8f7cef | |||
| f4d67ec5e6 | |||
| 59aafa6c1e | |||
| 75da46dac5 | |||
| 1f1d380e26 | |||
| 35b3e425be | |||
| b4535c489d | |||
| f6bb502e87 | |||
| 6cf825d3d8 | |||
| f766841fad | |||
| 10e4e7f6c6 | |||
| 1277e56435 | |||
| 4089532f81 | |||
| 9790b4d2e9 | |||
| ad37c0d2ac | |||
| b7a1fd4f8f | |||
| be5362e393 | |||
| 4977c9bc4c | |||
| e13dbc03da | |||
| 16fec0679b | |||
| 55361b8552 | |||
| cc67993621 | |||
| 49ba4998d6 | |||
| 03eb381b3b | |||
| e5161faa43 | |||
| de78fb7a1c | |||
| a9ceb5e21a | |||
| e62b41f615 | |||
| 592e339b70 | |||
| 84a9138df7 | |||
| 6db7c4a8eb | |||
| 213bd09a9c | |||
| 8eb246cdec | |||
| caacf8e373 | |||
| c53d42a578 | |||
| cfc5ef4a3c | |||
| e78c1bbec9 | |||
| b1582d33c0 | |||
| 5abcf28a0e | |||
| 4cd57856ce | |||
| a826fd5c0e | |||
| 7de23ec2aa | |||
| d7d2957319 | |||
| fbd81b9930 | |||
| dacb52403a | |||
| e008a02b99 | |||
| 9363453720 | |||
| 9c70615fd1 | |||
| 1c78c65816 | |||
| 2a9a68ca53 | |||
| fb16baab99 | |||
| 54f509c210 | |||
| 5be8e5eff3 | |||
| 29b9f129f6 | |||
| f41629daae | |||
| feea6023f4 | |||
| 262d8cd0d9 | |||
| fbbec04f8c | |||
| 3e4eaee96b | |||
| 5f99a2240d | |||
| 5937387e94 | |||
| b3099d8e71 | |||
| 7721f74200 | |||
| fa58cc05f3 | |||
| c61a9e47b2 | |||
| 148ee266ed | |||
| 8ccec81cc6 | |||
| 668901f71d | |||
| ad6ad144a5 | |||
| d5997a30b2 | |||
| ecc7501377 | |||
| 45262a1a46 | |||
| 3c04e08df2 | |||
| 7c7675179e | |||
| 88ed49a833 | |||
| 0c88e62815 | |||
| 88d34012c4 | |||
| 3be8de6fb0 | |||
| 804fbf5d5f | |||
| 1a68dcbc14 | |||
| a9a72a767d | |||
| afc3a8d373 | |||
| da00e6588c | |||
| d6376d0ddf | |||
| 1cca711560 | |||
| 552f9196af | |||
| c6fa72cd83 | |||
| 42edc46887 | |||
| ec7e343673 | |||
| 69d4d8acb0 | |||
| a7eab8df22 | |||
| 4247da9118 | |||
| cfba8aeb89 | |||
| db26a26926 | |||
| 1551fbeb1f | |||
| d5c53ca624 | |||
| b34702e370 | |||
| 8b1543d9c9 | |||
| e264375a97 | |||
| fd31d07f41 | |||
| fac15f5539 | |||
| 6ad88101f1 | |||
| 2768b66d10 | |||
| d10164be26 | |||
| ce7ff13bbe | |||
| c1d2c159f3 | |||
| d3dbdb153c | |||
| e7218c0321 | |||
| 48d8fdb875 | |||
| b387ca921a | |||
| 53c1a322ed | |||
| 0c502fc9cf | |||
| 5d1e3b6c93 | |||
| 7fc16fa2c8 | |||
| fe6a88c5df | |||
| 7b14df82e0 | |||
| 966b41313d | |||
| 91f1c3322c | |||
| 0b276c5a76 | |||
| b654c2e170 | |||
| 32c88da6c4 | |||
| 3d19d0816b | |||
| 15da707324 | |||
| 387e8b04f9 | |||
| 4ca47be8a8 | |||
| f9d9dc68b7 | |||
| 14a8f4511c | |||
| 97d237eed0 | |||
| 20d0406a24 | |||
| 73e843abd3 | |||
| 8d956da65b | |||
| 9b9cdfe937 | |||
| 3dcba9362c | |||
| 2d3a3ada57 | |||
| 143d1d5e35 | |||
| 13b32b269c | |||
| 592cea6a27 | |||
| e76ca304e6 | |||
| d77d8df1ac | |||
| 4f35d5dabd | |||
| e927feb2d6 | |||
| 98f20c57c1 | |||
| 7a88d31fd9 | |||
| 96326411bf | |||
| 4e882e7d4d | |||
| bff10c1714 | |||
| cee2484108 | |||
| 93078abe87 | |||
| 8caa1a9664 | |||
| d7b46ee03c | |||
| e07b3da983 | |||
| 0f166cee48 | |||
| 08df42d05b | |||
| 2c165c3873 | |||
| 9135402d9e | |||
| 53135ec2c0 | |||
| 4eb8c8dea9 | |||
| f318bec53c | |||
| 7d84d6909e | |||
| 9224645473 | |||
| 6717771f9a | |||
| 99e0902b74 | |||
| c7f2805b05 | |||
| 6e1909647b | |||
| 0c7db56e15 | |||
| 53a8f65ecc | |||
| faa76ee017 | |||
| ffdefb4106 |
@@ -17,7 +17,9 @@ jobs:
|
||||
xcode-version: latest-stable
|
||||
- name: Make
|
||||
working-directory: OSBindings/Mac
|
||||
run: xcodebuild CODE_SIGN_IDENTITY=-
|
||||
run: |
|
||||
xcodebuild -downloadComponent MetalToolchain
|
||||
xcodebuild CODE_SIGN_IDENTITY=-
|
||||
|
||||
build-sdl-cmake:
|
||||
name: SDL UI / cmake / ${{ matrix.os }}
|
||||
@@ -37,6 +39,7 @@ jobs:
|
||||
sudo apt-get --fix-missing install cmake gcc-10 libsdl2-dev
|
||||
;;
|
||||
macOS)
|
||||
brew uninstall cmake
|
||||
brew install cmake sdl2
|
||||
;;
|
||||
esac
|
||||
|
||||
+1
-1
@@ -14,7 +14,7 @@ namespace Activity {
|
||||
|
||||
class Source {
|
||||
public:
|
||||
virtual void set_activity_observer(Observer *observer) = 0;
|
||||
virtual void set_activity_observer(Observer *) = 0;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
|
||||
using namespace Analyser::Dynamic;
|
||||
|
||||
float ConfidenceCounter::get_confidence() {
|
||||
float ConfidenceCounter::confidence() const {
|
||||
return float(hits_) / float(hits_ + misses_);
|
||||
}
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ namespace Analyser::Dynamic {
|
||||
class ConfidenceCounter: public ConfidenceSource {
|
||||
public:
|
||||
/*! @returns The computed probability, based on the history of events. */
|
||||
float get_confidence() final;
|
||||
float confidence() const final;
|
||||
|
||||
/*! Records an event that implies this is the appropriate class: pushes probability up towards 1.0. */
|
||||
void add_hit();
|
||||
|
||||
@@ -17,7 +17,7 @@ namespace Analyser::Dynamic {
|
||||
program is handed to an Atari 2600 then its confidence should grow towards 1.0.
|
||||
*/
|
||||
struct ConfidenceSource {
|
||||
virtual float get_confidence() = 0;
|
||||
virtual float confidence() const = 0;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -22,10 +22,10 @@ ConfidenceSummary::ConfidenceSummary(
|
||||
weight_sum_ = std::accumulate(weights.begin(), weights.end(), 0.0f);
|
||||
}
|
||||
|
||||
float ConfidenceSummary::get_confidence() {
|
||||
float ConfidenceSummary::confidence() const {
|
||||
float result = 0.0f;
|
||||
for(std::size_t index = 0; index < sources_.size(); ++index) {
|
||||
result += sources_[index]->get_confidence() * weights_[index];
|
||||
result += sources_[index]->confidence() * weights_[index];
|
||||
}
|
||||
return result / weight_sum_;
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@ public:
|
||||
const std::vector<float> &weights);
|
||||
|
||||
/*! @returns The weighted sum of all sources. */
|
||||
float get_confidence() final;
|
||||
float confidence() const final;
|
||||
|
||||
private:
|
||||
const std::vector<ConfidenceSource *> sources_;
|
||||
|
||||
@@ -106,5 +106,5 @@ void MultiTimedMachine::run_for(const Time::Seconds duration) {
|
||||
if(machine->get_confidence() >= 0.01f) machine->run_for(duration);
|
||||
});
|
||||
|
||||
if(delegate_) delegate_->did_run_machines(this);
|
||||
if(delegate_) delegate_->did_run_machines(*this);
|
||||
}
|
||||
|
||||
@@ -60,7 +60,7 @@ public:
|
||||
been received.
|
||||
*/
|
||||
struct Delegate {
|
||||
virtual void did_run_machines(MultiTimedMachine *) = 0;
|
||||
virtual void did_run_machines(MultiTimedMachine &) = 0;
|
||||
};
|
||||
/// Sets @c delegate as the receiver of delegate messages.
|
||||
void set_delegate(Delegate *const delegate) {
|
||||
|
||||
@@ -60,24 +60,24 @@ void MultiSpeaker::set_output_volume(const float volume) {
|
||||
}
|
||||
}
|
||||
|
||||
void MultiSpeaker::speaker_did_complete_samples(Speaker *const speaker, const std::vector<int16_t> &buffer) {
|
||||
void MultiSpeaker::speaker_did_complete_samples(Speaker &speaker, const std::vector<int16_t> &buffer) {
|
||||
auto delegate = delegate_.load(std::memory_order_relaxed);
|
||||
if(!delegate) return;
|
||||
{
|
||||
std::lock_guard lock_guard(front_speaker_mutex_);
|
||||
if(speaker != front_speaker_) return;
|
||||
if(&speaker != front_speaker_) return;
|
||||
}
|
||||
did_complete_samples(this, buffer, stereo_output_);
|
||||
}
|
||||
|
||||
void MultiSpeaker::speaker_did_change_input_clock(Speaker *const speaker) {
|
||||
void MultiSpeaker::speaker_did_change_input_clock(Speaker &speaker) {
|
||||
auto delegate = delegate_.load(std::memory_order_relaxed);
|
||||
if(!delegate) return;
|
||||
{
|
||||
std::lock_guard lock_guard(front_speaker_mutex_);
|
||||
if(speaker != front_speaker_) return;
|
||||
if(&speaker != front_speaker_) return;
|
||||
}
|
||||
delegate->speaker_did_change_input_clock(this);
|
||||
delegate->speaker_did_change_input_clock(*this);
|
||||
}
|
||||
|
||||
void MultiSpeaker::set_new_front_machine(::Machine::DynamicMachine *const machine) {
|
||||
@@ -87,6 +87,6 @@ void MultiSpeaker::set_new_front_machine(::Machine::DynamicMachine *const machin
|
||||
}
|
||||
auto delegate = delegate_.load(std::memory_order_relaxed);
|
||||
if(delegate) {
|
||||
delegate->speaker_did_change_input_clock(this);
|
||||
delegate->speaker_did_change_input_clock(*this);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,8 +42,8 @@ public:
|
||||
void set_output_volume(float) override;
|
||||
|
||||
private:
|
||||
void speaker_did_complete_samples(Speaker *speaker, const std::vector<int16_t> &buffer) final;
|
||||
void speaker_did_change_input_clock(Speaker *speaker) final;
|
||||
void speaker_did_complete_samples(Speaker &, const std::vector<int16_t> &buffer) final;
|
||||
void speaker_did_change_input_clock(Speaker &) final;
|
||||
MultiSpeaker(const std::vector<Outputs::Speaker::Speaker *> &speakers);
|
||||
|
||||
std::vector<Outputs::Speaker::Speaker *> speakers_;
|
||||
|
||||
@@ -12,9 +12,7 @@
|
||||
#include <algorithm>
|
||||
|
||||
namespace {
|
||||
|
||||
Log::Logger<Log::Source::MultiMachine> logger;
|
||||
|
||||
using Logger = Log::Logger<Log::Source::MultiMachine>;
|
||||
}
|
||||
|
||||
using namespace Analyser::Dynamic;
|
||||
@@ -68,11 +66,11 @@ bool MultiMachine::would_collapse(const std::vector<std::unique_ptr<DynamicMachi
|
||||
(machines.front()->timed_machine()->get_confidence() >= 2.0f * machines[1]->timed_machine()->get_confidence());
|
||||
}
|
||||
|
||||
void MultiMachine::did_run_machines(MultiTimedMachine *) {
|
||||
void MultiMachine::did_run_machines(MultiTimedMachine &) {
|
||||
std::lock_guard machines_lock(machines_mutex_);
|
||||
|
||||
if constexpr (logger.enabled) {
|
||||
auto line = logger.info();
|
||||
if constexpr (Logger::InfoEnabled) {
|
||||
auto line = Logger::info();
|
||||
for(const auto &machine: machines_) {
|
||||
auto timed_machine = machine->timed_machine();
|
||||
line.append("%0.4f %s; ", timed_machine->get_confidence(), timed_machine->debug_type().c_str());
|
||||
|
||||
@@ -62,7 +62,7 @@ public:
|
||||
void *raw_pointer() final;
|
||||
|
||||
private:
|
||||
void did_run_machines(MultiTimedMachine *) final;
|
||||
void did_run_machines(MultiTimedMachine &) final;
|
||||
|
||||
std::vector<std::unique_ptr<DynamicMachine>> machines_;
|
||||
std::recursive_mutex machines_mutex_;
|
||||
|
||||
@@ -18,6 +18,7 @@ enum class Machine {
|
||||
AtariST,
|
||||
Amiga,
|
||||
Archimedes,
|
||||
BBCMicro,
|
||||
ColecoVision,
|
||||
Electron,
|
||||
Enterprise,
|
||||
|
||||
@@ -44,7 +44,7 @@ std::unique_ptr<Catalogue> Analyser::Static::Acorn::GetDFSCatalogue(const std::s
|
||||
case 3: catalogue->bootOption = Catalogue::BootOption::ExecBOOT; break;
|
||||
}
|
||||
|
||||
for(std::size_t file_offset = 8; file_offset < final_file_offset; file_offset += 8) {
|
||||
for(std::size_t file_offset = 8; file_offset <= final_file_offset; file_offset += 8) {
|
||||
File new_file;
|
||||
char name[10];
|
||||
snprintf(name, 10, "%c.%.7s", names->samples[0][file_offset + 7] & 0x7f, &names->samples[0][file_offset]);
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
@@ -19,6 +19,23 @@
|
||||
|
||||
using namespace Analyser::Static::Acorn;
|
||||
|
||||
namespace {
|
||||
bool is_basic(const File &file) {
|
||||
std::size_t pointer = 0;
|
||||
const uint8_t *const data = file.data.data();
|
||||
const std::size_t data_size = file.data.size();
|
||||
while(true) {
|
||||
if(pointer >= data_size-1 || data[pointer] != 0x0d) {
|
||||
return false;
|
||||
}
|
||||
if((data[pointer+1]&0x7f) == 0x7f) break;
|
||||
pointer += data[pointer+3];
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static std::vector<std::shared_ptr<Storage::Cartridge::Cartridge>>
|
||||
AcornCartridgesFrom(const std::vector<std::shared_ptr<Storage::Cartridge::Cartridge>> &cartridges) {
|
||||
std::vector<std::shared_ptr<Storage::Cartridge::Cartridge>> acorn_cartridges;
|
||||
@@ -68,11 +85,22 @@ Analyser::Static::TargetList Analyser::Static::Acorn::GetTargets(
|
||||
TargetPlatform::IntType,
|
||||
bool
|
||||
) {
|
||||
auto target8bit = std::make_unique<ElectronTarget>();
|
||||
auto targetArchimedes = std::make_unique<ArchimedesTarget>();
|
||||
const auto early_exit = [](auto &ptr) {
|
||||
TargetList list;
|
||||
list.push_back(std::move(ptr));
|
||||
return list;
|
||||
};
|
||||
|
||||
// Copy appropriate cartridges to the 8-bit target.
|
||||
target8bit->media.cartridges = AcornCartridgesFrom(media.cartridges);
|
||||
auto targetElectron = std::make_unique<ElectronTarget>();
|
||||
auto targetBBC = std::make_unique<BBCMicroTarget>();
|
||||
auto targetArchimedes = std::make_unique<ArchimedesTarget>();
|
||||
int bbc_hits = 0;
|
||||
int electron_hits = 0;
|
||||
bool format_prefers_bbc = false;
|
||||
|
||||
// Copy appropriate cartridges to the 8-bit targets.
|
||||
targetElectron->media.cartridges = AcornCartridgesFrom(media.cartridges);
|
||||
targetBBC->media.cartridges = AcornCartridgesFrom(media.cartridges);
|
||||
|
||||
// If there are tapes, attempt to get data from the first.
|
||||
if(!media.tapes.empty()) {
|
||||
@@ -80,35 +108,15 @@ Analyser::Static::TargetList Analyser::Static::Acorn::GetTargets(
|
||||
auto serialiser = tape->serialiser();
|
||||
std::vector<File> files = GetFiles(*serialiser);
|
||||
|
||||
// continue if there are any files
|
||||
// Continue only if there are any files.
|
||||
if(!files.empty()) {
|
||||
bool is_basic = true;
|
||||
|
||||
// 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.
|
||||
if(is_basic) {
|
||||
std::size_t pointer = 0;
|
||||
uint8_t *const data = &files.front().data[0];
|
||||
const std::size_t data_size = files.front().data.size();
|
||||
while(true) {
|
||||
if(pointer >= data_size-1 || data[pointer] != 0x0d) {
|
||||
is_basic = false;
|
||||
break;
|
||||
}
|
||||
if((data[pointer+1]&0x7f) == 0x7f) break;
|
||||
pointer += data[pointer+3];
|
||||
}
|
||||
}
|
||||
|
||||
// Inspect first file. If it's protected or doesn't look like BASIC
|
||||
// then the loading command is *RUN. Otherwise it's CHAIN"".
|
||||
target8bit->loading_command = is_basic ? "CHAIN\"\"\n" : "*RUN\n";
|
||||
target8bit->media.tapes = media.tapes;
|
||||
targetElectron->loading_command =
|
||||
(files.front().flags & File::Flags::ExecuteOnly) || !is_basic(files.front()) ? "*RUN\n" : "CHAIN\"\"\n";
|
||||
targetElectron->media.tapes = media.tapes;
|
||||
|
||||
// TODO: my BBC Micro doesn't yet support tapes; evaluate here in the future.
|
||||
}
|
||||
}
|
||||
|
||||
@@ -123,25 +131,67 @@ Analyser::Static::TargetList Analyser::Static::Acorn::GetTargets(
|
||||
// 8-bit options: DFS and Hugo-style ADFS.
|
||||
if(dfs_catalogue || (adfs_catalogue && !adfs_catalogue->has_large_sectors && adfs_catalogue->is_hugo)) {
|
||||
// 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.
|
||||
target8bit->media.disks = media.disks;
|
||||
target8bit->has_dfs = bool(dfs_catalogue);
|
||||
target8bit->has_pres_adfs = bool(adfs_catalogue);
|
||||
|
||||
// Electron: use the Pres ADFS if using an ADFS, as it leaves Page at &E00.
|
||||
targetElectron->media.disks = media.disks;
|
||||
targetElectron->has_dfs = bool(dfs_catalogue);
|
||||
targetElectron->has_pres_adfs = bool(adfs_catalogue);
|
||||
|
||||
// BBC: only the 1770 DFS is currently supported, so use that.
|
||||
targetBBC->media.disks = media.disks;
|
||||
targetBBC->has_1770dfs = bool(dfs_catalogue);
|
||||
targetBBC->has_adfs = bool(adfs_catalogue);
|
||||
|
||||
// Special case: if there's only one file, and it is called CPMDISC,
|
||||
// select a BBC with the Z80 second processor.
|
||||
const auto &files = dfs_catalogue ? dfs_catalogue->files : adfs_catalogue->files;
|
||||
if(files.size() == 1 && files[0].name == "$.CPMDISC") {
|
||||
targetBBC->tube_processor = BBCMicroTarget::TubeProcessor::Z80;
|
||||
return early_exit(targetBBC);
|
||||
}
|
||||
|
||||
// Check whether a simple shift+break will do for loading this disk.
|
||||
const auto bootOption = (dfs_catalogue ?: adfs_catalogue)->bootOption;
|
||||
if(bootOption != Catalogue::BootOption::None) {
|
||||
target8bit->should_shift_restart = true;
|
||||
targetBBC->should_shift_restart = targetElectron->should_shift_restart = true;
|
||||
} else {
|
||||
target8bit->loading_command = "*CAT\n";
|
||||
// Otherwise: if there's only one BASIC program then chain it.
|
||||
// Failing that, do a *CAT to be communicative.
|
||||
|
||||
const File *sole_basic_file = nullptr;
|
||||
for(const auto &file: dfs_catalogue ? dfs_catalogue->files : adfs_catalogue->files) {
|
||||
if(is_basic(file)) {
|
||||
if(!sole_basic_file) {
|
||||
sole_basic_file = &file;
|
||||
} else {
|
||||
sole_basic_file = nullptr;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
targetBBC->loading_command = targetElectron->loading_command =
|
||||
sole_basic_file ? "CHAIN \"" + sole_basic_file->name + "\"\n" : "*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) {
|
||||
// Further special case: if any of the files have a top word of 0x0003 then
|
||||
// they're for a 6502 second processor, so provide a BBC with one of those.
|
||||
for(const auto &file: files) {
|
||||
if((file.load_address >> 16) == 3) {
|
||||
targetBBC->tube_processor = BBCMicroTarget::TubeProcessor::WDC65C02;
|
||||
return early_exit(targetBBC);
|
||||
}
|
||||
}
|
||||
|
||||
// Add a slight preference for the BBC over the Electron, all else being equal, if this is a DFS floppy.
|
||||
format_prefers_bbc = bool(dfs_catalogue);
|
||||
|
||||
for(const auto &file: files) {
|
||||
// Electron: 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 &command: {
|
||||
"AQRPAGE", "BUILD", "DUMP", "FORMAT", "INSERT", "LANG", "LIST", "LOADROM",
|
||||
"LOCK", "LROMS", "RLOAD", "ROMS", "RSAVE", "SAVEROM", "SRLOAD", "SRPAGE",
|
||||
@@ -149,10 +199,60 @@ Analyser::Static::TargetList Analyser::Static::Acorn::GetTargets(
|
||||
"VERIFY", "ZERO"
|
||||
}) {
|
||||
if(std::search(file.data.begin(), file.data.end(), command, command+strlen(command)) != file.data.end()) {
|
||||
target8bit->has_ap6_rom = true;
|
||||
target8bit->has_sideways_ram = true;
|
||||
targetElectron->has_ap6_rom = true;
|
||||
targetElectron->has_sideways_ram = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Look for any 'BBC indicators', i.e. direct access to BBC-specific hardware.
|
||||
// Also currently a dense search.
|
||||
|
||||
const auto hits = [&](const std::initializer_list<uint16_t> collection) {
|
||||
int hits = 0;
|
||||
for(const auto address: collection) {
|
||||
const uint8_t sta_address[3] = {
|
||||
0x8d, uint8_t(address & 0xff), uint8_t(address >> 8)
|
||||
};
|
||||
|
||||
if(std::search(
|
||||
file.data.begin(), file.data.end(),
|
||||
std::begin(sta_address), std::end(sta_address)
|
||||
) != file.data.end()) {
|
||||
++hits;
|
||||
}
|
||||
|
||||
// I think I'll want std::ranges::contains_subrange if/when building for C++23.
|
||||
}
|
||||
return hits;
|
||||
};
|
||||
|
||||
bbc_hits += hits({
|
||||
// The video control registers.
|
||||
0xfe20, 0xfe21,
|
||||
|
||||
// The system VIA.
|
||||
0xfe40, 0xfe41, 0xfe42, 0xfe43, 0xfe44, 0xfe45, 0xfe46, 0xfe47,
|
||||
0xfe48, 0xfe49, 0xfe4a, 0xfe4b, 0xfe4c, 0xfe4d, 0xfe4e, 0xfe4f,
|
||||
|
||||
// The user VIA.
|
||||
0xfe60, 0xfe61, 0xfe62, 0xfe63, 0xfe64, 0xfe65, 0xfe66, 0xfe67,
|
||||
0xfe68, 0xfe69, 0xfe6a, 0xfe6b, 0xfe6c, 0xfe6d, 0xfe6e, 0xfe6f,
|
||||
});
|
||||
// BASIC for "MODE7".
|
||||
static constexpr uint8_t mode7[] = {0xeb, 0x37};
|
||||
bbc_hits += std::search(
|
||||
file.data.begin(), file.data.end(),
|
||||
std::begin(mode7), std::end(mode7)
|
||||
) != file.data.end();
|
||||
|
||||
electron_hits += hits({
|
||||
// ULA addresses that aren't also the BBC's CRTC.
|
||||
0xfe03, 0xfe04, 0xfe05,
|
||||
0xfe06, 0xfe07, 0xfe08,
|
||||
0xfe09, 0xfe0a, 0xfe0b,
|
||||
0xfe0c, 0xfe0d, 0xfe0e,
|
||||
0xfe0f,
|
||||
});
|
||||
}
|
||||
} else if(adfs_catalogue) {
|
||||
// Archimedes options, implicitly: ADFS, non-Hugo.
|
||||
@@ -167,8 +267,8 @@ Analyser::Static::TargetList Analyser::Static::Acorn::GetTargets(
|
||||
// Take whatever else comes with a preference for things that don't
|
||||
// have 'boot' or 'read' in them (the latter of which will tend to be
|
||||
// read_me or read_this or similar).
|
||||
constexpr char read[] = "read";
|
||||
constexpr char boot[] = "boot";
|
||||
static constexpr char read[] = "read";
|
||||
static constexpr char boot[] = "boot";
|
||||
const auto has = [&](const char *begin, const char *end) {
|
||||
return std::search(
|
||||
file.name.begin(), file.name.end(),
|
||||
@@ -197,28 +297,38 @@ Analyser::Static::TargetList Analyser::Static::Acorn::GetTargets(
|
||||
// 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()) {
|
||||
target8bit->has_pres_adfs = false; // To override a floppy selection, if one was made.
|
||||
target8bit->has_acorn_adfs = true;
|
||||
targetElectron->has_pres_adfs = false; // To override a floppy selection, if one was made.
|
||||
targetElectron->has_acorn_adfs = true;
|
||||
|
||||
// Assume some sort of later-era Acorn work is likely to happen;
|
||||
// so ensure *TYPE, etc are present.
|
||||
target8bit->has_ap6_rom = true;
|
||||
target8bit->has_sideways_ram = true;
|
||||
targetElectron->has_ap6_rom = true;
|
||||
targetElectron->has_sideways_ram = true;
|
||||
|
||||
target8bit->media.mass_storage_devices = media.mass_storage_devices;
|
||||
targetElectron->media.mass_storage_devices = media.mass_storage_devices;
|
||||
|
||||
// Check for a boot option.
|
||||
const auto sector = target8bit->media.mass_storage_devices.front()->get_block(1);
|
||||
const auto sector = targetElectron->media.mass_storage_devices.front()->get_block(1);
|
||||
if(sector[0xfd]) {
|
||||
target8bit->should_shift_restart = true;
|
||||
targetElectron->should_shift_restart = true;
|
||||
} else {
|
||||
target8bit->loading_command = "*CAT\n";
|
||||
targetElectron->loading_command = "*CAT\n";
|
||||
}
|
||||
}
|
||||
|
||||
TargetList targets;
|
||||
if(!target8bit->media.empty()) {
|
||||
targets.push_back(std::move(target8bit));
|
||||
if(!targetElectron->media.empty() && !targetBBC->media.empty()) {
|
||||
if(bbc_hits > electron_hits || (bbc_hits == electron_hits && format_prefers_bbc)) {
|
||||
targets.push_back(std::move(targetBBC));
|
||||
} else {
|
||||
targets.push_back(std::move(targetElectron));
|
||||
}
|
||||
} else {
|
||||
if(!targetElectron->media.empty()) {
|
||||
targets.push_back(std::move(targetElectron));
|
||||
} else if(!targetBBC->media.empty()) {
|
||||
targets.push_back(std::move(targetBBC));
|
||||
}
|
||||
}
|
||||
if(!targetArchimedes->media.empty()) {
|
||||
targets.push_back(std::move(targetArchimedes));
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "Analyser/Static/StaticAnalyser.hpp"
|
||||
#include "Reflection/Enum.hpp"
|
||||
#include "Reflection/Struct.hpp"
|
||||
#include <string>
|
||||
|
||||
@@ -36,6 +37,32 @@ private:
|
||||
}
|
||||
};
|
||||
|
||||
struct BBCMicroTarget: public ::Analyser::Static::Target, public Reflection::StructImpl<BBCMicroTarget> {
|
||||
std::string loading_command;
|
||||
bool should_shift_restart = false;
|
||||
|
||||
bool has_1770dfs = false;
|
||||
bool has_adfs = false;
|
||||
bool has_sideways_ram = true;
|
||||
bool has_beebsid = false;
|
||||
|
||||
ReflectableEnum(TubeProcessor, None, WDC65C02, Z80);
|
||||
TubeProcessor tube_processor = TubeProcessor::None;
|
||||
|
||||
BBCMicroTarget() : Analyser::Static::Target(Machine::BBCMicro) {}
|
||||
|
||||
private:
|
||||
friend Reflection::StructImpl<BBCMicroTarget>;
|
||||
void declare_fields() {
|
||||
DeclareField(has_1770dfs);
|
||||
DeclareField(has_adfs);
|
||||
DeclareField(has_sideways_ram);
|
||||
DeclareField(has_beebsid);
|
||||
AnnounceEnum(TubeProcessor);
|
||||
DeclareField(tube_processor);
|
||||
}
|
||||
};
|
||||
|
||||
struct ArchimedesTarget: public ::Analyser::Static::Target, public Reflection::StructImpl<ArchimedesTarget> {
|
||||
std::string main_program;
|
||||
|
||||
|
||||
@@ -16,9 +16,18 @@
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
#include <unordered_set>
|
||||
|
||||
namespace {
|
||||
|
||||
std::string rtrimmed(const std::string &input) {
|
||||
auto trimmed = input;
|
||||
trimmed.erase(std::find_if(trimmed.rbegin(), trimmed.rend(), [](const char ch) {
|
||||
return !std::isspace(ch);
|
||||
}).base(), trimmed.end());
|
||||
return trimmed;
|
||||
}
|
||||
|
||||
bool strcmp_insensitive(const char *a, const char *b) {
|
||||
if(std::strlen(a) != std::strlen(b)) return false;
|
||||
while(*a) {
|
||||
@@ -104,58 +113,85 @@ void InspectCatalogue(
|
||||
return;
|
||||
}
|
||||
|
||||
// If only one file is [potentially] BASIC, run that one; otherwise if only one has a suffix
|
||||
// that AMSDOS allows to be omitted, pick that one.
|
||||
int basic_files = 0;
|
||||
int implicit_suffixed_files = 0;
|
||||
const auto run_name = [&]() -> std::optional<std::string> {
|
||||
// Collect:
|
||||
//
|
||||
// 1. a set of all files that can be run without specifying an extension plus their appearance counts;
|
||||
// 2. a set of all BASIC file names.
|
||||
std::unordered_map<std::string, int> candidates;
|
||||
std::unordered_set<std::string> basic_names;
|
||||
for(std::size_t c = 0; c < candidate_files.size(); c++) {
|
||||
// Files with nothing but spaces in their name can't be loaded by the user, so disregard them.
|
||||
if(
|
||||
(candidate_files[c]->type == " " && candidate_files[c]->name == " ") ||
|
||||
!is_implied_extension(candidate_files[c]->type)
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
std::size_t last_basic_file = 0;
|
||||
std::size_t last_implicit_suffixed_file = 0;
|
||||
|
||||
for(std::size_t c = 0; c < candidate_files.size(); c++) {
|
||||
// Files with nothing but spaces in their name can't be loaded by the user, so disregard them.
|
||||
if(candidate_files[c]->type == " " && candidate_files[c]->name == " ")
|
||||
continue;
|
||||
|
||||
// Check for whether this is [potentially] BASIC.
|
||||
if(candidate_files[c]->data.size() >= 128 && !((candidate_files[c]->data[18] >> 1) & 7)) {
|
||||
basic_files++;
|
||||
last_basic_file = c;
|
||||
}
|
||||
|
||||
// Check suffix for emptiness.
|
||||
if(is_implied_extension(candidate_files[c]->type)) {
|
||||
implicit_suffixed_files++;
|
||||
last_implicit_suffixed_file = c;
|
||||
}
|
||||
}
|
||||
if(basic_files == 1 || implicit_suffixed_files == 1) {
|
||||
std::size_t selected_file = (basic_files == 1) ? last_basic_file : last_implicit_suffixed_file;
|
||||
target->loading_command = RunCommandFor(*candidate_files[selected_file]);
|
||||
return;
|
||||
}
|
||||
|
||||
// One more guess: if only one remaining candidate file has a different name than the others,
|
||||
// assume it is intended to stand out.
|
||||
std::map<std::string, int> name_counts;
|
||||
std::map<std::string, std::size_t> indices_by_name;
|
||||
std::size_t index = 0;
|
||||
for(const auto &file : candidate_files) {
|
||||
name_counts[file->name]++;
|
||||
indices_by_name[file->name] = index;
|
||||
index++;
|
||||
}
|
||||
if(name_counts.size() == 2) {
|
||||
for(const auto &pair : name_counts) {
|
||||
if(pair.second == 1) {
|
||||
target->loading_command = RunCommandFor(*candidate_files[indices_by_name[pair.first]]);
|
||||
return;
|
||||
++candidates[candidate_files[c]->name];
|
||||
if(candidate_files[c]->data.size() >= 128 && !((candidate_files[c]->data[18] >> 1) & 7)) {
|
||||
basic_names.insert(candidate_files[c]->name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Desperation.
|
||||
target->loading_command = "cat\n";
|
||||
// Only one candidate total.
|
||||
if(candidates.size() == 1) {
|
||||
return candidates.begin()->first;
|
||||
}
|
||||
|
||||
// Only one BASIC candidate.
|
||||
if(basic_names.size() == 1) {
|
||||
return *basic_names.begin();
|
||||
}
|
||||
|
||||
// Exactly two candidate names, but only one is a unique name.
|
||||
if(candidates.size() == 2) {
|
||||
const auto item1 = candidates.begin();
|
||||
const auto item2 = std::next(item1);
|
||||
|
||||
if(item1->second == 1 && item2->second != 1) {
|
||||
return item1->first;
|
||||
}
|
||||
if(item2->second == 1 && item1->second != 1) {
|
||||
return item2->first;
|
||||
}
|
||||
}
|
||||
|
||||
// Remove from candidates anything that is just a suffixed version of
|
||||
// another name, as long as the other name is three or more characters.
|
||||
std::vector<std::string> to_remove;
|
||||
for(const auto &lhs: candidates) {
|
||||
const auto trimmed = rtrimmed(lhs.first);
|
||||
if(trimmed.size() < 3) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for(const auto &rhs: candidates) {
|
||||
if(lhs.first == rhs.first) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if(rhs.first.find(trimmed) == 0) {
|
||||
to_remove.push_back(rhs.first);
|
||||
}
|
||||
}
|
||||
}
|
||||
for(const auto &candidate: to_remove) {
|
||||
candidates.erase(candidate);
|
||||
}
|
||||
if(candidates.size() == 1) {
|
||||
return candidates.begin()->first;
|
||||
}
|
||||
|
||||
return {};
|
||||
} ();
|
||||
|
||||
if(run_name.has_value()) {
|
||||
target->loading_command = "run\"" + rtrimmed(*run_name) + "\n";
|
||||
} else {
|
||||
target->loading_command = "cat\n";
|
||||
}
|
||||
}
|
||||
|
||||
bool CheckBootSector(
|
||||
|
||||
@@ -258,7 +258,7 @@ analyse_starting_address(uint16_t starting_address) {
|
||||
case 0x1c01:
|
||||
// TODO: assume C128.
|
||||
default:
|
||||
Log::Logger<Log::Source::CommodoreStaticAnalyser>().error().append(
|
||||
Log::Logger<Log::Source::CommodoreStaticAnalyser>::error().append(
|
||||
"Unrecognised loading address for Commodore program: %04x", starting_address);
|
||||
[[fallthrough]];
|
||||
case 0x1001:
|
||||
|
||||
@@ -9,6 +9,8 @@
|
||||
#include "Tape.hpp"
|
||||
#include "Storage/Tape/Parsers/Commodore.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
using namespace Analyser::Static::Commodore;
|
||||
|
||||
std::vector<File> Analyser::Static::Commodore::GetFiles(Storage::Tape::TapeSerialiser &serialiser, TargetPlatform::Type type) {
|
||||
@@ -37,7 +39,7 @@ std::vector<File> Analyser::Static::Commodore::GetFiles(Storage::Tape::TapeSeria
|
||||
header = parser.get_next_header(serialiser);
|
||||
if(!header) continue;
|
||||
if(header->type != Storage::Tape::Commodore::Header::DataBlock) break;
|
||||
std::copy(header->data.begin(), header->data.end(), std::back_inserter(new_file.data));
|
||||
std::ranges::copy(header->data, std::back_inserter(new_file.data));
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -60,11 +60,11 @@ private:
|
||||
bool overrun_ = false;
|
||||
};
|
||||
|
||||
constexpr uint8_t x(uint8_t v) { return v >> 6; }
|
||||
constexpr uint8_t y(uint8_t v) { return (v >> 3) & 7; }
|
||||
constexpr uint8_t q(uint8_t v) { return (v >> 3) & 1; }
|
||||
constexpr uint8_t p(uint8_t v) { return (v >> 4) & 3; }
|
||||
constexpr uint8_t z(uint8_t v) { return v & 7; }
|
||||
constexpr uint8_t x(const uint8_t v) { return v >> 6; }
|
||||
constexpr uint8_t y(const uint8_t v) { return (v >> 3) & 7; }
|
||||
constexpr uint8_t q(const uint8_t v) { return (v >> 3) & 1; }
|
||||
constexpr uint8_t p(const uint8_t v) { return (v >> 4) & 3; }
|
||||
constexpr uint8_t z(const uint8_t v) { return v & 7; }
|
||||
|
||||
Instruction::Condition condition_table[] = {
|
||||
Instruction::Condition::NZ, Instruction::Condition::Z,
|
||||
@@ -92,7 +92,7 @@ Instruction::Location RegisterTableEntry(
|
||||
Instruction &instruction,
|
||||
const bool needs_indirect_offset
|
||||
) {
|
||||
constexpr Instruction::Location register_table[] = {
|
||||
static constexpr Instruction::Location register_table[] = {
|
||||
Instruction::Location::B, Instruction::Location::C,
|
||||
Instruction::Location::D, Instruction::Location::E,
|
||||
Instruction::Location::H, Instruction::Location::L,
|
||||
|
||||
@@ -19,6 +19,18 @@ ReflectableEnum(Model,
|
||||
AT
|
||||
);
|
||||
|
||||
constexpr bool is_xt(const Model model) {
|
||||
return model <= Model::TurboXT;
|
||||
}
|
||||
|
||||
constexpr bool is_at(const Model model) {
|
||||
return model >= Model::AT;
|
||||
}
|
||||
|
||||
constexpr bool has_ide(const Model model) {
|
||||
return model >= Model::AT;
|
||||
}
|
||||
|
||||
struct Target: public Analyser::Static::Target, public Reflection::StructImpl<Target> {
|
||||
ReflectableEnum(VideoAdaptor,
|
||||
MDA,
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#include "StaticAnalyser.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <bit>
|
||||
#include <cstddef>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
@@ -52,6 +53,7 @@
|
||||
#include "Storage/Disk/DiskImage/Formats/HFE.hpp"
|
||||
#include "Storage/Disk/DiskImage/Formats/IPF.hpp"
|
||||
#include "Storage/Disk/DiskImage/Formats/IMD.hpp"
|
||||
#include "Storage/Disk/DiskImage/Formats/JFD.hpp"
|
||||
#include "Storage/Disk/DiskImage/Formats/MacintoshIMG.hpp"
|
||||
#include "Storage/Disk/DiskImage/Formats/MSA.hpp"
|
||||
#include "Storage/Disk/DiskImage/Formats/NIB.hpp"
|
||||
@@ -66,6 +68,7 @@
|
||||
#include "Storage/MassStorage/Formats/DSK.hpp"
|
||||
#include "Storage/MassStorage/Formats/HDV.hpp"
|
||||
#include "Storage/MassStorage/Formats/HFV.hpp"
|
||||
#include "Storage/MassStorage/Formats/VHD.hpp"
|
||||
|
||||
// State Snapshots
|
||||
#include "Storage/State/SNA.hpp"
|
||||
@@ -205,6 +208,7 @@ static Media GetMediaAndPlatforms(const std::string &file_name, TargetPlatform::
|
||||
accumulator.try_standard<Disk::DiskImageHolder<Disk::AcornADF>>(TargetPlatform::Acorn, "adf");
|
||||
accumulator.try_standard<Disk::DiskImageHolder<Disk::AmigaADF>>(TargetPlatform::Amiga, "adf");
|
||||
accumulator.try_standard<Disk::DiskImageHolder<Disk::AcornADF>>(TargetPlatform::Acorn, "adl");
|
||||
accumulator.try_standard<Disk::DiskImageHolder<Disk::JFD>>(TargetPlatform::Archimedes, "jfd");
|
||||
|
||||
accumulator.try_standard<Cartridge::BinaryDump>(TargetPlatform::AllCartridge, "bin");
|
||||
|
||||
@@ -299,6 +303,8 @@ static Media GetMediaAndPlatforms(const std::string &file_name, TargetPlatform::
|
||||
|
||||
accumulator.try_standard<Tape::UEF>(TargetPlatform::Acorn, "uef");
|
||||
|
||||
accumulator.try_standard<MassStorage::VHD>(TargetPlatform::PCCompatible, "vhd");
|
||||
|
||||
accumulator.try_standard<Disk::DiskImageHolder<Disk::WOZ>>(TargetPlatform::DiskII, "woz");
|
||||
|
||||
return accumulator.media;
|
||||
@@ -340,13 +346,7 @@ TargetList Analyser::Static::GetTargets(const std::string &file_name) {
|
||||
TargetPlatform::IntType potential_platforms = 0;
|
||||
Media media = GetMediaAndPlatforms(file_name, potential_platforms);
|
||||
|
||||
// TODO: std::popcount here.
|
||||
int total_options = 0;
|
||||
TargetPlatform::IntType mask = 1;
|
||||
while(mask) {
|
||||
total_options += bool(potential_platforms & mask);
|
||||
mask <<= 1;
|
||||
}
|
||||
int total_options = std::popcount(potential_platforms);
|
||||
const bool is_confident = total_options == 1;
|
||||
// i.e. This analyser `is_confident` if file analysis suggested only one potential target platform.
|
||||
// The machine-specific static analyser will still run in case it can provide meaningful annotations on
|
||||
|
||||
+1
-1
@@ -5,7 +5,7 @@ project(CLK
|
||||
VERSION 24.01.22
|
||||
)
|
||||
|
||||
set(CMAKE_CXX_STANDARD 17)
|
||||
set(CMAKE_CXX_STANDARD 20)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
set(CMAKE_CXX_EXTENSIONS OFF)
|
||||
|
||||
|
||||
@@ -128,12 +128,7 @@ public:
|
||||
|
||||
forceinline constexpr T operator -() const { return T(- length_); }
|
||||
|
||||
forceinline constexpr bool operator <(const T &rhs) const { return length_ < rhs.length_; }
|
||||
forceinline constexpr bool operator >(const T &rhs) const { return length_ > rhs.length_; }
|
||||
forceinline constexpr bool operator <=(const T &rhs) const { return length_ <= rhs.length_; }
|
||||
forceinline constexpr bool operator >=(const T &rhs) const { return length_ >= rhs.length_; }
|
||||
forceinline constexpr bool operator ==(const T &rhs) const { return length_ == rhs.length_; }
|
||||
forceinline constexpr bool operator !=(const T &rhs) const { return length_ != rhs.length_; }
|
||||
auto operator <=>(const WrappedInt &) const = default;
|
||||
|
||||
forceinline constexpr bool operator !() const { return !length_; }
|
||||
// bool operator () is not supported because it offers an implicit cast to int,
|
||||
|
||||
@@ -16,10 +16,10 @@ 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);
|
||||
static constexpr int elements_per_uint32 = sizeof(uint32_t) / sizeof(ValueT);
|
||||
static constexpr int unit_shift = sizeof(ValueT) * 8;
|
||||
static constexpr int insert_shift = (DeferredDepth & (elements_per_uint32 - 1)) * unit_shift;
|
||||
static constexpr uint32_t insert_mask = ~(0xffff'ffff << insert_shift);
|
||||
|
||||
std::array<uint32_t, (DeferredDepth + elements_per_uint32 - 1) / elements_per_uint32> backlog;
|
||||
|
||||
|
||||
@@ -325,7 +325,7 @@ public:
|
||||
/// Flushes all accumulated time.
|
||||
inline void flush() {
|
||||
if(!is_flushed_) {
|
||||
task_queue_.flush();
|
||||
task_queue_.lock_flush();
|
||||
object_.run_for(time_since_update_.template flush<TargetTimeScale>());
|
||||
is_flushed_ = true;
|
||||
}
|
||||
|
||||
+79
-59
@@ -8,11 +8,11 @@
|
||||
|
||||
#include "1770.hpp"
|
||||
|
||||
#include "Storage/Disk/Encodings/MFM/Constants.hpp"
|
||||
#include "Outputs/Log.hpp"
|
||||
#include "Storage/Disk/Encodings/MFM/Constants.hpp"
|
||||
|
||||
namespace {
|
||||
Log::Logger<Log::Source::WDFDC> logger;
|
||||
using Logger = Log::Logger<Log::Source::WDFDC>;
|
||||
}
|
||||
|
||||
using namespace WD;
|
||||
@@ -31,10 +31,10 @@ void WD1770::write(const int address, const uint8_t value) {
|
||||
if((value&0xf0) == 0xd0) {
|
||||
if(value == 0xd0) {
|
||||
// Force interrupt **immediately**.
|
||||
logger.info().append("Force interrupt immediately");
|
||||
Logger::info().append("Force interrupt immediately");
|
||||
posit_event(int(Event1770::ForceInterrupt));
|
||||
} else {
|
||||
logger.error().append("TODO: force interrupt");
|
||||
Logger::error().append("TODO: force interrupt");
|
||||
update_status([] (Status &status) {
|
||||
status.type = Status::One;
|
||||
});
|
||||
@@ -101,19 +101,20 @@ uint8_t WD1770::read(const int address) {
|
||||
if(status_.type == Status::One)
|
||||
status |= (status_.spin_up ? Flag::SpinUp : 0);
|
||||
}
|
||||
// logger.info().append("Returned status %02x of type %d", status, 1+int(status_.type));
|
||||
// Logger::info().append("Returned status %02x of type %d", status, 1+int(status_.type));
|
||||
return status;
|
||||
}
|
||||
case 1:
|
||||
logger.info().append("Returned track %d", track_);
|
||||
Logger::info().append("Returned track %d", track_);
|
||||
return track_;
|
||||
case 2:
|
||||
logger.info().append("Returned sector %d", sector_);
|
||||
Logger::info().append("Returned sector %d", sector_);
|
||||
return sector_;
|
||||
case 3:
|
||||
update_status([] (Status &status) {
|
||||
status.data_request = false;
|
||||
});
|
||||
// Logger::info().append("Returned data %02x; [drq:%d]", data_, status_.data_request);
|
||||
return data_;
|
||||
}
|
||||
}
|
||||
@@ -132,26 +133,46 @@ void WD1770::run_for(const Cycles cycles) {
|
||||
}
|
||||
}
|
||||
|
||||
#define WAIT_FOR_EVENT(mask) resume_point_ = __LINE__; interesting_event_mask_ = int(mask); return; case __LINE__:
|
||||
#define WAIT_FOR_TIME(ms) resume_point_ = __LINE__; delay_time_ = ms * 8000; WAIT_FOR_EVENT(Event1770::Timer);
|
||||
#define WAIT_FOR_BYTES(count) resume_point_ = __LINE__; distance_into_section_ = 0; WAIT_FOR_EVENT(Event::Token); if(get_latest_token().type == Token::Byte) distance_into_section_++; if(distance_into_section_ < count) { interesting_event_mask_ = int(Event::Token); return; }
|
||||
#define BEGIN_SECTION() switch(resume_point_) { default:
|
||||
#define END_SECTION() (void)0; }
|
||||
#include <iostream>
|
||||
|
||||
#define READ_ID() \
|
||||
if(new_event_type == int(Event::Token)) { \
|
||||
if(!distance_into_section_ && get_latest_token().type == Token::ID) {\
|
||||
set_data_mode(DataMode::Reading); \
|
||||
++distance_into_section_; \
|
||||
} else if(distance_into_section_ && distance_into_section_ < 7 && get_latest_token().type == Token::Byte) { \
|
||||
header_[distance_into_section_ - 1] = get_latest_token().byte_value; \
|
||||
++distance_into_section_; \
|
||||
} \
|
||||
void WD1770::posit_event(const int new_event_type) {
|
||||
#define WAIT_FOR_EVENT(mask) { \
|
||||
interesting_event_mask_ = int(mask); \
|
||||
static constexpr int location = __COUNTER__ + 1; \
|
||||
resume_point_ = location; \
|
||||
return; \
|
||||
case location: \
|
||||
(void)0; \
|
||||
}
|
||||
|
||||
#define WAIT_FOR_TIME(ms) \
|
||||
delay_time_ = ms * 8000; \
|
||||
WAIT_FOR_EVENT(Event1770::Timer);
|
||||
|
||||
#define WAIT_FOR_BYTES(count) \
|
||||
distance_into_section_ = 0; \
|
||||
WAIT_FOR_EVENT(Event::Token); \
|
||||
distance_into_section_ += get_latest_token().type == Token::Byte; \
|
||||
if(distance_into_section_ < count) { \
|
||||
RESUME_WAIT(Event::Token); \
|
||||
}
|
||||
|
||||
#define RESUME_WAIT(mask) interesting_event_mask_ = int(mask); return;
|
||||
|
||||
#define BEGIN_SECTION() switch(resume_point_) { default:
|
||||
#define END_SECTION() (void)0; }
|
||||
|
||||
const auto READ_ID = [&] {
|
||||
if(new_event_type == int(Event::Token)) {
|
||||
if(!distance_into_section_ && get_latest_token().type == Token::ID) {
|
||||
set_data_mode(DataMode::Reading);
|
||||
++distance_into_section_;
|
||||
} else if(distance_into_section_ && distance_into_section_ < 7 && get_latest_token().type == Token::Byte) {
|
||||
header_[distance_into_section_ - 1] = get_latest_token().byte_value;
|
||||
++distance_into_section_;
|
||||
}
|
||||
}
|
||||
|
||||
#define CONCATENATE(x, y) x ## y
|
||||
#define INDIRECT_CONCATENATE(x, y) TOKENPASTE(x, y)
|
||||
#define LINE_LABEL INDIRECT_CONCATENATE(label, __LINE__)
|
||||
};
|
||||
|
||||
#define SPIN_UP() \
|
||||
set_motor_on(true); \
|
||||
@@ -160,24 +181,6 @@ void WD1770::run_for(const Cycles cycles) {
|
||||
WAIT_FOR_EVENT(Event1770::IndexHoleTarget); \
|
||||
status_.spin_up = true;
|
||||
|
||||
// +--------+----------+-------------------------+
|
||||
// ! ! ! BITS !
|
||||
// ! TYPE ! COMMAND ! 7 6 5 4 3 2 1 0 !
|
||||
// +--------+----------+-------------------------+
|
||||
// ! 1 ! Restore ! 0 0 0 0 h v r1 r0 !
|
||||
// ! 1 ! Seek ! 0 0 0 1 h v r1 r0 !
|
||||
// ! 1 ! Step ! 0 0 1 u h v r1 r0 !
|
||||
// ! 1 ! Step-in ! 0 1 0 u h v r1 r0 !
|
||||
// ! 1 ! Step-out ! 0 1 1 u h v r1 r0 !
|
||||
// ! 2 ! Rd sectr ! 1 0 0 m h E 0 0 !
|
||||
// ! 2 ! Wt sectr ! 1 0 1 m h E P a0 !
|
||||
// ! 3 ! Rd addr ! 1 1 0 0 h E 0 0 !
|
||||
// ! 3 ! Rd track ! 1 1 1 0 h E 0 0 !
|
||||
// ! 3 ! Wt track ! 1 1 1 1 h E P 0 !
|
||||
// ! 4 ! Forc int ! 1 1 0 1 i3 i2 i1 i0 !
|
||||
// +--------+----------+-------------------------+
|
||||
|
||||
void WD1770::posit_event(const int new_event_type) {
|
||||
if(new_event_type == int(Event::IndexHole)) {
|
||||
index_hole_count_++;
|
||||
if(index_hole_count_target_ == index_hole_count_) {
|
||||
@@ -198,7 +201,7 @@ void WD1770::posit_event(const int new_event_type) {
|
||||
|
||||
if(new_event_type == int(Event1770::ForceInterrupt)) {
|
||||
interesting_event_mask_ = 0;
|
||||
resume_point_ = 0;
|
||||
resume_point_ = IdleResumePoint;
|
||||
update_status([] (Status &status) {
|
||||
status.type = Status::One;
|
||||
status.data_request = false;
|
||||
@@ -209,12 +212,29 @@ void WD1770::posit_event(const int new_event_type) {
|
||||
interesting_event_mask_ &= ~new_event_type;
|
||||
}
|
||||
|
||||
// +--------+----------+-------------------------+
|
||||
// ! ! ! BITS !
|
||||
// ! TYPE ! COMMAND ! 7 6 5 4 3 2 1 0 !
|
||||
// +--------+----------+-------------------------+
|
||||
// ! 1 ! Restore ! 0 0 0 0 h v r1 r0 !
|
||||
// ! 1 ! Seek ! 0 0 0 1 h v r1 r0 !
|
||||
// ! 1 ! Step ! 0 0 1 u h v r1 r0 !
|
||||
// ! 1 ! Step-in ! 0 1 0 u h v r1 r0 !
|
||||
// ! 1 ! Step-out ! 0 1 1 u h v r1 r0 !
|
||||
// ! 2 ! Rd sectr ! 1 0 0 m h E 0 0 !
|
||||
// ! 2 ! Wt sectr ! 1 0 1 m h E P a0 !
|
||||
// ! 3 ! Rd addr ! 1 1 0 0 h E 0 0 !
|
||||
// ! 3 ! Rd track ! 1 1 1 0 h E 0 0 !
|
||||
// ! 3 ! Wt track ! 1 1 1 1 h E P 0 !
|
||||
// ! 4 ! Forc int ! 1 1 0 1 i3 i2 i1 i0 !
|
||||
// +--------+----------+-------------------------+
|
||||
|
||||
BEGIN_SECTION()
|
||||
|
||||
// Wait for a new command, branch to the appropriate handler.
|
||||
case 0:
|
||||
case IdleResumePoint:
|
||||
wait_for_command:
|
||||
logger.info().append("Idle...");
|
||||
Logger::info().append("Idle...");
|
||||
set_data_mode(DataMode::Scanning);
|
||||
index_hole_count_ = 0;
|
||||
|
||||
@@ -231,7 +251,7 @@ void WD1770::posit_event(const int new_event_type) {
|
||||
status.track_zero = false; // Always reset by a non-type 1; so reset regardless and set properly later.
|
||||
});
|
||||
|
||||
logger.info().append("Starting %02x", command_);
|
||||
Logger::info().append("Starting %02x", command_);
|
||||
|
||||
if(!(command_ & 0x80)) goto begin_type_1;
|
||||
if(!(command_ & 0x40)) goto begin_type_2;
|
||||
@@ -261,7 +281,7 @@ void WD1770::posit_event(const int new_event_type) {
|
||||
status.data_request = false;
|
||||
});
|
||||
|
||||
logger.info().append("Step/Seek/Restore with track %d data %d", track_, data_);
|
||||
Logger::info().append("Step/Seek/Restore with track %d data %d", track_, data_);
|
||||
if(!has_motor_on_line() && !has_head_load_line()) goto test_type1_type;
|
||||
|
||||
if(has_motor_on_line()) goto begin_type1_spin_up;
|
||||
@@ -341,7 +361,7 @@ void WD1770::posit_event(const int new_event_type) {
|
||||
READ_ID();
|
||||
|
||||
if(index_hole_count_ == 6) {
|
||||
logger.info().append("Nothing found to verify");
|
||||
Logger::info().append("Nothing found to verify");
|
||||
update_status([] (Status &status) {
|
||||
status.seek_error = true;
|
||||
});
|
||||
@@ -359,7 +379,7 @@ void WD1770::posit_event(const int new_event_type) {
|
||||
}
|
||||
|
||||
if(header_[0] == track_) {
|
||||
logger.info().append("Reached track %d", track_);
|
||||
Logger::info().append("Reached track %d", track_);
|
||||
update_status([] (Status &status) {
|
||||
status.crc_error = false;
|
||||
});
|
||||
@@ -432,7 +452,7 @@ void WD1770::posit_event(const int new_event_type) {
|
||||
READ_ID();
|
||||
|
||||
if(index_hole_count_ == 5) {
|
||||
logger.info().append("Failed to find sector %d", sector_);
|
||||
Logger::info().append("Failed to find sector %d", sector_);
|
||||
update_status([] (Status &status) {
|
||||
status.record_not_found = true;
|
||||
});
|
||||
@@ -442,12 +462,12 @@ void WD1770::posit_event(const int new_event_type) {
|
||||
distance_into_section_ = 0;
|
||||
set_data_mode(DataMode::Scanning);
|
||||
|
||||
logger.info().append("Considering %d/%d", header_[0], header_[2]);
|
||||
Logger::info().append("Considering %d/%d", header_[0], header_[2]);
|
||||
if( header_[0] == track_ && header_[2] == sector_ &&
|
||||
(has_motor_on_line() || !(command_&0x02) || ((command_&0x08) >> 3) == header_[1])) {
|
||||
logger.info().append("Found %d/%d", header_[0], header_[2]);
|
||||
Logger::info().append("Found %d/%d", header_[0], header_[2]);
|
||||
if(get_crc_generator().get_value()) {
|
||||
logger.info().append("CRC error; back to searching");
|
||||
Logger::info().append("CRC error; back to searching");
|
||||
update_status([] (Status &status) {
|
||||
status.crc_error = true;
|
||||
});
|
||||
@@ -505,18 +525,18 @@ void WD1770::posit_event(const int new_event_type) {
|
||||
set_data_mode(DataMode::Scanning);
|
||||
|
||||
if(get_crc_generator().get_value()) {
|
||||
logger.info().append("CRC error; terminating");
|
||||
Logger::info().append("CRC error; terminating");
|
||||
update_status([] (Status &status) {
|
||||
status.crc_error = true;
|
||||
});
|
||||
goto wait_for_command;
|
||||
}
|
||||
|
||||
logger.info().append("Finished reading sector %d", sector_);
|
||||
Logger::info().append("Finished reading sector %d", sector_);
|
||||
|
||||
if(command_ & 0x10) {
|
||||
sector_++;
|
||||
logger.info().append("Advancing to search for sector %d", sector_);
|
||||
Logger::info().append("Advancing to search for sector %d", sector_);
|
||||
goto test_type2_write_protection;
|
||||
}
|
||||
goto wait_for_command;
|
||||
@@ -600,7 +620,7 @@ void WD1770::posit_event(const int new_event_type) {
|
||||
sector_++;
|
||||
goto test_type2_write_protection;
|
||||
}
|
||||
logger.info().append("Wrote sector %d", sector_);
|
||||
Logger::info().append("Wrote sector %d", sector_);
|
||||
goto wait_for_command;
|
||||
|
||||
|
||||
@@ -818,7 +838,7 @@ void WD1770::update_status(const std::function<void(Status &)> updater) {
|
||||
(status_.busy != old_status.busy) ||
|
||||
(status_.data_request != old_status.data_request) ||
|
||||
(status_.interrupt_request != old_status.interrupt_request);
|
||||
if(did_change) delegate_->wd1770_did_change_output(this);
|
||||
if(did_change) delegate_->wd1770_did_change_output(*this);
|
||||
} else updater(status_);
|
||||
|
||||
if(status_.busy != old_status.busy) update_clocking_observer();
|
||||
|
||||
@@ -66,9 +66,8 @@ public:
|
||||
/// @returns The current value of the DRQ line output.
|
||||
inline bool get_data_request_line() const { return status_.data_request; }
|
||||
|
||||
class Delegate {
|
||||
public:
|
||||
virtual void wd1770_did_change_output(WD1770 *wd1770) = 0;
|
||||
struct Delegate {
|
||||
virtual void wd1770_did_change_output(WD1770 &) = 0;
|
||||
};
|
||||
inline void set_delegate(Delegate *delegate) { delegate_ = delegate; }
|
||||
|
||||
@@ -125,9 +124,12 @@ private:
|
||||
};
|
||||
void posit_event(int type);
|
||||
int interesting_event_mask_;
|
||||
int resume_point_ = 0;
|
||||
Cycles::IntType delay_time_ = 0;
|
||||
|
||||
// Current state machine stap pointer.
|
||||
static constexpr int IdleResumePoint = 0;
|
||||
int resume_point_ = IdleResumePoint;
|
||||
|
||||
// ID buffer
|
||||
uint8_t header_[6];
|
||||
|
||||
|
||||
+24
-24
@@ -11,7 +11,7 @@
|
||||
#include "Outputs/Log.hpp"
|
||||
|
||||
namespace {
|
||||
Log::Logger<Log::Source::NCR5380> logger;
|
||||
using Logger = Log::Logger<Log::Source::NCR5380>;
|
||||
}
|
||||
// TODO:
|
||||
//
|
||||
@@ -25,7 +25,7 @@ NCR5380::NCR5380(SCSI::Bus &bus, const int clock_rate) :
|
||||
bus_(bus),
|
||||
clock_rate_(clock_rate) {
|
||||
device_id_ = bus_.add_device();
|
||||
bus_.add_observer(this);
|
||||
bus_.add_observer(*this);
|
||||
|
||||
// TODO: use clock rate and expected phase. This implementation currently
|
||||
// provides only CPU-driven polling behaviour.
|
||||
@@ -36,7 +36,7 @@ NCR5380::NCR5380(SCSI::Bus &bus, const int clock_rate) :
|
||||
void NCR5380::write(const int address, const uint8_t value, bool) {
|
||||
switch(address & 7) {
|
||||
case 0:
|
||||
logger.info().append("[0] Set current SCSI bus state to %02x", value);
|
||||
Logger::info().append("[0] Set current SCSI bus state to %02x", value);
|
||||
|
||||
data_bus_ = value;
|
||||
if(dma_request_ && dma_operation_ == DMAOperation::Send) {
|
||||
@@ -45,7 +45,7 @@ void NCR5380::write(const int address, const uint8_t value, bool) {
|
||||
break;
|
||||
|
||||
case 1: {
|
||||
logger.info().append("[1] Initiator command register set: %02x", value);
|
||||
Logger::info().append("[1] Initiator command register set: %02x", value);
|
||||
initiator_command_ = value;
|
||||
|
||||
bus_output_ &= ~(Line::Reset | Line::Acknowledge | Line::Busy | Line::SelectTarget | Line::Attention);
|
||||
@@ -61,7 +61,7 @@ void NCR5380::write(const int address, const uint8_t value, bool) {
|
||||
} break;
|
||||
|
||||
case 2:
|
||||
logger.info().append("[2] Set mode: %02x", value);
|
||||
Logger::info().append("[2] Set mode: %02x", value);
|
||||
mode_ = value;
|
||||
|
||||
// bit 7: 1 = use block mode DMA mode (if DMA mode is also enabled)
|
||||
@@ -102,27 +102,27 @@ void NCR5380::write(const int address, const uint8_t value, bool) {
|
||||
break;
|
||||
|
||||
case 3:
|
||||
logger.info().append("[3] Set target command: %02x", value);
|
||||
Logger::info().append("[3] Set target command: %02x", value);
|
||||
target_command_ = value;
|
||||
update_control_output();
|
||||
break;
|
||||
|
||||
case 4:
|
||||
logger.info().append("[4] Set select enabled: %02x", value);
|
||||
Logger::info().append("[4] Set select enabled: %02x", value);
|
||||
break;
|
||||
|
||||
case 5:
|
||||
logger.info().append("[5] Start DMA send: %02x", value);
|
||||
Logger::info().append("[5] Start DMA send: %02x", value);
|
||||
dma_operation_ = DMAOperation::Send;
|
||||
break;
|
||||
|
||||
case 6:
|
||||
logger.info().append("[6] Start DMA target receive: %02x", value);
|
||||
Logger::info().append("[6] Start DMA target receive: %02x", value);
|
||||
dma_operation_ = DMAOperation::TargetReceive;
|
||||
break;
|
||||
|
||||
case 7:
|
||||
logger.info().append("[7] Start DMA initiator receive: %02x", value);
|
||||
Logger::info().append("[7] Start DMA initiator receive: %02x", value);
|
||||
dma_operation_ = DMAOperation::InitiatorReceive;
|
||||
break;
|
||||
}
|
||||
@@ -146,15 +146,15 @@ void NCR5380::write(const int address, const uint8_t value, bool) {
|
||||
uint8_t NCR5380::read(const int address, bool) {
|
||||
switch(address & 7) {
|
||||
case 0:
|
||||
logger.info().append("[0] Get current SCSI bus state: %02x", (bus_.get_state() & 0xff));
|
||||
Logger::info().append("[0] Get current SCSI bus state: %02x", (bus_.state() & 0xff));
|
||||
|
||||
if(dma_request_ && dma_operation_ == DMAOperation::InitiatorReceive) {
|
||||
return dma_acknowledge();
|
||||
}
|
||||
return uint8_t(bus_.get_state());
|
||||
return uint8_t(bus_.state());
|
||||
|
||||
case 1:
|
||||
logger.info().append(
|
||||
Logger::info().append(
|
||||
"[1] Initiator command register get: %c%c",
|
||||
arbitration_in_progress_ ? 'p' : '-',
|
||||
lost_arbitration_ ? 'l' : '-');
|
||||
@@ -169,15 +169,15 @@ uint8_t NCR5380::read(const int address, bool) {
|
||||
(lost_arbitration_ ? 0x20 : 0x00);
|
||||
|
||||
case 2:
|
||||
logger.info().append("[2] Get mode");
|
||||
Logger::info().append("[2] Get mode");
|
||||
return mode_;
|
||||
|
||||
case 3:
|
||||
logger.info().append("[3] Get target command");
|
||||
Logger::info().append("[3] Get target command");
|
||||
return target_command_;
|
||||
|
||||
case 4: {
|
||||
const auto bus_state = bus_.get_state();
|
||||
const auto bus_state = bus_.state();
|
||||
const uint8_t result =
|
||||
((bus_state & Line::Reset) ? 0x80 : 0x00) |
|
||||
((bus_state & Line::Busy) ? 0x40 : 0x00) |
|
||||
@@ -187,12 +187,12 @@ uint8_t NCR5380::read(const int address, bool) {
|
||||
((bus_state & Line::Input) ? 0x04 : 0x00) |
|
||||
((bus_state & Line::SelectTarget) ? 0x02 : 0x00) |
|
||||
((bus_state & Line::Parity) ? 0x01 : 0x00);
|
||||
logger.info().append("[4] Get current bus state: %02x", result);
|
||||
Logger::info().append("[4] Get current bus state: %02x", result);
|
||||
return result;
|
||||
}
|
||||
|
||||
case 5: {
|
||||
const auto bus_state = bus_.get_state();
|
||||
const auto bus_state = bus_.state();
|
||||
const uint8_t result =
|
||||
(end_of_dma_ ? 0x80 : 0x00) |
|
||||
((dma_request_ && state_ == ExecutionState::PerformingDMA) ? 0x40 : 0x00) |
|
||||
@@ -202,16 +202,16 @@ uint8_t NCR5380::read(const int address, bool) {
|
||||
/* b2 = busy error */
|
||||
((bus_state & Line::Attention) ? 0x02 : 0x00) |
|
||||
((bus_state & Line::Acknowledge) ? 0x01 : 0x00);
|
||||
logger.info().append("[5] Get bus and status: %02x", result);
|
||||
Logger::info().append("[5] Get bus and status: %02x", result);
|
||||
return result;
|
||||
}
|
||||
|
||||
case 6:
|
||||
logger.info().append("[6] Get input data");
|
||||
Logger::info().append("[6] Get input data");
|
||||
return 0xff;
|
||||
|
||||
case 7:
|
||||
logger.info().append("[7] Reset parity/interrupt");
|
||||
Logger::info().append("[7] Reset parity/interrupt");
|
||||
irq_ = false;
|
||||
return 0xff;
|
||||
}
|
||||
@@ -242,7 +242,7 @@ void NCR5380::update_control_output() {
|
||||
}
|
||||
}
|
||||
|
||||
void NCR5380::scsi_bus_did_change(SCSI::Bus *, const SCSI::BusState new_state, const double time_since_change) {
|
||||
void NCR5380::scsi_bus_did_change(SCSI::Bus &, const SCSI::BusState new_state, const double time_since_change) {
|
||||
/*
|
||||
When connected as an Initiator with DMA Mode True,
|
||||
if the phase lines I//O, C//D, and /MSG do not match the
|
||||
@@ -350,7 +350,7 @@ bool NCR5380::dma_request() {
|
||||
}
|
||||
|
||||
uint8_t NCR5380::dma_acknowledge() {
|
||||
const uint8_t bus_state = uint8_t(bus_.get_state());
|
||||
const uint8_t bus_state = uint8_t(bus_.state());
|
||||
|
||||
dma_acknowledge_ = true;
|
||||
dma_request_ = false;
|
||||
@@ -370,7 +370,7 @@ void NCR5380::dma_acknowledge(const uint8_t value) {
|
||||
}
|
||||
|
||||
bool NCR5380::phase_matches() const {
|
||||
const auto bus_state = bus_.get_state();
|
||||
const auto bus_state = bus_.state();
|
||||
return
|
||||
(target_output() & (Line::Message | Line::Control | Line::Input)) ==
|
||||
(bus_state & (Line::Message | Line::Control | Line::Input));
|
||||
|
||||
@@ -78,7 +78,7 @@ private:
|
||||
SCSI::BusState target_output() const;
|
||||
void update_control_output();
|
||||
|
||||
void scsi_bus_did_change(SCSI::Bus *, SCSI::BusState new_state, double time_since_change) final;
|
||||
void scsi_bus_did_change(SCSI::Bus &, SCSI::BusState, double) final;
|
||||
bool phase_matches() const;
|
||||
};
|
||||
|
||||
|
||||
@@ -60,10 +60,9 @@ public:
|
||||
*/
|
||||
class IRQDelegatePortHandler: public PortHandler {
|
||||
public:
|
||||
class Delegate {
|
||||
public:
|
||||
/// Indicates that the interrupt status has changed for the IRQDelegatePortHandler provided.
|
||||
virtual void mos6522_did_change_interrupt_status(void *irq_delegate) = 0;
|
||||
struct Delegate {
|
||||
/// Indicates that the interrupt status has changed for the IRQDelegatePortHandler provided.
|
||||
virtual void mos6522_did_change_interrupt_status(void *irq_delegate) = 0;
|
||||
};
|
||||
|
||||
/// Sets the delegate that will receive notification of changes in the interrupt line.
|
||||
|
||||
@@ -23,7 +23,7 @@ template <typename T> void MOS6522<T>::access(const int address) {
|
||||
}
|
||||
break;
|
||||
|
||||
case 0xf:
|
||||
// case 0xf:
|
||||
case 0x1:
|
||||
// In both handshake and pulse modes, CA2 goes low on any read or write of Port A.
|
||||
if(handshake_modes_[0] != HandshakeMode::None) {
|
||||
@@ -57,14 +57,17 @@ template <typename T> void MOS6522<T>::write(int address, const uint8_t value) {
|
||||
bus_handler_.run_for(time_since_bus_handler_call_.flush<HalfCycles>());
|
||||
bus_handler_.template set_port_output<Port::A>(value, registers_.data_direction[0]);
|
||||
|
||||
if(handshake_modes_[1] != HandshakeMode::None) {
|
||||
set_control_line_output<Port::A, Line::Two>(LineState::Off);
|
||||
}
|
||||
// Avoid handshaking if this was via address 0xf.
|
||||
if(address == 0x1) {
|
||||
if(handshake_modes_[1] != HandshakeMode::None) {
|
||||
set_control_line_output<Port::A, Line::Two>(LineState::Off);
|
||||
}
|
||||
|
||||
registers_.interrupt_flags &= ~(
|
||||
InterruptFlag::CA1ActiveEdge |
|
||||
((registers_.peripheral_control&0x02) ? 0 : InterruptFlag::CB2ActiveEdge)
|
||||
);
|
||||
registers_.interrupt_flags &= ~(
|
||||
InterruptFlag::CA1ActiveEdge |
|
||||
((registers_.peripheral_control&0x02) ? 0 : InterruptFlag::CB2ActiveEdge)
|
||||
);
|
||||
}
|
||||
reevaluate_interrupts();
|
||||
break;
|
||||
|
||||
@@ -144,10 +147,11 @@ template <typename T> void MOS6522<T>::write(int address, const uint8_t value) {
|
||||
reevaluate_interrupts();
|
||||
break;
|
||||
case 0xe: // Interrupt enable register ('IER').
|
||||
if(value&0x80)
|
||||
if(value&0x80) {
|
||||
registers_.interrupt_enable |= value;
|
||||
else
|
||||
} else {
|
||||
registers_.interrupt_enable &= ~value;
|
||||
}
|
||||
reevaluate_interrupts();
|
||||
break;
|
||||
}
|
||||
@@ -195,9 +199,10 @@ template <typename T> uint8_t MOS6522<T>::read(int address) {
|
||||
registers_.interrupt_flags &= ~(InterruptFlag::CB1ActiveEdge | InterruptFlag::CB2ActiveEdge);
|
||||
reevaluate_interrupts();
|
||||
return get_port_input<Port::B>(registers_.data_direction[1], registers_.output[1], registers_.auxiliary_control & 0x80);
|
||||
case 0xf:
|
||||
case 0x1: // Read Port A ('IRA').
|
||||
case 0x1: // Read Port A ('IRA') [with handshaking].
|
||||
registers_.interrupt_flags &= ~(InterruptFlag::CA1ActiveEdge | InterruptFlag::CA2ActiveEdge);
|
||||
[[fallthrough]];
|
||||
case 0xf: // Read Port A ('IRA') [without handshaking].
|
||||
reevaluate_interrupts();
|
||||
return get_port_input<Port::A>(registers_.data_direction[0], registers_.output[0], 0);
|
||||
|
||||
@@ -245,8 +250,10 @@ uint8_t MOS6522<T>::get_port_input(
|
||||
) {
|
||||
bus_handler_.run_for(time_since_bus_handler_call_.flush<HalfCycles>());
|
||||
const uint8_t input = bus_handler_.template get_port_input<port>();
|
||||
output = (output & ~timer_mask) | (registers_.timer_port_b_output & timer_mask);
|
||||
return (input & ~output_mask) | (output & output_mask);
|
||||
|
||||
// Force any timer-adjusted PB7 to be visible even if the pin is set as input.
|
||||
output = (input & ~output_mask) | (output & output_mask);
|
||||
return (output & ~timer_mask) | (registers_.timer_port_b_output & timer_mask);
|
||||
}
|
||||
|
||||
template <typename T> T &MOS6522<T>::bus_handler() {
|
||||
|
||||
@@ -83,7 +83,7 @@ private:
|
||||
|
||||
bool serial_line_did_produce_bit(Serial::Line<true> *line, int bit) final;
|
||||
|
||||
Log::Logger<Log::Source::MOS6526> log;
|
||||
using Logger = Log::Logger<Log::Source::MOS6526>;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -129,7 +129,7 @@ void MOS6526<BusHandlerT, personality>::write(int address, const uint8_t value)
|
||||
|
||||
// Shift control.
|
||||
case 12:
|
||||
log.error().append("TODO: write to shift register");
|
||||
Logger::error().append("TODO: write to shift register");
|
||||
break;
|
||||
|
||||
// Logically unreachable.
|
||||
|
||||
@@ -125,11 +125,11 @@ private:
|
||||
uint32_t alarm_ = 0xff'ffff;
|
||||
|
||||
public:
|
||||
template <int byte> void write(uint8_t v) {
|
||||
template <int byte> void write(const uint8_t v) {
|
||||
if constexpr (byte == 3) {
|
||||
return;
|
||||
}
|
||||
constexpr int shift = byte << 3;
|
||||
static 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
|
||||
@@ -147,7 +147,7 @@ public:
|
||||
if constexpr (byte == 3) {
|
||||
return 0xff; // Assumed. Just a guess.
|
||||
}
|
||||
constexpr int shift = byte << 3;
|
||||
static constexpr int shift = byte << 3;
|
||||
|
||||
if constexpr (byte == 2) {
|
||||
latch_ = value_ | 0xff00'0000;
|
||||
@@ -163,7 +163,7 @@ public:
|
||||
return result;
|
||||
}
|
||||
|
||||
bool advance(int count) {
|
||||
bool advance(const 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;
|
||||
@@ -221,8 +221,7 @@ struct MOS6526Storage {
|
||||
control = v;
|
||||
|
||||
if(v&2) {
|
||||
Log::Logger<Log::Source::MOS6526> log;
|
||||
log.error().append("UNIMPLEMENTED: PB strobe");
|
||||
Log::Logger<Log::Source::MOS6526>::error().append("UNIMPLEMENTED: PB strobe");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+23
-14
@@ -12,10 +12,9 @@
|
||||
|
||||
using namespace MOS::MOS6560;
|
||||
|
||||
AudioGenerator::AudioGenerator(Concurrency::AsyncTaskQueue<false> &audio_queue) :
|
||||
AudioGenerator::AudioGenerator(Outputs::Speaker::TaskQueue &audio_queue) :
|
||||
audio_queue_(audio_queue) {}
|
||||
|
||||
|
||||
void AudioGenerator::set_volume(const uint8_t volume) {
|
||||
audio_queue_.enqueue([this, volume]() {
|
||||
volume_ = int16_t(volume) * range_multiplier_;
|
||||
@@ -97,19 +96,29 @@ constexpr uint8_t noise_pattern[] = {
|
||||
0xf0, 0xe1, 0xe0, 0x78, 0x70, 0x38, 0x3c, 0x3e, 0x1e, 0x3c, 0x1e, 0x1c, 0x70, 0x3c, 0x38, 0x3f,
|
||||
};
|
||||
|
||||
#define shift(r) shift_registers_[r] = \
|
||||
(shift_registers_[r] << 1) | (((shift_registers_[r]^0x80)&control_registers_[r]) >> 7)
|
||||
#define increment(r) shift_registers_[r] = (shift_registers_[r]+1)%8191
|
||||
#define update(r, m, up) \
|
||||
counters_[r]++; if((counters_[r] >> m) == 0x80) { up(r); counters_[r] = unsigned(control_registers_[r]&0x7f) << m; }
|
||||
// Note on slightly askew test: as far as I can make out, if the value in the register is 0x7f then what's supposed to
|
||||
// happen is that the 0x7f is loaded, on the next clocked cycle the Vic spots a 0x7f, pumps the output, reloads, etc. No
|
||||
// increment ever occurs. It's conditional. I don't really want two conditionals if I can avoid it so I'm incrementing
|
||||
// regardless and testing against 0x80. The effect should be the same: loading with 0x7f means an output update every
|
||||
// cycle, loading with 0x7e means every second cycle, etc.
|
||||
|
||||
template <Outputs::Speaker::Action action>
|
||||
void AudioGenerator::apply_samples(const std::size_t number_of_samples, Outputs::Speaker::MonoSample *const target) {
|
||||
const auto shift = [&](const int r) {
|
||||
shift_registers_[r] =
|
||||
(shift_registers_[r] << 1) | (((shift_registers_[r] ^ 0x80) & control_registers_[r]) >> 7);
|
||||
};
|
||||
const auto increment = [&](const int r) {
|
||||
shift_registers_[r] = (shift_registers_[r] + 1) % 8191;
|
||||
};
|
||||
const auto update = [&](const int r, const int m, auto &&up) {
|
||||
++counters_[r];
|
||||
|
||||
if((counters_[r] >> m) == 0x80) {
|
||||
up(r);
|
||||
counters_[r] = unsigned(control_registers_[r]&0x7f) << m;
|
||||
}
|
||||
// Note on slightly askew test: as far as I can make out, if the value in the register is 0x7f then what's
|
||||
// supposed to happen is that the 0x7f is loaded, on the next clocked cycle the Vic spots a 0x7f, pumps the
|
||||
// output, reloads, etc. No increment ever occurs. It's conditional. I don't really want two conditionals if I
|
||||
// can avoid it so I'm incrementing regardless and testing against 0x80. The effect should be the same: loading
|
||||
// with 0x7f means an output update every cycle, loading with 0x7e means every second cycle, etc.
|
||||
};
|
||||
|
||||
for(unsigned int c = 0; c < number_of_samples; ++c) {
|
||||
update(0, 2, shift);
|
||||
update(1, 1, shift);
|
||||
@@ -138,7 +147,7 @@ template void AudioGenerator::apply_samples<Outputs::Speaker::Action::Store>(
|
||||
template void AudioGenerator::apply_samples<Outputs::Speaker::Action::Ignore>(
|
||||
std::size_t, Outputs::Speaker::MonoSample *);
|
||||
|
||||
void AudioGenerator::set_sample_volume_range(std::int16_t range) {
|
||||
void AudioGenerator::set_sample_volume_range(const std::int16_t range) {
|
||||
range_multiplier_ = int16_t(range / 64);
|
||||
}
|
||||
|
||||
|
||||
+14
-25
@@ -19,7 +19,7 @@ namespace MOS::MOS6560 {
|
||||
// audio state
|
||||
class AudioGenerator: public Outputs::Speaker::BufferSource<AudioGenerator, false> {
|
||||
public:
|
||||
AudioGenerator(Concurrency::AsyncTaskQueue<false> &audio_queue);
|
||||
AudioGenerator(Outputs::Speaker::TaskQueue &audio_queue);
|
||||
|
||||
void set_volume(uint8_t);
|
||||
void set_control(int channel, uint8_t value);
|
||||
@@ -30,7 +30,7 @@ public:
|
||||
void set_sample_volume_range(std::int16_t);
|
||||
|
||||
private:
|
||||
Concurrency::AsyncTaskQueue<false> &audio_queue_;
|
||||
Outputs::Speaker::TaskQueue &audio_queue_;
|
||||
|
||||
unsigned int counters_[4] = {2, 1, 0, 0}; // create a slight phase offset for the three channels
|
||||
unsigned int shift_registers_[4] = {0, 0, 0, 0};
|
||||
@@ -64,8 +64,7 @@ public:
|
||||
MOS6560(BusHandler &bus_handler) :
|
||||
bus_handler_(bus_handler),
|
||||
crt_(65*4, 1, Outputs::Display::Type::NTSC60, Outputs::Display::InputDataType::Luminance8Phase8),
|
||||
audio_generator_(audio_queue_),
|
||||
speaker_(audio_generator_)
|
||||
audio_(Cycles(4))
|
||||
{
|
||||
// default to s-video output
|
||||
crt_.set_display_type(Outputs::Display::DisplayType::SVideo);
|
||||
@@ -75,11 +74,11 @@ public:
|
||||
}
|
||||
|
||||
~MOS6560() {
|
||||
audio_queue_.flush();
|
||||
audio_.stop();
|
||||
}
|
||||
|
||||
void set_clock_rate(const double clock_rate) {
|
||||
speaker_.set_input_rate(float(clock_rate / 4.0));
|
||||
audio_.speaker().set_input_rate(float(clock_rate / 4.0));
|
||||
}
|
||||
|
||||
void set_scan_target(Outputs::Display::ScanTarget *const scan_target) {
|
||||
@@ -95,11 +94,11 @@ public:
|
||||
return crt_.get_display_type();
|
||||
}
|
||||
Outputs::Speaker::Speaker *get_speaker() {
|
||||
return &speaker_;
|
||||
return &audio_.speaker();
|
||||
}
|
||||
|
||||
void set_high_frequency_cutoff(const float cutoff) {
|
||||
speaker_.set_high_frequency_cutoff(cutoff);
|
||||
audio_.speaker().set_high_frequency_cutoff(cutoff);
|
||||
}
|
||||
|
||||
/*!
|
||||
@@ -161,10 +160,10 @@ public:
|
||||
|
||||
switch(output_mode) {
|
||||
case OutputMode::PAL:
|
||||
crt_.set_visible_area(Outputs::Display::Rect(0.1f, 0.07f, 0.9f, 0.9f));
|
||||
crt_.set_fixed_framing(Outputs::Display::Rect(0.1f, 0.07f, 0.9f, 0.9f));
|
||||
break;
|
||||
case OutputMode::NTSC:
|
||||
crt_.set_visible_area(Outputs::Display::Rect(0.05f, 0.05f, 0.9f, 0.9f));
|
||||
crt_.set_fixed_framing(Outputs::Display::Rect(0.05f, 0.05f, 0.9f, 0.9f));
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -180,7 +179,7 @@ public:
|
||||
*/
|
||||
inline void run_for(const Cycles cycles) {
|
||||
// keep track of the amount of time since the speaker was updated; lazy updates are applied
|
||||
cycles_since_speaker_update_ += cycles;
|
||||
audio_ += cycles;
|
||||
|
||||
auto number_of_cycles = cycles.as_integral();
|
||||
while(number_of_cycles--) {
|
||||
@@ -377,8 +376,7 @@ public:
|
||||
Causes the 6560 to flush as much pending CRT and speaker communications as possible.
|
||||
*/
|
||||
inline void flush() {
|
||||
update_audio();
|
||||
audio_queue_.perform();
|
||||
audio_.perform();
|
||||
}
|
||||
|
||||
/*!
|
||||
@@ -420,14 +418,12 @@ public:
|
||||
case 0xb:
|
||||
case 0xc:
|
||||
case 0xd:
|
||||
update_audio();
|
||||
audio_generator_.set_control(address - 0xa, value);
|
||||
audio_->set_control(address - 0xa, value);
|
||||
break;
|
||||
|
||||
case 0xe:
|
||||
update_audio();
|
||||
registers_.auxiliary_colour = colours_[value >> 4];
|
||||
audio_generator_.set_volume(value & 0xf);
|
||||
audio_->set_volume(value & 0xf);
|
||||
break;
|
||||
|
||||
case 0xf: {
|
||||
@@ -467,14 +463,7 @@ private:
|
||||
BusHandler &bus_handler_;
|
||||
Outputs::CRT::CRT crt_;
|
||||
|
||||
Concurrency::AsyncTaskQueue<false> audio_queue_;
|
||||
AudioGenerator audio_generator_;
|
||||
Outputs::Speaker::PullLowpass<AudioGenerator> speaker_;
|
||||
|
||||
Cycles cycles_since_speaker_update_;
|
||||
void update_audio() {
|
||||
speaker_.run_for(audio_queue_, Cycles(cycles_since_speaker_update_.divide(Cycles(4))));
|
||||
}
|
||||
Outputs::Speaker::PullLowpassSpeakerQueue<Cycles, AudioGenerator> audio_;
|
||||
|
||||
// register state
|
||||
struct {
|
||||
|
||||
+417
-258
@@ -9,28 +9,43 @@
|
||||
#pragma once
|
||||
|
||||
#include "ClockReceiver/ClockReceiver.hpp"
|
||||
#include "Numeric/SizedInt.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
|
||||
//
|
||||
// WARNING: code is in flux. I'm attempting to use hoglet's FPGA implementation at
|
||||
// https://github.com/hoglet67/BeebFpga/blob/master/src/common/mc6845.vhd as an authoritative guide to proper behaviour,
|
||||
// having found his Electron ULA to be excellent. This is starting by mapping various bits of internal state here
|
||||
// to hoglet's equivalents; cf. comments.
|
||||
//
|
||||
|
||||
namespace Motorola::CRTC {
|
||||
|
||||
using RefreshAddress = Numeric::SizedInt<14>;
|
||||
using LineAddress = Numeric::SizedInt<5>;
|
||||
|
||||
using SyncCounter = Numeric::SizedInt<4>;
|
||||
using CharacterAddress = Numeric::SizedInt<8>;
|
||||
using RowAddress = Numeric::SizedInt<7>;
|
||||
|
||||
struct BusState {
|
||||
bool display_enable = false;
|
||||
bool hsync = false;
|
||||
bool vsync = false;
|
||||
bool hsync = false; // hs
|
||||
bool vsync = false; // vs
|
||||
bool cursor = false;
|
||||
uint16_t refresh_address = 0;
|
||||
uint16_t row_address = 0;
|
||||
RefreshAddress refresh;
|
||||
LineAddress line;
|
||||
|
||||
// Not strictly part of the bus state; provided because the partition between 6845 and bus handler
|
||||
// doesn't quite hold up in some emulated systems where the two are integrated and share more state.
|
||||
int field_count = 0;
|
||||
Numeric::SizedInt<5> field_count = 0; // field_counter
|
||||
};
|
||||
|
||||
class BusHandler {
|
||||
public:
|
||||
void perform_bus_cycle(const BusState &) {}
|
||||
public:
|
||||
void perform_bus_cycle(const BusState &) {}
|
||||
};
|
||||
|
||||
enum class Personality {
|
||||
@@ -39,14 +54,8 @@ enum class Personality {
|
||||
UM6845R, // Type 1 in CPC parlance. Status register, fixed-length VSYNC.
|
||||
MC6845, // Type 2. No status register, fixed-length VSYNC, no zero-length HSYNC.
|
||||
AMS40226, // Type 3. Status is get register, fixed-length VSYNC, no zero-length HSYNC.
|
||||
|
||||
EGA, // Extended EGA-style CRTC; uses 16-bit addressing throughout.
|
||||
};
|
||||
|
||||
constexpr bool is_egavga(const Personality p) {
|
||||
return p >= Personality::EGA;
|
||||
}
|
||||
|
||||
// https://www.pcjs.org/blog/2018/03/20/ advises that "the behavior of bits 5 and 6 [of register 10, the cursor start
|
||||
// register is really card specific".
|
||||
//
|
||||
@@ -54,10 +63,10 @@ constexpr bool is_egavga(const Personality p) {
|
||||
enum class CursorType {
|
||||
/// No cursor signal is generated.
|
||||
None,
|
||||
/// Built-in 6845 style: 00 => no blinking; 01 => no cursor; 10 => slow blink; 11 => fast blink
|
||||
Native,
|
||||
/// MDA style: 00 => symmetric blinking; 01 or 10 => no blinking; 11 => short on, long off.
|
||||
MDA,
|
||||
/// EGA style: ignore the bits completely.
|
||||
EGA,
|
||||
};
|
||||
|
||||
// TODO UM6845R and R12/R13; see http://www.cpcwiki.eu/index.php/CRTC#CRTC_Differences
|
||||
@@ -67,7 +76,7 @@ public:
|
||||
CRTC6845(BusHandlerT &bus_handler) noexcept :
|
||||
bus_handler_(bus_handler), status_(0) {}
|
||||
|
||||
void select_register(uint8_t r) {
|
||||
void select_register(const uint8_t r) {
|
||||
selected_register_ = r;
|
||||
}
|
||||
|
||||
@@ -85,65 +94,64 @@ public:
|
||||
if(selected_register_ == 16 || selected_register_ == 17) status_ &= ~0x40;
|
||||
|
||||
if(personality == Personality::UM6845R && selected_register_ == 31) return dummy_register_;
|
||||
if(selected_register_ < 12 || selected_register_ > 17) return 0xff;
|
||||
return registers_[selected_register_];
|
||||
|
||||
// Registers below 12 are write-only; no registers are defined above position 17
|
||||
// (other than the UM6845R-specific test register as per above).
|
||||
//
|
||||
// Per the BBC Wiki, attempting to read such a register results in 0.
|
||||
if(selected_register_ < 12 || selected_register_ > 17) return 0x00;
|
||||
|
||||
return registers_[selected_register_.get()];
|
||||
}
|
||||
|
||||
void set_register(const uint8_t value) {
|
||||
static constexpr bool is_ega = is_egavga(personality);
|
||||
|
||||
const auto load_low = [value](uint16_t &target) {
|
||||
target = (target & 0xff00) | value;
|
||||
};
|
||||
const auto load_high = [value](uint16_t &target) {
|
||||
constexpr uint8_t mask = RefreshMask >> 8;
|
||||
target = uint16_t((target & 0x00ff) | ((value & mask) << 8));
|
||||
};
|
||||
|
||||
switch(selected_register_) {
|
||||
switch(selected_register_.get()) {
|
||||
case 0: layout_.horizontal.total = value; break;
|
||||
case 1: layout_.horizontal.displayed = value; break;
|
||||
case 2: layout_.horizontal.start_sync = value; break;
|
||||
case 3:
|
||||
layout_.horizontal.sync_width = value & 0xf;
|
||||
layout_.horizontal.sync_width = value;
|
||||
layout_.vertical.sync_lines = value >> 4;
|
||||
// TODO: vertical sync lines:
|
||||
// "(0 means 16 on some CRTC. Not present on all CRTCs, fixed to 16 lines on these)"
|
||||
break;
|
||||
case 4: layout_.vertical.total = value & 0x7f; break;
|
||||
case 5: layout_.vertical.adjust = value & 0x1f; break;
|
||||
case 6: layout_.vertical.displayed = value & 0x7f; break;
|
||||
case 7: layout_.vertical.start_sync = value & 0x7f; break;
|
||||
case 4: layout_.vertical.total = value; break;
|
||||
case 5: layout_.vertical.adjust = value; break;
|
||||
case 6: layout_.vertical.displayed = value; break;
|
||||
case 7: layout_.vertical.start_sync = value; break;
|
||||
case 8:
|
||||
switch(value & 3) {
|
||||
default: layout_.interlace_mode_ = InterlaceMode::Off; break;
|
||||
case 0b01: layout_.interlace_mode_ = InterlaceMode::InterlaceSync; break;
|
||||
case 0b11: layout_.interlace_mode_ = InterlaceMode::InterlaceSyncAndVideo; break;
|
||||
default: layout_.interlace_mode_ = InterlaceMode::Off; break;
|
||||
case 0b01: layout_.interlace_mode_ = InterlaceMode::Sync; break;
|
||||
case 0b11: layout_.interlace_mode_ = InterlaceMode::SyncAndVideo; break;
|
||||
}
|
||||
|
||||
// Per CPC documentation, skew doesn't work on a "type 1 or 2", i.e. an MC6845 or a UM6845R.
|
||||
if(personality != Personality::UM6845R && personality != Personality::MC6845) {
|
||||
switch((value >> 4)&3) {
|
||||
default: display_skew_mask_ = 1; break;
|
||||
case 1: display_skew_mask_ = 2; break;
|
||||
case 2: display_skew_mask_ = 4; break;
|
||||
}
|
||||
}
|
||||
// if(personality != Personality::UM6845R && personality != Personality::MC6845) {
|
||||
// switch((value >> 4)&3) {
|
||||
// default: display_skew_mask_ = 1; break;
|
||||
// case 1: display_skew_mask_ = 2; break;
|
||||
// case 2: display_skew_mask_ = 4; break;
|
||||
// }
|
||||
// }
|
||||
break;
|
||||
case 9: layout_.vertical.end_row = value & 0x1f; break;
|
||||
case 9: layout_.vertical.end_line = value; break;
|
||||
case 10:
|
||||
layout_.vertical.start_cursor = value & 0x1f;
|
||||
layout_.cursor_flags = (value >> 5) & 3;
|
||||
layout_.vertical.start_cursor = value;
|
||||
layout_.cursor_flags = value >> 5;
|
||||
update_cursor_mask();
|
||||
break;
|
||||
case 11:
|
||||
layout_.vertical.end_cursor = value & 0x1f;
|
||||
layout_.vertical.end_cursor = value;
|
||||
break;
|
||||
case 12: load_high(layout_.start_address); break;
|
||||
case 13: load_low(layout_.start_address); break;
|
||||
case 14: load_high(layout_.cursor_address); break;
|
||||
case 15: load_low(layout_.cursor_address); break;
|
||||
case 12: layout_.start_address.template load<8>(value); break;
|
||||
case 13: layout_.start_address.template load<0>(value); break;
|
||||
case 14: layout_.cursor_address.template load<8>(value); break;
|
||||
case 15: layout_.cursor_address.template load<0>(value); break;
|
||||
}
|
||||
|
||||
// Take redundant copies of all registers, limited to their actual bit sizes,
|
||||
// to proffer up if the registers are read.
|
||||
static constexpr uint8_t masks[] = {
|
||||
0xff, // Horizontal total.
|
||||
0xff, // Horizontal display end.
|
||||
@@ -152,24 +160,24 @@ public:
|
||||
// EGA: b0–b4: end of horizontal blank;
|
||||
// b5–b6: "Number of character clocks to delay start of display after Horizontal Total has been reached."
|
||||
|
||||
is_ega ? 0xff : 0x7f, // Start horizontal retrace.
|
||||
0x7f, // Start horizontal retrace.
|
||||
0x1f, 0x7f, 0x7f,
|
||||
0xff, 0x1f, 0x7f, 0x1f,
|
||||
uint8_t(RefreshMask >> 8), uint8_t(RefreshMask),
|
||||
uint8_t(RefreshMask >> 8), uint8_t(RefreshMask),
|
||||
0xfc, 0x1f, 0x7f, 0x1f,
|
||||
uint8_t(RefreshAddress::Mask >> 8), uint8_t(RefreshAddress::Mask),
|
||||
uint8_t(RefreshAddress::Mask >> 8), uint8_t(RefreshAddress::Mask),
|
||||
};
|
||||
|
||||
if(selected_register_ < 16) {
|
||||
registers_[selected_register_] = value & masks[selected_register_];
|
||||
registers_[selected_register_.get()] = value & masks[selected_register_.get()];
|
||||
}
|
||||
if(selected_register_ == 31 && personality == Personality::UM6845R) {
|
||||
if(selected_register_.get() == 31 && personality == Personality::UM6845R) {
|
||||
dummy_register_ = value;
|
||||
}
|
||||
}
|
||||
|
||||
void trigger_light_pen() {
|
||||
registers_[17] = bus_state_.refresh_address & 0xff;
|
||||
registers_[16] = bus_state_.refresh_address >> 8;
|
||||
registers_[17] = bus_state_.refresh.get() & 0xff;
|
||||
registers_[16] = bus_state_.refresh.get() >> 8;
|
||||
status_ |= 0x40;
|
||||
}
|
||||
|
||||
@@ -180,179 +188,291 @@ public:
|
||||
// ordered so that whatever assignments result don't affect any subsequent conditionals
|
||||
|
||||
|
||||
// Do bus work.
|
||||
bus_state_.cursor = is_cursor_line_ &&
|
||||
bus_state_.refresh_address == layout_.cursor_address;
|
||||
bus_state_.display_enable = character_is_visible_ && line_is_visible_;
|
||||
bus_handler_.perform_bus_cycle(bus_state_);
|
||||
//
|
||||
// External bus activity.
|
||||
//
|
||||
bus_state_.line = line_is_interlaced_ ? (line_ & LineAddress::IntT(~1)) | (odd_field_ ? 1 : 0) : line_;
|
||||
bus_state_.display_enable = character_is_visible_ && row_is_visible_;
|
||||
bus_state_.cursor = (cursor_mask_ && is_cursor_line_ && bus_state_.refresh == layout_.cursor_address)
|
||||
&& bus_state_.display_enable;
|
||||
|
||||
bus_handler_.perform_bus_cycle(bus_state_);
|
||||
|
||||
bus_state_.refresh = refresh_; // Deliberate: do this after bus activity.
|
||||
// TODO: is this a hack?
|
||||
|
||||
|
||||
//
|
||||
// Shared, stateless signals.
|
||||
// Shared signals.
|
||||
//
|
||||
const bool character_total_hit = character_counter_ == layout_.horizontal.total;
|
||||
const uint8_t lines_per_row =
|
||||
layout_.interlace_mode_ == InterlaceMode::InterlaceSyncAndVideo ?
|
||||
layout_.vertical.end_row & ~1 : layout_.vertical.end_row;
|
||||
const bool row_end_hit = bus_state_.row_address == lines_per_row && !is_in_adjustment_period_;
|
||||
const bool was_eof = eof_latched_;
|
||||
const bool character_total_hit = character_counter_ == layout_.horizontal.total; // r00_h_total_hit
|
||||
const auto lines_per_row =
|
||||
layout_.interlace_mode_ == InterlaceMode::SyncAndVideo ?
|
||||
layout_.vertical.end_line & LineAddress::IntT(~1) : layout_.vertical.end_line; // max_scanline
|
||||
const bool line_end_hit = line_ == lines_per_row && !is_in_adjustment_period_; // max_scanline_hit
|
||||
const bool new_frame =
|
||||
character_total_hit && was_eof &&
|
||||
character_total_hit && eof_latched_ &&
|
||||
(
|
||||
layout_.interlace_mode_ == InterlaceMode::Off ||
|
||||
!odd_field_
|
||||
);
|
||||
!bus_state_.field_count.bit<0>() ||
|
||||
extra_line_
|
||||
); // new_frame
|
||||
|
||||
//
|
||||
// Horizontal.
|
||||
//
|
||||
|
||||
// Update horizontal sync.
|
||||
if(bus_state_.hsync) {
|
||||
++hsync_counter_;
|
||||
bus_state_.hsync = hsync_counter_ != layout_.horizontal.sync_width;
|
||||
}
|
||||
if(character_counter_ == layout_.horizontal.start_sync) {
|
||||
hsync_counter_ = 0;
|
||||
bus_state_.hsync = true;
|
||||
}
|
||||
|
||||
// Check for end-of-line.
|
||||
character_reset_history_ <<= 1;
|
||||
if(character_total_hit) {
|
||||
character_counter_ = 0;
|
||||
character_is_visible_ = true;
|
||||
character_reset_history_ |= 1;
|
||||
} else {
|
||||
character_counter_++;
|
||||
}
|
||||
|
||||
// Check for end of visible characters.
|
||||
if(character_counter_ == layout_.horizontal.displayed) {
|
||||
character_is_visible_ = false;
|
||||
}
|
||||
|
||||
//
|
||||
// End-of-frame.
|
||||
//
|
||||
|
||||
if(character_total_hit) {
|
||||
if(was_eof) {
|
||||
eof_latched_ = eom_latched_ = is_in_adjustment_period_ = false;
|
||||
adjustment_counter_ = 0;
|
||||
} else if(is_in_adjustment_period_) {
|
||||
adjustment_counter_ = (adjustment_counter_ + 1) & 31;
|
||||
}
|
||||
}
|
||||
|
||||
if(character_reset_history_ & 2) {
|
||||
eom_latched_ |= row_end_hit && row_counter_ == layout_.vertical.total;
|
||||
}
|
||||
|
||||
if(character_reset_history_ & 4 && eom_latched_) {
|
||||
// TODO: I don't believe the "add 1 for interlaced" test here is accurate;
|
||||
// others represent the extra scanline as additional state, presumably because
|
||||
// adjust total might be reprogrammed at any time.
|
||||
const auto adjust_length =
|
||||
layout_.vertical.adjust + (layout_.interlace_mode_ != InterlaceMode::Off && odd_field_ ? 1 : 0);
|
||||
is_in_adjustment_period_ |= adjustment_counter_ != adjust_length;
|
||||
eof_latched_ |= adjustment_counter_ == adjust_length;
|
||||
}
|
||||
|
||||
//
|
||||
// Vertical.
|
||||
//
|
||||
|
||||
// Sync.
|
||||
const bool vsync_horizontal =
|
||||
(!odd_field_ && !character_counter_) ||
|
||||
(odd_field_ && character_counter_ == (layout_.horizontal.total >> 1));
|
||||
if(vsync_horizontal) {
|
||||
if((row_counter_ == layout_.vertical.start_sync && !bus_state_.row_address) || bus_state_.vsync) {
|
||||
bus_state_.vsync = true;
|
||||
vsync_counter_ = (vsync_counter_ + 1) & 0xf;
|
||||
} else {
|
||||
vsync_counter_ = 0;
|
||||
}
|
||||
|
||||
if(vsync_counter_ == layout_.vertical.sync_lines) {
|
||||
bus_state_.vsync = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Row address.
|
||||
if(character_total_hit) {
|
||||
if(was_eof) {
|
||||
bus_state_.row_address = 0;
|
||||
eof_latched_ = eom_latched_ = false;
|
||||
} else if(row_end_hit) {
|
||||
bus_state_.row_address = 0;
|
||||
} else if(layout_.interlace_mode_ == InterlaceMode::InterlaceSyncAndVideo) {
|
||||
bus_state_.row_address = (bus_state_.row_address + 2) & ~1 & 31;
|
||||
} else {
|
||||
bus_state_.row_address = (bus_state_.row_address + 1) & 31;
|
||||
}
|
||||
}
|
||||
|
||||
// Row counter.
|
||||
row_counter_ = next_row_counter_;
|
||||
if(new_frame) {
|
||||
next_row_counter_ = 0;
|
||||
is_first_scanline_ = true;
|
||||
} else {
|
||||
next_row_counter_ = row_end_hit && character_total_hit ?
|
||||
(next_row_counter_ + 1) : next_row_counter_;
|
||||
is_first_scanline_ &= !row_end_hit;
|
||||
}
|
||||
|
||||
// Vertical display enable.
|
||||
if(is_first_scanline_) {
|
||||
line_is_visible_ = true;
|
||||
odd_field_ = bus_state_.field_count & 1;
|
||||
} else if(line_is_visible_ && row_counter_ == layout_.vertical.displayed) {
|
||||
line_is_visible_ = false;
|
||||
++bus_state_.field_count;
|
||||
}
|
||||
|
||||
|
||||
// Cursor.
|
||||
if constexpr (cursor_type != CursorType::None) {
|
||||
// Check for cursor enable.
|
||||
is_cursor_line_ |= bus_state_.row_address == layout_.vertical.start_cursor;
|
||||
is_cursor_line_ &= bus_state_.row_address != layout_.vertical.end_cursor;
|
||||
|
||||
switch(cursor_type) {
|
||||
// MDA-style blinking.
|
||||
// https://retrocomputing.stackexchange.com/questions/27803/what-are-the-blinking-rates-of-the-caret-and-of-blinking-text-on-pc-graphics-car
|
||||
// gives an 8/8 pattern for regular blinking though mode 11 is then just a guess.
|
||||
case CursorType::MDA:
|
||||
switch(layout_.cursor_flags) {
|
||||
case 0b11: is_cursor_line_ &= (bus_state_.field_count & 8) < 3; break;
|
||||
case 0b00: is_cursor_line_ &= bool(bus_state_.field_count & 8); break;
|
||||
case 0b01: is_cursor_line_ = false; break;
|
||||
case 0b10: is_cursor_line_ = true; break;
|
||||
default: break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Addressing.
|
||||
//
|
||||
|
||||
// Start-of-line address: seeded with the programmed display start address upon a new frame;
|
||||
// otherwise copied from the refresh address at the end of each line of characters.
|
||||
const auto initial_line_address = line_address_;
|
||||
if(new_frame) {
|
||||
bus_state_.refresh_address = layout_.start_address;
|
||||
line_address_ = layout_.start_address;
|
||||
} else if(character_counter_ == layout_.horizontal.displayed && line_end_hit) {
|
||||
line_address_ = refresh_;
|
||||
}
|
||||
|
||||
// Refresh address: seeded with the programmed display start address upon a new frame;
|
||||
// otherwise copied from the start-of-line address is a new line is about to start;
|
||||
// otherwise incremented across the line.
|
||||
if(new_frame) {
|
||||
refresh_ = layout_.start_address;
|
||||
} else if(character_total_hit) {
|
||||
bus_state_.refresh_address = line_address_;
|
||||
refresh_ = initial_line_address;
|
||||
} else {
|
||||
bus_state_.refresh_address = (bus_state_.refresh_address + 1) & RefreshMask;
|
||||
++refresh_;
|
||||
}
|
||||
|
||||
//
|
||||
// Per hoglet: b0 does not vary within a line even if you switch in/out of interlaced mode.
|
||||
// He reproduces the same with extra state, which probably doesn't exist on the real device.
|
||||
// This implementation follows his lead.
|
||||
//
|
||||
if(character_total_hit) {
|
||||
line_is_interlaced_ = layout_.interlace_mode_ == InterlaceMode::SyncAndVideo;
|
||||
}
|
||||
|
||||
//
|
||||
// Sync.
|
||||
//
|
||||
|
||||
// Vertical sync.
|
||||
//
|
||||
// Counter:
|
||||
// Sync width of 0 => 16 lines of sync.
|
||||
// Triggered by the row counter becoming equal to the sync start position, regardless of when.
|
||||
// Subsequently increments at the start of each line.
|
||||
const bool hit_vsync = row_counter_ == layout_.vertical.start_sync; // vs_hit
|
||||
const bool is_vsync_rising_edge = hit_vsync && !hit_vsync_last_;
|
||||
hit_vsync_last_ = hit_vsync;
|
||||
|
||||
// Select odd or even sync depending on the field.
|
||||
// (Noted: the reverse-odd-test is intentional)
|
||||
bus_state_.vsync = (layout_.interlace_mode_ != InterlaceMode::Off && !odd_field_) ?
|
||||
vsync_odd_ : vsync_even_;
|
||||
|
||||
// Odd sync copies even sync, but half a line later.
|
||||
if(character_counter_ == layout_.horizontal.total >> 1) {
|
||||
vsync_odd_ = vsync_even_;
|
||||
}
|
||||
|
||||
// Even sync begins on the rising edge of vsync, then continues until the counter hits its proper
|
||||
// target, one cycle after reset of the horizontal counter.
|
||||
if(is_vsync_rising_edge) {
|
||||
vsync_even_ = true;
|
||||
} else if(vsync_counter_ == layout_.vertical.sync_lines && character_reset_history_.bit<0>()) {
|
||||
vsync_even_ = false;
|
||||
}
|
||||
|
||||
// The vsync counter is zeroed by the rising edge of sync but subsequently increments immediately
|
||||
// upon reset of the horizontal counter.
|
||||
if(is_vsync_rising_edge) {
|
||||
vsync_counter_ = 0;
|
||||
} else if(character_total_hit) {
|
||||
++vsync_counter_;
|
||||
}
|
||||
|
||||
// Horizontal sync.
|
||||
//
|
||||
// A sync width of 0 should mean that no sync is observed.
|
||||
// Hitting the start sync condition while sync is already ongoing should have no effect.
|
||||
if(bus_state_.hsync) {
|
||||
++hsync_counter_;
|
||||
} else {
|
||||
hsync_counter_ = 0;
|
||||
}
|
||||
if(hsync_counter_ == layout_.horizontal.sync_width) {
|
||||
bus_state_.hsync = false;
|
||||
} else if(character_counter_ == layout_.horizontal.start_sync) {
|
||||
bus_state_.hsync = true;
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// Horizontal.
|
||||
//
|
||||
|
||||
// Check for visible characters; visibility starts in the first column and continues
|
||||
if(!character_counter_) {
|
||||
character_is_visible_ = true;
|
||||
}
|
||||
if(character_counter_ == layout_.horizontal.displayed || character_total_hit) {
|
||||
character_is_visible_ = false;
|
||||
}
|
||||
|
||||
// Check for end-of-line.
|
||||
//
|
||||
// character_reset_history_ is used because some events are defined to occur one or two
|
||||
// cycles after end-of-line regardless of whether an additional end of line is hit in
|
||||
// the interim.
|
||||
if(character_total_hit) {
|
||||
character_counter_ = 0;
|
||||
} else {
|
||||
++character_counter_;
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// Vertical.
|
||||
//
|
||||
|
||||
// Update line counter (which also counts the vertical adjust period).
|
||||
//
|
||||
// Counts in steps of 2 only if & 3) mode is InterlaceMode::SyncAndVideo and this is
|
||||
// not the adjustment period. Otherwise counts in steps of 1.
|
||||
if(new_frame) {
|
||||
line_ = 0;
|
||||
} else if(character_total_hit) {
|
||||
line_ = next_line_;
|
||||
}
|
||||
|
||||
if(line_end_hit) {
|
||||
next_line_ = 0;
|
||||
} else if(is_in_adjustment_period_ || layout_.interlace_mode_ != InterlaceMode::SyncAndVideo) {
|
||||
next_line_ = line_ + 1;
|
||||
} else {
|
||||
next_line_ = (line_ + 2) & LineAddress::IntT(~1);
|
||||
}
|
||||
|
||||
// Update row counter.
|
||||
//
|
||||
// Very straightforward: tests at end of line whether row end has also been hit. If so, increments.
|
||||
row_counter_ = next_row_counter_;
|
||||
if(new_frame) {
|
||||
next_row_counter_ = 0;
|
||||
} else if(character_total_hit && line_end_hit) {
|
||||
next_row_counter_ = row_counter_ + 1;
|
||||
}
|
||||
|
||||
// Vertical display enable.
|
||||
if(is_first_scanline_) {
|
||||
row_is_visible_ = true;
|
||||
odd_field_ = bus_state_.field_count.bit<0>();
|
||||
} else if(row_is_visible_ && row_counter_ == layout_.vertical.displayed) {
|
||||
row_is_visible_ = false;
|
||||
++bus_state_.field_count;
|
||||
update_cursor_mask();
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// End-of-frame.
|
||||
//
|
||||
|
||||
if(new_frame) {
|
||||
is_in_adjustment_period_ = false;
|
||||
} else if(character_total_hit && eom_latched_ && will_adjust_) {
|
||||
is_in_adjustment_period_ = true;
|
||||
}
|
||||
|
||||
if(new_frame) {
|
||||
line_address_ = layout_.start_address;
|
||||
} else if(character_counter_ == layout_.horizontal.displayed && row_end_hit) {
|
||||
line_address_ = bus_state_.refresh_address;
|
||||
is_first_scanline_ = true;
|
||||
} else if(character_total_hit) {
|
||||
is_first_scanline_ = false;
|
||||
}
|
||||
|
||||
// The extra-line flag holds true for a single line if one is needed to complete
|
||||
// an odd interlaced field.
|
||||
if(
|
||||
character_total_hit &&
|
||||
eof_latched_ &&
|
||||
layout_.interlace_mode_ != InterlaceMode::Off &&
|
||||
bus_state_.field_count.bit<0>() &&
|
||||
!extra_line_
|
||||
) {
|
||||
extra_line_ = true;
|
||||
} else if(character_total_hit) {
|
||||
extra_line_ = false;
|
||||
}
|
||||
|
||||
// EOF (end of field) marks the end of the regular set of scans, including the adjustment area.
|
||||
// It doesn't include the extra line added during odd interlaced fields.
|
||||
if(new_frame) {
|
||||
eof_latched_ = false;
|
||||
} else if(eom_latched_ && !will_adjust_ && character_reset_history_.bit<2>()) {
|
||||
eof_latched_ = true;
|
||||
}
|
||||
|
||||
// Will-adjust indicates whether an adjustment area is upcoming; if so then it occurs after EOM.
|
||||
if(new_frame) {
|
||||
will_adjust_ = false;
|
||||
} else if(character_reset_history_.bit<1>() && eom_latched_) {
|
||||
if(next_line_ == layout_.vertical.adjust) {
|
||||
will_adjust_ = false;
|
||||
} else {
|
||||
will_adjust_ = true;
|
||||
}
|
||||
}
|
||||
|
||||
// EOM (end of main) marks the end of the visible set of rows, prior to any adjustment area.
|
||||
// It is set one cycle after the most-recent start of line.
|
||||
if(new_frame) {
|
||||
eom_latched_ = false;
|
||||
} else if(character_reset_history_.bit<0>() && line_end_hit && row_counter_ == layout_.vertical.total) {
|
||||
eom_latched_ = true;
|
||||
}
|
||||
|
||||
//
|
||||
// Cursor
|
||||
//
|
||||
cursor_history_ <<= 1;
|
||||
if constexpr (cursor_type != CursorType::None) {
|
||||
if(character_total_hit) {
|
||||
// This is clearly a nonsense test; there's absolutely no reason a real 6845 would do anything
|
||||
// other than equality comparisons, to maintain internal state.
|
||||
//
|
||||
// ... that said, I have been unable to reconcile:
|
||||
//
|
||||
// 1. the PCjs results on real MC6845Ps that show wraparound cursors
|
||||
// Cf. https://www.pcjs.org/blog/2018/03/20/ ; and
|
||||
// 2. the expectations of the BBC Micro (which sets an out-of-range stop line for its cursor
|
||||
// right at initial boot) and various pieces of its software (including but
|
||||
// not limited to Arcadians, which uses in-range numbers but has start > end and expects
|
||||
// the cursor correspondingly to be hidden).
|
||||
//
|
||||
// I also note that the two BBC FPGA implementations I glanced at, hoglet's and Mister's, use
|
||||
// fictional range comparisons.
|
||||
//
|
||||
// But, on the other hand, Tom Seddon remarks at https://github.com/tom-seddon/6845-tests that
|
||||
// "Looks like the cursor switches on when cursor is off and raster matches R10, and switches
|
||||
// off when cursor is on and raster matches R11."
|
||||
//
|
||||
// (but also seems to use a range test in his software implementation?)
|
||||
is_cursor_line_ =
|
||||
line_ >= layout_.vertical.start_cursor &&
|
||||
line_ <= layout_.vertical.end_cursor;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// Event history.
|
||||
//
|
||||
|
||||
// Somewhat of a fiction, this keeps a track of recent character resets because
|
||||
// some events are keyed on 1 cycle after last reset, 2 cycles after last reset, etc.
|
||||
character_reset_history_ <<= 1;
|
||||
character_reset_history_ |= character_total_hit;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -361,78 +481,117 @@ public:
|
||||
}
|
||||
|
||||
private:
|
||||
static constexpr uint16_t RefreshMask = (personality >= Personality::EGA) ? 0xffff : 0x3fff;
|
||||
|
||||
BusHandlerT &bus_handler_;
|
||||
BusState bus_state_;
|
||||
|
||||
enum class InterlaceMode {
|
||||
/// No interlacing.
|
||||
Off,
|
||||
InterlaceSync,
|
||||
InterlaceSyncAndVideo,
|
||||
};
|
||||
enum class BlinkMode {
|
||||
// TODO.
|
||||
/// Provide interlaced sync, but just scan out the exact same display for each field.
|
||||
Sync,
|
||||
/// Provide interlaced sync and scan even/odd lines depending on field.
|
||||
SyncAndVideo,
|
||||
};
|
||||
|
||||
// Comments on the right provide the corresponding signal name in hoglet's VHDL implementation.
|
||||
struct {
|
||||
struct {
|
||||
uint8_t total;
|
||||
uint8_t displayed;
|
||||
uint8_t start_sync;
|
||||
uint8_t sync_width;
|
||||
CharacterAddress total; // r00_h_total
|
||||
CharacterAddress displayed; // r01_h_displayed
|
||||
CharacterAddress start_sync; // r02_h_sync_pos
|
||||
SyncCounter sync_width; // r03_h_sync_width
|
||||
} horizontal;
|
||||
|
||||
struct {
|
||||
uint8_t total;
|
||||
uint8_t displayed;
|
||||
uint8_t start_sync;
|
||||
uint8_t sync_lines;
|
||||
uint8_t adjust;
|
||||
RowAddress total; // r04_v_total
|
||||
RowAddress displayed; // r06_v_displayed
|
||||
RowAddress start_sync; // r07_v_sync_pos
|
||||
SyncCounter sync_lines; // r03_v_sync_width
|
||||
LineAddress adjust; // r05_v_total_adj
|
||||
|
||||
uint8_t end_row;
|
||||
uint8_t start_cursor;
|
||||
uint8_t end_cursor;
|
||||
LineAddress end_line; // r09_max_scanline_addr
|
||||
LineAddress start_cursor; // r10_cursor_start
|
||||
LineAddress end_cursor; // r11_cursor_end
|
||||
} vertical;
|
||||
|
||||
InterlaceMode interlace_mode_ = InterlaceMode::Off;
|
||||
uint8_t end_row() const {
|
||||
return interlace_mode_ == InterlaceMode::InterlaceSyncAndVideo ? vertical.end_row & ~1 : vertical.end_row;
|
||||
}
|
||||
InterlaceMode interlace_mode_ = InterlaceMode::Off; // r08_interlace
|
||||
|
||||
uint16_t start_address;
|
||||
uint16_t cursor_address;
|
||||
uint16_t light_pen_address;
|
||||
uint8_t cursor_flags;
|
||||
RefreshAddress start_address; // r12_start_addr_h + r13_start_addr_l
|
||||
RefreshAddress cursor_address; // r14_cursor_h + r15_cursor_l
|
||||
RefreshAddress light_pen_address; // r16_light_pen_h + r17_light_pen_l
|
||||
Numeric::SizedInt<2> cursor_flags; // r10_cursor_mode
|
||||
} layout_;
|
||||
|
||||
uint8_t registers_[18]{};
|
||||
uint8_t dummy_register_ = 0;
|
||||
int selected_register_ = 0;
|
||||
Numeric::SizedInt<5> selected_register_ = 0;
|
||||
|
||||
uint8_t character_counter_ = 0;
|
||||
uint32_t character_reset_history_ = 0;
|
||||
uint8_t row_counter_ = 0, next_row_counter_ = 0;
|
||||
uint8_t adjustment_counter_ = 0;
|
||||
CharacterAddress character_counter_; // h_counter
|
||||
Numeric::SizedInt<3> character_reset_history_; // sol
|
||||
RowAddress row_counter_; // row_counter
|
||||
RowAddress next_row_counter_; // row_counter_next
|
||||
LineAddress line_; // line_counter
|
||||
LineAddress next_line_; // line_counter_next
|
||||
RefreshAddress refresh_; // ma_i
|
||||
|
||||
bool character_is_visible_ = false;
|
||||
bool line_is_visible_ = false;
|
||||
bool character_is_visible_ = false; // h_display
|
||||
bool row_is_visible_ = false; // v_display
|
||||
bool is_first_scanline_ = false;
|
||||
bool is_cursor_line_ = false;
|
||||
bool cursor_mask_ = false;
|
||||
|
||||
int hsync_counter_ = 0;
|
||||
int vsync_counter_ = 0;
|
||||
bool is_in_adjustment_period_ = false;
|
||||
SyncCounter hsync_counter_; // h_sync_counter
|
||||
SyncCounter vsync_counter_; // v_sync_counter
|
||||
bool will_adjust_ = false; // in_adj
|
||||
bool is_in_adjustment_period_ = false; // adj_in_progress
|
||||
|
||||
uint16_t line_address_ = 0;
|
||||
RefreshAddress line_address_; // ma_row
|
||||
uint8_t status_ = 0;
|
||||
|
||||
int display_skew_mask_ = 1;
|
||||
unsigned int character_is_visible_shifter_ = 0;
|
||||
// int display_skew_mask_ = 1;
|
||||
|
||||
bool eof_latched_ = false;
|
||||
bool eom_latched_ = false;
|
||||
uint16_t next_row_address_ = 0;
|
||||
bool odd_field_ = false;
|
||||
bool eof_latched_ = false; // eof_latched
|
||||
bool eom_latched_ = false; // eom_latched
|
||||
bool odd_field_ = false; // odd_field
|
||||
bool extra_line_ = false; // extra_scanline
|
||||
|
||||
bool hit_vsync_last_ = false; // vs_hit_last
|
||||
bool vsync_even_ = false; // vs_even
|
||||
bool vsync_odd_ = false; // vs_odd
|
||||
|
||||
Numeric::SizedInt<3> cursor_history_; // cursor0, cursor1, cursor2 [TODO]
|
||||
bool line_is_interlaced_ = false;
|
||||
|
||||
void update_cursor_mask() {
|
||||
switch(cursor_type) {
|
||||
case CursorType::None:
|
||||
break;
|
||||
|
||||
// MDA-style blinking.
|
||||
// https://retrocomputing.stackexchange.com/questions/27803/what-are-the-blinking-rates-of-the-caret-and-of-blinking-text-on-pc-graphics-car
|
||||
// gives an 8/8 pattern for regular blinking though mode 11 is then just a guess.
|
||||
case CursorType::MDA:
|
||||
switch(layout_.cursor_flags.get()) {
|
||||
case 0b11: cursor_mask_ = (bus_state_.field_count & 8) < 3; break;
|
||||
case 0b00: cursor_mask_ = bus_state_.field_count.bit<3>(); break;
|
||||
case 0b01: cursor_mask_ = false; break;
|
||||
case 0b10: cursor_mask_ = true; break;
|
||||
default: break;
|
||||
}
|
||||
break;
|
||||
|
||||
// Standard built-in 6845 blinking.
|
||||
case CursorType::Native:
|
||||
switch(layout_.cursor_flags.get()) {
|
||||
case 0b00: cursor_mask_ = true; break;
|
||||
case 0b01: cursor_mask_ = false; break;
|
||||
case 0b10: cursor_mask_ = bus_state_.field_count.bit<3>(); break;
|
||||
case 0b11: cursor_mask_= bus_state_.field_count.bit<4>(); break;
|
||||
default: break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
|
||||
#include "6850.hpp"
|
||||
|
||||
#include <bit>
|
||||
#include <cassert>
|
||||
|
||||
using namespace Motorola::ACIA;
|
||||
@@ -141,11 +142,8 @@ int ACIA::expected_bits() {
|
||||
return 1 + data_bits_ + stop_bits_ + (parity_ != Parity::None);
|
||||
}
|
||||
|
||||
uint8_t ACIA::parity(uint8_t value) {
|
||||
value ^= value >> 4;
|
||||
value ^= value >> 2;
|
||||
value ^= value >> 1;
|
||||
return value ^ (parity_ == Parity::Even);
|
||||
uint8_t ACIA::parity(const uint8_t value) {
|
||||
return (std::popcount(value) & 1) ^ (parity_ == Parity::Even);
|
||||
}
|
||||
|
||||
bool ACIA::serial_line_did_produce_bit(Serial::Line<false> *, const int bit) {
|
||||
|
||||
@@ -14,9 +14,7 @@
|
||||
#include "Outputs/Log.hpp"
|
||||
|
||||
namespace {
|
||||
|
||||
Log::Logger<Log::Source::MFP68901> logger;
|
||||
|
||||
using Logger = Log::Logger<Log::Source::MFP68901>;
|
||||
}
|
||||
|
||||
using namespace Motorola::MFP68901;
|
||||
@@ -65,11 +63,11 @@ uint8_t MFP68901::read(int address) {
|
||||
case 0x11: case 0x12: return get_timer_data(address - 0xf);
|
||||
|
||||
// USART block: TODO.
|
||||
case 0x13: logger.error().append("Read: sync character generator"); break;
|
||||
case 0x14: logger.error().append("Read: USART control"); break;
|
||||
case 0x15: logger.error().append("Read: receiver status"); break;
|
||||
case 0x16: logger.error().append("Read: transmitter status"); break;
|
||||
case 0x17: logger.error().append("Read: USART data"); break;
|
||||
case 0x13: Logger::error().append("Read: sync character generator"); break;
|
||||
case 0x14: Logger::error().append("Read: USART control"); break;
|
||||
case 0x15: Logger::error().append("Read: receiver status"); break;
|
||||
case 0x16: Logger::error().append("Read: transmitter status"); break;
|
||||
case 0x17: Logger::error().append("Read: USART data"); break;
|
||||
}
|
||||
return 0x00;
|
||||
}
|
||||
@@ -114,7 +112,7 @@ void MFP68901::write(int address, const uint8_t value) {
|
||||
return;
|
||||
}
|
||||
|
||||
constexpr int timer_prescales[] = {
|
||||
static constexpr int timer_prescales[] = {
|
||||
1, 4, 10, 16, 50, 64, 100, 200
|
||||
};
|
||||
|
||||
@@ -170,11 +168,11 @@ void MFP68901::write(int address, const uint8_t value) {
|
||||
break;
|
||||
|
||||
// USART block: TODO.
|
||||
case 0x13: logger.error().append("Write: sync character generator"); break;
|
||||
case 0x14: logger.error().append("Write: USART control"); break;
|
||||
case 0x15: logger.error().append("Write: receiver status"); break;
|
||||
case 0x16: logger.error().append("Write: transmitter status"); break;
|
||||
case 0x17: logger.error().append("Write: USART data"); break;
|
||||
case 0x13: Logger::error().append("Write: sync character generator"); break;
|
||||
case 0x14: Logger::error().append("Write: USART control"); break;
|
||||
case 0x15: Logger::error().append("Write: receiver status"); break;
|
||||
case 0x16: Logger::error().append("Write: transmitter status"); break;
|
||||
case 0x17: Logger::error().append("Write: USART data"); break;
|
||||
}
|
||||
|
||||
update_clocking_observer();
|
||||
@@ -221,7 +219,7 @@ HalfCycles MFP68901::next_sequence_point() {
|
||||
// MARK: - Timers
|
||||
|
||||
void MFP68901::set_timer_mode(const int timer, const TimerMode mode, const int prescale, const bool reset_timer) {
|
||||
logger.error().append("Timer %d mode set: %d; prescale: %d", timer, mode, prescale);
|
||||
Logger::error().append("Timer %d mode set: %d; prescale: %d", timer, mode, prescale);
|
||||
timers_[timer].mode = mode;
|
||||
if(reset_timer) {
|
||||
timers_[timer].prescale_count = 0;
|
||||
@@ -399,7 +397,7 @@ int MFP68901::acknowledge_interrupt() {
|
||||
|
||||
int selected = 0;
|
||||
while((1 << selected) != mask) ++selected;
|
||||
// logger.error().append("Interrupt acknowledged: %d", selected);
|
||||
// Logger::error().append("Interrupt acknowledged: %d", selected);
|
||||
return (interrupt_vector_ & 0xf0) | uint8_t(selected);
|
||||
}
|
||||
|
||||
|
||||
@@ -16,8 +16,8 @@
|
||||
namespace Motorola::MFP68901 {
|
||||
|
||||
class PortHandler {
|
||||
public:
|
||||
// TODO: announce changes in output.
|
||||
public:
|
||||
// TODO: announce changes in output.
|
||||
};
|
||||
|
||||
/*!
|
||||
|
||||
@@ -91,12 +91,12 @@ public:
|
||||
void set(const MainStatus flag, const bool value) {
|
||||
set(uint8_t(flag), value, main_status_);
|
||||
}
|
||||
void start_seek(const int drive) { main_status_ |= 1 << drive; }
|
||||
void set(const Status0 flag) { set(uint8_t(flag), true, status_[0]); }
|
||||
void set(const Status1 flag) { set(uint8_t(flag), true, status_[1]); }
|
||||
void set(const Status2 flag) { set(uint8_t(flag), true, status_[2]); }
|
||||
void start_seek(const int drive) { main_status_ |= 1 << drive; }
|
||||
void set(const Status0 flag) { set(uint8_t(flag), true, status_[0]); }
|
||||
void set(const Status1 flag) { set(uint8_t(flag), true, status_[1]); }
|
||||
void set(const Status2 flag) { set(uint8_t(flag), true, status_[2]); }
|
||||
|
||||
void set_status0(uint8_t value) { status_[0] = value; }
|
||||
void set_status0(const uint8_t value) { status_[0] = value; }
|
||||
|
||||
//
|
||||
// Flag getters.
|
||||
|
||||
+100
-70
@@ -11,9 +11,8 @@
|
||||
#include "Outputs/Log.hpp"
|
||||
|
||||
namespace {
|
||||
|
||||
Log::Logger<Log::Source::i8272> logger;
|
||||
|
||||
using Logger = Log::Logger<Log::Source::i8272>;
|
||||
constexpr int ms_to_cycles(const int x) { return x * 8000; }
|
||||
}
|
||||
|
||||
using namespace Intel::i8272;
|
||||
@@ -59,14 +58,18 @@ void i8272::run_for(const Cycles cycles) {
|
||||
drives_[c].step_rate_counter %= (8000 * step_rate_time_);
|
||||
while(steps--) {
|
||||
// Perform a step.
|
||||
int direction = (drives_[c].target_head_position < drives_[c].head_position) ? -1 : 1;
|
||||
logger.info().append("Target %d versus believed %d", drives_[c].target_head_position, drives_[c].head_position);
|
||||
const int direction = (drives_[c].target_head_position < drives_[c].head_position) ? -1 : 1;
|
||||
select_drive(c);
|
||||
get_drive().step(Storage::Disk::HeadPosition(direction));
|
||||
if(drives_[c].target_head_position >= 0) drives_[c].head_position += direction;
|
||||
|
||||
Logger::info().append(
|
||||
"Drive %d: seeking %d but seemingly at %d", c, drives_[c].target_head_position, drives_[c].head_position);
|
||||
|
||||
// Check for completion.
|
||||
if(seek_is_satisfied(c)) {
|
||||
Logger::info().append(
|
||||
"Drive %d: seek satisfied", c, drives_[c].target_head_position, drives_[c].head_position);
|
||||
drives_[c].phase = Drive::CompletedSeeking;
|
||||
drives_seeking_--;
|
||||
break;
|
||||
@@ -140,52 +143,73 @@ uint8_t i8272::read(const int address) {
|
||||
}
|
||||
}
|
||||
|
||||
#define BEGIN_SECTION() switch(resume_point_) { default:
|
||||
void i8272::posit_event(const int event_type) {
|
||||
|
||||
#define BEGIN_SECTION() switch(resume_point_) { default: case IdleResumePoint:
|
||||
#define END_SECTION() }
|
||||
|
||||
#define MS_TO_CYCLES(x) x * 8000
|
||||
#define WAIT_FOR_EVENT(mask) resume_point_ = __LINE__; interesting_event_mask_ = int(mask); return; case __LINE__:
|
||||
#define WAIT_FOR_TIME(ms) resume_point_ = __LINE__; interesting_event_mask_ = int(Event8272::Timer); delay_time_ = MS_TO_CYCLES(ms); is_sleeping_ = false; update_clocking_observer(); case __LINE__: if(delay_time_) return;
|
||||
#define WAIT_FOR_EVENT(mask) { \
|
||||
static constexpr int location = __COUNTER__ + 1; \
|
||||
resume_point_ = location; \
|
||||
interesting_event_mask_ = int(mask); \
|
||||
return; \
|
||||
case location: \
|
||||
(void)0; \
|
||||
}
|
||||
|
||||
#define WAIT_FOR_TIME(ms) { \
|
||||
static constexpr int location = __COUNTER__ + 1; \
|
||||
interesting_event_mask_ = int(Event8272::Timer); \
|
||||
delay_time_ = ms_to_cycles(ms); \
|
||||
is_sleeping_ = false; \
|
||||
update_clocking_observer(); \
|
||||
resume_point_ = location; \
|
||||
[[fallthrough]]; \
|
||||
case location: \
|
||||
if(delay_time_) return; \
|
||||
}
|
||||
|
||||
#define PASTE(x, y) x##y
|
||||
#define CONCAT(x, y) PASTE(x, y)
|
||||
#define LABEL(x, y) PASTE(x, y)
|
||||
|
||||
#define FIND_HEADER() \
|
||||
set_data_mode(DataMode::Scanning); \
|
||||
CONCAT(find_header, __LINE__): WAIT_FOR_EVENT(int(Event::Token) | int(Event::IndexHole)); \
|
||||
LABEL(find_header, __LINE__): WAIT_FOR_EVENT(int(Event::Token) | int(Event::IndexHole)); \
|
||||
if(event_type == int(Event::IndexHole)) { index_hole_limit_--; } \
|
||||
else if(get_latest_token().type == Token::ID) goto CONCAT(header_found, __LINE__); \
|
||||
else if(get_latest_token().type == Token::ID) goto LABEL(header_found, __LINE__); \
|
||||
\
|
||||
if(index_hole_limit_) goto CONCAT(find_header, __LINE__); \
|
||||
CONCAT(header_found, __LINE__): (void)0;\
|
||||
if(index_hole_limit_) goto LABEL(find_header, __LINE__); \
|
||||
LABEL(header_found, __LINE__): (void)0;\
|
||||
|
||||
#define FIND_DATA() \
|
||||
set_data_mode(DataMode::Scanning); \
|
||||
CONCAT(find_data, __LINE__): WAIT_FOR_EVENT(int(Event::Token) | int(Event::IndexHole)); \
|
||||
LABEL(find_data, __LINE__): WAIT_FOR_EVENT(int(Event::Token) | int(Event::IndexHole)); \
|
||||
if(event_type == int(Event::Token)) { \
|
||||
if(get_latest_token().type == Token::Byte || get_latest_token().type == Token::Sync) goto CONCAT(find_data, __LINE__); \
|
||||
if(get_latest_token().type == Token::Byte || get_latest_token().type == Token::Sync) \
|
||||
goto LABEL(find_data, __LINE__); \
|
||||
}
|
||||
|
||||
#define READ_HEADER() \
|
||||
distance_into_section_ = 0; \
|
||||
set_data_mode(DataMode::Reading); \
|
||||
CONCAT(read_header, __LINE__): WAIT_FOR_EVENT(Event::Token); \
|
||||
LABEL(read_header, __LINE__): WAIT_FOR_EVENT(Event::Token); \
|
||||
header_[distance_into_section_] = get_latest_token().byte_value; \
|
||||
distance_into_section_++; \
|
||||
if(distance_into_section_ < 6) goto CONCAT(read_header, __LINE__); \
|
||||
if(distance_into_section_ < 6) goto LABEL(read_header, __LINE__); \
|
||||
|
||||
#define SET_DRIVE_HEAD_MFM() \
|
||||
active_drive_ = command_.target().drive; \
|
||||
active_head_ = command_.target().head; \
|
||||
select_drive(active_drive_); \
|
||||
get_drive().set_head(active_head_); \
|
||||
set_is_double_density(command_.target().mfm);
|
||||
const auto SET_DRIVE_HEAD_MFM = [&] {
|
||||
active_drive_ = command_.target().drive;
|
||||
active_head_ = command_.target().head;
|
||||
select_drive(active_drive_);
|
||||
get_drive().set_head(active_head_);
|
||||
set_is_double_density(command_.target().mfm);
|
||||
};
|
||||
|
||||
#define WAIT_FOR_BYTES(n) \
|
||||
distance_into_section_ = 0; \
|
||||
CONCAT(wait_bytes, __LINE__): WAIT_FOR_EVENT(Event::Token); \
|
||||
LABEL(wait_bytes, __LINE__): WAIT_FOR_EVENT(Event::Token); \
|
||||
if(get_latest_token().type == Token::Byte) distance_into_section_++; \
|
||||
if(distance_into_section_ < (n)) goto CONCAT(wait_bytes, __LINE__);
|
||||
if(distance_into_section_ < (n)) goto LABEL(wait_bytes, __LINE__);
|
||||
|
||||
#define LOAD_HEAD() \
|
||||
if(!drives_[active_drive_].head_is_loaded[active_head_]) { \
|
||||
@@ -198,18 +222,20 @@ uint8_t i8272::read(const int address) {
|
||||
} \
|
||||
}
|
||||
|
||||
#define SCHEDULE_HEAD_UNLOAD() \
|
||||
if(drives_[active_drive_].head_is_loaded[active_head_]) {\
|
||||
if(drives_[active_drive_].head_unload_delay[active_head_] == 0) { \
|
||||
head_timers_running_++; \
|
||||
is_sleeping_ = false; \
|
||||
update_clocking_observer(); \
|
||||
} \
|
||||
drives_[active_drive_].head_unload_delay[active_head_] = MS_TO_CYCLES(head_unload_time_);\
|
||||
}
|
||||
const auto SCHEDULE_HEAD_UNLOAD = [&] {
|
||||
if(drives_[active_drive_].head_is_loaded[active_head_]) {
|
||||
if(drives_[active_drive_].head_unload_delay[active_head_] == 0) {
|
||||
++head_timers_running_;
|
||||
is_sleeping_ = false;
|
||||
update_clocking_observer();
|
||||
}
|
||||
drives_[active_drive_].head_unload_delay[active_head_] = ms_to_cycles(head_unload_time_);
|
||||
}
|
||||
};
|
||||
|
||||
void i8272::posit_event(const int event_type) {
|
||||
if(event_type == int(Event::IndexHole)) index_hole_count_++;
|
||||
if(event_type == int(Event::IndexHole)) {
|
||||
++index_hole_count_;
|
||||
}
|
||||
if(event_type == int(Event8272::NoLongerReady)) {
|
||||
status_.set(Status0::NotReady);
|
||||
goto abort;
|
||||
@@ -234,7 +260,7 @@ void i8272::posit_event(const int event_type) {
|
||||
wait_for_complete_command_sequence:
|
||||
status_.set(MainStatus::DataReady, true);
|
||||
status_.set(MainStatus::DataIsToProcessor, false);
|
||||
WAIT_FOR_EVENT(Event8272::CommandByte)
|
||||
WAIT_FOR_EVENT(Event8272::CommandByte);
|
||||
|
||||
if(!command_.has_command()) {
|
||||
goto wait_for_complete_command_sequence;
|
||||
@@ -258,8 +284,8 @@ void i8272::posit_event(const int event_type) {
|
||||
drives_seeking_--;
|
||||
}
|
||||
}
|
||||
// Establishes the drive and head being addressed, and whether in double density mode; populates the internal
|
||||
// cylinder, head, sector and size registers from the command stream.
|
||||
// Establishes the drive and head being addressed, and whether in double density mode; populates
|
||||
// the internal cylinder, head, sector and size registers from the command stream.
|
||||
is_executing_ = true;
|
||||
if(!dma_mode_) {
|
||||
status_.set(MainStatus::InNonDMAExecution, true);
|
||||
@@ -309,17 +335,17 @@ void i8272::posit_event(const int event_type) {
|
||||
// the index hole limit is breached or a sector is found with a cylinder, head, sector and size equal to the
|
||||
// values in the internal registers.
|
||||
index_hole_limit_ = 2;
|
||||
// logger.info().append("Seeking " << PADDEC(0) << cylinder_ << " " << head_ " " << sector_ << " " << size_);
|
||||
// Logger::info().append("Seeking " << PADDEC(0) << cylinder_ << " " << head_ " " << sector_ << " " << size_);
|
||||
find_next_sector:
|
||||
FIND_HEADER();
|
||||
if(!index_hole_limit_) {
|
||||
// Two index holes have passed wihout finding the header sought.
|
||||
// logger.info().append("Not found");
|
||||
// Logger::info().append("Not found");
|
||||
status_.set(Status1::NoData);
|
||||
goto abort;
|
||||
}
|
||||
index_hole_count_ = 0;
|
||||
// logger.info().append("Header");
|
||||
// Logger::info().append("Header");
|
||||
READ_HEADER();
|
||||
if(index_hole_count_) {
|
||||
// This implies an index hole was sighted within the header. Error out.
|
||||
@@ -330,26 +356,30 @@ void i8272::posit_event(const int event_type) {
|
||||
// This implies a CRC error in the header; mark as such but continue.
|
||||
status_.set(Status1::DataError);
|
||||
}
|
||||
// logger.info().append("Considering %02x %02x %02x %02x [%04x]", header_[0], header_[1], header_[2], header_[3], get_crc_generator().get_value());
|
||||
if(header_[0] != cylinder_ || header_[1] != head_ || header_[2] != sector_ || header_[3] != size_) goto find_next_sector;
|
||||
// Logger::info().append(
|
||||
// "Considering %02x %02x %02x %02x [%04x]",
|
||||
// header_[0], header_[1], header_[2], header_[3], get_crc_generator().get_value());
|
||||
if(header_[0] != cylinder_ || header_[1] != head_ || header_[2] != sector_ || header_[3] != size_) {
|
||||
goto find_next_sector;
|
||||
}
|
||||
|
||||
// Branch to whatever is supposed to happen next
|
||||
// logger.info().append("Proceeding");
|
||||
// Logger::info().append("Proceeding");
|
||||
switch(command_.command()) {
|
||||
default:
|
||||
case Command::ReadData:
|
||||
case Command::ReadDeletedData:
|
||||
goto read_data_found_header;
|
||||
|
||||
case Command::WriteData: // write data
|
||||
case Command::WriteDeletedData: // write deleted data
|
||||
case Command::WriteData:
|
||||
case Command::WriteDeletedData:
|
||||
goto write_data_found_header;
|
||||
}
|
||||
|
||||
|
||||
// Performs the read data or read deleted data command.
|
||||
read_data:
|
||||
// logger.info().append("Read [deleted] data [%02x %02x %02x %02x ... %02x %02x]",
|
||||
// Logger::info().append("Read [deleted] data [%02x %02x %02x %02x ... %02x %02x]",
|
||||
// command_[2],
|
||||
// command_[3],
|
||||
// command_[4],
|
||||
@@ -390,8 +420,8 @@ void i8272::posit_event(const int event_type) {
|
||||
distance_into_section_ = 0;
|
||||
set_data_mode(Reading);
|
||||
|
||||
// Waits for the next token, then supplies it to the CPU by: (i) setting data request and direction; and (ii) resetting
|
||||
// data request once the byte has been taken. Continues until all bytes have been read.
|
||||
// Waits for the next token, then supplies it to the CPU by: (i) setting data request and direction; and
|
||||
// (ii) resetting data request once the byte has been taken. Continues until all bytes have been read.
|
||||
//
|
||||
// TODO: consider DTL.
|
||||
read_data_get_byte:
|
||||
@@ -439,7 +469,7 @@ void i8272::posit_event(const int event_type) {
|
||||
goto post_st012chrn;
|
||||
|
||||
write_data:
|
||||
// logger.info().append("Write [deleted] data [%02x %02x %02x %02x ... %02x %02x]",
|
||||
// Logger::info().append("Write [deleted] data [%02x %02x %02x %02x ... %02x %02x]",
|
||||
// command_[2],
|
||||
// command_[3],
|
||||
// command_[4],
|
||||
@@ -480,7 +510,7 @@ void i8272::posit_event(const int event_type) {
|
||||
goto write_loop;
|
||||
}
|
||||
|
||||
logger.info().append("Wrote %d bytes", distance_into_section_);
|
||||
Logger::info().append("Wrote %d bytes", distance_into_section_);
|
||||
write_crc();
|
||||
expects_input_ = false;
|
||||
WAIT_FOR_EVENT(Event::DataWritten);
|
||||
@@ -496,10 +526,11 @@ void i8272::posit_event(const int event_type) {
|
||||
// Performs the read ID command.
|
||||
read_id:
|
||||
// Establishes the drive and head being addressed, and whether in double density mode.
|
||||
// logger.info().append("Read ID [%02x %02x]", command_[0], command_[1]);
|
||||
// Logger::info().append("Read ID [%02x %02x]", command_[0], command_[1]);
|
||||
|
||||
// Sets a maximum index hole limit of 2 then waits either until it finds a header mark or sees too many index holes.
|
||||
// If a header mark is found, reads in the following bytes that produce a header. Otherwise branches to data not found.
|
||||
// Sets a maximum index hole limit of 2 then waits either until it finds a header mark or sees too many
|
||||
// index holes. If a header mark is found, reads in the following bytes that produce a header. Otherwise
|
||||
// branches to data not found.
|
||||
index_hole_limit_ = 2;
|
||||
FIND_HEADER();
|
||||
if(!index_hole_limit_) {
|
||||
@@ -518,7 +549,7 @@ void i8272::posit_event(const int event_type) {
|
||||
|
||||
// Performs read track.
|
||||
read_track:
|
||||
// logger.info().append("Read track [%02x %02x %02x %02x]"
|
||||
// Logger::info().append("Read track [%02x %02x %02x %02x]"
|
||||
// command_[2],
|
||||
// command_[3],
|
||||
// command_[4],
|
||||
@@ -563,7 +594,7 @@ void i8272::posit_event(const int event_type) {
|
||||
|
||||
// Performs format [/write] track.
|
||||
format_track:
|
||||
logger.info().append("Format track");
|
||||
Logger::info().append("Format track");
|
||||
if(get_drive().is_read_only()) {
|
||||
status_.set(Status1::NotWriteable);
|
||||
goto abort;
|
||||
@@ -607,7 +638,7 @@ void i8272::posit_event(const int event_type) {
|
||||
break;
|
||||
}
|
||||
|
||||
logger.info().append("W: %02x %02x %02x %02x, %04x",
|
||||
Logger::info().append("W: %02x %02x %02x %02x, %04x",
|
||||
header_[0], header_[1], header_[2], header_[3], get_crc_generator().get_value());
|
||||
write_crc();
|
||||
|
||||
@@ -640,15 +671,15 @@ void i8272::posit_event(const int event_type) {
|
||||
goto post_st012chrn;
|
||||
|
||||
scan_low:
|
||||
logger.error().append("Scan low unimplemented!!");
|
||||
Logger::error().append("Scan low unimplemented!!");
|
||||
goto wait_for_command;
|
||||
|
||||
scan_low_or_equal:
|
||||
logger.error().append("Scan low or equal unimplemented!!");
|
||||
Logger::error().append("Scan low or equal unimplemented!!");
|
||||
goto wait_for_command;
|
||||
|
||||
scan_high_or_equal:
|
||||
logger.error().append("Scan high or equal unimplemented!!");
|
||||
Logger::error().append("Scan high or equal unimplemented!!");
|
||||
goto wait_for_command;
|
||||
|
||||
// Performs both recalibrate and seek commands. These commands occur asynchronously, so the actual work
|
||||
@@ -679,11 +710,11 @@ void i8272::posit_event(const int event_type) {
|
||||
// up in run_for understands to mean 'keep going until track 0 is active').
|
||||
if(command_.command() != Command::Recalibrate) {
|
||||
drives_[drive].target_head_position = command_.seek_target();
|
||||
logger.info().append("Seek to %d", command_.seek_target());
|
||||
Logger::info().append("Seek to %d", command_.seek_target());
|
||||
} else {
|
||||
drives_[drive].target_head_position = -1;
|
||||
drives_[drive].head_position = 0;
|
||||
logger.info().append("Recalibrate");
|
||||
Logger::info().append("Recalibrate");
|
||||
}
|
||||
|
||||
// Check whether any steps are even needed; if not then mark as completed already.
|
||||
@@ -696,7 +727,6 @@ void i8272::posit_event(const int event_type) {
|
||||
|
||||
// Performs sense interrupt status.
|
||||
sense_interrupt_status:
|
||||
logger.info().append("Sense interrupt status");
|
||||
{
|
||||
// Find the first drive that is in the CompletedSeeking state.
|
||||
int found_drive = -1;
|
||||
@@ -711,12 +741,12 @@ void i8272::posit_event(const int event_type) {
|
||||
if(found_drive != -1) {
|
||||
drives_[found_drive].phase = Drive::NotSeeking;
|
||||
status_.set_status0(uint8_t(found_drive | uint8_t(Status0::SeekEnded)));
|
||||
// status_.end_sense_interrupt_status(found_drive, 0);
|
||||
// status_.set(Status0::SeekEnded);
|
||||
|
||||
result_stack_ = { drives_[found_drive].head_position, status_[0]};
|
||||
Logger::info().append("Sense interrupt status: returning %02x %02x", result_stack_[0], result_stack_[1]);
|
||||
} else {
|
||||
result_stack_ = { 0x80 };
|
||||
Logger::info().append("Sense interrupt status: returning %02x", result_stack_[0]);
|
||||
}
|
||||
}
|
||||
goto post_result;
|
||||
@@ -724,7 +754,7 @@ void i8272::posit_event(const int event_type) {
|
||||
// Performs specify.
|
||||
specify:
|
||||
// Just store the values, and terminate the command.
|
||||
logger.info().append("Specify");
|
||||
Logger::info().append("Specify");
|
||||
step_rate_time_ = command_.specify_specs().step_rate_time;
|
||||
head_unload_time_ = command_.specify_specs().head_unload_time;
|
||||
head_load_time_ = command_.specify_specs().head_load_time;
|
||||
@@ -735,7 +765,7 @@ void i8272::posit_event(const int event_type) {
|
||||
goto wait_for_command;
|
||||
|
||||
sense_drive_status:
|
||||
logger.info().append("Sense drive status");
|
||||
Logger::info().append("Sense drive status");
|
||||
{
|
||||
int drive = command_.target().drive;
|
||||
select_drive(drive);
|
||||
@@ -775,7 +805,7 @@ void i8272::posit_event(const int event_type) {
|
||||
// last thing in it will be returned first.
|
||||
post_result:
|
||||
// {
|
||||
// auto line = logger.info();
|
||||
// auto line = Logger::info();
|
||||
// line.append("Result to %02x, main %02x", command_[0] & 0x1f, main_status_);
|
||||
// for(std::size_t c = 0; c < result_stack_.size(); c++) {
|
||||
// line.append(" %02x", result_stack_[result_stack_.size() - 1 - c]);
|
||||
|
||||
@@ -72,9 +72,11 @@ private:
|
||||
};
|
||||
void posit_event(int type) final;
|
||||
int interesting_event_mask_ = int(Event8272::CommandByte);
|
||||
int resume_point_ = 0;
|
||||
bool is_access_command_ = false;
|
||||
|
||||
static constexpr int IdleResumePoint = 0;
|
||||
int resume_point_ = 0;
|
||||
|
||||
// The counter used for ::Timer events.
|
||||
Cycles::IntType delay_time_ = 0;
|
||||
|
||||
|
||||
+19
-21
@@ -13,9 +13,7 @@
|
||||
using namespace Zilog::SCC;
|
||||
|
||||
namespace {
|
||||
|
||||
Log::Logger<Log::Source::SCC> log;
|
||||
|
||||
using Logger = Log::Logger<Log::Source::SCC>;
|
||||
}
|
||||
|
||||
void z8530::reset() {
|
||||
@@ -54,7 +52,7 @@ std::uint8_t z8530::read(const int address) {
|
||||
|
||||
case 2: // Handled non-symmetrically between channels.
|
||||
if(address & 1) {
|
||||
log.error().append("Unimplemented: register 2 status bits");
|
||||
Logger::error().append("Unimplemented: register 2 status bits");
|
||||
} else {
|
||||
result = interrupt_vector_;
|
||||
|
||||
@@ -111,11 +109,11 @@ void z8530::write(const int address, const std::uint8_t value) {
|
||||
case 2: // Interrupt vector register; used only by Channel B.
|
||||
// So there's only one of these.
|
||||
interrupt_vector_ = value;
|
||||
log.info().append("Interrupt vector set to %d", value);
|
||||
Logger::info().append("Interrupt vector set to %d", value);
|
||||
break;
|
||||
|
||||
case 9: // Master interrupt and reset register; there is also only one of these.
|
||||
log.info().append("Master interrupt and reset register: %02x", value);
|
||||
Logger::info().append("Master interrupt and reset register: %02x", value);
|
||||
master_interrupt_control_ = value;
|
||||
break;
|
||||
}
|
||||
@@ -152,7 +150,7 @@ uint8_t z8530::Channel::read(const bool data, const uint8_t pointer) {
|
||||
if(data) {
|
||||
return data_;
|
||||
} else {
|
||||
log.info().append("Control read from register %d", pointer);
|
||||
Logger::info().append("Control read from register %d", pointer);
|
||||
// Otherwise, this is a control read...
|
||||
switch(pointer) {
|
||||
default:
|
||||
@@ -237,10 +235,10 @@ void z8530::Channel::write(const bool data, const uint8_t pointer, const uint8_t
|
||||
data_ = value;
|
||||
return;
|
||||
} else {
|
||||
log.info().append("Control write: %02x to register %d", value, pointer);
|
||||
Logger::info().append("Control write: %02x to register %d", value, pointer);
|
||||
switch(pointer) {
|
||||
default:
|
||||
log.info().append("Unrecognised control write: %02x to register %d", value, pointer);
|
||||
Logger::info().append("Unrecognised control write: %02x to register %d", value, pointer);
|
||||
break;
|
||||
|
||||
case 0x0: // Write register 0 — CRC reset and other functions.
|
||||
@@ -248,13 +246,13 @@ void z8530::Channel::write(const bool data, const uint8_t pointer, const uint8_t
|
||||
switch(value >> 6) {
|
||||
default: /* Do nothing. */ break;
|
||||
case 1:
|
||||
log.error().append("TODO: reset Rx CRC checker.");
|
||||
Logger::error().append("TODO: reset Rx CRC checker.");
|
||||
break;
|
||||
case 2:
|
||||
log.error().append("TODO: reset Tx CRC checker.");
|
||||
Logger::error().append("TODO: reset Tx CRC checker.");
|
||||
break;
|
||||
case 3:
|
||||
log.error().append("TODO: reset Tx underrun/EOM latch.");
|
||||
Logger::error().append("TODO: reset Tx underrun/EOM latch.");
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -262,24 +260,24 @@ void z8530::Channel::write(const bool data, const uint8_t pointer, const uint8_t
|
||||
switch((value >> 3)&7) {
|
||||
default: /* Do nothing. */ break;
|
||||
case 2:
|
||||
// log.info().append("reset ext/status interrupts.");
|
||||
// Logger::info().append("reset ext/status interrupts.");
|
||||
external_status_interrupt_ = false;
|
||||
external_interrupt_status_ = 0;
|
||||
break;
|
||||
case 3:
|
||||
log.error().append("TODO: send abort (SDLC).");
|
||||
Logger::error().append("TODO: send abort (SDLC).");
|
||||
break;
|
||||
case 4:
|
||||
log.error().append("TODO: enable interrupt on next Rx character.");
|
||||
Logger::error().append("TODO: enable interrupt on next Rx character.");
|
||||
break;
|
||||
case 5:
|
||||
log.error().append("TODO: reset Tx interrupt pending.");
|
||||
Logger::error().append("TODO: reset Tx interrupt pending.");
|
||||
break;
|
||||
case 6:
|
||||
log.error().append("TODO: reset error.");
|
||||
Logger::error().append("TODO: reset error.");
|
||||
break;
|
||||
case 7:
|
||||
log.error().append("TODO: reset highest IUS.");
|
||||
Logger::error().append("TODO: reset highest IUS.");
|
||||
break;
|
||||
}
|
||||
break;
|
||||
@@ -304,7 +302,7 @@ void z8530::Channel::write(const bool data, const uint8_t pointer, const uint8_t
|
||||
b1 = 1 => transmit buffer empty interrupt is enabled; 0 => it isn't.
|
||||
b0 = 1 => external interrupt is enabled; 0 => it isn't.
|
||||
*/
|
||||
log.info().append("Interrupt mask: %02x", value);
|
||||
Logger::info().append("Interrupt mask: %02x", value);
|
||||
break;
|
||||
|
||||
case 0x2: // Write register 2 - interrupt vector.
|
||||
@@ -319,7 +317,7 @@ void z8530::Channel::write(const bool data, const uint8_t pointer, const uint8_t
|
||||
case 2: receive_bit_count = 6; break;
|
||||
case 3: receive_bit_count = 8; break;
|
||||
}
|
||||
log.info().append("Receive bit count: %d", receive_bit_count);
|
||||
Logger::info().append("Receive bit count: %d", receive_bit_count);
|
||||
|
||||
/*
|
||||
b7,b6:
|
||||
@@ -425,6 +423,6 @@ void z8530::update_delegate() {
|
||||
const bool interrupt_line = get_interrupt_line();
|
||||
if(interrupt_line != previous_interrupt_line_) {
|
||||
previous_interrupt_line_ = interrupt_line;
|
||||
if(delegate_) delegate_->did_change_interrupt_status(this, interrupt_line);
|
||||
if(delegate_) delegate_->did_change_interrupt_status(*this, interrupt_line);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,7 +43,7 @@ public:
|
||||
/*!
|
||||
Communicates that @c scc now has the interrupt line status @c new_status.
|
||||
*/
|
||||
virtual void did_change_interrupt_status(z8530 *, bool new_status) = 0;
|
||||
virtual void did_change_interrupt_status(z8530 &, bool new_status) = 0;
|
||||
};
|
||||
|
||||
/*!
|
||||
@@ -54,7 +54,7 @@ public:
|
||||
void set_delegate(Delegate *const delegate) {
|
||||
if(delegate_ == delegate) return;
|
||||
delegate_ = delegate;
|
||||
delegate_->did_change_interrupt_status(this, get_interrupt_line());
|
||||
delegate_->did_change_interrupt_status(*this, get_interrupt_line());
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
@@ -23,7 +23,7 @@ namespace {
|
||||
constexpr unsigned int CRTCyclesPerLine = 1365;
|
||||
constexpr unsigned int CRTCyclesDivider = 4;
|
||||
|
||||
Log::Logger<Log::Source::TMS9918> logger;
|
||||
using Logger = Log::Logger<Log::Source::TMS9918>;
|
||||
|
||||
}
|
||||
|
||||
@@ -74,9 +74,9 @@ TMS9918<personality>::TMS9918() {
|
||||
this->crt_.set_display_type(Outputs::Display::DisplayType::RGB);
|
||||
|
||||
if constexpr (is_yamaha_vdp(personality)) {
|
||||
this->crt_.set_visible_area(Outputs::Display::Rect(0.07f, 0.065f, 0.875f, 0.875f));
|
||||
this->crt_.set_fixed_framing(Outputs::Display::Rect(0.07f, 0.065f, 0.875f, 0.875f));
|
||||
} else {
|
||||
this->crt_.set_visible_area(Outputs::Display::Rect(0.07f, 0.0375f, 0.875f, 0.875f));
|
||||
this->crt_.set_fixed_framing(Outputs::Display::Rect(0.07f, 0.0375f, 0.875f, 0.875f));
|
||||
}
|
||||
|
||||
// The TMS remains in-phase with the NTSC colour clock; this is an empirical measurement
|
||||
@@ -226,7 +226,7 @@ void TMS9918<personality>::run_for(const HalfCycles cycles) {
|
||||
// TODO: where did this magic constant come from? https://www.smspower.org/forums/17970-RoadRashHow#111000 mentioned in passing
|
||||
// that "the vertical scroll register is latched at the start of the active display" and this is two clocks before that, so it's
|
||||
// not uncompelling. I can just no longer find my source.
|
||||
constexpr auto latch_time = LineLayout<personality>::EndOfLeftBorder - 2;
|
||||
static constexpr auto latch_time = LineLayout<personality>::EndOfLeftBorder - 2;
|
||||
static_assert(latch_time > 0);
|
||||
if(this->fetch_pointer_.column < latch_time && end_column >= latch_time) {
|
||||
if(!this->fetch_pointer_.row) {
|
||||
@@ -683,13 +683,7 @@ void Base<personality>::output_border(int cycles, [[maybe_unused]] const uint32_
|
||||
return;
|
||||
}
|
||||
|
||||
// If the border colour is 0, that can be communicated
|
||||
// more efficiently as an explicit blank.
|
||||
if(border_colour) {
|
||||
crt_.output_level<uint32_t>(cycles, border_colour);
|
||||
} else {
|
||||
crt_.output_blank(cycles);
|
||||
}
|
||||
crt_.output_level<uint32_t>(cycles, border_colour);
|
||||
}
|
||||
|
||||
// MARK: - External interface.
|
||||
@@ -843,7 +837,7 @@ void Base<personality>::commit_register(int reg, const uint8_t value) {
|
||||
Storage<personality>::solid_background_ = value & 0x20;
|
||||
Storage<personality>::sprites_enabled_ = !(value & 0x02);
|
||||
if(value & 0x01) {
|
||||
logger.error().append("TODO: Yamaha greyscale");
|
||||
Logger::error().append("TODO: Yamaha greyscale");
|
||||
}
|
||||
// b7: "1 = input on colour bus, enable mouse; 1 = output on colour bus, disable mouse" [documentation clearly in error]
|
||||
// b6: 1 = enable light pen
|
||||
@@ -860,7 +854,7 @@ void Base<personality>::commit_register(int reg, const uint8_t value) {
|
||||
// TODO: on the Yamaha, at least, tie this interrupt overtly to vertical state.
|
||||
|
||||
if(value & 0x08) {
|
||||
logger.error().append("TODO: Yamaha interlace mode");
|
||||
Logger::error().append("TODO: Yamaha interlace mode");
|
||||
}
|
||||
|
||||
// b7: 1 = 212 lines of pixels; 0 = 192
|
||||
@@ -924,7 +918,7 @@ void Base<personality>::commit_register(int reg, const uint8_t value) {
|
||||
case 20:
|
||||
case 21:
|
||||
case 22:
|
||||
// logger.error().append("TODO: Yamaha colour burst selection; %02x", value);
|
||||
// Logger::error().append("TODO: Yamaha colour burst selection; %02x", value);
|
||||
// Documentation is "fill with 0s for no colour burst; magic pattern for colour burst"
|
||||
break;
|
||||
|
||||
@@ -1010,7 +1004,7 @@ void Base<personality>::commit_register(int reg, const uint8_t value) {
|
||||
// Kill the command immediately if it's done in zero operations
|
||||
// (e.g. a line of length 0).
|
||||
if(!Storage<personality>::command_ && (value >> 4)) {
|
||||
logger.error().append("TODO: Yamaha command %02x", value);
|
||||
Logger::error().append("TODO: Yamaha command %02x", value);
|
||||
}
|
||||
|
||||
// Seed timing information if a command was found.
|
||||
@@ -1211,7 +1205,7 @@ uint8_t TMS9918<personality>::read(const int address) {
|
||||
template <Personality personality>
|
||||
int Base<personality>::fetch_line() const {
|
||||
// This is the proper Master System value; TODO: what's correct for Yamaha, etc?
|
||||
constexpr int row_change_position = 31;
|
||||
static constexpr int row_change_position = 31;
|
||||
|
||||
return
|
||||
(this->fetch_pointer_.column < row_change_position)
|
||||
|
||||
@@ -87,8 +87,8 @@ template <Personality personality> struct Base: public Storage<personality> {
|
||||
/// applicable @c memory_mask.
|
||||
template <int shift, int length = 8> void install_field(AddressT &target, const uint8_t source) {
|
||||
static_assert(length > 0 && length <= 8);
|
||||
constexpr auto source_mask = (1 << length) - 1;
|
||||
constexpr auto mask = AddressT(~(source_mask << shift));
|
||||
static constexpr auto source_mask = (1 << length) - 1;
|
||||
static constexpr auto mask = AddressT(~(source_mask << shift));
|
||||
target = (
|
||||
(target & mask) |
|
||||
AddressT((source & source_mask) << shift)
|
||||
@@ -545,8 +545,8 @@ template <Personality personality> struct Base: public Storage<personality> {
|
||||
if(Storage<personality>::cram_is_selected_) {
|
||||
// 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};
|
||||
static constexpr uint8_t rg_scale[] = {0, 85, 170, 255};
|
||||
static constexpr uint8_t b_scale[] = {0, 104, 170, 255};
|
||||
Storage<personality>::colour_ram_[address & 0x1f] = palette_pack(
|
||||
rg_scale[(read_ahead_buffer_ >> 0) & 3],
|
||||
rg_scale[(read_ahead_buffer_ >> 2) & 3],
|
||||
|
||||
@@ -93,8 +93,8 @@ void Base<personality>::draw_sprites(
|
||||
assert(!buffer.is_filling);
|
||||
}
|
||||
|
||||
constexpr uint32_t sprite_colour_selection_masks[2] = {0x00000000, 0xffffffff};
|
||||
constexpr int colour_masks[16] = {0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1};
|
||||
static constexpr uint32_t sprite_colour_selection_masks[2] = {0x00000000, 0xffffffff};
|
||||
static constexpr int colour_masks[16] = {0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1};
|
||||
const int sprite_width = sprites_16x16_ ? 16 : 8;
|
||||
const int shifter_target = sprite_width << 1;
|
||||
const int pixel_width = sprites_magnified_ ? sprite_width << 1 : sprite_width;
|
||||
@@ -526,7 +526,7 @@ void Base<personality>::draw_yamaha(const uint8_t y, int start, int end) {
|
||||
}
|
||||
}
|
||||
|
||||
constexpr std::array<uint32_t, 16> graphics7_sprite_palette = {
|
||||
static constexpr std::array<uint32_t, 16> graphics7_sprite_palette = {
|
||||
palette_pack(0b00000000, 0b00000000, 0b00000000), palette_pack(0b00000000, 0b00000000, 0b01001001),
|
||||
palette_pack(0b00000000, 0b01101101, 0b00000000), palette_pack(0b00000000, 0b01101101, 0b01001001),
|
||||
palette_pack(0b01101101, 0b00000000, 0b00000000), palette_pack(0b01101101, 0b00000000, 0b01001001),
|
||||
|
||||
@@ -407,8 +407,8 @@ struct TextSequencer {
|
||||
return;
|
||||
} else {
|
||||
// For the 120 slots in between follow a three-step pattern of:
|
||||
constexpr int offset = cycle - 30;
|
||||
constexpr auto column = AddressT(offset / 3);
|
||||
static constexpr int offset = cycle - 30;
|
||||
static constexpr auto column = AddressT(offset / 3);
|
||||
switch(offset % 3) {
|
||||
case 0: // (1) fetch tile name.
|
||||
fetcher.fetch_name(column);
|
||||
@@ -457,16 +457,16 @@ struct CharacterSequencer {
|
||||
|
||||
// Body of line: tiles themselves, plus some additional potential sprites.
|
||||
if(cycle >= 27 && cycle < 155) {
|
||||
constexpr int offset = cycle - 27;
|
||||
constexpr int block = offset >> 2;
|
||||
constexpr int sub_block = offset & 3;
|
||||
static constexpr int offset = cycle - 27;
|
||||
static constexpr int block = offset >> 2;
|
||||
static constexpr int sub_block = offset & 3;
|
||||
switch(sub_block) {
|
||||
case 0: character_fetcher.fetch_name(block); break;
|
||||
case 1:
|
||||
if(!(block & 3)) {
|
||||
character_fetcher.base->do_external_slot(to_internal<personality, Clock::TMSMemoryWindow, Clock::FromStartOfSync>(cycle));
|
||||
} else {
|
||||
constexpr int sprite = 8 + ((block >> 2) * 3) + ((block & 3) - 1);
|
||||
static constexpr int sprite = 8 + ((block >> 2) * 3) + ((block & 3) - 1);
|
||||
sprite_fetcher.fetch_y(sprite);
|
||||
}
|
||||
break;
|
||||
@@ -549,9 +549,9 @@ struct SMSSequencer {
|
||||
}
|
||||
|
||||
if(cycle >= 25 && cycle < 153) {
|
||||
constexpr int offset = cycle - 25;
|
||||
constexpr int block = offset >> 2;
|
||||
constexpr int sub_block = offset & 3;
|
||||
static constexpr int offset = cycle - 25;
|
||||
static constexpr int block = offset >> 2;
|
||||
static constexpr int sub_block = offset & 3;
|
||||
|
||||
switch(sub_block) {
|
||||
default: break;
|
||||
@@ -561,7 +561,7 @@ struct SMSSequencer {
|
||||
if(!(block & 3)) {
|
||||
fetcher.base->do_external_slot(to_internal<personality, Clock::TMSMemoryWindow, Clock::FromStartOfSync>(cycle));
|
||||
} else {
|
||||
constexpr int sprite = (8 + ((block >> 2) * 3) + ((block & 3) - 1)) << 1;
|
||||
static constexpr int sprite = (8 + ((block >> 2) * 3) + ((block & 3) - 1)) << 1;
|
||||
fetcher.posit_sprite(sprite);
|
||||
fetcher.posit_sprite(sprite+1);
|
||||
}
|
||||
|
||||
@@ -29,49 +29,49 @@ template <Personality personality, typename Enable = void> struct LineLayout;
|
||||
// * text mode on all VDPs adjusts border width.
|
||||
|
||||
template <Personality personality> struct LineLayout<personality, std::enable_if_t<is_classic_vdp(personality)>> {
|
||||
constexpr static int StartOfSync = 0;
|
||||
constexpr static int EndOfSync = 26;
|
||||
constexpr static int StartOfColourBurst = 29;
|
||||
constexpr static int EndOfColourBurst = 43;
|
||||
constexpr static int EndOfLeftErase = 50;
|
||||
constexpr static int EndOfLeftBorder = 63;
|
||||
constexpr static int EndOfPixels = 319;
|
||||
constexpr static int EndOfRightBorder = 334;
|
||||
static constexpr int StartOfSync = 0;
|
||||
static constexpr int EndOfSync = 26;
|
||||
static constexpr int StartOfColourBurst = 29;
|
||||
static constexpr int EndOfColourBurst = 43;
|
||||
static constexpr int EndOfLeftErase = 50;
|
||||
static constexpr int EndOfLeftBorder = 63;
|
||||
static constexpr int EndOfPixels = 319;
|
||||
static constexpr int EndOfRightBorder = 334;
|
||||
|
||||
constexpr static int CyclesPerLine = 342;
|
||||
static constexpr int CyclesPerLine = 342;
|
||||
|
||||
constexpr static int TextModeEndOfLeftBorder = 69;
|
||||
constexpr static int TextModeEndOfPixels = 309;
|
||||
static constexpr int TextModeEndOfLeftBorder = 69;
|
||||
static constexpr int TextModeEndOfPixels = 309;
|
||||
|
||||
constexpr static int ModeLatchCycle = 36; // Just a guess; correlates with the known 144 for the Yamaha VDPs,
|
||||
static constexpr int ModeLatchCycle = 36; // Just a guess; correlates with the known 144 for the Yamaha VDPs,
|
||||
// and falls into the collection gap between the final sprite
|
||||
// graphics and the initial tiles or pixels.
|
||||
|
||||
/// The number of internal cycles that must elapse between a request to read or write and
|
||||
/// it becoming a candidate for action.
|
||||
constexpr static int VRAMAccessDelay = 6;
|
||||
static constexpr int VRAMAccessDelay = 6;
|
||||
};
|
||||
|
||||
template <Personality personality> struct LineLayout<personality, std::enable_if_t<is_yamaha_vdp(personality)>> {
|
||||
constexpr static int StartOfSync = 0;
|
||||
constexpr static int EndOfSync = 100;
|
||||
constexpr static int StartOfColourBurst = 113;
|
||||
constexpr static int EndOfColourBurst = 167;
|
||||
constexpr static int EndOfLeftErase = 202;
|
||||
constexpr static int EndOfLeftBorder = 258;
|
||||
constexpr static int EndOfPixels = 1282;
|
||||
constexpr static int EndOfRightBorder = 1341;
|
||||
static constexpr int StartOfSync = 0;
|
||||
static constexpr int EndOfSync = 100;
|
||||
static constexpr int StartOfColourBurst = 113;
|
||||
static constexpr int EndOfColourBurst = 167;
|
||||
static constexpr int EndOfLeftErase = 202;
|
||||
static constexpr int EndOfLeftBorder = 258;
|
||||
static constexpr int EndOfPixels = 1282;
|
||||
static constexpr int EndOfRightBorder = 1341;
|
||||
|
||||
constexpr static int CyclesPerLine = 1368;
|
||||
static constexpr int CyclesPerLine = 1368;
|
||||
|
||||
constexpr static int TextModeEndOfLeftBorder = 294;
|
||||
constexpr static int TextModeEndOfPixels = 1254;
|
||||
static constexpr int TextModeEndOfLeftBorder = 294;
|
||||
static constexpr int TextModeEndOfPixels = 1254;
|
||||
|
||||
constexpr static int ModeLatchCycle = 144;
|
||||
static constexpr int ModeLatchCycle = 144;
|
||||
|
||||
/// The number of internal cycles that must elapse between a request to read or write and
|
||||
/// it becoming a candidate for action.
|
||||
constexpr static int VRAMAccessDelay = 16;
|
||||
static constexpr int VRAMAccessDelay = 16;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -202,6 +202,7 @@ protected:
|
||||
} else {
|
||||
return Event::Type::External;
|
||||
}
|
||||
break;
|
||||
case 6:
|
||||
if((block & 3) != 3) {
|
||||
return Event::Type::External;
|
||||
@@ -280,11 +281,11 @@ protected:
|
||||
const int block = offset / 32;
|
||||
const int sub_block = offset & 31;
|
||||
switch(sub_block) {
|
||||
case 0: if(block > 0) return Event(Event::Type::Name, uint8_t(block - 1));
|
||||
case 6: if((sub_block & 3) != 3) return Event::Type::External;
|
||||
case 12: if(block < 32) return Event(Event::Type::SpriteY, uint8_t(block));
|
||||
case 18: if(block > 0) return Event(Event::Type::Pattern, uint8_t(block - 1));
|
||||
case 24: if(block > 0) return Event(Event::Type::Colour, uint8_t(block - 1));
|
||||
case 0: if(block > 0) return Event(Event::Type::Name, uint8_t(block - 1)); break;
|
||||
case 6: if((sub_block & 3) != 3) return Event::Type::External; break;
|
||||
case 12: if(block < 32) return Event(Event::Type::SpriteY, uint8_t(block)); break;
|
||||
case 18: if(block > 0) return Event(Event::Type::Pattern, uint8_t(block - 1)); break;
|
||||
case 24: if(block > 0) return Event(Event::Type::Colour, uint8_t(block - 1)); break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -18,8 +18,8 @@ struct Vector {
|
||||
int v[2]{};
|
||||
|
||||
template <int offset, bool high> void set(const uint8_t value) {
|
||||
constexpr uint8_t mask = high ? (offset ? 0x3 : 0x1) : 0xff;
|
||||
constexpr int shift = high ? 8 : 0;
|
||||
static constexpr uint8_t mask = high ? (offset ? 0x3 : 0x1) : 0xff;
|
||||
static constexpr int shift = high ? 8 : 0;
|
||||
v[offset] = (v[offset] & ~(mask << shift)) | ((value & mask) << shift);
|
||||
}
|
||||
|
||||
|
||||
@@ -91,7 +91,7 @@ void AY38910SampleSource<is_stereo>::set_sample_volume_range(const std::int16_t
|
||||
// from the YM's datasheet, showing a clear power curve, and fitting that to observed
|
||||
// values reported elsewhere.
|
||||
const float max_volume = float(range) / 3.0f; // As there are three channels.
|
||||
constexpr float root_two = 1.414213562373095f;
|
||||
static constexpr float root_two = 1.414213562373095f;
|
||||
for(int v = 0; v < 32; v++) {
|
||||
volumes_[v] = int(max_volume / powf(root_two, float(v ^ 0x1f) / 3.18f));
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
|
||||
#include "DiskII.hpp"
|
||||
|
||||
#include <bit>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
|
||||
@@ -62,11 +63,7 @@ void DiskII::set_control(const Control control, const bool on) {
|
||||
if(stepper_mask_&4) direction += (((stepper_position_ - 4) + 4)&7) - 4;
|
||||
if(stepper_mask_&8) direction += (((stepper_position_ - 6) + 4)&7) - 4;
|
||||
|
||||
// TODO: when adopting C++20, replace with std::popcount.
|
||||
int bits_set = stepper_mask_;
|
||||
bits_set = (bits_set & 0x5) + ((bits_set >> 1) & 0x5);
|
||||
bits_set = (bits_set & 0x3) + ((bits_set >> 2) & 0x3);
|
||||
|
||||
const int bits_set = std::popcount(uint8_t(stepper_mask_));
|
||||
direction /= bits_set;
|
||||
|
||||
// Compare to the stepper position to decide whether that pulls in the current cog notch,
|
||||
|
||||
@@ -8,6 +8,8 @@
|
||||
|
||||
#include "DiskIIDrive.hpp"
|
||||
|
||||
#include <bit>
|
||||
|
||||
using namespace Apple::Disk;
|
||||
|
||||
DiskIIDrive::DiskIIDrive(const int input_clock_rate) :
|
||||
@@ -29,7 +31,7 @@ void DiskIIDrive::set_control_lines(const int lines) {
|
||||
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);
|
||||
const int bits_set = std::popcount(uint8_t(lines));
|
||||
direction /= bits_set;
|
||||
|
||||
// Compare to the stepper position to decide whether that pulls in the
|
||||
|
||||
+19
-17
@@ -24,7 +24,7 @@ namespace {
|
||||
constexpr int SEL = 1 << 8; /* This is an additional input, not available on a Disk II, with a
|
||||
confusingly-similar name to SELECT but a distinct purpose. */
|
||||
|
||||
Log::Logger<Log::Source::IWM> logger;
|
||||
using Logger = Log::Logger<Log::Source::IWM>;
|
||||
}
|
||||
|
||||
IWM::IWM(int clock_rate) :
|
||||
@@ -53,7 +53,7 @@ uint8_t IWM::read(const int address) {
|
||||
|
||||
switch(state_ & (Q6 | Q7 | ENABLE)) {
|
||||
default:
|
||||
logger.info().append("Invalid read\n");
|
||||
Logger::info().append("Invalid read\n");
|
||||
return 0xff;
|
||||
|
||||
case 0:
|
||||
@@ -62,9 +62,11 @@ uint8_t IWM::read(const int address) {
|
||||
|
||||
if(data_register_ & 0x80) {
|
||||
data_register_ = 0;
|
||||
// logger.info().append("Reading data: %02x", result);
|
||||
// Logger::info().append("Reading data: %02x", result);
|
||||
} else {
|
||||
// Logger::info().append("Spurious read?");
|
||||
}
|
||||
// logger.info().append("Reading data register: %02x", result);
|
||||
// Logger::info().append("Reading data register: %02x", result);
|
||||
|
||||
return result;
|
||||
}
|
||||
@@ -97,7 +99,7 @@ uint8_t IWM::read(const 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).
|
||||
*/
|
||||
// logger.info().append("Reading write handshake: %02x", 0x3f | write_handshake_);
|
||||
// Logger::info().append("Reading write handshake: %02x", 0x3f | write_handshake_);
|
||||
return 0x3f | write_handshake_;
|
||||
}
|
||||
|
||||
@@ -128,9 +130,9 @@ void IWM::write(const int address, const uint8_t input) {
|
||||
|
||||
// TEMPORARY. To test for the unimplemented mode.
|
||||
if(input&0x2) {
|
||||
logger.info().append("Switched to asynchronous mode");
|
||||
Logger::info().append("Switched to asynchronous mode");
|
||||
} else {
|
||||
logger.info().append("Switched to synchronous mode");
|
||||
Logger::info().append("Switched to synchronous mode");
|
||||
}
|
||||
|
||||
switch(mode_ & 0x18) {
|
||||
@@ -139,8 +141,8 @@ void IWM::write(const int address, const uint8_t input) {
|
||||
case 0x10: bit_length_ = Cycles(32); break; // slow mode, 8Mhz
|
||||
case 0x18: bit_length_ = Cycles(16); break; // fast mode, 8Mhz
|
||||
}
|
||||
logger.info().append("Mode is now %02x", mode_);
|
||||
logger.info().append("New bit length is %d", bit_length_.as<int>());
|
||||
Logger::info().append("Mode is now %02x", mode_);
|
||||
Logger::info().append("New bit length is %d", bit_length_.as<int>());
|
||||
break;
|
||||
|
||||
case Q7|Q6|ENABLE: // Write data register.
|
||||
@@ -254,7 +256,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) {
|
||||
// logger.info().append("Shifting 0 at %d ", cycles_since_shift_.as<int>());
|
||||
// Logger::info().append("Shifting 0 at %d ", cycles_since_shift_.as<int>());
|
||||
propose_shift(0);
|
||||
}
|
||||
}
|
||||
@@ -288,11 +290,11 @@ void IWM::run_for(const Cycles cycles) {
|
||||
if(!(write_handshake_ & 0x80)) {
|
||||
shift_register_ = next_output_;
|
||||
output_bits_remaining_ = 8;
|
||||
// logger.info().append("Next byte: %02x", shift_register_);
|
||||
// Logger::info().append("Next byte: %02x", shift_register_);
|
||||
} else {
|
||||
write_handshake_ &= ~0x40;
|
||||
if(drives_[active_drive_]) drives_[active_drive_]->end_writing();
|
||||
logger.info().append("Overrun; done.");
|
||||
Logger::info().append("Overrun; done.");
|
||||
output_bits_remaining_ = 1;
|
||||
}
|
||||
|
||||
@@ -348,7 +350,7 @@ void IWM::select_shift_mode() {
|
||||
shift_register_ = next_output_;
|
||||
write_handshake_ |= 0x80 | 0x40;
|
||||
output_bits_remaining_ = 8;
|
||||
logger.info().append("Seeding output with %02x", shift_register_);
|
||||
Logger::info().append("Seeding output with %02x", shift_register_);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -362,7 +364,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:
|
||||
// logger.info().append("Shifting 1 at %d", cycles_since_shift_.as<int>());
|
||||
// Logger::info().append("Shifting 1 at %d", cycles_since_shift_.as<int>());
|
||||
propose_shift(1);
|
||||
break;
|
||||
}
|
||||
@@ -371,8 +373,8 @@ void IWM::process_event(const Storage::Disk::Drive::Event &event) {
|
||||
void IWM::propose_shift(const uint8_t bit) {
|
||||
// TODO: synchronous mode.
|
||||
|
||||
// logger.info().append("Shifting at %d", cycles_since_shift_.as<int>());
|
||||
// logger.info().append("Shifting input");
|
||||
// Logger::info().append("Shifting at %d", cycles_since_shift_.as<int>());
|
||||
// Logger::info().append("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.
|
||||
@@ -387,7 +389,7 @@ void IWM::propose_shift(const uint8_t bit) {
|
||||
|
||||
shift_register_ = uint8_t((shift_register_ << 1) | bit);
|
||||
if(shift_register_ & 0x80) {
|
||||
// if(data_register_ & 0x80) logger.info().append("Byte missed");
|
||||
// if(data_register_ & 0x80) Logger::info().append("Byte missed");
|
||||
data_register_ = shift_register_;
|
||||
shift_register_ = 0;
|
||||
}
|
||||
|
||||
@@ -13,9 +13,7 @@
|
||||
using namespace I2C;
|
||||
|
||||
namespace {
|
||||
|
||||
Log::Logger<Log::Source::I2C> logger;
|
||||
|
||||
using Logger = Log::Logger<Log::Source::I2C>;
|
||||
}
|
||||
|
||||
void Bus::set_data(const bool pulled) {
|
||||
@@ -52,7 +50,7 @@ void Bus::set_clock_data(const bool clock_pulled, const bool data_pulled) {
|
||||
if(peripheral_bits_) {
|
||||
// Trailing edge of clock => bit has been consumed.
|
||||
if(!prior_clock && clock_) {
|
||||
logger.info().append("<< %d", (peripheral_response_ >> 7) & 1);
|
||||
Logger::info().append("<< %d", (peripheral_response_ >> 7) & 1);
|
||||
--peripheral_bits_;
|
||||
peripheral_response_ <<= 1;
|
||||
|
||||
@@ -68,10 +66,10 @@ void Bus::set_clock_data(const bool clock_pulled, const bool data_pulled) {
|
||||
// A data transition outside of a clock cycle implies a start or stop.
|
||||
in_bit_ = false;
|
||||
if(data_) {
|
||||
logger.info().append("S");
|
||||
Logger::info().append("S");
|
||||
signal(Event::Start);
|
||||
} else {
|
||||
logger.info().append("W");
|
||||
Logger::info().append("W");
|
||||
signal(Event::Stop);
|
||||
}
|
||||
} else if(clock_ != prior_clock) {
|
||||
@@ -85,10 +83,10 @@ void Bus::set_clock_data(const bool clock_pulled, const bool data_pulled) {
|
||||
in_bit_ = false;
|
||||
|
||||
if(data_) {
|
||||
logger.info().append("0");
|
||||
Logger::info().append("0");
|
||||
signal(Event::Zero);
|
||||
} else {
|
||||
logger.info().append("1");
|
||||
Logger::info().append("1");
|
||||
signal(Event::One);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,8 +18,8 @@ public:
|
||||
this is used to select a key scaling rate if key-rate scaling is enabled.
|
||||
*/
|
||||
void set_period(const int period, const int octave) {
|
||||
constexpr int key_level_scales[16] = {0, 48, 64, 74, 80, 86, 90, 94, 96, 100, 102, 104, 106, 108, 110, 112};
|
||||
constexpr int masks[2] = {~0, 0};
|
||||
static constexpr int key_level_scales[16] = {0, 48, 64, 74, 80, 86, 90, 94, 96, 100, 102, 104, 106, 108, 110, 112};
|
||||
static constexpr int masks[2] = {~0, 0};
|
||||
|
||||
// A two's complement assumption is embedded below; the use of masks relies
|
||||
// on the sign bit to clamp to zero.
|
||||
@@ -33,7 +33,7 @@ public:
|
||||
*/
|
||||
void set_key_scaling_level(const int level) {
|
||||
// '7' is just a number large enough to render all possible scaling coefficients as 0.
|
||||
constexpr int key_level_scale_shifts[4] = {7, 1, 2, 0};
|
||||
static constexpr int key_level_scale_shifts[4] = {7, 1, 2, 0};
|
||||
shift_ = key_level_scale_shifts[level];
|
||||
}
|
||||
|
||||
|
||||
@@ -24,8 +24,8 @@ public:
|
||||
Advances the phase generator a single step, given the current state of the low-frequency oscillator, @c oscillator.
|
||||
*/
|
||||
void update(const LowFrequencyOscillator &oscillator) {
|
||||
constexpr int vibrato_shifts[4] = {3, 1, 0, 1};
|
||||
constexpr int vibrato_signs[2] = {1, -1};
|
||||
static constexpr int vibrato_shifts[4] = {3, 1, 0, 1};
|
||||
static constexpr int vibrato_signs[2] = {1, -1};
|
||||
|
||||
// Get just the top three bits of the period_.
|
||||
const int top_freq = period_ >> (precision - 3);
|
||||
@@ -61,7 +61,7 @@ public:
|
||||
plus the degree of feedback to apply
|
||||
*/
|
||||
void apply_feedback(const LogSign first, const LogSign second, const int level) {
|
||||
constexpr int masks[] = {0, ~0, ~0, ~0, ~0, ~0, ~0, ~0};
|
||||
static constexpr int masks[] = {0, ~0, ~0, ~0, ~0, ~0, ~0, ~0};
|
||||
phase_ += ((second.level(precision) + first.level(precision)) >> (8 - level)) & masks[level];
|
||||
}
|
||||
|
||||
@@ -72,7 +72,7 @@ public:
|
||||
void set_multiple(const int multiple) {
|
||||
// This encodes the MUL -> multiple table given on page 12,
|
||||
// multiplied by two.
|
||||
constexpr int multipliers[] = {
|
||||
static constexpr int multipliers[] = {
|
||||
1, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 20, 24, 24, 30, 30
|
||||
};
|
||||
assert(multiple < 16);
|
||||
|
||||
@@ -22,8 +22,8 @@ public:
|
||||
/*!
|
||||
@returns The output of waveform @c form at [integral] phase @c phase.
|
||||
*/
|
||||
static constexpr LogSign wave(Waveform form, int phase) {
|
||||
constexpr int waveforms[4][4] = {
|
||||
static constexpr LogSign wave(const Waveform form, const int phase) {
|
||||
int waveforms[4][4] = {
|
||||
{1023, 1023, 1023, 1023}, // Sine: don't mask in any quadrant.
|
||||
{511, 511, 0, 0}, // Half sine: keep the first half intact, lock to 0 in the second half.
|
||||
{511, 511, 511, 511}, // AbsSine: endlessly repeat the first half of the sine wave.
|
||||
|
||||
+38
-33
@@ -314,7 +314,11 @@ void OPLL::update_all_channels() {
|
||||
envelope_generators_[c + 9].update(oscillator_);
|
||||
}
|
||||
|
||||
#define VOLUME(x) int16_t(((x) * total_volume_) >> 12)
|
||||
const auto volume = [&](const int x) {
|
||||
return int16_t(
|
||||
(x * total_volume_) >> 12
|
||||
);
|
||||
};
|
||||
|
||||
if(rhythm_mode_enabled_) {
|
||||
// Advance the rhythm envelope generators.
|
||||
@@ -323,32 +327,32 @@ void OPLL::update_all_channels() {
|
||||
}
|
||||
|
||||
// Fill in the melodic channels.
|
||||
output_levels_[3] = VOLUME(melodic_output(0));
|
||||
output_levels_[4] = VOLUME(melodic_output(1));
|
||||
output_levels_[5] = VOLUME(melodic_output(2));
|
||||
output_levels_[3] = volume(melodic_output(0));
|
||||
output_levels_[4] = volume(melodic_output(1));
|
||||
output_levels_[5] = volume(melodic_output(2));
|
||||
|
||||
output_levels_[9] = VOLUME(melodic_output(3));
|
||||
output_levels_[10] = VOLUME(melodic_output(4));
|
||||
output_levels_[11] = VOLUME(melodic_output(5));
|
||||
output_levels_[9] = volume(melodic_output(3));
|
||||
output_levels_[10] = volume(melodic_output(4));
|
||||
output_levels_[11] = volume(melodic_output(5));
|
||||
|
||||
// Bass drum, which is a regular FM effect.
|
||||
output_levels_[2] = output_levels_[15] = VOLUME(bass_drum());
|
||||
output_levels_[2] = output_levels_[15] = volume(bass_drum());
|
||||
oscillator_.update_lfsr();
|
||||
|
||||
// Tom tom, which is a single operator.
|
||||
output_levels_[1] = output_levels_[14] = VOLUME(tom_tom());
|
||||
output_levels_[1] = output_levels_[14] = volume(tom_tom());
|
||||
oscillator_.update_lfsr();
|
||||
|
||||
// Snare.
|
||||
output_levels_[6] = output_levels_[16] = VOLUME(snare_drum());
|
||||
output_levels_[6] = output_levels_[16] = volume(snare_drum());
|
||||
oscillator_.update_lfsr();
|
||||
|
||||
// Cymbal.
|
||||
output_levels_[7] = output_levels_[17] = VOLUME(cymbal());
|
||||
output_levels_[7] = output_levels_[17] = volume(cymbal());
|
||||
oscillator_.update_lfsr();
|
||||
|
||||
// High-hat.
|
||||
output_levels_[0] = output_levels_[13] = VOLUME(high_hat());
|
||||
output_levels_[0] = output_levels_[13] = volume(high_hat());
|
||||
oscillator_.update_lfsr();
|
||||
|
||||
// Unutilised slots.
|
||||
@@ -365,32 +369,35 @@ void OPLL::update_all_channels() {
|
||||
output_levels_[6] = output_levels_[7] = output_levels_[8] =
|
||||
output_levels_[12] = output_levels_[13] = output_levels_[14] = 0;
|
||||
|
||||
output_levels_[3] = VOLUME(melodic_output(0));
|
||||
output_levels_[4] = VOLUME(melodic_output(1));
|
||||
output_levels_[5] = VOLUME(melodic_output(2));
|
||||
output_levels_[3] = volume(melodic_output(0));
|
||||
output_levels_[4] = volume(melodic_output(1));
|
||||
output_levels_[5] = volume(melodic_output(2));
|
||||
|
||||
output_levels_[9] = VOLUME(melodic_output(3));
|
||||
output_levels_[10] = VOLUME(melodic_output(4));
|
||||
output_levels_[11] = VOLUME(melodic_output(5));
|
||||
output_levels_[9] = volume(melodic_output(3));
|
||||
output_levels_[10] = volume(melodic_output(4));
|
||||
output_levels_[11] = volume(melodic_output(5));
|
||||
|
||||
output_levels_[15] = VOLUME(melodic_output(6));
|
||||
output_levels_[16] = VOLUME(melodic_output(7));
|
||||
output_levels_[17] = VOLUME(melodic_output(8));
|
||||
output_levels_[15] = volume(melodic_output(6));
|
||||
output_levels_[16] = volume(melodic_output(7));
|
||||
output_levels_[17] = volume(melodic_output(8));
|
||||
}
|
||||
|
||||
#undef VOLUME
|
||||
|
||||
// TODO: batch updates of the LFSR.
|
||||
}
|
||||
|
||||
// TODO: verify attenuation scales pervasively below.
|
||||
namespace {
|
||||
|
||||
#define ATTENUATION(x) ((x) << 7)
|
||||
// TODO: verify attenuation scales pervasively below.
|
||||
constexpr int attenuation(const int x) {
|
||||
return x << 7;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
int OPLL::melodic_output(const int channel) {
|
||||
// The modulator always updates after the carrier, oddly enough. So calculate actual output first, based on the modulator's last value.
|
||||
auto carrier = WaveformGenerator<period_precision>::wave(channels_[channel].carrier_waveform, phase_generators_[channel].scaled_phase(), channels_[channel].modulator_output);
|
||||
carrier += envelope_generators_[channel].attenuation() + ATTENUATION(channels_[channel].attenuation) + key_level_scalers_[channel].attenuation();
|
||||
carrier += envelope_generators_[channel].attenuation() + attenuation(channels_[channel].attenuation) + key_level_scalers_[channel].attenuation();
|
||||
|
||||
// Get the modulator's new value.
|
||||
auto modulation = WaveformGenerator<period_precision>::wave(channels_[channel].modulator_waveform, phase_generators_[channel + 9].phase());
|
||||
@@ -409,7 +416,7 @@ int OPLL::bass_drum() const {
|
||||
modulation += rhythm_envelope_generators_[RhythmIndices::BassModulator].attenuation();
|
||||
|
||||
auto carrier = WaveformGenerator<period_precision>::wave(Waveform::Sine, phase_generators_[6].scaled_phase(), modulation);
|
||||
carrier += rhythm_envelope_generators_[RhythmIndices::BassCarrier].attenuation() + ATTENUATION(channels_[6].attenuation);
|
||||
carrier += rhythm_envelope_generators_[RhythmIndices::BassCarrier].attenuation() + attenuation(channels_[6].attenuation);
|
||||
return carrier.level();
|
||||
}
|
||||
|
||||
@@ -417,7 +424,7 @@ int OPLL::tom_tom() const {
|
||||
// Use modulator 8 and the 'instrument' selection for channel 8 as an attenuation.
|
||||
auto tom_tom = WaveformGenerator<period_precision>::wave(Waveform::Sine, phase_generators_[8 + 9].phase());
|
||||
tom_tom += rhythm_envelope_generators_[RhythmIndices::TomTom].attenuation();
|
||||
tom_tom += ATTENUATION(channels_[8].instrument);
|
||||
tom_tom += attenuation(channels_[8].instrument);
|
||||
return tom_tom.level();
|
||||
}
|
||||
|
||||
@@ -425,7 +432,7 @@ int OPLL::snare_drum() const {
|
||||
// Use modulator 7 and the carrier attenuation level for channel 7.
|
||||
LogSign snare = WaveformGenerator<period_precision>::snare(oscillator_, phase_generators_[7 + 9].phase());
|
||||
snare += rhythm_envelope_generators_[RhythmIndices::Snare].attenuation();
|
||||
snare += ATTENUATION(channels_[7].attenuation);
|
||||
snare += attenuation(channels_[7].attenuation);
|
||||
return snare.level();
|
||||
}
|
||||
|
||||
@@ -433,7 +440,7 @@ int OPLL::cymbal() const {
|
||||
// Use modulator 7, carrier 8 and the attenuation level for channel 8.
|
||||
LogSign cymbal = WaveformGenerator<period_precision>::cymbal(phase_generators_[8].phase(), phase_generators_[7 + 9].phase());
|
||||
cymbal += rhythm_envelope_generators_[RhythmIndices::Cymbal].attenuation();
|
||||
cymbal += ATTENUATION(channels_[8].attenuation);
|
||||
cymbal += attenuation(channels_[8].attenuation);
|
||||
return cymbal.level();
|
||||
}
|
||||
|
||||
@@ -441,8 +448,6 @@ int OPLL::high_hat() const {
|
||||
// Use modulator 7, carrier 8 a and the 'instrument' selection for channel 7 as an attenuation.
|
||||
LogSign high_hat = WaveformGenerator<period_precision>::high_hat(oscillator_, phase_generators_[8].phase(), phase_generators_[7 + 9].phase());
|
||||
high_hat += rhythm_envelope_generators_[RhythmIndices::HighHat].attenuation();
|
||||
high_hat += ATTENUATION(channels_[7].instrument);
|
||||
high_hat += attenuation(channels_[7].instrument);
|
||||
return high_hat.level();
|
||||
}
|
||||
|
||||
#undef ATTENUATION
|
||||
|
||||
@@ -47,7 +47,7 @@ void RP5C01::run_for(const HalfCycles cycles) {
|
||||
// Update time within day.
|
||||
seconds_ += elapsed_seconds;
|
||||
|
||||
constexpr int day_length = 60 * 60 * 24;
|
||||
static constexpr int day_length = 60 * 60 * 24;
|
||||
if(seconds_ < day_length) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,351 @@
|
||||
//
|
||||
// SAA5050.cpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 24/09/2025.
|
||||
// Copyright © 2025 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#include "SAA5050.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdint>
|
||||
|
||||
namespace {
|
||||
// SAA5050 font, padded out to one byte per row. The least-significant five bits of each byte
|
||||
// are the meaningful pixels for that row, with the LSB being on the right.
|
||||
constexpr uint8_t font[][10] = {
|
||||
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, }, // Character 32.
|
||||
{0x00, 0x04, 0x04, 0x04, 0x04, 0x04, 0x00, 0x04, 0x00, 0x00, },
|
||||
{0x00, 0x0a, 0x0a, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, },
|
||||
{0x00, 0x06, 0x09, 0x08, 0x1c, 0x08, 0x08, 0x1f, 0x00, 0x00, },
|
||||
{0x00, 0x0e, 0x15, 0x14, 0x0e, 0x05, 0x15, 0x0e, 0x00, 0x00, },
|
||||
{0x00, 0x18, 0x19, 0x02, 0x04, 0x08, 0x13, 0x03, 0x00, 0x00, },
|
||||
{0x00, 0x08, 0x14, 0x14, 0x08, 0x15, 0x12, 0x0d, 0x00, 0x00, },
|
||||
{0x00, 0x04, 0x04, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, },
|
||||
{0x00, 0x02, 0x04, 0x08, 0x08, 0x08, 0x04, 0x02, 0x00, 0x00, },
|
||||
{0x00, 0x08, 0x04, 0x02, 0x02, 0x02, 0x04, 0x08, 0x00, 0x00, },
|
||||
{0x00, 0x04, 0x15, 0x0e, 0x04, 0x0e, 0x15, 0x04, 0x00, 0x00, },
|
||||
{0x00, 0x00, 0x04, 0x04, 0x1f, 0x04, 0x04, 0x00, 0x00, 0x00, },
|
||||
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x04, 0x08, 0x00, },
|
||||
{0x00, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x00, 0x00, },
|
||||
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, },
|
||||
{0x00, 0x00, 0x01, 0x02, 0x04, 0x08, 0x10, 0x00, 0x00, 0x00, },
|
||||
{0x00, 0x04, 0x0a, 0x11, 0x11, 0x11, 0x0a, 0x04, 0x00, 0x00, },
|
||||
{0x00, 0x04, 0x0c, 0x04, 0x04, 0x04, 0x04, 0x0e, 0x00, 0x00, },
|
||||
{0x00, 0x0e, 0x11, 0x01, 0x06, 0x08, 0x10, 0x1f, 0x00, 0x00, },
|
||||
{0x00, 0x1f, 0x01, 0x02, 0x06, 0x01, 0x11, 0x0e, 0x00, 0x00, },
|
||||
{0x00, 0x02, 0x06, 0x0a, 0x12, 0x1f, 0x02, 0x02, 0x00, 0x00, },
|
||||
{0x00, 0x1f, 0x10, 0x1e, 0x01, 0x01, 0x11, 0x0e, 0x00, 0x00, },
|
||||
{0x00, 0x06, 0x08, 0x10, 0x1e, 0x11, 0x11, 0x0e, 0x00, 0x00, },
|
||||
{0x00, 0x1f, 0x01, 0x02, 0x04, 0x08, 0x08, 0x08, 0x00, 0x00, },
|
||||
{0x00, 0x0e, 0x11, 0x11, 0x0e, 0x11, 0x11, 0x0e, 0x00, 0x00, },
|
||||
{0x00, 0x0e, 0x11, 0x11, 0x0f, 0x01, 0x02, 0x0c, 0x00, 0x00, },
|
||||
{0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, },
|
||||
{0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x04, 0x04, 0x08, 0x00, },
|
||||
{0x00, 0x02, 0x04, 0x08, 0x10, 0x08, 0x04, 0x02, 0x00, 0x00, },
|
||||
{0x00, 0x00, 0x00, 0x1f, 0x00, 0x1f, 0x00, 0x00, 0x00, 0x00, },
|
||||
{0x00, 0x08, 0x04, 0x02, 0x01, 0x02, 0x04, 0x08, 0x00, 0x00, },
|
||||
{0x00, 0x0e, 0x11, 0x02, 0x04, 0x04, 0x00, 0x04, 0x00, 0x00, },
|
||||
{0x00, 0x0e, 0x11, 0x17, 0x15, 0x17, 0x10, 0x0e, 0x00, 0x00, },
|
||||
{0x00, 0x04, 0x0a, 0x11, 0x11, 0x1f, 0x11, 0x11, 0x00, 0x00, },
|
||||
{0x00, 0x1e, 0x11, 0x11, 0x1e, 0x11, 0x11, 0x1e, 0x00, 0x00, },
|
||||
{0x00, 0x0e, 0x11, 0x10, 0x10, 0x10, 0x11, 0x0e, 0x00, 0x00, },
|
||||
{0x00, 0x1e, 0x11, 0x11, 0x11, 0x11, 0x11, 0x1e, 0x00, 0x00, },
|
||||
{0x00, 0x1f, 0x10, 0x10, 0x1e, 0x10, 0x10, 0x1f, 0x00, 0x00, },
|
||||
{0x00, 0x1f, 0x10, 0x10, 0x1e, 0x10, 0x10, 0x10, 0x00, 0x00, },
|
||||
{0x00, 0x0e, 0x11, 0x10, 0x10, 0x13, 0x11, 0x0f, 0x00, 0x00, },
|
||||
{0x00, 0x11, 0x11, 0x11, 0x1f, 0x11, 0x11, 0x11, 0x00, 0x00, },
|
||||
{0x00, 0x0e, 0x04, 0x04, 0x04, 0x04, 0x04, 0x0e, 0x00, 0x00, },
|
||||
{0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x11, 0x0e, 0x00, 0x00, },
|
||||
{0x00, 0x11, 0x12, 0x14, 0x18, 0x14, 0x12, 0x11, 0x00, 0x00, },
|
||||
{0x00, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x1f, 0x00, 0x00, },
|
||||
{0x00, 0x11, 0x1b, 0x15, 0x15, 0x11, 0x11, 0x11, 0x00, 0x00, },
|
||||
{0x00, 0x11, 0x11, 0x19, 0x15, 0x13, 0x11, 0x11, 0x00, 0x00, },
|
||||
{0x00, 0x0e, 0x11, 0x11, 0x11, 0x11, 0x11, 0x0e, 0x00, 0x00, },
|
||||
{0x00, 0x1e, 0x11, 0x11, 0x1e, 0x10, 0x10, 0x10, 0x00, 0x00, },
|
||||
{0x00, 0x0e, 0x11, 0x11, 0x11, 0x15, 0x12, 0x0d, 0x00, 0x00, },
|
||||
{0x00, 0x1e, 0x11, 0x11, 0x1e, 0x14, 0x12, 0x11, 0x00, 0x00, },
|
||||
{0x00, 0x0e, 0x11, 0x10, 0x0e, 0x01, 0x11, 0x0e, 0x00, 0x00, },
|
||||
{0x00, 0x1f, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x00, 0x00, },
|
||||
{0x00, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x0e, 0x00, 0x00, },
|
||||
{0x00, 0x11, 0x11, 0x11, 0x0a, 0x0a, 0x04, 0x04, 0x00, 0x00, },
|
||||
{0x00, 0x11, 0x11, 0x11, 0x15, 0x15, 0x15, 0x0a, 0x00, 0x00, },
|
||||
{0x00, 0x11, 0x11, 0x0a, 0x04, 0x0a, 0x11, 0x11, 0x00, 0x00, },
|
||||
{0x00, 0x11, 0x11, 0x0a, 0x04, 0x04, 0x04, 0x04, 0x00, 0x00, },
|
||||
{0x00, 0x1f, 0x01, 0x02, 0x04, 0x08, 0x10, 0x1f, 0x00, 0x00, },
|
||||
{0x00, 0x00, 0x04, 0x08, 0x1f, 0x08, 0x04, 0x00, 0x00, 0x00, },
|
||||
{0x00, 0x10, 0x10, 0x10, 0x10, 0x16, 0x01, 0x02, 0x04, 0x07, },
|
||||
{0x00, 0x00, 0x04, 0x02, 0x1f, 0x02, 0x04, 0x00, 0x00, 0x00, },
|
||||
{0x00, 0x00, 0x04, 0x0e, 0x15, 0x04, 0x04, 0x00, 0x00, 0x00, },
|
||||
{0x00, 0x0a, 0x0a, 0x1f, 0x0a, 0x1f, 0x0a, 0x0a, 0x00, 0x00, },
|
||||
{0x00, 0x00, 0x00, 0x00, 0x1f, 0x00, 0x00, 0x00, 0x00, 0x00, },
|
||||
{0x00, 0x00, 0x00, 0x0e, 0x01, 0x0f, 0x11, 0x0f, 0x00, 0x00, },
|
||||
{0x00, 0x10, 0x10, 0x1e, 0x11, 0x11, 0x11, 0x1e, 0x00, 0x00, },
|
||||
{0x00, 0x00, 0x00, 0x0f, 0x10, 0x10, 0x10, 0x0f, 0x00, 0x00, },
|
||||
{0x00, 0x01, 0x01, 0x0f, 0x11, 0x11, 0x11, 0x0f, 0x00, 0x00, },
|
||||
{0x00, 0x00, 0x00, 0x0e, 0x11, 0x1f, 0x10, 0x0e, 0x00, 0x00, },
|
||||
{0x00, 0x02, 0x04, 0x04, 0x0e, 0x04, 0x04, 0x04, 0x00, 0x00, },
|
||||
{0x00, 0x00, 0x00, 0x0f, 0x11, 0x11, 0x11, 0x0f, 0x01, 0x0e, },
|
||||
{0x00, 0x10, 0x10, 0x1e, 0x11, 0x11, 0x11, 0x11, 0x00, 0x00, },
|
||||
{0x00, 0x04, 0x00, 0x0c, 0x04, 0x04, 0x04, 0x0e, 0x00, 0x00, },
|
||||
{0x00, 0x04, 0x00, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x08, },
|
||||
{0x00, 0x08, 0x08, 0x09, 0x0a, 0x0c, 0x0a, 0x09, 0x00, 0x00, },
|
||||
{0x00, 0x0c, 0x04, 0x04, 0x04, 0x04, 0x04, 0x0e, 0x00, 0x00, },
|
||||
{0x00, 0x00, 0x00, 0x1a, 0x15, 0x15, 0x15, 0x15, 0x00, 0x00, },
|
||||
{0x00, 0x00, 0x00, 0x1e, 0x11, 0x11, 0x11, 0x11, 0x00, 0x00, },
|
||||
{0x00, 0x00, 0x00, 0x0e, 0x11, 0x11, 0x11, 0x0e, 0x00, 0x00, },
|
||||
{0x00, 0x00, 0x00, 0x1e, 0x11, 0x11, 0x11, 0x1e, 0x10, 0x10, },
|
||||
{0x00, 0x00, 0x00, 0x0f, 0x11, 0x11, 0x11, 0x0f, 0x01, 0x01, },
|
||||
{0x00, 0x00, 0x00, 0x0b, 0x0c, 0x08, 0x08, 0x08, 0x00, 0x00, },
|
||||
{0x00, 0x00, 0x00, 0x0f, 0x10, 0x0e, 0x01, 0x1e, 0x00, 0x00, },
|
||||
{0x00, 0x04, 0x04, 0x0e, 0x04, 0x04, 0x04, 0x02, 0x00, 0x00, },
|
||||
{0x00, 0x00, 0x00, 0x11, 0x11, 0x11, 0x11, 0x0f, 0x00, 0x00, },
|
||||
{0x00, 0x00, 0x00, 0x11, 0x11, 0x0a, 0x0a, 0x04, 0x00, 0x00, },
|
||||
{0x00, 0x00, 0x00, 0x11, 0x11, 0x15, 0x15, 0x0a, 0x00, 0x00, },
|
||||
{0x00, 0x00, 0x00, 0x11, 0x0a, 0x04, 0x0a, 0x11, 0x00, 0x00, },
|
||||
{0x00, 0x00, 0x00, 0x11, 0x11, 0x11, 0x11, 0x0f, 0x01, 0x0e, },
|
||||
{0x00, 0x00, 0x00, 0x1f, 0x02, 0x04, 0x08, 0x1f, 0x00, 0x00, },
|
||||
{0x00, 0x10, 0x10, 0x10, 0x10, 0x11, 0x03, 0x05, 0x07, 0x01, },
|
||||
{0x00, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x00, 0x00, },
|
||||
{0x00, 0x18, 0x04, 0x18, 0x04, 0x19, 0x03, 0x05, 0x07, 0x01, },
|
||||
{0x00, 0x00, 0x04, 0x00, 0x1f, 0x00, 0x04, 0x00, 0x00, 0x00, },
|
||||
{0x00, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x00, 0x00, },
|
||||
};
|
||||
|
||||
enum ControlCode: uint8_t {
|
||||
RedAlpha = 0x01,
|
||||
GreenAlpha = 0x02,
|
||||
YellowAlpha = 0x03,
|
||||
BlueAlpha = 0x04,
|
||||
MagentaAlpha = 0x05,
|
||||
CyanAlpha = 0x06,
|
||||
WhiteAlpha = 0x07,
|
||||
|
||||
Flash = 0x08,
|
||||
Steady = 0x09,
|
||||
|
||||
RedGraphics = 0x11,
|
||||
GreenGraphics = 0x12,
|
||||
YellowGraphics = 0x13,
|
||||
BlueGraphics = 0x14,
|
||||
MagentaGraphics = 0x15,
|
||||
CyanGraphics = 0x16,
|
||||
WhiteGraphics = 0x17,
|
||||
|
||||
Conceal = 0x18,
|
||||
|
||||
ContinuousGraphics = 0x19,
|
||||
SeparatedGraphics = 0x1a,
|
||||
|
||||
NormalHeight = 0xc,
|
||||
DoubleHeight = 0xd,
|
||||
|
||||
BlackBackground = 0x1c,
|
||||
NewBackground = 0x1d,
|
||||
|
||||
HoldGraphics = 0x1e,
|
||||
ReleaseGraphics = 0x1f,
|
||||
};}
|
||||
|
||||
using namespace Mullard;
|
||||
|
||||
void SAA5050Serialiser::begin_frame(const bool is_odd) {
|
||||
line_ = -2;
|
||||
row_ = 0;
|
||||
odd_frame_ = is_odd;
|
||||
|
||||
row_has_double_height_ = false;
|
||||
double_height_offset_ = 0;
|
||||
|
||||
++frame_counter_;
|
||||
}
|
||||
|
||||
void SAA5050Serialiser::begin_line() {
|
||||
line_ += 2;
|
||||
if(line_ == 20) {
|
||||
line_ = 0;
|
||||
++row_;
|
||||
|
||||
if(row_has_double_height_) {
|
||||
double_height_offset_ = (double_height_offset_ + 5) % 10;
|
||||
}
|
||||
row_has_double_height_ = false;
|
||||
}
|
||||
|
||||
output_.reset();
|
||||
has_output_ = false;
|
||||
|
||||
apply_control(ControlCode::WhiteAlpha);
|
||||
apply_control(ControlCode::Steady);
|
||||
apply_control(ControlCode::NormalHeight);
|
||||
apply_control(ControlCode::ContinuousGraphics);
|
||||
apply_control(ControlCode::BlackBackground);
|
||||
apply_control(ControlCode::ReleaseGraphics);
|
||||
}
|
||||
|
||||
bool SAA5050Serialiser::has_output() const {
|
||||
return has_output_;
|
||||
}
|
||||
|
||||
SAA5050Serialiser::Output SAA5050Serialiser::output() {
|
||||
has_output_ = false;
|
||||
return output_;
|
||||
}
|
||||
|
||||
void SAA5050Serialiser::apply_control(const uint8_t value) {
|
||||
const auto set_alpha = [&](const uint8_t colour) {
|
||||
alpha_mode_ = true;
|
||||
conceal_ = false;
|
||||
output_.alpha = colour;
|
||||
hold_graphics_ = false;
|
||||
};
|
||||
|
||||
const auto set_graphics = [&](const uint8_t colour) {
|
||||
alpha_mode_ = false;
|
||||
conceal_ = false;
|
||||
output_.alpha = colour;
|
||||
hold_graphics_ = false;
|
||||
};
|
||||
|
||||
switch(value) {
|
||||
default: break;
|
||||
|
||||
case RedAlpha: set_alpha(0b100); break;
|
||||
case GreenAlpha: set_alpha(0b010); break;
|
||||
case YellowAlpha: set_alpha(0b110); break;
|
||||
case BlueAlpha: set_alpha(0b001); break;
|
||||
case MagentaAlpha: set_alpha(0b101); break;
|
||||
case CyanAlpha: set_alpha(0b011); break;
|
||||
case WhiteAlpha: set_alpha(0b111); break;
|
||||
|
||||
case Flash: flash_ = true; break;
|
||||
case Steady: flash_ = false; break;
|
||||
|
||||
case RedGraphics: set_graphics(0b100); break;
|
||||
case GreenGraphics: set_graphics(0b010); break;
|
||||
case YellowGraphics: set_graphics(0b110); break;
|
||||
case BlueGraphics: set_graphics(0b001); break;
|
||||
case MagentaGraphics: set_graphics(0b101); break;
|
||||
case CyanGraphics: set_graphics(0b011); break;
|
||||
case WhiteGraphics: set_graphics(0b111); break;
|
||||
|
||||
case Conceal: conceal_ = true; break;
|
||||
|
||||
case ContinuousGraphics: separated_graphics_ = false; break;
|
||||
case SeparatedGraphics: separated_graphics_ = true; break;
|
||||
|
||||
case NormalHeight: double_height_ = false; break;
|
||||
case DoubleHeight: double_height_ = row_has_double_height_ = true; break;
|
||||
|
||||
case BlackBackground: output_.background = 0; break;
|
||||
case NewBackground: output_.background = output_.alpha; break;
|
||||
|
||||
case HoldGraphics: hold_graphics_ = true; break;
|
||||
case ReleaseGraphics: hold_graphics_ = false; last_graphic_ = 32; break;
|
||||
}
|
||||
}
|
||||
|
||||
void SAA5050Serialiser::set_reveal(const bool reveal) {
|
||||
reveal_ = reveal;
|
||||
}
|
||||
|
||||
void SAA5050Serialiser::add(const Numeric::SizedInt<7> c) {
|
||||
has_output_ = true;
|
||||
if(c.get() < 32) {
|
||||
if(hold_graphics_) {
|
||||
load_pixels(last_graphic_);
|
||||
} else {
|
||||
output_.reset();
|
||||
}
|
||||
apply_control(c.get());
|
||||
return;
|
||||
}
|
||||
load_pixels(c.get());
|
||||
}
|
||||
|
||||
void SAA5050Serialiser::load_pixels(const uint8_t c) {
|
||||
if(flash_ && ((frame_counter_&31) > 23)) { // Complete guess on the blink period here.
|
||||
output_.reset();
|
||||
return;
|
||||
}
|
||||
|
||||
if(conceal_ && !reveal_) {
|
||||
output_.reset();
|
||||
return;
|
||||
}
|
||||
|
||||
// Divert into graphics only if both the mode and the character code allows it.
|
||||
if(!alpha_mode_ && (c & (1 << 5))) {
|
||||
last_graphic_ = c;
|
||||
|
||||
// Graphics layout:
|
||||
//
|
||||
// |----|----|
|
||||
// | | |
|
||||
// | b0 | b1 |
|
||||
// | | |
|
||||
// |----|----|
|
||||
// | | |
|
||||
// | b2 | b3 |
|
||||
// | | |
|
||||
// |----|----|
|
||||
// | | |
|
||||
// | b4 | b6 |
|
||||
// | | |
|
||||
// |----|----|
|
||||
|
||||
if(separated_graphics_ && (line_ == 6 || line_ == 12 || line_ == 18)) {
|
||||
output_.reset();
|
||||
return;
|
||||
}
|
||||
|
||||
uint8_t pixels;
|
||||
if(line_ < 6) {
|
||||
pixels =
|
||||
((c & 1) ? 0b111'000 : 0) |
|
||||
((c & 2) ? 0b000'111 : 0);
|
||||
} else if(line_ < 14) {
|
||||
pixels =
|
||||
((c & 4) ? 0b111'000 : 0) |
|
||||
((c & 8) ? 0b000'111 : 0);
|
||||
} else {
|
||||
pixels =
|
||||
((c & 16) ? 0b111'000 : 0) |
|
||||
((c & 64) ? 0b000'111 : 0);
|
||||
}
|
||||
|
||||
if(separated_graphics_) {
|
||||
pixels &= 0b011'011;
|
||||
}
|
||||
|
||||
output_.load(pixels);
|
||||
return;
|
||||
}
|
||||
|
||||
if(double_height_) {
|
||||
const auto top_address = (line_ >> 2) + double_height_offset_;
|
||||
const uint8_t top = font[c - 32][top_address];
|
||||
const uint8_t bottom = font[c - 32][std::min(9, top_address + 1)];
|
||||
|
||||
if(line_ & 2) {
|
||||
output_.load(bottom, top);
|
||||
} else {
|
||||
output_.load(top, bottom);
|
||||
}
|
||||
} else {
|
||||
if(double_height_offset_) {
|
||||
output_.reset();
|
||||
} else {
|
||||
const auto top_address = line_ >> 1;
|
||||
const uint8_t top = font[c - 32][top_address];
|
||||
const uint8_t bottom = font[c - 32][std::min(9, top_address + 1)];
|
||||
|
||||
if(odd_frame_) {
|
||||
output_.load(bottom, top);
|
||||
} else {
|
||||
output_.load(top, bottom);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
//
|
||||
// SAA5050.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 24/09/2025.
|
||||
// Copyright © 2025 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include "Numeric/SizedInt.hpp"
|
||||
|
||||
namespace Mullard {
|
||||
|
||||
struct SAA5050Serialiser {
|
||||
public:
|
||||
void begin_frame(bool is_odd);
|
||||
void begin_line();
|
||||
|
||||
void add(Numeric::SizedInt<7>);
|
||||
|
||||
struct Output {
|
||||
void reset() {
|
||||
top_ = bottom_ = 0;
|
||||
}
|
||||
void load(const uint8_t top, const uint8_t bottom) {
|
||||
top_ = top;
|
||||
bottom_ = bottom;
|
||||
}
|
||||
void load(const uint8_t top) {
|
||||
top_ = bottom_ = top;
|
||||
}
|
||||
|
||||
// The low twelve bits of this word provide 1bpp pixels.
|
||||
uint16_t pixels() const {
|
||||
// Adapted from old ElectrEm source; my original provenance for this being the correct logic is unknown.
|
||||
uint16_t wide =
|
||||
((top_ & 0b000001) ? 0b0000'0000'0011 : 0) |
|
||||
((top_ & 0b000010) ? 0b0000'0000'1100 : 0) |
|
||||
((top_ & 0b000100) ? 0b0000'0011'0000 : 0) |
|
||||
((top_ & 0b001000) ? 0b0000'1100'0000 : 0) |
|
||||
((top_ & 0b010000) ? 0b0011'0000'0000 : 0) |
|
||||
((top_ & 0b100000) ? 0b1100'0000'0000 : 0);
|
||||
|
||||
if(top_ != bottom_) {
|
||||
if((top_ & 0b10000) && (bottom_ & 0b11000) == 0b01000) wide |= 0b0000'1000'0000;
|
||||
if((top_ & 0b01000) && (bottom_ & 0b01100) == 0b00100) wide |= 0b0000'0010'0000;
|
||||
if((top_ & 0b00100) && (bottom_ & 0b00110) == 0b00010) wide |= 0b0000'0000'1000;
|
||||
if((top_ & 0b00010) && (bottom_ & 0b00011) == 0b00001) wide |= 0b0000'0000'0010;
|
||||
|
||||
if((top_ & 0b01000) && (bottom_ & 0b11000) == 0b10000) wide |= 0b0001'0000'0000;
|
||||
if((top_ & 0b00100) && (bottom_ & 0b01100) == 0b01000) wide |= 0b0000'0100'0000;
|
||||
if((top_ & 0b00010) && (bottom_ & 0b00110) == 0b00100) wide |= 0b0000'0001'0000;
|
||||
if((top_ & 0b00001) && (bottom_ & 0b00011) == 0b00010) wide |= 0b0000'0000'0100;
|
||||
}
|
||||
|
||||
return wide;
|
||||
}
|
||||
|
||||
// Colours for foreground and background pixels.
|
||||
uint8_t alpha;
|
||||
uint8_t background;
|
||||
|
||||
private:
|
||||
uint8_t top_, bottom_;
|
||||
};
|
||||
bool has_output() const;
|
||||
Output output();
|
||||
|
||||
void set_reveal(bool);
|
||||
|
||||
private:
|
||||
Output output_;
|
||||
bool has_output_ = false;
|
||||
|
||||
int row_, line_;
|
||||
bool odd_frame_;
|
||||
|
||||
bool flash_ = false;
|
||||
int frame_counter_ = 0;
|
||||
|
||||
bool reveal_ = false;
|
||||
bool conceal_ = false;
|
||||
|
||||
bool alpha_mode_ = true;
|
||||
bool separated_graphics_ = false;
|
||||
|
||||
bool double_height_ = false;
|
||||
bool row_has_double_height_ = false;
|
||||
int double_height_offset_ = 0;
|
||||
|
||||
bool hold_graphics_ = false;
|
||||
uint8_t last_graphic_ = 0;
|
||||
|
||||
void load_pixels(const uint8_t);
|
||||
void apply_control(const uint8_t);
|
||||
};
|
||||
|
||||
}
|
||||
@@ -0,0 +1,433 @@
|
||||
//
|
||||
// SID.cpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 07/11/2025.
|
||||
// Copyright © 2025 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#include "SID.hpp"
|
||||
|
||||
// Sources used:
|
||||
//
|
||||
// (1) SID Article v0.2 at https://github.com/ImreOlajos/SID-Article
|
||||
// (2) Technical SID Information/Software stuff at http://www.sidmusic.org/sid/sidtech2.html
|
||||
// (3) SID 6581/8580 (Sound Interface Device) reference at https://oxyron.de/html/registers_sid.html
|
||||
|
||||
using namespace MOS::SID;
|
||||
|
||||
SID::SID(Concurrency::AsyncTaskQueue<false> &audio_queue) :
|
||||
audio_queue_(audio_queue),
|
||||
output_filter_(
|
||||
SignalProcessing::BiquadFilter::Type::LowPass,
|
||||
1000000.0f,
|
||||
15000.0f
|
||||
) {}
|
||||
|
||||
// MARK: - Programmer interface.
|
||||
|
||||
void SID::write(const Numeric::SizedInt<5> address, const uint8_t value) {
|
||||
last_write_ = value;
|
||||
audio_queue_.enqueue([=, this] {
|
||||
const auto voice = [&]() -> Voice & {
|
||||
return voices_[address.get() / 7];
|
||||
};
|
||||
const auto oscillator = [&]() -> Voice::Oscillator & {
|
||||
return voice().oscillator;
|
||||
};
|
||||
const auto adsr = [&]() -> Voice::ADSR & {
|
||||
return voice().adsr;
|
||||
};
|
||||
|
||||
switch(address.get()) {
|
||||
case 0x00: case 0x07: case 0x0e:
|
||||
oscillator().pitch = (oscillator().pitch & 0xff'00'00) | uint32_t(value << 8);
|
||||
break;
|
||||
case 0x01: case 0x08: case 0x0f:
|
||||
oscillator().pitch = (oscillator().pitch & 0x00'ff'00) | uint32_t(value << 16);
|
||||
break;
|
||||
case 0x02: case 0x09: case 0x10:
|
||||
oscillator().pulse_width = (oscillator().pitch & 0xf0'00'00'00) | uint32_t(value << 20);
|
||||
break;
|
||||
case 0x03: case 0x0a: case 0x11:
|
||||
// The top bit of the phase counter is inverted; since it'll be compared directly with the
|
||||
// pulse width, invert that bit too.
|
||||
oscillator().pulse_width =
|
||||
(
|
||||
(oscillator().pitch & 0x0f'f0'00'00) |
|
||||
uint32_t(value << 28)
|
||||
);
|
||||
break;
|
||||
case 0x04: case 0x0b: case 0x12:
|
||||
voice().set_control(value);
|
||||
break;
|
||||
case 0x05: case 0x0c: case 0x13:
|
||||
adsr().attack = value >> 4;
|
||||
adsr().decay = value;
|
||||
adsr().set_phase(adsr().phase);
|
||||
break;
|
||||
case 0x06: case 0x0d: case 0x14:
|
||||
adsr().sustain = (value >> 4) | (value & 0xf0);
|
||||
adsr().release = value;
|
||||
adsr().set_phase(adsr().phase);
|
||||
break;
|
||||
|
||||
case 0x15:
|
||||
filter_cutoff_.load<0, 3>(value);
|
||||
update_filter();
|
||||
break;
|
||||
case 0x16:
|
||||
filter_cutoff_.load<3>(value);
|
||||
update_filter();
|
||||
break;
|
||||
case 0x17:
|
||||
filter_channels_ = value;
|
||||
filter_resonance_ = value >> 4;
|
||||
update_filter();
|
||||
break;
|
||||
case 0x18:
|
||||
volume_ = value & 0x0f;
|
||||
filter_mode_ = value >> 4;
|
||||
voice3_disable_ = value & 0x80;
|
||||
update_filter();
|
||||
break;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void SID::set_potentometer_input(const int index, const uint8_t value) {
|
||||
potentometers_[index] = value;
|
||||
}
|
||||
|
||||
void SID::update_filter() {
|
||||
using Type = SignalProcessing::BiquadFilter::Type;
|
||||
Type type = Type::AllPass;
|
||||
|
||||
switch(filter_mode_.get()) {
|
||||
case 0:
|
||||
filter_ = SignalProcessing::BiquadFilter();
|
||||
return;
|
||||
|
||||
case 1:
|
||||
case 3: type = Type::LowPass; break;
|
||||
|
||||
case 2: type = Type::BandPass; break;
|
||||
case 5: type = Type::Notch; break;
|
||||
|
||||
case 4:
|
||||
case 6: type = Type::HighPass; break;
|
||||
|
||||
case 7: type = Type::AllPass; break;
|
||||
}
|
||||
|
||||
filter_.configure(
|
||||
type,
|
||||
1'000'000.0f,
|
||||
30.0f + float(filter_cutoff_.get()) * 5.8f,
|
||||
0.707f + float(filter_resonance_.get()) * 0.2862f,
|
||||
6.0f,
|
||||
true
|
||||
);
|
||||
|
||||
// Filter cutoff: the data sheet provides that it is linear, and "approximate Cutoff Frequency
|
||||
// ranges between 30Hz and 12KHz [with recommended externally-supplied capacitors]."
|
||||
//
|
||||
// It's an 11-bit number, so the above is "approximate"ly right.
|
||||
|
||||
// Resonance: a complete from-thin-air guess. The data sheet says merely:
|
||||
//
|
||||
// "There are 16 Resonance settings ranging from about 0.707 (Critical Damping) for a count of 0
|
||||
// to a maximum for a count of 15"
|
||||
//
|
||||
// i.e. no information is given on the maximum. I've taken it to be 5-ish per commentary on more general sites
|
||||
// that 5 is a typical ceiling for the resonance factor.
|
||||
}
|
||||
|
||||
uint8_t SID::read(const Numeric::SizedInt<5> address) {
|
||||
switch(address.get()) {
|
||||
default: return last_write_;
|
||||
|
||||
case 0x19: return potentometers_[0];
|
||||
case 0x1a: return potentometers_[1];
|
||||
|
||||
case 0x1b:
|
||||
case 0x1c:
|
||||
// Ensure all channels are entirely up to date.
|
||||
audio_queue_.spin_flush();
|
||||
return (address == 0x1c) ? voices_[2].adsr.envelope : uint8_t(voices_[2].output(voices_[1]) >> 4);
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Oscillators.
|
||||
|
||||
void Voice::Oscillator::reset_phase() {
|
||||
phase = PhaseReload;
|
||||
}
|
||||
|
||||
bool Voice::Oscillator::did_raise_b23() const {
|
||||
return previous_phase > phase;
|
||||
}
|
||||
|
||||
bool Voice::Oscillator::did_raise_b19() const {
|
||||
static constexpr int NoiseBit = 1 << (19 + 8);
|
||||
return (previous_phase ^ phase) & phase & NoiseBit;
|
||||
}
|
||||
|
||||
uint16_t Voice::Oscillator::sawtooth_output() const {
|
||||
return (phase >> 20) ^ 0x800;
|
||||
}
|
||||
|
||||
// MARK: - Noise generator.
|
||||
|
||||
uint16_t Voice::NoiseGenerator::output() const {
|
||||
// Uses bits: 20, 18, 14, 11, 9, 5, 2 and 0, plus four more zero bits.
|
||||
const uint16_t output =
|
||||
((noise >> 9) & 0b1000'0000'0000) | // b20 -> b11
|
||||
((noise >> 8) & 0b0100'0000'0000) | // b18 -> b10
|
||||
((noise >> 5) & 0b0010'0000'0000) | // b14 -> b9
|
||||
((noise >> 3) & 0b0001'0000'0000) | // b11 -> b8
|
||||
((noise >> 2) & 0b0000'1000'0000) | // b9 -> b7
|
||||
((noise << 1) & 0b0000'0100'0000) | // b5 -> b6
|
||||
((noise << 3) & 0b0000'0010'0000) | // b2 -> b5
|
||||
((noise << 4) & 0b0000'0001'0000); // b0 -> b4
|
||||
|
||||
assert(output <= Voice::MaxWaveformValue);
|
||||
return output;
|
||||
}
|
||||
|
||||
void Voice::NoiseGenerator::update(const bool test) {
|
||||
noise =
|
||||
(noise << 1) |
|
||||
(((noise >> 17) ^ ((noise >> 22) | test)) & 1);
|
||||
}
|
||||
|
||||
// MARK: - ADSR.
|
||||
|
||||
void Voice::ADSR::set_phase(const Phase new_phase) {
|
||||
static constexpr uint16_t rate_prescaler[] = {
|
||||
9, 32, 63, 95, 149, 220, 267, 313, 392, 977, 1954, 3126, 3907, 11720, 19532, 31251
|
||||
};
|
||||
static_assert(sizeof(rate_prescaler) / sizeof(*rate_prescaler) == 16);
|
||||
|
||||
phase = new_phase;
|
||||
switch(phase) {
|
||||
case Phase::Attack: rate_counter_target = rate_prescaler[attack.get()]; break;
|
||||
case Phase::DecayAndHold: rate_counter_target = rate_prescaler[decay.get()]; break;
|
||||
case Phase::Release: rate_counter_target = rate_prescaler[release.get()]; break;
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Voices.
|
||||
|
||||
void Voice::set_control(const uint8_t new_control) {
|
||||
const bool old_gate = gate();
|
||||
control = new_control;
|
||||
if(gate() && !old_gate) {
|
||||
adsr.set_phase(ADSR::Phase::Attack);
|
||||
} else if(!gate() && old_gate) {
|
||||
adsr.set_phase(ADSR::Phase::Release);
|
||||
}
|
||||
}
|
||||
|
||||
bool Voice::noise() const { return control.bit<7>(); }
|
||||
bool Voice::pulse() const { return control.bit<6>(); }
|
||||
bool Voice::sawtooth() const { return control.bit<5>(); }
|
||||
bool Voice::triangle() const { return control.bit<4>(); }
|
||||
bool Voice::test() const { return control.bit<3>(); }
|
||||
bool Voice::ring_mod() const { return control.bit<2>(); }
|
||||
bool Voice::sync() const { return control.bit<1>(); }
|
||||
bool Voice::gate() const { return control.bit<0>(); }
|
||||
|
||||
void Voice::update() {
|
||||
// Oscillator.
|
||||
oscillator.previous_phase = oscillator.phase;
|
||||
if(test()) {
|
||||
oscillator.phase = 0;
|
||||
} else {
|
||||
oscillator.phase += oscillator.pitch;
|
||||
|
||||
if(oscillator.did_raise_b19()) {
|
||||
noise_generator.update(test());
|
||||
}
|
||||
}
|
||||
|
||||
// ADSR.
|
||||
|
||||
// First prescalar, which is a function of the programmer-set rate.
|
||||
++ adsr.rate_counter;
|
||||
if(adsr.rate_counter == adsr.rate_counter_target) {
|
||||
adsr.rate_counter = 0;
|
||||
|
||||
// Second prescalar, which approximates an exponential.
|
||||
static constexpr uint8_t exponential_prescaler[] = {
|
||||
1, // 0
|
||||
30, 30, 30, 30, 30, 30, // 1–6
|
||||
16, 16, 16, 16, 16, 16, 16, 16, // 7–14
|
||||
8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, // 15–26
|
||||
4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
|
||||
4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, // 27–54
|
||||
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
|
||||
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
|
||||
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, // 55–94
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
||||
1, 1, 1, 1, 1, 1, 1,
|
||||
};
|
||||
static_assert(sizeof(exponential_prescaler) == 256);
|
||||
static_assert(exponential_prescaler[0] == 1);
|
||||
static_assert(exponential_prescaler[1] == 30);
|
||||
static_assert(exponential_prescaler[6] == 30);
|
||||
static_assert(exponential_prescaler[7] == 16);
|
||||
static_assert(exponential_prescaler[14] == 16);
|
||||
static_assert(exponential_prescaler[15] == 8);
|
||||
static_assert(exponential_prescaler[26] == 8);
|
||||
static_assert(exponential_prescaler[27] == 4);
|
||||
static_assert(exponential_prescaler[54] == 4);
|
||||
static_assert(exponential_prescaler[55] == 2);
|
||||
static_assert(exponential_prescaler[94] == 2);
|
||||
static_assert(exponential_prescaler[95] == 1);
|
||||
static_assert(exponential_prescaler[255] == 1);
|
||||
|
||||
if(adsr.phase == ADSR::Phase::Attack) {
|
||||
++adsr.envelope;
|
||||
// TODO: what really resets the exponential counter? If anything?
|
||||
adsr.exponential_counter = 0;
|
||||
|
||||
if(adsr.envelope == 0xff) {
|
||||
adsr.set_phase(ADSR::Phase::DecayAndHold);
|
||||
}
|
||||
} else {
|
||||
++adsr.exponential_counter;
|
||||
if(adsr.exponential_counter == exponential_prescaler[adsr.envelope]) {
|
||||
adsr.exponential_counter = 0;
|
||||
|
||||
if(adsr.envelope && (adsr.envelope != adsr.sustain || adsr.phase != ADSR::Phase::DecayAndHold)) {
|
||||
--adsr.envelope;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Voice::synchronise(const Voice &prior) {
|
||||
// Only oscillator work to do here.
|
||||
if(
|
||||
sync() &&
|
||||
prior.oscillator.did_raise_b23()
|
||||
) {
|
||||
oscillator.phase = Oscillator::PhaseReload;
|
||||
}
|
||||
}
|
||||
|
||||
uint16_t Voice::pulse_output() const {
|
||||
return (
|
||||
(oscillator.phase ^ 0x8000'0000) < oscillator.pulse_width
|
||||
) ? 0 : MaxWaveformValue;
|
||||
}
|
||||
|
||||
uint16_t Voice::triangle_output(const Voice &prior) const {
|
||||
const uint16_t sawtooth = oscillator.sawtooth_output();
|
||||
const uint16_t xor_mask1 = sawtooth;
|
||||
const uint16_t xor_mask2 = ring_mod() ? prior.sawtooth() : 0;
|
||||
const uint16_t xor_mask = ((xor_mask1 ^ xor_mask2) & 0x800) ? 0xfff : 0x000;
|
||||
return ((sawtooth << 1) ^ xor_mask) & 0xfff;
|
||||
}
|
||||
|
||||
uint16_t Voice::output(const Voice &prior) const {
|
||||
// TODO: true composite waves.
|
||||
//
|
||||
// My current understanding on this: if multiple waveforms are enabled, the pull to zero beats the
|
||||
// pull to one on any line where the two compete. But the twist is that the lines are not necessarily
|
||||
// one per bit since they lead to a common ground. Ummm, I think.
|
||||
//
|
||||
// Anyway, first pass: logical AND. It's not right. It will temporarily do.
|
||||
|
||||
uint16_t output = MaxWaveformValue;
|
||||
|
||||
if(pulse()) output &= pulse_output();
|
||||
if(sawtooth()) output &= oscillator.sawtooth_output();
|
||||
if(triangle()) output &= triangle_output(prior);
|
||||
if(noise()) output &= noise_generator.output();
|
||||
|
||||
return (output * adsr.envelope) / 255;
|
||||
}
|
||||
|
||||
// MARK: - Wave generation
|
||||
|
||||
void SID::set_sample_volume_range(const std::int16_t range) {
|
||||
range_ = range;
|
||||
}
|
||||
|
||||
bool SID::is_zero_level() const {
|
||||
return false;
|
||||
}
|
||||
|
||||
template <Outputs::Speaker::Action action>
|
||||
void SID::apply_samples(const std::size_t number_of_samples, Outputs::Speaker::MonoSample *const target) {
|
||||
for(std::size_t c = 0; c < number_of_samples; c++) {
|
||||
// Advance phase.
|
||||
voices_[0].update();
|
||||
voices_[1].update();
|
||||
voices_[2].update();
|
||||
|
||||
// Apply hard synchronisations.
|
||||
voices_[0].synchronise(voices_[2]);
|
||||
voices_[1].synchronise(voices_[0]);
|
||||
voices_[2].synchronise(voices_[1]);
|
||||
|
||||
// Construct filtered and unfiltered output.
|
||||
const uint16_t outputs[3] = {
|
||||
voices_[0].output(voices_[2]),
|
||||
voices_[1].output(voices_[0]),
|
||||
voices_[2].output(voices_[1]),
|
||||
};
|
||||
|
||||
const uint16_t direct_sample =
|
||||
(filter_channels_.bit<0>() ? 0 : outputs[0]) +
|
||||
(filter_channels_.bit<1>() ? 0 : outputs[1]) +
|
||||
(filter_channels_.bit<2>() || voice3_disable_ ? 0 : outputs[2]);
|
||||
|
||||
const int16_t filtered_sample =
|
||||
filter_.apply(
|
||||
(filter_channels_.bit<0>() ? outputs[0] : 0) +
|
||||
(filter_channels_.bit<1>() ? outputs[1] : 0) +
|
||||
(filter_channels_.bit<2>() ? outputs[2] : 0)
|
||||
);
|
||||
|
||||
// Sum, apply volume and output.
|
||||
const auto sample = output_filter_.apply(int16_t(
|
||||
(
|
||||
volume_ * (
|
||||
direct_sample +
|
||||
filtered_sample
|
||||
- 227 // DC offset.
|
||||
)
|
||||
- 88732
|
||||
) / 3
|
||||
));
|
||||
// Maximum range of above: 15 * (4095 * 3 - 227) = [-3405, 180870]
|
||||
// So subtracting 88732 will move to the centre of the range, and 3 is the smallest
|
||||
// integer that avoids clipping.
|
||||
|
||||
Outputs::Speaker::apply<action>(
|
||||
target[c],
|
||||
Outputs::Speaker::MonoSample((sample * range_) >> 16)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
template void SID::apply_samples<Outputs::Speaker::Action::Mix>(
|
||||
std::size_t, Outputs::Speaker::MonoSample *);
|
||||
template void SID::apply_samples<Outputs::Speaker::Action::Store>(
|
||||
std::size_t, Outputs::Speaker::MonoSample *);
|
||||
template void SID::apply_samples<Outputs::Speaker::Action::Ignore>(
|
||||
std::size_t, Outputs::Speaker::MonoSample *);
|
||||
@@ -0,0 +1,129 @@
|
||||
//
|
||||
// SID.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 07/11/2025.
|
||||
// Copyright © 2025 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Numeric/SizedInt.hpp"
|
||||
|
||||
#include "Concurrency/AsyncTaskQueue.hpp"
|
||||
#include "Outputs/Speaker/Implementation/BufferSource.hpp"
|
||||
#include "SignalProcessing/BiquadFilter.hpp"
|
||||
|
||||
namespace MOS::SID {
|
||||
|
||||
struct Voice {
|
||||
static constexpr uint16_t MaxWaveformValue = (1 << 12) - 1;
|
||||
|
||||
struct Oscillator {
|
||||
// Programmer inputs.
|
||||
uint32_t pitch = 0;
|
||||
uint32_t pulse_width = 0;
|
||||
|
||||
// State.
|
||||
//
|
||||
// A real SID has a 24-bit phase counter and does various things when the top bit transitions from 0 to 1.
|
||||
// This implementation maintains a 32-bit phase counter in which the low byte is unused and the top bit
|
||||
// is inverted. That saves the cost of any masking and makes the 0 -> 1 transition test actually a 1 -> 0
|
||||
// transition test, which can be phrased simply as after < before. Sadly overflow of signed integers is
|
||||
// still undefined behaviour in C++ at the time of writing.
|
||||
static constexpr uint32_t PhaseReload = 0x8000'0000;
|
||||
uint32_t phase = PhaseReload;
|
||||
uint32_t previous_phase = PhaseReload;
|
||||
|
||||
void reset_phase();
|
||||
bool did_raise_b23() const;
|
||||
bool did_raise_b19() const;
|
||||
uint16_t sawtooth_output() const;
|
||||
} oscillator;
|
||||
struct ADSR {
|
||||
// Programmer inputs.
|
||||
Numeric::SizedInt<4> attack;
|
||||
Numeric::SizedInt<4> decay;
|
||||
Numeric::SizedInt<4> release;
|
||||
|
||||
Numeric::SizedInt<8> sustain;
|
||||
|
||||
// State.
|
||||
enum class Phase {
|
||||
Attack,
|
||||
DecayAndHold,
|
||||
Release,
|
||||
} phase = Phase::Release;
|
||||
Numeric::SizedInt<15> rate_counter;
|
||||
Numeric::SizedInt<15> rate_counter_target;
|
||||
|
||||
uint8_t exponential_counter;
|
||||
uint8_t envelope;
|
||||
|
||||
void set_phase(const Phase);
|
||||
} adsr;
|
||||
struct NoiseGenerator {
|
||||
static constexpr uint32_t NoiseReload = 0x7'ffff;
|
||||
uint32_t noise = NoiseReload;
|
||||
|
||||
uint16_t output() const;
|
||||
void update(const bool test);
|
||||
} noise_generator;
|
||||
|
||||
void set_control(const uint8_t);
|
||||
void update();
|
||||
void synchronise(const Voice &prior);
|
||||
uint16_t output(const Voice &prior) const;
|
||||
|
||||
private:
|
||||
Numeric::SizedInt<8> control;
|
||||
bool noise() const;
|
||||
bool pulse() const;
|
||||
bool sawtooth() const;
|
||||
bool triangle() const;
|
||||
bool test() const;
|
||||
bool ring_mod() const;
|
||||
bool sync() const;
|
||||
bool gate() const;
|
||||
|
||||
uint16_t pulse_output() const;
|
||||
uint16_t triangle_output(const Voice &prior) const;
|
||||
};
|
||||
|
||||
class SID: public Outputs::Speaker::BufferSource<SID, false> {
|
||||
public:
|
||||
SID(Concurrency::AsyncTaskQueue<false> &audio_queue);
|
||||
|
||||
void write(Numeric::SizedInt<5> address, uint8_t value);
|
||||
uint8_t read(Numeric::SizedInt<5> address);
|
||||
void set_potentometer_input(int index, uint8_t value);
|
||||
|
||||
// Outputs::Speaker::BufferSource.
|
||||
template <Outputs::Speaker::Action action>
|
||||
void apply_samples(std::size_t, Outputs::Speaker::MonoSample *);
|
||||
bool is_zero_level() const;
|
||||
void set_sample_volume_range(std::int16_t);
|
||||
|
||||
private:
|
||||
Concurrency::AsyncTaskQueue<false> &audio_queue_;
|
||||
Voice voices_[3];
|
||||
|
||||
uint8_t last_write_;
|
||||
|
||||
int16_t range_ = 0;
|
||||
uint8_t volume_ = 0;
|
||||
|
||||
SignalProcessing::BiquadFilter filter_;
|
||||
Numeric::SizedInt<11> filter_cutoff_;
|
||||
Numeric::SizedInt<4> filter_resonance_;
|
||||
Numeric::SizedInt<4> filter_channels_;
|
||||
Numeric::SizedInt<3> filter_mode_;
|
||||
bool voice3_disable_;
|
||||
void update_filter();
|
||||
|
||||
SignalProcessing::BiquadFilter output_filter_;
|
||||
|
||||
uint8_t potentometers_[2]{};
|
||||
};
|
||||
|
||||
}
|
||||
@@ -101,7 +101,7 @@ template <bool lsb_first, typename IntT> void Line<include_clock>::write_interna
|
||||
bit = levels & 1;
|
||||
levels >>= 1;
|
||||
} else {
|
||||
constexpr auto top_bit = IntT(0x80) << ((sizeof(IntT) - 1) * 8);
|
||||
static constexpr auto top_bit = IntT(0x80) << ((sizeof(IntT) - 1) * 8);
|
||||
bit = levels & top_bit;
|
||||
levels <<= 1;
|
||||
}
|
||||
|
||||
@@ -137,7 +137,6 @@ private:
|
||||
Defines an RS-232-esque srial port.
|
||||
*/
|
||||
class Port {
|
||||
public:
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
//
|
||||
// uPD7002.cpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 17/09/2025.
|
||||
// Copyright © 2025 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#include "uPD7002.hpp"
|
||||
|
||||
using namespace NEC;
|
||||
|
||||
uPD7002::uPD7002(const HalfCycles clock_rate) {
|
||||
// Per the BBC AUG: "8 bit conversions typically take 4 ms to complete whereas 10 bit
|
||||
// conversions typically take 10 ms to complete".
|
||||
fast_period_ = clock_rate / 250;
|
||||
slow_period_ = clock_rate / 100;
|
||||
}
|
||||
|
||||
void uPD7002::run_for(const HalfCycles count) {
|
||||
if(!conversion_time_remaining_) {
|
||||
return;
|
||||
}
|
||||
|
||||
if(count >= conversion_time_remaining_) {
|
||||
conversion_time_remaining_ = HalfCycles(0);
|
||||
result_ = uint16_t(inputs_[channel_] * 65535.0f) & (high_precision_ ? 0xfff0 : 0xff00);
|
||||
set_interrupt(true);
|
||||
return;
|
||||
}
|
||||
|
||||
conversion_time_remaining_ -= count;
|
||||
}
|
||||
|
||||
bool uPD7002::interrupt() const {
|
||||
return interrupt_;
|
||||
}
|
||||
|
||||
void uPD7002::write(const uint16_t address, const uint8_t value) {
|
||||
const auto local_address = address & 3;
|
||||
if(!local_address) {
|
||||
channel_ = value & 0b0000'0011;
|
||||
spare_ = value & 0b0000'0100;
|
||||
high_precision_ = value & 0b0000'1000;
|
||||
conversion_time_remaining_ = high_precision_ ? slow_period_ : fast_period_;
|
||||
set_interrupt(false);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t uPD7002::read(const uint16_t address) {
|
||||
switch(address & 3) {
|
||||
default:
|
||||
case 0: return status();
|
||||
case 1:
|
||||
set_interrupt(false);
|
||||
return uint8_t(result_ >> 8);
|
||||
case 2: return uint8_t(result_);
|
||||
case 3: return 0xff;
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t uPD7002::status() const {
|
||||
return
|
||||
channel_ |
|
||||
spare_ |
|
||||
(high_precision_ ? 0x08 : 0) |
|
||||
((result_ >> 14) & 0x30) |
|
||||
(conversion_time_remaining_ > HalfCycles(0) ? 0x00 : 0x40) |
|
||||
(interrupt_ ? 0x00 : 0x80);
|
||||
}
|
||||
|
||||
void uPD7002::set_delegate(Delegate *const delegate) {
|
||||
delegate_ = delegate;
|
||||
}
|
||||
|
||||
void uPD7002::set_interrupt(const bool value) {
|
||||
if(interrupt_ == value) return;
|
||||
interrupt_ = value;
|
||||
if(delegate_) delegate_->did_change_interrupt_status(*this);
|
||||
}
|
||||
|
||||
void uPD7002::set_input(const int channel, const float value) {
|
||||
inputs_[channel] = value;
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
//
|
||||
// uPD7002.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 17/09/2025.
|
||||
// Copyright © 2025 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#include "ClockReceiver/ClockReceiver.hpp"
|
||||
|
||||
namespace NEC {
|
||||
|
||||
class uPD7002 {
|
||||
public:
|
||||
/// Constructs a PD7002 that will receive @c run_for updates at the specified clock rate.
|
||||
uPD7002(HalfCycles clock_rate);
|
||||
void run_for(HalfCycles);
|
||||
|
||||
/// @returns The current state of the interrupt line.
|
||||
bool interrupt() const;
|
||||
|
||||
/// Defines a mean for an observer to receive notifications upon updates to the interrupt line.
|
||||
struct Delegate {
|
||||
virtual void did_change_interrupt_status(uPD7002 &) = 0;
|
||||
};
|
||||
void set_delegate(Delegate *);
|
||||
|
||||
void write(uint16_t address, uint8_t value);
|
||||
uint8_t read(uint16_t address);
|
||||
|
||||
/// Sets the floating point value, which should be in the range [0.0, 1.0], for the signal currently
|
||||
/// being supplied to @c channel.
|
||||
void set_input(int channel, float value);
|
||||
|
||||
private:
|
||||
float inputs_[4]{};
|
||||
uint16_t result_ = 0;
|
||||
bool interrupt_ = false;
|
||||
|
||||
uint8_t channel_ = 0, spare_ = 0;
|
||||
bool high_precision_ = false;
|
||||
|
||||
HalfCycles conversion_time_remaining_{};
|
||||
HalfCycles fast_period_, slow_period_;
|
||||
|
||||
uint8_t status() const;
|
||||
void set_interrupt(bool);
|
||||
Delegate *delegate_ = nullptr;
|
||||
};
|
||||
|
||||
}
|
||||
@@ -28,7 +28,7 @@ template <typename Performer> struct TaskQueueStorage {
|
||||
|
||||
protected:
|
||||
void update() {
|
||||
auto time_now = Time::nanos_now();
|
||||
const auto time_now = Time::nanos_now();
|
||||
performer.perform(time_now - last_fired_);
|
||||
last_fired_ = time_now;
|
||||
}
|
||||
@@ -40,9 +40,12 @@ private:
|
||||
/// An implementation detail; provides a no-op implementation of time advances for TaskQueues without a Performer.
|
||||
template <> struct TaskQueueStorage<void> {
|
||||
TaskQueueStorage() {}
|
||||
protected:
|
||||
void update() {}
|
||||
};
|
||||
|
||||
protected:
|
||||
void update() {}
|
||||
struct EnqueueDelegate {
|
||||
virtual std::function<void(void)> prepare_enqueue() = 0;
|
||||
};
|
||||
|
||||
/*!
|
||||
@@ -64,12 +67,22 @@ template <> struct TaskQueueStorage<void> {
|
||||
action occupies the asynchronous thread for long enough. So it is not true that @c perform will be
|
||||
called once per action.
|
||||
*/
|
||||
template <bool perform_automatically, bool start_immediately = true, typename Performer = void> class AsyncTaskQueue: public TaskQueueStorage<Performer> {
|
||||
template <
|
||||
bool perform_automatically,
|
||||
bool start_immediately = true,
|
||||
bool use_enqueue_delegate = false,
|
||||
typename Performer = void
|
||||
>
|
||||
class AsyncTaskQueue: public TaskQueueStorage<Performer> {
|
||||
public:
|
||||
void set_enqueue_delegate(EnqueueDelegate *const delegate) {
|
||||
enqueue_delegate_ = delegate;
|
||||
}
|
||||
|
||||
template <typename... Args> AsyncTaskQueue(Args&&... args) :
|
||||
TaskQueueStorage<Performer>(std::forward<Args>(args)...) {
|
||||
if constexpr (start_immediately) {
|
||||
start();
|
||||
start_impl();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -84,7 +97,10 @@ public:
|
||||
/// on the same thread as the performer, after the performer has been updated
|
||||
/// to 'now'.
|
||||
void enqueue(const std::function<void(void)> &post_action) {
|
||||
std::lock_guard guard(condition_mutex_);
|
||||
const std::lock_guard guard(condition_mutex_);
|
||||
if constexpr (use_enqueue_delegate) {
|
||||
actions_.push_back(enqueue_delegate_->prepare_enqueue());
|
||||
}
|
||||
actions_.push_back(post_action);
|
||||
|
||||
if constexpr (perform_automatically) {
|
||||
@@ -92,8 +108,15 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
/// @returns The number of items currently enqueued.
|
||||
size_t size() {
|
||||
const std::lock_guard guard(condition_mutex_);
|
||||
return actions_.size();
|
||||
}
|
||||
|
||||
/// Causes any enqueued actions that are not yet scheduled to be scheduled.
|
||||
void perform() {
|
||||
static_assert(!perform_automatically);
|
||||
if(actions_.empty()) {
|
||||
return;
|
||||
}
|
||||
@@ -106,7 +129,7 @@ public:
|
||||
/// The queue cannot be restarted; this is a destructive action.
|
||||
void stop() {
|
||||
if(thread_.joinable()) {
|
||||
should_quit_ = true;
|
||||
should_quit_.store(true, std::memory_order_relaxed);
|
||||
enqueue([] {});
|
||||
if constexpr (!perform_automatically) {
|
||||
perform();
|
||||
@@ -119,36 +142,13 @@ public:
|
||||
///
|
||||
/// This is not guaranteed safely to restart a stopped queue.
|
||||
void start() {
|
||||
thread_ = std::thread{
|
||||
[this] {
|
||||
ActionVector actions;
|
||||
|
||||
// Continue until told to quit.
|
||||
while(!should_quit_) {
|
||||
// Wait for new actions to be signalled, and grab them.
|
||||
std::unique_lock lock(condition_mutex_);
|
||||
while(actions_.empty() && !should_quit_) {
|
||||
condition_.wait(lock);
|
||||
}
|
||||
std::swap(actions, actions_);
|
||||
lock.unlock();
|
||||
|
||||
// Update to now (which is possibly a no-op).
|
||||
TaskQueueStorage<Performer>::update();
|
||||
|
||||
// Perform the actions and destroy them.
|
||||
for(const auto &action: actions) {
|
||||
action();
|
||||
}
|
||||
actions.clear();
|
||||
}
|
||||
}
|
||||
};
|
||||
static_assert(!start_immediately);
|
||||
start_impl();
|
||||
}
|
||||
|
||||
/// Schedules any remaining unscheduled work, then blocks synchronously
|
||||
/// until all scheduled work has been performed.
|
||||
void flush() {
|
||||
void lock_flush() {
|
||||
std::mutex flush_mutex;
|
||||
std::condition_variable flush_condition;
|
||||
bool has_run = false;
|
||||
@@ -167,11 +167,58 @@ public:
|
||||
flush_condition.wait(lock, [&has_run] { return has_run; });
|
||||
}
|
||||
|
||||
/// Schedules any remaining unscheduled work, then spins
|
||||
/// until all scheduled work has been performed, placing a memory barrier
|
||||
/// in between.
|
||||
void spin_flush() {
|
||||
std::atomic<bool> has_run = false;
|
||||
|
||||
enqueue([&has_run] () {
|
||||
has_run.store(true, std::memory_order::release);
|
||||
});
|
||||
|
||||
if constexpr (!perform_automatically) {
|
||||
perform();
|
||||
}
|
||||
|
||||
while(!has_run.load(std::memory_order::acquire));
|
||||
}
|
||||
|
||||
~AsyncTaskQueue() {
|
||||
stop();
|
||||
}
|
||||
|
||||
private:
|
||||
void start_impl() {
|
||||
thread_ = std::thread{
|
||||
[this] {
|
||||
ActionVector actions;
|
||||
|
||||
// Continue until told to quit.
|
||||
while(!should_quit_.load(std::memory_order_relaxed)) {
|
||||
// Wait for new actions to be signalled, and grab them.
|
||||
std::unique_lock lock(condition_mutex_);
|
||||
condition_.wait(lock, [&] {
|
||||
return !actions_.empty() || should_quit_.load(std::memory_order_relaxed);
|
||||
});
|
||||
std::swap(actions, actions_);
|
||||
lock.unlock();
|
||||
|
||||
// Update to now (which is possibly a no-op).
|
||||
TaskQueueStorage<Performer>::update();
|
||||
|
||||
// Perform the actions and destroy them.
|
||||
for(const auto &action: actions) {
|
||||
action();
|
||||
}
|
||||
actions.clear();
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
EnqueueDelegate *enqueue_delegate_ = nullptr;
|
||||
|
||||
// The list of actions waiting be performed. These will be elided,
|
||||
// increasing their latency, if the emulation thread falls behind.
|
||||
using ActionVector = std::vector<std::function<void(void)>>;
|
||||
|
||||
@@ -25,10 +25,12 @@ ReflectableEnum(Display,
|
||||
// ensure unified property naming.
|
||||
//===
|
||||
|
||||
template <typename Owner> class DisplayOption {
|
||||
namespace Options {
|
||||
|
||||
template <typename Owner> class Display {
|
||||
public:
|
||||
Configurable::Display output;
|
||||
DisplayOption(Configurable::Display output) : output(output) {}
|
||||
Display(const Configurable::Display output) noexcept : output(output) {}
|
||||
|
||||
protected:
|
||||
void declare_display_option() {
|
||||
@@ -37,26 +39,38 @@ protected:
|
||||
}
|
||||
};
|
||||
|
||||
template <typename Owner> class QuickloadOption {
|
||||
template <typename Owner> class QuickLoad {
|
||||
public:
|
||||
bool quickload;
|
||||
QuickloadOption(bool quickload) : quickload(quickload) {}
|
||||
bool quick_load;
|
||||
QuickLoad(const bool quick_load) noexcept : quick_load(quick_load) {}
|
||||
|
||||
protected:
|
||||
void declare_quickload_option() {
|
||||
static_cast<Owner *>(this)->declare(&quickload, "quickload");
|
||||
static_cast<Owner *>(this)->declare(&quick_load, "quickload");
|
||||
}
|
||||
};
|
||||
|
||||
template <typename Owner> class QuickbootOption {
|
||||
template <typename Owner> class QuickBoot {
|
||||
public:
|
||||
bool quickboot;
|
||||
QuickbootOption(bool quickboot) : quickboot(quickboot) {}
|
||||
bool quick_boot;
|
||||
QuickBoot(const bool quick_boot) noexcept : quick_boot(quick_boot) {}
|
||||
|
||||
protected:
|
||||
void declare_quickboot_option() {
|
||||
static_cast<Owner *>(this)->declare(&quickboot, "quickboot");
|
||||
static_cast<Owner *>(this)->declare(&quick_boot, "quickboot");
|
||||
}
|
||||
};
|
||||
|
||||
template <typename Owner> class DynamicCrop {
|
||||
public:
|
||||
bool dynamic_crop;
|
||||
DynamicCrop(const bool dynamic_crop) noexcept : dynamic_crop(dynamic_crop) {}
|
||||
|
||||
protected:
|
||||
void declare_dynamic_crop_option() {
|
||||
static_cast<Owner *>(this)->declare(&dynamic_crop, "dynamiccrop");
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
+76
-19
@@ -29,7 +29,7 @@ public:
|
||||
/// Defines the broad type of the input.
|
||||
enum Type {
|
||||
// Half-axis inputs.
|
||||
Up, Down, Left, Right,
|
||||
Down, Up, Left, Right,
|
||||
// Full-axis inputs.
|
||||
Horizontal, Vertical,
|
||||
// Fire buttons.
|
||||
@@ -165,10 +165,11 @@ public:
|
||||
const bool is_analogue_axis = input.is_analogue_axis();
|
||||
if(is_digital_axis || is_analogue_axis) {
|
||||
const size_t required_size = size_t(input.info.control.index+1);
|
||||
if(stick_types_.size() < required_size) {
|
||||
stick_types_.resize(required_size);
|
||||
if(sticks_.size() < required_size) {
|
||||
sticks_.resize(required_size);
|
||||
}
|
||||
stick_types_[size_t(input.info.control.index)] = is_digital_axis ? StickType::Digital : StickType::Analogue;
|
||||
sticks_[size_t(input.info.control.index)].type =
|
||||
is_digital_axis ? Stick::Type::Digital : Stick::Type::Analogue;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -179,26 +180,50 @@ public:
|
||||
|
||||
void set_input(const Input &input, const bool is_active) final {
|
||||
// If this is a digital setting to a digital property, just pass it along.
|
||||
if(input.is_button() || stick_types_[input.info.control.index] == StickType::Digital) {
|
||||
if(input.is_button() || sticks_[input.info.control.index].type == Stick::Type::Digital) {
|
||||
did_set_input(input, is_active);
|
||||
return;
|
||||
}
|
||||
|
||||
// Otherwise this is logically to an analogue axis; for now just use some
|
||||
// convenient hard-coded values. TODO: make these a function of time.
|
||||
using Type = Joystick::Input::Type;
|
||||
// Otherwise this is logically to an analogue axis; map appropriately.
|
||||
// TODO: make these a function of time.
|
||||
auto &stick = sticks_[input.info.control.index];
|
||||
stick.apply_digital(input, is_active);
|
||||
const auto analogue_value = [&](const int mask) {
|
||||
switch(mask) {
|
||||
default: return 0.5f;
|
||||
case 0b01: return digital_minimum();
|
||||
case 0b10: return digital_maximum();
|
||||
}
|
||||
};
|
||||
|
||||
switch(input.type) {
|
||||
default: did_set_input(input, is_active ? 1.0f : 0.0f); break;
|
||||
case Type::Left: did_set_input(Input(Type::Horizontal, input.info.control.index), is_active ? 0.1f : 0.5f); break;
|
||||
case Type::Right: did_set_input(Input(Type::Horizontal, input.info.control.index), is_active ? 0.9f : 0.5f); break;
|
||||
case Type::Up: did_set_input(Input(Type::Vertical, input.info.control.index), is_active ? 0.1f : 0.5f); break;
|
||||
case Type::Down: did_set_input(Input(Type::Vertical, input.info.control.index), is_active ? 0.9f : 0.5f); break;
|
||||
using enum Joystick::Input::Type;
|
||||
|
||||
default:
|
||||
did_set_input(input, is_active ? 1.0f : 0.0f);
|
||||
break;
|
||||
|
||||
case Left:
|
||||
case Right:
|
||||
did_set_input(
|
||||
Input(Horizontal, input.info.control.index),
|
||||
analogue_value(stick.digital_mask(Horizontal))
|
||||
);
|
||||
break;
|
||||
case Up:
|
||||
case Down:
|
||||
did_set_input(
|
||||
Input(Vertical, input.info.control.index),
|
||||
analogue_value(stick.digital_mask(Vertical))
|
||||
);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void set_input(const Input &input, const float value) final {
|
||||
// If this is an analogue setting to an analogue property, just pass it along.
|
||||
if(!input.is_button() && stick_types_[input.info.control.index] == StickType::Analogue) {
|
||||
if(!input.is_button() && sticks_[input.info.control.index].type == Stick::Type::Analogue) {
|
||||
did_set_input(input, value);
|
||||
return;
|
||||
}
|
||||
@@ -206,7 +231,9 @@ public:
|
||||
// Otherwise apply a threshold test to convert to digital, with remapping from axes to digital inputs.
|
||||
using Type = Joystick::Input::Type;
|
||||
switch(input.type) {
|
||||
default: did_set_input(input, value > 0.5f); break;
|
||||
default:
|
||||
did_set_input(input, value > 0.5f);
|
||||
break;
|
||||
case Type::Horizontal:
|
||||
did_set_input(Input(Type::Left, input.info.control.index), value <= 0.25f);
|
||||
did_set_input(Input(Type::Right, input.info.control.index), value >= 0.75f);
|
||||
@@ -221,15 +248,45 @@ public:
|
||||
protected:
|
||||
virtual void did_set_input([[maybe_unused]] const Input &input, [[maybe_unused]] float value) {}
|
||||
virtual void did_set_input([[maybe_unused]] const Input &input, [[maybe_unused]] bool value) {}
|
||||
virtual float digital_minimum() const { return 0.1f; }
|
||||
virtual float digital_maximum() const { return 0.9f; }
|
||||
|
||||
private:
|
||||
const std::vector<Input> inputs_;
|
||||
|
||||
enum class StickType {
|
||||
Digital,
|
||||
Analogue
|
||||
struct Stick {
|
||||
enum class Type {
|
||||
Digital,
|
||||
Analogue
|
||||
} type;
|
||||
|
||||
void apply_digital(const Input &input, const bool is_active) {
|
||||
const int mask = [&] {
|
||||
switch(input.type) {
|
||||
default: return 0;
|
||||
case Input::Type::Up: return 1 << 0;
|
||||
case Input::Type::Down: return 1 << 1;
|
||||
|
||||
case Input::Type::Left: return 1 << 2;
|
||||
case Input::Type::Right: return 1 << 3;
|
||||
}
|
||||
} ();
|
||||
if(is_active) {
|
||||
digital_inputs_ |= mask;
|
||||
} else {
|
||||
digital_inputs_ &= ~mask;
|
||||
}
|
||||
}
|
||||
int digital_mask(const Input::Type axis) const {
|
||||
switch(axis) {
|
||||
default: return 0;
|
||||
case Input::Type::Horizontal: return (digital_inputs_ >> 2) & 3;
|
||||
case Input::Type::Vertical: return (digital_inputs_ >> 0) & 3;
|
||||
}
|
||||
}
|
||||
int digital_inputs_ = 0;
|
||||
};
|
||||
std::vector<StickType> stick_types_;
|
||||
std::vector<Stick> sticks_;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
+3
-2
@@ -8,6 +8,7 @@
|
||||
|
||||
#include "Keyboard.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstddef>
|
||||
|
||||
using namespace Inputs;
|
||||
@@ -28,7 +29,7 @@ bool Keyboard::set_key_pressed(const Key key, const char, const bool is_pressed,
|
||||
}
|
||||
key_states_[key_offset] = is_pressed;
|
||||
|
||||
if(delegate_) return delegate_->keyboard_did_change_key(this, key, is_pressed);
|
||||
if(delegate_) return delegate_->keyboard_did_change_key(*this, key, is_pressed);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -38,7 +39,7 @@ const std::set<Inputs::Keyboard::Key> &Keyboard::get_essential_modifiers() const
|
||||
|
||||
void Keyboard::reset_all_keys() {
|
||||
std::fill(key_states_.begin(), key_states_.end(), false);
|
||||
if(delegate_) delegate_->reset_all_keys(this);
|
||||
if(delegate_) delegate_->reset_all_keys(*this);
|
||||
}
|
||||
|
||||
void Keyboard::set_delegate(Delegate *const delegate) {
|
||||
|
||||
+2
-2
@@ -73,8 +73,8 @@ public:
|
||||
|
||||
// Delegate interface.
|
||||
struct Delegate {
|
||||
virtual bool keyboard_did_change_key(Keyboard *keyboard, Key key, bool is_pressed) = 0;
|
||||
virtual void reset_all_keys(Keyboard *keyboard) = 0;
|
||||
virtual bool keyboard_did_change_key(Keyboard &, Key, bool is_pressed) = 0;
|
||||
virtual void reset_all_keys(Keyboard &) = 0;
|
||||
};
|
||||
void set_delegate(Delegate *);
|
||||
bool get_key_state(Key) const;
|
||||
|
||||
+21
-21
@@ -15,30 +15,30 @@ namespace Inputs {
|
||||
some quantity of buttons.
|
||||
*/
|
||||
class Mouse {
|
||||
public:
|
||||
/*!
|
||||
Indicates a movement of the mouse.
|
||||
*/
|
||||
virtual void move([[maybe_unused]] const int x, [[maybe_unused]] const int y) {}
|
||||
public:
|
||||
/*!
|
||||
Indicates a movement of the mouse.
|
||||
*/
|
||||
virtual void move([[maybe_unused]] const int x, [[maybe_unused]] const int y) {}
|
||||
|
||||
/*!
|
||||
@returns the number of buttons on this mouse.
|
||||
*/
|
||||
virtual int get_number_of_buttons() const {
|
||||
return 1;
|
||||
}
|
||||
/*!
|
||||
@returns the number of buttons on this mouse.
|
||||
*/
|
||||
virtual int get_number_of_buttons() const {
|
||||
return 1;
|
||||
}
|
||||
|
||||
/*!
|
||||
Indicates that button @c index is now either pressed or unpressed.
|
||||
The intention is that @c index be semantic, not positional:
|
||||
0 for the primary button, 1 for the secondary, 2 for the tertiary, etc.
|
||||
*/
|
||||
virtual void set_button_pressed([[maybe_unused]] const int index, [[maybe_unused]] const bool is_pressed) {}
|
||||
/*!
|
||||
Indicates that button @c index is now either pressed or unpressed.
|
||||
The intention is that @c index be semantic, not positional:
|
||||
0 for the primary button, 1 for the secondary, 2 for the tertiary, etc.
|
||||
*/
|
||||
virtual void set_button_pressed([[maybe_unused]] const int index, [[maybe_unused]] const bool is_pressed) {}
|
||||
|
||||
/*!
|
||||
Releases all depressed buttons.
|
||||
*/
|
||||
virtual void reset_all_buttons() {}
|
||||
/*!
|
||||
Releases all depressed buttons.
|
||||
*/
|
||||
virtual void reset_all_buttons() {}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -79,23 +79,23 @@ template <int i, typename SchedulerT> void OperationMapper<Page::Page0>::dispatc
|
||||
using AM = AddressingMode;
|
||||
using O = Operation;
|
||||
|
||||
constexpr auto upper = (i >> 4) & 0xf;
|
||||
constexpr auto lower = (i >> 0) & 0xf;
|
||||
static constexpr auto upper = (i >> 4) & 0xf;
|
||||
static constexpr auto lower = (i >> 0) & 0xf;
|
||||
|
||||
constexpr AddressingMode modes[] = {
|
||||
static constexpr AddressingMode modes[] = {
|
||||
AM::Immediate, AM::Direct, AM::Indexed, AM::Extended
|
||||
};
|
||||
constexpr AddressingMode mode = modes[(i >> 4) & 3];
|
||||
static constexpr AddressingMode mode = modes[(i >> 4) & 3];
|
||||
|
||||
switch(upper) {
|
||||
default: break;
|
||||
|
||||
case 0x1: {
|
||||
constexpr Operation operations[] = {
|
||||
static constexpr Operation operations[] = {
|
||||
O::Page1, O::Page2, O::NOP, O::SYNC, O::None, O::None, O::LBRA, O::LBSR,
|
||||
O::None, O::DAA, O::ORCC, O::None, O::ANDCC, O::SEX, O::EXG, O::TFR,
|
||||
};
|
||||
constexpr AddressingMode modes[] = {
|
||||
static constexpr AddressingMode modes[] = {
|
||||
AM::Variant, AM::Variant, AM::Inherent, AM::Inherent,
|
||||
AM::Illegal, AM::Illegal, AM::Relative, AM::Relative,
|
||||
AM::Illegal, AM::Inherent, AM::Immediate, AM::Illegal,
|
||||
@@ -104,18 +104,18 @@ template <int i, typename SchedulerT> void OperationMapper<Page::Page0>::dispatc
|
||||
s.template schedule<operations[lower], modes[lower]>();
|
||||
} break;
|
||||
case 0x2: {
|
||||
constexpr Operation operations[] = {
|
||||
static constexpr Operation operations[] = {
|
||||
O::BRA, O::BRN, O::BHI, O::BLS, O::BCC, O::BCS, O::BNE, O::BEQ,
|
||||
O::BVC, O::BVS, O::BPL, O::BMI, O::BGE, O::BLT, O::BGT, O::BLE,
|
||||
};
|
||||
s.template schedule<operations[lower], AM::Relative>();
|
||||
} break;
|
||||
case 0x3: {
|
||||
constexpr Operation operations[] = {
|
||||
static constexpr Operation operations[] = {
|
||||
O::LEAX, O::LEAY, O::LEAS, O::LEAU, O::PSHS, O::PULS, O::PSHU, O::PULU,
|
||||
O::None, O::RTS, O::ABX, O::RTI, O::CWAI, O::MUL, O::RESET, O::SWI,
|
||||
};
|
||||
constexpr auto op = operations[lower];
|
||||
static constexpr auto op = operations[lower];
|
||||
switch(lower) {
|
||||
case 0x0: case 0x1: case 0x2: case 0x3:
|
||||
s.template schedule<op, AM::Indexed>();
|
||||
@@ -132,31 +132,31 @@ template <int i, typename SchedulerT> void OperationMapper<Page::Page0>::dispatc
|
||||
}
|
||||
} break;
|
||||
case 0x4: {
|
||||
constexpr Operation operations[] = {
|
||||
static constexpr Operation operations[] = {
|
||||
O::NEGA, O::None, O::None, O::COMA, O::LSRA, O::None, O::RORA, O::ASRA,
|
||||
O::LSLA, O::ROLA, O::DECA, O::None, O::INCA, O::TSTA, O::None, O::CLRA,
|
||||
};
|
||||
constexpr auto op = operations[lower];
|
||||
static constexpr auto op = operations[lower];
|
||||
s.template schedule<op, op == O::None ? AM::Illegal : AM::Inherent>();
|
||||
} break;
|
||||
case 0x5: {
|
||||
constexpr Operation operations[] = {
|
||||
static constexpr Operation operations[] = {
|
||||
O::NEGB, O::None, O::None, O::COMB, O::LSRB, O::None, O::RORB, O::ASRB,
|
||||
O::LSLB, O::ROLB, O::DECB, O::None, O::INCB, O::TSTB, O::None, O::CLRB,
|
||||
};
|
||||
constexpr auto op = operations[lower];
|
||||
static constexpr auto op = operations[lower];
|
||||
s.template schedule<op, op == O::None ? AM::Illegal : AM::Inherent>();
|
||||
} break;
|
||||
case 0x0: case 0x6: case 0x7: {
|
||||
constexpr Operation operations[] = {
|
||||
static constexpr Operation operations[] = {
|
||||
O::NEG, O::None, O::None, O::COM, O::LSR, O::None, O::ROR, O::ASR,
|
||||
O::LSL, O::ROL, O::DEC, O::None, O::INC, O::TST, O::JMP, O::CLR,
|
||||
};
|
||||
constexpr auto op = operations[lower];
|
||||
static constexpr auto op = operations[lower];
|
||||
s.template schedule<op, op == O::None ? AM::Illegal : upper == 0 ? AM::Direct : mode>();
|
||||
} break;
|
||||
case 0x8: case 0x9: case 0xa: case 0xb: {
|
||||
constexpr Operation operations[] = {
|
||||
static constexpr Operation operations[] = {
|
||||
O::SUBA, O::CMPA, O::SBCA, O::SUBD, O::ANDA, O::BITA, O::LDA, O::STA,
|
||||
O::EORA, O::ADCA, O::ORA, O::ADDA, O::CMPX, O::JSR, O::LDX, O::STX,
|
||||
};
|
||||
@@ -164,7 +164,7 @@ template <int i, typename SchedulerT> void OperationMapper<Page::Page0>::dispatc
|
||||
else s.template schedule<operations[lower], mode>();
|
||||
} break;
|
||||
case 0xc: case 0xd: case 0xe: case 0xf: {
|
||||
constexpr Operation operations[] = {
|
||||
static constexpr Operation operations[] = {
|
||||
O::SUBB, O::CMPB, O::SBCB, O::ADDD, O::ANDB, O::BITB, O::LDB, O::STB,
|
||||
O::EORB, O::ADCB, O::ORB, O::ADDB, O::LDD, O::STD, O::LDU, O::STU,
|
||||
};
|
||||
@@ -178,13 +178,13 @@ template <int i, typename SchedulerT> void OperationMapper<Page::Page1>::dispatc
|
||||
using AM = AddressingMode;
|
||||
using O = Operation;
|
||||
|
||||
constexpr AddressingMode modes[] = {
|
||||
static constexpr AddressingMode modes[] = {
|
||||
AM::Immediate, AM::Direct, AM::Indexed, AM::Extended
|
||||
};
|
||||
constexpr auto mode = modes[(i >> 4) & 3];
|
||||
static constexpr auto mode = modes[(i >> 4) & 3];
|
||||
|
||||
if constexpr (i >= 0x21 && i < 0x30) {
|
||||
constexpr Operation operations[] = {
|
||||
static constexpr Operation operations[] = {
|
||||
O::LBRN, O::LBHI, O::LBLS, O::LBCC, O::LBCS, O::LBNE, O::LBEQ,
|
||||
O::LBVC, O::LBVS, O::LBPL, O::LBMI, O::LBGE, O::LBLT, O::LBGT, O::LBLE,
|
||||
};
|
||||
@@ -219,10 +219,10 @@ template <int i, typename SchedulerT> void OperationMapper<Page::Page2>::dispatc
|
||||
using AM = AddressingMode;
|
||||
using O = Operation;
|
||||
|
||||
constexpr AddressingMode modes[] = {
|
||||
static constexpr AddressingMode modes[] = {
|
||||
AM::Immediate, AM::Direct, AM::Indexed, AM::Extended
|
||||
};
|
||||
constexpr auto mode = modes[(i >> 4) & 3];
|
||||
static constexpr auto mode = modes[(i >> 4) & 3];
|
||||
|
||||
switch(i) {
|
||||
default: s.template schedule<O::None, AM::Illegal>(); break;
|
||||
|
||||
@@ -8,6 +8,8 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <bit>
|
||||
|
||||
namespace InstructionSet::ARM {
|
||||
|
||||
enum class ShiftType {
|
||||
@@ -99,14 +101,12 @@ void shift(uint32_t &source, uint32_t amount, const typename Carry<set_carry>::t
|
||||
amount &= 31;
|
||||
if(amount) {
|
||||
if constexpr (set_carry) carry = source & (1 << (amount - 1));
|
||||
source = (source >> amount) | (source << (32 - amount));
|
||||
source = std::rotr(source, int(amount));
|
||||
} else {
|
||||
if constexpr (set_carry) carry = source & 0x8000'0000;
|
||||
}
|
||||
} break;
|
||||
|
||||
// TODO: upon adoption of C++20, use std::rotr.
|
||||
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -172,7 +172,7 @@ struct Disassembler {
|
||||
}
|
||||
|
||||
template <Flags f> void perform(const DataProcessing fields) {
|
||||
constexpr DataProcessingFlags flags(f);
|
||||
static constexpr DataProcessingFlags flags(f);
|
||||
|
||||
instruction_.operand1.type = Operand::Type::Register;
|
||||
instruction_.operand1.value = fields.operand1();
|
||||
@@ -215,7 +215,7 @@ struct Disassembler {
|
||||
|
||||
template <Flags> void perform(Multiply) {}
|
||||
template <Flags f> void perform(const SingleDataTransfer fields) {
|
||||
constexpr SingleDataTransferFlags flags(f);
|
||||
static constexpr SingleDataTransferFlags flags(f);
|
||||
instruction_.operation =
|
||||
(flags.operation() == SingleDataTransferFlags::Operation::STR) ?
|
||||
Instruction::Operation::STR : Instruction::Operation::LDR;
|
||||
@@ -227,7 +227,7 @@ struct Disassembler {
|
||||
instruction_.operand1.value = fields.base();
|
||||
}
|
||||
template <Flags f> void perform(const BlockDataTransfer fields) {
|
||||
constexpr BlockDataTransferFlags flags(f);
|
||||
static constexpr BlockDataTransferFlags flags(f);
|
||||
instruction_.operation =
|
||||
(flags.operation() == BlockDataTransferFlags::Operation::STM) ?
|
||||
Instruction::Operation::STM : Instruction::Operation::LDM;
|
||||
@@ -239,7 +239,7 @@ struct Disassembler {
|
||||
instruction_.operand1.value = fields.register_list();
|
||||
}
|
||||
template <Flags f> void perform(const Branch fields) {
|
||||
constexpr BranchFlags flags(f);
|
||||
static constexpr BranchFlags flags(f);
|
||||
instruction_.operation =
|
||||
(flags.operation() == BranchFlags::Operation::BL) ?
|
||||
Instruction::Operation::BL : Instruction::Operation::B;
|
||||
@@ -247,7 +247,7 @@ struct Disassembler {
|
||||
instruction_.operand1.value = fields.offset();
|
||||
}
|
||||
template <Flags f> void perform(CoprocessorRegisterTransfer) {
|
||||
constexpr CoprocessorRegisterTransferFlags flags(f);
|
||||
static constexpr CoprocessorRegisterTransferFlags flags(f);
|
||||
instruction_.operation =
|
||||
(flags.operation() == CoprocessorRegisterTransferFlags::Operation::MRC) ?
|
||||
Instruction::Operation::MRC : Instruction::Operation::MCR;
|
||||
|
||||
@@ -63,7 +63,7 @@ struct Executor {
|
||||
return condition == Condition::AL ? true : registers_.test(condition);
|
||||
}
|
||||
|
||||
template <bool allow_register, bool set_carry, typename FieldsT>
|
||||
template <bool set_carry, typename FieldsT>
|
||||
uint32_t decode_shift(const FieldsT fields, uint32_t &rotate_carry, const uint32_t pc_offset) {
|
||||
// "When R15 appears in the Rm position it will give the value of the PC together
|
||||
// with the PSR flags to the barrel shifter. ...
|
||||
@@ -78,8 +78,7 @@ struct Executor {
|
||||
operand2 = registers_[fields.operand2()];
|
||||
}
|
||||
|
||||
// TODO: in C++20, a quick `if constexpr (requires` can eliminate the `allow_register` parameter.
|
||||
if constexpr (allow_register) {
|
||||
if constexpr (requires {fields.shift_count_is_register();}) {
|
||||
if(fields.shift_count_is_register()) {
|
||||
uint32_t shift_amount;
|
||||
|
||||
@@ -106,7 +105,7 @@ struct Executor {
|
||||
}
|
||||
|
||||
template <Flags f> void perform(const DataProcessing fields) {
|
||||
constexpr DataProcessingFlags flags(f);
|
||||
static constexpr DataProcessingFlags flags(f);
|
||||
const bool shift_by_register = !flags.operand2_is_immediate() && fields.shift_count_is_register();
|
||||
|
||||
// Write a raw result into the PC proxy if the target is R15; it'll be stored properly later.
|
||||
@@ -128,14 +127,14 @@ struct Executor {
|
||||
uint32_t rotate_carry = registers_.c();
|
||||
|
||||
// Populate carry from the shift only if it'll be used.
|
||||
constexpr bool shift_sets_carry = is_logical(flags.operation()) && flags.set_condition_codes();
|
||||
static constexpr bool shift_sets_carry = is_logical(flags.operation()) && flags.set_condition_codes();
|
||||
|
||||
// Get operand 2.
|
||||
if constexpr (flags.operand2_is_immediate()) {
|
||||
operand2 = fields.immediate();
|
||||
shift<ShiftType::RotateRight, shift_sets_carry, false>(operand2, fields.rotate(), rotate_carry);
|
||||
} else {
|
||||
operand2 = decode_shift<true, shift_sets_carry>(fields, rotate_carry, shift_by_register ? 8 : 4);
|
||||
operand2 = decode_shift<shift_sets_carry>(fields, rotate_carry, shift_by_register ? 8 : 4);
|
||||
}
|
||||
|
||||
uint32_t conditions = 0;
|
||||
@@ -231,7 +230,7 @@ struct Executor {
|
||||
}
|
||||
|
||||
template <Flags f> void perform(const Multiply fields) {
|
||||
constexpr MultiplyFlags flags(f);
|
||||
static constexpr MultiplyFlags flags(f);
|
||||
|
||||
// R15 rules:
|
||||
//
|
||||
@@ -258,7 +257,7 @@ struct Executor {
|
||||
}
|
||||
|
||||
template <Flags f> void perform(const Branch branch) {
|
||||
constexpr BranchFlags flags(f);
|
||||
static constexpr BranchFlags flags(f);
|
||||
|
||||
if constexpr (flags.operation() == BranchFlags::Operation::BL) {
|
||||
registers_[14] = registers_.pc_status(0);
|
||||
@@ -267,7 +266,7 @@ struct Executor {
|
||||
}
|
||||
|
||||
template <Flags f> void perform(const SingleDataTransfer transfer) {
|
||||
constexpr SingleDataTransferFlags flags(f);
|
||||
static constexpr SingleDataTransferFlags flags(f);
|
||||
|
||||
// Calculate offset.
|
||||
uint32_t offset;
|
||||
@@ -276,7 +275,7 @@ struct Executor {
|
||||
// the register specified shift amounts are not available
|
||||
// in this instruction class.
|
||||
uint32_t carry = registers_.c();
|
||||
offset = decode_shift<false, false>(transfer, carry, 4);
|
||||
offset = decode_shift<false>(transfer, carry, 4);
|
||||
} else {
|
||||
offset = transfer.immediate();
|
||||
}
|
||||
@@ -392,8 +391,8 @@ struct Executor {
|
||||
}
|
||||
}
|
||||
template <Flags f> void perform(const BlockDataTransfer transfer) {
|
||||
constexpr BlockDataTransferFlags flags(f);
|
||||
constexpr bool is_ldm = flags.operation() == BlockDataTransferFlags::Operation::LDM;
|
||||
static constexpr BlockDataTransferFlags flags(f);
|
||||
static constexpr bool is_ldm = flags.operation() == BlockDataTransferFlags::Operation::LDM;
|
||||
|
||||
// Ensure that *base points to the base register if it can be written back;
|
||||
// also set address to the base.
|
||||
|
||||
@@ -293,18 +293,7 @@ struct BlockDataTransfer: public WithShiftControlBits {
|
||||
uint32_t base() const { return (opcode_ >> 16) & 0xf; }
|
||||
|
||||
/// A bitfield indicating which registers to load or store.
|
||||
uint16_t register_list() const { return static_cast<uint16_t>(opcode_); }
|
||||
uint32_t popcount() const {
|
||||
const uint16_t list = register_list();
|
||||
|
||||
// TODO: just use std::popcount when adopting C++20.
|
||||
uint32_t total = ((list & 0xaaaa) >> 1) + (list & 0x5555);
|
||||
total = ((total & 0xcccc) >> 2) + (total & 0x3333);
|
||||
total = ((total & 0xf0f0) >> 4) + (total & 0x0f0f);
|
||||
total = ((total & 0xff00) >> 8) + (total & 0x00ff);
|
||||
|
||||
return total;
|
||||
}
|
||||
uint16_t register_list() const { return static_cast<uint16_t>(opcode_); }
|
||||
};
|
||||
|
||||
//
|
||||
|
||||
@@ -17,7 +17,8 @@ Instruction Decoder::instrucion_for_opcode(const 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);
|
||||
#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);
|
||||
|
||||
@@ -18,8 +18,8 @@
|
||||
using namespace InstructionSet::M50740;
|
||||
|
||||
namespace {
|
||||
constexpr int port_remap[] = {0, 1, 2, 0, 3};
|
||||
Log::Logger<Log::Source::M50740> logger;
|
||||
constexpr int port_remap[] = {0, 1, 2, 0, 3};
|
||||
using Logger = Log::Logger<Log::Source::M50740>;
|
||||
}
|
||||
|
||||
Executor::Executor(PortHandler &port_handler) : port_handler_(port_handler) {
|
||||
@@ -82,13 +82,13 @@ uint8_t Executor::read(uint16_t address) {
|
||||
port_handler_.run_ports_for(cycles_since_port_handler_.flush<Cycles>());
|
||||
switch(address) {
|
||||
default:
|
||||
logger.error().append("Unrecognised read from %02x", address);
|
||||
Logger::error().append("Unrecognised read from %02x", 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:
|
||||
logger.error().append("Unimplemented Port R read from %04x", address);
|
||||
Logger::error().append("Unimplemented Port R read from %04x", address);
|
||||
return 0x00;
|
||||
|
||||
// Ports P0–P3.
|
||||
@@ -133,7 +133,7 @@ void Executor::write(uint16_t address, const uint8_t value) {
|
||||
|
||||
// ROM 'writes' are almost as easy (albeit unexpected).
|
||||
if(address >= 0x100) {
|
||||
logger.info().append("Attempted ROM write of %02x to %04x", value, address);
|
||||
Logger::info().append("Attempted ROM write of %02x to %04x", value, address);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -142,13 +142,13 @@ void Executor::write(uint16_t address, const uint8_t value) {
|
||||
|
||||
switch(address) {
|
||||
default:
|
||||
logger.error().append("Unrecognised write of %02x to %04x", value, address);
|
||||
Logger::error().append("Unrecognised write of %02x to %04x", value, 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:
|
||||
logger.error().append("Unimplemented Port R write of %02x to %04x", value, address);
|
||||
Logger::error().append("Unimplemented Port R write of %02x to %04x", value, address);
|
||||
break;
|
||||
|
||||
// Ports P0–P3.
|
||||
@@ -241,7 +241,7 @@ template <Operation operation, AddressingMode addressing_mode> void Executor::pe
|
||||
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[] = {
|
||||
static constexpr int t_lengths[] = {
|
||||
0,
|
||||
operation == Operation::LDA ? 2 : (operation == Operation::CMP ? 1 : 3)
|
||||
};
|
||||
@@ -444,7 +444,7 @@ template <Operation operation, AddressingMode addressing_mode> void Executor::pe
|
||||
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));
|
||||
static constexpr uint8_t mask = 1 << (int(operation) - int(Operation::BBS0));
|
||||
if(value & mask) {
|
||||
set_program_counter(uint16_t(address));
|
||||
subtract_duration(2);
|
||||
@@ -454,7 +454,7 @@ template <Operation operation, AddressingMode addressing_mode> void Executor::pe
|
||||
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));
|
||||
static constexpr uint8_t mask = 1 << (int(operation) - int(Operation::BBC0));
|
||||
if(!(value & mask)) {
|
||||
set_program_counter(uint16_t(address));
|
||||
subtract_duration(2);
|
||||
@@ -792,7 +792,7 @@ template <Operation operation> void Executor::perform(uint8_t *operand [[maybe_u
|
||||
*/
|
||||
|
||||
default:
|
||||
logger.error().append("Unimplemented operation: %d", operation);
|
||||
Logger::error().append("Unimplemented operation: %d", operation);
|
||||
assert(false);
|
||||
}
|
||||
}
|
||||
@@ -805,8 +805,9 @@ inline void Executor::subtract_duration(const int duration) {
|
||||
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.
|
||||
static 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 clock_ticks = timer_divider_ / t12_divider;
|
||||
@@ -835,13 +836,13 @@ inline void Executor::subtract_duration(const int duration) {
|
||||
}
|
||||
} break;
|
||||
case 0x04:
|
||||
logger.error().append("TODO: Timer X; Pulse output mode");
|
||||
Logger::error().append("TODO: Timer X; Pulse output mode");
|
||||
break;
|
||||
case 0x08:
|
||||
logger.error().append("TODO: Timer X; Event counter mode");
|
||||
Logger::error().append("TODO: Timer X; Event counter mode");
|
||||
break;
|
||||
case 0x0c:
|
||||
logger.error().append("TODO: Timer X; Pulse width measurement mode");
|
||||
Logger::error().append("TODO: Timer X; Pulse width measurement mode");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -202,7 +202,7 @@ template <typename IntT> IntT Executor<model, BusHandler>::State::read_pc() {
|
||||
template <Model model, typename BusHandler>
|
||||
uint32_t Executor<model, BusHandler>::State::index_8bitdisplacement(uint32_t base) {
|
||||
// Determine whether full extension addressing modes are supported.
|
||||
constexpr bool supports_full_extensions = model >= Model::M68020;
|
||||
static constexpr bool supports_full_extensions = model >= Model::M68020;
|
||||
|
||||
// Get the [first] extension word.
|
||||
const auto extension = read_pc<uint16_t>();
|
||||
|
||||
@@ -14,6 +14,7 @@ template <Model model, Operation t_operation> constexpr uint8_t operand_flags(co
|
||||
switch((t_operation != Operation::Undefined) ? t_operation : r_operation) {
|
||||
default:
|
||||
assert(false);
|
||||
[[fallthrough]];
|
||||
|
||||
//
|
||||
// No operands are fetched or stored.
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
#include "InstructionSets/M68k/ExceptionVectors.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <bit>
|
||||
#include <cassert>
|
||||
#include <cmath>
|
||||
|
||||
@@ -33,7 +34,7 @@ static void add_sub(const IntT source, IntT &destination, Status &status) {
|
||||
static_assert(!std::numeric_limits<IntT>::is_signed);
|
||||
|
||||
static_assert(operation == Numeric::Operation::Add || operation == Numeric::Operation::Subtract);
|
||||
constexpr bool is_add = operation == Numeric::Operation::Add;
|
||||
static constexpr bool is_add = operation == Numeric::Operation::Add;
|
||||
IntT result = is_add ?
|
||||
destination + source :
|
||||
destination - source;
|
||||
@@ -312,7 +313,7 @@ void shift(const uint32_t source, IntT &destination, Status &status, FlowControl
|
||||
operation == Operation::LSRb || operation == Operation::LSRw || operation == Operation::LSRl
|
||||
);
|
||||
|
||||
constexpr auto size = Numeric::bit_size<IntT>();
|
||||
static constexpr auto size = Numeric::bit_size<IntT>();
|
||||
const auto shift = shift_count<IntT>(uint8_t(source), flow_controller);
|
||||
|
||||
if(!shift) {
|
||||
@@ -407,7 +408,7 @@ void rotate(const uint32_t source, IntT &destination, Status &status, FlowContro
|
||||
operation == Operation::RORb || operation == Operation::RORw || operation == Operation::RORl
|
||||
);
|
||||
|
||||
constexpr auto size = Numeric::bit_size<IntT>();
|
||||
static constexpr auto size = Numeric::bit_size<IntT>();
|
||||
auto shift = shift_count<IntT>(uint8_t(source), flow_controller);
|
||||
|
||||
if(!shift) {
|
||||
@@ -417,21 +418,11 @@ void rotate(const uint32_t source, IntT &destination, Status &status, FlowContro
|
||||
|
||||
switch(operation) {
|
||||
case Operation::ROLb: case Operation::ROLw: case Operation::ROLl:
|
||||
if(shift) {
|
||||
destination = IntT(
|
||||
(destination << shift) |
|
||||
(destination >> (size - shift))
|
||||
);
|
||||
}
|
||||
destination = std::rotl<IntT>(destination, shift);
|
||||
status.carry_flag = Status::FlagT(destination & 1);
|
||||
break;
|
||||
case Operation::RORb: case Operation::RORw: case Operation::RORl:
|
||||
if(shift) {
|
||||
destination = IntT(
|
||||
(destination >> shift) |
|
||||
(destination << (size - shift))
|
||||
);
|
||||
}
|
||||
destination = std::rotr<IntT>(destination, shift);
|
||||
status.carry_flag = Status::FlagT(destination & Numeric::top_bit<IntT>());
|
||||
break;
|
||||
}
|
||||
@@ -449,7 +440,7 @@ void rox(const uint32_t source, IntT &destination, Status &status, FlowControlle
|
||||
operation == Operation::ROXRb || operation == Operation::ROXRw || operation == Operation::ROXRl
|
||||
);
|
||||
|
||||
constexpr auto size = Numeric::bit_size<IntT>();
|
||||
static constexpr auto size = Numeric::bit_size<IntT>();
|
||||
auto shift = shift_count<IntT>(uint8_t(source), flow_controller) % (size + 1);
|
||||
|
||||
if(!shift) {
|
||||
|
||||
@@ -14,8 +14,6 @@ using namespace InstructionSet::M68k;
|
||||
|
||||
std::string Preinstruction::operand_description(const int index, const int opcode) const {
|
||||
switch(mode(index)) {
|
||||
default: assert(false);
|
||||
|
||||
case AddressingMode::None:
|
||||
return "";
|
||||
|
||||
@@ -54,7 +52,12 @@ std::string Preinstruction::operand_description(const int index, const int opcod
|
||||
return "Q";
|
||||
}
|
||||
return std::to_string(int(quick(uint16_t(opcode), operation)));
|
||||
|
||||
// TODO: 68020+ modes.
|
||||
default: break;
|
||||
}
|
||||
assert(false);
|
||||
return "[TODO]";
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
@@ -1500,7 +1500,7 @@ struct Instruction {
|
||||
|
||||
/// A 24-bit signed number; provided as already sign extended.
|
||||
int32_t li() const {
|
||||
constexpr uint32_t extensions[2] = {
|
||||
static constexpr uint32_t extensions[2] = {
|
||||
0x0000'0000,
|
||||
0xfc00'0000
|
||||
};
|
||||
|
||||
@@ -8,6 +8,8 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <type_traits>
|
||||
|
||||
namespace InstructionSet::x86 {
|
||||
|
||||
/// Explains the type of access that `perform` intends to perform; is provided as a template parameter to whatever
|
||||
@@ -30,29 +32,54 @@ enum class AccessType {
|
||||
PreauthorisedRead,
|
||||
};
|
||||
|
||||
constexpr const char *to_string(const AccessType type) {
|
||||
switch(type) {
|
||||
case AccessType::Read: return "read";
|
||||
case AccessType::Write: return "write";
|
||||
case AccessType::ReadModifyWrite: return "read-modify-write";
|
||||
case AccessType::PreauthorisedRead: return "preauthorised read";
|
||||
}
|
||||
}
|
||||
|
||||
constexpr bool is_writeable(const AccessType type) {
|
||||
return type == AccessType::ReadModifyWrite || type == AccessType::Write;
|
||||
}
|
||||
|
||||
// Allow only 8-, 16- and 32-bit unsigned int accesses.
|
||||
template <typename IntT>
|
||||
concept is_x86_data_type
|
||||
= std::is_same_v<IntT, uint8_t> || std::is_same_v<IntT, uint16_t> || std::is_same_v<IntT, uint32_t>;
|
||||
|
||||
template <typename IntT, AccessType type> struct Accessor;
|
||||
|
||||
// Reads: return a value directly.
|
||||
template <typename IntT> struct Accessor<IntT, AccessType::Read> { using type = const IntT; };
|
||||
template <typename IntT> struct Accessor<IntT, AccessType::PreauthorisedRead> { using type = const IntT; };
|
||||
template <typename IntT>
|
||||
requires is_x86_data_type<IntT>
|
||||
struct Accessor<IntT, AccessType::Read> { using type = IntT; };
|
||||
|
||||
template <typename IntT>
|
||||
requires is_x86_data_type<IntT>
|
||||
struct Accessor<IntT, AccessType::PreauthorisedRead> { using type = IntT; };
|
||||
|
||||
// Writes: return a custom type that can be written but not read.
|
||||
template <typename IntT>
|
||||
requires is_x86_data_type<IntT>
|
||||
class Writeable {
|
||||
public:
|
||||
Writeable(IntT &target) : target_(target) {}
|
||||
IntT operator=(IntT value) { return target_ = value; }
|
||||
constexpr Writeable(IntT &target) noexcept : target_(target) {}
|
||||
IntT operator=(const IntT value) { return target_ = value; }
|
||||
private:
|
||||
IntT &target_;
|
||||
};
|
||||
template <typename IntT> struct Accessor<IntT, AccessType::Write> { using type = Writeable<IntT>; };
|
||||
|
||||
template <typename IntT>
|
||||
requires is_x86_data_type<IntT>
|
||||
struct Accessor<IntT, AccessType::Write> { using type = Writeable<IntT>; };
|
||||
|
||||
// Read-modify-writes: return a reference.
|
||||
template <typename IntT> struct Accessor<IntT, AccessType::ReadModifyWrite> { using type = IntT &; };
|
||||
template <typename IntT>
|
||||
requires is_x86_data_type<IntT>
|
||||
struct Accessor<IntT, AccessType::ReadModifyWrite> { using type = IntT &; };
|
||||
|
||||
// Shorthands; assumed that preauthorised reads have the same return type as reads.
|
||||
template<typename IntT> using read_t = typename Accessor<IntT, AccessType::Read>::type;
|
||||
|
||||
@@ -30,6 +30,7 @@ std::pair<int, typename Decoder<model>::InstructionT> Decoder<model>::decode(
|
||||
// without any loss of context. This reduces the risk of the decoder tricking a caller into
|
||||
// an infinite loop.
|
||||
static constexpr int max_instruction_length = model >= Model::i80386 ? 15 : (model == Model::i80286 ? 10 : 65536);
|
||||
static constexpr bool is_32bit = instruction_type(model) == InstructionType::Bits32;
|
||||
const uint8_t *const end = source + std::min(length, size_t(max_instruction_length - consumed_));
|
||||
|
||||
// MARK: - Prefixes (if present) and the opcode.
|
||||
@@ -194,6 +195,8 @@ std::pair<int, typename Decoder<model>::InstructionT> Decoder<model>::decode(
|
||||
displacement(Operation::JP, DataSize::Byte);
|
||||
} else {
|
||||
immediate(Operation::PUSH, DataSize::Byte);
|
||||
operation_size_ = data_size_;
|
||||
sign_extend_operand_ = true;
|
||||
}
|
||||
break;
|
||||
case 0x6b:
|
||||
@@ -688,7 +691,7 @@ std::pair<int, typename Decoder<model>::InstructionT> Decoder<model>::decode(
|
||||
};
|
||||
displacement_size_ = sizes[mod];
|
||||
|
||||
if(is_32bit(model) && address_size_ == AddressSize::b32) {
|
||||
if(is_32bit && 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]);
|
||||
@@ -734,9 +737,8 @@ std::pair<int, typename Decoder<model>::InstructionT> Decoder<model>::decode(
|
||||
|
||||
switch(reg) {
|
||||
default:
|
||||
// case 1 is treated as another form of TEST on the 8086.
|
||||
// (and, I guess, the 80186?)
|
||||
if constexpr (model >= Model::i80286) {
|
||||
// case 1 is treated as another form of TEST through to at least the 80286.
|
||||
if constexpr (model >= Model::i80386) {
|
||||
return undefined();
|
||||
}
|
||||
[[fallthrough]];
|
||||
@@ -763,7 +765,7 @@ std::pair<int, typename Decoder<model>::InstructionT> Decoder<model>::decode(
|
||||
|
||||
// The 16-bit chips have four segment registers;
|
||||
// the 80386 onwards has six.
|
||||
if constexpr (is_32bit(model)) {
|
||||
if constexpr (is_32bit) {
|
||||
if(masked_reg > 5) {
|
||||
return undefined();
|
||||
}
|
||||
@@ -790,15 +792,27 @@ std::pair<int, typename Decoder<model>::InstructionT> Decoder<model>::decode(
|
||||
case ModRegRMFormat::MemRegROL_to_SAR:
|
||||
destination_ = memreg;
|
||||
|
||||
// TODO: is this true? It appears empirically to be so from the PC AT BIOS, but find a source.
|
||||
if(operand_size_ != DataSize::None) {
|
||||
operand_size_ = DataSize::Byte;
|
||||
}
|
||||
|
||||
switch(reg) {
|
||||
default:
|
||||
if constexpr (model == Model::i8086) {
|
||||
if(source_ == Source::eCX) {
|
||||
set(Operation::SETMOC);
|
||||
} else {
|
||||
set(Operation::SETMO);
|
||||
}
|
||||
} else {
|
||||
switch(model) {
|
||||
case Model::i8086:
|
||||
if(source_ == Source::eCX) {
|
||||
set(Operation::SETMOC);
|
||||
} else {
|
||||
set(Operation::SETMO);
|
||||
}
|
||||
break;
|
||||
|
||||
case Model::i80286:
|
||||
set(Operation::SAL);
|
||||
break;
|
||||
|
||||
default:
|
||||
return undefined();
|
||||
}
|
||||
break;
|
||||
@@ -858,6 +872,13 @@ std::pair<int, typename Decoder<model>::InstructionT> Decoder<model>::decode(
|
||||
source_ = Source::Immediate;
|
||||
destination_ = memreg;
|
||||
operand_size_ = operation_size_;
|
||||
|
||||
// This form requires that the reg field be blank
|
||||
if constexpr (model >= Model::i80286) {
|
||||
if(reg != 0) {
|
||||
return undefined();
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case ModRegRMFormat::MemRegADD_to_CMP:
|
||||
@@ -888,7 +909,7 @@ std::pair<int, typename Decoder<model>::InstructionT> Decoder<model>::decode(
|
||||
destination_ = source_ = memreg;
|
||||
|
||||
switch(reg) {
|
||||
default: return undefined();
|
||||
default: return undefined();
|
||||
|
||||
case 0: set(Operation::SLDT); break;
|
||||
case 1: set(Operation::STR); break;
|
||||
@@ -903,7 +924,7 @@ std::pair<int, typename Decoder<model>::InstructionT> Decoder<model>::decode(
|
||||
destination_ = source_ = memreg;
|
||||
|
||||
switch(reg) {
|
||||
default: return undefined();
|
||||
default: return undefined();
|
||||
|
||||
case 0: set(Operation::SGDT); break;
|
||||
case 1: set(Operation::SIDT); break;
|
||||
@@ -943,7 +964,7 @@ std::pair<int, typename Decoder<model>::InstructionT> Decoder<model>::decode(
|
||||
|
||||
// MARK: - ScaleIndexBase
|
||||
|
||||
if constexpr (is_32bit(model)) {
|
||||
if constexpr (is_32bit) {
|
||||
if(phase_ == Phase::ScaleIndexBase && source != end) {
|
||||
sib_ = *source;
|
||||
++source;
|
||||
@@ -1010,8 +1031,13 @@ std::pair<int, typename Decoder<model>::InstructionT> Decoder<model>::decode(
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Provide a genuine measure of further bytes required.
|
||||
return std::make_pair(-(outstanding_bytes - bytes_to_consume), InstructionT());
|
||||
// Provide a genuine measure of further bytes required, or post a bad instruction
|
||||
// if the length limit has been breached.
|
||||
if(consumed_ != max_instruction_length) {
|
||||
return std::make_pair(-(outstanding_bytes - bytes_to_consume), InstructionT());
|
||||
} else {
|
||||
return overlong();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1030,6 +1056,18 @@ std::pair<int, typename Decoder<model>::InstructionT> Decoder<model>::decode(
|
||||
// operations would unlock an extra bit of storage for a net gain of 239 extra operation types and thereby
|
||||
// alleviating any concerns over whether there'll be space to handle MMX, floating point extensions, etc.
|
||||
|
||||
if constexpr (model >= Model::i80286) {
|
||||
if(operation_ == Operation::BOUND && !is_address(source_)) {
|
||||
return undefined();
|
||||
}
|
||||
}
|
||||
if(
|
||||
(operation_ == Operation::JMPfar || operation_ == Operation::CALLfar) &&
|
||||
destination_ < Source::DirectAddress
|
||||
) {
|
||||
return undefined();
|
||||
}
|
||||
|
||||
const auto result = std::make_pair(
|
||||
consumed_,
|
||||
InstructionT(
|
||||
@@ -1051,14 +1089,7 @@ std::pair<int, typename Decoder<model>::InstructionT> Decoder<model>::decode(
|
||||
|
||||
// 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));
|
||||
} else {
|
||||
result = std::make_pair(consumed_, InstructionT());
|
||||
}
|
||||
reset_parsing();
|
||||
return result;
|
||||
return overlong();
|
||||
}
|
||||
|
||||
// i.e. not done yet.
|
||||
@@ -1066,7 +1097,7 @@ std::pair<int, typename Decoder<model>::InstructionT> Decoder<model>::decode(
|
||||
}
|
||||
|
||||
template <Model model> void Decoder<model>::set_32bit_protected_mode(bool enabled) {
|
||||
if constexpr (!is_32bit(model)) {
|
||||
if constexpr (instruction_type(model) == InstructionType::Bits16) {
|
||||
assert(!enabled);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@ namespace InstructionSet::x86 {
|
||||
*/
|
||||
template <Model model> class Decoder {
|
||||
public:
|
||||
using InstructionT = Instruction<is_32bit(model)>;
|
||||
using InstructionT = Instruction<instruction_type(model)>;
|
||||
|
||||
/*!
|
||||
@returns an @c Instruction plus a size; a positive size indicates successful decoding of
|
||||
@@ -349,6 +349,7 @@ private:
|
||||
phase_ = Phase::DisplacementOrOperand;
|
||||
displacement_size_ = DataSize::Word;
|
||||
operand_size_ = DataSize::Byte;
|
||||
operation_size_ = DataSize::Byte;
|
||||
}
|
||||
|
||||
/// Sets up the operation size, oncoming phase and modregrm format for a member of the shift group (i.e. 'group 2').
|
||||
@@ -367,6 +368,16 @@ private:
|
||||
reset_parsing();
|
||||
return result;
|
||||
}
|
||||
|
||||
std::pair<int, typename Decoder<model>::InstructionT> overlong() {
|
||||
const auto consumed = consumed_;
|
||||
reset_parsing();
|
||||
if(consumed == 65536) {
|
||||
return std::make_pair(consumed, InstructionT(Operation::NOP));
|
||||
} else {
|
||||
return std::make_pair(consumed, InstructionT(ExceptionCode()));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
extern template class InstructionSet::x86::Decoder<InstructionSet::x86::Model::i8086>;
|
||||
|
||||
@@ -0,0 +1,356 @@
|
||||
//
|
||||
// Descriptors.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 19/03/2025.
|
||||
// Copyright © 2025 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Exceptions.hpp"
|
||||
#include "Instruction.hpp"
|
||||
//#include "Perform.hpp"
|
||||
|
||||
#include <cassert>
|
||||
#include <concepts>
|
||||
|
||||
namespace InstructionSet::x86 {
|
||||
|
||||
enum class DescriptorTable {
|
||||
Global, Local, Interrupt,
|
||||
};
|
||||
|
||||
struct DescriptorTablePointer {
|
||||
uint16_t limit;
|
||||
uint32_t base;
|
||||
};
|
||||
|
||||
struct DescriptorBounds {
|
||||
uint32_t begin, end;
|
||||
auto operator <=>(const DescriptorBounds &) const = default;
|
||||
};
|
||||
|
||||
enum class DescriptorType {
|
||||
Code, Data, Stack,
|
||||
CallGate, TaskGate, InterruptGate, TrapGate,
|
||||
AvailableTaskStateSegment, LDT, BusyTaskStateSegment,
|
||||
Invalid,
|
||||
};
|
||||
|
||||
constexpr bool is_data_or_code(const DescriptorType type) {
|
||||
return type <= DescriptorType::Stack;
|
||||
}
|
||||
|
||||
enum DescriptorTypeFlag: uint8_t {
|
||||
Accessed = 1 << 0,
|
||||
Busy = 1 << 1,
|
||||
};
|
||||
|
||||
struct DescriptorDescription {
|
||||
DescriptorType type = DescriptorType::Invalid;
|
||||
bool readable = false;
|
||||
bool writeable = false;
|
||||
bool conforming = false;
|
||||
bool is32bit = false;
|
||||
};
|
||||
|
||||
struct SegmentDescriptor {
|
||||
SegmentDescriptor() = default;
|
||||
|
||||
/// Creates a new descriptor with four 16-bit from a descriptor table.
|
||||
SegmentDescriptor(
|
||||
const uint16_t segment,
|
||||
const bool local,
|
||||
const uint16_t descriptor[4]
|
||||
) noexcept : segment_(segment), local_(local) {
|
||||
base_ = uint32_t(descriptor[1] | ((descriptor[2] & 0xff) << 16));
|
||||
type_ = descriptor[2] >> 8;
|
||||
|
||||
offset_ = descriptor[0];
|
||||
if(description().type != DescriptorType::Stack) {
|
||||
bounds_ = DescriptorBounds{ 0, offset_ };
|
||||
} else {
|
||||
if(offset_ != std::numeric_limits<uint32_t>::max()) {
|
||||
bounds_ = DescriptorBounds{ uint32_t(offset_ + 1), std::numeric_limits<uint32_t>::max() };
|
||||
} else {
|
||||
// This descriptor is impossible to satisfy for reasons that aren't
|
||||
// properly expressed if the lower bound is incremented, so make it
|
||||
// impossible to satisfy in a more prosaic sense.
|
||||
bounds_ = DescriptorBounds{ 1, 0 };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Rewrites this descriptor as a real-mode segment.
|
||||
void set_segment(const uint16_t segment) {
|
||||
segment_ = segment;
|
||||
base_ = uint32_t(segment) << 4;
|
||||
bounds_ = DescriptorBounds{ 0x0000, 0xffff };
|
||||
offset_ = 0;
|
||||
type_ = 0b1'00'1'001'0; // Present, privilege level 0, expand-up writeable data, unaccessed.
|
||||
}
|
||||
|
||||
uint16_t segment() const {
|
||||
return segment_;
|
||||
}
|
||||
|
||||
/// @returns The linear address for offest @c address within the segment described by this descriptor.
|
||||
uint32_t to_linear(const uint32_t address) const {
|
||||
return base_ + address;
|
||||
}
|
||||
|
||||
void throw_gpf() const {
|
||||
throw Exception::exception<Vector::GeneralProtectionFault>(
|
||||
ExceptionCode(
|
||||
segment_,
|
||||
local_,
|
||||
false,
|
||||
false
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
template <AccessType type, typename AddressT>
|
||||
requires std::same_as<AddressT, uint16_t> || std::same_as<AddressT, uint32_t>
|
||||
void authorise(const AddressT begin, const AddressT end) const {
|
||||
// Test for bounds; end && end < begin captures instances where end is
|
||||
// both out of bounds and beyond the range of AddressT.
|
||||
if(begin < bounds_.begin || end > bounds_.end || (end && end < begin)) {
|
||||
throw_gpf();
|
||||
}
|
||||
|
||||
// Tested at loading (?): present(), privilege_level().
|
||||
const auto desc = description();
|
||||
if(type == AccessType::Read && !desc.readable) {
|
||||
throw_gpf();
|
||||
}
|
||||
|
||||
if(type == AccessType::Write && !desc.writeable) {
|
||||
throw_gpf();
|
||||
}
|
||||
}
|
||||
|
||||
void validate_as(const Source segment) const {
|
||||
const auto desc = description();
|
||||
switch(segment) {
|
||||
case Source::DS:
|
||||
case Source::ES:
|
||||
if(!desc.readable) {
|
||||
printf("TODO: throw for unreadable DS or ES source.\n");
|
||||
assert(false);
|
||||
}
|
||||
break;
|
||||
|
||||
case Source::SS:
|
||||
if(!desc.writeable) {
|
||||
printf("TODO: throw for unwriteable SS target.\n");
|
||||
assert(false);
|
||||
}
|
||||
break;
|
||||
|
||||
default: break;
|
||||
}
|
||||
|
||||
// TODO: is this descriptor privilege within reach?
|
||||
// TODO: is this an empty descriptor*? If so: exception!
|
||||
}
|
||||
|
||||
// TODO: validators for:
|
||||
// INT
|
||||
// IRET
|
||||
// JMP
|
||||
// RET
|
||||
//
|
||||
// Verify also: MOV, POP, both of which can mutate DS/ES, SS, etc.
|
||||
|
||||
void validate_call(
|
||||
const std::function<void(const SegmentDescriptor &)> &call_callback
|
||||
) const {
|
||||
const auto desc = description();
|
||||
switch(desc.type) {
|
||||
case DescriptorType::Code:
|
||||
if(desc.conforming) {
|
||||
// TODO:
|
||||
// DPL must be :5 CPL else #GP (code segment selector)
|
||||
} else {
|
||||
|
||||
}
|
||||
|
||||
call_callback(*this);
|
||||
break;
|
||||
|
||||
case DescriptorType::CallGate:
|
||||
assert(false);
|
||||
break;
|
||||
|
||||
case DescriptorType::AvailableTaskStateSegment:
|
||||
assert(false);
|
||||
break;
|
||||
|
||||
default:
|
||||
throw_gpf();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/// @returns The base of this segment descriptor.
|
||||
uint32_t base() const { return base_; }
|
||||
|
||||
/// @returns The offset of this segment descriptor.
|
||||
uint32_t offset() const { return offset_; }
|
||||
|
||||
/// @returns The bounds of this segment descriptor; will be either [0, limit] or [limit, INT_MAX] depending on descriptor type.
|
||||
/// Accesses must be `>= bounds().begin` and `<= bounds().end`.
|
||||
DescriptorBounds bounds() const { return bounds_; }
|
||||
|
||||
bool present() const { return type_ & 0x80; }
|
||||
int privilege_level() const { return (type_ >> 5) & 3; }
|
||||
uint8_t access_rights() const { return uint8_t(type_); }
|
||||
|
||||
DescriptorDescription description() const {
|
||||
using Type = DescriptorType;
|
||||
switch(type_ & 0b11111) {
|
||||
default:
|
||||
case 0b00000: return { .type = Type::Invalid };
|
||||
case 0b00001: return { .type = Type::AvailableTaskStateSegment, .is32bit = false };
|
||||
case 0b00010: return { .type = Type::LDT };
|
||||
case 0b00011: return { .type = Type::BusyTaskStateSegment, .is32bit = false };
|
||||
|
||||
case 0b00100: return { .type = Type::CallGate, .is32bit = false };
|
||||
case 0b00101: return { .type = Type::TaskGate };
|
||||
case 0b00110: return { .type = Type::InterruptGate, .is32bit = false };
|
||||
case 0b00111: return { .type = Type::TrapGate, .is32bit = false };
|
||||
|
||||
case 0b01000: return { .type = Type::Invalid };
|
||||
case 0b01001: return { .type = Type::AvailableTaskStateSegment, .is32bit = true };
|
||||
case 0b01010: return { .type = Type::Invalid };
|
||||
case 0b01011: return { .type = Type::BusyTaskStateSegment, .is32bit = true };
|
||||
|
||||
case 0b01100: return { .type = Type::CallGate, .is32bit = true };
|
||||
case 0b01101: return { .type = Type::Invalid };
|
||||
case 0b01110: return { .type = Type::InterruptGate, .is32bit = true };
|
||||
case 0b01111: return { .type = Type::TrapGate, .is32bit = true };
|
||||
|
||||
// b0 is the accessed flag for non-system descriptors; it doesn't affect the type.
|
||||
case 0b10000:
|
||||
case 0b10001: return { .type = Type::Data, .readable = true, .writeable = false };
|
||||
case 0b10010:
|
||||
case 0b10011: return { .type = Type::Data, .readable = true, .writeable = true };
|
||||
case 0b10100:
|
||||
case 0b10101: return { .type = Type::Stack, .readable = true, .writeable = false };
|
||||
case 0b10110:
|
||||
case 0b10111: return { .type = Type::Stack, .readable = true, .writeable = true };
|
||||
|
||||
case 0b11000:
|
||||
case 0b11001: return { .type = Type::Code, .readable = false, .writeable = false, .conforming = false };
|
||||
case 0b11010:
|
||||
case 0b11011: return { .type = Type::Code, .readable = true, .writeable = false, .conforming = false };
|
||||
case 0b11100:
|
||||
case 0b11101: return { .type = Type::Code, .readable = false, .writeable = false, .conforming = true };
|
||||
case 0b11110:
|
||||
case 0b11111: return { .type = Type::Code, .readable = true, .writeable = false, .conforming = true };
|
||||
}
|
||||
}
|
||||
|
||||
auto operator <=> (const SegmentDescriptor &) const = default;
|
||||
|
||||
private:
|
||||
uint32_t base_;
|
||||
uint32_t offset_;
|
||||
DescriptorBounds bounds_;
|
||||
uint8_t type_;
|
||||
uint16_t segment_;
|
||||
bool local_;
|
||||
};
|
||||
|
||||
struct InterruptDescriptor {
|
||||
InterruptDescriptor(const uint16_t, bool, const uint16_t descriptor[4]) noexcept :
|
||||
segment_(descriptor[1]),
|
||||
offset_(uint32_t(descriptor[0] | (descriptor[3] << 16))),
|
||||
flags_(descriptor[2] >> 8) {}
|
||||
|
||||
uint16_t segment() const { return segment_; }
|
||||
uint32_t offset() const { return offset_; }
|
||||
bool present() const { return flags_ & 0x80; }
|
||||
uint8_t priority() const { return (flags_ >> 5) & 3; }
|
||||
|
||||
enum class Type {
|
||||
Task = 0x5,
|
||||
Interrupt16 = 0x6, Trap16 = 0x7,
|
||||
Interrupt32 = 0xe, Trap32 = 0xf,
|
||||
};
|
||||
Type type() const {
|
||||
return Type(flags_ & 0xf);
|
||||
}
|
||||
|
||||
private:
|
||||
uint16_t segment_;
|
||||
uint32_t offset_;
|
||||
uint8_t flags_;
|
||||
};
|
||||
|
||||
|
||||
template <typename SegmentT>
|
||||
struct SegmentRegisterSet {
|
||||
SegmentT &operator[](const Source segment) {
|
||||
return values_[index_of(segment)];
|
||||
}
|
||||
|
||||
const SegmentT &operator[](const Source segment) const {
|
||||
return values_[index_of(segment)];
|
||||
}
|
||||
|
||||
auto operator <=>(const SegmentRegisterSet &rhs) const = default;
|
||||
|
||||
private:
|
||||
std::array<SegmentT, 6> values_{};
|
||||
static constexpr size_t index_of(const Source segment) {
|
||||
assert(is_segment_register(segment));
|
||||
return size_t(segment) - size_t(Source::ES);
|
||||
}
|
||||
};
|
||||
|
||||
template <typename DescriptorT, typename LinearMemoryT>
|
||||
//requires is_linear_memory<LinearMemoryT>
|
||||
DescriptorT descriptor_at(
|
||||
LinearMemoryT &memory,
|
||||
const DescriptorTablePointer table,
|
||||
const uint16_t offset,
|
||||
const bool local
|
||||
) {
|
||||
if(offset > table.limit - 8) {
|
||||
printf("TODO: descriptor table overrun exception.\n");
|
||||
assert(false);
|
||||
}
|
||||
const auto address = table.base + offset;
|
||||
|
||||
using AccessType = InstructionSet::x86::AccessType;
|
||||
const uint32_t table_end = table.base + table.limit;
|
||||
const uint16_t entry[] = {
|
||||
memory.template access<uint16_t, AccessType::Read>(address, table_end),
|
||||
memory.template access<uint16_t, AccessType::Read>(address + 2, table_end),
|
||||
memory.template access<uint16_t, AccessType::Read>(address + 4, table_end),
|
||||
memory.template access<uint16_t, AccessType::Read>(address + 6, table_end)
|
||||
};
|
||||
|
||||
return DescriptorT(uint16_t(offset) & ~7, local, entry);
|
||||
}
|
||||
|
||||
template <typename DescriptorT, typename LinearMemoryT>
|
||||
//requires is_linear_memory<LinearMemoryT>
|
||||
void set_descriptor_type_flag(
|
||||
LinearMemoryT &memory,
|
||||
const DescriptorTablePointer table,
|
||||
const DescriptorT &descriptor,
|
||||
const DescriptorTypeFlag flag
|
||||
) {
|
||||
const auto address = table.base + (descriptor.segment() & ~7);
|
||||
const uint32_t table_end = table.base + table.limit;
|
||||
|
||||
auto type = memory.template access<uint16_t, AccessType::PreauthorisedRead>(address + 5, table_end);
|
||||
type |= flag;
|
||||
memory.template access<uint16_t, AccessType::Write>(address + 5, table_end) = type;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,168 @@
|
||||
//
|
||||
// Interrupts.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 06/10/2023.
|
||||
// Copyright © 2023 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cassert>
|
||||
#include <cstdint>
|
||||
|
||||
namespace InstructionSet::x86 {
|
||||
|
||||
enum class Vector: uint8_t {
|
||||
//
|
||||
// Present on all devices.
|
||||
//
|
||||
DivideError = 0,
|
||||
SingleStep = 1,
|
||||
NMI = 2,
|
||||
Breakpoint = 3,
|
||||
Overflow = 4,
|
||||
BoundRangeExceeded = 5,
|
||||
|
||||
//
|
||||
// Added by the 80286.
|
||||
//
|
||||
InvalidOpcode = 6,
|
||||
DeviceNotAvailable = 7,
|
||||
DoubleFault = 8,
|
||||
CoprocessorSegmentOverrun = 9,
|
||||
InvalidTSS = 10,
|
||||
SegmentNotPresent = 11,
|
||||
StackSegmentFault = 12,
|
||||
GeneralProtectionFault = 13,
|
||||
FloatingPointException = 16,
|
||||
|
||||
//
|
||||
// Added by the 80286.
|
||||
//
|
||||
PageFault = 14,
|
||||
AlignmentCheck = 17,
|
||||
MachineCheck = 18,
|
||||
};
|
||||
|
||||
constexpr bool has_error_code(const Vector vector) {
|
||||
switch(vector) {
|
||||
using enum Vector;
|
||||
|
||||
case DivideError:
|
||||
case SingleStep:
|
||||
case NMI:
|
||||
case Breakpoint:
|
||||
case Overflow:
|
||||
case BoundRangeExceeded:
|
||||
case InvalidOpcode:
|
||||
case DeviceNotAvailable:
|
||||
case CoprocessorSegmentOverrun:
|
||||
case FloatingPointException:
|
||||
return false;
|
||||
|
||||
case DoubleFault:
|
||||
case InvalidTSS:
|
||||
case SegmentNotPresent:
|
||||
case StackSegmentFault:
|
||||
case GeneralProtectionFault:
|
||||
return true;
|
||||
|
||||
default: // 386 exceptions; I don't know yet.
|
||||
break;
|
||||
}
|
||||
assert(false);
|
||||
return false;
|
||||
}
|
||||
|
||||
constexpr bool posts_next_ip(const Vector vector) {
|
||||
switch(vector) {
|
||||
using enum Vector;
|
||||
|
||||
default:
|
||||
return false;
|
||||
|
||||
case SingleStep:
|
||||
case Breakpoint:
|
||||
case Overflow:
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
struct ExceptionCode {
|
||||
ExceptionCode() = default;
|
||||
ExceptionCode(
|
||||
const uint16_t index,
|
||||
const bool is_local,
|
||||
const bool is_interrupt,
|
||||
const bool was_external) noexcept :
|
||||
value_(
|
||||
index |
|
||||
(is_local ? 0x4 : 0x0) |
|
||||
(is_interrupt ? 0x2 : 0x0) |
|
||||
(was_external ? 0x1 : 0x0)
|
||||
) {}
|
||||
|
||||
// i.e.:
|
||||
// b3–b15: IDT/GDT/LDT entry
|
||||
// b2: 1 => in LDT; 0 => in GDT;
|
||||
// b1: 1 => in IDT, ignore b2; 0 => use b2;
|
||||
// b0:
|
||||
// 1 => trigger was external to program code;
|
||||
// 0 => trigger was caused by the instruction described by the CS:IP that is on the stack.
|
||||
|
||||
ExceptionCode(const uint16_t value) :
|
||||
value_(value) {}
|
||||
|
||||
operator uint16_t() const {
|
||||
return value_;
|
||||
}
|
||||
|
||||
static ExceptionCode zero() {
|
||||
return ExceptionCode();
|
||||
}
|
||||
|
||||
private:
|
||||
uint16_t value_ = 0;
|
||||
};
|
||||
|
||||
struct Exception {
|
||||
ExceptionCode code{}; // Exception code to push to the stack if this is an internal
|
||||
// exception that provides a code and post_ip_as_code is `false`.
|
||||
uint8_t vector{}; // Will be equal to value of a `Vector` enum if internal.
|
||||
|
||||
enum class CodeType: uint8_t {
|
||||
Internal,
|
||||
External,
|
||||
};
|
||||
CodeType code_type = CodeType::Internal;
|
||||
|
||||
/// Generates an internal exception with no error code.
|
||||
template <Vector cause>
|
||||
requires (!has_error_code(cause))
|
||||
static constexpr Exception exception() {
|
||||
return Exception(uint8_t(cause));
|
||||
}
|
||||
|
||||
/// Generates an internal exception with a specified error code.
|
||||
template <Vector cause>
|
||||
requires (has_error_code(cause))
|
||||
static constexpr Exception exception(const ExceptionCode code) {
|
||||
return Exception(uint8_t(cause), code);
|
||||
}
|
||||
|
||||
/// Generates an externally-motivated exception (i.e. an interrupt).
|
||||
static constexpr Exception interrupt(const uint8_t vector) {
|
||||
return Exception(vector, CodeType::External);
|
||||
}
|
||||
|
||||
private:
|
||||
constexpr Exception(const uint8_t vector) noexcept : vector(vector) {}
|
||||
constexpr Exception(const uint8_t vector, const ExceptionCode code) noexcept : code(code), vector(vector){}
|
||||
constexpr Exception(const uint8_t vector, const CodeType code_type) noexcept :
|
||||
vector(vector), code_type(code_type) {}
|
||||
};
|
||||
|
||||
static_assert(sizeof(Exception) <= 4);
|
||||
|
||||
}
|
||||
@@ -9,6 +9,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "Numeric/Carry.hpp"
|
||||
#include "AccessType.hpp"
|
||||
|
||||
namespace InstructionSet::x86 {
|
||||
|
||||
@@ -80,6 +81,18 @@ class Flags {
|
||||
public:
|
||||
using FlagT = uint32_t;
|
||||
|
||||
Flags(const Model model) {
|
||||
switch(model) {
|
||||
case Model::i8086:
|
||||
case Model::i80186:
|
||||
forced_set_ = 0xf002;
|
||||
break;
|
||||
default:
|
||||
forced_set_ = 0x0002;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Flag getters.
|
||||
template <Flag flag_v> bool flag() const {
|
||||
switch(flag_v) {
|
||||
@@ -121,7 +134,9 @@ public:
|
||||
/// • Flag::Interrupt: sets interrupt if @c value is non-zero;
|
||||
/// • Flag::Trap: sets interrupt if @c value is non-zero;
|
||||
/// • Flag::Direction: sets direction if @c value is non-zero.
|
||||
template <typename IntT, Flag... flags> void set_from(const IntT value) {
|
||||
template <typename IntT, Flag... flags>
|
||||
requires is_x86_data_type<IntT>
|
||||
void set_from(const IntT value) {
|
||||
for(const auto flag: {flags...}) {
|
||||
switch(flag) {
|
||||
default: break;
|
||||
@@ -141,7 +156,9 @@ public:
|
||||
set_from<FlagT, flags...>(value);
|
||||
}
|
||||
|
||||
template <typename IntT> IntT carry_bit() const { return carry_ ? 1 : 0; }
|
||||
template <typename IntT>
|
||||
requires is_x86_data_type<IntT>
|
||||
IntT carry_bit() const { return carry_ ? 1 : 0; }
|
||||
bool not_parity_bit() const {
|
||||
// x86 parity always considers the lowest 8-bits only.
|
||||
auto result = static_cast<uint8_t>(parity_);
|
||||
@@ -151,7 +168,9 @@ public:
|
||||
return result & 1;
|
||||
}
|
||||
|
||||
template <typename IntT> IntT direction() const { return static_cast<IntT>(direction_); }
|
||||
template <typename IntT>
|
||||
requires is_x86_data_type<IntT>
|
||||
IntT direction() const { return static_cast<IntT>(direction_); }
|
||||
|
||||
// Complete value get and set.
|
||||
void set(uint16_t value) {
|
||||
@@ -170,7 +189,7 @@ public:
|
||||
|
||||
uint16_t get() const {
|
||||
return
|
||||
0xf002 |
|
||||
forced_set_ |
|
||||
|
||||
(flag<Flag::Carry>() ? FlagValue::Carry : 0) |
|
||||
(flag<Flag::AuxiliaryCarry>() ? FlagValue::AuxiliaryCarry : 0) |
|
||||
@@ -209,22 +228,25 @@ public:
|
||||
|
||||
private:
|
||||
// Non-zero => set; zero => unset.
|
||||
uint32_t carry_;
|
||||
uint32_t auxiliary_carry_;
|
||||
uint32_t sign_;
|
||||
uint32_t overflow_;
|
||||
uint32_t trap_;
|
||||
uint32_t interrupt_;
|
||||
uint32_t carry_{};
|
||||
uint32_t auxiliary_carry_{};
|
||||
uint32_t sign_{};
|
||||
uint32_t overflow_{};
|
||||
uint32_t trap_{};
|
||||
uint32_t interrupt_{};
|
||||
|
||||
// +1 = direction flag not set;
|
||||
// -1 = direction flag set.
|
||||
int32_t direction_;
|
||||
int32_t direction_{};
|
||||
|
||||
// Zero => set; non-zero => unset.
|
||||
uint32_t zero_;
|
||||
uint32_t zero_{};
|
||||
|
||||
// Odd number of bits => set; even => unset.
|
||||
uint32_t parity_;
|
||||
uint32_t parity_{};
|
||||
|
||||
// Model specific stuff: bits that are always set, regardless of other state.
|
||||
uint16_t forced_set_{};
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "InstructionSets/x86/AccessType.hpp"
|
||||
#include "InstructionSets/x86/Interrupts.hpp"
|
||||
#include "InstructionSets/x86/Exceptions.hpp"
|
||||
#include "InstructionSets/x86/Perform.hpp"
|
||||
|
||||
#include "Numeric/Carry.hpp"
|
||||
@@ -92,11 +92,11 @@ void test(
|
||||
/*
|
||||
The OF and CF flags are cleared to 0.
|
||||
The SF, ZF, and PF flags are set according to the result (see the “Operation” section above).
|
||||
The state of the AF flag is undefined.
|
||||
The state of the AF flag is formally undefined but known to be reset.
|
||||
*/
|
||||
const IntT result = destination & source;
|
||||
|
||||
context.flags.template set_from<Flag::Carry, Flag::Overflow>(0);
|
||||
context.flags.template set_from<Flag::Carry, Flag::Overflow, Flag::AuxiliaryCarry>(0);
|
||||
context.flags.template set_from<IntT, Flag::Zero, Flag::Sign, Flag::ParityOdd>(result);
|
||||
}
|
||||
|
||||
@@ -128,7 +128,7 @@ void mul(
|
||||
}
|
||||
|
||||
template <typename IntT, typename ContextT>
|
||||
void imul(
|
||||
void imul_double(
|
||||
modify_t<IntT> destination_high,
|
||||
modify_t<IntT> destination_low,
|
||||
read_t<IntT> source,
|
||||
@@ -167,6 +167,36 @@ void imul(
|
||||
context.flags.template set_from<Flag::Overflow, Flag::Carry>(destination_high != sign_extension);
|
||||
}
|
||||
|
||||
template <typename IntT, typename ContextT>
|
||||
void imul_single(
|
||||
write_t<IntT> destination,
|
||||
read_t<IntT> source1,
|
||||
read_t<IntT> source2,
|
||||
ContextT &context
|
||||
) {
|
||||
using sIntT = typename std::make_signed<IntT>::type;
|
||||
const auto top_part = IntT((sIntT(source1) * sIntT(source2)) >> (8 * sizeof(IntT)));
|
||||
const auto result = IntT(sIntT(source1) * sIntT(source2));
|
||||
destination = result;
|
||||
|
||||
const auto sign_extension = (result & Numeric::top_bit<IntT>()) ? IntT(~0) : 0;
|
||||
context.flags.template set_from<Flag::Overflow, Flag::Carry>(top_part != sign_extension);
|
||||
}
|
||||
|
||||
template <typename ContextT>
|
||||
void divide_error(ContextT &context) {
|
||||
// 8086-style: just segue directly to the interrupt.
|
||||
//
|
||||
// 80286-style: throw the divide error, allowing the caller to insert
|
||||
// additional context (primarily: IP of this instruction, not the next).
|
||||
static constexpr auto exception = Exception::exception<Vector::DivideError>();
|
||||
if constexpr (uses_8086_exceptions(ContextT::model)) {
|
||||
interrupt(exception, context);
|
||||
} else {
|
||||
throw exception;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename IntT, typename ContextT>
|
||||
void div(
|
||||
modify_t<IntT> destination_high,
|
||||
@@ -212,16 +242,14 @@ void div(
|
||||
The CF, OF, SF, ZF, AF, and PF flags are undefined.
|
||||
*/
|
||||
if(!source) {
|
||||
interrupt(Interrupt::DivideError, context);
|
||||
return;
|
||||
return divide_error(context);
|
||||
}
|
||||
|
||||
// TEMPORARY HACK. Will not work with DWords.
|
||||
const uint32_t dividend = uint32_t((destination_high << (8 * sizeof(IntT))) + destination_low);
|
||||
const auto result = dividend / source;
|
||||
if(IntT(result) != result) {
|
||||
interrupt(Interrupt::DivideError, context);
|
||||
return;
|
||||
return divide_error(context);
|
||||
}
|
||||
|
||||
destination_low = IntT(result);
|
||||
@@ -262,7 +290,7 @@ void idiv(
|
||||
FI;
|
||||
ELSE (* quadword/doubleword operation *)
|
||||
temp ← EDX:EAX / SRC; (* signed division *)
|
||||
IF (temp > 7FFFFFFFH) OR (temp < 80000000H) (* if a positive result is greater than 7FFFFFFFH
|
||||
IF (temp > 7FFFFFFFH) OR (temp < 80000000H) (* if a positive result is greater than 7FFFFFFFH
|
||||
or a negative result is less than 80000000H *)
|
||||
THEN #DE; (* divide error *) ;
|
||||
ELSE
|
||||
@@ -276,8 +304,7 @@ void idiv(
|
||||
The CF, OF, SF, ZF, AF, and PF flags are undefined.
|
||||
*/
|
||||
if(!source) {
|
||||
interrupt(Interrupt::DivideError, context);
|
||||
return;
|
||||
return divide_error(context);
|
||||
}
|
||||
|
||||
// TEMPORARY HACK. Will not work with DWords.
|
||||
@@ -292,8 +319,7 @@ void idiv(
|
||||
}
|
||||
|
||||
if(sIntT(result) != result) {
|
||||
interrupt(Interrupt::DivideError, context);
|
||||
return;
|
||||
return divide_error(context);
|
||||
}
|
||||
|
||||
destination_low = IntT(result);
|
||||
|
||||
@@ -22,10 +22,18 @@ void aaas(
|
||||
) {
|
||||
if((ax.halves.low & 0x0f) > 9 || context.flags.template flag<Flag::AuxiliaryCarry>()) {
|
||||
if constexpr (add) {
|
||||
ax.halves.low += 6;
|
||||
if constexpr (ContextT::model <= Model::i80186) {
|
||||
ax.halves.low += 6;
|
||||
} else {
|
||||
ax.full += 6;
|
||||
}
|
||||
++ax.halves.high;
|
||||
} else {
|
||||
ax.halves.low -= 6;
|
||||
if constexpr (ContextT::model <= Model::i80186) {
|
||||
ax.halves.low -= 6;
|
||||
} else {
|
||||
ax.full -= 6;
|
||||
}
|
||||
--ax.halves.high;
|
||||
}
|
||||
context.flags.template set_from<Flag::Carry, Flag::AuxiliaryCarry>(1);
|
||||
@@ -75,8 +83,13 @@ void aam(
|
||||
If ... an immediate value of 0 is used, it will cause a #DE (divide error) exception.
|
||||
*/
|
||||
if(!imm) {
|
||||
interrupt(Interrupt::DivideError, context);
|
||||
return;
|
||||
static constexpr auto exception = Exception::exception<Vector::DivideError>();
|
||||
if constexpr (uses_8086_exceptions(ContextT::model)) {
|
||||
interrupt(exception, context);
|
||||
return;
|
||||
} else {
|
||||
throw exception;
|
||||
}
|
||||
}
|
||||
|
||||
ax.halves.high = ax.halves.low / imm;
|
||||
@@ -91,18 +104,27 @@ void daas(
|
||||
ContextT &context
|
||||
) {
|
||||
bool top_exceeded_threshold;
|
||||
if constexpr (ContextT::model == Model::i8086) {
|
||||
static constexpr bool is_8086 = ContextT::model == Model::i8086;
|
||||
if constexpr (is_8086) {
|
||||
top_exceeded_threshold = al > (context.flags.template flag<Flag::AuxiliaryCarry>() ? 0x9f : 0x99);
|
||||
} else {
|
||||
top_exceeded_threshold = al > 0x99;
|
||||
}
|
||||
|
||||
const auto initial_cf = context.flags.template flag<Flag::Carry>();
|
||||
if((al & 0x0f) > 0x09 || context.flags.template flag<Flag::AuxiliaryCarry>()) {
|
||||
if constexpr (add) al += 0x06; else al -= 0x06;
|
||||
const auto prior_al = al;
|
||||
if constexpr (add) {
|
||||
al += 0x06;
|
||||
if(!is_8086 && al < prior_al) context.flags.template set_from<Flag::Carry>(1);
|
||||
} else {
|
||||
al -= 0x06;
|
||||
if(!is_8086 && al > prior_al) context.flags.template set_from<Flag::Carry>(1);
|
||||
}
|
||||
context.flags.template set_from<Flag::AuxiliaryCarry>(1);
|
||||
}
|
||||
|
||||
if(top_exceeded_threshold || context.flags.template flag<Flag::Carry>()) {
|
||||
if(top_exceeded_threshold || initial_cf) {
|
||||
if constexpr (add) al += 0x60; else al -= 0x60;
|
||||
context.flags.template set_from<Flag::Carry>(1);
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
|
||||
#include "Resolver.hpp"
|
||||
#include "Stack.hpp"
|
||||
#include "InstructionSets/x86/TaskStateSegment.hpp"
|
||||
#include "InstructionSets/x86/AccessType.hpp"
|
||||
|
||||
#include <type_traits>
|
||||
@@ -105,59 +106,73 @@ void jump_absolute(
|
||||
context.flow_controller.template jump<uint16_t>(target);
|
||||
}
|
||||
|
||||
template <typename AddressT, typename InstructionT, typename ContextT>
|
||||
template <typename AddressT, typename ContextT>
|
||||
void call_far(
|
||||
InstructionT &instruction,
|
||||
const uint16_t segment,
|
||||
const AddressT offset,
|
||||
ContextT &context
|
||||
) {
|
||||
// TODO: eliminate 16-bit assumption below.
|
||||
const Source source_segment = instruction.data_segment();
|
||||
context.memory.preauthorise_stack_write(sizeof(uint16_t) * 2);
|
||||
context.segments.preauthorise_call(
|
||||
Source::CS,
|
||||
offset,
|
||||
[&] {
|
||||
context.memory.preauthorise_stack_write(sizeof(uint16_t) * 2, sizeof(uint16_t));
|
||||
push<uint16_t, true>(context.registers.cs(), context);
|
||||
push<uint16_t, true>(context.registers.ip(), context);
|
||||
context.flow_controller.template jump<AddressT>(segment, offset);
|
||||
},
|
||||
[&] (const SegmentDescriptor &descriptor) {
|
||||
(void)descriptor;
|
||||
printf("TODO: protected mode far call");
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
uint16_t source_address;
|
||||
template <typename AddressT, InstructionType type, typename ContextT>
|
||||
void call_far(
|
||||
const Instruction<type> &instruction,
|
||||
ContextT &context
|
||||
) {
|
||||
const Source source_segment = instruction.data_segment();
|
||||
|
||||
AddressT source_address;
|
||||
const auto pointer = instruction.destination();
|
||||
switch(pointer.source()) {
|
||||
default:
|
||||
case Source::Immediate:
|
||||
push<uint16_t, true>(context.registers.cs(), context);
|
||||
push<uint16_t, true>(context.registers.ip(), context);
|
||||
context.flow_controller.template jump<uint16_t>(instruction.segment(), instruction.offset());
|
||||
call_far(instruction.segment(), instruction.offset(), context);
|
||||
return;
|
||||
|
||||
case Source::Indirect:
|
||||
source_address = uint16_t(
|
||||
address<Source::Indirect, uint16_t, AccessType::Read>(instruction, pointer, context)
|
||||
address<Source::Indirect, AddressT, AccessType::Read>(instruction, pointer, context)
|
||||
);
|
||||
break;
|
||||
case Source::IndirectNoBase:
|
||||
source_address = uint16_t(
|
||||
address<Source::IndirectNoBase, uint16_t, AccessType::Read>(instruction, pointer, context)
|
||||
address<Source::IndirectNoBase, AddressT, AccessType::Read>(instruction, pointer, context)
|
||||
);
|
||||
break;
|
||||
case Source::DirectAddress:
|
||||
source_address = uint16_t(
|
||||
address<Source::DirectAddress, uint16_t, AccessType::Read>(instruction, pointer, context)
|
||||
address<Source::DirectAddress, AddressT, AccessType::Read>(instruction, pointer, context)
|
||||
);
|
||||
break;
|
||||
}
|
||||
|
||||
context.memory.preauthorise_read(source_segment, source_address, sizeof(uint16_t) * 2);
|
||||
// context.memory.preauthorise_read(source_segment, source_address, sizeof(uint16_t) + sizeof(AddressT));
|
||||
const auto offset =
|
||||
context.memory.template access<uint16_t, AccessType::PreauthorisedRead>(source_segment, source_address);
|
||||
context.memory.template access<AddressT, AccessType::Read>(source_segment, source_address);
|
||||
source_address += 2;
|
||||
const auto segment =
|
||||
context.memory.template access<uint16_t, AccessType::PreauthorisedRead>(source_segment, source_address);
|
||||
context.memory.template access<uint16_t, AccessType::Read>(source_segment, source_address);
|
||||
|
||||
// At least on an 8086, the stack writes occur after the target address read.
|
||||
push<uint16_t, true>(context.registers.cs(), context);
|
||||
push<uint16_t, true>(context.registers.ip(), context);
|
||||
|
||||
context.flow_controller.template jump<AddressT>(segment, offset);
|
||||
call_far(segment, offset, context);
|
||||
}
|
||||
|
||||
template <typename InstructionT, typename ContextT>
|
||||
template <InstructionType type, typename ContextT>
|
||||
void jump_far(
|
||||
InstructionT &instruction,
|
||||
const Instruction<type> &instruction,
|
||||
ContextT &context
|
||||
) {
|
||||
// TODO: eliminate 16-bit assumption below.
|
||||
@@ -187,13 +202,13 @@ void jump_far(
|
||||
}
|
||||
|
||||
const Source source_segment = instruction.data_segment();
|
||||
context.memory.preauthorise_read(source_segment, source_address, sizeof(uint16_t) * 2);
|
||||
// context.memory.preauthorise_read(source_segment, source_address, sizeof(uint16_t) * 2);
|
||||
|
||||
const auto offset =
|
||||
context.memory.template access<uint16_t, AccessType::PreauthorisedRead>(source_segment, source_address);
|
||||
context.memory.template access<uint16_t, AccessType::Read>(source_segment, source_address);
|
||||
source_address += 2;
|
||||
const auto segment =
|
||||
context.memory.template access<uint16_t, AccessType::PreauthorisedRead>(source_segment, source_address);
|
||||
context.memory.template access<uint16_t, AccessType::Read>(source_segment, source_address);
|
||||
context.flow_controller.template jump<uint16_t>(segment, offset);
|
||||
}
|
||||
|
||||
@@ -202,7 +217,7 @@ void iret(
|
||||
ContextT &context
|
||||
) {
|
||||
// TODO: all modes other than 16-bit real mode.
|
||||
context.memory.preauthorise_stack_read(sizeof(uint16_t) * 3);
|
||||
context.memory.preauthorise_stack_read(sizeof(uint16_t) * 3, sizeof(uint16_t));
|
||||
const auto ip = pop<uint16_t, true>(context);
|
||||
const auto cs = pop<uint16_t, true>(context);
|
||||
context.flags.set(pop<uint16_t, true>(context));
|
||||
@@ -224,7 +239,7 @@ void ret_far(
|
||||
const InstructionT instruction,
|
||||
ContextT &context
|
||||
) {
|
||||
context.memory.preauthorise_stack_read(sizeof(uint16_t) * 2);
|
||||
context.memory.preauthorise_stack_read(sizeof(uint16_t) * 2, sizeof(uint16_t));
|
||||
const auto ip = pop<uint16_t, true>(context);
|
||||
const auto cs = pop<uint16_t, true>(context);
|
||||
context.registers.sp() += instruction.operand();
|
||||
@@ -236,28 +251,37 @@ void into(
|
||||
ContextT &context
|
||||
) {
|
||||
if(context.flags.template flag<Flag::Overflow>()) {
|
||||
interrupt(Interrupt::Overflow, context);
|
||||
static constexpr auto exception = Exception::exception<Vector::Overflow>();
|
||||
if constexpr (uses_8086_exceptions(ContextT::model)) {
|
||||
interrupt(exception, context);
|
||||
} else {
|
||||
throw exception;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <typename IntT, typename AddressT, typename InstructionT, typename ContextT>
|
||||
void bound(
|
||||
const InstructionT &instruction,
|
||||
read_t<AddressT> destination,
|
||||
read_t<IntT> destination,
|
||||
read_t<AddressT> source,
|
||||
ContextT &context
|
||||
) {
|
||||
using sIntT = typename std::make_signed<IntT>::type;
|
||||
|
||||
const auto source_segment = instruction.data_segment();
|
||||
context.memory.preauthorise_read(source_segment, source, 2*sizeof(IntT));
|
||||
const auto lower_bound =
|
||||
sIntT(context.memory.template access<IntT, AccessType::PreauthorisedRead>(source_segment, source));
|
||||
sIntT(context.memory.template access<IntT, AccessType::Read>(source_segment, source));
|
||||
const auto upper_bound =
|
||||
sIntT(context.memory.template access<IntT, AccessType::PreauthorisedRead>(source_segment, IntT(source + 2)));
|
||||
sIntT(context.memory.template access<IntT, AccessType::Read>(source_segment, IntT(source + 2)));
|
||||
|
||||
if(sIntT(destination) < lower_bound || sIntT(destination) > upper_bound) {
|
||||
interrupt(Interrupt::BoundRangeExceeded, context);
|
||||
static constexpr auto exception = Exception::exception<Vector::BoundRangeExceeded>();
|
||||
if constexpr (uses_8086_exceptions(ContextT::model)) {
|
||||
interrupt(exception, context);
|
||||
} else {
|
||||
throw exception;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user