mirror of
				https://github.com/TomHarte/CLK.git
				synced 2025-10-31 05:16:08 +00:00 
			
		
		
		
	Compare commits
	
		
			594 Commits
		
	
	
		
			2019-08-04
			...
			2020-01-15
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|  | 9bb294a023 | ||
|  | 1972ca00a4 | ||
|  | 6a185a574a | ||
|  | c606931c93 | ||
|  | 93cecf0882 | ||
|  | aac3d27c10 | ||
|  | 99122efbbc | ||
|  | 30e856b9e4 | ||
|  | 91fae86e73 | ||
|  | f5c194386c | ||
|  | 98f7662185 | ||
|  | 62c3720c97 | ||
|  | 6b08239199 | ||
|  | f258fc2971 | ||
|  | 6b84ae3095 | ||
|  | 5dd8c677f1 | ||
|  | 1cbcd5355f | ||
|  | 9799250f2c | ||
|  | fab35b360a | ||
|  | 80fcf5b5c0 | ||
|  | b3b2e18c4b | ||
|  | 2d233b6358 | ||
|  | 83ed36eb08 | ||
|  | 89f4032ffc | ||
|  | 8c90ec4636 | ||
|  | 514141f8c5 | ||
|  | 8e3a618619 | ||
|  | 6df6af09de | ||
|  | f42655a0fc | ||
|  | f81a7f0faf | ||
|  | 2b4c924399 | ||
|  | 64517a02b7 | ||
|  | b4befd57a9 | ||
|  | 2c742a051e | ||
|  | 6595f8f527 | ||
|  | 985b36da73 | ||
|  | cdb31b1c2b | ||
|  | 6a44936a7c | ||
|  | 45afb13a54 | ||
|  | 3ced31043a | ||
|  | 7361e7ec34 | ||
|  | 533729638c | ||
|  | 9f30be1c13 | ||
|  | 09289f383d | ||
|  | 20b25ce866 | ||
|  | c1bae49a92 | ||
|  | b3f806201b | ||
|  | 9f2f547932 | ||
|  | f0d5bbecf2 | ||
|  | 3d7ef43293 | ||
|  | 4578b65487 | ||
|  | a28c52c250 | ||
|  | e4349f5e05 | ||
|  | 7b2777ac08 | ||
|  | 0fbcbfc61b | ||
|  | 3ab4fb8c79 | ||
|  | 42a9585321 | ||
|  | 937cba8978 | ||
|  | 627d3c28ea | ||
|  | 19ddfae6d6 | ||
|  | 56ebd08af0 | ||
|  | 7de1181213 | ||
|  | c7a5b054db | ||
|  | ca12ba297b | ||
|  | 7abf527084 | ||
|  | c0b5bfe726 | ||
|  | 414b0cc234 | ||
|  | 134e828336 | ||
|  | 455e831b87 | ||
|  | 617e0bada9 | ||
|  | 7dea99b1cc | ||
|  | 42ccf48966 | ||
|  | 2f8078db22 | ||
|  | ea45ae78d1 | ||
|  | cb7d6c185c | ||
|  | 5be30b1f7b | ||
|  | 0bf1a87f4c | ||
|  | b184426f2b | ||
|  | 2456fb120d | ||
|  | 23ed9ad2de | ||
|  | 017681a97c | ||
|  | 153f60735d | ||
|  | 90b899c00e | ||
|  | 5ce8d7c0e5 | ||
|  | c11fe25537 | ||
|  | c4edd635c5 | ||
|  | 0a12893d63 | ||
|  | 8e777c299f | ||
|  | 09513ec14c | ||
|  | e23d1a2958 | ||
|  | 6449403f6a | ||
|  | c8fe66092b | ||
|  | b33218c61e | ||
|  | 8ce26e7182 | ||
|  | 47068ee081 | ||
|  | 5361ee2526 | ||
|  | 214b6a254a | ||
|  | 93f6964d8a | ||
|  | 13f11e071a | ||
|  | f7825dd2a2 | ||
|  | a9d1f5d925 | ||
|  | 2757e5d600 | ||
|  | 5026de9653 | ||
|  | 5fa8e046d8 | ||
|  | ec9357e080 | ||
|  | f8dd33b645 | ||
|  | de43e86310 | ||
|  | 314973a5ef | ||
|  | d26ce65236 | ||
|  | 1de4f179c0 | ||
|  | 3cb5684d95 | ||
|  | a9a92de954 | ||
|  | daacd6805e | ||
|  | 54fe01b532 | ||
|  | 42dd70dbff | ||
|  | e59de71d79 | ||
|  | a8ba3607b7 | ||
|  | 4205e95883 | ||
|  | f633cf4c3f | ||
|  | dfa6b11737 | ||
|  | 42926e72cc | ||
|  | 80cb06eb33 | ||
|  | 5068328a15 | ||
|  | adc2b77833 | ||
|  | 99415217dc | ||
|  | 48d519d475 | ||
|  | ed831e5912 | ||
|  | 1db7c7989b | ||
|  | b2bed82da6 | ||
|  | afae1443b4 | ||
|  | 0dae608da5 | ||
|  | 8a1fe99fa4 | ||
|  | ac604b30f3 | ||
|  | b035b92f33 | ||
|  | d25b48878c | ||
|  | 34a3790e11 | ||
|  | f3378f3e3e | ||
|  | 78accc1db1 | ||
|  | a756985e18 | ||
|  | 30e0d4aa30 | ||
|  | de72c66c64 | ||
|  | 6edd3c9698 | ||
|  | 5456a4a39d | ||
|  | 66d9b60b98 | ||
|  | 274867579b | ||
|  | a847654ef2 | ||
|  | 05d77d3297 | ||
|  | e9318efeb6 | ||
|  | 25da5ebdae | ||
|  | cf16f41939 | ||
|  | 08f2877382 | ||
|  | 6f4444d834 | ||
|  | 993dfeae1b | ||
|  | b4fd506361 | ||
|  | e5440a4146 | ||
|  | 57ce10418f | ||
|  | 47508d50a7 | ||
|  | 56cc191a8b | ||
|  | 2a1520c04e | ||
|  | 3d83f5ab49 | ||
|  | 0007dc23b3 | ||
|  | 416d68ab3a | ||
|  | ed7f171736 | ||
|  | 0e066f0f70 | ||
|  | 3e6f51f5cf | ||
|  | 797abae4b3 | ||
|  | 4605b1b264 | ||
|  | d802e8aee3 | ||
|  | 206ab380c7 | ||
|  | d85ae21b2f | ||
|  | 470cc572fd | ||
|  | f0d9d8542b | ||
|  | d2390fcb11 | ||
|  | 5ce612cb38 | ||
|  | ec7aa2d355 | ||
|  | 9464658d1e | ||
|  | a3e64cae41 | ||
|  | e969b386f1 | ||
|  | af9c0aca97 | ||
|  | 8a2ac87209 | ||
|  | 096b447b4b | ||
|  | 0d23f141d6 | ||
|  | 84167af54f | ||
|  | 8be26502c4 | ||
|  | ba2436206f | ||
|  | 60a9b260b1 | ||
|  | e603fc6aaa | ||
|  | 81cc278b98 | ||
|  | 4c068e9bb8 | ||
|  | f23c5ada31 | ||
|  | dc1abd874e | ||
|  | 1bf4686c59 | ||
|  | a500fbcd73 | ||
|  | d0ef41f11e | ||
|  | adf6723bf6 | ||
|  | 37e26c0c37 | ||
|  | ac1575be27 | ||
|  | 923287bf01 | ||
|  | 77fe14cdb3 | ||
|  | c00ae7ce6a | ||
|  | d5b2e6514a | ||
|  | fc7f46006e | ||
|  | 41503d7253 | ||
|  | f88c942fef | ||
|  | 4bcf217324 | ||
|  | f6f2b4b90f | ||
|  | 95b5db4d87 | ||
|  | de4403e021 | ||
|  | 0a405d1c06 | ||
|  | 768b3709b8 | ||
|  | 7cc5d0b209 | ||
|  | c2646a415f | ||
|  | e1c7a140d0 | ||
|  | 7cd11ecb7f | ||
|  | 4dd235f677 | ||
|  | a7cfb840ef | ||
|  | acfe2c63b8 | ||
|  | b192381928 | ||
|  | c785797da6 | ||
|  | 0408592ada | ||
|  | 407cc78c78 | ||
|  | 4536c6a224 | ||
|  | 0ed87c61bd | ||
|  | 332f0d6167 | ||
|  | 08a27bdec7 | ||
|  | 288cabbad1 | ||
|  | 7ff57f8cdf | ||
|  | 06edeea866 | ||
|  | 3c77d3bda0 | ||
|  | 72cb3a1cf6 | ||
|  | e0ceab6642 | ||
|  | 894066984c | ||
|  | c91495d068 | ||
|  | e787c03530 | ||
|  | b12136691a | ||
|  | c04d2f6c6e | ||
|  | 6990abc0d3 | ||
|  | 0ce5057fd9 | ||
|  | ade8df7217 | ||
|  | b98703bd5b | ||
|  | 82c984afa4 | ||
|  | 1202b0a65f | ||
|  | facc0a1976 | ||
|  | 25da8b7787 | ||
|  | 253dd84109 | ||
|  | 9d07765823 | ||
|  | 11de0e198f | ||
|  | f16f0897d5 | ||
|  | 3d4d45ef62 | ||
|  | 04c4f5f321 | ||
|  | fa900d22e8 | ||
|  | aee2890b25 | ||
|  | 40e1ec28fb | ||
|  | c6e2b1237c | ||
|  | efdd27a435 | ||
|  | 2c4f372872 | ||
|  | 74be876d72 | ||
|  | e8e166eec5 | ||
|  | 4ec8fa0d20 | ||
|  | b019c6f8dd | ||
|  | 6ec3c47cc0 | ||
|  | ccce127f13 | ||
|  | f4cfca0451 | ||
|  | eb287605f7 | ||
|  | 2026761a07 | ||
|  | 6a82c87320 | ||
|  | f0478225f0 | ||
|  | d6edfa5c6d | ||
|  | e7253a8713 | ||
|  | c6f6bc68e1 | ||
|  | ab34fad8ca | ||
|  | e4c77614c1 | ||
|  | 072b0266af | ||
|  | 7ae0902103 | ||
|  | 8e9428623e | ||
|  | 2c25135d8a | ||
|  | 3741cba88c | ||
|  | 860837d894 | ||
|  | cef07038c1 | ||
|  | 0bf61c9c99 | ||
|  | 837dfd1ab8 | ||
|  | 0204003680 | ||
|  | 5fc4e57db7 | ||
|  | c4fefe1eb3 | ||
|  | 77ef7dc8fc | ||
|  | e3abbc9966 | ||
|  | 70c6010fe0 | ||
|  | 8c736a639a | ||
|  | cc7ff1ec9e | ||
|  | 9d12ca691f | ||
|  | db03b03276 | ||
|  | 45375fb5d0 | ||
|  | d2324e413d | ||
|  | e0c15f43bb | ||
|  | 8b0d550b81 | ||
|  | d1259f829e | ||
|  | 7caef46c05 | ||
|  | 6902251d8b | ||
|  | 0b683b0360 | ||
|  | 5d5dc79f2c | ||
|  | 68f9084d9e | ||
|  | b7c407be10 | ||
|  | f45798faf0 | ||
|  | 7c66d7a13c | ||
|  | f9a35c6636 | ||
|  | 5e1570258d | ||
|  | fc8021c0b0 | ||
|  | c9cd56915e | ||
|  | 1fd19c5786 | ||
|  | ce66b5fd9c | ||
|  | 8aa425c9d8 | ||
|  | ec68bc5047 | ||
|  | 0971bbeebe | ||
|  | 9292f66c2d | ||
|  | f3e2e88986 | ||
|  | 6afefa107e | ||
|  | 0ce807805d | ||
|  | 41f3c29e30 | ||
|  | 6c75c60149 | ||
|  | 015f2101f8 | ||
|  | 35f1a7ab10 | ||
|  | 8df1eea955 | ||
|  | eeafdf2c03 | ||
|  | befe2c2929 | ||
|  | 46ec3510be | ||
|  | e9965c2738 | ||
|  | 48b0d8c329 | ||
|  | 07582cee4a | ||
|  | 4dbd2a805a | ||
|  | 20bf425f98 | ||
|  | 0567410bcf | ||
|  | 6d1e09ba55 | ||
|  | f40dbefa67 | ||
|  | f93cdd21de | ||
|  | e1dc3b1915 | ||
|  | cbf25a16dc | ||
|  | 14e790746b | ||
|  | bf7e9cfd62 | ||
|  | a67e0014a4 | ||
|  | c070f2100c | ||
|  | 75e34b4215 | ||
|  | a5bbf54a27 | ||
|  | 5309ac7c30 | ||
|  | 731dc350b4 | ||
|  | 635e18a50d | ||
|  | 4857ceb3eb | ||
|  | 1c154131f9 | ||
|  | fd02b6fc18 | ||
|  | 553f3b6d8b | ||
|  | 1135576a3a | ||
|  | a5057e6540 | ||
|  | 1d790ec2a9 | ||
|  | 0f2d72c436 | ||
|  | aa52652027 | ||
|  | 4a1fa8fc13 | ||
|  | 95d3b6e79f | ||
|  | 5f6711b72c | ||
|  | d44734d105 | ||
|  | 1aaa6331a0 | ||
|  | de1bfb4e24 | ||
|  | 0cb19421e8 | ||
|  | 92847037b3 | ||
|  | f4556ef6b0 | ||
|  | 4266264449 | ||
|  | 1aba1db62c | ||
|  | 0fc191c87d | ||
|  | dc4a0e4e3b | ||
|  | 3794d94b68 | ||
|  | 0082dc4411 | ||
|  | 22754683f8 | ||
|  | 909685d87d | ||
|  | 55710ea00e | ||
|  | 36a9a5288b | ||
|  | e89be6249d | ||
|  | ac39fd0235 | ||
|  | ecc0cea5a1 | ||
|  | eae11cbf17 | ||
|  | e96386f572 | ||
|  | a8d481a764 | ||
|  | 2207638287 | ||
|  | 872897029e | ||
|  | 51b4b5551d | ||
|  | 7a2de47f58 | ||
|  | f2f98ed60c | ||
|  | 77f14fa638 | ||
|  | f09a240e6c | ||
|  | 092a61f93e | ||
|  | e30ba58e0d | ||
|  | 7cb82fccc0 | ||
|  | ed9a5b0430 | ||
|  | 8f59a73425 | ||
|  | 91223b9ec8 | ||
|  | 83f5f0e2ad | ||
|  | cf37e9f5de | ||
|  | e4f7ead894 | ||
|  | 4134463094 | ||
|  | 83d73fb088 | ||
|  | 75c3e2dacd | ||
|  | cf07982a9b | ||
|  | 313aaa8f95 | ||
|  | 2e86dada1d | ||
|  | 696af5c3a6 | ||
|  | f08b38d0ae | ||
|  | 9a8352282d | ||
|  | 3d03cce6b1 | ||
|  | 34075a7674 | ||
|  | f79c87659f | ||
|  | c10b64e1c0 | ||
|  | 5d5fe52144 | ||
|  | d461331fd2 | ||
|  | ff62eb6dce | ||
|  | 374439693e | ||
|  | c4ef33b23f | ||
|  | a7ed357569 | ||
|  | 4e5b440145 | ||
|  | 2bd7be13b5 | ||
|  | 4b09d7c41d | ||
|  | 97d44129cb | ||
|  | b0f5f7bd37 | ||
|  | d1dd6876b5 | ||
|  | a59ec9e818 | ||
|  | 4ead905c3c | ||
|  | 127bb043e7 | ||
|  | 42ebe06474 | ||
|  | 74fe32da23 | ||
|  | 780916551f | ||
|  | 305b1211ba | ||
|  | 2cf52fb89c | ||
|  | 6e1b606adf | ||
|  | 3bb0bf9e14 | ||
|  | 87a6d22894 | ||
|  | 484a0ceeb8 | ||
|  | da1436abd2 | ||
|  | 125f781ced | ||
|  | c66c484c54 | ||
|  | 345b32d6e3 | ||
|  | 8b397626bf | ||
|  | 0da1881a07 | ||
|  | d4077afd30 | ||
|  | 95c45b5515 | ||
|  | 684644420a | ||
|  | 735586f5f8 | ||
|  | ddae086661 | ||
|  | 9c7aa5f3fc | ||
|  | 418cd07e17 | ||
|  | 2ae5739b8b | ||
|  | e095a622d3 | ||
|  | 9ab49065cd | ||
|  | ab50f17d87 | ||
|  | f5a2e180f9 | ||
|  | f2e1584275 | ||
|  | 0fd8813ddb | ||
|  | b69180ba01 | ||
|  | c352d8ae8c | ||
|  | 530e831064 | ||
|  | 3b165a78f2 | ||
|  | 8d87e9eb1c | ||
|  | f86dc082bb | ||
|  | d7982aa84e | ||
|  | 516d78f5a8 | ||
|  | 8b50a7d6e3 | ||
|  | 4bf81d3b90 | ||
|  | cd75978e4e | ||
|  | fda99d9c5f | ||
|  | c5ebf75351 | ||
|  | 2581b520af | ||
|  | 52e5296544 | ||
|  | d7ce2c26e8 | ||
|  | f88e1b1373 | ||
|  | 021d4dbaf1 | ||
|  | dbde8f2ee7 | ||
|  | 5d06930df4 | ||
|  | 7722596a3b | ||
|  | 1de1818ebb | ||
|  | 885f890df1 | ||
|  | e195497ab7 | ||
|  | fcd2143697 | ||
|  | 3f45cd2380 | ||
|  | a8a34497ff | ||
|  | 953423cc02 | ||
|  | a2ca887b99 | ||
|  | 3c5ae9cf8e | ||
|  | fe621d7e52 | ||
|  | 814bb4ec63 | ||
|  | e8bc254f3f | ||
|  | 3c146a3fb2 | ||
|  | b609ce6fcb | ||
|  | 929475d31e | ||
|  | f14d98452e | ||
|  | 9d17d48bca | ||
|  | 4ac3839185 | ||
|  | c089d1cd09 | ||
|  | cb85ec25cc | ||
|  | fbf95ec2b8 | ||
|  | 6adca98f34 | ||
|  | 48f4d8b875 | ||
|  | 7758f9d0a9 | ||
|  | 7112f0336c | ||
|  | 298694a881 | ||
|  | 7ff4594f09 | ||
|  | e8bd538182 | ||
|  | 8489e8650f | ||
|  | 114f81941e | ||
|  | 077c7d767f | ||
|  | 8f88addf9f | ||
|  | f28c124039 | ||
|  | a416bc0058 | ||
|  | e78b1dcf3c | ||
|  | 8a14f5d814 | ||
|  | e5f983fbac | ||
|  | 3e639e96e7 | ||
|  | 61993f0687 | ||
|  | 5f16fa8c08 | ||
|  | dcea9c9ab2 | ||
|  | e7bf0799b6 | ||
|  | e760421f6f | ||
|  | 8ea4c17315 | ||
|  | 2e24da4614 | ||
|  | e46601872b | ||
|  | 6d0e41b760 | ||
|  | 5a82df837d | ||
|  | 776b819a5a | ||
|  | 1783f6c84b | ||
|  | 2ef2c73efe | ||
|  | 55e003ccc1 | ||
|  | 3d54d55dbb | ||
|  | 72c0a631f7 | ||
|  | 1608a90d5d | ||
|  | 4f8a45a6ce | ||
|  | 4f0f1dcf18 | ||
|  | 839e51d92d | ||
|  | e470cf23d8 | ||
|  | 8d4a96683a | ||
|  | f53411a319 | ||
|  | 128a1da626 | ||
|  | 962275c22a | ||
|  | 3002ac8a4a | ||
|  | ff43674638 | ||
|  | 2f6c366668 | ||
|  | 2ce1f0a3b1 | ||
|  | 210129c3a1 | ||
|  | 934901447a | ||
|  | 960b289e70 | ||
|  | 243e40cd79 | ||
|  | c849188016 | ||
|  | 87e8dade2f | ||
|  | 6fc5b4e825 | ||
|  | 00ce7f8ae0 | ||
|  | 6e0e9afe2f | ||
|  | cb0d994827 | ||
|  | bee782234a | ||
|  | 64dad35026 | ||
|  | cbd1a8cf78 | ||
|  | a4ab0afce3 | ||
|  | 1c7e0f3c9d | ||
|  | 318cdb41ea | ||
|  | 2f8e31bc8b | ||
|  | 310c722cc0 | ||
|  | 25956bd90f | ||
|  | 1a60ced61b | ||
|  | 081316c071 | ||
|  | eafbc12cc1 | ||
|  | ca08716c52 | ||
|  | 30cef1ee22 | ||
|  | 5598802439 | ||
|  | 1c6720b0db | ||
|  | 404b088199 | ||
|  | 7d61df238a | ||
|  | c86db12f1c | ||
|  | ce2e85af8b | ||
|  | 2d82855f26 | ||
|  | faec516a2c | ||
|  | 8e274ec5d0 | ||
|  | bb1a0a0b76 | ||
|  | 252650808d | ||
|  | e3d9254555 | ||
|  | 90cf99b626 | ||
|  | 955e909e61 | ||
|  | 8339e2044c | ||
|  | 0e0c789b02 | ||
|  | 7e001c1d03 | ||
|  | 9047932b81 | ||
|  | f668e4a54c | ||
|  | ce1c96d68c | ||
|  | 0f67e490e8 | ||
|  | 895c315fa5 | ||
|  | a90a74a512 | ||
|  | 3e1286cbef | ||
|  | 949c1e1668 | ||
|  | bbd4e4d3dc | ||
|  | 4c5f596533 | ||
|  | 4859d3781b | ||
|  | bac0461f7f | ||
|  | f26a200d78 | 
							
								
								
									
										22
									
								
								.github/workflows/ccpp.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								.github/workflows/ccpp.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,22 @@ | ||||
| name: SDL/Ubuntu | ||||
|  | ||||
| on: | ||||
|   push: | ||||
|     branches:  | ||||
|       - master | ||||
|  | ||||
|   pull_request: | ||||
|     branches:  | ||||
|       - master | ||||
|  | ||||
| jobs: | ||||
|   build: | ||||
|  | ||||
|     runs-on: ubuntu-latest | ||||
|      | ||||
|     steps: | ||||
|     - uses: actions/checkout@v1 | ||||
|     - name: Install dependencies | ||||
|       run: sudo apt-get --allow-releaseinfo-change update; sudo apt-get install libsdl2-dev scons | ||||
|     - name: Make | ||||
|       run: cd OSBindings/SDL; scons | ||||
							
								
								
									
										13
									
								
								.travis.yml
									
									
									
									
									
								
							
							
						
						
									
										13
									
								
								.travis.yml
									
									
									
									
									
								
							| @@ -1,13 +0,0 @@ | ||||
| # language: objective-c | ||||
| # osx_image: xcode8.2 | ||||
| # xcode_project: OSBindings/Mac/Clock Signal.xcodeproj | ||||
| # xcode_scheme: Clock Signal | ||||
| # xcode_sdk: macosx10.12 | ||||
|  | ||||
| language: cpp | ||||
| before_install: | ||||
| 	- sudo apt-get install libsdl2-dev | ||||
| script: cd OSBindings/SDL && scons | ||||
| compiler: | ||||
| 	- clang | ||||
| 	- gcc | ||||
| @@ -81,6 +81,6 @@ MultiJoystickMachine::MultiJoystickMachine(const std::vector<std::unique_ptr<::M | ||||
| 	} | ||||
| } | ||||
|  | ||||
| std::vector<std::unique_ptr<Inputs::Joystick>> &MultiJoystickMachine::get_joysticks() { | ||||
| const std::vector<std::unique_ptr<Inputs::Joystick>> &MultiJoystickMachine::get_joysticks() { | ||||
| 	return joysticks_; | ||||
| } | ||||
|   | ||||
| @@ -28,7 +28,7 @@ class MultiJoystickMachine: public JoystickMachine::Machine { | ||||
| 		MultiJoystickMachine(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines); | ||||
|  | ||||
| 		// Below is the standard JoystickMachine::Machine interface; see there for documentation. | ||||
| 		std::vector<std::unique_ptr<Inputs::Joystick>> &get_joysticks() override; | ||||
| 		const std::vector<std::unique_ptr<Inputs::Joystick>> &get_joysticks() override; | ||||
|  | ||||
| 	private: | ||||
| 		std::vector<std::unique_ptr<Inputs::Joystick>> joysticks_; | ||||
|   | ||||
| @@ -15,6 +15,7 @@ enum class Machine { | ||||
| 	AmstradCPC, | ||||
| 	AppleII, | ||||
| 	Atari2600, | ||||
| 	AtariST, | ||||
| 	ColecoVision, | ||||
| 	Electron, | ||||
| 	Macintosh, | ||||
|   | ||||
| @@ -18,7 +18,7 @@ using namespace Analyser::Static::Acorn; | ||||
|  | ||||
| std::unique_ptr<Catalogue> Analyser::Static::Acorn::GetDFSCatalogue(const std::shared_ptr<Storage::Disk::Disk> &disk) { | ||||
| 	// c.f. http://beebwiki.mdfs.net/Acorn_DFS_disc_format | ||||
| 	std::unique_ptr<Catalogue> catalogue(new Catalogue); | ||||
| 	auto catalogue = std::make_unique<Catalogue>(); | ||||
| 	Storage::Encodings::MFM::Parser parser(false, disk); | ||||
|  | ||||
| 	Storage::Encodings::MFM::Sector *names = parser.get_sector(0, 0, 0); | ||||
| @@ -75,7 +75,7 @@ std::unique_ptr<Catalogue> Analyser::Static::Acorn::GetDFSCatalogue(const std::s | ||||
| 	return catalogue; | ||||
| } | ||||
| std::unique_ptr<Catalogue> Analyser::Static::Acorn::GetADFSCatalogue(const std::shared_ptr<Storage::Disk::Disk> &disk) { | ||||
| 	std::unique_ptr<Catalogue> catalogue(new Catalogue); | ||||
| 	auto catalogue = std::make_unique<Catalogue>(); | ||||
| 	Storage::Encodings::MFM::Parser parser(true, disk); | ||||
|  | ||||
| 	Storage::Encodings::MFM::Sector *free_space_map_second_half = parser.get_sector(0, 0, 1); | ||||
|   | ||||
| @@ -58,7 +58,7 @@ static std::vector<std::shared_ptr<Storage::Cartridge::Cartridge>> | ||||
| } | ||||
|  | ||||
| Analyser::Static::TargetList Analyser::Static::Acorn::GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms) { | ||||
| 	std::unique_ptr<Target> target(new Target); | ||||
| 	auto target = std::make_unique<Target>(); | ||||
| 	target->machine = Machine::Electron; | ||||
| 	target->confidence = 0.5; // TODO: a proper estimation | ||||
| 	target->has_dfs = false; | ||||
|   | ||||
| @@ -16,7 +16,7 @@ | ||||
| using namespace Analyser::Static::Acorn; | ||||
|  | ||||
| static std::unique_ptr<File::Chunk> GetNextChunk(const std::shared_ptr<Storage::Tape::Tape> &tape, Storage::Tape::Acorn::Parser &parser) { | ||||
| 	std::unique_ptr<File::Chunk> new_chunk(new File::Chunk); | ||||
| 	auto new_chunk = std::make_unique<File::Chunk>(); | ||||
| 	int shift_register = 0; | ||||
|  | ||||
| // TODO: move this into the parser | ||||
| @@ -90,7 +90,7 @@ static std::unique_ptr<File> GetNextFile(std::deque<File::Chunk> &chunks) { | ||||
| 	if(!chunks.size()) return nullptr; | ||||
|  | ||||
| 	// accumulate chunks for as long as block number is sequential and the end-of-file bit isn't set | ||||
| 	std::unique_ptr<File> file(new File); | ||||
| 	auto file = std::make_unique<File>(); | ||||
|  | ||||
| 	uint16_t block_number = 0; | ||||
|  | ||||
|   | ||||
| @@ -181,7 +181,7 @@ static bool CheckBootSector(const std::shared_ptr<Storage::Disk::Disk> &disk, co | ||||
|  | ||||
| Analyser::Static::TargetList Analyser::Static::AmstradCPC::GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms) { | ||||
| 	TargetList destination; | ||||
| 	std::unique_ptr<Target> target(new Target); | ||||
| 	auto target = std::make_unique<Target>(); | ||||
| 	target->machine = Machine::AmstradCPC; | ||||
| 	target->confidence = 0.5; | ||||
|  | ||||
|   | ||||
| @@ -10,7 +10,7 @@ | ||||
| #include "Target.hpp" | ||||
|  | ||||
| Analyser::Static::TargetList Analyser::Static::AppleII::GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms) { | ||||
| 	auto target = std::unique_ptr<Target>(new Target); | ||||
| 	auto target = std::make_unique<Target>(); | ||||
| 	target->machine = Machine::AppleII; | ||||
| 	target->media = media; | ||||
|  | ||||
|   | ||||
| @@ -12,9 +12,10 @@ | ||||
| 
 | ||||
| #include "../Disassembler/6502.hpp" | ||||
| 
 | ||||
| using namespace Analyser::Static::Atari; | ||||
| using namespace Analyser::Static::Atari2600; | ||||
| using Target = Analyser::Static::Atari2600::Target; | ||||
| 
 | ||||
| static void DeterminePagingFor2kCartridge(Analyser::Static::Atari::Target &target, const Storage::Cartridge::Cartridge::Segment &segment) { | ||||
| static void DeterminePagingFor2kCartridge(Target &target, const Storage::Cartridge::Cartridge::Segment &segment) { | ||||
| 	// if this is a 2kb cartridge then it's definitely either unpaged or a CommaVid
 | ||||
| 	uint16_t entry_address, break_address; | ||||
| 
 | ||||
| @@ -48,10 +49,10 @@ static void DeterminePagingFor2kCartridge(Analyser::Static::Atari::Target &targe | ||||
| 	// caveat: false positives aren't likely to be problematic; a false positive is a 2KB ROM that always addresses
 | ||||
| 	// itself so as to land in ROM even if mapped as a CommaVid and this code is on the fence as to whether it
 | ||||
| 	// attempts to modify itself but it probably doesn't
 | ||||
| 	if(has_wide_area_store) target.paging_model = Analyser::Static::Atari::Target::PagingModel::CommaVid; | ||||
| 	if(has_wide_area_store) target.paging_model = Target::PagingModel::CommaVid; | ||||
| } | ||||
| 
 | ||||
| static void DeterminePagingFor8kCartridge(Analyser::Static::Atari::Target &target, const Storage::Cartridge::Cartridge::Segment &segment, const Analyser::Static::MOS6502::Disassembly &disassembly) { | ||||
| static void DeterminePagingFor8kCartridge(Target &target, const Storage::Cartridge::Cartridge::Segment &segment, const Analyser::Static::MOS6502::Disassembly &disassembly) { | ||||
| 	// Activision stack titles have their vectors at the top of the low 4k, not the top, and
 | ||||
| 	// always list 0xf000 as both vectors; they do not repeat them, and, inexplicably, they all
 | ||||
| 	// issue an SEI as their first instruction (maybe some sort of relic of the development environment?)
 | ||||
| @@ -60,12 +61,12 @@ static void DeterminePagingFor8kCartridge(Analyser::Static::Atari::Target &targe | ||||
| 		(segment.data[8191] != 0xf0 || segment.data[8189] != 0xf0 || segment.data[8190] != 0x00 || segment.data[8188] != 0x00) && | ||||
| 		segment.data[0] == 0x78 | ||||
| 	) { | ||||
| 		target.paging_model = Analyser::Static::Atari::Target::PagingModel::ActivisionStack; | ||||
| 		target.paging_model = Target::PagingModel::ActivisionStack; | ||||
| 		return; | ||||
| 	} | ||||
| 
 | ||||
| 	// make an assumption that this is the Atari paging model
 | ||||
| 	target.paging_model = Analyser::Static::Atari::Target::PagingModel::Atari8k; | ||||
| 	target.paging_model = Target::PagingModel::Atari8k; | ||||
| 
 | ||||
| 	std::set<uint16_t> internal_accesses; | ||||
| 	internal_accesses.insert(disassembly.internal_stores.begin(), disassembly.internal_stores.end()); | ||||
| @@ -85,13 +86,13 @@ static void DeterminePagingFor8kCartridge(Analyser::Static::Atari::Target &targe | ||||
| 		tigervision_access_count += masked_address == 0x3f; | ||||
| 	} | ||||
| 
 | ||||
| 	if(parker_access_count > atari_access_count) target.paging_model = Analyser::Static::Atari::Target::PagingModel::ParkerBros; | ||||
| 	else if(tigervision_access_count > atari_access_count) target.paging_model = Analyser::Static::Atari::Target::PagingModel::Tigervision; | ||||
| 	if(parker_access_count > atari_access_count) target.paging_model = Target::PagingModel::ParkerBros; | ||||
| 	else if(tigervision_access_count > atari_access_count) target.paging_model = Target::PagingModel::Tigervision; | ||||
| } | ||||
| 
 | ||||
| static void DeterminePagingFor16kCartridge(Analyser::Static::Atari::Target &target, const Storage::Cartridge::Cartridge::Segment &segment, const Analyser::Static::MOS6502::Disassembly &disassembly) { | ||||
| static void DeterminePagingFor16kCartridge(Target &target, const Storage::Cartridge::Cartridge::Segment &segment, const Analyser::Static::MOS6502::Disassembly &disassembly) { | ||||
| 	// make an assumption that this is the Atari paging model
 | ||||
| 	target.paging_model = Analyser::Static::Atari::Target::PagingModel::Atari16k; | ||||
| 	target.paging_model = Target::PagingModel::Atari16k; | ||||
| 
 | ||||
| 	std::set<uint16_t> internal_accesses; | ||||
| 	internal_accesses.insert(disassembly.internal_stores.begin(), disassembly.internal_stores.end()); | ||||
| @@ -106,17 +107,17 @@ static void DeterminePagingFor16kCartridge(Analyser::Static::Atari::Target &targ | ||||
| 		mnetwork_access_count += masked_address >= 0x1fe0 && masked_address < 0x1ffb; | ||||
| 	} | ||||
| 
 | ||||
| 	if(mnetwork_access_count > atari_access_count) target.paging_model = Analyser::Static::Atari::Target::PagingModel::MNetwork; | ||||
| 	if(mnetwork_access_count > atari_access_count) target.paging_model = Target::PagingModel::MNetwork; | ||||
| } | ||||
| 
 | ||||
| static void DeterminePagingFor64kCartridge(Analyser::Static::Atari::Target &target, const Storage::Cartridge::Cartridge::Segment &segment, const Analyser::Static::MOS6502::Disassembly &disassembly) { | ||||
| static void DeterminePagingFor64kCartridge(Target &target, const Storage::Cartridge::Cartridge::Segment &segment, const Analyser::Static::MOS6502::Disassembly &disassembly) { | ||||
| 	// make an assumption that this is a Tigervision if there is a write to 3F
 | ||||
| 	target.paging_model = | ||||
| 		(disassembly.external_stores.find(0x3f) != disassembly.external_stores.end()) ? | ||||
| 			Analyser::Static::Atari::Target::PagingModel::Tigervision : Analyser::Static::Atari::Target::PagingModel::MegaBoy; | ||||
| 			Target::PagingModel::Tigervision : Target::PagingModel::MegaBoy; | ||||
| } | ||||
| 
 | ||||
| static void DeterminePagingForCartridge(Analyser::Static::Atari::Target &target, const Storage::Cartridge::Cartridge::Segment &segment) { | ||||
| static void DeterminePagingForCartridge(Target &target, const Storage::Cartridge::Cartridge::Segment &segment) { | ||||
| 	if(segment.data.size() == 2048) { | ||||
| 		DeterminePagingFor2kCartridge(target, segment); | ||||
| 		return; | ||||
| @@ -140,16 +141,16 @@ static void DeterminePagingForCartridge(Analyser::Static::Atari::Target &target, | ||||
| 			DeterminePagingFor8kCartridge(target, segment, disassembly); | ||||
| 		break; | ||||
| 		case 10495: | ||||
| 			target.paging_model = Analyser::Static::Atari::Target::PagingModel::Pitfall2; | ||||
| 			target.paging_model = Target::PagingModel::Pitfall2; | ||||
| 		break; | ||||
| 		case 12288: | ||||
| 			target.paging_model = Analyser::Static::Atari::Target::PagingModel::CBSRamPlus; | ||||
| 			target.paging_model = Target::PagingModel::CBSRamPlus; | ||||
| 		break; | ||||
| 		case 16384: | ||||
| 			DeterminePagingFor16kCartridge(target, segment, disassembly); | ||||
| 		break; | ||||
| 		case 32768: | ||||
| 			target.paging_model = Analyser::Static::Atari::Target::PagingModel::Atari32k; | ||||
| 			target.paging_model = Target::PagingModel::Atari32k; | ||||
| 		break; | ||||
| 		case 65536: | ||||
| 			DeterminePagingFor64kCartridge(target, segment, disassembly); | ||||
| @@ -161,8 +162,8 @@ static void DeterminePagingForCartridge(Analyser::Static::Atari::Target &target, | ||||
| 	// check for a Super Chip. Atari ROM images [almost] always have the same value stored over RAM
 | ||||
| 	// regions; when they don't they at least seem to have the first 128 bytes be the same as the
 | ||||
| 	// next 128 bytes. So check for that.
 | ||||
| 	if(	target.paging_model != Analyser::Static::Atari::Target::PagingModel::CBSRamPlus && | ||||
| 		target.paging_model != Analyser::Static::Atari::Target::PagingModel::MNetwork) { | ||||
| 	if(	target.paging_model != Target::PagingModel::CBSRamPlus && | ||||
| 		target.paging_model != Target::PagingModel::MNetwork) { | ||||
| 		bool has_superchip = true; | ||||
| 		for(std::size_t address = 0; address < 128; address++) { | ||||
| 			if(segment.data[address] != segment.data[address+128]) { | ||||
| @@ -174,19 +175,19 @@ static void DeterminePagingForCartridge(Analyser::Static::Atari::Target &target, | ||||
| 	} | ||||
| 
 | ||||
| 	// check for a Tigervision or Tigervision-esque scheme
 | ||||
| 	if(target.paging_model == Analyser::Static::Atari::Target::PagingModel::None && segment.data.size() > 4096) { | ||||
| 	if(target.paging_model == Target::PagingModel::None && segment.data.size() > 4096) { | ||||
| 		bool looks_like_tigervision = disassembly.external_stores.find(0x3f) != disassembly.external_stores.end(); | ||||
| 		if(looks_like_tigervision) target.paging_model = Analyser::Static::Atari::Target::PagingModel::Tigervision; | ||||
| 		if(looks_like_tigervision) target.paging_model = Target::PagingModel::Tigervision; | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| Analyser::Static::TargetList Analyser::Static::Atari::GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms) { | ||||
| Analyser::Static::TargetList Analyser::Static::Atari2600::GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms) { | ||||
| 	// TODO: sanity checking; is this image really for an Atari 2600?
 | ||||
| 	std::unique_ptr<Analyser::Static::Atari::Target> target(new Analyser::Static::Atari::Target); | ||||
| 	auto target = std::make_unique<Target>(); | ||||
| 	target->machine = Machine::Atari2600; | ||||
| 	target->confidence = 0.5; | ||||
| 	target->media.cartridges = media.cartridges; | ||||
| 	target->paging_model = Analyser::Static::Atari::Target::PagingModel::None; | ||||
| 	target->paging_model = Target::PagingModel::None; | ||||
| 	target->uses_superchip = false; | ||||
| 
 | ||||
| 	// try to figure out the paging scheme
 | ||||
| @@ -15,7 +15,7 @@ | ||||
| 
 | ||||
| namespace Analyser { | ||||
| namespace Static { | ||||
| namespace Atari { | ||||
| namespace Atari2600 { | ||||
| 
 | ||||
| TargetList GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms); | ||||
| 
 | ||||
| @@ -6,14 +6,14 @@ | ||||
| //  Copyright 2018 Thomas Harte. All rights reserved.
 | ||||
| //
 | ||||
| 
 | ||||
| #ifndef Analyser_Static_Atari_Target_h | ||||
| #define Analyser_Static_Atari_Target_h | ||||
| #ifndef Analyser_Static_Atari2600_Target_h | ||||
| #define Analyser_Static_Atari2600_Target_h | ||||
| 
 | ||||
| #include "../StaticAnalyser.hpp" | ||||
| 
 | ||||
| namespace Analyser { | ||||
| namespace Static { | ||||
| namespace Atari { | ||||
| namespace Atari2600 { | ||||
| 
 | ||||
| struct Target: public ::Analyser::Static::Target { | ||||
| 	enum class PagingModel { | ||||
							
								
								
									
										26
									
								
								Analyser/Static/AtariST/StaticAnalyser.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								Analyser/Static/AtariST/StaticAnalyser.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,26 @@ | ||||
| // | ||||
| //  StaticAnalyser.cpp | ||||
| //  Clock Signal | ||||
| // | ||||
| //  Created by Thomas Harte on 03/10/2019. | ||||
| //  Copyright © 2019 Thomas Harte. All rights reserved. | ||||
| // | ||||
|  | ||||
| #include "StaticAnalyser.hpp" | ||||
| #include "Target.hpp" | ||||
|  | ||||
| Analyser::Static::TargetList Analyser::Static::AtariST::GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms) { | ||||
| 	// This analyser can comprehend disks and mass-storage devices only. | ||||
| 	if(media.disks.empty()) return {}; | ||||
|  | ||||
| 	// As there is at least one usable media image, wave it through. | ||||
| 	Analyser::Static::TargetList targets; | ||||
|  | ||||
| 	using Target = Analyser::Static::Target; | ||||
| 	auto *target = new Target; | ||||
| 	target->machine = Analyser::Machine::AtariST; | ||||
| 	target->media = media; | ||||
| 	targets.push_back(std::unique_ptr<Analyser::Static::Target>(target)); | ||||
|  | ||||
| 	return targets; | ||||
| } | ||||
							
								
								
									
										27
									
								
								Analyser/Static/AtariST/StaticAnalyser.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								Analyser/Static/AtariST/StaticAnalyser.hpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,27 @@ | ||||
| // | ||||
| //  StaticAnalyser.hpp | ||||
| //  Clock Signal | ||||
| // | ||||
| //  Created by Thomas Harte on 03/10/2019. | ||||
| //  Copyright © 2019 Thomas Harte. All rights reserved. | ||||
| // | ||||
|  | ||||
| #ifndef Analyser_Static_AtariST_StaticAnalyser_hpp | ||||
| #define Analyser_Static_AtariST_StaticAnalyser_hpp | ||||
|  | ||||
| #include "../StaticAnalyser.hpp" | ||||
| #include "../../../Storage/TargetPlatforms.hpp" | ||||
| #include <string> | ||||
|  | ||||
| namespace Analyser { | ||||
| namespace Static { | ||||
| namespace AtariST { | ||||
|  | ||||
| TargetList GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms); | ||||
|  | ||||
| } | ||||
| } | ||||
| } | ||||
|  | ||||
|  | ||||
| #endif /* Analyser_Static_AtariST_StaticAnalyser_hpp */ | ||||
							
								
								
									
										23
									
								
								Analyser/Static/AtariST/Target.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								Analyser/Static/AtariST/Target.hpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,23 @@ | ||||
| // | ||||
| //  Target.hpp | ||||
| //  Clock Signal | ||||
| // | ||||
| //  Created by Thomas Harte on 03/06/2019. | ||||
| //  Copyright © 2019 Thomas Harte. All rights reserved. | ||||
| // | ||||
|  | ||||
| #ifndef Analyser_Static_AtariST_Target_h | ||||
| #define Analyser_Static_AtariST_Target_h | ||||
|  | ||||
| namespace Analyser { | ||||
| namespace Static { | ||||
| namespace AtariST { | ||||
|  | ||||
| struct Target: public ::Analyser::Static::Target { | ||||
| }; | ||||
|  | ||||
| } | ||||
| } | ||||
| } | ||||
|  | ||||
| #endif /* Analyser_Static_AtariST_Target_h */ | ||||
| @@ -54,7 +54,7 @@ static std::vector<std::shared_ptr<Storage::Cartridge::Cartridge>> | ||||
|  | ||||
| Analyser::Static::TargetList Analyser::Static::Coleco::GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms) { | ||||
| 	TargetList targets; | ||||
| 	std::unique_ptr<Target> target(new Target); | ||||
| 	auto target = std::make_unique<Target>(); | ||||
| 	target->machine = Machine::ColecoVision; | ||||
| 	target->confidence = 1.0f - 1.0f / 32768.0f; | ||||
| 	target->media.cartridges = ColecoCartridgesFrom(media.cartridges); | ||||
|   | ||||
| @@ -22,7 +22,7 @@ class CommodoreGCRParser: public Storage::Disk::Controller { | ||||
| 		std::shared_ptr<Storage::Disk::Drive> drive; | ||||
|  | ||||
| 		CommodoreGCRParser() : Storage::Disk::Controller(4000000), shift_register_(0), track_(1) { | ||||
| 			drive.reset(new Storage::Disk::Drive(4000000, 300, 2)); | ||||
| 			drive = std::make_shared<Storage::Disk::Drive>(4000000, 300, 2); | ||||
| 			set_drive(drive); | ||||
| 			drive->set_motor_on(true); | ||||
| 		} | ||||
| @@ -125,7 +125,7 @@ class CommodoreGCRParser: public Storage::Disk::Controller { | ||||
| 		} | ||||
|  | ||||
| 		std::shared_ptr<Sector> get_next_sector() { | ||||
| 			std::shared_ptr<Sector> sector(new Sector); | ||||
| 			auto sector = std::make_shared<Sector>(); | ||||
| 			const int max_index_count = index_count_ + 2; | ||||
|  | ||||
| 			while(index_count_ < max_index_count) { | ||||
|   | ||||
| @@ -16,6 +16,7 @@ | ||||
| #include "../../../Outputs/Log.hpp" | ||||
|  | ||||
| #include <algorithm> | ||||
| #include <cstring> | ||||
| #include <sstream> | ||||
|  | ||||
| using namespace Analyser::Static::Commodore; | ||||
| @@ -44,7 +45,7 @@ static std::vector<std::shared_ptr<Storage::Cartridge::Cartridge>> | ||||
| Analyser::Static::TargetList Analyser::Static::Commodore::GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms) { | ||||
| 	TargetList destination; | ||||
|  | ||||
| 	std::unique_ptr<Target> target(new Target); | ||||
| 	auto target = std::make_unique<Target>(); | ||||
| 	target->machine = Machine::Vic20;	// TODO: machine estimation | ||||
| 	target->confidence = 0.5; // TODO: a proper estimation | ||||
|  | ||||
| @@ -78,7 +79,7 @@ Analyser::Static::TargetList Analyser::Static::Commodore::GetTargets(const Media | ||||
| 	} | ||||
|  | ||||
| 	if(!files.empty()) { | ||||
| 		target->memory_model = Target::MemoryModel::Unexpanded; | ||||
| 		auto memory_model = Target::MemoryModel::Unexpanded; | ||||
| 		std::ostringstream string_stream; | ||||
| 		string_stream << "LOAD\"" << (is_disk ? "*" : "") << "\"," << device << ","; | ||||
| 		if(files.front().is_basic()) { | ||||
| @@ -94,16 +95,18 @@ Analyser::Static::TargetList Analyser::Static::Commodore::GetTargets(const Media | ||||
| 			default: | ||||
| 				LOG("Unrecognised loading address for Commodore program: " << PADHEX(4) <<  files.front().starting_address); | ||||
| 			case 0x1001: | ||||
| 				target->memory_model = Target::MemoryModel::Unexpanded; | ||||
| 				memory_model = Target::MemoryModel::Unexpanded; | ||||
| 			break; | ||||
| 			case 0x1201: | ||||
| 				target->memory_model = Target::MemoryModel::ThirtyTwoKB; | ||||
| 				memory_model = Target::MemoryModel::ThirtyTwoKB; | ||||
| 			break; | ||||
| 			case 0x0401: | ||||
| 				target->memory_model = Target::MemoryModel::EightKB; | ||||
| 				memory_model = Target::MemoryModel::EightKB; | ||||
| 			break; | ||||
| 		} | ||||
|  | ||||
| 		target->set_memory_model(memory_model); | ||||
|  | ||||
| 		// General approach: increase memory size conservatively such that the largest file found will fit. | ||||
| //		for(File &file : files) { | ||||
| //			std::size_t file_size = file.data.size(); | ||||
| @@ -145,13 +148,52 @@ Analyser::Static::TargetList Analyser::Static::Commodore::GetTargets(const Media | ||||
| 	} | ||||
|  | ||||
| 	if(!target->media.empty()) { | ||||
| 		// Inspect filename for a region hint. | ||||
| 		// Inspect filename for configuration hints. | ||||
| 		std::string lowercase_name = file_name; | ||||
| 		std::transform(lowercase_name.begin(), lowercase_name.end(), lowercase_name.begin(), ::tolower); | ||||
|  | ||||
| 		// Hint 1: 'ntsc' anywhere in the name implies America. | ||||
| 		if(lowercase_name.find("ntsc") != std::string::npos) { | ||||
| 			target->region = Analyser::Static::Commodore::Target::Region::American; | ||||
| 		} | ||||
|  | ||||
| 		// Potential additional hints: check for TheC64 tags. | ||||
| 		auto final_underscore = lowercase_name.find_last_of('_'); | ||||
| 		if(final_underscore != std::string::npos) { | ||||
| 			auto iterator = lowercase_name.begin() + ssize_t(final_underscore) + 1; | ||||
|  | ||||
| 			while(iterator != lowercase_name.end()) { | ||||
| 				// Grab the next tag. | ||||
| 				char next_tag[3] = {0, 0, 0}; | ||||
| 				next_tag[0] = *iterator++; | ||||
| 				if(iterator == lowercase_name.end()) break; | ||||
| 				next_tag[1] = *iterator++; | ||||
|  | ||||
| 				// Exit early if attempting to read another tag has run over the file extension. | ||||
| 				if(next_tag[0] == '.' || next_tag[1] == '.') break; | ||||
|  | ||||
| 				// Check whether it's anything. | ||||
| 				target->enabled_ram.bank0 |= !strcmp(next_tag, "b0"); | ||||
| 				target->enabled_ram.bank1 |= !strcmp(next_tag, "b1"); | ||||
| 				target->enabled_ram.bank2 |= !strcmp(next_tag, "b2"); | ||||
| 				target->enabled_ram.bank3 |= !strcmp(next_tag, "b3"); | ||||
| 				target->enabled_ram.bank5 |= !strcmp(next_tag, "b5"); | ||||
| 				if(!strcmp(next_tag, "tn")) {	// i.e. NTSC. | ||||
| 					target->region = Analyser::Static::Commodore::Target::Region::American; | ||||
| 				} | ||||
| 				if(!strcmp(next_tag, "tp")) {	// i.e. PAL. | ||||
| 					target->region = Analyser::Static::Commodore::Target::Region::European; | ||||
| 				} | ||||
|  | ||||
| 				// Unhandled: | ||||
| 				// | ||||
| 				//	M6: 	this is a C64 file. | ||||
| 				//	MV: 	this is a Vic-20 file. | ||||
| 				//	J1/J2:	this C64 file should have the primary joystick in slot 1/2. | ||||
| 				//	RO:		this disk image should be treated as read-only. | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		// Attach a 1540 if there are any disks here. | ||||
| 		target->has_c1540 = !target->media.disks.empty(); | ||||
|  | ||||
|   | ||||
| @@ -31,7 +31,26 @@ struct Target: public ::Analyser::Static::Target { | ||||
| 		Swedish | ||||
| 	}; | ||||
|  | ||||
| 	MemoryModel memory_model = MemoryModel::Unexpanded; | ||||
| 	/// Maps from a named memory model to a bank enabled/disabled set. | ||||
| 	void set_memory_model(MemoryModel memory_model) { | ||||
| 		// This is correct for unexpanded and 32kb memory models. | ||||
| 		enabled_ram.bank0 = enabled_ram.bank1 = | ||||
| 		enabled_ram.bank2 = enabled_ram.bank3 = | ||||
| 		enabled_ram.bank5 = memory_model == MemoryModel::ThirtyTwoKB; | ||||
|  | ||||
| 		// Bank 0 will need to be enabled if this is an 8kb machine. | ||||
| 		enabled_ram.bank0 |= memory_model == MemoryModel::EightKB; | ||||
| 	} | ||||
| 	struct { | ||||
| 		bool bank0 = false; | ||||
| 		bool bank1 = false; | ||||
| 		bool bank2 = false; | ||||
| 		bool bank3 = false; | ||||
| 		bool bank5 = false; | ||||
| 					// Sic. There is no bank 4; this is because the area that logically would be | ||||
| 					// bank 4 is occupied by the character ROM, colour RAM, hardware registers, etc. | ||||
| 	} enabled_ram; | ||||
|  | ||||
| 	Region region = Region::European; | ||||
| 	bool has_c1540 = false; | ||||
| 	std::string loading_command; | ||||
|   | ||||
| @@ -34,7 +34,7 @@ static std::unique_ptr<Analyser::Static::Target> CartridgeTarget( | ||||
| 		output_segments.emplace_back(start_address, segment.data); | ||||
| 	} | ||||
|  | ||||
| 	std::unique_ptr<Analyser::Static::MSX::Target> target(new Analyser::Static::MSX::Target); | ||||
| 	auto target = std::make_unique<Analyser::Static::MSX::Target>(); | ||||
| 	target->machine = Analyser::Machine::MSX; | ||||
| 	target->confidence = confidence; | ||||
|  | ||||
| @@ -269,7 +269,7 @@ Analyser::Static::TargetList Analyser::Static::MSX::GetTargets(const Media &medi | ||||
| 	std::move(cartridge_targets.begin(), cartridge_targets.end(), std::back_inserter(destination)); | ||||
|  | ||||
| 	// Consider building a target for disks and/or tapes. | ||||
| 	std::unique_ptr<Target> target(new Target); | ||||
| 	auto target = std::make_unique<Target>(); | ||||
|  | ||||
| 	// Check tapes for loadable files. | ||||
| 	for(auto &tape : media.tapes) { | ||||
|   | ||||
| @@ -10,10 +10,10 @@ | ||||
| #include "Target.hpp" | ||||
|  | ||||
| Analyser::Static::TargetList Analyser::Static::Macintosh::GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms) { | ||||
| 	// This analyser can comprehend disks only. | ||||
| 	if(media.disks.empty()) return {}; | ||||
| 	// This analyser can comprehend disks and mass-storage devices only. | ||||
| 	if(media.disks.empty() && media.mass_storage_devices.empty()) return {}; | ||||
|  | ||||
| 	// If there is at least one disk, wave it through. | ||||
| 	// As there is at least one usable media image, wave it through. | ||||
| 	Analyser::Static::TargetList targets; | ||||
|  | ||||
| 	using Target = Analyser::Static::Macintosh::Target; | ||||
|   | ||||
| @@ -21,7 +21,7 @@ struct Target: public ::Analyser::Static::Target { | ||||
| 		MacPlus | ||||
| 	}; | ||||
|  | ||||
| 	Model model = Model::Mac512ke; | ||||
| 	Model model = Model::MacPlus; | ||||
| }; | ||||
|  | ||||
| } | ||||
|   | ||||
| @@ -20,7 +20,9 @@ | ||||
|  | ||||
| using namespace Analyser::Static::Oric; | ||||
|  | ||||
| static int Score(const Analyser::Static::MOS6502::Disassembly &disassembly, const std::set<uint16_t> &rom_functions, const std::set<uint16_t> &variable_locations) { | ||||
| namespace { | ||||
|  | ||||
| int score(const Analyser::Static::MOS6502::Disassembly &disassembly, const std::set<uint16_t> &rom_functions, const std::set<uint16_t> &variable_locations) { | ||||
| 	int score = 0; | ||||
|  | ||||
| 	for(const auto address : disassembly.outward_calls)		score += (rom_functions.find(address) != rom_functions.end()) ? 1 : -1; | ||||
| @@ -30,7 +32,7 @@ static int Score(const Analyser::Static::MOS6502::Disassembly &disassembly, cons | ||||
| 	return score; | ||||
| } | ||||
|  | ||||
| static int Basic10Score(const Analyser::Static::MOS6502::Disassembly &disassembly) { | ||||
| int basic10_score(const Analyser::Static::MOS6502::Disassembly &disassembly) { | ||||
| 	const std::set<uint16_t> rom_functions = { | ||||
| 		0x0228,	0x022b, | ||||
| 		0xc3ca,	0xc3f8,	0xc448,	0xc47c,	0xc4b5,	0xc4e3,	0xc4e0,	0xc524,	0xc56f,	0xc5a2,	0xc5f8,	0xc60a,	0xc6a5,	0xc6de,	0xc719,	0xc738, | ||||
| @@ -51,10 +53,10 @@ static int Basic10Score(const Analyser::Static::MOS6502::Disassembly &disassembl | ||||
| 		0x0228, 0x0229, 0x022a, 0x022b, 0x022c, 0x022d, 0x0230 | ||||
| 	}; | ||||
|  | ||||
| 	return Score(disassembly, rom_functions, variable_locations); | ||||
| 	return score(disassembly, rom_functions, variable_locations); | ||||
| } | ||||
|  | ||||
| static int Basic11Score(const Analyser::Static::MOS6502::Disassembly &disassembly) { | ||||
| int basic11_score(const Analyser::Static::MOS6502::Disassembly &disassembly) { | ||||
| 	const std::set<uint16_t> rom_functions = { | ||||
| 		0x0238,	0x023b,	0x023e,	0x0241,	0x0244,	0x0247, | ||||
| 		0xc3c6,	0xc3f4,	0xc444,	0xc47c,	0xc4a8,	0xc4d3,	0xc4e0,	0xc524,	0xc55f,	0xc592,	0xc5e8,	0xc5fa,	0xc692,	0xc6b3,	0xc6ee,	0xc70d, | ||||
| @@ -76,10 +78,10 @@ static int Basic11Score(const Analyser::Static::MOS6502::Disassembly &disassembl | ||||
| 		0x0244, 0x0245, 0x0246, 0x0247, 0x0248, 0x0249, 0x024a, 0x024b, 0x024c | ||||
| 	}; | ||||
|  | ||||
| 	return Score(disassembly, rom_functions, variable_locations); | ||||
| 	return score(disassembly, rom_functions, variable_locations); | ||||
| } | ||||
|  | ||||
| static bool IsMicrodisc(Storage::Encodings::MFM::Parser &parser) { | ||||
| bool is_microdisc(Storage::Encodings::MFM::Parser &parser) { | ||||
| 	/* | ||||
| 		The Microdisc boot sector is sector 2 of track 0 and contains a 23-byte signature. | ||||
| 	*/ | ||||
| @@ -100,8 +102,51 @@ static bool IsMicrodisc(Storage::Encodings::MFM::Parser &parser) { | ||||
| 	return !std::memcmp(signature, first_sample.data(), sizeof(signature)); | ||||
| } | ||||
|  | ||||
| bool is_400_loader(Storage::Encodings::MFM::Parser &parser, uint16_t range_start, uint16_t range_end) { | ||||
| 	/* | ||||
| 		Both the Jasmin and BD-DOS boot sectors are sector 1 of track 0 and are loaded at $400; | ||||
| 		use disassembly to test for likely matches. | ||||
| 	*/ | ||||
|  | ||||
| 	Storage::Encodings::MFM::Sector *sector = parser.get_sector(0, 0, 1); | ||||
| 	if(!sector) return false; | ||||
| 	if(sector->samples.empty()) return false; | ||||
|  | ||||
| 	// Take a copy of the first sampling, and keep only the final 256 bytes (assuming at least that many were found). | ||||
| 	std::vector<uint8_t> first_sample = sector->samples[0]; | ||||
| 	if(first_sample.size() < 256) return false; | ||||
| 	if(first_sample.size() > 256) { | ||||
| 		first_sample.erase(first_sample.end() - 256, first_sample.end()); | ||||
| 	} | ||||
|  | ||||
| 	// Grab a disassembly. | ||||
| 	const auto disassembly = | ||||
| 		Analyser::Static::MOS6502::Disassemble(first_sample, Analyser::Static::Disassembler::OffsetMapper(0x400), {0x400}); | ||||
|  | ||||
| 	// Check for references to the Jasmin registers. | ||||
| 	int register_hits = 0; | ||||
| 	for(auto list : {disassembly.external_stores, disassembly.external_loads, disassembly.external_modifies}) { | ||||
| 		for(auto address : list) { | ||||
| 			register_hits += (address >= range_start && address <= range_end); | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	// Arbitrary, sure, but as long as at least two accesses to the requested register range are found, accept this. | ||||
| 	return register_hits >= 2; | ||||
| } | ||||
|  | ||||
| bool is_jasmin(Storage::Encodings::MFM::Parser &parser) { | ||||
| 	return is_400_loader(parser, 0x3f4, 0x3ff); | ||||
| } | ||||
|  | ||||
| bool is_bd500(Storage::Encodings::MFM::Parser &parser) { | ||||
| 	return is_400_loader(parser, 0x310, 0x323); | ||||
| } | ||||
|  | ||||
| } | ||||
|  | ||||
| Analyser::Static::TargetList Analyser::Static::Oric::GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms) { | ||||
| 	std::unique_ptr<Target> target(new Target); | ||||
| 	auto target = std::make_unique<Target>(); | ||||
| 	target->machine = Machine::Oric; | ||||
| 	target->confidence = 0.5; | ||||
|  | ||||
| @@ -115,12 +160,10 @@ Analyser::Static::TargetList Analyser::Static::Oric::GetTargets(const Media &med | ||||
| 			for(const auto &file : tape_files) { | ||||
| 				if(file.data_type == File::MachineCode) { | ||||
| 					std::vector<uint16_t> entry_points = {file.starting_address}; | ||||
| 					Analyser::Static::MOS6502::Disassembly disassembly = | ||||
| 					const Analyser::Static::MOS6502::Disassembly disassembly = | ||||
| 						Analyser::Static::MOS6502::Disassemble(file.data, Analyser::Static::Disassembler::OffsetMapper(file.starting_address), entry_points); | ||||
|  | ||||
| 					int basic10_score = Basic10Score(disassembly); | ||||
| 					int basic11_score = Basic11Score(disassembly); | ||||
| 					if(basic10_score > basic11_score) basic10_votes++; else basic11_votes++; | ||||
| 					if(basic10_score(disassembly) > basic11_score(disassembly)) ++basic10_votes; else ++basic11_votes; | ||||
| 				} | ||||
| 			} | ||||
|  | ||||
| @@ -130,12 +173,22 @@ Analyser::Static::TargetList Analyser::Static::Oric::GetTargets(const Media &med | ||||
| 	} | ||||
|  | ||||
| 	if(!media.disks.empty()) { | ||||
| 		// Only the Microdisc is emulated right now, so accept only disks that it can boot from. | ||||
| 		// 8-DOS is recognised by a dedicated Disk II analyser, so check only for Microdisc, | ||||
| 		// Jasmin and BD-DOS formats here. | ||||
| 		for(auto &disk: media.disks) { | ||||
| 			Storage::Encodings::MFM::Parser parser(true, disk); | ||||
| 			if(IsMicrodisc(parser)) { | ||||
|  | ||||
| 			if(is_microdisc(parser)) { | ||||
| 				target->disk_interface = Target::DiskInterface::Microdisc; | ||||
| 				target->media.disks.push_back(disk); | ||||
| 			} else if(is_jasmin(parser)) { | ||||
| 				target->disk_interface = Target::DiskInterface::Jasmin; | ||||
| 				target->should_start_jasmin = true; | ||||
| 				target->media.disks.push_back(disk); | ||||
| 			} else if(is_bd500(parser)) { | ||||
| 				target->disk_interface = Target::DiskInterface::BD500; | ||||
| 				target->media.disks.push_back(disk); | ||||
| 				target->rom = Target::ROM::BASIC10; | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
|   | ||||
| @@ -26,12 +26,15 @@ struct Target: public ::Analyser::Static::Target { | ||||
| 	enum class DiskInterface { | ||||
| 		Microdisc, | ||||
| 		Pravetz, | ||||
| 		Jasmin, | ||||
| 		BD500, | ||||
| 		None | ||||
| 	}; | ||||
|  | ||||
| 	ROM rom = ROM::BASIC11; | ||||
| 	DiskInterface disk_interface = DiskInterface::None; | ||||
| 	std::string loading_command; | ||||
| 	bool should_start_jasmin = false; | ||||
| }; | ||||
|  | ||||
| } | ||||
|   | ||||
| @@ -18,7 +18,7 @@ Analyser::Static::TargetList Analyser::Static::Sega::GetTargets(const Media &med | ||||
| 		return {}; | ||||
|  | ||||
| 	TargetList targets; | ||||
| 	std::unique_ptr<Target> target(new Target); | ||||
| 	auto target = std::make_unique<Target>(); | ||||
|  | ||||
| 	target->machine = Machine::MasterSystem; | ||||
|  | ||||
|   | ||||
| @@ -17,7 +17,8 @@ | ||||
| #include "Acorn/StaticAnalyser.hpp" | ||||
| #include "AmstradCPC/StaticAnalyser.hpp" | ||||
| #include "AppleII/StaticAnalyser.hpp" | ||||
| #include "Atari/StaticAnalyser.hpp" | ||||
| #include "Atari2600/StaticAnalyser.hpp" | ||||
| #include "AtariST/StaticAnalyser.hpp" | ||||
| #include "Coleco/StaticAnalyser.hpp" | ||||
| #include "Commodore/StaticAnalyser.hpp" | ||||
| #include "DiskII/StaticAnalyser.hpp" | ||||
| @@ -40,12 +41,18 @@ | ||||
| #include "../../Storage/Disk/DiskImage/Formats/G64.hpp" | ||||
| #include "../../Storage/Disk/DiskImage/Formats/DMK.hpp" | ||||
| #include "../../Storage/Disk/DiskImage/Formats/HFE.hpp" | ||||
| #include "../../Storage/Disk/DiskImage/Formats/MSA.hpp" | ||||
| #include "../../Storage/Disk/DiskImage/Formats/MSXDSK.hpp" | ||||
| #include "../../Storage/Disk/DiskImage/Formats/NIB.hpp" | ||||
| #include "../../Storage/Disk/DiskImage/Formats/OricMFMDSK.hpp" | ||||
| #include "../../Storage/Disk/DiskImage/Formats/SSD.hpp" | ||||
| #include "../../Storage/Disk/DiskImage/Formats/ST.hpp" | ||||
| #include "../../Storage/Disk/DiskImage/Formats/STX.hpp" | ||||
| #include "../../Storage/Disk/DiskImage/Formats/WOZ.hpp" | ||||
|  | ||||
| // Mass Storage Devices (i.e. usually, hard disks) | ||||
| #include "../../Storage/MassStorage/Formats/HFV.hpp" | ||||
|  | ||||
| // Tapes | ||||
| #include "../../Storage/Tape/Formats/CAS.hpp" | ||||
| #include "../../Storage/Tape/Formats/CommodoreTAP.hpp" | ||||
| @@ -102,7 +109,8 @@ static Media GetMediaAndPlatforms(const std::string &file_name, TargetPlatform:: | ||||
| 	Format("dsd", result.disks, Disk::DiskImageHolder<Storage::Disk::SSD>, TargetPlatform::Acorn)				// DSD | ||||
| 	Format("dsk", result.disks, Disk::DiskImageHolder<Storage::Disk::CPCDSK>, TargetPlatform::AmstradCPC)		// DSK (Amstrad CPC) | ||||
| 	Format("dsk", result.disks, Disk::DiskImageHolder<Storage::Disk::AppleDSK>, TargetPlatform::DiskII)			// DSK (Apple II) | ||||
| 	Format("dsk", result.disks, Disk::DiskImageHolder<Storage::Disk::MacintoshIMG>, TargetPlatform::Macintosh)	// DSK (Macintosh) | ||||
| 	Format("dsk", result.disks, Disk::DiskImageHolder<Storage::Disk::MacintoshIMG>, TargetPlatform::Macintosh)	// DSK (Macintosh, floppy disk) | ||||
| 	Format("dsk", result.mass_storage_devices, MassStorage::HFV, TargetPlatform::Macintosh)						// DSK (Macintosh, hard disk) | ||||
| 	Format("dsk", result.disks, Disk::DiskImageHolder<Storage::Disk::MSXDSK>, TargetPlatform::MSX)				// DSK (MSX) | ||||
| 	Format("dsk", result.disks, Disk::DiskImageHolder<Storage::Disk::OricMFMDSK>, TargetPlatform::Oric)			// DSK (Oric) | ||||
| 	Format("g64", result.disks, Disk::DiskImageHolder<Storage::Disk::G64>, TargetPlatform::Commodore)			// G64 | ||||
| @@ -113,6 +121,7 @@ static Media GetMediaAndPlatforms(const std::string &file_name, TargetPlatform:: | ||||
| 			// HFE (TODO: switch to AllDisk once the MSX stops being so greedy) | ||||
| 	Format("img", result.disks, Disk::DiskImageHolder<Storage::Disk::MacintoshIMG>, TargetPlatform::Macintosh)		// IMG (DiskCopy 4.2) | ||||
| 	Format("image", result.disks, Disk::DiskImageHolder<Storage::Disk::MacintoshIMG>, TargetPlatform::Macintosh)	// IMG (DiskCopy 4.2) | ||||
| 	Format("msa", result.disks, Disk::DiskImageHolder<Storage::Disk::MSA>, TargetPlatform::AtariST)				// MSA | ||||
| 	Format("nib", result.disks, Disk::DiskImageHolder<Storage::Disk::NIB>, TargetPlatform::DiskII)				// NIB | ||||
| 	Format("o", result.tapes, Tape::ZX80O81P, TargetPlatform::ZX8081)											// O | ||||
| 	Format("p", result.tapes, Tape::ZX80O81P, TargetPlatform::ZX8081)											// P | ||||
| @@ -138,6 +147,8 @@ static Media GetMediaAndPlatforms(const std::string &file_name, TargetPlatform:: | ||||
| 	Format("sg", result.cartridges, Cartridge::BinaryDump, TargetPlatform::Sega)								// SG | ||||
| 	Format("sms", result.cartridges, Cartridge::BinaryDump, TargetPlatform::Sega)								// SMS | ||||
| 	Format("ssd", result.disks, Disk::DiskImageHolder<Storage::Disk::SSD>, TargetPlatform::Acorn)				// SSD | ||||
| 	Format("st", result.disks, Disk::DiskImageHolder<Storage::Disk::ST>, TargetPlatform::AtariST)				// ST | ||||
| 	Format("stx", result.disks, Disk::DiskImageHolder<Storage::Disk::STX>, TargetPlatform::AtariST)				// STX | ||||
| 	Format("tap", result.tapes, Tape::CommodoreTAP, TargetPlatform::Commodore)									// TAP (Commodore) | ||||
| 	Format("tap", result.tapes, Tape::OricTAP, TargetPlatform::Oric)											// TAP (Oric) | ||||
| 	Format("tsx", result.tapes, Tape::TZX, TargetPlatform::MSX)													// TSX | ||||
| @@ -160,7 +171,7 @@ Media Analyser::Static::GetMedia(const std::string &file_name) { | ||||
| TargetList Analyser::Static::GetTargets(const std::string &file_name) { | ||||
| 	TargetList targets; | ||||
|  | ||||
| 	// Collect all disks, tapes and ROMs as can be extrapolated from this file, forming the | ||||
| 	// Collect all disks, tapes ROMs, etc as can be extrapolated from this file, forming the | ||||
| 	// union of all platforms this file might be a target for. | ||||
| 	TargetPlatform::IntType potential_platforms = 0; | ||||
| 	Media media = GetMediaAndPlatforms(file_name, potential_platforms); | ||||
| @@ -174,7 +185,8 @@ TargetList Analyser::Static::GetTargets(const std::string &file_name) { | ||||
| 	if(potential_platforms & TargetPlatform::Acorn)			Append(Acorn); | ||||
| 	if(potential_platforms & TargetPlatform::AmstradCPC)	Append(AmstradCPC); | ||||
| 	if(potential_platforms & TargetPlatform::AppleII)		Append(AppleII); | ||||
| 	if(potential_platforms & TargetPlatform::Atari2600)		Append(Atari); | ||||
| 	if(potential_platforms & TargetPlatform::Atari2600)		Append(Atari2600); | ||||
| 	if(potential_platforms & TargetPlatform::AtariST)		Append(AtariST); | ||||
| 	if(potential_platforms & TargetPlatform::ColecoVision)	Append(Coleco); | ||||
| 	if(potential_platforms & TargetPlatform::Commodore)		Append(Commodore); | ||||
| 	if(potential_platforms & TargetPlatform::DiskII)		Append(DiskII); | ||||
|   | ||||
| @@ -11,9 +11,10 @@ | ||||
|  | ||||
| #include "../Machines.hpp" | ||||
|  | ||||
| #include "../../Storage/Tape/Tape.hpp" | ||||
| #include "../../Storage/Disk/Disk.hpp" | ||||
| #include "../../Storage/Cartridge/Cartridge.hpp" | ||||
| #include "../../Storage/Disk/Disk.hpp" | ||||
| #include "../../Storage/MassStorage/MassStorageDevice.hpp" | ||||
| #include "../../Storage/Tape/Tape.hpp" | ||||
|  | ||||
| #include <memory> | ||||
| #include <string> | ||||
| @@ -29,9 +30,10 @@ struct Media { | ||||
| 	std::vector<std::shared_ptr<Storage::Disk::Disk>> disks; | ||||
| 	std::vector<std::shared_ptr<Storage::Tape::Tape>> tapes; | ||||
| 	std::vector<std::shared_ptr<Storage::Cartridge::Cartridge>> cartridges; | ||||
| 	std::vector<std::shared_ptr<Storage::MassStorage::MassStorageDevice>> mass_storage_devices; | ||||
|  | ||||
| 	bool empty() const { | ||||
| 		return disks.empty() && tapes.empty() && cartridges.empty(); | ||||
| 		return disks.empty() && tapes.empty() && cartridges.empty() && mass_storage_devices.empty(); | ||||
| 	} | ||||
| }; | ||||
|  | ||||
|   | ||||
| @@ -10,6 +10,7 @@ | ||||
| #define ClockReceiver_hpp | ||||
|  | ||||
| #include "ForceInline.hpp" | ||||
| #include <cstdint> | ||||
|  | ||||
| /* | ||||
| 	Informal pattern for all classes that run from a clock cycle: | ||||
| @@ -54,7 +55,9 @@ | ||||
| */ | ||||
| template <class T> class WrappedInt { | ||||
| 	public: | ||||
| 		forceinline constexpr WrappedInt(int l) noexcept : length_(l) {} | ||||
| 		using IntType = int64_t; | ||||
|  | ||||
| 		forceinline constexpr WrappedInt(IntType l) noexcept : length_(l) {} | ||||
| 		forceinline constexpr WrappedInt() noexcept : length_(0) {} | ||||
|  | ||||
| 		forceinline T &operator =(const T &rhs) { | ||||
| @@ -133,16 +136,20 @@ template <class T> class WrappedInt { | ||||
| 		forceinline constexpr bool operator !() const					{	return !length_;					} | ||||
| 		// bool operator () is not supported because it offers an implicit cast to int, which is prone silently to permit misuse | ||||
|  | ||||
| 		forceinline constexpr int as_int() const { return length_; } | ||||
| 		/// @returns The underlying int, cast to an integral type of your choosing. | ||||
| 		template<typename Type = IntType> forceinline constexpr Type as() const { return Type(length_); } | ||||
|  | ||||
| 		/// @returns The underlying int, in its native form. | ||||
| 		forceinline constexpr IntType as_integral() const { return length_; } | ||||
|  | ||||
| 		/*! | ||||
| 			Severs from @c this the effect of dividing by @c divisor; @c this will end up with | ||||
| 			the value of @c this modulo @c divisor and @c divided by @c divisor is returned. | ||||
| 		*/ | ||||
| 		forceinline T divide(const T &divisor) { | ||||
| 			T result(length_ / divisor.length_); | ||||
| 			length_ %= divisor.length_; | ||||
| 			return result; | ||||
| 		template <typename Result = T> forceinline Result divide(const T &divisor) { | ||||
| 			Result r; | ||||
| 			static_cast<T *>(this)->fill(r, divisor); | ||||
| 			return r; | ||||
| 		} | ||||
|  | ||||
| 		/*! | ||||
| @@ -161,13 +168,13 @@ template <class T> class WrappedInt { | ||||
| 		// classes that use this template. | ||||
|  | ||||
| 	protected: | ||||
| 		int length_; | ||||
| 		IntType length_; | ||||
| }; | ||||
|  | ||||
| /// Describes an integer number of whole cycles: pairs of clock signal transitions. | ||||
| class Cycles: public WrappedInt<Cycles> { | ||||
| 	public: | ||||
| 		forceinline constexpr Cycles(int l) noexcept : WrappedInt<Cycles>(l) {} | ||||
| 		forceinline constexpr Cycles(IntType l) noexcept : WrappedInt<Cycles>(l) {} | ||||
| 		forceinline constexpr Cycles() noexcept : WrappedInt<Cycles>() {} | ||||
| 		forceinline constexpr Cycles(const Cycles &cycles) noexcept : WrappedInt<Cycles>(cycles.length_) {} | ||||
|  | ||||
| @@ -177,15 +184,20 @@ class Cycles: public WrappedInt<Cycles> { | ||||
| 			result.length_ = length_; | ||||
| 			length_ = 0; | ||||
| 		} | ||||
|  | ||||
| 		void fill(Cycles &result, const Cycles &divisor) { | ||||
| 			result.length_ = length_ / divisor.length_; | ||||
| 			length_ %= divisor.length_; | ||||
| 		} | ||||
| }; | ||||
|  | ||||
| /// Describes an integer number of half cycles: single clock signal transitions. | ||||
| class HalfCycles: public WrappedInt<HalfCycles> { | ||||
| 	public: | ||||
| 		forceinline constexpr HalfCycles(int l) noexcept : WrappedInt<HalfCycles>(l) {} | ||||
| 		forceinline constexpr HalfCycles(IntType l) noexcept : WrappedInt<HalfCycles>(l) {} | ||||
| 		forceinline constexpr HalfCycles() noexcept : WrappedInt<HalfCycles>() {} | ||||
|  | ||||
| 		forceinline constexpr HalfCycles(const Cycles &cycles) noexcept : WrappedInt<HalfCycles>(cycles.as_int() * 2) {} | ||||
| 		forceinline constexpr HalfCycles(const Cycles &cycles) noexcept : WrappedInt<HalfCycles>(cycles.as_integral() * 2) {} | ||||
| 		forceinline constexpr HalfCycles(const HalfCycles &half_cycles) noexcept : WrappedInt<HalfCycles>(half_cycles.length_) {} | ||||
|  | ||||
| 		/// @returns The number of whole cycles completely covered by this span of half cycles. | ||||
| @@ -215,6 +227,16 @@ class HalfCycles: public WrappedInt<HalfCycles> { | ||||
| 			result.length_ = length_; | ||||
| 			length_ = 0; | ||||
| 		} | ||||
|  | ||||
| 		void fill(Cycles &result, const HalfCycles &divisor) { | ||||
| 			result = Cycles(length_ / (divisor.length_ << 1)); | ||||
| 			length_ %= (divisor.length_ << 1); | ||||
| 		} | ||||
|  | ||||
| 		void fill(HalfCycles &result, const HalfCycles &divisor) { | ||||
| 			result.length_ = length_ / divisor.length_; | ||||
| 			length_ %= divisor.length_; | ||||
| 		} | ||||
| }; | ||||
|  | ||||
| // Create a specialisation of WrappedInt::flush for converting HalfCycles to Cycles | ||||
|   | ||||
| @@ -9,9 +9,9 @@ | ||||
| #ifndef ForceInline_hpp | ||||
| #define ForceInline_hpp | ||||
|  | ||||
| #ifdef DEBUG | ||||
| #ifndef NDEBUG | ||||
|  | ||||
| #define forceinline | ||||
| #define forceinline inline | ||||
|  | ||||
| #else | ||||
|  | ||||
|   | ||||
| @@ -10,6 +10,7 @@ | ||||
| #define JustInTime_h | ||||
|  | ||||
| #include "../Concurrency/AsyncTaskQueue.hpp" | ||||
| #include "ForceInline.hpp" | ||||
|  | ||||
| /*! | ||||
| 	A JustInTimeActor holds (i) an embedded object with a run_for method; and (ii) an amount | ||||
| @@ -21,32 +22,44 @@ | ||||
| 	Machines that accumulate HalfCycle time but supply to a Cycle-counted device may supply a | ||||
| 	separate @c TargetTimeScale at template declaration. | ||||
| */ | ||||
| template <class T, class LocalTimeScale, class TargetTimeScale = LocalTimeScale> class JustInTimeActor { | ||||
| template <class T, int multiplier = 1, int divider = 1, class LocalTimeScale = HalfCycles, class TargetTimeScale = LocalTimeScale> class JustInTimeActor { | ||||
| 	public: | ||||
| 		/// Constructs a new JustInTimeActor using the same construction arguments as the included object. | ||||
| 		template<typename... Args> JustInTimeActor(Args&&... args) : object_(std::forward<Args>(args)...) {} | ||||
|  | ||||
| 		/// Adds time to the actor. | ||||
| 		inline void operator += (const LocalTimeScale &rhs) { | ||||
| 			time_since_update_ += rhs; | ||||
| 		forceinline void operator += (const LocalTimeScale &rhs) { | ||||
| 			if constexpr (multiplier != 1) { | ||||
| 				time_since_update_ += rhs * multiplier; | ||||
| 			} else { | ||||
| 				time_since_update_ += rhs; | ||||
| 			} | ||||
| 			is_flushed_ = false; | ||||
| 		} | ||||
|  | ||||
| 		/// Flushes all accumulated time and returns a pointer to the included object. | ||||
| 		inline T *operator->() { | ||||
| 		forceinline T *operator->() { | ||||
| 			flush(); | ||||
| 			return &object_; | ||||
| 		} | ||||
|  | ||||
| 		/// Returns a pointer to the included object without flushing time. | ||||
| 		inline T *last_valid() { | ||||
| 		forceinline T *last_valid() { | ||||
| 			return &object_; | ||||
| 		} | ||||
|  | ||||
| 		/// Flushes all accumulated time. | ||||
| 		inline void flush() { | ||||
| 			if(!is_flushed_) object_.run_for(time_since_update_.template flush<TargetTimeScale>()); | ||||
| 			is_flushed_ = true; | ||||
| 		forceinline void flush() { | ||||
| 			if(!is_flushed_) { | ||||
| 				is_flushed_ = true; | ||||
| 				if constexpr (divider == 1) { | ||||
| 					object_.run_for(time_since_update_.template flush<TargetTimeScale>()); | ||||
| 				} else { | ||||
| 					const auto duration = time_since_update_.template divide<TargetTimeScale>(LocalTimeScale(divider)); | ||||
| 					if(duration > TargetTimeScale(0)) | ||||
| 						object_.run_for(duration); | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 	private: | ||||
| @@ -60,7 +73,7 @@ template <class T, class LocalTimeScale, class TargetTimeScale = LocalTimeScale> | ||||
| 	Any time the amount of accumulated time crosses a threshold provided at construction time, | ||||
| 	the object will be updated on the AsyncTaskQueue. | ||||
| */ | ||||
| template <class T, class LocalTimeScale, class TargetTimeScale = LocalTimeScale> class AsyncJustInTimeActor { | ||||
| template <class T, class LocalTimeScale = HalfCycles, class TargetTimeScale = LocalTimeScale> class AsyncJustInTimeActor { | ||||
| 	public: | ||||
| 		/// Constructs a new AsyncJustInTimeActor using the same construction arguments as the included object. | ||||
| 		template<typename... Args> AsyncJustInTimeActor(TargetTimeScale threshold, Args&&... args) : | ||||
|   | ||||
| @@ -9,6 +9,8 @@ | ||||
| #include "1770.hpp" | ||||
|  | ||||
| #include "../../Storage/Disk/Encodings/MFM/Constants.hpp" | ||||
|  | ||||
| #define LOG_PREFIX "[WD FDC] " | ||||
| #include "../../Outputs/Log.hpp" | ||||
|  | ||||
| using namespace WD; | ||||
| @@ -16,19 +18,19 @@ using namespace WD; | ||||
| WD1770::WD1770(Personality p) : | ||||
| 		Storage::Disk::MFMController(8000000), | ||||
| 		personality_(p), | ||||
| 		interesting_event_mask_(static_cast<int>(Event1770::Command)) { | ||||
| 		interesting_event_mask_(int(Event1770::Command)) { | ||||
| 	set_is_double_density(false); | ||||
| 	posit_event(static_cast<int>(Event1770::Command)); | ||||
| 	posit_event(int(Event1770::Command)); | ||||
| } | ||||
|  | ||||
| void WD1770::set_register(int address, uint8_t value) { | ||||
| void WD1770::write(int address, uint8_t value) { | ||||
| 	switch(address&3) { | ||||
| 		case 0: { | ||||
| 			if((value&0xf0) == 0xd0) { | ||||
| 				if(value == 0xd0) { | ||||
| 					// Force interrupt **immediately**. | ||||
| 					LOG("Force interrupt immediately"); | ||||
| 					posit_event(static_cast<int>(Event1770::ForceInterrupt)); | ||||
| 					posit_event(int(Event1770::ForceInterrupt)); | ||||
| 				} else { | ||||
| 					ERROR("!!!TODO: force interrupt!!!"); | ||||
| 					update_status([] (Status &status) { | ||||
| @@ -37,7 +39,7 @@ void WD1770::set_register(int address, uint8_t value) { | ||||
| 				} | ||||
| 			} else { | ||||
| 				command_ = value; | ||||
| 				posit_event(static_cast<int>(Event1770::Command)); | ||||
| 				posit_event(int(Event1770::Command)); | ||||
| 			} | ||||
| 		} | ||||
| 		break; | ||||
| @@ -52,27 +54,35 @@ void WD1770::set_register(int address, uint8_t value) { | ||||
| 	} | ||||
| } | ||||
|  | ||||
| uint8_t WD1770::get_register(int address) { | ||||
| uint8_t WD1770::read(int address) { | ||||
| 	switch(address&3) { | ||||
| 		default: { | ||||
| 			update_status([] (Status &status) { | ||||
| 				status.interrupt_request = false; | ||||
| 			}); | ||||
| 			uint8_t status = | ||||
| 					(status_.write_protect ? Flag::WriteProtect : 0) | | ||||
| 					(status_.crc_error ? Flag::CRCError : 0) | | ||||
| 					(status_.busy ? Flag::Busy : 0); | ||||
| 				(status_.crc_error ? Flag::CRCError : 0) | | ||||
| 				(status_.busy ? Flag::Busy : 0); | ||||
|  | ||||
| 			// Per Jean Louis-Guérin's documentation: | ||||
| 			// | ||||
| 			//	* 	the write-protect bit is locked into place by a type 2 or type 3 command, but is | ||||
| 			//		read live after a type 1. | ||||
| 			//	*	the track 0 bit is captured during a type 1 instruction and lost upon any other type, | ||||
| 			//		it is not live sampled. | ||||
| 			switch(status_.type) { | ||||
| 				case Status::One: | ||||
| 					status |= | ||||
| 						(get_drive().get_is_track_zero() ? Flag::TrackZero : 0) | | ||||
| 						(status_.seek_error ? Flag::SeekError : 0); | ||||
| 						// TODO: index hole | ||||
| 						(status_.track_zero ? Flag::TrackZero : 0) | | ||||
| 						(status_.seek_error ? Flag::SeekError : 0) | | ||||
| 						(get_drive().get_is_read_only() ? Flag::WriteProtect : 0) | | ||||
| 						(get_drive().get_index_pulse() ? Flag::Index : 0); | ||||
| 				break; | ||||
|  | ||||
| 				case Status::Two: | ||||
| 				case Status::Three: | ||||
| 					status |= | ||||
| 						(status_.write_protect ? Flag::WriteProtect : 0) | | ||||
| 						(status_.record_type ? Flag::RecordType : 0) | | ||||
| 						(status_.lost_data ? Flag::LostData : 0) | | ||||
| 						(status_.data_request ? Flag::DataRequest : 0) | | ||||
| @@ -89,10 +99,15 @@ uint8_t WD1770::get_register(int address) { | ||||
| 				if(status_.type == Status::One) | ||||
| 					status |= (status_.spin_up ? Flag::SpinUp : 0); | ||||
| 			} | ||||
| //			LOG("Returned status " << PADHEX(2) << int(status) << " of type " << 1+int(status_.type)); | ||||
| 			return status; | ||||
| 		} | ||||
| 		case 1:		return track_; | ||||
| 		case 2:		return sector_; | ||||
| 		case 1: | ||||
| 			LOG("Returned track " << int(track_)); | ||||
| 			return track_; | ||||
| 		case 2: | ||||
| 			LOG("Returned sector " << int(sector_)); | ||||
| 			return sector_; | ||||
| 		case 3: | ||||
| 			update_status([] (Status &status) { | ||||
| 				status.data_request = false; | ||||
| @@ -105,28 +120,30 @@ void WD1770::run_for(const Cycles cycles) { | ||||
| 	Storage::Disk::Controller::run_for(cycles); | ||||
|  | ||||
| 	if(delay_time_) { | ||||
| 		unsigned int number_of_cycles = static_cast<unsigned int>(cycles.as_int()); | ||||
| 		const auto number_of_cycles = cycles.as_integral(); | ||||
| 		if(delay_time_ <= number_of_cycles) { | ||||
| 			delay_time_ = 0; | ||||
| 			posit_event(static_cast<int>(Event1770::Timer)); | ||||
| 			posit_event(int(Event1770::Timer)); | ||||
| 		} else { | ||||
| 			delay_time_ -= number_of_cycles; | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| #define WAIT_FOR_EVENT(mask)	resume_point_ = __LINE__; interesting_event_mask_ = static_cast<int>(mask); return; case __LINE__: | ||||
| #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_ = static_cast<int>(Event::Token); return; } | ||||
| #define WAIT_FOR_BYTES(count)	resume_point_ = __LINE__; distance_into_section_ = 0; WAIT_FOR_EVENT(Event::Token); if(get_latest_token().type == Token::Byte) distance_into_section_++; if(distance_into_section_ < count) { interesting_event_mask_ = int(Event::Token); return; } | ||||
| #define BEGIN_SECTION()	switch(resume_point_) { default: | ||||
| #define END_SECTION()	(void)0; } | ||||
|  | ||||
| #define READ_ID()	\ | ||||
| 		if(new_event_type == static_cast<int>(Event::Token)) {	\ | ||||
| 			if(!distance_into_section_ && get_latest_token().type == Token::ID) {set_data_mode(DataMode::Reading); distance_into_section_++; }	\ | ||||
| 			else if(distance_into_section_ && distance_into_section_ < 7 && get_latest_token().type == Token::Byte) {	\ | ||||
| 		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_++;	\ | ||||
| 				++distance_into_section_;	\ | ||||
| 			}	\ | ||||
| 		} | ||||
|  | ||||
| @@ -159,10 +176,10 @@ void WD1770::run_for(const Cycles cycles) { | ||||
| // +--------+----------+-------------------------+ | ||||
|  | ||||
| void WD1770::posit_event(int new_event_type) { | ||||
| 	if(new_event_type == static_cast<int>(Event::IndexHole)) { | ||||
| 	if(new_event_type == int(Event::IndexHole)) { | ||||
| 		index_hole_count_++; | ||||
| 		if(index_hole_count_target_ == index_hole_count_) { | ||||
| 			posit_event(static_cast<int>(Event1770::IndexHoleTarget)); | ||||
| 			posit_event(int(Event1770::IndexHoleTarget)); | ||||
| 			index_hole_count_target_ = -1; | ||||
| 		} | ||||
|  | ||||
| @@ -177,15 +194,16 @@ void WD1770::posit_event(int new_event_type) { | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	if(new_event_type == static_cast<int>(Event1770::ForceInterrupt)) { | ||||
| 	if(new_event_type == int(Event1770::ForceInterrupt)) { | ||||
| 		interesting_event_mask_ = 0; | ||||
| 		resume_point_ = 0; | ||||
| 		update_status([] (Status &status) { | ||||
| 			status.type = Status::One; | ||||
| 			status.data_request = false; | ||||
| 			status.spin_up = false; | ||||
| 		}); | ||||
| 	} else { | ||||
| 		if(!(interesting_event_mask_ & static_cast<int>(new_event_type))) return; | ||||
| 		if(!(interesting_event_mask_ & int(new_event_type))) return; | ||||
| 		interesting_event_mask_ &= ~new_event_type; | ||||
| 	} | ||||
|  | ||||
| @@ -208,6 +226,7 @@ void WD1770::posit_event(int new_event_type) { | ||||
| 		update_status([] (Status &status) { | ||||
| 			status.busy = true; | ||||
| 			status.interrupt_request = false; | ||||
| 			status.track_zero = false;	// Always reset by a non-type 1; so reset regardless and set properly later. | ||||
| 		}); | ||||
|  | ||||
| 		LOG("Starting " << PADHEX(2) << int(command_)); | ||||
| @@ -240,6 +259,7 @@ void WD1770::posit_event(int new_event_type) { | ||||
| 			status.data_request = false; | ||||
| 		}); | ||||
|  | ||||
| 		LOG("Step/Seek/Restore with track " << int(track_) << " data " << int(data_)); | ||||
| 		if(!has_motor_on_line() && !has_head_load_line()) goto test_type1_type; | ||||
|  | ||||
| 		if(has_motor_on_line()) goto begin_type1_spin_up; | ||||
| @@ -272,19 +292,19 @@ void WD1770::posit_event(int new_event_type) { | ||||
| 		} | ||||
|  | ||||
| 	perform_seek_or_restore_command: | ||||
| 		if(track_ == data_) goto verify; | ||||
| 		if(track_ == data_) goto verify_seek; | ||||
| 		step_direction_ = (data_ > track_); | ||||
|  | ||||
| 	adjust_track: | ||||
| 		if(step_direction_) track_++; else track_--; | ||||
| 		if(step_direction_) ++track_; else --track_; | ||||
|  | ||||
| 	perform_step: | ||||
| 		if(!step_direction_ && get_drive().get_is_track_zero()) { | ||||
| 			track_ = 0; | ||||
| 			goto verify; | ||||
| 			goto verify_seek; | ||||
| 		} | ||||
| 		get_drive().step(Storage::Disk::HeadPosition(step_direction_ ? 1 : -1)); | ||||
| 		unsigned int time_to_wait; | ||||
| 		Cycles::IntType time_to_wait; | ||||
| 		switch(command_ & 3) { | ||||
| 			default: | ||||
| 			case 0: time_to_wait = 6;	break; | ||||
| @@ -293,14 +313,17 @@ void WD1770::posit_event(int new_event_type) { | ||||
| 			case 3: time_to_wait = (personality_ == P1772) ? 3 : 30;	break; | ||||
| 		} | ||||
| 		WAIT_FOR_TIME(time_to_wait); | ||||
| 		if(command_ >> 5) goto verify; | ||||
| 		if(command_ >> 5) goto verify_seek; | ||||
| 		goto perform_seek_or_restore_command; | ||||
|  | ||||
| 	perform_step_command: | ||||
| 		if(command_ & 0x10) goto adjust_track; | ||||
| 		goto perform_step; | ||||
|  | ||||
| 	verify: | ||||
| 	verify_seek: | ||||
| 		update_status([this] (Status &status) { | ||||
| 			status.track_zero = get_drive().get_is_track_zero(); | ||||
| 		}); | ||||
| 		if(!(command_ & 0x04)) { | ||||
| 			goto wait_for_command; | ||||
| 		} | ||||
| @@ -309,17 +332,20 @@ void WD1770::posit_event(int new_event_type) { | ||||
| 		distance_into_section_ = 0; | ||||
|  | ||||
| 	verify_read_data: | ||||
| 		WAIT_FOR_EVENT(static_cast<int>(Event::IndexHole) | static_cast<int>(Event::Token)); | ||||
| 		WAIT_FOR_EVENT(int(Event::IndexHole) | int(Event::Token)); | ||||
| 		READ_ID(); | ||||
|  | ||||
| 		if(index_hole_count_ == 6) { | ||||
| 			LOG("Nothing found to verify"); | ||||
| 			update_status([] (Status &status) { | ||||
| 				status.seek_error = true; | ||||
| 			}); | ||||
| 			goto wait_for_command; | ||||
| 		} | ||||
| 		if(distance_into_section_ == 7) { | ||||
| 			distance_into_section_ = 0; | ||||
| 			set_data_mode(DataMode::Scanning); | ||||
|  | ||||
| 			if(get_crc_generator().get_value()) { | ||||
| 				update_status([] (Status &status) { | ||||
| 					status.crc_error = true; | ||||
| @@ -334,8 +360,6 @@ void WD1770::posit_event(int new_event_type) { | ||||
| 				}); | ||||
| 				goto wait_for_command; | ||||
| 			} | ||||
|  | ||||
| 			distance_into_section_ = 0; | ||||
| 		} | ||||
| 		goto verify_read_data; | ||||
|  | ||||
| @@ -392,8 +416,11 @@ void WD1770::posit_event(int new_event_type) { | ||||
| 			goto wait_for_command; | ||||
| 		} | ||||
|  | ||||
| 		distance_into_section_ = 0; | ||||
| 		set_data_mode(DataMode::Scanning); | ||||
|  | ||||
| 	type2_get_header: | ||||
| 		WAIT_FOR_EVENT(static_cast<int>(Event::IndexHole) | static_cast<int>(Event::Token)); | ||||
| 		WAIT_FOR_EVENT(int(Event::IndexHole) | int(Event::Token)); | ||||
| 		READ_ID(); | ||||
|  | ||||
| 		if(index_hole_count_ == 5) { | ||||
| @@ -404,8 +431,10 @@ void WD1770::posit_event(int new_event_type) { | ||||
| 			goto wait_for_command; | ||||
| 		} | ||||
| 		if(distance_into_section_ == 7) { | ||||
| 			LOG("Considering " << std::dec << int(header_[0]) << "/" << int(header_[2])); | ||||
| 			distance_into_section_ = 0; | ||||
| 			set_data_mode(DataMode::Scanning); | ||||
|  | ||||
| 			LOG("Considering " << std::dec << int(header_[0]) << "/" << int(header_[2])); | ||||
| 			if(		header_[0] == track_ && header_[2] == sector_ && | ||||
| 					(has_motor_on_line() || !(command_&0x02) || ((command_&0x08) >> 3) == header_[1])) { | ||||
| 				LOG("Found " << std::dec << int(header_[0]) << "/" << int(header_[2])); | ||||
| @@ -422,7 +451,6 @@ void WD1770::posit_event(int new_event_type) { | ||||
| 				}); | ||||
| 				goto type2_read_or_write_data; | ||||
| 			} | ||||
| 			distance_into_section_ = 0; | ||||
| 		} | ||||
| 		goto type2_get_header; | ||||
|  | ||||
| @@ -465,6 +493,9 @@ void WD1770::posit_event(int new_event_type) { | ||||
| 		header_[distance_into_section_] = get_latest_token().byte_value; | ||||
| 		distance_into_section_++; | ||||
| 		if(distance_into_section_ == 2) { | ||||
| 			distance_into_section_ = 0; | ||||
| 			set_data_mode(DataMode::Scanning); | ||||
|  | ||||
| 			if(get_crc_generator().get_value()) { | ||||
| 				LOG("CRC error; terminating"); | ||||
| 				update_status([this] (Status &status) { | ||||
| @@ -473,11 +504,13 @@ void WD1770::posit_event(int new_event_type) { | ||||
| 				goto wait_for_command; | ||||
| 			} | ||||
|  | ||||
| 			LOG("Finished reading sector " << std::dec << int(sector_)); | ||||
|  | ||||
| 			if(command_ & 0x10) { | ||||
| 				sector_++; | ||||
| 				LOG("Advancing to search for sector " << std::dec << int(sector_)); | ||||
| 				goto test_type2_write_protection; | ||||
| 			} | ||||
| 			LOG("Finished reading sector " << std::dec << int(sector_)); | ||||
| 			goto wait_for_command; | ||||
| 		} | ||||
| 		goto type2_check_crc; | ||||
| @@ -610,8 +643,8 @@ void WD1770::posit_event(int new_event_type) { | ||||
| 		distance_into_section_ = 0; | ||||
|  | ||||
| 	read_address_get_header: | ||||
| 		WAIT_FOR_EVENT(static_cast<int>(Event::IndexHole) | static_cast<int>(Event::Token)); | ||||
| 		if(new_event_type == static_cast<int>(Event::Token)) { | ||||
| 		WAIT_FOR_EVENT(int(Event::IndexHole) | int(Event::Token)); | ||||
| 		if(new_event_type == int(Event::Token)) { | ||||
| 			if(!distance_into_section_ && get_latest_token().type == Token::ID) {set_data_mode(DataMode::Reading); distance_into_section_++; } | ||||
| 			else if(distance_into_section_ && distance_into_section_ < 7 && get_latest_token().type == Token::Byte) { | ||||
| 				if(status_.data_request) { | ||||
| @@ -625,9 +658,11 @@ void WD1770::posit_event(int new_event_type) { | ||||
| 				update_status([] (Status &status) { | ||||
| 					status.data_request = true; | ||||
| 				}); | ||||
| 				distance_into_section_++; | ||||
| 				++distance_into_section_; | ||||
|  | ||||
| 				if(distance_into_section_ == 7) { | ||||
| 					distance_into_section_ = 0; | ||||
|  | ||||
| 					if(get_crc_generator().get_value()) { | ||||
| 						update_status([] (Status &status) { | ||||
| 							status.crc_error = true; | ||||
| @@ -651,7 +686,7 @@ void WD1770::posit_event(int new_event_type) { | ||||
| 		index_hole_count_ = 0; | ||||
|  | ||||
| 	read_track_read_byte: | ||||
| 		WAIT_FOR_EVENT(static_cast<int>(Event::Token) | static_cast<int>(Event::IndexHole)); | ||||
| 		WAIT_FOR_EVENT(int(Event::Token) | int(Event::IndexHole)); | ||||
| 		if(index_hole_count_) { | ||||
| 			goto wait_for_command; | ||||
| 		} | ||||
| @@ -718,7 +753,7 @@ void WD1770::posit_event(int new_event_type) { | ||||
| 				case 0xfd: case 0xfe: | ||||
| 					// clock is 0xc7 = 1010 0000 0010 1010 = 0xa022 | ||||
| 					write_raw_short( | ||||
| 						static_cast<uint16_t>( | ||||
| 						uint16_t( | ||||
| 							0xa022 | | ||||
| 							((data_ & 0x80) << 7) | | ||||
| 							((data_ & 0x40) << 6) | | ||||
| @@ -767,15 +802,18 @@ void WD1770::posit_event(int new_event_type) { | ||||
| } | ||||
|  | ||||
| void WD1770::update_status(std::function<void(Status &)> updater) { | ||||
| 	const Status old_status = status_; | ||||
|  | ||||
| 	if(delegate_) { | ||||
| 		Status old_status = status_; | ||||
| 		updater(status_); | ||||
| 		bool did_change = | ||||
| 		const bool did_change = | ||||
| 			(status_.busy != old_status.busy) || | ||||
| 			(status_.data_request != old_status.data_request); | ||||
| 			(status_.data_request != old_status.data_request) || | ||||
| 			(status_.interrupt_request != old_status.interrupt_request); | ||||
| 		if(did_change) delegate_->wd1770_did_change_output(this); | ||||
| 	} | ||||
| 	else updater(status_); | ||||
| 	} else updater(status_); | ||||
|  | ||||
| 	if(status_.busy != old_status.busy) update_clocking_observer(); | ||||
| } | ||||
|  | ||||
| void WD1770::set_head_load_request(bool head_load) {} | ||||
| @@ -783,5 +821,14 @@ void WD1770::set_motor_on(bool motor_on) {} | ||||
|  | ||||
| void WD1770::set_head_loaded(bool head_loaded) { | ||||
| 	head_is_loaded_ = head_loaded; | ||||
| 	if(head_loaded) posit_event(static_cast<int>(Event1770::HeadLoad)); | ||||
| 	if(head_loaded) posit_event(int(Event1770::HeadLoad)); | ||||
| } | ||||
|  | ||||
| bool WD1770::get_head_loaded() { | ||||
| 	return head_is_loaded_; | ||||
| } | ||||
|  | ||||
| ClockingHint::Preference WD1770::preferred_clocking() { | ||||
| 	if(status_.busy) return ClockingHint::Preference::RealTime; | ||||
| 	return Storage::Disk::MFMController::preferred_clocking(); | ||||
| } | ||||
|   | ||||
| @@ -36,10 +36,10 @@ class WD1770: public Storage::Disk::MFMController { | ||||
| 		using Storage::Disk::MFMController::set_is_double_density; | ||||
|  | ||||
| 		/// Writes @c value to the register at @c address. Only the low two bits of the address are decoded. | ||||
| 		void set_register(int address, uint8_t value); | ||||
| 		void write(int address, uint8_t value); | ||||
|  | ||||
| 		/// Fetches the value of the register @c address. Only the low two bits of the address are decoded. | ||||
| 		uint8_t get_register(int address); | ||||
| 		uint8_t read(int address); | ||||
|  | ||||
| 		/// Runs the controller for @c number_of_cycles cycles. | ||||
| 		void run_for(const Cycles cycles); | ||||
| @@ -73,11 +73,16 @@ class WD1770: public Storage::Disk::MFMController { | ||||
| 		}; | ||||
| 		inline void set_delegate(Delegate *delegate)	{	delegate_ = delegate;			} | ||||
|  | ||||
| 		ClockingHint::Preference preferred_clocking() final; | ||||
|  | ||||
| 	protected: | ||||
| 		virtual void set_head_load_request(bool head_load); | ||||
| 		virtual void set_motor_on(bool motor_on); | ||||
| 		void set_head_loaded(bool head_loaded); | ||||
|  | ||||
| 		/// @returns The last value posted to @c set_head_loaded. | ||||
| 		bool get_head_loaded(); | ||||
|  | ||||
| 	private: | ||||
| 		Personality personality_; | ||||
| 		inline bool has_motor_on_line() { return (personality_ != P1793 ) && (personality_ != P1773); } | ||||
| @@ -94,6 +99,7 @@ class WD1770: public Storage::Disk::MFMController { | ||||
| 			bool data_request = false; | ||||
| 			bool interrupt_request = false; | ||||
| 			bool busy = false; | ||||
| 			bool track_zero = false; | ||||
| 			enum { | ||||
| 				One, Two, Three | ||||
| 			} type = One; | ||||
| @@ -121,7 +127,7 @@ class WD1770: public Storage::Disk::MFMController { | ||||
| 		void posit_event(int type); | ||||
| 		int interesting_event_mask_; | ||||
| 		int resume_point_ = 0; | ||||
| 		unsigned int delay_time_ = 0; | ||||
| 		Cycles::IntType delay_time_ = 0; | ||||
|  | ||||
| 		// ID buffer | ||||
| 		uint8_t header_[6]; | ||||
|   | ||||
							
								
								
									
										312
									
								
								Components/5380/ncr5380.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										312
									
								
								Components/5380/ncr5380.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,312 @@ | ||||
| // | ||||
| //  ncr5380.cpp | ||||
| //  Clock Signal | ||||
| // | ||||
| //  Created by Thomas Harte on 10/08/2019. | ||||
| //  Copyright © 2019 Thomas Harte. All rights reserved. | ||||
| // | ||||
|  | ||||
| #include "ncr5380.hpp" | ||||
|  | ||||
| #include "../../Outputs/Log.hpp" | ||||
|  | ||||
| using namespace NCR::NCR5380; | ||||
| using SCSI::Line; | ||||
|  | ||||
| NCR5380::NCR5380(SCSI::Bus &bus, int clock_rate) : | ||||
| 	bus_(bus), | ||||
| 	clock_rate_(clock_rate) { | ||||
| 	device_id_ = bus_.add_device(); | ||||
| 	bus_.add_observer(this); | ||||
| } | ||||
|  | ||||
| void NCR5380::write(int address, uint8_t value, bool dma_acknowledge) { | ||||
| 	switch(address & 7) { | ||||
| 		case 0: | ||||
| //			LOG("[SCSI 0] Set current SCSI bus state to " << PADHEX(2) << int(value)); | ||||
| 			data_bus_ = value; | ||||
|  | ||||
| 			if(dma_request_ && dma_operation_ == DMAOperation::Send) { | ||||
| //				printf("w %02x\n", value); | ||||
| 				dma_acknowledge_ = true; | ||||
| 				dma_request_ = false; | ||||
| 				update_control_output(); | ||||
| 				bus_.set_device_output(device_id_, bus_output_); | ||||
| 			} | ||||
| 		break; | ||||
|  | ||||
| 		case 1: { | ||||
| //			LOG("[SCSI 1] Initiator command register set: " << PADHEX(2) << int(value)); | ||||
| 			initiator_command_ = value; | ||||
|  | ||||
| 			bus_output_ &= ~(Line::Reset | Line::Acknowledge | Line::Busy | Line::SelectTarget | Line::Attention); | ||||
| 			if(value & 0x80) bus_output_ |= Line::Reset; | ||||
| 			if(value & 0x08) bus_output_ |= Line::Busy; | ||||
| 			if(value & 0x04) bus_output_ |= Line::SelectTarget; | ||||
|  | ||||
| 			/* bit 5 = differential enable if this were a 5381 */ | ||||
|  | ||||
| 			test_mode_ = value & 0x40; | ||||
| 			assert_data_bus_ = value & 0x01; | ||||
| 			update_control_output(); | ||||
| 		} break; | ||||
|  | ||||
| 		case 2: | ||||
| //			LOG("[SCSI 2] Set mode: " << PADHEX(2) << int(value)); | ||||
| 			mode_ = value; | ||||
|  | ||||
| 			// bit 7: 1 = use block mode DMA mode (if DMA mode is also enabled) | ||||
| 			// bit 6: 1 = be a SCSI target; 0 = be an initiator | ||||
| 			// bit 5: 1 = check parity | ||||
| 			// bit 4: 1 = generate an interrupt if parity checking is enabled and an error is found | ||||
| 			// bit 3: 1 = generate an interrupt when an EOP is received from the DMA controller | ||||
| 			// bit 2: 1 = generate an interrupt and reset low 6 bits of register 1 if an unexpected loss of Line::Busy occurs | ||||
| 			// bit 1: 1 = use DMA mode | ||||
| 			// bit 0: 1 = begin arbitration mode (device ID should be in register 0) | ||||
| 			arbitration_in_progress_ = false; | ||||
| 			switch(mode_ & 0x3) { | ||||
| 				case 0x0: | ||||
| 					bus_output_ &= ~SCSI::Line::Busy; | ||||
| 					dma_request_ = false; | ||||
| 					set_execution_state(ExecutionState::None); | ||||
| 				break; | ||||
|  | ||||
| 				case 0x1: | ||||
| 					arbitration_in_progress_ = true; | ||||
| 					set_execution_state(ExecutionState::WaitingForBusy); | ||||
| 					lost_arbitration_ = false; | ||||
| 				break; | ||||
|  | ||||
| 				default: | ||||
| 					assert_data_bus_ = false; | ||||
| 					set_execution_state(ExecutionState::PerformingDMA); | ||||
| 					bus_.update_observers(); | ||||
| 				break; | ||||
| 			} | ||||
| 			update_control_output(); | ||||
| 		break; | ||||
|  | ||||
| 		case 3: { | ||||
| //			LOG("[SCSI 3] Set target command: " << PADHEX(2) << int(value)); | ||||
| 			target_command_ = value; | ||||
| 			update_control_output(); | ||||
| 		} break; | ||||
|  | ||||
| 		case 4: | ||||
| //			LOG("[SCSI 4] Set select enabled: " << PADHEX(2) << int(value)); | ||||
| 		break; | ||||
|  | ||||
| 		case 5: | ||||
| //			LOG("[SCSI 5] Start DMA send: " << PADHEX(2) << int(value)); | ||||
| 			dma_operation_ = DMAOperation::Send; | ||||
| 		break; | ||||
|  | ||||
| 		case 6: | ||||
| //			LOG("[SCSI 6] Start DMA target receive: " << PADHEX(2) << int(value)); | ||||
| 			dma_operation_ = DMAOperation::TargetReceive; | ||||
| 		break; | ||||
|  | ||||
| 		case 7: | ||||
| //			LOG("[SCSI 7] Start DMA initiator receive: " << PADHEX(2) << int(value)); | ||||
| 			dma_operation_ = DMAOperation::InitiatorReceive; | ||||
| 		break; | ||||
| 	} | ||||
|  | ||||
| 	// Data is output only if the data bus is asserted. | ||||
| 	if(assert_data_bus_) { | ||||
| 		bus_output_ = (bus_output_ & ~SCSI::Line::Data) | data_bus_; | ||||
| 	} else { | ||||
| 		bus_output_ &= ~SCSI::Line::Data; | ||||
| 	} | ||||
|  | ||||
| 	// In test mode, still nothing is output. Otherwise throw out | ||||
| 	// the current value of bus_output_. | ||||
| 	if(test_mode_) { | ||||
| 		bus_.set_device_output(device_id_, SCSI::DefaultBusState); | ||||
| 	} else { | ||||
| 		bus_.set_device_output(device_id_, bus_output_); | ||||
| 	} | ||||
| } | ||||
|  | ||||
| uint8_t NCR5380::read(int address, bool dma_acknowledge) { | ||||
| 	switch(address & 7) { | ||||
| 		case 0: | ||||
| //			LOG("[SCSI 0] Get current SCSI bus state: " << PADHEX(2) << (bus_.get_state() & 0xff)); | ||||
|  | ||||
| 			if(dma_request_ && dma_operation_ == DMAOperation::InitiatorReceive) { | ||||
| 				dma_acknowledge_ = true; | ||||
| 				dma_request_ = false; | ||||
| 				update_control_output(); | ||||
| 				bus_.set_device_output(device_id_, bus_output_); | ||||
| 			} | ||||
| 		return uint8_t(bus_.get_state()); | ||||
|  | ||||
| 		case 1: | ||||
| //			LOG("[SCSI 1] Initiator command register get: " << (arbitration_in_progress_ ? 'p' : '-') <<  (lost_arbitration_ ? 'l' : '-')); | ||||
| 		return | ||||
| 			// Bits repeated as they were set. | ||||
| 			(initiator_command_ & ~0x60) | | ||||
|  | ||||
| 			// Arbitration in progress. | ||||
| 			(arbitration_in_progress_ ? 0x40 : 0x00) | | ||||
|  | ||||
| 			// Lost arbitration. | ||||
| 			(lost_arbitration_ ? 0x20 : 0x00); | ||||
|  | ||||
| 		case 2: | ||||
| //			LOG("[SCSI 2] Get mode"); | ||||
| 		return mode_; | ||||
|  | ||||
| 		case 3: | ||||
| //			LOG("[SCSI 3] Get target command"); | ||||
| 		return target_command_; | ||||
|  | ||||
| 		case 4: { | ||||
| 			const auto bus_state = bus_.get_state(); | ||||
| 			const uint8_t result = | ||||
| 				((bus_state & Line::Reset)			? 0x80 : 0x00) | | ||||
| 				((bus_state & Line::Busy)			? 0x40 : 0x00) | | ||||
| 				((bus_state & Line::Request)		? 0x20 : 0x00) | | ||||
| 				((bus_state & Line::Message)		? 0x10 : 0x00) | | ||||
| 				((bus_state & Line::Control)		? 0x08 : 0x00) | | ||||
| 				((bus_state & Line::Input)			? 0x04 : 0x00) | | ||||
| 				((bus_state & Line::SelectTarget)	? 0x02 : 0x00) | | ||||
| 				((bus_state & Line::Parity)			? 0x01 : 0x00); | ||||
| //			LOG("[SCSI 4] Get current bus state: " << PADHEX(2) << int(result)); | ||||
| 			return result; | ||||
| 		} | ||||
|  | ||||
| 		case 5: { | ||||
| 			const auto bus_state = bus_.get_state(); | ||||
| 			const bool phase_matches = | ||||
| 				(target_output() & (Line::Message | Line::Control | Line::Input)) == | ||||
| 				(bus_state & (Line::Message | Line::Control | Line::Input)); | ||||
|  | ||||
| 			const uint8_t result = | ||||
| 				/* b7 = end of DMA */ | ||||
| 				((dma_request_ && state_ == ExecutionState::PerformingDMA) ? 0x40 : 0x00)	| | ||||
| 				/* b5 = parity error */ | ||||
| 				/* b4 = IRQ active */ | ||||
| 				(phase_matches ? 0x08 : 0x00)	| | ||||
| 				/* b2 = busy error */ | ||||
| 				((bus_state & Line::Attention) ? 0x02 : 0x00) | | ||||
| 				((bus_state & Line::Acknowledge) ? 0x01 : 0x00); | ||||
| //			LOG("[SCSI 5] Get bus and status: " << PADHEX(2) << int(result)); | ||||
| 			return result; | ||||
| 		} | ||||
|  | ||||
| 		case 6: | ||||
| //			LOG("[SCSI 6] Get input data"); | ||||
| 		return 0xff; | ||||
|  | ||||
| 		case 7: | ||||
| //			LOG("[SCSI 7] Reset parity/interrupt"); | ||||
| 		return 0xff; | ||||
| 	} | ||||
| 	return 0; | ||||
| } | ||||
|  | ||||
| SCSI::BusState NCR5380::target_output() { | ||||
| 	SCSI::BusState output = SCSI::DefaultBusState; | ||||
| 	if(target_command_ & 0x08) output |= Line::Request; | ||||
| 	if(target_command_ & 0x04) output |= Line::Message; | ||||
| 	if(target_command_ & 0x02) output |= Line::Control; | ||||
| 	if(target_command_ & 0x01) output |= Line::Input; | ||||
| 	return output; | ||||
| } | ||||
|  | ||||
| void NCR5380::update_control_output() { | ||||
| 	bus_output_ &= ~(Line::Request | Line::Message | Line::Control | Line::Input | Line::Acknowledge | Line::Attention); | ||||
| 	if(mode_ & 0x40) { | ||||
| 		// This is a target; C/D, I/O, /MSG and /REQ are signalled on the bus. | ||||
| 		bus_output_ |= target_output(); | ||||
| 	} else { | ||||
| 		// This is an initiator; /ATN and /ACK are signalled on the bus. | ||||
| 		if( | ||||
| 			(initiator_command_ & 0x10) || | ||||
| 			(state_ == ExecutionState::PerformingDMA && dma_acknowledge_) | ||||
| 		) bus_output_ |= Line::Acknowledge; | ||||
| 		if(initiator_command_ & 0x02) bus_output_ |= Line::Attention; | ||||
| 	} | ||||
| } | ||||
|  | ||||
| void NCR5380::scsi_bus_did_change(SCSI::Bus *, SCSI::BusState new_state, double time_since_change) { | ||||
| 	switch(state_) { | ||||
| 		default: break; | ||||
|  | ||||
| 		/* | ||||
| 			Official documentation: | ||||
|  | ||||
| 				Arbitration is accomplished using a bus-free filter to continuously monitor BSY. | ||||
| 				If BSY remains inactive for at least 400 nsec then the SCSI bus is considered free | ||||
| 				and arbitration may begin. Arbitration will begin if the bus is free, SEL is inactive | ||||
| 				and the ARBITRATION bit (port 2, bit 0) is active. Once arbitration has begun | ||||
| 				(BSY asserted), an arbitration delay of 2.2 /Lsec must elapse before the data bus | ||||
| 				can be examined to deter- mine if arbitration has been won. This delay must be | ||||
| 				implemented in the controlling software driver. | ||||
|  | ||||
| 			Personal notes: | ||||
|  | ||||
| 				I'm discounting that "arbitratation is accomplished" opening, and assuming that what needs | ||||
| 				to happen is: | ||||
|  | ||||
| 					(i) wait for BSY to be inactive; | ||||
| 					(ii) count 400 nsec; | ||||
| 					(iii) check that BSY and SEL are inactive. | ||||
| 		*/ | ||||
|  | ||||
| 		case ExecutionState::WaitingForBusy: | ||||
| 			if(!(new_state & SCSI::Line::Busy) || time_since_change < SCSI::DeskewDelay) return; | ||||
| 			state_ = ExecutionState::WatchingBusy; | ||||
|  | ||||
| 		case ExecutionState::WatchingBusy: | ||||
| 			if(!(new_state & SCSI::Line::Busy)) { | ||||
| 				lost_arbitration_ = true; | ||||
| 				set_execution_state(ExecutionState::None); | ||||
| 			} | ||||
|  | ||||
| 			// Check for having hit 400ns (more or less) since BSY was inactive. | ||||
| 			if(time_since_change >= SCSI::BusSettleDelay) { | ||||
| //				arbitration_in_progress_ = false; | ||||
| 				if(new_state & SCSI::Line::SelectTarget) { | ||||
| 					lost_arbitration_ = true; | ||||
| 					set_execution_state(ExecutionState::None); | ||||
| 				} else { | ||||
| 					bus_output_ &= ~SCSI::Line::Busy; | ||||
| 					set_execution_state(ExecutionState::None); | ||||
| 				} | ||||
| 			} | ||||
|  | ||||
| 			/* TODO: there's a bug here, given that the dropping of Busy isn't communicated onward. */ | ||||
| 		break; | ||||
|  | ||||
| 		case ExecutionState::PerformingDMA: | ||||
| 			if(time_since_change < SCSI::DeskewDelay) return; | ||||
|  | ||||
| 			// Signal a DMA request if the request line is active, i.e. meaningful data is | ||||
| 			// on the bus, and this device hasn't yet acknowledged it. | ||||
| 			switch(new_state & (SCSI::Line::Request | SCSI::Line::Acknowledge)) { | ||||
| 				case 0: | ||||
| 					dma_request_ = false; | ||||
| 				break; | ||||
| 				case SCSI::Line::Request: | ||||
| 					dma_request_ = true; | ||||
| 				break; | ||||
| 				case SCSI::Line::Request | SCSI::Line::Acknowledge: | ||||
| 					dma_request_ = false; | ||||
| 				break; | ||||
| 				case SCSI::Line::Acknowledge: | ||||
| 					dma_acknowledge_ = false; | ||||
| 					dma_request_ = false; | ||||
| 					update_control_output(); | ||||
| 					bus_.set_device_output(device_id_, bus_output_); | ||||
| 				break; | ||||
| 			} | ||||
| 		break; | ||||
| 	} | ||||
| } | ||||
|  | ||||
| void NCR5380::set_execution_state(ExecutionState state) { | ||||
| 	state_ = state; | ||||
| 	if(state != ExecutionState::PerformingDMA) dma_operation_ = DMAOperation::Ready; | ||||
| } | ||||
							
								
								
									
										75
									
								
								Components/5380/ncr5380.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										75
									
								
								Components/5380/ncr5380.hpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,75 @@ | ||||
| // | ||||
| //  ncr5380.hpp | ||||
| //  Clock Signal | ||||
| // | ||||
| //  Created by Thomas Harte on 10/08/2019. | ||||
| //  Copyright © 2019 Thomas Harte. All rights reserved. | ||||
| // | ||||
|  | ||||
| #ifndef ncr5380_hpp | ||||
| #define ncr5380_hpp | ||||
|  | ||||
| #include <cstdint> | ||||
|  | ||||
| #include "../../Storage/MassStorage/SCSI/SCSI.hpp" | ||||
|  | ||||
|  | ||||
| namespace NCR { | ||||
| namespace NCR5380 { | ||||
|  | ||||
| /*! | ||||
| 	Models the NCR 5380, a SCSI interface chip. | ||||
| */ | ||||
| class NCR5380 final: public SCSI::Bus::Observer { | ||||
| 	public: | ||||
| 		NCR5380(SCSI::Bus &bus, int clock_rate); | ||||
|  | ||||
| 		/*! Writes @c value to @c address.  */ | ||||
| 		void write(int address, uint8_t value, bool dma_acknowledge = false); | ||||
|  | ||||
| 		/*! Reads from @c address. */ | ||||
| 		uint8_t read(int address, bool dma_acknowledge = false); | ||||
|  | ||||
| 	private: | ||||
| 		SCSI::Bus &bus_; | ||||
|  | ||||
| 		const int clock_rate_; | ||||
| 		size_t device_id_; | ||||
|  | ||||
| 		SCSI::BusState bus_output_ = SCSI::DefaultBusState; | ||||
| 		SCSI::BusState expected_phase_ = SCSI::DefaultBusState; | ||||
| 		uint8_t mode_ = 0xff; | ||||
| 		uint8_t initiator_command_ = 0xff; | ||||
| 		uint8_t data_bus_ = 0xff; | ||||
| 		uint8_t target_command_ = 0xff; | ||||
| 		bool test_mode_ = false; | ||||
| 		bool assert_data_bus_ = false; | ||||
| 		bool dma_request_ = false; | ||||
| 		bool dma_acknowledge_ = false; | ||||
|  | ||||
| 		enum class ExecutionState { | ||||
| 			None, | ||||
| 			WaitingForBusy, | ||||
| 			WatchingBusy, | ||||
| 			PerformingDMA, | ||||
| 		} state_ = ExecutionState::None; | ||||
| 		enum class DMAOperation { | ||||
| 			Ready, | ||||
| 			Send, | ||||
| 			TargetReceive, | ||||
| 			InitiatorReceive | ||||
| 		} dma_operation_ = DMAOperation::Ready; | ||||
| 		bool lost_arbitration_ = false, arbitration_in_progress_ = false; | ||||
|  | ||||
| 		void set_execution_state(ExecutionState state); | ||||
|  | ||||
| 		SCSI::BusState target_output(); | ||||
| 		void update_control_output(); | ||||
|  | ||||
| 		void scsi_bus_did_change(SCSI::Bus *, SCSI::BusState new_state, double time_since_change) final; | ||||
| }; | ||||
|  | ||||
| } | ||||
| } | ||||
|  | ||||
| #endif /* ncr5380_hpp */ | ||||
| @@ -94,10 +94,10 @@ template <class T> class MOS6522: public MOS6522Storage { | ||||
| 		MOS6522(const MOS6522 &) = delete; | ||||
|  | ||||
| 		/*! Sets a register value. */ | ||||
| 		void set_register(int address, uint8_t value); | ||||
| 		void write(int address, uint8_t value); | ||||
|  | ||||
| 		/*! Gets a register value. */ | ||||
| 		uint8_t get_register(int address); | ||||
| 		uint8_t read(int address); | ||||
|  | ||||
| 		/*! @returns the bus handler. */ | ||||
| 		T &bus_handler(); | ||||
|   | ||||
| @@ -30,7 +30,7 @@ template <typename T> void MOS6522<T>::access(int address) { | ||||
| 	} | ||||
| } | ||||
|  | ||||
| template <typename T> void MOS6522<T>::set_register(int address, uint8_t value) { | ||||
| template <typename T> void MOS6522<T>::write(int address, uint8_t value) { | ||||
| 	address &= 0xf; | ||||
| 	access(address); | ||||
| 	switch(address) { | ||||
| @@ -155,7 +155,7 @@ template <typename T> void MOS6522<T>::set_register(int address, uint8_t value) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| template <typename T> uint8_t MOS6522<T>::get_register(int address) { | ||||
| template <typename T> uint8_t MOS6522<T>::read(int address) { | ||||
| 	address &= 0xf; | ||||
| 	access(address); | ||||
| 	switch(address) { | ||||
| @@ -346,7 +346,7 @@ template <typename T> void MOS6522<T>::do_phase1() { | ||||
|  | ||||
| /*! Runs for a specified number of half cycles. */ | ||||
| template <typename T> void MOS6522<T>::run_for(const HalfCycles half_cycles) { | ||||
| 	int number_of_half_cycles = half_cycles.as_int(); | ||||
| 	auto number_of_half_cycles = half_cycles.as_integral(); | ||||
| 	if(!number_of_half_cycles) return; | ||||
|  | ||||
| 	if(is_phase2_) { | ||||
| @@ -375,7 +375,7 @@ template <typename T> void MOS6522<T>::flush() { | ||||
|  | ||||
| /*! Runs for a specified number of cycles. */ | ||||
| template <typename T> void MOS6522<T>::run_for(const Cycles cycles) { | ||||
| 	int number_of_cycles = cycles.as_int(); | ||||
| 	auto number_of_cycles = cycles.as_integral(); | ||||
| 	while(number_of_cycles--) { | ||||
| 		do_phase1(); | ||||
| 		do_phase2(); | ||||
|   | ||||
| @@ -32,7 +32,7 @@ template <class T> class MOS6532 { | ||||
| 		inline void set_ram(uint16_t address, uint8_t value)	{	ram_[address&0x7f] = value;		} | ||||
| 		inline uint8_t get_ram(uint16_t address)				{	return ram_[address & 0x7f];	} | ||||
|  | ||||
| 		inline void set_register(int address, uint8_t value) { | ||||
| 		inline void write(int address, uint8_t value) { | ||||
| 			const uint8_t decodedAddress = address & 0x07; | ||||
| 			switch(decodedAddress) { | ||||
| 				// Port output | ||||
| @@ -63,7 +63,7 @@ template <class T> class MOS6532 { | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		inline uint8_t get_register(int address) { | ||||
| 		inline uint8_t read(int address) { | ||||
| 			const uint8_t decodedAddress = address & 0x7; | ||||
| 			switch(decodedAddress) { | ||||
| 				// Port input | ||||
| @@ -107,7 +107,7 @@ template <class T> class MOS6532 { | ||||
| 		} | ||||
|  | ||||
| 		inline void run_for(const Cycles cycles) { | ||||
| 			unsigned int number_of_cycles = static_cast<unsigned int>(cycles.as_int()); | ||||
| 			unsigned int number_of_cycles = static_cast<unsigned int>(cycles.as_integral()); | ||||
|  | ||||
| 			// permit counting _to_ zero; counting _through_ zero initiates the other behaviour | ||||
| 			if(timer_.value >= number_of_cycles) { | ||||
|   | ||||
| @@ -58,7 +58,7 @@ enum class OutputMode { | ||||
| 	To run the VIC for a cycle, the caller should call @c get_address, make the requested bus access | ||||
| 	and call @c set_graphics_value with the result. | ||||
|  | ||||
| 	@c set_register and @c get_register provide register access. | ||||
| 	@c write and @c read provide register access. | ||||
| */ | ||||
| template <class BusHandler> class MOS6560 { | ||||
| 	public: | ||||
| @@ -170,7 +170,7 @@ template <class BusHandler> class MOS6560 { | ||||
| 			// keep track of the amount of time since the speaker was updated; lazy updates are applied | ||||
| 			cycles_since_speaker_update_ += cycles; | ||||
|  | ||||
| 			int number_of_cycles = cycles.as_int(); | ||||
| 			auto number_of_cycles = cycles.as_integral(); | ||||
| 			while(number_of_cycles--) { | ||||
| 				// keep an old copy of the vertical count because that test is a cycle later than the actual changes | ||||
| 				int previous_vertical_counter = vertical_counter_; | ||||
| @@ -353,7 +353,7 @@ template <class BusHandler> class MOS6560 { | ||||
| 		/*! | ||||
| 			Writes to a 6560 register. | ||||
| 		*/ | ||||
| 		void set_register(int address, uint8_t value) { | ||||
| 		void write(int address, uint8_t value) { | ||||
| 			address &= 0xf; | ||||
| 			registers_.direct_values[address] = value; | ||||
| 			switch(address) { | ||||
| @@ -417,7 +417,7 @@ template <class BusHandler> class MOS6560 { | ||||
| 		/* | ||||
| 			Reads from a 6560 register. | ||||
| 		*/ | ||||
| 		uint8_t get_register(int address) { | ||||
| 		uint8_t read(int address) { | ||||
| 			address &= 0xf; | ||||
| 			switch(address) { | ||||
| 				default: return registers_.direct_values[address]; | ||||
|   | ||||
| @@ -111,7 +111,7 @@ template <class T> class CRTC6845 { | ||||
| 		} | ||||
|  | ||||
| 		void run_for(Cycles cycles) { | ||||
| 			int cyles_remaining = cycles.as_int(); | ||||
| 			auto cyles_remaining = cycles.as_integral(); | ||||
| 			while(cyles_remaining--) { | ||||
| 				// check for end of visible characters | ||||
| 				if(character_counter_ == registers_[1]) { | ||||
|   | ||||
							
								
								
									
										228
									
								
								Components/6850/6850.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										228
									
								
								Components/6850/6850.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,228 @@ | ||||
| // | ||||
| //  6850.cpp | ||||
| //  Clock Signal | ||||
| // | ||||
| //  Created by Thomas Harte on 10/10/2019. | ||||
| //  Copyright © 2019 Thomas Harte. All rights reserved. | ||||
| // | ||||
|  | ||||
| #include "6850.hpp" | ||||
|  | ||||
| #include <cassert> | ||||
|  | ||||
| using namespace Motorola::ACIA; | ||||
|  | ||||
| const HalfCycles ACIA::SameAsTransmit; | ||||
|  | ||||
| ACIA::ACIA(HalfCycles transmit_clock_rate, HalfCycles receive_clock_rate) : | ||||
| 	transmit_clock_rate_(transmit_clock_rate), | ||||
| 	receive_clock_rate_((receive_clock_rate != SameAsTransmit) ? receive_clock_rate : transmit_clock_rate) { | ||||
| 	transmit.set_writer_clock_rate(transmit_clock_rate); | ||||
| 	request_to_send.set_writer_clock_rate(transmit_clock_rate); | ||||
| } | ||||
|  | ||||
| uint8_t ACIA::read(int address) { | ||||
| 	if(address&1) { | ||||
| 		overran_ = false; | ||||
| 		received_data_ |= NoValueMask; | ||||
| 		update_interrupt_line(); | ||||
| 		return uint8_t(received_data_); | ||||
| 	} else { | ||||
| 		return get_status(); | ||||
| 	} | ||||
| } | ||||
|  | ||||
| void ACIA::reset() { | ||||
| 	transmit.reset_writing(); | ||||
| 	transmit.write(true); | ||||
| 	request_to_send.reset_writing(); | ||||
|  | ||||
| 	bits_received_ = bits_incoming_ = 0; | ||||
| 	receive_interrupt_enabled_ = transmit_interrupt_enabled_ = false; | ||||
| 	overran_ = false; | ||||
| 	next_transmission_ = received_data_ = NoValueMask; | ||||
|  | ||||
| 	update_interrupt_line(); | ||||
| 	assert(!interrupt_line_); | ||||
| } | ||||
|  | ||||
| void ACIA::write(int address, uint8_t value) { | ||||
| 	if(address&1) { | ||||
| 		next_transmission_ = value; | ||||
| 		consider_transmission(); | ||||
| 		update_interrupt_line(); | ||||
| 	} else { | ||||
| 		if((value&3) == 3) { | ||||
| 			reset(); | ||||
| 		} else { | ||||
| 			switch(value & 3) { | ||||
| 				default: | ||||
| 				case 0: divider_ = 1;	break; | ||||
| 				case 1: divider_ = 16;	break; | ||||
| 				case 2: divider_ = 64;	break; | ||||
| 			} | ||||
| 			switch((value >> 2) & 7) { | ||||
| 				default: | ||||
| 				case 0:	data_bits_ = 7; stop_bits_ = 2; parity_ = Parity::Even;	break; | ||||
| 				case 1:	data_bits_ = 7; stop_bits_ = 2; parity_ = Parity::Odd;	break; | ||||
| 				case 2:	data_bits_ = 7; stop_bits_ = 1; parity_ = Parity::Even;	break; | ||||
| 				case 3:	data_bits_ = 7; stop_bits_ = 1; parity_ = Parity::Odd;	break; | ||||
| 				case 4:	data_bits_ = 8; stop_bits_ = 2; parity_ = Parity::None;	break; | ||||
| 				case 5:	data_bits_ = 8; stop_bits_ = 1; parity_ = Parity::None;	break; | ||||
| 				case 6:	data_bits_ = 8; stop_bits_ = 1; parity_ = Parity::Even;	break; | ||||
| 				case 7:	data_bits_ = 8; stop_bits_ = 1; parity_ = Parity::Odd;	break; | ||||
| 			} | ||||
| 			switch((value >> 5) & 3) { | ||||
| 				case 0:	request_to_send.write(false); transmit_interrupt_enabled_ = false;	break; | ||||
| 				case 1:	request_to_send.write(false); transmit_interrupt_enabled_ = true;	break; | ||||
| 				case 2:	request_to_send.write(true); transmit_interrupt_enabled_ = false;	break; | ||||
| 				case 3: | ||||
| 					request_to_send.write(false); | ||||
| 					transmit_interrupt_enabled_ = false; | ||||
| 					transmit.reset_writing(); | ||||
| 					transmit.write(false); | ||||
| 				break; | ||||
| 			} | ||||
| 			receive.set_read_delegate(this, Storage::Time(divider_ * 2, int(receive_clock_rate_.as_integral()))); | ||||
| 			receive_interrupt_enabled_ = value & 0x80; | ||||
|  | ||||
| 			update_interrupt_line(); | ||||
| 		} | ||||
| 	} | ||||
| 	update_clocking_observer(); | ||||
| } | ||||
|  | ||||
| void ACIA::consider_transmission() { | ||||
| 	if(next_transmission_ != NoValueMask && !transmit.write_data_time_remaining()) { | ||||
| 		// Establish start bit and [7 or 8] data bits. | ||||
| 		if(data_bits_ == 7) next_transmission_ &= 0x7f; | ||||
| 		int transmission = next_transmission_ << 1; | ||||
|  | ||||
| 		// Add a parity bit, if any. | ||||
| 		int mask = 0x2 << data_bits_; | ||||
| 		if(parity_ != Parity::None) { | ||||
| 			transmission |= parity(uint8_t(next_transmission_)) ? mask : 0; | ||||
| 			mask <<= 1; | ||||
| 		} | ||||
|  | ||||
| 		// Add stop bits. | ||||
| 		for(int c = 0; c < stop_bits_; ++c) { | ||||
| 			transmission |= mask; | ||||
| 			mask <<= 1; | ||||
| 		} | ||||
|  | ||||
| 		// Output all that. | ||||
| 		const int total_bits = expected_bits(); | ||||
| 		transmit.write(divider_ * 2, total_bits, transmission); | ||||
|  | ||||
| 		// Mark the transmit register as empty again. | ||||
| 		next_transmission_ = NoValueMask; | ||||
| 	} | ||||
| } | ||||
|  | ||||
| ClockingHint::Preference ACIA::preferred_clocking() { | ||||
| 	// Real-time clocking is required if a transmission is ongoing; this is a courtesy for whomever | ||||
| 	// is on the receiving end. | ||||
| 	if(transmit.transmission_data_time_remaining() > 0) return ClockingHint::Preference::RealTime; | ||||
|  | ||||
| 	// If a bit reception is ongoing that might lead to an interrupt, ask for real-time clocking | ||||
| 	// because it's unclear when the interrupt might come. | ||||
| 	if(bits_incoming_ && receive_interrupt_enabled_) return ClockingHint::Preference::RealTime; | ||||
|  | ||||
| 	// No clocking required then. | ||||
| 	return ClockingHint::Preference::None; | ||||
| } | ||||
|  | ||||
| bool ACIA::get_interrupt_line() const { | ||||
| 	return interrupt_line_; | ||||
| } | ||||
|  | ||||
| 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); | ||||
| } | ||||
|  | ||||
| bool ACIA::serial_line_did_produce_bit(Serial::Line *line, int bit) { | ||||
| 	// Shift this bit into the 11-bit input register; this is big enough to hold | ||||
| 	// the largest transmission symbol. | ||||
| 	++bits_received_; | ||||
| 	bits_incoming_ = (bits_incoming_ >> 1) | (bit << 10); | ||||
|  | ||||
| 	// If that's the now-expected number of bits, update. | ||||
| 	const int bit_target = expected_bits(); | ||||
| 	if(bits_received_ >= bit_target) { | ||||
| 		bits_received_ = 0; | ||||
| 		overran_ |= get_status() & 1; | ||||
| 		received_data_ = uint8_t(bits_incoming_ >> (12 - bit_target)); | ||||
| 		update_interrupt_line(); | ||||
| 		update_clocking_observer(); | ||||
| 		return false; | ||||
| 	} | ||||
|  | ||||
| 	// TODO: overrun, and parity. | ||||
|  | ||||
| 	// Keep receiving, and consider a potential clocking change. | ||||
| 	if(bits_received_ == 1) update_clocking_observer(); | ||||
| 	return true; | ||||
| } | ||||
|  | ||||
| void ACIA::set_interrupt_delegate(InterruptDelegate *delegate) { | ||||
| 	interrupt_delegate_ = delegate; | ||||
| } | ||||
|  | ||||
| void ACIA::update_interrupt_line() { | ||||
| 	const bool old_line = interrupt_line_; | ||||
|  | ||||
| 	/* | ||||
| 		"Bit 7 of the control register is the rie bit. When the rie bit is high, the rdrf, ndcd, | ||||
| 		and ovr bits will assert the nirq output. When the rie bit is low, nirq generation is disabled." | ||||
|  | ||||
| 		rie = read interrupt enable | ||||
| 		rdrf = receive data register full (status word bit 0) | ||||
| 		ndcd = data carrier detect (status word bit 2) | ||||
| 		over = receiver overrun (status word bit 5) | ||||
|  | ||||
| 		"Bit 1 of the status register is the tdre bit. When high, the tdre bit indicates that data has been | ||||
| 		transferred from the transmitter data register to the output shift register. At this point, the a6850 | ||||
| 		is ready to accept a new transmit data byte. However, if the ncts signal is high, the tdre bit remains | ||||
| 		low regardless of the status of the transmitter data register. Also, if transmit interrupt is enabled, | ||||
| 		the nirq output is asserted." | ||||
|  | ||||
| 		tdre = transmitter data register empty | ||||
| 		ncts = clear to send | ||||
| 	*/ | ||||
| 	const auto status = get_status(); | ||||
| 	interrupt_line_ = | ||||
| 		(receive_interrupt_enabled_ && (status & 0x25)) || | ||||
| 		(transmit_interrupt_enabled_ && (status & 0x02)); | ||||
|  | ||||
| 	if(interrupt_delegate_ && old_line != interrupt_line_) { | ||||
| 		interrupt_delegate_->acia6850_did_change_interrupt_status(this); | ||||
| 	} | ||||
| } | ||||
|  | ||||
| uint8_t ACIA::get_status() { | ||||
| 	return | ||||
| 		((received_data_ & NoValueMask) ? 0x00 : 0x01) | | ||||
| 		((next_transmission_ == NoValueMask) ? 0x02 : 0x00) | | ||||
| //		(data_carrier_detect.read() ? 0x04 : 0x00) | | ||||
| //		(clear_to_send.read() ? 0x08 : 0x00) | | ||||
| 		(overran_ ? 0x20 : 0x00) | | ||||
| 		(interrupt_line_ ? 0x80 : 0x00) | ||||
| 	; | ||||
|  | ||||
| 	// b0: receive data full. | ||||
| 	// b1: transmit data empty. | ||||
| 	// b2: DCD. | ||||
| 	// b3: CTS. | ||||
| 	// b4: framing error (i.e. no first stop bit where expected). | ||||
| 	// b5: receiver overran. | ||||
| 	// b6: parity error. | ||||
| 	// b7: IRQ state. | ||||
| } | ||||
							
								
								
									
										132
									
								
								Components/6850/6850.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										132
									
								
								Components/6850/6850.hpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,132 @@ | ||||
| // | ||||
| //  6850.hpp | ||||
| //  Clock Signal | ||||
| // | ||||
| //  Created by Thomas Harte on 10/10/2019. | ||||
| //  Copyright © 2019 Thomas Harte. All rights reserved. | ||||
| // | ||||
|  | ||||
| #ifndef Motorola_ACIA_6850_hpp | ||||
| #define Motorola_ACIA_6850_hpp | ||||
|  | ||||
| #include <cstdint> | ||||
| #include "../../ClockReceiver/ClockReceiver.hpp" | ||||
| #include "../../ClockReceiver/ForceInline.hpp" | ||||
| #include "../../ClockReceiver/ClockingHintSource.hpp" | ||||
| #include "../Serial/Line.hpp" | ||||
|  | ||||
| namespace Motorola { | ||||
| namespace ACIA { | ||||
|  | ||||
| class ACIA: public ClockingHint::Source, private Serial::Line::ReadDelegate { | ||||
| 	public: | ||||
| 		static constexpr const HalfCycles SameAsTransmit = HalfCycles(0); | ||||
|  | ||||
| 		/*! | ||||
| 			Constructs a new instance of ACIA which will receive a transmission clock at a rate of | ||||
| 			@c transmit_clock_rate, and a receive clock at a rate of @c receive_clock_rate. | ||||
| 		*/ | ||||
| 		ACIA(HalfCycles transmit_clock_rate, HalfCycles receive_clock_rate = SameAsTransmit); | ||||
|  | ||||
| 		/*! | ||||
| 			Reads from the ACIA. | ||||
|  | ||||
| 			Bit 0 of the address is used as the ACIA's register select line — | ||||
| 			so even addresses select control/status registers, odd addresses | ||||
| 			select transmit/receive data registers. | ||||
| 		*/ | ||||
| 		uint8_t read(int address); | ||||
|  | ||||
| 		/*! | ||||
| 			Writes to the ACIA. | ||||
|  | ||||
| 			Bit 0 of the address is used as the ACIA's register select line — | ||||
| 			so even addresses select control/status registers, odd addresses | ||||
| 			select transmit/receive data registers. | ||||
| 		*/ | ||||
| 		void write(int address, uint8_t value); | ||||
|  | ||||
| 		/*! | ||||
| 			Advances @c transmission_cycles in time, which should be | ||||
| 			counted relative to the @c transmit_clock_rate. | ||||
| 		*/ | ||||
| 		forceinline void run_for(HalfCycles transmission_cycles) { | ||||
| 			if(transmit.transmission_data_time_remaining() > HalfCycles(0)) { | ||||
| 				const auto write_data_time_remaining = transmit.write_data_time_remaining(); | ||||
|  | ||||
| 				// There's at most one further byte available to enqueue, so a single 'if' | ||||
| 				// rather than a 'while' is correct here. It's the responsibilit of the caller | ||||
| 				// to ensure run_for lengths are appropriate for longer sequences. | ||||
| 				if(transmission_cycles >= write_data_time_remaining) { | ||||
| 					if(next_transmission_ != NoValueMask) { | ||||
| 						transmit.advance_writer(write_data_time_remaining); | ||||
| 						consider_transmission(); | ||||
| 						transmit.advance_writer(transmission_cycles - write_data_time_remaining); | ||||
| 					} else { | ||||
| 						transmit.advance_writer(transmission_cycles); | ||||
| 						update_clocking_observer(); | ||||
| 						update_interrupt_line(); | ||||
| 					} | ||||
| 				} else { | ||||
| 					transmit.advance_writer(transmission_cycles); | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		bool get_interrupt_line() const; | ||||
| 		void reset(); | ||||
|  | ||||
| 		// Input lines. | ||||
| 		Serial::Line receive; | ||||
| 		Serial::Line clear_to_send; | ||||
| 		Serial::Line data_carrier_detect; | ||||
|  | ||||
| 		// Output lines. | ||||
| 		Serial::Line transmit; | ||||
| 		Serial::Line request_to_send; | ||||
|  | ||||
| 		// ClockingHint::Source. | ||||
| 		ClockingHint::Preference preferred_clocking() final; | ||||
|  | ||||
| 		struct InterruptDelegate { | ||||
| 			virtual void acia6850_did_change_interrupt_status(ACIA *acia) = 0; | ||||
| 		}; | ||||
| 		void set_interrupt_delegate(InterruptDelegate *delegate); | ||||
|  | ||||
| 	private: | ||||
| 		int divider_ = 1; | ||||
| 		enum class Parity { | ||||
| 			Even, Odd, None | ||||
| 		} parity_ = Parity::None; | ||||
| 		int data_bits_ = 7, stop_bits_ = 2; | ||||
|  | ||||
| 		static constexpr int NoValueMask = 0x100; | ||||
| 		int next_transmission_ = NoValueMask; | ||||
| 		int received_data_ = NoValueMask; | ||||
|  | ||||
| 		int bits_received_ = 0; | ||||
| 		int bits_incoming_ = 0; | ||||
| 		bool overran_ = false; | ||||
|  | ||||
| 		void consider_transmission(); | ||||
| 		int expected_bits(); | ||||
| 		uint8_t parity(uint8_t value); | ||||
|  | ||||
| 		bool receive_interrupt_enabled_ = false; | ||||
| 		bool transmit_interrupt_enabled_ = false; | ||||
|  | ||||
| 		HalfCycles transmit_clock_rate_; | ||||
| 		HalfCycles receive_clock_rate_; | ||||
|  | ||||
| 		bool serial_line_did_produce_bit(Serial::Line *line, int bit) final; | ||||
|  | ||||
| 		bool interrupt_line_ = false; | ||||
| 		void update_interrupt_line(); | ||||
| 		InterruptDelegate *interrupt_delegate_ = nullptr; | ||||
| 		uint8_t get_status(); | ||||
| }; | ||||
|  | ||||
| } | ||||
| } | ||||
|  | ||||
| #endif /* Motorola_ACIA_6850_hpp */ | ||||
							
								
								
									
										374
									
								
								Components/68901/MFP68901.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										374
									
								
								Components/68901/MFP68901.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,374 @@ | ||||
| // | ||||
| //  MFP68901.cpp | ||||
| //  Clock Signal | ||||
| // | ||||
| //  Created by Thomas Harte on 06/10/2019. | ||||
| //  Copyright © 2019 Thomas Harte. All rights reserved. | ||||
| // | ||||
|  | ||||
| #include "MFP68901.hpp" | ||||
|  | ||||
| #include <algorithm> | ||||
| #include <cstring> | ||||
|  | ||||
| #ifndef NDEBUG | ||||
| #define NDEBUG | ||||
| #endif | ||||
|  | ||||
| #define LOG_PREFIX "[MFP] " | ||||
| #include "../../Outputs/Log.hpp" | ||||
|  | ||||
| using namespace Motorola::MFP68901; | ||||
|  | ||||
| ClockingHint::Preference MFP68901::preferred_clocking() { | ||||
| 	// Rule applied: if any timer is actively running and permitted to produce an | ||||
| 	// interrupt, request real-time running. | ||||
| 	return | ||||
| 		(timers_[0].mode >= TimerMode::Delay && interrupt_enable_&Interrupt::TimerA) || | ||||
| 		(timers_[1].mode >= TimerMode::Delay && interrupt_enable_&Interrupt::TimerB) || | ||||
| 		(timers_[2].mode >= TimerMode::Delay && interrupt_enable_&Interrupt::TimerC) || | ||||
| 		(timers_[3].mode >= TimerMode::Delay && interrupt_enable_&Interrupt::TimerD) | ||||
| 			? ClockingHint::Preference::RealTime : ClockingHint::Preference::JustInTime; | ||||
| } | ||||
|  | ||||
| uint8_t MFP68901::read(int address) { | ||||
| 	address &= 0x1f; | ||||
|  | ||||
| 	// Interrupt block: various bits of state can be read, all passively. | ||||
| 	if(address >= 0x03 && address <= 0x0b) { | ||||
| 		const int shift = (address&1) << 3; | ||||
| 		switch(address) { | ||||
| 			case 0x03:	case 0x04:	return uint8_t(interrupt_enable_ >> shift); | ||||
| 			case 0x05:	case 0x06:	return uint8_t(interrupt_pending_ >> shift); | ||||
| 			case 0x07:	case 0x08:	return uint8_t(interrupt_in_service_ >> shift); | ||||
| 			case 0x09:	case 0x0a:	return uint8_t(interrupt_mask_ >> shift); | ||||
| 			case 0x0b:	return interrupt_vector_; | ||||
|  | ||||
| 			default: break; | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	switch(address) { | ||||
| 		// GPIP block: input, and configured active edge and direction values. | ||||
| 		case 0x00:	return (gpip_input_ & ~gpip_direction_) | (gpip_output_ & gpip_direction_); | ||||
| 		case 0x01:	return gpip_active_edge_; | ||||
| 		case 0x02:	return gpip_direction_; | ||||
|  | ||||
| 		/* Interrupt block dealt with above. */ | ||||
| 		default: break; | ||||
|  | ||||
| 		// Timer block: read back A, B and C/D control, and read current timer values. | ||||
| 		case 0x0c:	case 0x0d:	return timer_ab_control_[address - 0xc]; | ||||
| 		case 0x0e:				return timer_cd_control_; | ||||
| 		case 0x0f:	case 0x10: | ||||
| 		case 0x11:	case 0x12:	return get_timer_data(address - 0xf); | ||||
|  | ||||
| 		// USART block: TODO. | ||||
| 		case 0x13:		LOG("Read: sync character generator");	break; | ||||
| 		case 0x14:		LOG("Read: USART control");				break; | ||||
| 		case 0x15:		LOG("Read: receiver status");			break; | ||||
| 		case 0x16:		LOG("Read: transmitter status");		break; | ||||
| 		case 0x17:		LOG("Read: USART data");				break; | ||||
| 	} | ||||
| 	return 0x00; | ||||
| } | ||||
|  | ||||
| void MFP68901::write(int address, uint8_t value) { | ||||
| 	address &= 0x1f; | ||||
|  | ||||
| 	// Interrupt block: enabled and masked interrupts can be set; pending and in-service interrupts can be masked. | ||||
| 	if(address >= 0x03 && address <= 0x0b) { | ||||
| 		const int shift = (address&1) << 3; | ||||
| 		const int preserve = 0xff00 >> shift; | ||||
| 		const int word_value = value << shift; | ||||
|  | ||||
| 		switch(address) { | ||||
| 			default: break; | ||||
| 			case 0x03: case 0x04:	// Adjust enabled interrupts; disabled ones also cease to be pending. | ||||
| 				interrupt_enable_ = (interrupt_enable_ & preserve) | word_value; | ||||
| 				interrupt_pending_ &= interrupt_enable_; | ||||
| 			break; | ||||
| 			case 0x05: case 0x06:	// Resolve pending interrupts. | ||||
| 				interrupt_pending_ &= (preserve | word_value); | ||||
| 			break; | ||||
| 			case 0x07: case 0x08:	// Resolve in-service interrupts. | ||||
| 				interrupt_in_service_ &= (preserve | word_value); | ||||
| 			break; | ||||
| 			case 0x09: case 0x0a:	// Adjust interrupt mask. | ||||
| 				interrupt_mask_ = (interrupt_mask_ & preserve) | word_value; | ||||
| 			break; | ||||
| 			case 0x0b:				// Set the interrupt vector, possibly changing end-of-interrupt mode. | ||||
| 				interrupt_vector_ = value; | ||||
|  | ||||
| 				// If automatic end-of-interrupt mode has now been enabled, clear | ||||
| 				// the in-process mask and re-evaluate. | ||||
| 				if(interrupt_vector_ & 0x08) return; | ||||
| 				interrupt_in_service_ = 0; | ||||
| 			break; | ||||
| 		} | ||||
|  | ||||
| 		// Whatever just happened may have affected the state of the interrupt line. | ||||
| 		update_interrupts(); | ||||
| 		update_clocking_observer(); | ||||
| 		return; | ||||
| 	} | ||||
|  | ||||
| 	constexpr int timer_prescales[] = { | ||||
| 		1, 4, 10, 16, 50, 64, 100, 200 | ||||
| 	}; | ||||
|  | ||||
| 	switch(address) { | ||||
| 		// GPIP block: output and configuration of active edge and direction values. | ||||
| 		case 0x00: | ||||
| 			gpip_output_ = value; | ||||
| 		break; | ||||
| 		case 0x01: | ||||
| 			gpip_active_edge_ = value; | ||||
| 			reevaluate_gpip_interrupts(); | ||||
| 		break; | ||||
| 		case 0x02: | ||||
| 			gpip_direction_ = value; | ||||
| 			reevaluate_gpip_interrupts(); | ||||
| 		break; | ||||
|  | ||||
| 		/* Interrupt block dealt with above. */ | ||||
| 		default: break; | ||||
|  | ||||
| 		// Timer block. | ||||
| 		case 0x0c: | ||||
| 		case 0x0d: { | ||||
| 			const auto timer = address - 0xc; | ||||
| 			const bool reset = value & 0x10; | ||||
| 			timer_ab_control_[timer] = value; | ||||
| 			switch(value & 0xf) { | ||||
| 				case 0x0:	set_timer_mode(timer, TimerMode::Stopped, 1, reset);		break; | ||||
| 				case 0x1:	set_timer_mode(timer, TimerMode::Delay, 4, reset);			break; | ||||
| 				case 0x2:	set_timer_mode(timer, TimerMode::Delay, 10, reset);			break; | ||||
| 				case 0x3:	set_timer_mode(timer, TimerMode::Delay, 16, reset);			break; | ||||
| 				case 0x4:	set_timer_mode(timer, TimerMode::Delay, 50, reset);			break; | ||||
| 				case 0x5:	set_timer_mode(timer, TimerMode::Delay, 64, reset);			break; | ||||
| 				case 0x6:	set_timer_mode(timer, TimerMode::Delay, 100, reset);		break; | ||||
| 				case 0x7:	set_timer_mode(timer, TimerMode::Delay, 200, reset);		break; | ||||
| 				case 0x8:	set_timer_mode(timer, TimerMode::EventCount, 1, reset);		break; | ||||
| 				case 0x9:	set_timer_mode(timer, TimerMode::PulseWidth, 4, reset);		break; | ||||
| 				case 0xa:	set_timer_mode(timer, TimerMode::PulseWidth, 10, reset);	break; | ||||
| 				case 0xb:	set_timer_mode(timer, TimerMode::PulseWidth, 16, reset);	break; | ||||
| 				case 0xc:	set_timer_mode(timer, TimerMode::PulseWidth, 50, reset);	break; | ||||
| 				case 0xd:	set_timer_mode(timer, TimerMode::PulseWidth, 64, reset);	break; | ||||
| 				case 0xe:	set_timer_mode(timer, TimerMode::PulseWidth, 100, reset);	break; | ||||
| 				case 0xf:	set_timer_mode(timer, TimerMode::PulseWidth, 200, reset);	break; | ||||
| 			} | ||||
| 		} break; | ||||
| 		case 0x0e: | ||||
| 			timer_cd_control_ = value; | ||||
| 			set_timer_mode(3, (value & 7) ? TimerMode::Delay : TimerMode::Stopped, timer_prescales[value & 7], false); | ||||
| 			set_timer_mode(2, ((value >> 4) & 7) ? TimerMode::Delay : TimerMode::Stopped, timer_prescales[(value >> 4) & 7], false); | ||||
| 		break; | ||||
| 		case 0x0f:	case 0x10:	case 0x11:	case 0x12: | ||||
| 			set_timer_data(address - 0xf, value); | ||||
| 		break; | ||||
|  | ||||
| 		// USART block: TODO. | ||||
| 		case 0x13:		LOG("Write: sync character generator");	break; | ||||
| 		case 0x14:		LOG("Write: USART control");			break; | ||||
| 		case 0x15:		LOG("Write: receiver status");			break; | ||||
| 		case 0x16:		LOG("Write: transmitter status");		break; | ||||
| 		case 0x17:		LOG("Write: USART data");				break; | ||||
| 	} | ||||
|  | ||||
| 	update_clocking_observer(); | ||||
| } | ||||
|  | ||||
| void MFP68901::run_for(HalfCycles time) { | ||||
| 	cycles_left_ += time; | ||||
|  | ||||
| 	const int cycles = int(cycles_left_.flush<Cycles>().as_integral()); | ||||
| 	if(!cycles) return; | ||||
|  | ||||
| 	for(int c = 0; c < 4; ++c) { | ||||
| 		if(timers_[c].mode >= TimerMode::Delay) { | ||||
| 			// This code applies the timer prescaling only. prescale_count is used to count | ||||
| 			// upwards rather than downwards for simplicity, but on the real hardware it's | ||||
| 			// pretty safe to assume it actually counted downwards. So the clamp to 0 is | ||||
| 			// because gymnastics may need to occur when the prescale value is altered, e.g. | ||||
| 			// if a prescale of 256 is set and the prescale_count is currently 2 then the | ||||
| 			// counter should roll over in 254 cycles. If the user at that point changes the | ||||
| 			// prescale_count to 1 then the counter will need to be altered to -253 and | ||||
| 			// allowed to keep counting up until it crosses both 0 and 1. | ||||
| 			const int dividend = timers_[c].prescale_count + cycles; | ||||
| 			const int decrements = std::max(dividend / timers_[c].prescale, 0); | ||||
| 			if(decrements) { | ||||
| 				decrement_timer(c, decrements); | ||||
| 				timers_[c].prescale_count = dividend % timers_[c].prescale; | ||||
| 			} else { | ||||
| 				timers_[c].prescale_count += cycles; | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| HalfCycles MFP68901::get_next_sequence_point() { | ||||
| 	return HalfCycles(-1); | ||||
| } | ||||
|  | ||||
| // MARK: - Timers | ||||
|  | ||||
| void MFP68901::set_timer_mode(int timer, TimerMode mode, int prescale, bool reset_timer) { | ||||
| 	LOG("Timer " << timer << " mode set: " << int(mode) << "; prescale: " << prescale); | ||||
| 	timers_[timer].mode = mode; | ||||
| 	if(reset_timer) { | ||||
| 		timers_[timer].prescale_count = 0; | ||||
| 		timers_[timer].value = timers_[timer].reload_value; | ||||
| 	} else { | ||||
| 		// This hoop is because the prescale_count here goes upward but I'm assuming it goes downward in | ||||
| 		// real hardware. Therefore this deals with the "switched to a lower prescaling" case whereby the | ||||
| 		// old cycle should be allowed naturally to expire. | ||||
| 		timers_[timer].prescale_count = prescale - (timers_[timer].prescale - timers_[timer].prescale_count); | ||||
| 	} | ||||
|  | ||||
| 	timers_[timer].prescale = prescale; | ||||
| } | ||||
|  | ||||
| void MFP68901::set_timer_data(int timer, uint8_t value) { | ||||
| 	if(timers_[timer].mode == TimerMode::Stopped) { | ||||
| 		timers_[timer].value = value; | ||||
| 	} | ||||
| 	timers_[timer].reload_value = value; | ||||
| } | ||||
|  | ||||
| uint8_t MFP68901::get_timer_data(int timer) { | ||||
| 	return timers_[timer].value; | ||||
| } | ||||
|  | ||||
| void MFP68901::set_timer_event_input(int channel, bool value) { | ||||
| 	if(timers_[channel].event_input == value) return; | ||||
|  | ||||
| 	timers_[channel].event_input = value; | ||||
| 	if(timers_[channel].mode == TimerMode::EventCount && (value == !!(gpip_active_edge_ & (0x10 >> channel)))) { | ||||
| 		// "The active state of the signal on TAI or TBI is dependent upon the associated | ||||
| 		// Interrupt Channel’s edge bit (GPIP 4 for TAI and GPIP 3 for TBI [...] ). | ||||
| 		// If the edge bit associated with the TAI or TBI input is a one, it will be active high. | ||||
| 		decrement_timer(channel, 1); | ||||
| 	} | ||||
|  | ||||
| 	// TODO: | ||||
| 	// | ||||
| 	// Altering the edge bit while the timer is in the event count mode can produce a count pulse. | ||||
| 	// The interrupt channel associated with the input (I3 for I4 for TAI) is allowed to function normally. | ||||
| 	// To count transitions reliably, the input must remain in each state (1/O) for a length of time equal | ||||
| 	// to four periods of the timer clock. | ||||
| 	// | ||||
| 	// (the final bit probably explains 13 cycles of the DE to interrupt latency; not sure about the other ~15) | ||||
| } | ||||
|  | ||||
| void MFP68901::decrement_timer(int timer, int amount) { | ||||
| 	while(amount--) { | ||||
| 		--timers_[timer].value; | ||||
| 		if(timers_[timer].value < 1) { | ||||
| 			switch(timer) { | ||||
| 				case 0: begin_interrupts(Interrupt::TimerA);	break; | ||||
| 				case 1: begin_interrupts(Interrupt::TimerB);	break; | ||||
| 				case 2: begin_interrupts(Interrupt::TimerC);	break; | ||||
| 				case 3: begin_interrupts(Interrupt::TimerD);	break; | ||||
| 			} | ||||
|  | ||||
| 			// Re: reloading when in event counting mode; I found the data sheet thoroughly unclear on | ||||
| 			// this, but it appears empirically to be correct. See e.g. Pompey Pirates menu 27. | ||||
| 			if(timers_[timer].mode == TimerMode::Delay || timers_[timer].mode == TimerMode::EventCount) { | ||||
| 				timers_[timer].value += timers_[timer].reload_value;	// TODO: properly. | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // MARK: - GPIP | ||||
| void MFP68901::set_port_input(uint8_t input) { | ||||
| 	gpip_input_ = input; | ||||
| 	reevaluate_gpip_interrupts(); | ||||
| } | ||||
|  | ||||
| uint8_t MFP68901::get_port_output() { | ||||
| 	return 0xff;	// TODO. | ||||
| } | ||||
|  | ||||
| void MFP68901::reevaluate_gpip_interrupts() { | ||||
| 	const uint8_t gpip_state = (gpip_input_ & ~gpip_direction_) ^ gpip_active_edge_; | ||||
|  | ||||
| 	// An interrupt is detected on any falling edge. | ||||
| 	const uint8_t new_interrupt_mask = (gpip_state ^ gpip_interrupt_state_) & gpip_interrupt_state_; | ||||
| 	if(new_interrupt_mask) { | ||||
| 		begin_interrupts( | ||||
| 			(new_interrupt_mask & 0x0f) | | ||||
| 			((new_interrupt_mask & 0x30) << 2) | | ||||
| 			((new_interrupt_mask & 0xc0) << 8) | ||||
| 		); | ||||
| 	} | ||||
| 	gpip_interrupt_state_ = gpip_state; | ||||
| } | ||||
|  | ||||
| // MARK: - Interrupts | ||||
|  | ||||
| void MFP68901::begin_interrupts(int interrupt) { | ||||
| 	interrupt_pending_ |= interrupt & interrupt_enable_; | ||||
| 	update_interrupts(); | ||||
| } | ||||
|  | ||||
| void MFP68901::end_interrupts(int interrupt) { | ||||
| 	interrupt_pending_ &= ~interrupt; | ||||
| 	update_interrupts(); | ||||
| } | ||||
|  | ||||
| void MFP68901::update_interrupts() { | ||||
| 	const auto old_interrupt_line = interrupt_line_; | ||||
| 	const auto firing_interrupts = interrupt_pending_ & interrupt_mask_; | ||||
|  | ||||
| 	if(!firing_interrupts) { | ||||
| 		interrupt_line_ = false; | ||||
| 	} else { | ||||
| 		if(interrupt_vector_ & 0x8) { | ||||
| 			// Software interrupt mode: permit only if neither this interrupt | ||||
| 			// nor a higher interrupt is currently in service. | ||||
| 			const int highest_bit = msb16(firing_interrupts); | ||||
| 			interrupt_line_ = !(interrupt_in_service_ & ~(highest_bit + highest_bit - 1)); | ||||
| 		} else { | ||||
| 			// Auto-interrupt mode; just signal. | ||||
| 			interrupt_line_ = true; | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	// Update the delegate if necessary. | ||||
| 	if(interrupt_delegate_ && interrupt_line_ != old_interrupt_line) { | ||||
| 		interrupt_delegate_->mfp68901_did_change_interrupt_status(this); | ||||
| 	} | ||||
| } | ||||
|  | ||||
| bool MFP68901::get_interrupt_line() { | ||||
| 	return interrupt_line_; | ||||
| } | ||||
|  | ||||
| int MFP68901::acknowledge_interrupt() { | ||||
| 	if(!(interrupt_pending_ & interrupt_mask_)) { | ||||
| 		return NoAcknowledgement; | ||||
| 	} | ||||
|  | ||||
| 	const int mask = msb16(interrupt_pending_ & interrupt_mask_); | ||||
|  | ||||
| 	// Clear the pending bit regardless. | ||||
| 	interrupt_pending_ &= ~mask; | ||||
|  | ||||
| 	// If this is software interrupt mode, set the in-service bit. | ||||
| 	if(interrupt_vector_ & 0x8) { | ||||
| 		interrupt_in_service_ |= mask; | ||||
| 	} | ||||
|  | ||||
| 	update_interrupts(); | ||||
|  | ||||
| 	int selected = 0; | ||||
| 	while((1 << selected) != mask) ++selected; | ||||
| //	LOG("Interrupt acknowledged: " << selected); | ||||
| 	return (interrupt_vector_ & 0xf0) | uint8_t(selected); | ||||
| } | ||||
|  | ||||
| void MFP68901::set_interrupt_delegate(InterruptDelegate *delegate) { | ||||
| 	interrupt_delegate_ = delegate; | ||||
| } | ||||
							
								
								
									
										187
									
								
								Components/68901/MFP68901.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										187
									
								
								Components/68901/MFP68901.hpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,187 @@ | ||||
| // | ||||
| //  MFP68901.hpp | ||||
| //  Clock Signal | ||||
| // | ||||
| //  Created by Thomas Harte on 06/10/2019. | ||||
| //  Copyright © 2019 Thomas Harte. All rights reserved. | ||||
| // | ||||
|  | ||||
| #ifndef MFP68901_hpp | ||||
| #define MFP68901_hpp | ||||
|  | ||||
| #include <cstdint> | ||||
| #include "../../ClockReceiver/ClockReceiver.hpp" | ||||
| #include "../../ClockReceiver/ClockingHintSource.hpp" | ||||
|  | ||||
| namespace Motorola { | ||||
| namespace MFP68901 { | ||||
|  | ||||
| class PortHandler { | ||||
| 	public: | ||||
| 		// TODO: announce changes in output. | ||||
| }; | ||||
|  | ||||
| /*! | ||||
| 	Models the Motorola 68901 Multi-Function Peripheral ('MFP'). | ||||
| */ | ||||
| class MFP68901: public ClockingHint::Source { | ||||
| 	public: | ||||
| 		/// @returns the result of a read from @c address. | ||||
| 		uint8_t read(int address); | ||||
|  | ||||
| 		/// Performs a write of @c value to @c address. | ||||
| 		void write(int address, uint8_t value); | ||||
|  | ||||
| 		/// Advances the MFP by the supplied number of HalfCycles. | ||||
| 		void run_for(HalfCycles); | ||||
|  | ||||
| 		/// @returns the number of cycles until the next possible sequence point — the next time | ||||
| 		/// at which the interrupt line _might_ change. This object conforms to ClockingHint::Source | ||||
| 		/// so that mechanism can also be used to reduce the quantity of calls into this class. | ||||
| 		/// | ||||
| 		/// @discussion TODO, alas. | ||||
| 		HalfCycles get_next_sequence_point(); | ||||
|  | ||||
| 		/// Sets the current level of either of the timer event inputs — TAI and TBI in datasheet terms. | ||||
| 		void set_timer_event_input(int channel, bool value); | ||||
|  | ||||
| 		/// Sets a port handler, a receiver that will be notified upon any change in GPIP output. | ||||
| 		/// | ||||
| 		/// @discussion TODO. | ||||
| 		void set_port_handler(PortHandler *); | ||||
|  | ||||
| 		/// Sets the current input GPIP values. | ||||
| 		void set_port_input(uint8_t); | ||||
|  | ||||
| 		/// @returns the current GPIP output values. | ||||
| 		/// | ||||
| 		/// @discussion TODO. | ||||
| 		uint8_t get_port_output(); | ||||
|  | ||||
| 		/// @returns @c true if the interrupt output is currently active; @c false otherwise.s | ||||
| 		bool get_interrupt_line(); | ||||
|  | ||||
| 		static constexpr int NoAcknowledgement = 0x100; | ||||
|  | ||||
| 		/// Communicates an interrupt acknowledge cycle. | ||||
| 		/// | ||||
| 		/// @returns the vector placed on the bus if any; @c NoAcknowledgement if nothing is loaded. | ||||
| 		int acknowledge_interrupt(); | ||||
|  | ||||
| 		struct InterruptDelegate { | ||||
| 			/// Informs the delegate of a change in the interrupt line of the nominated MFP. | ||||
| 			virtual void mfp68901_did_change_interrupt_status(MFP68901 *) = 0; | ||||
| 		}; | ||||
| 		/// Sets a delegate that will be notified upon any change in the interrupt line. | ||||
| 		void set_interrupt_delegate(InterruptDelegate *delegate); | ||||
|  | ||||
| 		// ClockingHint::Source. | ||||
| 		ClockingHint::Preference preferred_clocking() final; | ||||
|  | ||||
| 	private: | ||||
| 		// MARK: - Timers | ||||
| 		enum class TimerMode { | ||||
| 			Stopped, EventCount, Delay, PulseWidth | ||||
| 		}; | ||||
| 		void set_timer_mode(int timer, TimerMode, int prescale, bool reset_timer); | ||||
| 		void set_timer_data(int timer, uint8_t); | ||||
| 		uint8_t get_timer_data(int timer); | ||||
| 		void decrement_timer(int timer, int amount); | ||||
|  | ||||
| 		struct Timer { | ||||
| 			TimerMode mode = TimerMode::Stopped; | ||||
| 			uint8_t value = 0; | ||||
| 			uint8_t reload_value = 0; | ||||
| 			int prescale = 1; | ||||
| 			int prescale_count = 1; | ||||
| 			bool event_input = false; | ||||
| 		} timers_[4]; | ||||
| 		uint8_t timer_ab_control_[2] = { 0, 0 }; | ||||
| 		uint8_t timer_cd_control_ = 0; | ||||
|  | ||||
| 		HalfCycles cycles_left_; | ||||
|  | ||||
| 		// MARK: - GPIP | ||||
| 		uint8_t gpip_input_ = 0; | ||||
| 		uint8_t gpip_output_ = 0; | ||||
| 		uint8_t gpip_active_edge_ = 0; | ||||
| 		uint8_t gpip_direction_ = 0; | ||||
| 		uint8_t gpip_interrupt_state_ = 0; | ||||
|  | ||||
| 		void reevaluate_gpip_interrupts(); | ||||
|  | ||||
| 		// MARK: - Interrupts | ||||
|  | ||||
| 		InterruptDelegate *interrupt_delegate_ = nullptr; | ||||
|  | ||||
| 		// Ad hoc documentation: | ||||
| 		// | ||||
| 		// An interrupt becomes pending if it is enabled at the time it occurs. | ||||
| 		// | ||||
| 		// If a pending interrupt is enabled in the interrupt mask, a processor | ||||
| 		// interrupt is generated. Otherwise no processor interrupt is generated. | ||||
| 		// | ||||
| 		// (Disabling a bit in the enabled mask also instantaneously clears anything | ||||
| 		// in the pending mask.) | ||||
| 		// | ||||
| 		// The user can write to the pending interrupt register; a write | ||||
| 		// masks whatever is there — so you can disable bits but you cannot set them. | ||||
| 		// | ||||
| 		// If the vector register's 'S' bit is set then software end-of-interrupt mode applies: | ||||
| 		// Acknowledgement of an interrupt clears that interrupt's pending bit, but also sets | ||||
| 		// its in-service bit. That bit will remain set until the user writes a zero to its position. | ||||
| 		// If any bits are set in the in-service register, then they will prevent lower-priority | ||||
| 		// interrupts from being signalled to the CPU. Further interrupts of the same or a higher | ||||
| 		// priority may occur. | ||||
| 		// | ||||
| 		// If the vector register's 'S' bit is clear then automatic end-of-interrupt mode applies: | ||||
| 		// Acknowledgement of an interrupt will automatically clear the corresponding | ||||
| 		// pending bit. | ||||
| 		// | ||||
| 		int interrupt_enable_ = 0; | ||||
| 		int interrupt_pending_ = 0; | ||||
| 		int interrupt_mask_ = 0; | ||||
| 		int interrupt_in_service_ = 0; | ||||
| 		bool interrupt_line_ = false; | ||||
| 		uint8_t interrupt_vector_ = 0; | ||||
|  | ||||
| 		enum Interrupt { | ||||
| 			GPIP0				= (1 << 0), | ||||
| 			GPIP1				= (1 << 1), | ||||
| 			GPIP2				= (1 << 2), | ||||
| 			GPIP3				= (1 << 3), | ||||
| 			TimerD				= (1 << 4), | ||||
| 			TimerC				= (1 << 5), | ||||
| 			GPIP4				= (1 << 6), | ||||
| 			GPIP5				= (1 << 7), | ||||
|  | ||||
| 			TimerB				= (1 << 8), | ||||
| 			TransmitError		= (1 << 9), | ||||
| 			TransmitBufferEmpty	= (1 << 10), | ||||
| 			ReceiveError		= (1 << 11), | ||||
| 			ReceiveBufferFull	= (1 << 12), | ||||
| 			TimerA				= (1 << 13), | ||||
| 			GPIP6				= (1 << 14), | ||||
| 			GPIP7				= (1 << 15), | ||||
| 		}; | ||||
| 		void begin_interrupts(int interrupt); | ||||
| 		void end_interrupts(int interrupt); | ||||
| 		void update_interrupts(); | ||||
|  | ||||
| 		/// @returns the most significant bit set in v, assuming it is one of the least significant 16. | ||||
| 		inline static int msb16(int v) { | ||||
| 			// Saturate all bits below the MSB. | ||||
| 			v |= v >> 1; | ||||
| 			v |= v >> 2; | ||||
| 			v |= v >> 4; | ||||
| 			v |= v >> 8; | ||||
|  | ||||
| 			// Throw away lesser bits. | ||||
| 			return (v+1) >> 1; | ||||
| 		} | ||||
| }; | ||||
|  | ||||
| } | ||||
| } | ||||
|  | ||||
| #endif /* MFP68901_hpp */ | ||||
| @@ -27,7 +27,7 @@ template <class T> class i8255 { | ||||
| 			Stores the value @c value to the register at @c address. If this causes a change in 8255 output | ||||
| 			then the PortHandler will be informed. | ||||
| 		*/ | ||||
| 		void set_register(int address, uint8_t value) { | ||||
| 		void write(int address, uint8_t value) { | ||||
| 			switch(address & 3) { | ||||
| 				case 0: | ||||
| 					if(!(control_ & 0x10)) { | ||||
| @@ -60,7 +60,7 @@ template <class T> class i8255 { | ||||
| 			Obtains the current value for the register at @c address. If this provides a reading | ||||
| 			of input then the PortHandler will be queried. | ||||
| 		*/ | ||||
| 		uint8_t get_register(int address) { | ||||
| 		uint8_t read(int address) { | ||||
| 			switch(address & 3) { | ||||
| 				case 0:	return (control_ & 0x10) ? port_handler_.get_value(0) : outputs_[0]; | ||||
| 				case 1:	return (control_ & 0x02) ? port_handler_.get_value(1) : outputs_[1]; | ||||
|   | ||||
| @@ -95,11 +95,11 @@ void i8272::run_for(Cycles cycles) { | ||||
|  | ||||
| 	// check for an expired timer | ||||
| 	if(delay_time_ > 0) { | ||||
| 		if(cycles.as_int() >= delay_time_) { | ||||
| 		if(cycles.as_integral() >= delay_time_) { | ||||
| 			delay_time_ = 0; | ||||
| 			posit_event(static_cast<int>(Event8272::Timer)); | ||||
| 		} else { | ||||
| 			delay_time_ -= cycles.as_int(); | ||||
| 			delay_time_ -= cycles.as_integral(); | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| @@ -108,8 +108,8 @@ void i8272::run_for(Cycles cycles) { | ||||
| 		int drives_left = drives_seeking_; | ||||
| 		for(int c = 0; c < 4; c++) { | ||||
| 			if(drives_[c].phase == Drive::Seeking) { | ||||
| 				drives_[c].step_rate_counter += cycles.as_int(); | ||||
| 				int steps = drives_[c].step_rate_counter / (8000 * step_rate_time_); | ||||
| 				drives_[c].step_rate_counter += cycles.as_integral(); | ||||
| 				auto steps = drives_[c].step_rate_counter / (8000 * step_rate_time_); | ||||
| 				drives_[c].step_rate_counter %= (8000 * step_rate_time_); | ||||
| 				while(steps--) { | ||||
| 					// Perform a step. | ||||
| @@ -141,12 +141,12 @@ void i8272::run_for(Cycles cycles) { | ||||
| 			int head = c&1; | ||||
|  | ||||
| 			if(drives_[drive].head_unload_delay[head] > 0) { | ||||
| 				if(cycles.as_int() >= drives_[drive].head_unload_delay[head]) { | ||||
| 				if(cycles.as_integral() >= drives_[drive].head_unload_delay[head]) { | ||||
| 					drives_[drive].head_unload_delay[head] = 0; | ||||
| 					drives_[drive].head_is_loaded[head] = false; | ||||
| 					head_timers_running_--; | ||||
| 				} else { | ||||
| 					drives_[drive].head_unload_delay[head] -= cycles.as_int(); | ||||
| 					drives_[drive].head_unload_delay[head] -= cycles.as_integral(); | ||||
| 				} | ||||
| 				timers_left--; | ||||
| 				if(!timers_left) break; | ||||
| @@ -163,7 +163,7 @@ void i8272::run_for(Cycles cycles) { | ||||
| 	if(is_sleeping_) update_clocking_observer(); | ||||
| } | ||||
|  | ||||
| void i8272::set_register(int address, uint8_t value) { | ||||
| void i8272::write(int address, uint8_t value) { | ||||
| 	// don't consider attempted sets to the status register | ||||
| 	if(!address) return; | ||||
|  | ||||
| @@ -181,7 +181,7 @@ void i8272::set_register(int address, uint8_t value) { | ||||
| 	} | ||||
| } | ||||
|  | ||||
| uint8_t i8272::get_register(int address) { | ||||
| uint8_t i8272::read(int address) { | ||||
| 	if(address) { | ||||
| 		if(result_stack_.empty()) return 0xff; | ||||
| 		uint8_t result = result_stack_.back(); | ||||
| @@ -292,7 +292,7 @@ void i8272::posit_event(int event_type) { | ||||
| 			WAIT_FOR_EVENT(Event8272::CommandByte) | ||||
| 			SetBusy(); | ||||
|  | ||||
| 			static const std::size_t required_lengths[32] = { | ||||
| 			static constexpr std::size_t required_lengths[32] = { | ||||
| 				0,	0,	9,	3,	2,	9,	9,	2, | ||||
| 				1,	9,	2,	0,	9,	6,	0,	3, | ||||
| 				0,	9,	0,	0,	0,	0,	0,	0, | ||||
| @@ -865,7 +865,7 @@ void i8272::posit_event(int event_type) { | ||||
| 			SetDataRequest(); | ||||
| 			SetDataDirectionToProcessor(); | ||||
|  | ||||
| 			// The actual stuff of unwinding result_stack_ is handled by ::get_register; wait | ||||
| 			// The actual stuff of unwinding result_stack_ is handled by ::read; wait | ||||
| 			// until the processor has read all result bytes. | ||||
| 			WAIT_FOR_EVENT(Event8272::ResultEmpty); | ||||
|  | ||||
|   | ||||
| @@ -24,7 +24,7 @@ class BusHandler { | ||||
| 		virtual void set_interrupt(bool irq) {} | ||||
| }; | ||||
|  | ||||
| class i8272: public Storage::Disk::MFMController { | ||||
| class i8272 : public Storage::Disk::MFMController { | ||||
| 	public: | ||||
| 		i8272(BusHandler &bus_handler, Cycles clock_rate); | ||||
|  | ||||
| @@ -33,13 +33,13 @@ class i8272: public Storage::Disk::MFMController { | ||||
| 		void set_data_input(uint8_t value); | ||||
| 		uint8_t get_data_output(); | ||||
|  | ||||
| 		void set_register(int address, uint8_t value); | ||||
| 		uint8_t get_register(int address); | ||||
| 		void write(int address, uint8_t value); | ||||
| 		uint8_t read(int address); | ||||
|  | ||||
| 		void set_dma_acknowledge(bool dack); | ||||
| 		void set_terminal_count(bool tc); | ||||
|  | ||||
| 		ClockingHint::Preference preferred_clocking() override; | ||||
| 		ClockingHint::Preference preferred_clocking() final; | ||||
|  | ||||
| 	protected: | ||||
| 		virtual void select_drive(int number) = 0; | ||||
| @@ -73,7 +73,7 @@ class i8272: public Storage::Disk::MFMController { | ||||
| 		bool is_access_command_ = false; | ||||
|  | ||||
| 		// The counter used for ::Timer events. | ||||
| 		int delay_time_ = 0; | ||||
| 		Cycles::IntType delay_time_ = 0; | ||||
|  | ||||
| 		// The connected drives. | ||||
| 		struct Drive { | ||||
| @@ -89,12 +89,12 @@ class i8272: public Storage::Disk::MFMController { | ||||
| 			bool seek_failed = false; | ||||
|  | ||||
| 			// Seeking: transient state. | ||||
| 			int step_rate_counter = 0; | ||||
| 			Cycles::IntType step_rate_counter = 0; | ||||
| 			int steps_taken = 0; | ||||
| 			int target_head_position = 0;	// either an actual number, or -1 to indicate to step until track zero | ||||
|  | ||||
| 			// Head state. | ||||
| 			int head_unload_delay[2] = {0, 0}; | ||||
| 			Cycles::IntType head_unload_delay[2] = {0, 0}; | ||||
| 			bool head_is_loaded[2] = {false, false}; | ||||
|  | ||||
| 		} drives_[4]; | ||||
|   | ||||
| @@ -17,16 +17,16 @@ using namespace TI::TMS; | ||||
|  | ||||
| namespace { | ||||
|  | ||||
| const uint8_t StatusInterrupt = 0x80; | ||||
| const uint8_t StatusSpriteOverflow = 0x40; | ||||
| constexpr uint8_t StatusInterrupt = 0x80; | ||||
| constexpr uint8_t StatusSpriteOverflow = 0x40; | ||||
|  | ||||
| const int StatusSpriteCollisionShift = 5; | ||||
| const uint8_t StatusSpriteCollision = 0x20; | ||||
| constexpr int StatusSpriteCollisionShift = 5; | ||||
| constexpr uint8_t StatusSpriteCollision = 0x20; | ||||
|  | ||||
| // 342 internal cycles are 228/227.5ths of a line, so 341.25 cycles should be a whole | ||||
| // line. Therefore multiply everything by four, but set line length to 1365 rather than 342*4 = 1368. | ||||
| const unsigned int CRTCyclesPerLine = 1365; | ||||
| const unsigned int CRTCyclesDivider = 4; | ||||
| constexpr unsigned int CRTCyclesPerLine = 1365; | ||||
| constexpr unsigned int CRTCyclesDivider = 4; | ||||
|  | ||||
| struct ReverseTable { | ||||
| 	std::uint8_t map[256]; | ||||
| @@ -166,7 +166,7 @@ void TMS9918::run_for(const HalfCycles cycles) { | ||||
| 	// Convert 456 clocked half cycles per line to 342 internal cycles per line; | ||||
| 	// the internal clock is 1.5 times the nominal 3.579545 Mhz that I've advertised | ||||
| 	// for this part. So multiply by three quarters. | ||||
| 	int int_cycles = (cycles.as_int() * 3) + cycles_error_; | ||||
| 	int int_cycles = int(cycles.as_integral() * 3) + cycles_error_; | ||||
| 	cycles_error_ = int_cycles & 3; | ||||
| 	int_cycles >>= 2; | ||||
| 	if(!int_cycles) return; | ||||
| @@ -352,8 +352,7 @@ void TMS9918::run_for(const HalfCycles cycles) { | ||||
| 				// Output video stream. | ||||
| 				// -------------------- | ||||
|  | ||||
| #define intersect(left, right, code)	\ | ||||
| 	{	\ | ||||
| #define intersect(left, right, code)	{	\ | ||||
| 		const int start = std::max(read_pointer_.column, left);	\ | ||||
| 		const int end = std::min(end_column, right);	\ | ||||
| 		if(end > start) {\ | ||||
| @@ -493,7 +492,7 @@ void Base::output_border(int cycles, uint32_t cram_dot) { | ||||
| 	} | ||||
| } | ||||
|  | ||||
| void TMS9918::set_register(int address, uint8_t value) { | ||||
| void TMS9918::write(int address, uint8_t value) { | ||||
| 	// Writes to address 0 are writes to the video RAM. Store | ||||
| 	// the value and return. | ||||
| 	if(!(address & 1)) { | ||||
| @@ -625,7 +624,7 @@ void TMS9918::set_register(int address, uint8_t value) { | ||||
|  | ||||
| uint8_t TMS9918::get_current_line() { | ||||
| 	// Determine the row to return. | ||||
| 	static const int row_change_position = 63;	// This is the proper Master System value; substitute if any other VDPs turn out to have this functionality. | ||||
| 	constexpr int row_change_position = 63;	// This is the proper Master System value; substitute if any other VDPs turn out to have this functionality. | ||||
| 	int source_row = | ||||
| 		(write_pointer_.column < row_change_position) | ||||
| 			? (write_pointer_.row + mode_timing_.total_lines - 1)%mode_timing_.total_lines | ||||
| @@ -671,7 +670,7 @@ void TMS9918::latch_horizontal_counter() { | ||||
| 	latched_column_ = write_pointer_.column; | ||||
| } | ||||
|  | ||||
| uint8_t TMS9918::get_register(int address) { | ||||
| uint8_t TMS9918::read(int address) { | ||||
| 	write_phase_ = false; | ||||
|  | ||||
| 	// Reads from address 0 read video RAM, via the read-ahead buffer. | ||||
| @@ -830,8 +829,8 @@ void Base::draw_tms_character(int start, int end) { | ||||
| 		int sprite_collision = 0; | ||||
| 		memset(&sprite_buffer[start], 0, size_t(end - start)*sizeof(sprite_buffer[0])); | ||||
|  | ||||
| 		static const uint32_t sprite_colour_selection_masks[2] = {0x00000000, 0xffffffff}; | ||||
| 		static const int colour_masks[16] = {0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}; | ||||
| 		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}; | ||||
|  | ||||
| 		// Draw all sprites into the sprite buffer. | ||||
| 		const int shifter_target = sprites_16x16_ ? 32 : 16; | ||||
|   | ||||
| @@ -54,10 +54,10 @@ class TMS9918: public Base { | ||||
| 		void run_for(const HalfCycles cycles); | ||||
|  | ||||
| 		/*! Sets a register value. */ | ||||
| 		void set_register(int address, uint8_t value); | ||||
| 		void write(int address, uint8_t value); | ||||
|  | ||||
| 		/*! Gets a register value. */ | ||||
| 		uint8_t get_register(int address); | ||||
| 		uint8_t read(int address); | ||||
|  | ||||
| 		/*! Gets the current scan line; provided by the Master System only. */ | ||||
| 		uint8_t get_current_line(); | ||||
| @@ -69,8 +69,8 @@ class TMS9918: public Base { | ||||
| 		void latch_horizontal_counter(); | ||||
|  | ||||
| 		/*! | ||||
| 			Returns the amount of time until get_interrupt_line would next return true if | ||||
| 			there are no interceding calls to set_register or get_register. | ||||
| 			Returns the amount of time until @c get_interrupt_line would next return true if | ||||
| 			there are no interceding calls to @c write or to @c read. | ||||
|  | ||||
| 			If get_interrupt_line is true now, returns zero. If get_interrupt_line would | ||||
| 			never return true, returns -1. | ||||
|   | ||||
| @@ -100,7 +100,7 @@ class Base { | ||||
| 			// (though, in practice, it won't happen until the next | ||||
| 			// external slot after this number of cycles after the | ||||
| 			// device has requested the read or write). | ||||
| 			return 7; | ||||
| 			return 6; | ||||
| 		} | ||||
|  | ||||
| 		// Holds the main status register. | ||||
|   | ||||
| @@ -12,45 +12,56 @@ | ||||
|  | ||||
| using namespace GI::AY38910; | ||||
|  | ||||
| AY38910::AY38910(Concurrency::DeferringAsyncTaskQueue &task_queue) : task_queue_(task_queue) { | ||||
| 	// set up envelope lookup tables | ||||
| AY38910::AY38910(Personality personality, Concurrency::DeferringAsyncTaskQueue &task_queue) : task_queue_(task_queue) { | ||||
| 	// Don't use the low bit of the envelope position if this is an AY. | ||||
| 	envelope_position_mask_ |= personality == Personality::AY38910; | ||||
|  | ||||
| 	// Set up envelope lookup tables. | ||||
| 	for(int c = 0; c < 16; c++) { | ||||
| 		for(int p = 0; p < 32; p++) { | ||||
| 		for(int p = 0; p < 64; p++) { | ||||
| 			switch(c) { | ||||
| 				case 0: case 1: case 2: case 3: case 9: | ||||
| 					envelope_shapes_[c][p] = (p < 16) ? (p^0xf) : 0; | ||||
| 					envelope_overflow_masks_[c] = 0x1f; | ||||
| 					/* Envelope: \____ */ | ||||
| 					envelope_shapes_[c][p] = (p < 32) ? (p^0x1f) : 0; | ||||
| 					envelope_overflow_masks_[c] = 0x3f; | ||||
| 				break; | ||||
| 				case 4: case 5: case 6: case 7: case 15: | ||||
| 					envelope_shapes_[c][p] = (p < 16) ? p : 0; | ||||
| 					envelope_overflow_masks_[c] = 0x1f; | ||||
| 					/* Envelope: /____ */ | ||||
| 					envelope_shapes_[c][p] = (p < 32) ? p : 0; | ||||
| 					envelope_overflow_masks_[c] = 0x3f; | ||||
| 				break; | ||||
|  | ||||
| 				case 8: | ||||
| 					envelope_shapes_[c][p] = (p & 0xf) ^ 0xf; | ||||
| 					/* Envelope: \\\\\\\\ */ | ||||
| 					envelope_shapes_[c][p] = (p & 0x1f) ^ 0x1f; | ||||
| 					envelope_overflow_masks_[c] = 0x00; | ||||
| 				break; | ||||
| 				case 12: | ||||
| 					envelope_shapes_[c][p] = (p & 0xf); | ||||
| 					/* Envelope: //////// */ | ||||
| 					envelope_shapes_[c][p] = (p & 0x1f); | ||||
| 					envelope_overflow_masks_[c] = 0x00; | ||||
| 				break; | ||||
|  | ||||
| 				case 10: | ||||
| 					envelope_shapes_[c][p] = (p & 0xf) ^ ((p < 16) ? 0xf : 0x0); | ||||
| 					/* Envelope: \/\/\/\/ */ | ||||
| 					envelope_shapes_[c][p] = (p & 0x1f) ^ ((p < 32) ? 0x1f : 0x0); | ||||
| 					envelope_overflow_masks_[c] = 0x00; | ||||
| 				break; | ||||
| 				case 14: | ||||
| 					envelope_shapes_[c][p] = (p & 0xf) ^ ((p < 16) ? 0x0 : 0xf); | ||||
| 					/* Envelope: /\/\/\/\ */ | ||||
| 					envelope_shapes_[c][p] = (p & 0x1f) ^ ((p < 32) ? 0x0 : 0x1f); | ||||
| 					envelope_overflow_masks_[c] = 0x00; | ||||
| 				break; | ||||
|  | ||||
| 				case 11: | ||||
| 					envelope_shapes_[c][p] = (p < 16) ? (p^0xf) : 0xf; | ||||
| 					envelope_overflow_masks_[c] = 0x1f; | ||||
| 					/* Envelope: \------	(if - is high) */ | ||||
| 					envelope_shapes_[c][p] = (p < 32) ? (p^0x1f) : 0x1f; | ||||
| 					envelope_overflow_masks_[c] = 0x3f; | ||||
| 				break; | ||||
| 				case 13: | ||||
| 					envelope_shapes_[c][p] = (p < 16) ? p : 0xf; | ||||
| 					envelope_overflow_masks_[c] = 0x1f; | ||||
| 					/* Envelope: /------- */ | ||||
| 					envelope_shapes_[c][p] = (p < 32) ? p : 0x1f; | ||||
| 					envelope_overflow_masks_[c] = 0x3f; | ||||
| 				break; | ||||
| 			} | ||||
| 		} | ||||
| @@ -61,18 +72,27 @@ AY38910::AY38910(Concurrency::DeferringAsyncTaskQueue &task_queue) : task_queue_ | ||||
|  | ||||
| void AY38910::set_sample_volume_range(std::int16_t range) { | ||||
| 	// set up volume lookup table | ||||
| 	const float max_volume = static_cast<float>(range) / 3.0f;	// As there are three channels. | ||||
| 	const float root_two = sqrtf(2.0f); | ||||
| 	for(int v = 0; v < 16; v++) { | ||||
| 		volumes_[v] = static_cast<int>(max_volume / powf(root_two, static_cast<float>(v ^ 0xf))); | ||||
| 	const float max_volume = float(range) / 3.0f;	// As there are three channels. | ||||
| 	constexpr float root_two = 1.414213562373095f; | ||||
| 	for(int v = 0; v < 32; v++) { | ||||
| 		volumes_[v] = int(max_volume / powf(root_two, float(v ^ 0x1f) / 2.0f)); | ||||
| 	} | ||||
| 	volumes_[0] = 0; | ||||
| 	volumes_[0] = 0;	// Tie level 0 to silence. | ||||
| 	evaluate_output_volume(); | ||||
| } | ||||
|  | ||||
| void AY38910::get_samples(std::size_t number_of_samples, int16_t *target) { | ||||
| 	// Note on structure below: the real AY has a built-in divider of 8 | ||||
| 	// prior to applying its tone and noise dividers. But the YM fills the | ||||
| 	// same total periods for noise and tone with double-precision envelopes. | ||||
| 	// Therefore this class implements a divider of 4 and doubles the tone | ||||
| 	// and noise periods. The envelope ticks along at the divide-by-four rate, | ||||
| 	// but if this is an AY rather than a YM then its lowest bit is forced to 1, | ||||
| 	// matching the YM datasheet's depiction of envelope level 31 as equal to | ||||
| 	// programmatic volume 15, envelope level 29 as equal to programmatic 14, etc. | ||||
|  | ||||
| 	std::size_t c = 0; | ||||
| 	while((master_divider_&7) && c < number_of_samples) { | ||||
| 	while((master_divider_&3) && c < number_of_samples) { | ||||
| 		target[c] = output_volume_; | ||||
| 		master_divider_++; | ||||
| 		c++; | ||||
| @@ -83,49 +103,49 @@ void AY38910::get_samples(std::size_t number_of_samples, int16_t *target) { | ||||
| 	if(tone_counters_[c]) tone_counters_[c]--;\ | ||||
| 	else {\ | ||||
| 		tone_outputs_[c] ^= 1;\ | ||||
| 		tone_counters_[c] = tone_periods_[c];\ | ||||
| 		tone_counters_[c] = tone_periods_[c] << 1;\ | ||||
| 	} | ||||
|  | ||||
| 		// update the tone channels | ||||
| 		// Update the tone channels. | ||||
| 		step_channel(0); | ||||
| 		step_channel(1); | ||||
| 		step_channel(2); | ||||
|  | ||||
| #undef step_channel | ||||
|  | ||||
| 		// ... the noise generator. This recomputes the new bit repeatedly but harmlessly, only shifting | ||||
| 		// Update the noise generator. This recomputes the new bit repeatedly but harmlessly, only shifting | ||||
| 		// it into the official 17 upon divider underflow. | ||||
| 		if(noise_counter_) noise_counter_--; | ||||
| 		else { | ||||
| 			noise_counter_ = noise_period_; | ||||
| 			noise_counter_ = noise_period_ << 1;	// To cover the double resolution of envelopes. | ||||
| 			noise_output_ ^= noise_shift_register_&1; | ||||
| 			noise_shift_register_ |= ((noise_shift_register_ ^ (noise_shift_register_ >> 3))&1) << 17; | ||||
| 			noise_shift_register_ >>= 1; | ||||
| 		} | ||||
|  | ||||
| 		// ... and the envelope generator. Table based for pattern lookup, with a 'refill' step: a way of | ||||
| 		// implementing non-repeating patterns by locking them to table position 0x1f. | ||||
| 		// Update the envelope generator. Table based for pattern lookup, with a 'refill' step: a way of | ||||
| 		// implementing non-repeating patterns by locking them to the final table position. | ||||
| 		if(envelope_divider_) envelope_divider_--; | ||||
| 		else { | ||||
| 			envelope_divider_ = envelope_period_; | ||||
| 			envelope_position_ ++; | ||||
| 			if(envelope_position_ == 32) envelope_position_ = envelope_overflow_masks_[output_registers_[13]]; | ||||
| 			if(envelope_position_ == 64) envelope_position_ = envelope_overflow_masks_[output_registers_[13]]; | ||||
| 		} | ||||
|  | ||||
| 		evaluate_output_volume(); | ||||
|  | ||||
| 		for(int ic = 0; ic < 8 && c < number_of_samples; ic++) { | ||||
| 		for(int ic = 0; ic < 4 && c < number_of_samples; ic++) { | ||||
| 			target[c] = output_volume_; | ||||
| 			c++; | ||||
| 			master_divider_++; | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	master_divider_ &= 7; | ||||
| 	master_divider_ &= 3; | ||||
| } | ||||
|  | ||||
| void AY38910::evaluate_output_volume() { | ||||
| 	int envelope_volume = envelope_shapes_[output_registers_[13]][envelope_position_]; | ||||
| 	int envelope_volume = envelope_shapes_[output_registers_[13]][envelope_position_ | envelope_position_mask_]; | ||||
|  | ||||
| 	// The output level for a channel is: | ||||
| 	//	1 if neither tone nor noise is enabled; | ||||
| @@ -142,9 +162,20 @@ void AY38910::evaluate_output_volume() { | ||||
| 	}; | ||||
| #undef level | ||||
|  | ||||
| 		// Channel volume is a simple selection: if the bit at 0x10 is set, use the envelope volume; otherwise use the lower four bits | ||||
| 	// This remapping table seeks to map 'channel volumes', i.e. the levels produced from the | ||||
| 	// 16-step progammatic volumes set per channel to 'envelope volumes', i.e. the 32-step | ||||
| 	// volumes that are produced by the envelope generators (on a YM at least). My reading of | ||||
| 	// the data sheet is that '0' is still off, but 15 should be as loud as peak envelope. So | ||||
| 	// I've thrown in the discontinuity at the low end, where it'll be very quiet. | ||||
| 	const int channel_volumes[] = { | ||||
| 		0, 3, 5, 7, 9, 11, 13, 15, 17, 19, 21, 23, 25, 27, 29, 31 | ||||
| 	}; | ||||
| 	static_assert(sizeof(channel_volumes) == 16*sizeof(int)); | ||||
|  | ||||
| 		// Channel volume is a simple selection: if the bit at 0x10 is set, use the envelope volume; otherwise use the lower four bits, | ||||
| 		// mapped to the range 1–31 in case this is a YM. | ||||
| #define channel_volume(c)	\ | ||||
| 	((output_registers_[c] >> 4)&1) * envelope_volume + (((output_registers_[c] >> 4)&1)^1) * (output_registers_[c]&0xf) | ||||
| 	((output_registers_[c] >> 4)&1) * envelope_volume + (((output_registers_[c] >> 4)&1)^1) * channel_volumes[output_registers_[c]&0xf] | ||||
|  | ||||
| 	const int volumes[3] = { | ||||
| 		channel_volume(8), | ||||
|   | ||||
| @@ -35,9 +35,10 @@ class PortHandler { | ||||
| 		} | ||||
|  | ||||
| 		/*! | ||||
| 			Requests the current input on an AY port. | ||||
| 			Sets the current output on an AY port. | ||||
|  | ||||
| 			@param port_b @c true if the input being queried is Port B. @c false if it is Port A. | ||||
| 			@param port_b @c true if the output being posted is Port B. @c false if it is Port A. | ||||
| 			@param value the value now being output. | ||||
| 		*/ | ||||
| 		virtual void set_port_output(bool port_b, uint8_t value) {} | ||||
| }; | ||||
| @@ -51,6 +52,13 @@ enum ControlLines { | ||||
| 	BDIR	= (1 << 2) | ||||
| }; | ||||
|  | ||||
| enum class Personality { | ||||
| 	/// Provides 16 volume levels to envelopes. | ||||
| 	AY38910, | ||||
| 	/// Provides 32 volume levels to envelopes. | ||||
| 	YM2149F | ||||
| }; | ||||
|  | ||||
| /*! | ||||
| 	Provides emulation of an AY-3-8910 / YM2149, which is a three-channel sound chip with a | ||||
| 	noise generator and a volume envelope generator, which also provides two bidirectional | ||||
| @@ -59,7 +67,7 @@ enum ControlLines { | ||||
| class AY38910: public ::Outputs::Speaker::SampleSource { | ||||
| 	public: | ||||
| 		/// Creates a new AY38910. | ||||
| 		AY38910(Concurrency::DeferringAsyncTaskQueue &task_queue); | ||||
| 		AY38910(Personality, Concurrency::DeferringAsyncTaskQueue &); | ||||
|  | ||||
| 		/// Sets the value the AY would read from its data lines if it were not outputting. | ||||
| 		void set_data_input(uint8_t r); | ||||
| @@ -108,11 +116,11 @@ class AY38910: public ::Outputs::Speaker::SampleSource { | ||||
|  | ||||
| 		int envelope_period_ = 0; | ||||
| 		int envelope_divider_ = 0; | ||||
| 		int envelope_position_ = 0; | ||||
| 		int envelope_shapes_[16][32]; | ||||
| 		int envelope_position_ = 0, envelope_position_mask_ = 0; | ||||
| 		int envelope_shapes_[16][64]; | ||||
| 		int envelope_overflow_masks_[16]; | ||||
|  | ||||
| 		int volumes_[16]; | ||||
| 		int volumes_[32]; | ||||
|  | ||||
| 		enum ControlState { | ||||
| 			Inactive, | ||||
|   | ||||
| @@ -78,7 +78,7 @@ void DiskII::select_drive(int drive) { | ||||
| void DiskII::run_for(const Cycles cycles) { | ||||
| 	if(preferred_clocking() == ClockingHint::Preference::None) return; | ||||
|  | ||||
| 	int integer_cycles = cycles.as_int(); | ||||
| 	auto integer_cycles = cycles.as_integral(); | ||||
| 	while(integer_cycles--) { | ||||
| 		const int address = (state_ & 0xf0) | inputs_ | ((shift_register_&0x80) >> 6); | ||||
| 		if(flux_duration_) { | ||||
| @@ -124,7 +124,7 @@ void DiskII::run_for(const Cycles cycles) { | ||||
| 	// motor switch being flipped and the drive motor actually switching off. | ||||
| 	// This models that, accepting overrun as a risk. | ||||
| 	if(motor_off_time_ >= 0) { | ||||
| 		motor_off_time_ -= cycles.as_int(); | ||||
| 		motor_off_time_ -= cycles.as_integral(); | ||||
| 		if(motor_off_time_ < 0) { | ||||
| 			set_control(Control::Motor, false); | ||||
| 		} | ||||
| @@ -266,7 +266,7 @@ int DiskII::read_address(int address) { | ||||
| 		break; | ||||
| 		case 0xf: | ||||
| 			if(!(inputs_ & input_mode)) | ||||
| 				drives_[active_drive_].begin_writing(Storage::Time(1, clock_rate_), false); | ||||
| 				drives_[active_drive_].begin_writing(Storage::Time(1, int(clock_rate_)), false); | ||||
| 			inputs_ |= input_mode; | ||||
| 		break; | ||||
| 	} | ||||
|   | ||||
| @@ -26,7 +26,7 @@ namespace Apple { | ||||
| /*! | ||||
| 	Provides an emulation of the Apple Disk II. | ||||
| */ | ||||
| class DiskII: | ||||
| class DiskII final: | ||||
| 	public Storage::Disk::Drive::EventDelegate, | ||||
| 	public ClockingHint::Source, | ||||
| 	public ClockingHint::Observer { | ||||
| @@ -48,7 +48,7 @@ class DiskII: | ||||
| 			The value returned by @c read_address if accessing that address | ||||
| 			didn't cause the disk II to place anything onto the bus. | ||||
| 		*/ | ||||
| 		const int DidNotLoad = -1; | ||||
| 		static constexpr int DidNotLoad = -1; | ||||
|  | ||||
| 		/// Advances the controller by @c cycles. | ||||
| 		void run_for(const Cycles cycles); | ||||
| @@ -76,7 +76,7 @@ class DiskII: | ||||
| 		void set_disk(const std::shared_ptr<Storage::Disk::Disk> &disk, int drive); | ||||
|  | ||||
| 		// As per Sleeper. | ||||
| 		ClockingHint::Preference preferred_clocking() override; | ||||
| 		ClockingHint::Preference preferred_clocking() final; | ||||
|  | ||||
| 		// The Disk II functions as a potential target for @c Activity::Sources. | ||||
| 		void set_activity_observer(Activity::Observer *observer); | ||||
| @@ -101,7 +101,7 @@ class DiskII: | ||||
| 		void process_event(const Storage::Disk::Drive::Event &event) override; | ||||
| 		void set_component_prefers_clocking(ClockingHint::Source *component, ClockingHint::Preference preference) override; | ||||
|  | ||||
| 		const int clock_rate_ = 0; | ||||
| 		const Cycles::IntType clock_rate_ = 0; | ||||
|  | ||||
| 		uint8_t state_ = 0; | ||||
| 		uint8_t inputs_ = 0; | ||||
| @@ -109,7 +109,7 @@ class DiskII: | ||||
|  | ||||
| 		int stepper_mask_ = 0; | ||||
| 		int stepper_position_ = 0; | ||||
| 		int motor_off_time_ = -1; | ||||
| 		Cycles::IntType motor_off_time_ = -1; | ||||
|  | ||||
| 		bool is_write_protected(); | ||||
| 		std::array<uint8_t, 256> state_machine_; | ||||
|   | ||||
| @@ -13,15 +13,15 @@ | ||||
| using namespace Apple; | ||||
|  | ||||
| namespace  { | ||||
| 	const int CA0		= 1 << 0; | ||||
| 	const int CA1		= 1 << 1; | ||||
| 	const int CA2		= 1 << 2; | ||||
| 	const int LSTRB		= 1 << 3; | ||||
| 	const int ENABLE	= 1 << 4; | ||||
| 	const int DRIVESEL	= 1 << 5;	/* This means drive select, like on the original Disk II. */ | ||||
| 	const int Q6		= 1 << 6; | ||||
| 	const int Q7		= 1 << 7; | ||||
| 	const int SEL		= 1 << 8;	/* This is an additional input, not available on a Disk II, with a confusingly-similar name to SELECT but a distinct purpose. */ | ||||
| 	constexpr int CA0		= 1 << 0; | ||||
| 	constexpr int CA1		= 1 << 1; | ||||
| 	constexpr int CA2		= 1 << 2; | ||||
| 	constexpr int LSTRB		= 1 << 3; | ||||
| 	constexpr int ENABLE	= 1 << 4; | ||||
| 	constexpr int DRIVESEL	= 1 << 5;	/* This means drive select, like on the original Disk II. */ | ||||
| 	constexpr int Q6		= 1 << 6; | ||||
| 	constexpr int Q7		= 1 << 7; | ||||
| 	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. */ | ||||
| } | ||||
|  | ||||
| IWM::IWM(int clock_rate) : | ||||
| @@ -233,7 +233,7 @@ void IWM::run_for(const Cycles cycles) { | ||||
| 	} | ||||
|  | ||||
| 	// Activity otherwise depends on mode and motor state. | ||||
| 	int integer_cycles = cycles.as_int(); | ||||
| 	auto integer_cycles = cycles.as_integral(); | ||||
| 	switch(shift_mode_) { | ||||
| 		case ShiftMode::Reading: { | ||||
| 			// Per the IWM patent, column 7, around line 35 onwards: "The expected time | ||||
| @@ -241,7 +241,7 @@ void IWM::run_for(const Cycles cycles) { | ||||
| 			// expected time since the data is not precisely spaced when read due to | ||||
| 			// variations in drive speed and other external factors". The error_margin | ||||
| 			// here implements the 'after' part of that contract. | ||||
| 			const auto error_margin = Cycles(bit_length_.as_int() >> 1); | ||||
| 			const auto error_margin = Cycles(bit_length_.as_integral() >> 1); | ||||
|  | ||||
| 			if(drive_is_rotating_[active_drive_]) { | ||||
| 				while(integer_cycles--) { | ||||
| @@ -254,7 +254,7 @@ void IWM::run_for(const Cycles cycles) { | ||||
| 			} else { | ||||
| 				while(cycles_since_shift_ + integer_cycles >= bit_length_ + error_margin) { | ||||
| 					const auto run_length = bit_length_ + error_margin - cycles_since_shift_; | ||||
| 					integer_cycles -= run_length.as_int(); | ||||
| 					integer_cycles -= run_length.as_integral(); | ||||
| 					cycles_since_shift_ += run_length; | ||||
| 					propose_shift(0); | ||||
| 				} | ||||
| @@ -272,7 +272,7 @@ void IWM::run_for(const Cycles cycles) { | ||||
| 					drives_[active_drive_]->write_bit(shift_register_ & 0x80); | ||||
| 					shift_register_ <<= 1; | ||||
|  | ||||
| 					integer_cycles -= cycles_until_write.as_int(); | ||||
| 					integer_cycles -= cycles_until_write.as_integral(); | ||||
| 					cycles_since_shift_ = Cycles(0); | ||||
|  | ||||
| 					--output_bits_remaining_; | ||||
| @@ -333,7 +333,7 @@ void IWM::select_shift_mode() { | ||||
|  | ||||
| 	// If writing mode just began, set the drive into write mode and cue up the first output byte. | ||||
| 	if(drives_[active_drive_] && old_shift_mode != ShiftMode::Writing && shift_mode_ == ShiftMode::Writing) { | ||||
| 		drives_[active_drive_]->begin_writing(Storage::Time(1, clock_rate_ / bit_length_.as_int()), false); | ||||
| 		drives_[active_drive_]->begin_writing(Storage::Time(1, clock_rate_ / bit_length_.as_integral()), false); | ||||
| 		shift_register_ = next_output_; | ||||
| 		write_handshake_ |= 0x80 | 0x40; | ||||
| 		output_bits_remaining_ = 8; | ||||
| @@ -369,7 +369,7 @@ void IWM::propose_shift(uint8_t bit) { | ||||
| 	// shift in a 1 and start a new window wherever the first found 1 was. | ||||
| 	// | ||||
| 	// If no 1s are found, shift in a 0 and don't alter expectations as to window placement. | ||||
| 	const auto error_margin = Cycles(bit_length_.as_int() >> 1); | ||||
| 	const auto error_margin = Cycles(bit_length_.as_integral() >> 1); | ||||
| 	if(bit && cycles_since_shift_ < error_margin) return; | ||||
|  | ||||
| 	shift_register_ = uint8_t((shift_register_ << 1) | bit); | ||||
| @@ -401,6 +401,6 @@ void IWM::set_component_prefers_clocking(ClockingHint::Source *component, Clocki | ||||
| } | ||||
|  | ||||
| void IWM::set_activity_observer(Activity::Observer *observer) { | ||||
| 	if(drives_[0]) drives_[0]->set_activity_observer(observer, "Internal Drive", true); | ||||
| 	if(drives_[1]) drives_[1]->set_activity_observer(observer, "External Drive", true); | ||||
| 	if(drives_[0]) drives_[0]->set_activity_observer(observer, "Internal Floppy", true); | ||||
| 	if(drives_[1]) drives_[1]->set_activity_observer(observer, "External Floppy", true); | ||||
| } | ||||
|   | ||||
| @@ -48,7 +48,7 @@ void SN76489::set_sample_volume_range(std::int16_t range) { | ||||
| 	evaluate_output_volume(); | ||||
| } | ||||
|  | ||||
| void SN76489::set_register(uint8_t value) { | ||||
| void SN76489::write(uint8_t value) { | ||||
| 	task_queue_.defer([value, this] () { | ||||
| 		if(value & 0x80) { | ||||
| 			active_register_ = value; | ||||
|   | ||||
| @@ -26,7 +26,7 @@ class SN76489: public Outputs::Speaker::SampleSource { | ||||
| 		SN76489(Personality personality, Concurrency::DeferringAsyncTaskQueue &task_queue, int additional_divider = 1); | ||||
|  | ||||
| 		/// Writes a new value to the SN76489. | ||||
| 		void set_register(uint8_t value); | ||||
| 		void write(uint8_t value); | ||||
|  | ||||
| 		// As per SampleSource. | ||||
| 		void get_samples(std::size_t number_of_samples, std::int16_t *target); | ||||
|   | ||||
							
								
								
									
										140
									
								
								Components/Serial/Line.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										140
									
								
								Components/Serial/Line.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,140 @@ | ||||
| // | ||||
| //  SerialPort.cpp | ||||
| //  Clock Signal | ||||
| // | ||||
| //  Created by Thomas Harte on 12/10/2019. | ||||
| //  Copyright © 2019 Thomas Harte. All rights reserved. | ||||
| // | ||||
|  | ||||
| #include "Line.hpp" | ||||
|  | ||||
| using namespace Serial; | ||||
|  | ||||
| void Line::set_writer_clock_rate(HalfCycles clock_rate) { | ||||
| 	clock_rate_ = clock_rate; | ||||
| } | ||||
|  | ||||
| void Line::advance_writer(HalfCycles cycles) { | ||||
| 	if(cycles == HalfCycles(0)) return; | ||||
|  | ||||
| 	const auto integral_cycles = cycles.as_integral(); | ||||
| 	remaining_delays_ = std::max(remaining_delays_ - integral_cycles, Cycles::IntType(0)); | ||||
| 	if(events_.empty()) { | ||||
| 		write_cycles_since_delegate_call_ += integral_cycles; | ||||
| 		if(transmission_extra_) { | ||||
| 			transmission_extra_ -= integral_cycles; | ||||
| 			if(transmission_extra_ <= 0) { | ||||
| 				transmission_extra_ = 0; | ||||
| 				update_delegate(level_); | ||||
| 			} | ||||
| 		} | ||||
| 	} else { | ||||
| 		while(!events_.empty()) { | ||||
| 			if(events_.front().delay <= integral_cycles) { | ||||
| 				cycles -= events_.front().delay; | ||||
| 				write_cycles_since_delegate_call_ += events_.front().delay; | ||||
| 				const auto old_level = level_; | ||||
|  | ||||
| 				auto iterator = events_.begin() + 1; | ||||
| 				while(iterator != events_.end() && iterator->type != Event::Delay) { | ||||
| 					level_ = iterator->type == Event::SetHigh; | ||||
| 					++iterator; | ||||
| 				} | ||||
| 				events_.erase(events_.begin(), iterator); | ||||
|  | ||||
| 				if(old_level != level_) { | ||||
| 					update_delegate(old_level); | ||||
| 				} | ||||
|  | ||||
| 				// Book enough extra time for the read delegate to be posted | ||||
| 				// the final bit if one is attached. | ||||
| 				if(events_.empty()) { | ||||
| 					transmission_extra_ = minimum_write_cycles_for_read_delegate_bit(); | ||||
| 				} | ||||
| 			} else { | ||||
| 				events_.front().delay -= integral_cycles; | ||||
| 				write_cycles_since_delegate_call_ += integral_cycles; | ||||
| 				break; | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| void Line::write(bool level) { | ||||
| 	if(!events_.empty()) { | ||||
| 		events_.emplace_back(); | ||||
| 		events_.back().type = level ? Event::SetHigh : Event::SetLow; | ||||
| 	} else { | ||||
| 		level_ = level; | ||||
| 		transmission_extra_ = minimum_write_cycles_for_read_delegate_bit(); | ||||
| 	} | ||||
| } | ||||
|  | ||||
| void Line::write(HalfCycles cycles, int count, int levels) { | ||||
| 	remaining_delays_ += count * cycles.as_integral(); | ||||
|  | ||||
| 	auto event = events_.size(); | ||||
| 	events_.resize(events_.size() + size_t(count)*2); | ||||
| 	while(count--) { | ||||
| 		events_[event].type = Event::Delay; | ||||
| 		events_[event].delay = int(cycles.as_integral()); | ||||
| 		events_[event+1].type = (levels&1) ? Event::SetHigh : Event::SetLow; | ||||
| 		levels >>= 1; | ||||
| 		event += 2; | ||||
| 	} | ||||
| } | ||||
|  | ||||
| void Line::reset_writing() { | ||||
| 	remaining_delays_ = 0; | ||||
| 	events_.clear(); | ||||
| } | ||||
|  | ||||
| bool Line::read() { | ||||
| 	return level_; | ||||
| } | ||||
|  | ||||
| void Line::set_read_delegate(ReadDelegate *delegate, Storage::Time bit_length) { | ||||
| 	read_delegate_ = delegate; | ||||
| 	read_delegate_bit_length_ = bit_length; | ||||
| 	read_delegate_bit_length_.simplify(); | ||||
| 	write_cycles_since_delegate_call_ = 0; | ||||
| } | ||||
|  | ||||
| void Line::update_delegate(bool level) { | ||||
| 	// Exit early if there's no delegate, or if the delegate is waiting for | ||||
| 	// zero and this isn't zero. | ||||
| 	if(!read_delegate_) return; | ||||
|  | ||||
| 	const int cycles_to_forward = write_cycles_since_delegate_call_; | ||||
| 	write_cycles_since_delegate_call_ = 0; | ||||
| 	if(level && read_delegate_phase_ == ReadDelegatePhase::WaitingForZero) return; | ||||
|  | ||||
| 	// Deal with a transition out of waiting-for-zero mode by seeding time left | ||||
| 	// in bit at half a bit. | ||||
| 	if(read_delegate_phase_ == ReadDelegatePhase::WaitingForZero) { | ||||
| 		time_left_in_bit_ = read_delegate_bit_length_; | ||||
| 		time_left_in_bit_.clock_rate <<= 1; | ||||
| 		read_delegate_phase_ = ReadDelegatePhase::Serialising; | ||||
| 	} | ||||
|  | ||||
| 	// Forward as many bits as occur. | ||||
| 	Storage::Time time_left(cycles_to_forward, int(clock_rate_.as_integral())); | ||||
| 	const int bit = level ? 1 : 0; | ||||
| 	int bits = 0; | ||||
| 	while(time_left >= time_left_in_bit_) { | ||||
| 		++bits; | ||||
| 		if(!read_delegate_->serial_line_did_produce_bit(this, bit)) { | ||||
| 			read_delegate_phase_ = ReadDelegatePhase::WaitingForZero; | ||||
| 			if(bit) return; | ||||
| 		} | ||||
|  | ||||
| 		time_left -= time_left_in_bit_; | ||||
| 		time_left_in_bit_ = read_delegate_bit_length_; | ||||
| 	} | ||||
| 	time_left_in_bit_ -= time_left; | ||||
| } | ||||
|  | ||||
| Cycles::IntType Line::minimum_write_cycles_for_read_delegate_bit() { | ||||
| 	if(!read_delegate_) return 0; | ||||
| 	return 1 + (read_delegate_bit_length_ * static_cast<unsigned int>(clock_rate_.as_integral())).get<int>(); | ||||
| } | ||||
							
								
								
									
										112
									
								
								Components/Serial/Line.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										112
									
								
								Components/Serial/Line.hpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,112 @@ | ||||
| // | ||||
| //  SerialPort.hpp | ||||
| //  Clock Signal | ||||
| // | ||||
| //  Created by Thomas Harte on 12/10/2019. | ||||
| //  Copyright © 2019 Thomas Harte. All rights reserved. | ||||
| // | ||||
|  | ||||
| #ifndef SerialPort_hpp | ||||
| #define SerialPort_hpp | ||||
|  | ||||
| #include <vector> | ||||
| #include "../../Storage/Storage.hpp" | ||||
| #include "../../ClockReceiver/ClockReceiver.hpp" | ||||
| #include "../../ClockReceiver/ForceInline.hpp" | ||||
|  | ||||
| namespace Serial { | ||||
|  | ||||
| /*! | ||||
| 	@c Line connects a single reader and a single writer, allowing timestamped events to be | ||||
| 	published and consumed, potentially with a clock conversion in between. It allows line | ||||
| 	levels to be written and read in larger collections. | ||||
|  | ||||
| 	It is assumed that the owner of the reader and writer will ensure that the reader will never | ||||
| 	get ahead of the writer. If the writer posts events behind the reader they will simply be | ||||
| 	given instanteous effect. | ||||
| */ | ||||
| class Line { | ||||
| 	public: | ||||
| 		void set_writer_clock_rate(HalfCycles clock_rate); | ||||
|  | ||||
| 		/// Advances the read position by @c cycles relative to the writer's | ||||
| 		/// clock rate. | ||||
| 		void advance_writer(HalfCycles cycles); | ||||
|  | ||||
| 		/// Sets the line to @c level. | ||||
| 		void write(bool level); | ||||
|  | ||||
| 		/// Enqueues @c count level changes, the first occurring immediately | ||||
| 		/// after the final event currently posted and each subsequent event | ||||
| 		/// occurring @c cycles after the previous. An additional gap of @c cycles | ||||
| 		/// is scheduled after the final output. The levels to output are | ||||
| 		/// taken from @c levels which is read from lsb to msb. @c cycles is | ||||
| 		/// relative to the writer's clock rate. | ||||
| 		void write(HalfCycles cycles, int count, int levels); | ||||
|  | ||||
| 		/// @returns the number of cycles until currently enqueued write data is exhausted. | ||||
| 		forceinline HalfCycles write_data_time_remaining() const { | ||||
| 			return HalfCycles(remaining_delays_); | ||||
| 		} | ||||
|  | ||||
| 		/// @returns the number of cycles left until it is guaranteed that a passive reader | ||||
| 		/// has received all currently-enqueued bits. | ||||
| 		forceinline HalfCycles transmission_data_time_remaining() const { | ||||
| 			return HalfCycles(remaining_delays_ + transmission_extra_); | ||||
| 		} | ||||
|  | ||||
| 		/// Eliminates all future write states, leaving the output at whatever it is now. | ||||
| 		void reset_writing(); | ||||
|  | ||||
| 		/// @returns The instantaneous level of this line. | ||||
| 		bool read(); | ||||
|  | ||||
| 		struct ReadDelegate { | ||||
| 			virtual bool serial_line_did_produce_bit(Line *line, int bit) = 0; | ||||
| 		}; | ||||
| 		/*! | ||||
| 			Sets a read delegate, which will receive samples of the output level every | ||||
| 			@c bit_lengths of a second apart subject to a state machine: | ||||
|  | ||||
| 				* initially no bits will be delivered; | ||||
| 				* when a zero level is first detected, the line will wait half a bit's length, then start | ||||
| 				sampling at single-bit intervals, passing each bit to the delegate while it returns @c true; | ||||
| 				* as soon as the delegate returns @c false, the line will return to the initial state. | ||||
| 		*/ | ||||
| 		void set_read_delegate(ReadDelegate *delegate, Storage::Time bit_length); | ||||
|  | ||||
| 	private: | ||||
| 		struct Event { | ||||
| 			enum Type { | ||||
| 				Delay, SetHigh, SetLow | ||||
| 			} type; | ||||
| 			int delay; | ||||
| 		}; | ||||
| 		std::vector<Event> events_; | ||||
| 		HalfCycles::IntType remaining_delays_ = 0; | ||||
| 		HalfCycles::IntType transmission_extra_ = 0; | ||||
| 		bool level_ = true; | ||||
| 		HalfCycles clock_rate_ = 0; | ||||
|  | ||||
| 		ReadDelegate *read_delegate_ = nullptr; | ||||
| 		Storage::Time read_delegate_bit_length_, time_left_in_bit_; | ||||
| 		int write_cycles_since_delegate_call_ = 0; | ||||
| 		enum class ReadDelegatePhase { | ||||
| 			WaitingForZero, | ||||
| 			Serialising | ||||
| 		} read_delegate_phase_ = ReadDelegatePhase::WaitingForZero; | ||||
|  | ||||
| 		void update_delegate(bool level); | ||||
| 		HalfCycles::IntType minimum_write_cycles_for_read_delegate_bit(); | ||||
| }; | ||||
|  | ||||
| /*! | ||||
| 	Defines an RS-232-esque srial port. | ||||
| */ | ||||
| class Port { | ||||
| 	public: | ||||
| }; | ||||
|  | ||||
| } | ||||
|  | ||||
| #endif /* SerialPort_hpp */ | ||||
| @@ -18,7 +18,7 @@ AsyncTaskQueue::AsyncTaskQueue() | ||||
| #ifdef __APPLE__ | ||||
| 	serial_dispatch_queue_ = dispatch_queue_create("com.thomasharte.clocksignal.asyntaskqueue", DISPATCH_QUEUE_SERIAL); | ||||
| #else | ||||
| 	thread_.reset(new std::thread([this]() { | ||||
| 	thread_ = std::make_unique<std::thread>([this]() { | ||||
| 		while(!should_destruct_) { | ||||
| 			std::function<void(void)> next_function; | ||||
|  | ||||
| @@ -39,7 +39,7 @@ AsyncTaskQueue::AsyncTaskQueue() | ||||
| 				processing_condition_.wait(lock); | ||||
| 			} | ||||
| 		} | ||||
| 	})); | ||||
| 	}); | ||||
| #endif | ||||
| } | ||||
|  | ||||
| @@ -70,8 +70,8 @@ void AsyncTaskQueue::flush() { | ||||
| #ifdef __APPLE__ | ||||
| 	dispatch_sync(serial_dispatch_queue_, ^{}); | ||||
| #else | ||||
| 	std::shared_ptr<std::mutex> flush_mutex(new std::mutex); | ||||
| 	std::shared_ptr<std::condition_variable> flush_condition(new std::condition_variable); | ||||
| 	auto flush_mutex = std::make_shared<std::mutex>(); | ||||
| 	auto flush_condition = std::make_shared<std::condition_variable>(); | ||||
| 	std::unique_lock<std::mutex> lock(*flush_mutex); | ||||
| 	enqueue([=] () { | ||||
| 		std::unique_lock<std::mutex> inner_lock(*flush_mutex); | ||||
| @@ -88,7 +88,7 @@ DeferringAsyncTaskQueue::~DeferringAsyncTaskQueue() { | ||||
|  | ||||
| void DeferringAsyncTaskQueue::defer(std::function<void(void)> function) { | ||||
| 	if(!deferred_tasks_) { | ||||
| 		deferred_tasks_.reset(new std::list<std::function<void(void)>>); | ||||
| 		deferred_tasks_ = std::make_shared<std::list<std::function<void(void)>>>(); | ||||
| 	} | ||||
| 	deferred_tasks_->push_back(function); | ||||
| } | ||||
|   | ||||
| @@ -14,16 +14,16 @@ namespace { | ||||
| 	Appends a Boolean selection of @c selection for option @c name to @c selection_set. | ||||
| */ | ||||
| void append_bool(Configurable::SelectionSet &selection_set, const std::string &name, bool selection) { | ||||
| 	selection_set[name] = std::unique_ptr<Configurable::Selection>(new Configurable::BooleanSelection(selection)); | ||||
| 	selection_set[name] = std::make_unique<Configurable::BooleanSelection>(selection); | ||||
| } | ||||
|  | ||||
| /*! | ||||
| 	Enquires for a Boolean selection for option @c name from @c selections_by_option, storing it to @c result if found. | ||||
| */ | ||||
| bool get_bool(const Configurable::SelectionSet &selections_by_option, const std::string &name, bool &result) { | ||||
| 	auto quickload = Configurable::selection<Configurable::BooleanSelection>(selections_by_option, "quickload"); | ||||
| 	if(!quickload) return false; | ||||
| 	result = quickload->value; | ||||
| 	auto selection = Configurable::selection<Configurable::BooleanSelection>(selections_by_option, name); | ||||
| 	if(!selection) return false; | ||||
| 	result = selection->value; | ||||
| 	return true; | ||||
| } | ||||
|  | ||||
| @@ -42,6 +42,7 @@ std::vector<std::unique_ptr<Configurable::Option>> Configurable::standard_option | ||||
| 		options.emplace_back(new Configurable::ListOption("Display", "display", display_options)); | ||||
| 	} | ||||
| 	if(mask & AutomaticTapeMotorControl)	options.emplace_back(new Configurable::BooleanOption("Automatic Tape Motor Control", "autotapemotor")); | ||||
| 	if(mask & QuickBoot)					options.emplace_back(new Configurable::BooleanOption("Boot Quickly", "quickboot")); | ||||
| 	return options; | ||||
| } | ||||
|  | ||||
| @@ -63,7 +64,11 @@ void Configurable::append_display_selection(Configurable::SelectionSet &selectio | ||||
| 		case Display::CompositeMonochrome:	string_selection = "composite-mono";	break; | ||||
| 		case Display::CompositeColour:		string_selection = "composite";			break; | ||||
| 	} | ||||
| 	selection_set["display"] = std::unique_ptr<Configurable::Selection>(new Configurable::ListSelection(string_selection)); | ||||
| 	selection_set["display"] = std::make_unique<Configurable::ListSelection>(string_selection); | ||||
| } | ||||
|  | ||||
| void Configurable::append_quick_boot_selection(Configurable::SelectionSet &selection_set, bool selection) { | ||||
| 	append_bool(selection_set, "quickboot", selection); | ||||
| } | ||||
|  | ||||
| // MARK: - Selection parsers | ||||
| @@ -97,3 +102,7 @@ bool Configurable::get_display(const Configurable::SelectionSet &selections_by_o | ||||
| 	} | ||||
| 	return false; | ||||
| } | ||||
|  | ||||
| bool Configurable::get_quick_boot(const Configurable::SelectionSet &selections_by_option, bool &result) { | ||||
| 	return get_bool(selections_by_option, "quickboot", result); | ||||
| } | ||||
|   | ||||
| @@ -19,7 +19,8 @@ enum StandardOptions { | ||||
| 	DisplayCompositeColour		= (1 << 2), | ||||
| 	DisplayCompositeMonochrome	= (1 << 3), | ||||
| 	QuickLoadTape				= (1 << 4), | ||||
| 	AutomaticTapeMotorControl	= (1 << 5) | ||||
| 	AutomaticTapeMotorControl	= (1 << 5), | ||||
| 	QuickBoot					= (1 << 6), | ||||
| }; | ||||
|  | ||||
| enum class Display { | ||||
| @@ -49,6 +50,11 @@ void append_automatic_tape_motor_control_selection(SelectionSet &selection_set, | ||||
| */ | ||||
| void append_display_selection(SelectionSet &selection_set, Display selection); | ||||
|  | ||||
| /*! | ||||
| 	Appends to @c selection_set a selection of @c selection for QuickBoot. | ||||
| */ | ||||
| void append_quick_boot_selection(SelectionSet &selection_set, bool selection); | ||||
|  | ||||
| /*! | ||||
| 	Attempts to discern a QuickLoadTape selection from @c selections_by_option. | ||||
|   | ||||
| @@ -76,6 +82,15 @@ bool get_automatic_tape_motor_control_selection(const SelectionSet &selections_b | ||||
| */ | ||||
| bool get_display(const SelectionSet &selections_by_option, Display &result); | ||||
|  | ||||
| /*! | ||||
| 	Attempts to QuickBoot a QuickLoadTape selection from @c selections_by_option. | ||||
|  | ||||
| 	@param selections_by_option The user selections. | ||||
| 	@param result The location to which the selection will be stored if found. | ||||
| 	@returns @c true if a selection is found; @c false otherwise. | ||||
| */ | ||||
| bool get_quick_boot(const SelectionSet &selections_by_option, bool &result); | ||||
|  | ||||
| } | ||||
|  | ||||
| #endif /* StandardOptions_hpp */ | ||||
|   | ||||
| @@ -10,13 +10,14 @@ | ||||
|  | ||||
| using namespace Inputs; | ||||
|  | ||||
| Keyboard::Keyboard() { | ||||
| Keyboard::Keyboard(const std::set<Key> &essential_modifiers) : essential_modifiers_(essential_modifiers) { | ||||
| 	for(int k = 0; k < int(Key::Help); ++k) { | ||||
| 		observed_keys_.insert(Key(k)); | ||||
| 	} | ||||
| } | ||||
|  | ||||
| Keyboard::Keyboard(const std::set<Key> &observed_keys) : observed_keys_(observed_keys), is_exclusive_(false) {} | ||||
| Keyboard::Keyboard(const std::set<Key> &observed_keys, const std::set<Key> &essential_modifiers) : | ||||
| 	observed_keys_(observed_keys), essential_modifiers_(essential_modifiers), is_exclusive_(false) {} | ||||
|  | ||||
| void Keyboard::set_key_pressed(Key key, char value, bool is_pressed) { | ||||
| 	std::size_t key_offset = static_cast<std::size_t>(key); | ||||
| @@ -28,6 +29,10 @@ void Keyboard::set_key_pressed(Key key, char value, bool is_pressed) { | ||||
| 	if(delegate_) delegate_->keyboard_did_change_key(this, key, is_pressed); | ||||
| } | ||||
|  | ||||
| const std::set<Inputs::Keyboard::Key> &Keyboard::get_essential_modifiers() { | ||||
| 	return essential_modifiers_; | ||||
| } | ||||
|  | ||||
| void Keyboard::reset_all_keys() { | ||||
| 	std::fill(key_states_.begin(), key_states_.end(), false); | ||||
| 	if(delegate_) delegate_->reset_all_keys(this); | ||||
|   | ||||
| @@ -30,19 +30,19 @@ class Keyboard { | ||||
| 			LeftControl, LeftOption, LeftMeta, Space, RightMeta, RightOption, RightControl, | ||||
| 			Left, Right, Up, Down, | ||||
| 			Insert, Home, PageUp, Delete, End, PageDown, | ||||
| 			NumLock, KeyPadSlash, KeyPadAsterisk, KeyPadDelete, | ||||
| 			KeyPad7, KeyPad8, KeyPad9, KeyPadPlus, | ||||
| 			KeyPad4, KeyPad5, KeyPad6, KeyPadMinus, | ||||
| 			KeyPad1, KeyPad2, KeyPad3, KeyPadEnter, | ||||
| 			KeyPad0, KeyPadDecimalPoint, KeyPadEquals, | ||||
| 			NumLock, KeypadSlash, KeypadAsterisk, KeypadDelete, | ||||
| 			Keypad7, Keypad8, Keypad9, KeypadPlus, | ||||
| 			Keypad4, Keypad5, Keypad6, KeypadMinus, | ||||
| 			Keypad1, Keypad2, Keypad3, KeypadEnter, | ||||
| 			Keypad0, KeypadDecimalPoint, KeypadEquals, | ||||
| 			Help | ||||
| 		}; | ||||
|  | ||||
| 		/// Constructs a Keyboard that declares itself to observe all keys. | ||||
| 		Keyboard(); | ||||
| 		Keyboard(const std::set<Key> &essential_modifiers = {}); | ||||
|  | ||||
| 		/// Constructs a Keyboard that declares itself to observe only members of @c observed_keys. | ||||
| 		Keyboard(const std::set<Key> &observed_keys); | ||||
| 		Keyboard(const std::set<Key> &observed_keys, const std::set<Key> &essential_modifiers); | ||||
|  | ||||
| 		// Host interface. | ||||
| 		virtual void set_key_pressed(Key key, char value, bool is_pressed); | ||||
| @@ -51,10 +51,18 @@ class Keyboard { | ||||
| 		/// @returns a set of all Keys that this keyboard responds to. | ||||
| 		virtual const std::set<Key> &observed_keys(); | ||||
|  | ||||
| 		/* | ||||
| 		/// @returns the list of modifiers that this keyboard considers 'essential' (i.e. both mapped and highly used). | ||||
| 		virtual const std::set<Inputs::Keyboard::Key> &get_essential_modifiers(); | ||||
|  | ||||
| 		/*! | ||||
| 			@returns @c true if this keyboard, on its original machine, looked | ||||
| 			like a complete keyboard — i.e. if a user would expect this keyboard | ||||
| 			to be the only thing a real keyboard maps to. | ||||
|  | ||||
| 			So this would be true of something like the Amstrad CPC, which has a full | ||||
| 			keyboard, but it would be false of something like the Sega Master System | ||||
| 			which has some buttons that you'd expect an emulator to map to its host | ||||
| 			keyboard but which does not offer a full keyboard. | ||||
| 		*/ | ||||
| 		virtual bool is_exclusive(); | ||||
|  | ||||
| @@ -68,6 +76,7 @@ class Keyboard { | ||||
|  | ||||
| 	private: | ||||
| 		std::set<Key> observed_keys_; | ||||
| 		std::set<Key> essential_modifiers_; | ||||
| 		std::vector<bool> key_states_; | ||||
| 		Delegate *delegate_ = nullptr; | ||||
| 		bool is_exclusive_ = true; | ||||
|   | ||||
| @@ -41,7 +41,7 @@ namespace AmstradCPC { | ||||
|  | ||||
| std::vector<std::unique_ptr<Configurable::Option>> get_options() { | ||||
| 	return Configurable::standard_options( | ||||
| 		static_cast<Configurable::StandardOptions>(Configurable::DisplayRGB | Configurable::DisplayCompositeColour) | ||||
| 		Configurable::StandardOptions(Configurable::DisplayRGB | Configurable::DisplayCompositeColour) | ||||
| 	); | ||||
| } | ||||
|  | ||||
| @@ -124,7 +124,7 @@ class InterruptTimer { | ||||
| class AYDeferrer { | ||||
| 	public: | ||||
| 		/// Constructs a new AY instance and sets its clock rate. | ||||
| 		AYDeferrer() : ay_(audio_queue_), speaker_(ay_) { | ||||
| 		AYDeferrer() : ay_(GI::AY38910::Personality::AY38910, audio_queue_), speaker_(ay_) { | ||||
| 			speaker_.set_input_rate(1000000); | ||||
| 		} | ||||
|  | ||||
| @@ -171,7 +171,7 @@ class AYDeferrer { | ||||
| */ | ||||
| class CRTCBusHandler { | ||||
| 	public: | ||||
| 		CRTCBusHandler(uint8_t *ram, InterruptTimer &interrupt_timer) : | ||||
| 		CRTCBusHandler(const uint8_t *ram, InterruptTimer &interrupt_timer) : | ||||
| 			crt_(1024, 1, Outputs::Display::Type::PAL50, Outputs::Display::InputDataType::Red2Green2Blue2), | ||||
| 			ram_(ram), | ||||
| 			interrupt_timer_(interrupt_timer) { | ||||
| @@ -222,9 +222,9 @@ class CRTCBusHandler { | ||||
| 				if(cycles_) { | ||||
| 					switch(previous_output_mode_) { | ||||
| 						default: | ||||
| 						case OutputMode::Blank:			crt_.output_blank(cycles_ * 16);					break; | ||||
| 						case OutputMode::Blank:			crt_.output_blank(cycles_ * 16);				break; | ||||
| 						case OutputMode::Sync:			crt_.output_sync(cycles_ * 16);					break; | ||||
| 						case OutputMode::Border:		output_border(cycles_);								break; | ||||
| 						case OutputMode::Border:		output_border(cycles_);							break; | ||||
| 						case OutputMode::ColourBurst:	crt_.output_default_colour_burst(cycles_ * 16);	break; | ||||
| 						case OutputMode::Pixels: | ||||
| 							crt_.output_data(cycles_ * 16, size_t(cycles_ * 16 / pixel_divider_)); | ||||
| @@ -249,44 +249,46 @@ class CRTCBusHandler { | ||||
| 					// the CPC shuffles output lines as: | ||||
| 					//	MA13 MA12	RA2 RA1 RA0		MA9 MA8 MA7 MA6 MA5 MA4 MA3 MA2 MA1 MA0		CCLK | ||||
| 					// ... so form the real access address. | ||||
| 					uint16_t address = | ||||
| 						static_cast<uint16_t>( | ||||
| 					const uint16_t address = | ||||
| 						uint16_t( | ||||
| 							((state.refresh_address & 0x3ff) << 1) | | ||||
| 							((state.row_address & 0x7) << 11) | | ||||
| 							((state.refresh_address & 0x3000) << 2) | ||||
| 						); | ||||
|  | ||||
| 					// fetch two bytes and translate into pixels | ||||
| 					// Fetch two bytes and translate into pixels. Guaranteed: the mode can change only at | ||||
| 					// hsync, so there's no risk of pixel_pointer_ overrunning 320 output pixels without | ||||
| 					// exactly reaching 320 output pixels. | ||||
| 					switch(mode_) { | ||||
| 						case 0: | ||||
| 							reinterpret_cast<uint16_t *>(pixel_pointer_)[0] = mode0_output_[ram_[address]]; | ||||
| 							reinterpret_cast<uint16_t *>(pixel_pointer_)[1] = mode0_output_[ram_[address+1]]; | ||||
| 							pixel_pointer_ += 4; | ||||
| 							pixel_pointer_ += 2 * sizeof(uint16_t); | ||||
| 						break; | ||||
|  | ||||
| 						case 1: | ||||
| 							reinterpret_cast<uint32_t *>(pixel_pointer_)[0] = mode1_output_[ram_[address]]; | ||||
| 							reinterpret_cast<uint32_t *>(pixel_pointer_)[1] = mode1_output_[ram_[address+1]]; | ||||
| 							pixel_pointer_ += 8; | ||||
| 							pixel_pointer_ += 2 * sizeof(uint32_t); | ||||
| 						break; | ||||
|  | ||||
| 						case 2: | ||||
| 							reinterpret_cast<uint64_t *>(pixel_pointer_)[0] = mode2_output_[ram_[address]]; | ||||
| 							reinterpret_cast<uint64_t *>(pixel_pointer_)[1] = mode2_output_[ram_[address+1]]; | ||||
| 							pixel_pointer_ += 16; | ||||
| 							pixel_pointer_ += 2 * sizeof(uint64_t); | ||||
| 						break; | ||||
|  | ||||
| 						case 3: | ||||
| 							reinterpret_cast<uint16_t *>(pixel_pointer_)[0] = mode3_output_[ram_[address]]; | ||||
| 							reinterpret_cast<uint16_t *>(pixel_pointer_)[1] = mode3_output_[ram_[address+1]]; | ||||
| 							pixel_pointer_ += 4; | ||||
| 							pixel_pointer_ += 2 * sizeof(uint16_t); | ||||
| 						break; | ||||
|  | ||||
| 					} | ||||
|  | ||||
| 					// flush the current buffer pixel if full; the CRTC allows many different display | ||||
| 					// Flush the current buffer pixel if full; the CRTC allows many different display | ||||
| 					// widths so it's not necessarily possible to predict the correct number in advance | ||||
| 					// and using the upper bound could lead to inefficient behaviour | ||||
| 					// and using the upper bound could lead to inefficient behaviour. | ||||
| 					if(pixel_pointer_ == pixel_data_ + 320) { | ||||
| 						crt_.output_data(cycles_ * 16, size_t(cycles_ * 16 / pixel_divider_)); | ||||
| 						pixel_pointer_ = pixel_data_ = nullptr; | ||||
| @@ -369,9 +371,17 @@ class CRTCBusHandler { | ||||
|  | ||||
| 	private: | ||||
| 		void output_border(int length) { | ||||
| 			uint8_t *colour_pointer = static_cast<uint8_t *>(crt_.begin_data(1)); | ||||
| 			if(colour_pointer) *colour_pointer = border_; | ||||
| 			crt_.output_level(length * 16); | ||||
| 			assert(length >= 0); | ||||
|  | ||||
| 			// A black border can be output via crt_.output_blank for a minor performance | ||||
| 			// win; otherwise paint whatever the border colour really is. | ||||
| 			if(border_) { | ||||
| 				uint8_t *const colour_pointer = static_cast<uint8_t *>(crt_.begin_data(1)); | ||||
| 				if(colour_pointer) *colour_pointer = border_; | ||||
| 				crt_.output_level(length * 16); | ||||
| 			} else { | ||||
| 				crt_.output_blank(length * 16); | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| #define Mode0Colour0(c) ((c & 0x80) >> 7) | ((c & 0x20) >> 3) | ((c & 0x08) >> 2) | ((c & 0x02) << 2) | ||||
| @@ -387,16 +397,16 @@ class CRTCBusHandler { | ||||
|  | ||||
| 		void establish_palette_hits() { | ||||
| 			for(int c = 0; c < 256; c++) { | ||||
| 				mode0_palette_hits_[Mode0Colour0(c)].push_back(static_cast<uint8_t>(c)); | ||||
| 				mode0_palette_hits_[Mode0Colour1(c)].push_back(static_cast<uint8_t>(c)); | ||||
| 				mode0_palette_hits_[Mode0Colour0(c)].push_back(uint8_t(c)); | ||||
| 				mode0_palette_hits_[Mode0Colour1(c)].push_back(uint8_t(c)); | ||||
|  | ||||
| 				mode1_palette_hits_[Mode1Colour0(c)].push_back(static_cast<uint8_t>(c)); | ||||
| 				mode1_palette_hits_[Mode1Colour1(c)].push_back(static_cast<uint8_t>(c)); | ||||
| 				mode1_palette_hits_[Mode1Colour2(c)].push_back(static_cast<uint8_t>(c)); | ||||
| 				mode1_palette_hits_[Mode1Colour3(c)].push_back(static_cast<uint8_t>(c)); | ||||
| 				mode1_palette_hits_[Mode1Colour0(c)].push_back(uint8_t(c)); | ||||
| 				mode1_palette_hits_[Mode1Colour1(c)].push_back(uint8_t(c)); | ||||
| 				mode1_palette_hits_[Mode1Colour2(c)].push_back(uint8_t(c)); | ||||
| 				mode1_palette_hits_[Mode1Colour3(c)].push_back(uint8_t(c)); | ||||
|  | ||||
| 				mode3_palette_hits_[Mode3Colour0(c)].push_back(static_cast<uint8_t>(c)); | ||||
| 				mode3_palette_hits_[Mode3Colour1(c)].push_back(static_cast<uint8_t>(c)); | ||||
| 				mode3_palette_hits_[Mode3Colour0(c)].push_back(uint8_t(c)); | ||||
| 				mode3_palette_hits_[Mode3Colour1(c)].push_back(uint8_t(c)); | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| @@ -406,7 +416,7 @@ class CRTCBusHandler { | ||||
| 					// Mode 0: abcdefgh -> [gcea] [hdfb] | ||||
| 					for(int c = 0; c < 256; c++) { | ||||
| 						// prepare mode 0 | ||||
| 						uint8_t *mode0_pixels = reinterpret_cast<uint8_t *>(&mode0_output_[c]); | ||||
| 						uint8_t *const mode0_pixels = reinterpret_cast<uint8_t *>(&mode0_output_[c]); | ||||
| 						mode0_pixels[0] = palette_[Mode0Colour0(c)]; | ||||
| 						mode0_pixels[1] = palette_[Mode0Colour1(c)]; | ||||
| 					} | ||||
| @@ -415,7 +425,7 @@ class CRTCBusHandler { | ||||
| 				case 1: | ||||
| 					for(int c = 0; c < 256; c++) { | ||||
| 						// prepare mode 1 | ||||
| 						uint8_t *mode1_pixels = reinterpret_cast<uint8_t *>(&mode1_output_[c]); | ||||
| 						uint8_t *const mode1_pixels = reinterpret_cast<uint8_t *>(&mode1_output_[c]); | ||||
| 						mode1_pixels[0] = palette_[Mode1Colour0(c)]; | ||||
| 						mode1_pixels[1] = palette_[Mode1Colour1(c)]; | ||||
| 						mode1_pixels[2] = palette_[Mode1Colour2(c)]; | ||||
| @@ -426,7 +436,7 @@ class CRTCBusHandler { | ||||
| 				case 2: | ||||
| 					for(int c = 0; c < 256; c++) { | ||||
| 						// prepare mode 2 | ||||
| 						uint8_t *mode2_pixels = reinterpret_cast<uint8_t *>(&mode2_output_[c]); | ||||
| 						uint8_t *const mode2_pixels = reinterpret_cast<uint8_t *>(&mode2_output_[c]); | ||||
| 						mode2_pixels[0] = palette_[((c & 0x80) >> 7)]; | ||||
| 						mode2_pixels[1] = palette_[((c & 0x40) >> 6)]; | ||||
| 						mode2_pixels[2] = palette_[((c & 0x20) >> 5)]; | ||||
| @@ -441,7 +451,7 @@ class CRTCBusHandler { | ||||
| 				case 3: | ||||
| 					for(int c = 0; c < 256; c++) { | ||||
| 						// prepare mode 3 | ||||
| 						uint8_t *mode3_pixels = reinterpret_cast<uint8_t *>(&mode3_output_[c]); | ||||
| 						uint8_t *const mode3_pixels = reinterpret_cast<uint8_t *>(&mode3_output_[c]); | ||||
| 						mode3_pixels[0] = palette_[Mode3Colour0(c)]; | ||||
| 						mode3_pixels[1] = palette_[Mode3Colour1(c)]; | ||||
| 					} | ||||
| @@ -453,7 +463,7 @@ class CRTCBusHandler { | ||||
| 			switch(mode_) { | ||||
| 				case 0: { | ||||
| 					for(uint8_t c : mode0_palette_hits_[pen]) { | ||||
| 						uint8_t *mode0_pixels = reinterpret_cast<uint8_t *>(&mode0_output_[c]); | ||||
| 						uint8_t *const mode0_pixels = reinterpret_cast<uint8_t *>(&mode0_output_[c]); | ||||
| 						mode0_pixels[0] = palette_[Mode0Colour0(c)]; | ||||
| 						mode0_pixels[1] = palette_[Mode0Colour1(c)]; | ||||
| 					} | ||||
| @@ -461,7 +471,7 @@ class CRTCBusHandler { | ||||
| 				case 1: | ||||
| 					if(pen > 3) return; | ||||
| 					for(uint8_t c : mode1_palette_hits_[pen]) { | ||||
| 						uint8_t *mode1_pixels = reinterpret_cast<uint8_t *>(&mode1_output_[c]); | ||||
| 						uint8_t *const mode1_pixels = reinterpret_cast<uint8_t *>(&mode1_output_[c]); | ||||
| 						mode1_pixels[0] = palette_[Mode1Colour0(c)]; | ||||
| 						mode1_pixels[1] = palette_[Mode1Colour1(c)]; | ||||
| 						mode1_pixels[2] = palette_[Mode1Colour2(c)]; | ||||
| @@ -478,7 +488,7 @@ class CRTCBusHandler { | ||||
| 					if(pen > 3) return; | ||||
| 					// Same argument applies here as to case 1, as the unused bits aren't masked out. | ||||
| 					for(uint8_t c : mode3_palette_hits_[pen]) { | ||||
| 						uint8_t *mode3_pixels = reinterpret_cast<uint8_t *>(&mode3_output_[c]); | ||||
| 						uint8_t *const mode3_pixels = reinterpret_cast<uint8_t *>(&mode3_output_[c]); | ||||
| 						mode3_pixels[0] = palette_[Mode3Colour0(c)]; | ||||
| 						mode3_pixels[1] = palette_[Mode3Colour1(c)]; | ||||
| 					} | ||||
| @@ -499,7 +509,7 @@ class CRTCBusHandler { | ||||
|  | ||||
| 		uint8_t mapped_palette_value(uint8_t colour) { | ||||
| #define COL(r, g, b) (r << 4) | (g << 2) | b | ||||
| 			static const uint8_t mapping[32] = { | ||||
| 			constexpr uint8_t mapping[32] = { | ||||
| 				COL(1, 1, 1),	COL(1, 1, 1),	COL(0, 2, 1),	COL(2, 2, 1), | ||||
| 				COL(0, 0, 1),	COL(2, 0, 1),	COL(0, 1, 1),	COL(2, 1, 1), | ||||
| 				COL(2, 0, 1),	COL(2, 2, 1),	COL(2, 2, 0),	COL(2, 2, 2), | ||||
| @@ -528,7 +538,7 @@ class CRTCBusHandler { | ||||
| 		Outputs::CRT::CRT crt_; | ||||
| 		uint8_t *pixel_data_ = nullptr, *pixel_pointer_ = nullptr; | ||||
|  | ||||
| 		uint8_t *ram_ = nullptr; | ||||
| 		const uint8_t *const ram_ = nullptr; | ||||
|  | ||||
| 		int next_mode_ = 2, mode_ = 2; | ||||
|  | ||||
| @@ -564,7 +574,7 @@ class KeyboardState: public GI::AY38910::PortHandler { | ||||
| 			Sets the row currently being reported to the AY. | ||||
| 		*/ | ||||
| 		void set_row(int row) { | ||||
| 			row_ = static_cast<size_t>(row); | ||||
| 			row_ = size_t(row); | ||||
| 		} | ||||
|  | ||||
| 		/*! | ||||
| @@ -583,7 +593,7 @@ class KeyboardState: public GI::AY38910::PortHandler { | ||||
| 		*/ | ||||
| 		void set_is_pressed(bool is_pressed, int line, int key) { | ||||
| 			int mask = 1 << key; | ||||
| 			assert(static_cast<size_t>(line) < sizeof(rows_)); | ||||
| 			assert(size_t(line) < sizeof(rows_)); | ||||
| 			if(is_pressed) rows_[line] &= ~mask; else rows_[line] |= mask; | ||||
| 		} | ||||
|  | ||||
| @@ -594,7 +604,7 @@ class KeyboardState: public GI::AY38910::PortHandler { | ||||
| 			memset(rows_, 0xff, sizeof(rows_)); | ||||
| 		} | ||||
|  | ||||
| 		std::vector<std::unique_ptr<Inputs::Joystick>> &get_joysticks() { | ||||
| 		const std::vector<std::unique_ptr<Inputs::Joystick>> &get_joysticks() { | ||||
| 			return joysticks_; | ||||
| 		} | ||||
|  | ||||
| @@ -816,8 +826,8 @@ template <bool has_fdc> class ConcreteMachine: | ||||
| 			for(std::size_t index = 0; index < roms.size(); ++index) { | ||||
| 				auto &data = roms[index]; | ||||
| 				if(!data) throw ROMMachine::Error::MissingROMs; | ||||
| 				roms_[static_cast<int>(index)] = std::move(*data); | ||||
| 				roms_[static_cast<int>(index)].resize(16384); | ||||
| 				roms_[int(index)] = std::move(*data); | ||||
| 				roms_[int(index)].resize(16384); | ||||
| 			} | ||||
|  | ||||
| 			// Establish default memory map | ||||
| @@ -862,13 +872,15 @@ template <bool has_fdc> class ConcreteMachine: | ||||
|  | ||||
| 			// TODO (in the player, not here): adapt it to accept an input clock rate and | ||||
| 			// run_for as HalfCycles | ||||
| 			if(!tape_player_is_sleeping_) tape_player_.run_for(cycle.length.as_int()); | ||||
| 			if(!tape_player_is_sleeping_) tape_player_.run_for(cycle.length.as_integral()); | ||||
|  | ||||
| 			// Pump the AY | ||||
| 			ay_.run_for(cycle.length); | ||||
|  | ||||
| 			// Clock the FDC, if connected, using a lazy scale by two | ||||
| 			time_since_fdc_update_ += cycle.length; | ||||
| 			if constexpr (has_fdc) { | ||||
| 				// Clock the FDC, if connected, using a lazy scale by two | ||||
| 				time_since_fdc_update_ += cycle.length; | ||||
| 			} | ||||
|  | ||||
| 			// Update typing activity | ||||
| 			if(typer_) typer_->run_for(cycle.length); | ||||
| @@ -894,9 +906,11 @@ template <bool has_fdc> class ConcreteMachine: | ||||
| 					} | ||||
|  | ||||
| 					// Check for an upper ROM selection | ||||
| 					if(has_fdc && !(address&0x2000)) { | ||||
| 						upper_rom_ = (*cycle.value == 7) ? ROMType::AMSDOS : ROMType::BASIC; | ||||
| 						if(upper_rom_is_paged_) read_pointers_[3] = roms_[upper_rom_].data(); | ||||
| 					if constexpr (has_fdc) { | ||||
| 						if(!(address&0x2000)) { | ||||
| 							upper_rom_ = (*cycle.value == 7) ? ROMType::AMSDOS : ROMType::BASIC; | ||||
| 							if(upper_rom_is_paged_) read_pointers_[3] = roms_[upper_rom_].data(); | ||||
| 						} | ||||
| 					} | ||||
|  | ||||
| 					// Check for a CRTC access | ||||
| @@ -910,19 +924,21 @@ template <bool has_fdc> class ConcreteMachine: | ||||
|  | ||||
| 					// Check for an 8255 PIO access | ||||
| 					if(!(address & 0x800)) { | ||||
| 						i8255_.set_register((address >> 8) & 3, *cycle.value); | ||||
| 						i8255_.write((address >> 8) & 3, *cycle.value); | ||||
| 					} | ||||
|  | ||||
| 					// Check for an FDC access | ||||
| 					if(has_fdc && (address & 0x580) == 0x100) { | ||||
| 						flush_fdc(); | ||||
| 						fdc_.set_register(address & 1, *cycle.value); | ||||
| 					} | ||||
| 					if constexpr (has_fdc) { | ||||
| 						// Check for an FDC access | ||||
| 						if((address & 0x580) == 0x100) { | ||||
| 							flush_fdc(); | ||||
| 							fdc_.write(address & 1, *cycle.value); | ||||
| 						} | ||||
|  | ||||
| 					// Check for a disk motor access | ||||
| 					if(has_fdc && !(address & 0x580)) { | ||||
| 						flush_fdc(); | ||||
| 						fdc_.set_motor_on(!!(*cycle.value)); | ||||
| 						// Check for a disk motor access | ||||
| 						if(!(address & 0x580)) { | ||||
| 							flush_fdc(); | ||||
| 							fdc_.set_motor_on(!!(*cycle.value)); | ||||
| 						} | ||||
| 					} | ||||
| 				break; | ||||
| 				case CPU::Z80::PartialMachineCycle::Input: | ||||
| @@ -931,13 +947,15 @@ template <bool has_fdc> class ConcreteMachine: | ||||
|  | ||||
| 					// Check for a PIO access | ||||
| 					if(!(address & 0x800)) { | ||||
| 						*cycle.value &= i8255_.get_register((address >> 8) & 3); | ||||
| 						*cycle.value &= i8255_.read((address >> 8) & 3); | ||||
| 					} | ||||
|  | ||||
| 					// Check for an FDC access | ||||
| 					if(has_fdc && (address & 0x580) == 0x100) { | ||||
| 						flush_fdc(); | ||||
| 						*cycle.value &= fdc_.get_register(address & 1); | ||||
| 					if constexpr (has_fdc) { | ||||
| 						if((address & 0x580) == 0x100) { | ||||
| 							flush_fdc(); | ||||
| 							*cycle.value &= fdc_.read(address & 1); | ||||
| 						} | ||||
| 					} | ||||
|  | ||||
| 					// Check for a CRTC access; the below is not a typo, the CRTC can be selected | ||||
| @@ -1055,7 +1073,7 @@ template <bool has_fdc> class ConcreteMachine: | ||||
|  | ||||
| 		// MARK: - Activity Source | ||||
| 		void set_activity_observer(Activity::Observer *observer) override { | ||||
| 			if(has_fdc) fdc_.set_activity_observer(observer); | ||||
| 			if constexpr (has_fdc) fdc_.set_activity_observer(observer); | ||||
| 		} | ||||
|  | ||||
| 		// MARK: - Configuration options. | ||||
| @@ -1083,7 +1101,7 @@ template <bool has_fdc> class ConcreteMachine: | ||||
| 		} | ||||
|  | ||||
| 		// MARK: - Joysticks | ||||
| 		std::vector<std::unique_ptr<Inputs::Joystick>> &get_joysticks() override { | ||||
| 		const std::vector<std::unique_ptr<Inputs::Joystick>> &get_joysticks() override { | ||||
| 			return key_state_.get_joysticks(); | ||||
| 		} | ||||
|  | ||||
| @@ -1145,11 +1163,13 @@ template <bool has_fdc> class ConcreteMachine: | ||||
| 		FDC fdc_; | ||||
| 		HalfCycles time_since_fdc_update_; | ||||
| 		void flush_fdc() { | ||||
| 			// Clock the FDC, if connected, using a lazy scale by two | ||||
| 			if(has_fdc && !fdc_is_sleeping_) { | ||||
| 				fdc_.run_for(Cycles(time_since_fdc_update_.as_int())); | ||||
| 			if constexpr (has_fdc) { | ||||
| 				// Clock the FDC, if connected, using a lazy scale by two | ||||
| 				if(!fdc_is_sleeping_) { | ||||
| 					fdc_.run_for(Cycles(time_since_fdc_update_.as_integral())); | ||||
| 				} | ||||
| 				time_since_fdc_update_ = HalfCycles(0); | ||||
| 			} | ||||
| 			time_since_fdc_update_ = HalfCycles(0); | ||||
| 		} | ||||
|  | ||||
| 		InterruptTimer interrupt_timer_; | ||||
|   | ||||
| @@ -57,19 +57,19 @@ uint16_t KeyboardMapper::mapped_key_for_key(Inputs::Keyboard::Key key) { | ||||
| 		BIND(Left, KeyLeft);	BIND(Right, KeyRight); | ||||
| 		BIND(Up, KeyUp);		BIND(Down, KeyDown); | ||||
|  | ||||
| 		BIND(KeyPad0, KeyF0); | ||||
| 		BIND(KeyPad1, KeyF1);		BIND(KeyPad2, KeyF2);		BIND(KeyPad3, KeyF3); | ||||
| 		BIND(KeyPad4, KeyF4);		BIND(KeyPad5, KeyF5);		BIND(KeyPad6, KeyF6); | ||||
| 		BIND(KeyPad7, KeyF7);		BIND(KeyPad8, KeyF8);		BIND(KeyPad9, KeyF9); | ||||
| 		BIND(KeyPadPlus, KeySemicolon); | ||||
| 		BIND(KeyPadMinus, KeyMinus); | ||||
| 		BIND(Keypad0, KeyF0); | ||||
| 		BIND(Keypad1, KeyF1);		BIND(Keypad2, KeyF2);		BIND(Keypad3, KeyF3); | ||||
| 		BIND(Keypad4, KeyF4);		BIND(Keypad5, KeyF5);		BIND(Keypad6, KeyF6); | ||||
| 		BIND(Keypad7, KeyF7);		BIND(Keypad8, KeyF8);		BIND(Keypad9, KeyF9); | ||||
| 		BIND(KeypadPlus, KeySemicolon); | ||||
| 		BIND(KeypadMinus, KeyMinus); | ||||
|  | ||||
| 		BIND(KeyPadEnter, KeyEnter); | ||||
| 		BIND(KeyPadDecimalPoint, KeyFullStop); | ||||
| 		BIND(KeyPadEquals, KeyMinus); | ||||
| 		BIND(KeyPadSlash, KeyForwardSlash); | ||||
| 		BIND(KeyPadAsterisk, KeyColon); | ||||
| 		BIND(KeyPadDelete, KeyDelete); | ||||
| 		BIND(KeypadEnter, KeyEnter); | ||||
| 		BIND(KeypadDecimalPoint, KeyFullStop); | ||||
| 		BIND(KeypadEquals, KeyMinus); | ||||
| 		BIND(KeypadSlash, KeyForwardSlash); | ||||
| 		BIND(KeypadAsterisk, KeyColon); | ||||
| 		BIND(KeypadDelete, KeyDelete); | ||||
| 	} | ||||
| #undef BIND | ||||
| } | ||||
|   | ||||
| @@ -79,13 +79,15 @@ template <Analyser::Static::AppleII::Target::Model model> class ConcreteMachine: | ||||
| 		void update_video() { | ||||
| 			video_.run_for(cycles_since_video_update_.flush<Cycles>()); | ||||
| 		} | ||||
| 		static const int audio_divider = 8; | ||||
| 		static constexpr int audio_divider = 8; | ||||
| 		void update_audio() { | ||||
| 			speaker_.run_for(audio_queue_, cycles_since_audio_update_.divide(Cycles(audio_divider))); | ||||
| 		} | ||||
| 		void update_just_in_time_cards() { | ||||
| 			for(const auto &card : just_in_time_cards_) { | ||||
| 				card->run_for(cycles_since_card_update_, stretched_cycles_since_card_update_); | ||||
| 			if(cycles_since_card_update_ > Cycles(0)) { | ||||
| 				for(const auto &card : just_in_time_cards_) { | ||||
| 					card->run_for(cycles_since_card_update_, stretched_cycles_since_card_update_); | ||||
| 				} | ||||
| 			} | ||||
| 			cycles_since_card_update_ = 0; | ||||
| 			stretched_cycles_since_card_update_ = 0; | ||||
| @@ -124,19 +126,25 @@ template <Analyser::Static::AppleII::Target::Model model> class ConcreteMachine: | ||||
| 			pick_card_messaging_group(card); | ||||
| 		} | ||||
|  | ||||
| 		bool is_every_cycle_card(Apple::II::Card *card) { | ||||
| 		bool is_every_cycle_card(const Apple::II::Card *card) { | ||||
| 			return !card->get_select_constraints(); | ||||
| 		} | ||||
|  | ||||
| 		bool card_lists_are_dirty_ = true; | ||||
| 		bool card_became_just_in_time_ = false; | ||||
| 		void pick_card_messaging_group(Apple::II::Card *card) { | ||||
| 			// Simplify to a card being either just-in-time or realtime. | ||||
| 			// Don't worry about exactly what it's watching, | ||||
| 			const bool is_every_cycle = is_every_cycle_card(card); | ||||
| 			std::vector<Apple::II::Card *> &intended = is_every_cycle ? every_cycle_cards_ : just_in_time_cards_; | ||||
| 			std::vector<Apple::II::Card *> &undesired = is_every_cycle ? just_in_time_cards_ : every_cycle_cards_; | ||||
|  | ||||
| 			// If the card is already in the proper group, stop. | ||||
| 			if(std::find(intended.begin(), intended.end(), card) != intended.end()) return; | ||||
| 			auto old_membership = std::find(undesired.begin(), undesired.end(), card); | ||||
| 			if(old_membership != undesired.end()) undesired.erase(old_membership); | ||||
| 			intended.push_back(card); | ||||
|  | ||||
| 			// Otherwise, mark the sets as dirty. It isn't safe to transition the card here, | ||||
| 			// as the main loop may be part way through iterating the two lists. | ||||
| 			card_lists_are_dirty_ = true; | ||||
| 			card_became_just_in_time_ |= !is_every_cycle; | ||||
| 		} | ||||
|  | ||||
| 		void card_did_change_select_constraints(Apple::II::Card *card) override { | ||||
| @@ -321,7 +329,7 @@ template <Analyser::Static::AppleII::Target::Model model> class ConcreteMachine: | ||||
| 			audio_toggle_(audio_queue_), | ||||
| 			speaker_(audio_toggle_) { | ||||
| 			// The system's master clock rate. | ||||
| 			const float master_clock = 14318180.0; | ||||
| 			constexpr float master_clock = 14318180.0; | ||||
|  | ||||
| 			// This is where things get slightly convoluted: establish the machine as having a clock rate | ||||
| 			// equal to the number of cycles of work the 6502 will actually achieve. Which is less than | ||||
| @@ -753,6 +761,31 @@ template <Analyser::Static::AppleII::Target::Model model> class ConcreteMachine: | ||||
| 				} | ||||
| 			} | ||||
|  | ||||
| 			// Update the card lists if any mutations are due. | ||||
| 			if(card_lists_are_dirty_) { | ||||
| 				card_lists_are_dirty_ = false; | ||||
|  | ||||
| 				// There's only one counter of time since update | ||||
| 				// for just-in-time cards. If something new is | ||||
| 				// transitioning, that needs to be zeroed. | ||||
| 				if(card_became_just_in_time_) { | ||||
| 					card_became_just_in_time_ = false; | ||||
| 					update_just_in_time_cards(); | ||||
| 				} | ||||
|  | ||||
| 				// Clear the two lists and repopulate. | ||||
| 				every_cycle_cards_.clear(); | ||||
| 				just_in_time_cards_.clear(); | ||||
| 				for(const auto &card: cards_) { | ||||
| 					if(!card) continue; | ||||
| 					if(is_every_cycle_card(card.get())) { | ||||
| 						every_cycle_cards_.push_back(card.get()); | ||||
| 					} else { | ||||
| 						just_in_time_cards_.push_back(card.get()); | ||||
| 					} | ||||
| 				} | ||||
| 			} | ||||
|  | ||||
| 			// Update analogue charge level. | ||||
| 			analogue_charge_ = std::min(analogue_charge_ + 1.0f / 2820.0f, 1.1f); | ||||
|  | ||||
| @@ -818,7 +851,7 @@ template <Analyser::Static::AppleII::Target::Model model> class ConcreteMachine: | ||||
| 		} | ||||
|  | ||||
| 		void type_string(const std::string &string) override { | ||||
| 			string_serialiser_.reset(new Utility::StringSerialiser(string, true)); | ||||
| 			string_serialiser_ = std::make_unique<Utility::StringSerialiser>(string, true); | ||||
| 		} | ||||
|  | ||||
| 		// MARK:: Configuration options. | ||||
| @@ -860,7 +893,7 @@ template <Analyser::Static::AppleII::Target::Model model> class ConcreteMachine: | ||||
| 		} | ||||
|  | ||||
| 		// MARK: JoystickMachine | ||||
| 		std::vector<std::unique_ptr<Inputs::Joystick>> &get_joysticks() override { | ||||
| 		const std::vector<std::unique_ptr<Inputs::Joystick>> &get_joysticks() override { | ||||
| 			return joysticks_; | ||||
| 		} | ||||
| }; | ||||
|   | ||||
| @@ -83,10 +83,8 @@ class Card { | ||||
| 			will receive a perform_bus_operation every cycle. To reduce the number of | ||||
| 			virtual method calls, they **will not** receive run_for. run_for will propagate | ||||
| 			only to cards that register for IO and/or Device accesses only. | ||||
|  | ||||
|  | ||||
| 		*/ | ||||
| 		int get_select_constraints() { | ||||
| 		int get_select_constraints() const { | ||||
| 			return select_constraints_; | ||||
| 		} | ||||
|  | ||||
|   | ||||
| @@ -52,7 +52,7 @@ void DiskIICard::perform_bus_operation(Select select, bool is_read, uint16_t add | ||||
|  | ||||
| void DiskIICard::run_for(Cycles cycles, int stretches) { | ||||
| 	if(diskii_clocking_preference_ == ClockingHint::Preference::None) return; | ||||
| 	diskii_.run_for(Cycles(cycles.as_int() * 2)); | ||||
| 	diskii_.run_for(Cycles(cycles.as_integral() * 2)); | ||||
| } | ||||
|  | ||||
| void DiskIICard::set_disk(const std::shared_ptr<Storage::Disk::Disk> &disk, int drive) { | ||||
| @@ -65,7 +65,7 @@ void DiskIICard::set_activity_observer(Activity::Observer *observer) { | ||||
|  | ||||
| void DiskIICard::set_component_prefers_clocking(ClockingHint::Source *component, ClockingHint::Preference preference) { | ||||
| 	diskii_clocking_preference_ = preference; | ||||
| 	set_select_constraints((preference != ClockingHint::Preference::RealTime) ? (IO | Device) : 0); | ||||
| 	set_select_constraints((preference != ClockingHint::Preference::RealTime) ? (IO | Device) : None); | ||||
| } | ||||
|  | ||||
| Storage::Disk::Drive &DiskIICard::get_drive(int drive) { | ||||
|   | ||||
| @@ -203,7 +203,7 @@ class VideoBase { | ||||
| 		std::array<uint8_t, 40> auxiliary_stream_; | ||||
|  | ||||
| 		bool is_iie_ = false; | ||||
| 		static const int flash_length = 8406; | ||||
| 		static constexpr int flash_length = 8406; | ||||
|  | ||||
| 		// Describes the current text mode mapping from in-memory character index | ||||
| 		// to output character. | ||||
| @@ -284,7 +284,7 @@ template <class BusHandler, bool is_iie> class Video: public VideoBase { | ||||
| 			// Source: Have an Apple Split by Bob Bishop; http://rich12345.tripod.com/aiivideo/softalk.html | ||||
|  | ||||
| 			// Determine column at offset. | ||||
| 			int mapped_column = column_ + offset.as_int(); | ||||
| 			int mapped_column = column_ + int(offset.as_integral()); | ||||
|  | ||||
| 			// Map that backwards from the internal pixels-at-start generation to pixels-at-end | ||||
| 			// (so what was column 0 is now column 25). | ||||
| @@ -315,7 +315,7 @@ template <class BusHandler, bool is_iie> class Video: public VideoBase { | ||||
| 		bool get_is_vertical_blank(Cycles offset) { | ||||
| 			// Map that backwards from the internal pixels-at-start generation to pixels-at-end | ||||
| 			// (so what was column 0 is now column 25). | ||||
| 			int mapped_column = column_ + offset.as_int(); | ||||
| 			int mapped_column = column_ + int(offset.as_integral()); | ||||
|  | ||||
| 			// Map that backwards from the internal pixels-at-start generation to pixels-at-end | ||||
| 			// (so what was column 0 is now column 25). | ||||
| @@ -339,11 +339,11 @@ template <class BusHandler, bool is_iie> class Video: public VideoBase { | ||||
|  | ||||
| 				A frame is oriented around 65 cycles across, 262 lines down. | ||||
| 			*/ | ||||
| 			static const int first_sync_line = 220;		// A complete guess. Information needed. | ||||
| 			static const int first_sync_column = 49;	// Also a guess. | ||||
| 			static const int sync_length = 4;			// One of the two likely candidates. | ||||
| 			constexpr int first_sync_line = 220;		// A complete guess. Information needed. | ||||
| 			constexpr int first_sync_column = 49;	// Also a guess. | ||||
| 			constexpr int sync_length = 4;			// One of the two likely candidates. | ||||
|  | ||||
| 			int int_cycles = cycles.as_int(); | ||||
| 			int int_cycles = int(cycles.as_integral()); | ||||
| 			while(int_cycles) { | ||||
| 				const int cycles_this_line = std::min(65 - column_, int_cycles); | ||||
| 				const int ending_column = column_ + cycles_this_line; | ||||
|   | ||||
| @@ -11,7 +11,7 @@ | ||||
| using namespace Apple::Macintosh; | ||||
|  | ||||
| void DriveSpeedAccumulator::post_sample(uint8_t sample) { | ||||
| 	if(!number_of_drives_) return; | ||||
| 	if(!delegate_) return; | ||||
|  | ||||
| 	// An Euler-esque approximation is used here: just collect all | ||||
| 	// the samples until there is a certain small quantity of them, | ||||
| @@ -50,14 +50,7 @@ void DriveSpeedAccumulator::post_sample(uint8_t sample) { | ||||
| 		const float normalised_sum = float(sum) / float(samples_.size()); | ||||
| 		const float rotation_speed = (normalised_sum * 27.08f) - 259.0f; | ||||
|  | ||||
| 		for(int c = 0; c < number_of_drives_; ++c) { | ||||
| 			drives_[c]->set_rotation_speed(rotation_speed); | ||||
| 		} | ||||
| //		printf("RPM: %0.2f (%d sum)\n", rotation_speed, sum); | ||||
| 		delegate_->drive_speed_accumulator_set_drive_speed(this, rotation_speed); | ||||
| 	} | ||||
| } | ||||
|  | ||||
| void DriveSpeedAccumulator::add_drive(Apple::Macintosh::DoubleDensityDrive *drive) { | ||||
| 	drives_[number_of_drives_] = drive; | ||||
| 	++number_of_drives_; | ||||
| } | ||||
|   | ||||
| @@ -13,8 +13,6 @@ | ||||
| #include <cstddef> | ||||
| #include <cstdint> | ||||
|  | ||||
| #include "../../../Components/DiskII/MacintoshDoubleDensityDrive.hpp" | ||||
|  | ||||
| namespace Apple { | ||||
| namespace Macintosh { | ||||
|  | ||||
| @@ -25,18 +23,20 @@ class DriveSpeedAccumulator { | ||||
| 		*/ | ||||
| 		void post_sample(uint8_t sample); | ||||
|  | ||||
| 		struct Delegate { | ||||
| 			virtual void drive_speed_accumulator_set_drive_speed(DriveSpeedAccumulator *, float speed) = 0; | ||||
| 		}; | ||||
| 		/*! | ||||
| 			Adds a connected drive. Up to two of these | ||||
| 			can be supplied. Only Macintosh DoubleDensityDrives | ||||
| 			are supported. | ||||
| 			Sets the delegate to receive drive speed changes. | ||||
| 		*/ | ||||
| 		void add_drive(Apple::Macintosh::DoubleDensityDrive *drive); | ||||
| 		void set_delegate(Delegate *delegate) { | ||||
| 			delegate_ = delegate;; | ||||
| 		} | ||||
|  | ||||
| 	private: | ||||
| 		std::array<uint8_t, 20> samples_; | ||||
| 		std::size_t sample_pointer_ = 0; | ||||
| 		Apple::Macintosh::DoubleDensityDrive *drives_[2] = {nullptr, nullptr}; | ||||
| 		int number_of_drives_ = 0; | ||||
| 		Delegate *delegate_ = nullptr; | ||||
| }; | ||||
|  | ||||
| } | ||||
|   | ||||
| @@ -63,25 +63,25 @@ uint16_t KeyboardMapper::mapped_key_for_key(Inputs::Keyboard::Key key) { | ||||
| 		Bind(Space, MacKey::Space); | ||||
| 		Bind(Backslash, MacKey::Backslash); | ||||
|  | ||||
| 		Bind(KeyPadDelete, MacKey::KeyPadDelete); | ||||
| 		Bind(KeyPadEquals, MacKey::KeyPadEquals); | ||||
| 		Bind(KeyPadSlash, MacKey::KeyPadSlash); | ||||
| 		Bind(KeyPadAsterisk, MacKey::KeyPadAsterisk); | ||||
| 		Bind(KeyPadMinus, MacKey::KeyPadMinus); | ||||
| 		Bind(KeyPadPlus, MacKey::KeyPadPlus); | ||||
| 		Bind(KeyPadEnter, MacKey::KeyPadEnter); | ||||
| 		Bind(KeyPadDecimalPoint, MacKey::KeyPadDecimalPoint); | ||||
| 		Bind(KeypadDelete, MacKey::KeypadDelete); | ||||
| 		Bind(KeypadEquals, MacKey::KeypadEquals); | ||||
| 		Bind(KeypadSlash, MacKey::KeypadSlash); | ||||
| 		Bind(KeypadAsterisk, MacKey::KeypadAsterisk); | ||||
| 		Bind(KeypadMinus, MacKey::KeypadMinus); | ||||
| 		Bind(KeypadPlus, MacKey::KeypadPlus); | ||||
| 		Bind(KeypadEnter, MacKey::KeypadEnter); | ||||
| 		Bind(KeypadDecimalPoint, MacKey::KeypadDecimalPoint); | ||||
|  | ||||
| 		Bind(KeyPad9, MacKey::KeyPad9); | ||||
| 		Bind(KeyPad8, MacKey::KeyPad8); | ||||
| 		Bind(KeyPad7, MacKey::KeyPad7); | ||||
| 		Bind(KeyPad6, MacKey::KeyPad6); | ||||
| 		Bind(KeyPad5, MacKey::KeyPad5); | ||||
| 		Bind(KeyPad4, MacKey::KeyPad4); | ||||
| 		Bind(KeyPad3, MacKey::KeyPad3); | ||||
| 		Bind(KeyPad2, MacKey::KeyPad2); | ||||
| 		Bind(KeyPad1, MacKey::KeyPad1); | ||||
| 		Bind(KeyPad0, MacKey::KeyPad0); | ||||
| 		Bind(Keypad9, MacKey::Keypad9); | ||||
| 		Bind(Keypad8, MacKey::Keypad8); | ||||
| 		Bind(Keypad7, MacKey::Keypad7); | ||||
| 		Bind(Keypad6, MacKey::Keypad6); | ||||
| 		Bind(Keypad5, MacKey::Keypad5); | ||||
| 		Bind(Keypad4, MacKey::Keypad4); | ||||
| 		Bind(Keypad3, MacKey::Keypad3); | ||||
| 		Bind(Keypad2, MacKey::Keypad2); | ||||
| 		Bind(Keypad1, MacKey::Keypad1); | ||||
| 		Bind(Keypad0, MacKey::Keypad0); | ||||
|  | ||||
| #undef Bind | ||||
| 	} | ||||
|   | ||||
| @@ -18,7 +18,7 @@ | ||||
| namespace Apple { | ||||
| namespace Macintosh { | ||||
|  | ||||
| static const uint16_t KeypadMask = 0x100; | ||||
| constexpr uint16_t KeypadMask = 0x100; | ||||
|  | ||||
| /*! | ||||
| 	Defines the keycodes that could be passed directly to a Macintosh via set_key_pressed. | ||||
| @@ -63,25 +63,25 @@ enum class Key: uint16_t { | ||||
| 	Up = KeypadMask | 0x1b, | ||||
| 	Down = KeypadMask | 0x11, | ||||
|  | ||||
| 	KeyPadDelete = KeypadMask | 0x0f, | ||||
| 	KeyPadEquals = KeypadMask | 0x11, | ||||
| 	KeyPadSlash = KeypadMask | 0x1b, | ||||
| 	KeyPadAsterisk = KeypadMask | 0x05, | ||||
| 	KeyPadMinus = KeypadMask | 0x1d, | ||||
| 	KeyPadPlus = KeypadMask | 0x0d, | ||||
| 	KeyPadEnter = KeypadMask | 0x19, | ||||
| 	KeyPadDecimalPoint = KeypadMask | 0x03, | ||||
| 	KeypadDelete = KeypadMask | 0x0f, | ||||
| 	KeypadEquals = KeypadMask | 0x11, | ||||
| 	KeypadSlash = KeypadMask | 0x1b, | ||||
| 	KeypadAsterisk = KeypadMask | 0x05, | ||||
| 	KeypadMinus = KeypadMask | 0x1d, | ||||
| 	KeypadPlus = KeypadMask | 0x0d, | ||||
| 	KeypadEnter = KeypadMask | 0x19, | ||||
| 	KeypadDecimalPoint = KeypadMask | 0x03, | ||||
|  | ||||
| 	KeyPad9 = KeypadMask | 0x39, | ||||
| 	KeyPad8 = KeypadMask | 0x37, | ||||
| 	KeyPad7 = KeypadMask | 0x33, | ||||
| 	KeyPad6 = KeypadMask | 0x31, | ||||
| 	KeyPad5 = KeypadMask | 0x2f, | ||||
| 	KeyPad4 = KeypadMask | 0x2d, | ||||
| 	KeyPad3 = KeypadMask | 0x2b, | ||||
| 	KeyPad2 = KeypadMask | 0x29, | ||||
| 	KeyPad1 = KeypadMask | 0x27, | ||||
| 	KeyPad0 = KeypadMask | 0x25 | ||||
| 	Keypad9 = KeypadMask | 0x39, | ||||
| 	Keypad8 = KeypadMask | 0x37, | ||||
| 	Keypad7 = KeypadMask | 0x33, | ||||
| 	Keypad6 = KeypadMask | 0x31, | ||||
| 	Keypad5 = KeypadMask | 0x2f, | ||||
| 	Keypad4 = KeypadMask | 0x2d, | ||||
| 	Keypad3 = KeypadMask | 0x2b, | ||||
| 	Keypad2 = KeypadMask | 0x29, | ||||
| 	Keypad1 = KeypadMask | 0x27, | ||||
| 	Keypad0 = KeypadMask | 0x25 | ||||
| }; | ||||
|  | ||||
| class Keyboard { | ||||
| @@ -291,7 +291,7 @@ class Keyboard { | ||||
| 	Provides a mapping from idiomatic PC keys to Macintosh keys. | ||||
| */ | ||||
| class KeyboardMapper: public KeyboardMachine::MappedMachine::KeyboardMapper { | ||||
| 	uint16_t mapped_key_for_key(Inputs::Keyboard::Key key) override; | ||||
| 	uint16_t mapped_key_for_key(Inputs::Keyboard::Key key) final; | ||||
| }; | ||||
|  | ||||
| } | ||||
|   | ||||
| @@ -26,15 +26,22 @@ | ||||
| #include "../../../Outputs/Log.hpp" | ||||
|  | ||||
| #include "../../../ClockReceiver/JustInTime.hpp" | ||||
| #include "../../../ClockReceiver/ClockingHintSource.hpp" | ||||
| #include "../../../Configurable/StandardOptions.hpp" | ||||
|  | ||||
| //#define LOG_TRACE | ||||
|  | ||||
| #include "../../../Components/5380/ncr5380.hpp" | ||||
| #include "../../../Components/6522/6522.hpp" | ||||
| #include "../../../Components/8530/z8530.hpp" | ||||
| #include "../../../Components/DiskII/IWM.hpp" | ||||
| #include "../../../Components/DiskII/MacintoshDoubleDensityDrive.hpp" | ||||
| #include "../../../Processors/68000/68000.hpp" | ||||
|  | ||||
| #include "../../../Storage/MassStorage/SCSI/SCSI.hpp" | ||||
| #include "../../../Storage/MassStorage/SCSI/DirectAccessDevice.hpp" | ||||
| #include "../../../Storage/MassStorage/Encodings/MacintoshVolume.hpp" | ||||
|  | ||||
| #include "../../../Analyser/Static/Macintosh/Target.hpp" | ||||
|  | ||||
| #include "../../Utility/MemoryPacker.hpp" | ||||
| @@ -42,13 +49,19 @@ | ||||
|  | ||||
| namespace { | ||||
|  | ||||
| const int CLOCK_RATE = 7833600; | ||||
| constexpr int CLOCK_RATE = 7833600; | ||||
|  | ||||
| } | ||||
|  | ||||
| namespace Apple { | ||||
| namespace Macintosh { | ||||
|  | ||||
| std::vector<std::unique_ptr<Configurable::Option>> get_options() { | ||||
| 	return Configurable::standard_options( | ||||
| 		static_cast<Configurable::StandardOptions>(Configurable::QuickBoot) | ||||
| 	); | ||||
| } | ||||
|  | ||||
| template <Analyser::Static::Macintosh::Target::Model model> class ConcreteMachine: | ||||
| 	public Machine, | ||||
| 	public CRTMachine::Machine, | ||||
| @@ -57,16 +70,27 @@ template <Analyser::Static::Macintosh::Target::Model model> class ConcreteMachin | ||||
| 	public CPU::MC68000::BusHandler, | ||||
| 	public KeyboardMachine::MappedMachine, | ||||
| 	public Zilog::SCC::z8530::Delegate, | ||||
| 	public Activity::Source { | ||||
| 	public Activity::Source, | ||||
| 	public Configurable::Device, | ||||
| 	public DriveSpeedAccumulator::Delegate, | ||||
| 	public ClockingHint::Observer { | ||||
| 	public: | ||||
| 		using Target = Analyser::Static::Macintosh::Target; | ||||
|  | ||||
| 		ConcreteMachine(const Target &target, const ROMMachine::ROMFetcher &rom_fetcher) : | ||||
| 			KeyboardMachine::MappedMachine({ | ||||
| 				Inputs::Keyboard::Key::LeftShift, Inputs::Keyboard::Key::RightShift, | ||||
| 				Inputs::Keyboard::Key::LeftOption, Inputs::Keyboard::Key::RightOption, | ||||
| 				Inputs::Keyboard::Key::LeftMeta, Inputs::Keyboard::Key::RightMeta, | ||||
| 			}), | ||||
| 		 	mc68000_(*this), | ||||
| 		 	iwm_(CLOCK_RATE), | ||||
| 		 	video_(ram_, audio_, drive_speed_accumulator_), | ||||
| 		 	video_(audio_, drive_speed_accumulator_), | ||||
| 		 	via_(via_port_handler_), | ||||
| 		 	via_port_handler_(*this, clock_, keyboard_, video_, audio_, iwm_, mouse_), | ||||
| 		 	via_port_handler_(*this, clock_, keyboard_, audio_, iwm_, mouse_), | ||||
| 		 	scsi_bus_(CLOCK_RATE * 2), | ||||
| 		 	scsi_(scsi_bus_, CLOCK_RATE * 2), | ||||
| 		 	hard_drive_(scsi_bus_, 6 /* SCSI ID */), | ||||
| 		 	drives_{ | ||||
| 		 		{CLOCK_RATE, model >= Analyser::Static::Macintosh::Target::Model::Mac512ke}, | ||||
| 		 		{CLOCK_RATE, model >= Analyser::Static::Macintosh::Target::Model::Mac512ke} | ||||
| @@ -93,7 +117,7 @@ template <Analyser::Static::Macintosh::Target::Model model> class ConcreteMachin | ||||
| 				break; | ||||
| 				case Model::Mac512ke: | ||||
| 				case Model::MacPlus: { | ||||
| 					ram_size = 512*1024; | ||||
| 					ram_size = ((model == Model::MacPlus) ? 4096 : 512)*1024; | ||||
| 					rom_size = 128*1024; | ||||
| 					const std::initializer_list<uint32_t> crc32s = { 0x4fa5b399, 0x7cacd18f, 0xb2102e8e }; | ||||
| 					rom_descriptions.emplace_back(machine_name, "the Macintosh Plus ROM", "macplus.rom", 128*1024, crc32s); | ||||
| @@ -101,7 +125,8 @@ template <Analyser::Static::Macintosh::Target::Model model> class ConcreteMachin | ||||
| 			} | ||||
| 			ram_mask_ = (ram_size >> 1) - 1; | ||||
| 			rom_mask_ = (rom_size >> 1) - 1; | ||||
| 			video_.set_ram_mask(ram_mask_); | ||||
| 			ram_.resize(ram_size >> 1); | ||||
| 			video_.set_ram(ram_.data(), ram_mask_); | ||||
|  | ||||
| 			// Grab a copy of the ROM and convert it into big-endian data. | ||||
| 			const auto roms = rom_fetcher(rom_descriptions); | ||||
| @@ -112,19 +137,25 @@ template <Analyser::Static::Macintosh::Target::Model model> class ConcreteMachin | ||||
| 			Memory::PackBigEndian16(*roms[0], rom_); | ||||
|  | ||||
| 			// Randomise memory contents. | ||||
| 			Memory::Fuzz(ram_, sizeof(ram_) / sizeof(*ram_)); | ||||
| 			Memory::Fuzz(ram_); | ||||
|  | ||||
| 			// Attach the drives to the IWM. | ||||
| 			iwm_.iwm.set_drive(0, &drives_[0]); | ||||
| 			iwm_.iwm.set_drive(1, &drives_[1]); | ||||
| 			iwm_->set_drive(0, &drives_[0]); | ||||
| 			iwm_->set_drive(1, &drives_[1]); | ||||
|  | ||||
| 			// If they are 400kb drives, also attach them to the drive-speed accumulator. | ||||
| 			if(!drives_[0].is_800k()) drive_speed_accumulator_.add_drive(&drives_[0]); | ||||
| 			if(!drives_[1].is_800k()) drive_speed_accumulator_.add_drive(&drives_[1]); | ||||
| 			if(!drives_[0].is_800k() || !drives_[1].is_800k()) { | ||||
| 				drive_speed_accumulator_.set_delegate(this); | ||||
| 			} | ||||
|  | ||||
| 			// Make sure interrupt changes from the SCC are observed. | ||||
| 			scc_.set_delegate(this); | ||||
|  | ||||
| 			// Also watch for changes in clocking requirement from the SCSI chip. | ||||
| 			if constexpr (model == Analyser::Static::Macintosh::Target::Model::MacPlus) { | ||||
| 				scsi_bus_.set_clocking_hint_observer(this); | ||||
| 			} | ||||
|  | ||||
| 			// The Mac runs at 7.8336mHz. | ||||
| 			set_clock_rate(double(CLOCK_RATE)); | ||||
| 			audio_.speaker.set_input_rate(float(CLOCK_RATE) / 2.0f); | ||||
| @@ -180,7 +211,7 @@ template <Analyser::Static::Macintosh::Target::Model model> class ConcreteMachin | ||||
| 			// Grab the word-precision address being accessed. | ||||
| 			uint16_t *memory_base = nullptr; | ||||
| 			HalfCycles delay; | ||||
| 			switch(memory_map_[word_address >> 18]) { | ||||
| 			switch(memory_map_[word_address >> 16]) { | ||||
| 				default: assert(false); | ||||
|  | ||||
| 				case BusDevice::Unassigned: | ||||
| @@ -196,9 +227,9 @@ template <Analyser::Static::Macintosh::Target::Model model> class ConcreteMachin | ||||
| 						// VIA accesses are via address 0xefe1fe + register*512, | ||||
| 						// which at word precision is 0x77f0ff + register*256. | ||||
| 						if(cycle.operation & Microcycle::Read) { | ||||
| 							cycle.value->halves.low = via_.get_register(register_address); | ||||
| 							cycle.value->halves.low = via_.read(register_address); | ||||
| 						} else { | ||||
| 							via_.set_register(register_address, cycle.value->halves.low); | ||||
| 							via_.write(register_address, cycle.value->halves.low); | ||||
| 						} | ||||
|  | ||||
| 						if(cycle.operation & Microcycle::SelectWord) cycle.value->halves.high = 0xff; | ||||
| @@ -218,11 +249,10 @@ template <Analyser::Static::Macintosh::Target::Model model> class ConcreteMachin | ||||
| 						const int register_address = word_address >> 8; | ||||
|  | ||||
| 						// The IWM; this is a purely polled device, so can be run on demand. | ||||
| 						iwm_.flush(); | ||||
| 						if(cycle.operation & Microcycle::Read) { | ||||
| 							cycle.value->halves.low = iwm_.iwm.read(register_address); | ||||
| 							cycle.value->halves.low = iwm_->read(register_address); | ||||
| 						} else { | ||||
| 							iwm_.iwm.write(register_address, cycle.value->halves.low); | ||||
| 							iwm_->write(register_address, cycle.value->halves.low); | ||||
| 						} | ||||
|  | ||||
| 						if(cycle.operation & Microcycle::SelectWord) cycle.value->halves.high = 0xff; | ||||
| @@ -231,6 +261,36 @@ template <Analyser::Static::Macintosh::Target::Model model> class ConcreteMachin | ||||
| 					} | ||||
| 				} return delay; | ||||
|  | ||||
| 				case BusDevice::SCSI: { | ||||
| 					const int register_address = word_address >> 3; | ||||
| 					const bool dma_acknowledge = word_address & 0x100; | ||||
|  | ||||
| 					// Even accesses = read; odd = write. | ||||
| 					if(*cycle.address & 1) { | ||||
| 						// Odd access => this is a write. Data will be in the upper byte. | ||||
| 						if(cycle.operation & Microcycle::Read) { | ||||
| 							scsi_.write(register_address, 0xff, dma_acknowledge); | ||||
| 						} else { | ||||
| 							if(cycle.operation & Microcycle::SelectWord) { | ||||
| 								scsi_.write(register_address, cycle.value->halves.high, dma_acknowledge); | ||||
| 							} else { | ||||
| 								scsi_.write(register_address, cycle.value->halves.low, dma_acknowledge); | ||||
| 							} | ||||
| 						} | ||||
| 					} else { | ||||
| 						// Even access => this is a read. | ||||
| 						if(cycle.operation & Microcycle::Read) { | ||||
| 							const auto result = scsi_.read(register_address, dma_acknowledge); | ||||
| 							if(cycle.operation & Microcycle::SelectWord) { | ||||
| 								// Data is loaded on the top part of the bus only. | ||||
| 								cycle.value->full = uint16_t((result << 8) | 0xff); | ||||
| 							} else { | ||||
| 								cycle.value->halves.low = result; | ||||
| 							} | ||||
| 						} | ||||
| 					} | ||||
| 				} return delay; | ||||
|  | ||||
| 				case BusDevice::SCCReadResetPhase: { | ||||
| 					// Any word access here adjusts phase. | ||||
| 					if(cycle.operation & Microcycle::SelectWord) { | ||||
| @@ -279,15 +339,15 @@ template <Analyser::Static::Macintosh::Target::Model model> class ConcreteMachin | ||||
| 					if(word_address > ram_mask_ - 0x6c80) | ||||
| 						update_video(); | ||||
|  | ||||
| 					memory_base = ram_; | ||||
| 					memory_base = ram_.data(); | ||||
| 					word_address &= ram_mask_; | ||||
|  | ||||
| 					// Apply a delay due to video contention if applicable; technically this is | ||||
| 					// incorrectly placed — strictly speaking here I'm extending the part of the | ||||
| 					// bus cycle after DTACK rather than delaying DTACK. But it adds up to the | ||||
| 					// same thing. | ||||
| 					if(ram_subcycle_ < 4) { | ||||
| 						delay = HalfCycles(4 - ram_subcycle_); | ||||
| 					// Apply a delay due to video contention if applicable; scheme applied: | ||||
| 					// only every other access slot is available during the period of video | ||||
| 					// output. I believe this to be correct for the 128k, 512k and Plus. | ||||
| 					// More research to do on other models. | ||||
| 					if(video_is_outputting() && ram_subcycle_ < 8) { | ||||
| 						delay = HalfCycles(8 - ram_subcycle_); | ||||
| 						advance_time(delay); | ||||
| 					} | ||||
| 				} break; | ||||
| @@ -347,12 +407,12 @@ template <Analyser::Static::Macintosh::Target::Model model> class ConcreteMachin | ||||
| 				case Model::Mac128k: | ||||
| 				case Model::Mac512k: | ||||
| 				case Model::Mac512ke: | ||||
| 					populate_memory_map([rom_is_overlay] (std::function<void(int target, BusDevice device)> map_to) { | ||||
| 					populate_memory_map(0, [rom_is_overlay] (std::function<void(int target, BusDevice device)> map_to) { | ||||
| 						// Addresses up to $80 0000 aren't affected by this bit. | ||||
| 						if(rom_is_overlay) { | ||||
| 							// Up to $60 0000 mirrors of the ROM alternate with unassigned areas every $10 0000 byes. | ||||
| 							for(int c = 0; c <= 0x600000; c += 0x100000) { | ||||
| 								map_to(c, ((c >> 20)&1) ? BusDevice::ROM : BusDevice::Unassigned); | ||||
| 							for(int c = 0; c < 0x600000; c += 0x100000) { | ||||
| 								map_to(c + 0x100000, (c & 0x100000) ? BusDevice::Unassigned : BusDevice::ROM); | ||||
| 							} | ||||
| 							map_to(0x800000, BusDevice::RAM); | ||||
| 						} else { | ||||
| @@ -364,19 +424,19 @@ template <Analyser::Static::Macintosh::Target::Model model> class ConcreteMachin | ||||
| 				break; | ||||
|  | ||||
| 				case Model::MacPlus: | ||||
| 					populate_memory_map([rom_is_overlay] (std::function<void(int target, BusDevice device)> map_to) { | ||||
| 					populate_memory_map(0, [rom_is_overlay] (std::function<void(int target, BusDevice device)> map_to) { | ||||
| 						// Addresses up to $80 0000 aren't affected by this bit. | ||||
| 						if(rom_is_overlay) { | ||||
| 							map_to(0x100000, BusDevice::ROM); | ||||
| 							map_to(0x400000, BusDevice::Unassigned); | ||||
| 							map_to(0x500000, BusDevice::ROM); | ||||
| 							map_to(0x580000, BusDevice::Unassigned); | ||||
| 							for(int c = 0; c < 0x580000; c += 0x20000) { | ||||
| 								map_to(c + 0x20000, ((c & 0x100000) || (c & 0x20000)) ? BusDevice::Unassigned : BusDevice::ROM); | ||||
| 							} | ||||
| 							map_to(0x600000, BusDevice::SCSI); | ||||
| 							map_to(0x800000, BusDevice::RAM); | ||||
| 						} else { | ||||
| 							map_to(0x400000, BusDevice::RAM); | ||||
| 							map_to(0x500000, BusDevice::ROM); | ||||
| 							map_to(0x580000, BusDevice::Unassigned); | ||||
| 							for(int c = 0x400000; c < 0x580000; c += 0x20000) { | ||||
| 								map_to(c + 0x20000, ((c & 0x100000) || (c & 0x20000)) ? BusDevice::Unassigned : BusDevice::ROM); | ||||
| 							} | ||||
| 							map_to(0x600000, BusDevice::SCSI); | ||||
| 							map_to(0x800000, BusDevice::Unassigned); | ||||
| 						} | ||||
| @@ -395,16 +455,27 @@ template <Analyser::Static::Macintosh::Target::Model model> class ConcreteMachin | ||||
| 		} | ||||
|  | ||||
| 		bool insert_media(const Analyser::Static::Media &media) override { | ||||
| 			if(media.disks.empty()) | ||||
| 			if(media.disks.empty() && media.mass_storage_devices.empty()) | ||||
| 				return false; | ||||
|  | ||||
| 			// TODO: shouldn't allow disks to be replaced like this, as the Mac | ||||
| 			// uses software eject. Will need to expand messaging ability of | ||||
| 			// insert_media. | ||||
| 			if(drives_[0].has_disk()) | ||||
| 				drives_[1].set_disk(media.disks[0]); | ||||
| 			else | ||||
| 				drives_[0].set_disk(media.disks[0]); | ||||
| 			if(!media.disks.empty()) { | ||||
| 				if(drives_[0].has_disk()) | ||||
| 					drives_[1].set_disk(media.disks[0]); | ||||
| 				else | ||||
| 					drives_[0].set_disk(media.disks[0]); | ||||
| 			} | ||||
|  | ||||
| 			// TODO: allow this only at machine startup. | ||||
| 			if(!media.mass_storage_devices.empty()) { | ||||
| 				const auto volume = dynamic_cast<Storage::MassStorage::Encodings::Macintosh::Volume *>(media.mass_storage_devices.front().get()); | ||||
| 				if(volume) { | ||||
| 					volume->set_drive_type(Storage::MassStorage::Encodings::Macintosh::DriveType::SCSI); | ||||
| 				} | ||||
| 				hard_drive_->set_storage(media.mass_storage_devices.front()); | ||||
| 			} | ||||
|  | ||||
| 			return true; | ||||
| 		} | ||||
| @@ -441,10 +512,53 @@ template <Analyser::Static::Macintosh::Target::Model model> class ConcreteMachin | ||||
|  | ||||
| 		// MARK: - Activity Source | ||||
| 		void set_activity_observer(Activity::Observer *observer) override { | ||||
| 			iwm_.iwm.set_activity_observer(observer); | ||||
| 			iwm_->set_activity_observer(observer); | ||||
|  | ||||
| 			if constexpr (model == Analyser::Static::Macintosh::Target::Model::MacPlus) { | ||||
| 				scsi_bus_.set_activity_observer(observer); | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		// MARK: - Configuration options. | ||||
| 		std::vector<std::unique_ptr<Configurable::Option>> get_options() override { | ||||
| 			return Apple::Macintosh::get_options(); | ||||
| 		} | ||||
|  | ||||
| 		void set_selections(const Configurable::SelectionSet &selections_by_option) override { | ||||
| 			bool quick_boot; | ||||
| 			if(Configurable::get_quick_boot(selections_by_option, quick_boot)) { | ||||
| 				if(quick_boot) { | ||||
| 					// Cf. Big Mess o' Wires' disassembly of the Mac Plus ROM, and the | ||||
| 					// test at $E00. TODO: adapt as(/if?) necessary for other Macs. | ||||
| 					ram_[0x02ae >> 1] = 0x40; | ||||
| 					ram_[0x02b0 >> 1] = 0x00; | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		Configurable::SelectionSet get_accurate_selections() override { | ||||
| 			Configurable::SelectionSet selection_set; | ||||
| 			Configurable::append_quick_boot_selection(selection_set, false); | ||||
| 			return selection_set; | ||||
| 		} | ||||
|  | ||||
| 		Configurable::SelectionSet get_user_friendly_selections() override { | ||||
| 			Configurable::SelectionSet selection_set; | ||||
| 			Configurable::append_quick_boot_selection(selection_set, true); | ||||
| 			return selection_set; | ||||
| 		} | ||||
|  | ||||
| 	private: | ||||
| 		void set_component_prefers_clocking(ClockingHint::Source *component, ClockingHint::Preference clocking) override { | ||||
| 			scsi_bus_is_clocked_ = scsi_bus_.preferred_clocking() != ClockingHint::Preference::None; | ||||
| 		} | ||||
|  | ||||
| 		void drive_speed_accumulator_set_drive_speed(DriveSpeedAccumulator *, float speed) override { | ||||
| 			iwm_.flush(); | ||||
| 			drives_[0].set_rotation_speed(speed); | ||||
| 			drives_[1].set_rotation_speed(speed); | ||||
| 		} | ||||
|  | ||||
| 		forceinline void adjust_phase() { | ||||
| 			++phase_; | ||||
| 		} | ||||
| @@ -461,8 +575,8 @@ template <Analyser::Static::Macintosh::Target::Model model> class ConcreteMachin | ||||
| 		/// Advances all non-CPU components by @c duration half cycles. | ||||
| 		forceinline void advance_time(HalfCycles duration) { | ||||
| 			time_since_video_update_ += duration; | ||||
| 			iwm_.time_since_update += duration; | ||||
| 			ram_subcycle_ = (ram_subcycle_ + duration.as_int()) & 15; | ||||
| 			iwm_ += duration; | ||||
| 			ram_subcycle_ = (ram_subcycle_ + duration.as_integral()) & 15; | ||||
|  | ||||
| 			// The VIA runs at one-tenth of the 68000's clock speed, in sync with the E clock. | ||||
| 			// See: Guide to the Macintosh Hardware Family p149 (PDF p188). Some extra division | ||||
| @@ -519,13 +633,18 @@ template <Analyser::Static::Macintosh::Target::Model model> class ConcreteMachin | ||||
|  | ||||
| 			// Consider updating the real-time clock. | ||||
| 			real_time_clock_ += duration; | ||||
| 			auto ticks = real_time_clock_.divide_cycles(Cycles(CLOCK_RATE)).as_int(); | ||||
| 			auto ticks = real_time_clock_.divide_cycles(Cycles(CLOCK_RATE)).as_integral(); | ||||
| 			while(ticks--) { | ||||
| 				clock_.update(); | ||||
| 				// TODO: leave a delay between toggling the input rather than using this coupled hack. | ||||
| 				via_.set_control_line_input(MOS::MOS6522::Port::A, MOS::MOS6522::Line::Two, true); | ||||
| 				via_.set_control_line_input(MOS::MOS6522::Port::A, MOS::MOS6522::Line::Two, false); | ||||
| 			} | ||||
|  | ||||
| 			// Update the SCSI if currently active. | ||||
| 			if constexpr (model == Analyser::Static::Macintosh::Target::Model::MacPlus) { | ||||
| 				if(scsi_bus_is_clocked_) scsi_bus_.run_for(duration); | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		forceinline void update_video() { | ||||
| @@ -537,21 +656,12 @@ template <Analyser::Static::Macintosh::Target::Model model> class ConcreteMachin | ||||
| 			return mouse_; | ||||
| 		} | ||||
|  | ||||
| 		struct IWM { | ||||
| 			IWM(int clock_rate) : iwm(clock_rate) {} | ||||
|  | ||||
| 			HalfCycles time_since_update; | ||||
| 			Apple::IWM iwm; | ||||
|  | ||||
| 			void flush() { | ||||
| 				iwm.run_for(time_since_update.flush<Cycles>()); | ||||
| 			} | ||||
| 		}; | ||||
| 		using IWMActor = JustInTimeActor<IWM, 1, 1, HalfCycles, Cycles>; | ||||
|  | ||||
| 		class VIAPortHandler: public MOS::MOS6522::PortHandler { | ||||
| 			public: | ||||
| 				VIAPortHandler(ConcreteMachine &machine, RealTimeClock &clock, Keyboard &keyboard, Video &video, DeferredAudio &audio, IWM &iwm, Inputs::QuadratureMouse &mouse) : | ||||
| 					machine_(machine), clock_(clock), keyboard_(keyboard), video_(video), audio_(audio), iwm_(iwm), mouse_(mouse) {} | ||||
| 				VIAPortHandler(ConcreteMachine &machine, RealTimeClock &clock, Keyboard &keyboard, DeferredAudio &audio, IWMActor &iwm, Inputs::QuadratureMouse &mouse) : | ||||
| 					machine_(machine), clock_(clock), keyboard_(keyboard), audio_(audio), iwm_(iwm), mouse_(mouse) {} | ||||
|  | ||||
| 				using Port = MOS::MOS6522::Port; | ||||
| 				using Line = MOS::MOS6522::Line; | ||||
| @@ -572,8 +682,7 @@ template <Analyser::Static::Macintosh::Target::Model model> class ConcreteMachin | ||||
| 									b3:	0 = use alternate sound buffer, 1 = use ordinary sound buffer | ||||
| 									b2–b0:	audio output volume | ||||
| 							*/ | ||||
| 							iwm_.flush(); | ||||
| 							iwm_.iwm.set_select(!!(value & 0x20)); | ||||
| 							iwm_->set_select(!!(value & 0x20)); | ||||
|  | ||||
| 							machine_.set_use_alternate_buffers(!(value & 0x40), !(value&0x08)); | ||||
| 							machine_.set_rom_is_overlay(!!(value & 0x10)); | ||||
| @@ -641,7 +750,7 @@ template <Analyser::Static::Macintosh::Target::Model model> class ConcreteMachin | ||||
| 				void run_for(HalfCycles duration) { | ||||
| 					// The 6522 enjoys a divide-by-ten, so multiply back up here to make the | ||||
| 					// divided-by-two clock the audio works on. | ||||
| 					audio_.time_since_update += HalfCycles(duration.as_int() * 5); | ||||
| 					audio_.time_since_update += HalfCycles(duration.as_integral() * 5); | ||||
| 				} | ||||
|  | ||||
| 				void flush() { | ||||
| @@ -656,16 +765,15 @@ template <Analyser::Static::Macintosh::Target::Model model> class ConcreteMachin | ||||
| 				ConcreteMachine &machine_; | ||||
| 				RealTimeClock &clock_; | ||||
| 				Keyboard &keyboard_; | ||||
| 				Video &video_; | ||||
| 				DeferredAudio &audio_; | ||||
| 				IWM &iwm_; | ||||
| 				IWMActor &iwm_; | ||||
| 				Inputs::QuadratureMouse &mouse_; | ||||
| 		}; | ||||
|  | ||||
| 		CPU::MC68000::Processor<ConcreteMachine, true> mc68000_; | ||||
|  | ||||
| 		DriveSpeedAccumulator drive_speed_accumulator_; | ||||
| 		IWM iwm_; | ||||
| 		IWMActor iwm_; | ||||
|  | ||||
| 		DeferredAudio audio_; | ||||
| 		Video video_; | ||||
| @@ -677,6 +785,10 @@ template <Analyser::Static::Macintosh::Target::Model model> class ConcreteMachin | ||||
|  		VIAPortHandler via_port_handler_; | ||||
|  | ||||
|  		Zilog::SCC::z8530 scc_; | ||||
| 		SCSI::Bus scsi_bus_; | ||||
|  		NCR::NCR5380::NCR5380 scsi_; | ||||
| 		SCSI::Target::Target<SCSI::DirectAccessDevice> hard_drive_; | ||||
|  		bool scsi_bus_is_clocked_ = false; | ||||
|  | ||||
|  		HalfCycles via_clock_; | ||||
|  		HalfCycles real_time_clock_; | ||||
| @@ -698,72 +810,39 @@ template <Analyser::Static::Macintosh::Target::Model model> class ConcreteMachin | ||||
| 			RAM, ROM, VIA, IWM, SCCWrite, SCCReadResetPhase, SCSI, PhaseRead, Unassigned | ||||
| 		}; | ||||
|  | ||||
| 		/// Divides the 24-bit address space up into $80000 (i.e. 512kb) segments, recording | ||||
| 		/// Divides the 24-bit address space up into $20000 (i.e. 128kb) segments, recording | ||||
| 		/// which device is current mapped in each area. Keeping it in a table is a bit faster | ||||
| 		/// than the multi-level address inspection that is otherwise required, as well as | ||||
| 		/// simplifying slightly the handling of different models. | ||||
| 		/// | ||||
| 		/// So: index with the top 5 bits of the 24-bit address. | ||||
| 		BusDevice memory_map_[32]; | ||||
| 		/// So: index with the top 7 bits of the 24-bit address. | ||||
| 		BusDevice memory_map_[128]; | ||||
|  | ||||
| 		void setup_memory_map() { | ||||
| 			// Apply the power-up memory map, i.e. assume that ROM_is_overlay_ = true. | ||||
| 			// Apply the power-up memory map, i.e. assume that ROM_is_overlay_ = true; | ||||
| 			// start by calling into set_rom_is_overlay to seed everything up to $800000. | ||||
| 			set_rom_is_overlay(true); | ||||
|  | ||||
| 			using Model = Analyser::Static::Macintosh::Target::Model; | ||||
| 			switch(model) { | ||||
| 				default: assert(false); | ||||
|  | ||||
| 				case Model::Mac128k: | ||||
| 				case Model::Mac512k: | ||||
| 				case Model::Mac512ke: | ||||
| 					populate_memory_map([] (std::function<void(int target, BusDevice device)> map_to) { | ||||
| 						// Up to $60 0000 mirrors of the ROM alternate with unassigned areas every $10 0000 byes. | ||||
| 						for(int c = 0; c <= 0x600000; c += 0x100000) { | ||||
| 							map_to(c, ((c >> 20)&1) ? BusDevice::ROM : BusDevice::Unassigned); | ||||
| 						} | ||||
| 						map_to(0x800000, BusDevice::RAM); | ||||
| 						map_to(0x900000, BusDevice::Unassigned); | ||||
| 						map_to(0xa00000, BusDevice::SCCReadResetPhase); | ||||
| 						map_to(0xb00000, BusDevice::Unassigned); | ||||
| 						map_to(0xc00000, BusDevice::SCCWrite); | ||||
| 						map_to(0xd00000, BusDevice::Unassigned); | ||||
| 						map_to(0xe00000, BusDevice::IWM); | ||||
| 						map_to(0xe80000, BusDevice::Unassigned); | ||||
| 						map_to(0xf00000, BusDevice::VIA); | ||||
| 						map_to(0xf80000, BusDevice::PhaseRead); | ||||
| 						map_to(0x1000000, BusDevice::Unassigned); | ||||
| 					}); | ||||
| 				break; | ||||
|  | ||||
| 				case Model::MacPlus: | ||||
| 					populate_memory_map([] (std::function<void(int target, BusDevice device)> map_to) { | ||||
| 						map_to(0x100000, BusDevice::ROM); | ||||
| 						map_to(0x400000, BusDevice::Unassigned); | ||||
| 						map_to(0x500000, BusDevice::ROM); | ||||
| 						map_to(0x580000, BusDevice::Unassigned); | ||||
| 						map_to(0x600000, BusDevice::SCSI); | ||||
| 						map_to(0x800000, BusDevice::RAM); | ||||
| 						map_to(0x900000, BusDevice::Unassigned); | ||||
| 						map_to(0xa00000, BusDevice::SCCReadResetPhase); | ||||
| 						map_to(0xb00000, BusDevice::Unassigned); | ||||
| 						map_to(0xc00000, BusDevice::SCCWrite); | ||||
| 						map_to(0xd00000, BusDevice::Unassigned); | ||||
| 						map_to(0xe00000, BusDevice::IWM); | ||||
| 						map_to(0xe80000, BusDevice::Unassigned); | ||||
| 						map_to(0xf00000, BusDevice::VIA); | ||||
| 						map_to(0xf80000, BusDevice::PhaseRead); | ||||
| 						map_to(0x1000000, BusDevice::Unassigned); | ||||
| 					}); | ||||
| 				break; | ||||
| 			} | ||||
| 			populate_memory_map(0x800000, [] (std::function<void(int target, BusDevice device)> map_to) { | ||||
| 				map_to(0x900000, BusDevice::Unassigned); | ||||
| 				map_to(0xa00000, BusDevice::SCCReadResetPhase); | ||||
| 				map_to(0xb00000, BusDevice::Unassigned); | ||||
| 				map_to(0xc00000, BusDevice::SCCWrite); | ||||
| 				map_to(0xd00000, BusDevice::Unassigned); | ||||
| 				map_to(0xe00000, BusDevice::IWM); | ||||
| 				map_to(0xe80000, BusDevice::Unassigned); | ||||
| 				map_to(0xf00000, BusDevice::VIA); | ||||
| 				map_to(0xf80000, BusDevice::PhaseRead); | ||||
| 				map_to(0x1000000, BusDevice::Unassigned); | ||||
| 			}); | ||||
| 		} | ||||
|  | ||||
| 		void populate_memory_map(std::function<void(std::function<void(int, BusDevice)>)> populator) { | ||||
| 		void populate_memory_map(int start_address, std::function<void(std::function<void(int, BusDevice)>)> populator) { | ||||
| 			// Define semantics for below; map_to will write from the current cursor position | ||||
| 			// to the supplied 24-bit address, setting a particular mapped device. | ||||
| 			int segment = 0; | ||||
| 			int segment = start_address >> 17; | ||||
| 			auto map_to = [&segment, this](int address, BusDevice device) { | ||||
| 				for(; segment < address >> 19; ++segment) { | ||||
| 				for(; segment < address >> 17; ++segment) { | ||||
| 					this->memory_map_[segment] = device; | ||||
| 				} | ||||
| 			}; | ||||
| @@ -773,8 +852,8 @@ template <Analyser::Static::Macintosh::Target::Model model> class ConcreteMachin | ||||
|  | ||||
| 		uint32_t ram_mask_ = 0; | ||||
| 		uint32_t rom_mask_ = 0; | ||||
| 		uint16_t rom_[64*1024]; | ||||
| 		uint16_t ram_[256*1024]; | ||||
| 		uint16_t rom_[64*1024];	// i.e. up to 128kb in size. | ||||
| 		std::vector<uint16_t> ram_; | ||||
| }; | ||||
|  | ||||
| } | ||||
|   | ||||
| @@ -9,12 +9,15 @@ | ||||
| #ifndef Macintosh_hpp | ||||
| #define Macintosh_hpp | ||||
|  | ||||
| #include "../../../Configurable/Configurable.hpp" | ||||
| #include "../../../Analyser/Static/StaticAnalyser.hpp" | ||||
| #include "../../ROMMachine.hpp" | ||||
|  | ||||
| namespace Apple { | ||||
| namespace Macintosh { | ||||
|  | ||||
| std::vector<std::unique_ptr<Configurable::Option>> get_options(); | ||||
|  | ||||
| class Machine { | ||||
| 	public: | ||||
| 		virtual ~Machine(); | ||||
|   | ||||
| @@ -25,8 +25,15 @@ class RealTimeClock { | ||||
| 	public: | ||||
| 		RealTimeClock() { | ||||
| 			// TODO: this should persist, if possible, rather than | ||||
| 			// being randomly initialised. | ||||
| 			Memory::Fuzz(data_, sizeof(data_)); | ||||
| 			// being default initialised. | ||||
| 			const uint8_t default_data[] = { | ||||
| 				0xa8, 0x00, 0x00, 0x00, | ||||
| 				0xcc, 0x0a, 0xcc, 0x0a, | ||||
| 				0x00, 0x00, 0x00, 0x00, | ||||
| 				0x00, 0x02, 0x63, 0x00, | ||||
| 				0x03, 0x88, 0x00, 0x4c | ||||
| 			}; | ||||
| 			memcpy(data_, default_data, sizeof(data_)); | ||||
| 		} | ||||
|  | ||||
| 		/*! | ||||
|   | ||||
| @@ -12,16 +12,6 @@ | ||||
|  | ||||
| using namespace Apple::Macintosh; | ||||
|  | ||||
| namespace { | ||||
|  | ||||
| const HalfCycles line_length(704); | ||||
| const int number_of_lines = 370; | ||||
| const HalfCycles frame_length(line_length * HalfCycles(number_of_lines)); | ||||
| const int sync_start = 36; | ||||
| const int sync_end = 38; | ||||
|  | ||||
| } | ||||
|  | ||||
| // Re: CRT timings, see the Apple Guide to the Macintosh Hardware Family, | ||||
| // bottom of page 400: | ||||
| // | ||||
| @@ -33,11 +23,10 @@ const int sync_end = 38; | ||||
| //	"The visible portion of a full-screen display consists of 342 horizontal scan lines... | ||||
| //	During the vertical blanking interval, the turned-off beam ... traces out an additional 28 scan lines," | ||||
| // | ||||
| Video::Video(uint16_t *ram, DeferredAudio &audio, DriveSpeedAccumulator &drive_speed_accumulator) : | ||||
| Video::Video(DeferredAudio &audio, DriveSpeedAccumulator &drive_speed_accumulator) : | ||||
| 	audio_(audio), | ||||
| 	drive_speed_accumulator_(drive_speed_accumulator), | ||||
|  	crt_(704, 1, 370, Outputs::Display::ColourSpace::YIQ, 1, 1, 6, false, Outputs::Display::InputDataType::Luminance1), | ||||
|  	ram_(ram) { | ||||
|  	crt_(704, 1, 370, Outputs::Display::ColourSpace::YIQ, 1, 1, 6, false, Outputs::Display::InputDataType::Luminance1) { | ||||
|  | ||||
|  	crt_.set_display_type(Outputs::Display::DisplayType::RGB); | ||||
| 	crt_.set_visible_area(Outputs::Display::Rect(0.08f, -0.025f, 0.82f, 0.82f)); | ||||
| @@ -58,7 +47,7 @@ void Video::run_for(HalfCycles duration) { | ||||
| 	// the number of fetches. | ||||
| 	while(duration > HalfCycles(0)) { | ||||
| 		const auto pixel_start = frame_position_ % line_length; | ||||
| 		const int line = (frame_position_ / line_length).as_int(); | ||||
| 		const int line = int((frame_position_ / line_length).as_integral()); | ||||
|  | ||||
| 		const auto cycles_left_in_line = std::min(line_length - pixel_start, duration); | ||||
|  | ||||
| @@ -73,8 +62,8 @@ void Video::run_for(HalfCycles duration) { | ||||
| 		// | ||||
| 		//	Then 12 lines of border, 3 of sync, 11 more of border. | ||||
|  | ||||
| 		const int first_word = pixel_start.as_int() >> 4; | ||||
| 		const int final_word = (pixel_start + cycles_left_in_line).as_int() >> 4; | ||||
| 		const int first_word = int(pixel_start.as_integral()) >> 4; | ||||
| 		const int final_word = int((pixel_start + cycles_left_in_line).as_integral()) >> 4; | ||||
|  | ||||
| 		if(first_word != final_word) { | ||||
| 			if(line < 342) { | ||||
| @@ -164,12 +153,12 @@ void Video::run_for(HalfCycles duration) { | ||||
| } | ||||
|  | ||||
| bool Video::vsync() { | ||||
| 	const int line = (frame_position_ / line_length).as_int(); | ||||
| 	const auto line = (frame_position_ / line_length).as_integral(); | ||||
| 	return line >= 353 && line < 356; | ||||
| } | ||||
|  | ||||
| HalfCycles Video::get_next_sequence_point() { | ||||
| 	const int line = (frame_position_ / line_length).as_int(); | ||||
| 	const auto line = (frame_position_ / line_length).as_integral(); | ||||
| 	if(line >= 353 && line < 356) { | ||||
| 		// Currently in vsync, so get time until start of line 357, | ||||
| 		// when vsync will end. | ||||
| @@ -184,18 +173,12 @@ HalfCycles Video::get_next_sequence_point() { | ||||
| 	} | ||||
| } | ||||
|  | ||||
| bool Video::is_outputting(HalfCycles offset) { | ||||
| 	const auto offset_position = frame_position_ + offset % frame_length; | ||||
| 	const int column = (offset_position % line_length).as_int() >> 4; | ||||
| 	const int line = (offset_position / line_length).as_int(); | ||||
| 	return line < 342 && column < 32; | ||||
| } | ||||
|  | ||||
| void Video::set_use_alternate_buffers(bool use_alternate_screen_buffer, bool use_alternate_audio_buffer) { | ||||
| 	use_alternate_screen_buffer_ = use_alternate_screen_buffer; | ||||
| 	use_alternate_audio_buffer_ = use_alternate_audio_buffer; | ||||
| } | ||||
|  | ||||
| void Video::set_ram_mask(uint32_t mask) { | ||||
| void Video::set_ram(uint16_t *ram, uint32_t mask) { | ||||
| 	ram_ = ram; | ||||
| 	ram_mask_ = mask; | ||||
| } | ||||
|   | ||||
| @@ -17,6 +17,12 @@ | ||||
| namespace Apple { | ||||
| namespace Macintosh { | ||||
|  | ||||
| constexpr HalfCycles line_length(704); | ||||
| constexpr int number_of_lines = 370; | ||||
| constexpr HalfCycles frame_length(line_length * HalfCycles(number_of_lines)); | ||||
| constexpr int sync_start = 36; | ||||
| constexpr int sync_end = 38; | ||||
|  | ||||
| /*! | ||||
| 	Models the 68000-era Macintosh video hardware, producing a 512x348 pixel image, | ||||
| 	within a total scanning area of 370 lines, at 352 cycles per line. | ||||
| @@ -29,7 +35,7 @@ class Video { | ||||
| 			Constructs an instance of @c Video sourcing its pixel data from @c ram and | ||||
| 			providing audio and drive-speed bytes to @c audio and @c drive_speed_accumulator. | ||||
| 		*/ | ||||
| 		Video(uint16_t *ram, DeferredAudio &audio, DriveSpeedAccumulator &drive_speed_accumulator); | ||||
| 		Video(DeferredAudio &audio, DriveSpeedAccumulator &drive_speed_accumulator); | ||||
|  | ||||
| 		/*! | ||||
| 			Sets the target device for video data. | ||||
| @@ -47,10 +53,10 @@ class Video { | ||||
| 		void set_use_alternate_buffers(bool use_alternate_screen_buffer, bool use_alternate_audio_buffer); | ||||
|  | ||||
| 		/*! | ||||
| 			Provides a mask indicating which parts of the generated video and audio/drive addresses are | ||||
| 			Provides a base address and a mask indicating which parts of the generated video and audio/drive addresses are | ||||
| 			actually decoded, accessing *word-sized memory*; e.g. for a 128kb Macintosh this should be (1 << 16) - 1 = 0xffff. | ||||
| 		*/ | ||||
| 		void set_ram_mask(uint32_t); | ||||
| 		void set_ram(uint16_t *ram, uint32_t mask); | ||||
|  | ||||
| 		/*! | ||||
| 			@returns @c true if the video is currently outputting a vertical sync, @c false otherwise. | ||||
| @@ -61,7 +67,12 @@ class Video { | ||||
| 			@returns @c true if in @c offset half cycles from now, the video will be outputting pixels; | ||||
| 				@c false otherwise. | ||||
| 		*/ | ||||
| 		bool is_outputting(HalfCycles offset = HalfCycles(0)); | ||||
| 		bool is_outputting(HalfCycles offset = HalfCycles(0)) { | ||||
| 			const auto offset_position = frame_position_ + offset % frame_length; | ||||
| 			const int column = int((offset_position % line_length).as_integral()) >> 4; | ||||
| 			const int line = int((offset_position / line_length).as_integral()); | ||||
| 			return line < 342 && column < 32; | ||||
| 		} | ||||
|  | ||||
| 		/*! | ||||
| 			@returns the amount of time until there is next a transition on the | ||||
|   | ||||
| @@ -11,10 +11,10 @@ | ||||
| #include <algorithm> | ||||
| #include <cstdio> | ||||
| 
 | ||||
| #include "../CRTMachine.hpp" | ||||
| #include "../JoystickMachine.hpp" | ||||
| #include "../../CRTMachine.hpp" | ||||
| #include "../../JoystickMachine.hpp" | ||||
| 
 | ||||
| #include "../../Analyser/Static/Atari/Target.hpp" | ||||
| #include "../../../Analyser/Static/Atari2600/Target.hpp" | ||||
| 
 | ||||
| #include "Cartridges/Atari8k.hpp" | ||||
| #include "Cartridges/Atari16k.hpp" | ||||
| @@ -72,48 +72,50 @@ class Joystick: public Inputs::ConcreteJoystick { | ||||
| 		std::size_t shift_, fire_tia_input_; | ||||
| }; | ||||
| 
 | ||||
| using Target = Analyser::Static::Atari2600::Target; | ||||
| 
 | ||||
| class ConcreteMachine: | ||||
| 	public Machine, | ||||
| 	public CRTMachine::Machine, | ||||
| 	public JoystickMachine::Machine, | ||||
| 	public Outputs::CRT::Delegate { | ||||
| 	public: | ||||
| 		ConcreteMachine(const Analyser::Static::Atari::Target &target) { | ||||
| 		ConcreteMachine(const Target &target) { | ||||
| 			set_clock_rate(NTSC_clock_rate); | ||||
| 
 | ||||
| 			const std::vector<uint8_t> &rom = target.media.cartridges.front()->get_segments().front().data; | ||||
| 
 | ||||
| 			using PagingModel = Analyser::Static::Atari::Target::PagingModel; | ||||
| 			using PagingModel = Target::PagingModel; | ||||
| 			switch(target.paging_model) { | ||||
| 				case PagingModel::ActivisionStack:	bus_.reset(new Cartridge::Cartridge<Cartridge::ActivisionStack>(rom));	break; | ||||
| 				case PagingModel::CBSRamPlus:		bus_.reset(new Cartridge::Cartridge<Cartridge::CBSRAMPlus>(rom));		break; | ||||
| 				case PagingModel::CommaVid:			bus_.reset(new Cartridge::Cartridge<Cartridge::CommaVid>(rom));			break; | ||||
| 				case PagingModel::MegaBoy:			bus_.reset(new Cartridge::Cartridge<Cartridge::MegaBoy>(rom));			break; | ||||
| 				case PagingModel::MNetwork:			bus_.reset(new Cartridge::Cartridge<Cartridge::MNetwork>(rom));			break; | ||||
| 				case PagingModel::None:				bus_.reset(new Cartridge::Cartridge<Cartridge::Unpaged>(rom));			break; | ||||
| 				case PagingModel::ParkerBros:		bus_.reset(new Cartridge::Cartridge<Cartridge::ParkerBros>(rom));		break; | ||||
| 				case PagingModel::Pitfall2:			bus_.reset(new Cartridge::Cartridge<Cartridge::Pitfall2>(rom));			break; | ||||
| 				case PagingModel::Tigervision:		bus_.reset(new Cartridge::Cartridge<Cartridge::Tigervision>(rom));		break; | ||||
| 				case PagingModel::ActivisionStack:	bus_ = std::make_unique<Cartridge::Cartridge<Cartridge::ActivisionStack>>(rom);	break; | ||||
| 				case PagingModel::CBSRamPlus:		bus_ = std::make_unique<Cartridge::Cartridge<Cartridge::CBSRAMPlus>>(rom);		break; | ||||
| 				case PagingModel::CommaVid:			bus_ = std::make_unique<Cartridge::Cartridge<Cartridge::CommaVid>>(rom);		break; | ||||
| 				case PagingModel::MegaBoy:			bus_ = std::make_unique<Cartridge::Cartridge<Cartridge::MegaBoy>>(rom);			break; | ||||
| 				case PagingModel::MNetwork:			bus_ = std::make_unique<Cartridge::Cartridge<Cartridge::MNetwork>>(rom);		break; | ||||
| 				case PagingModel::None:				bus_ = std::make_unique<Cartridge::Cartridge<Cartridge::Unpaged>>(rom);			break; | ||||
| 				case PagingModel::ParkerBros:		bus_ = std::make_unique<Cartridge::Cartridge<Cartridge::ParkerBros>>(rom);		break; | ||||
| 				case PagingModel::Pitfall2:			bus_ = std::make_unique<Cartridge::Cartridge<Cartridge::Pitfall2>>(rom);		break; | ||||
| 				case PagingModel::Tigervision:		bus_ = std::make_unique<Cartridge::Cartridge<Cartridge::Tigervision>>(rom);		break; | ||||
| 
 | ||||
| 				case PagingModel::Atari8k: | ||||
| 					if(target.uses_superchip) { | ||||
| 						bus_.reset(new Cartridge::Cartridge<Cartridge::Atari8kSuperChip>(rom)); | ||||
| 						bus_ = std::make_unique<Cartridge::Cartridge<Cartridge::Atari8kSuperChip>>(rom); | ||||
| 					} else { | ||||
| 						bus_.reset(new Cartridge::Cartridge<Cartridge::Atari8k>(rom)); | ||||
| 						bus_ = std::make_unique<Cartridge::Cartridge<Cartridge::Atari8k>>(rom); | ||||
| 					} | ||||
| 				break; | ||||
| 				case PagingModel::Atari16k: | ||||
| 					if(target.uses_superchip) { | ||||
| 						bus_.reset(new Cartridge::Cartridge<Cartridge::Atari16kSuperChip>(rom)); | ||||
| 						bus_ = std::make_unique<Cartridge::Cartridge<Cartridge::Atari16kSuperChip>>(rom); | ||||
| 					} else { | ||||
| 						bus_.reset(new Cartridge::Cartridge<Cartridge::Atari16k>(rom)); | ||||
| 						bus_ = std::make_unique<Cartridge::Cartridge<Cartridge::Atari16k>>(rom); | ||||
| 					} | ||||
| 				break; | ||||
| 				case PagingModel::Atari32k: | ||||
| 					if(target.uses_superchip) { | ||||
| 						bus_.reset(new Cartridge::Cartridge<Cartridge::Atari32kSuperChip>(rom)); | ||||
| 						bus_ = std::make_unique<Cartridge::Cartridge<Cartridge::Atari32kSuperChip>>(rom); | ||||
| 					} else { | ||||
| 						bus_.reset(new Cartridge::Cartridge<Cartridge::Atari32k>(rom)); | ||||
| 						bus_ = std::make_unique<Cartridge::Cartridge<Cartridge::Atari32k>>(rom); | ||||
| 					} | ||||
| 				break; | ||||
| 			} | ||||
| @@ -122,7 +124,7 @@ class ConcreteMachine: | ||||
| 			joysticks_.emplace_back(new Joystick(bus_.get(), 4, 1)); | ||||
| 		} | ||||
| 
 | ||||
| 		std::vector<std::unique_ptr<Inputs::Joystick>> &get_joysticks() override { | ||||
| 		const std::vector<std::unique_ptr<Inputs::Joystick>> &get_joysticks() override { | ||||
| 			return joysticks_; | ||||
| 		} | ||||
| 
 | ||||
| @@ -232,7 +234,6 @@ class ConcreteMachine: | ||||
| using namespace Atari2600; | ||||
| 
 | ||||
| Machine *Machine::Atari2600(const Analyser::Static::Target *target, const ROMMachine::ROMFetcher &rom_fetcher) { | ||||
| 	using Target = Analyser::Static::Atari::Target; | ||||
| 	const Target *const atari_target = dynamic_cast<const Target *>(target); | ||||
| 	return new Atari2600::ConcreteMachine(*atari_target); | ||||
| } | ||||
| @@ -9,9 +9,9 @@ | ||||
| #ifndef Atari2600_cpp | ||||
| #define Atari2600_cpp | ||||
| 
 | ||||
| #include "../../Configurable/Configurable.hpp" | ||||
| #include "../../Analyser/Static/StaticAnalyser.hpp" | ||||
| #include "../ROMMachine.hpp" | ||||
| #include "../../../Configurable/Configurable.hpp" | ||||
| #include "../../../Analyser/Static/StaticAnalyser.hpp" | ||||
| #include "../../ROMMachine.hpp" | ||||
| 
 | ||||
| #include "Atari2600Inputs.h" | ||||
| 
 | ||||
| @@ -14,9 +14,9 @@ | ||||
| #include "TIA.hpp" | ||||
| #include "TIASound.hpp" | ||||
| 
 | ||||
| #include "../../Analyser/Dynamic/ConfidenceCounter.hpp" | ||||
| #include "../../ClockReceiver/ClockReceiver.hpp" | ||||
| #include "../../Outputs/Speaker/Implementation/LowpassSpeaker.hpp" | ||||
| #include "../../../Analyser/Dynamic/ConfidenceCounter.hpp" | ||||
| #include "../../../ClockReceiver/ClockReceiver.hpp" | ||||
| #include "../../../Outputs/Speaker/Implementation/LowpassSpeaker.hpp" | ||||
| 
 | ||||
| namespace Atari2600 { | ||||
| 
 | ||||
| @@ -9,7 +9,7 @@ | ||||
| #ifndef Atari2600_Cartridge_hpp | ||||
| #define Atari2600_Cartridge_hpp | ||||
| 
 | ||||
| #include "../../../Processors/6502/6502.hpp" | ||||
| #include "../../../../Processors/6502/6502.hpp" | ||||
| #include "../Bus.hpp" | ||||
| 
 | ||||
| namespace Atari2600 { | ||||
| @@ -51,7 +51,7 @@ template<class T> class Cartridge: | ||||
| 			Adjusts @c confidence_counter according to the results of the most recent run_for. | ||||
| 		*/ | ||||
| 		void apply_confidence(Analyser::Dynamic::ConfidenceCounter &confidence_counter) { | ||||
| 			if(cycle_count_.as_int() < 200) return; | ||||
| 			if(cycle_count_.as_integral() < 200) return; | ||||
| 			if(horizontal_counter_resets_ > 10) | ||||
| 				confidence_counter.add_miss(); | ||||
| 		} | ||||
| @@ -181,9 +181,9 @@ template<class T> class Cartridge: | ||||
| 				if((address&0x1280) == 0x280) { | ||||
| 					update_6532(); | ||||
| 					if(isReadOperation(operation)) { | ||||
| 						returnValue &= mos6532_.get_register(address); | ||||
| 						returnValue &= mos6532_.read(address); | ||||
| 					} else { | ||||
| 						mos6532_.set_register(address, *value); | ||||
| 						mos6532_.write(address, *value); | ||||
| 					} | ||||
| 				} | ||||
| 
 | ||||
| @@ -101,7 +101,7 @@ class Pitfall2: public BusExtender { | ||||
| 
 | ||||
| 		inline uint8_t update_audio() { | ||||
| 			const unsigned int clock_divisor = 57; | ||||
| 			int cycles_to_run_for = cycles_since_audio_update_.divide(clock_divisor).as_int(); | ||||
| 			int cycles_to_run_for = int(cycles_since_audio_update_.divide(clock_divisor).as_integral()); | ||||
| 
 | ||||
| 			int table_position = 0; | ||||
| 			for(int c = 0; c < 3; c++) { | ||||
| @@ -11,7 +11,7 @@ | ||||
| 
 | ||||
| #include <cstdint> | ||||
| 
 | ||||
| #include "../../Components/6532/6532.hpp" | ||||
| #include "../../../Components/6532/6532.hpp" | ||||
| 
 | ||||
| namespace Atari2600 { | ||||
| 
 | ||||
| @@ -13,11 +13,11 @@ | ||||
| 
 | ||||
| using namespace Atari2600; | ||||
| namespace { | ||||
| 	const int cycles_per_line = 228; | ||||
| 	const int first_pixel_cycle = 68; | ||||
| 	constexpr int cycles_per_line = 228; | ||||
| 	constexpr int first_pixel_cycle = 68; | ||||
| 
 | ||||
| 	const int sync_flag	= 0x1; | ||||
| 	const int blank_flag = 0x2; | ||||
| 	constexpr int sync_flag	= 0x1; | ||||
| 	constexpr int blank_flag = 0x2; | ||||
| 
 | ||||
| 	uint8_t reverse_table[256]; | ||||
| } | ||||
| @@ -143,7 +143,7 @@ void TIA::set_scan_target(Outputs::Display::ScanTarget *scan_target) { | ||||
| } | ||||
| 
 | ||||
| void TIA::run_for(const Cycles cycles) { | ||||
| 	int number_of_cycles = cycles.as_int(); | ||||
| 	int number_of_cycles = int(cycles.as_integral()); | ||||
| 
 | ||||
| 	// if part way through a line, definitely perform a partial, at most up to the end of the line
 | ||||
| 	if(horizontal_counter_) { | ||||
| @@ -176,7 +176,7 @@ void TIA::reset_horizontal_counter() { | ||||
| } | ||||
| 
 | ||||
| int TIA::get_cycles_until_horizontal_blank(const Cycles from_offset) { | ||||
| 	return (cycles_per_line - (horizontal_counter_ + from_offset.as_int()) % cycles_per_line) % cycles_per_line; | ||||
| 	return (cycles_per_line - (horizontal_counter_ + from_offset.as_integral()) % cycles_per_line) % cycles_per_line; | ||||
| } | ||||
| 
 | ||||
| void TIA::set_background_colour(uint8_t colour) { | ||||
| @@ -14,8 +14,8 @@ | ||||
| #include <cstdint> | ||||
| #include <functional> | ||||
| 
 | ||||
| #include "../../Outputs/CRT/CRT.hpp" | ||||
| #include "../../ClockReceiver/ClockReceiver.hpp" | ||||
| #include "../../../Outputs/CRT/CRT.hpp" | ||||
| #include "../../../ClockReceiver/ClockReceiver.hpp" | ||||
| 
 | ||||
| namespace Atari2600 { | ||||
| 
 | ||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user