mirror of
				https://github.com/TomHarte/CLK.git
				synced 2025-10-31 05:16:08 +00:00 
			
		
		
		
	Compare commits
	
		
			645 Commits
		
	
	
		
			2019-03-10
			...
			2019-08-01
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|  | 0c8e313fd5 | ||
|  | f64ec11668 | ||
|  | 9bbccd89d3 | ||
|  | 1ae3799aee | ||
|  | 260843e5b1 | ||
|  | e3f22e5787 | ||
|  | 2aa308efdd | ||
|  | 74c18d7861 | ||
|  | c41cccd9a6 | ||
|  | bba34b28b8 | ||
|  | d8a41575c8 | ||
|  | 0521de668a | ||
|  | 12441bddab | ||
|  | bc25c52683 | ||
|  | eb3fb70ea1 | ||
|  | 2f90ed1f32 | ||
|  | f3dd4b028d | ||
|  | 7dcad516bd | ||
|  | 9859f99513 | ||
|  | 51b7f2777d | ||
|  | 2f2478d2d3 | ||
|  | a43ada82b2 | ||
|  | 5149f290d0 | ||
|  | 0dc6f08deb | ||
|  | b1f04ed96d | ||
|  | cd49b3c89b | ||
|  | f894d43111 | ||
|  | 4033c0c754 | ||
|  | 786b26d23e | ||
|  | d08d8ed22c | ||
|  | b7b62aa3f6 | ||
|  | 39d7e3c62c | ||
|  | 81b57ecf7c | ||
|  | 572e8b52e1 | ||
|  | 9b634472c6 | ||
|  | d8bc20b1ab | ||
|  | d2bfd59953 | ||
|  | 3d20ae47ea | ||
|  | 85cf8d89bc | ||
|  | 50e954223a | ||
|  | 109d5d16bd | ||
|  | 1672dc5946 | ||
|  | 5769944918 | ||
|  | 9ef1211d53 | ||
|  | f2ae04597f | ||
|  | 1327de1c82 | ||
|  | 827c4e172a | ||
|  | c300bd17fa | ||
|  | 0187fd8eae | ||
|  | 0469f0240b | ||
|  | 4aca6c5ef8 | ||
|  | d69aee4972 | ||
|  | 3da47318b1 | ||
|  | ef036df2bc | ||
|  | 579f68cf11 | ||
|  | 90f6ca4635 | ||
|  | 374cac0107 | ||
|  | 4d361b1952 | ||
|  | fcee7779b0 | ||
|  | b4191b6225 | ||
|  | dbee37ab34 | ||
|  | a3ad0ab09b | ||
|  | ed0c4c117b | ||
|  | 2432151bf8 | ||
|  | 2129bfc570 | ||
|  | 8de6cd3f44 | ||
|  | 9b9831f28b | ||
|  | 8a2cac0d0c | ||
|  | e17b105574 | ||
|  | 67c5f6b7cb | ||
|  | d452d070a1 | ||
|  | a846c3245d | ||
|  | 4ffa3c1b49 | ||
|  | b2a6682798 | ||
|  | f3aac603f8 | ||
|  | 712cb473f7 | ||
|  | 3c68a5ca65 | ||
|  | 20670bab2f | ||
|  | 86d709ae01 | ||
|  | 0aba95cc9d | ||
|  | de3c8373fd | ||
|  | 75ecd4e72d | ||
|  | 56555a4d99 | ||
|  | cfad20bb33 | ||
|  | fa226bb1b9 | ||
|  | 77333ff9f7 | ||
|  | b9a34bee51 | ||
|  | 22ee51c12c | ||
|  | ee8d853fcb | ||
|  | 19198ea665 | ||
|  | bcbda4d855 | ||
|  | 79a624e696 | ||
|  | c123ca1054 | ||
|  | 9f0f35033d | ||
|  | 3633285aaa | ||
|  | cb16790330 | ||
|  | 67055d8b56 | ||
|  | ca37fd8f4c | ||
|  | 46b98dab70 | ||
|  | 0568996264 | ||
|  | 7baad61746 | ||
|  | 1d1e0d74f8 | ||
|  | d53d1c616f | ||
|  | 5b05a9bc61 | ||
|  | 2c39229b13 | ||
|  | 59b5dfddec | ||
|  | b730ac5d5a | ||
|  | 4860d8a7df | ||
|  | 9f0cde3d69 | ||
|  | c8917e677b | ||
|  | 6c2cc206a6 | ||
|  | 5a9f3cfc1e | ||
|  | 8f28b33342 | ||
|  | cac97a9663 | ||
|  | 2ccb564a7b | ||
|  | d1d0430fce | ||
|  | be251d6b03 | ||
|  | 6cfaf920ee | ||
|  | 1657f8768c | ||
|  | c4ab0bb867 | ||
|  | 886946cc8c | ||
|  | ed4ddcfda8 | ||
|  | 7886cd63bd | ||
|  | 69b94719a1 | ||
|  | b4a3f66773 | ||
|  | ab14433151 | ||
|  | 5078f6fb5c | ||
|  | fc6d62aefb | ||
|  | f73bccfec8 | ||
|  | 96be1a3f62 | ||
|  | 52e96e3d2a | ||
|  | 33e2721eb2 | ||
|  | 4bc44666e5 | ||
|  | 3d8e4f96c8 | ||
|  | 94457d81b6 | ||
|  | c212bf27db | ||
|  | 59b5ee65d4 | ||
|  | 60cedca97b | ||
|  | 1a9aa60bf7 | ||
|  | 6438a5ca1f | ||
|  | 3f303511bd | ||
|  | fb352a8d40 | ||
|  | ea7899f47d | ||
|  | fb6da1de4a | ||
|  | 2651b15db1 | ||
|  | 6e7a733c3c | ||
|  | 245e27c893 | ||
|  | 793c2df7ee | ||
|  | 28de629c08 | ||
|  | 210bcaa56d | ||
|  | d7329c1bdd | ||
|  | a5f0761a43 | ||
|  | dd963d6161 | ||
|  | 96c0253ee2 | ||
|  | 191a7a9386 | ||
|  | 387be4a0a6 | ||
|  | b9c2c42bc0 | ||
|  | fffe6ed2df | ||
|  | c4cbe9476c | ||
|  | 0a67cc3dab | ||
|  | 726e07ed5b | ||
|  | ebb6313eef | ||
|  | 11d8f765b2 | ||
|  | 514e57b3e9 | ||
|  | d8fb6fb951 | ||
|  | 255f0d4b2a | ||
|  | d30e7504c2 | ||
|  | 8d0cd356fd | ||
|  | aff40bf00a | ||
|  | eedf7358b4 | ||
|  | 26aebcc167 | ||
|  | 9d420c727e | ||
|  | 60fe84ad16 | ||
|  | 6a44c682ad | ||
|  | 60df44f0ca | ||
|  | ac926f5070 | ||
|  | 6e9a4a48f7 | ||
|  | a8894b308a | ||
|  | 7cc91e1bc5 | ||
|  | 9eb51f164c | ||
|  | a1c00e9318 | ||
|  | 17666bc059 | ||
|  | 241d29ff7c | ||
|  | c5039a4719 | ||
|  | fd604048db | ||
|  | 6a77ed1e07 | ||
|  | 9e38815ec4 | ||
|  | 86c325c4ec | ||
|  | bfcc6cf12c | ||
|  | 8ba8cf7c23 | ||
|  | 6c588a1510 | ||
|  | 651fd9c4a5 | ||
|  | 5d0db2198c | ||
|  | d81053ea38 | ||
|  | 8d39c3bc98 | ||
|  | da351a3e32 | ||
|  | c0591090f5 | ||
|  | 538aecb46e | ||
|  | dbdbea85c2 | ||
|  | ba2224dd06 | ||
|  | 44e2aa9183 | ||
|  | 202bff70fe | ||
|  | 26c0cd7f7c | ||
|  | cb76301fbe | ||
|  | 8bfa12edf1 | ||
|  | 7daa969a5a | ||
|  | 4aeb60100d | ||
|  | e2c7aaac5a | ||
|  | 6ff661c30d | ||
|  | 79066f8628 | ||
|  | 2c813a2692 | ||
|  | d2cb595b83 | ||
|  | cc4abcb00a | ||
|  | c1ca85987f | ||
|  | ecb5a0b8cc | ||
|  | e12e8fc616 | ||
|  | 1fbbf32cd2 | ||
|  | 31edb15369 | ||
|  | d7883d18d4 | ||
|  | 40100773d3 | ||
|  | 4048ed3a33 | ||
|  | 11f2d3cea7 | ||
|  | aa656a39b8 | ||
|  | e830d23533 | ||
|  | 9a666fb8cc | ||
|  | 0e208ed432 | ||
|  | c8b769de8a | ||
|  | c447655047 | ||
|  | 3ec9a1d869 | ||
|  | d326886852 | ||
|  | faef917cbd | ||
|  | d27ba90c07 | ||
|  | db4ca746e3 | ||
|  | d50fbfb506 | ||
|  | 5d283a9f1f | ||
|  | 86fdc75feb | ||
|  | b63231523a | ||
|  | 70e296674d | ||
|  | 5089fcd2f6 | ||
|  | df2ce8ca6f | ||
|  | 7e209353bb | ||
|  | c2806a94e2 | ||
|  | d428120776 | ||
|  | 8c8493bc9d | ||
|  | 6b996ae57d | ||
|  | ccfe1b13cb | ||
|  | 0c1c10bc66 | ||
|  | fafd1801fe | ||
|  | bcf6f665b8 | ||
|  | bd069490b5 | ||
|  | 79d8d27b4c | ||
|  | 624b0b6372 | ||
|  | 7976cf5b3c | ||
|  | 440f52c943 | ||
|  | 47b1218a68 | ||
|  | 91ced056d2 | ||
|  | 8dace34e63 | ||
|  | 8182b0363f | ||
|  | c5b036fedf | ||
|  | e26ddd0ed5 | ||
|  | ca83431e54 | ||
|  | 68a3e5a739 | ||
|  | b98f10cb45 | ||
|  | 9730800b6a | ||
|  | 506276a2bd | ||
|  | 00c32e4b59 | ||
|  | df56e6fe53 | ||
|  | 756641e837 | ||
|  | 05c2854dbc | ||
|  | 5c8aacdc17 | ||
|  | 745a5ab749 | ||
|  | fe0dc4df88 | ||
|  | 33f2664fe9 | ||
|  | a17e47fa43 | ||
|  | 877b46d2c1 | ||
|  | cc7226ae9f | ||
|  | bde975a3b9 | ||
|  | f6f9024631 | ||
|  | 39aae34323 | ||
|  | 5630141ad7 | ||
|  | 535747e3f2 | ||
|  | 59a94943aa | ||
|  | bf4889f238 | ||
|  | 7cc5afd798 | ||
|  | 11ab021672 | ||
|  | feafd4bdae | ||
|  | d6150645c0 | ||
|  | ccd2cb44a2 | ||
|  | ec5701459c | ||
|  | ad8b68c998 | ||
|  | c8066b01b6 | ||
|  | ebd59f4dd3 | ||
|  | 109953ef49 | ||
|  | 124c7bcbb0 | ||
|  | a0321aa6ff | ||
|  | 567feaac10 | ||
|  | 15c38e2f15 | ||
|  | 3c075e9542 | ||
|  | 9230969f43 | ||
|  | 0e16c67805 | ||
|  | 697e094a4e | ||
|  | 50d37798a2 | ||
|  | e9d0676e75 | ||
|  | 7591906777 | ||
|  | 08671ed69c | ||
|  | 511d292e73 | ||
|  | a413ae11cb | ||
|  | 833258f3d7 | ||
|  | b8a1553368 | ||
|  | 058fe3e986 | ||
|  | 51ee83a427 | ||
|  | 5b21da7874 | ||
|  | bd7f00bd9c | ||
|  | 517cca251f | ||
|  | 1033abd9fe | ||
|  | 113d022741 | ||
|  | 299a7b99ae | ||
|  | 66540ff86f | ||
|  | 8557558bd8 | ||
|  | 376cf08c71 | ||
|  | 83e5e650d2 | ||
|  | b860ba2ee3 | ||
|  | 661fe1e649 | ||
|  | 5b8375f0a0 | ||
|  | abe55fe950 | ||
|  | 4d4ddded6d | ||
|  | 1328708a70 | ||
|  | 85298319fa | ||
|  | 881feb1bd3 | ||
|  | 3e9fa63799 | ||
|  | da2b190288 | ||
|  | 48d837c636 | ||
|  | 983407896c | ||
|  | 5c08bb810e | ||
|  | 17635da812 | ||
|  | 6d985866ee | ||
|  | 723137c0d4 | ||
|  | 938928865d | ||
|  | d80b0cbf90 | ||
|  | e88ef30ce6 | ||
|  | 4197c6f149 | ||
|  | 035f07877c | ||
|  | 4632be4fe5 | ||
|  | b3d2b4cd37 | ||
|  | c86fe9ada9 | ||
|  | ecf93b7822 | ||
|  | 541b75ee6e | ||
|  | 77b08febdb | ||
|  | fcda376f33 | ||
|  | 0848fc7e03 | ||
|  | 3bb8d6717f | ||
|  | 5e2496d59c | ||
|  | c52da9d802 | ||
|  | 1d3dde32f2 | ||
|  | 0b999ce0e4 | ||
|  | b04bd7069d | ||
|  | 249b0fbb32 | ||
|  | 41740fb45e | ||
|  | 0ad88508f7 | ||
|  | 8293b18278 | ||
|  | 2ba0364850 | ||
|  | 8b72043f33 | ||
|  | 2e7bc0b98a | ||
|  | f0f9722ca6 | ||
|  | b5ef88902b | ||
|  | 8278809383 | ||
|  | 4367459cf2 | ||
|  | 254132b83d | ||
|  | 7b466e6d0a | ||
|  | 7e6d4f5a3e | ||
|  | ce099a297a | ||
|  | 949c848815 | ||
|  | 9bf9b9ea8c | ||
|  | d8ed8b66f3 | ||
|  | a131d39451 | ||
|  | b540f58457 | ||
|  | 4f5a38b5c5 | ||
|  | cefc3af08b | ||
|  | e6ed50383c | ||
|  | 96facc103a | ||
|  | 407bbfb379 | ||
|  | a99ebda513 | ||
|  | 537b604fc9 | ||
|  | 98bc570bf7 | ||
|  | 181b77c490 | ||
|  | bc9eb82e6f | ||
|  | 29fc024ecd | ||
|  | c1695d0910 | ||
|  | 6d6a4e79c9 | ||
|  | 417a3e1540 | ||
|  | fa8c804d47 | ||
|  | 68392ce6f5 | ||
|  | 6873f62ad8 | ||
|  | 5f385e15f6 | ||
|  | 8c5d37b6ee | ||
|  | 9c3c2192dd | ||
|  | 4f9f73ca81 | ||
|  | 2c9a1f7b16 | ||
|  | 0ea4c1ac80 | ||
|  | a873ec97eb | ||
|  | cc8a65780e | ||
|  | c117deb43b | ||
|  | ae31d45c88 | ||
|  | a0eb20ff1f | ||
|  | 34fe9981e4 | ||
|  | 291e91375f | ||
|  | 857f74b320 | ||
|  | 1d9608efc7 | ||
|  | 93616a4903 | ||
|  | bb07206c55 | ||
|  | 2e5c0811e7 | ||
|  | f6ac407e4d | ||
|  | 078c3135df | ||
|  | 92568c90c8 | ||
|  | f1879c5fbc | ||
|  | 31bb770fdd | ||
|  | e430f2658f | ||
|  | 3060175ff5 | ||
|  | eb4233e2fd | ||
|  | 6b4c656849 | ||
|  | 1b8fada6aa | ||
|  | 7332c64964 | ||
|  | 977f9ee831 | ||
|  | 16fb3b49a5 | ||
|  | 3da1b3bf9b | ||
|  | bc00856c05 | ||
|  | 52e3dece81 | ||
|  | 2c1d8fa18a | ||
|  | 3e34ae67f6 | ||
|  | d6e16d0042 | ||
|  | 8e02d29ae6 | ||
|  | ceebecec8d | ||
|  | 05d1eda422 | ||
|  | 31f318ad43 | ||
|  | 270f46e147 | ||
|  | c0e9c37cc7 | ||
|  | 8564945713 | ||
|  | 7bd7f3fb73 | ||
|  | 5b5bfc8445 | ||
|  | c466b6f9e7 | ||
|  | 407643c575 | ||
|  | d9071ee9f1 | ||
|  | 97e118abfa | ||
|  | 412f091d76 | ||
|  | d9278e9827 | ||
|  | ca1f669e64 | ||
|  | 0298b1b3b7 | ||
|  | 4b1324de77 | ||
|  | 8e8dce9bec | ||
|  | f4350522bf | ||
|  | e2abb66a11 | ||
|  | ab5fcab9bf | ||
|  | cf547ef569 | ||
|  | e75b386f7d | ||
|  | 796203859f | ||
|  | 40f68b70c1 | ||
|  | 40b2fe7339 | ||
|  | a3b6d2d16e | ||
|  | 3983f8303f | ||
|  | 7cbd5e0ef6 | ||
|  | dab9bb6575 | ||
|  | 7df85ea695 | ||
|  | c132bda01c | ||
|  | 4e25bcfcdc | ||
|  | ea463549c7 | ||
|  | 723acb31b3 | ||
|  | 5725db9234 | ||
|  | 8557e563bc | ||
|  | d2491633ce | ||
|  | 002796e5f5 | ||
|  | fa0accf251 | ||
|  | dcb8176d90 | ||
|  | be32b1a198 | ||
|  | 582e4acc11 | ||
|  | 10f75acf71 | ||
|  | b9933f512f | ||
|  | 75a7f7ab22 | ||
|  | 757be2906e | ||
|  | e214584c76 | ||
|  | 0bb6b498ce | ||
|  | 958d44a20d | ||
|  | bb9424d944 | ||
|  | 11bf706aa2 | ||
|  | 033b8e6b36 | ||
|  | 7c3ea7b2ea | ||
|  | a08043ae88 | ||
|  | 7c132a3ed5 | ||
|  | 20e774be1e | ||
|  | 6d6046757d | ||
|  | 55073b0a52 | ||
|  | 44eb4e51ed | ||
|  | 3cb042a49d | ||
|  | b78ea7d24c | ||
|  | c66728dce2 | ||
|  | 0be9a0cb88 | ||
|  | a90f12dab7 | ||
|  | ef33b004f9 | ||
|  | 2cac4b0d74 | ||
|  | a49f516265 | ||
|  | 71ac26944d | ||
|  | 2d97fc1f59 | ||
|  | 9ef7743205 | ||
|  | ee7ae11e90 | ||
|  | f67d7f1db5 | ||
|  | 99981751a2 | ||
|  | ffdf02c5df | ||
|  | 27c7d00a05 | ||
|  | 64c4137e5b | ||
|  | 8c26d0c6e6 | ||
|  | 81dcfd9f85 | ||
|  | 9334557fbf | ||
|  | b09de8efce | ||
|  | 5a50eb56dd | ||
|  | e49b257e94 | ||
|  | b8a0f4e831 | ||
|  | c265ea9847 | ||
|  | 29f8dcfb40 | ||
|  | 0c05983617 | ||
|  | 0bd653708c | ||
|  | 41d800cb63 | ||
|  | cadc0bd509 | ||
|  | b64da2710a | ||
|  | 82b08d0e3a | ||
|  | 8f77d1831b | ||
|  | be722143e1 | ||
|  | d8d974e2d7 | ||
|  | 9b7ca6f271 | ||
|  | 8ce018dbab | ||
|  | 180062c58c | ||
|  | 6076b8df69 | ||
|  | 5e65ee79b1 | ||
|  | c0861c7362 | ||
|  | 37656f14d8 | ||
|  | dec5535e54 | ||
|  | 1f0e3b157a | ||
|  | d802e83f49 | ||
|  | ebcae25762 | ||
|  | 5330267d16 | ||
|  | 892476973b | ||
|  | 84f4a25bc9 | ||
|  | 1460a88bb3 | ||
|  | 62e4c23961 | ||
|  | d25ab35d58 | ||
|  | a223cd90a1 | ||
|  | aef92ba29c | ||
|  | 328d297490 | ||
|  | 3d240f3f18 | ||
|  | 45f35236a7 | ||
|  | fba210f7ce | ||
|  | 8a09e5fc16 | ||
|  | 52e33e861c | ||
|  | 75d8824e6b | ||
|  | 325af677d3 | ||
|  | 1003e70b5e | ||
|  | d70229201d | ||
|  | 823f91605b | ||
|  | 53f75034fc | ||
|  | 78649a5b54 | ||
|  | f48db625a0 | ||
|  | 2ba66c4457 | ||
|  | 2c78ea1a4e | ||
|  | 73f50ac44e | ||
|  | 9ce48953c1 | ||
|  | 1098cd0c6b | ||
|  | 652ebd143c | ||
|  | 8e9d7c0f40 | ||
|  | a64948a2ba | ||
|  | 43f619a081 | ||
|  | a07de97df4 | ||
|  | 85d25068a8 | ||
|  | 7a0319cfe5 | ||
|  | f750671f33 | ||
|  | 7886fe677a | ||
|  | 73c027f8e3 | ||
|  | eda88cc462 | ||
|  | 652f4ebfed | ||
|  | 06a2f59bd0 | ||
|  | 0af57806da | ||
|  | 03f365e696 | ||
|  | 49a22674ba | ||
|  | ec494511ec | ||
|  | af02ce9c6e | ||
|  | 56e42859ab | ||
|  | 2d153359f8 | ||
|  | 068ce23716 | ||
|  | 03be2e3652 | ||
|  | 4ef2c0bed8 | ||
|  | bfd405613c | ||
|  | 73e1c8c780 | ||
|  | 689ba1d4a2 | ||
|  | 39b9d00550 | ||
|  | 64f99d83a4 | ||
|  | 8f1faefa1c | ||
|  | 2c5ff9ada0 | ||
|  | a9ceef5c37 | ||
|  | c6f977ed4b | ||
|  | cb240cd32a | ||
|  | bc6349f823 | ||
|  | a93a1ae40f | ||
|  | 25254255fe | ||
|  | b0b2798f39 | ||
|  | 7f5c637aeb | ||
|  | 42634b500c | ||
|  | 6f0eb5eccd | ||
|  | 3d83891eb0 | ||
|  | 69a2a133d5 | ||
|  | be4b38c76a | ||
|  | 7163b1132c | ||
|  | 3ccec1c996 | ||
|  | 47359dc8f1 | ||
|  | 43532c8455 | ||
|  | d7c3d4ce52 | ||
|  | ed7060a105 | ||
|  | db0da4b741 | ||
|  | c9c16968bb | ||
|  | 87420881c8 | ||
|  | fdc598f2e1 | ||
|  | f679145bd1 | ||
|  | eeb161ec51 | ||
|  | 21cb7307d0 | ||
|  | 412a1eb7ee | ||
|  | 1d801acf72 | ||
|  | 0d7bbdad54 | ||
|  | 53b3d9cf9d | ||
|  | c3ebbfb10e | ||
|  | 58f035e31a | ||
|  | a8f1d98d40 | ||
|  | cf6fa98433 | ||
|  | 937b3ca81d | ||
|  | d0c5cf0d2d | ||
|  | 4cbf2bef82 | ||
|  | 388d808536 | ||
|  | 720aba3f2d | ||
|  | f9101de956 | ||
|  | bb04981280 | ||
|  | 57898ed6dd | ||
|  | 33b53e7605 | ||
|  | 9e8928aad9 | ||
|  | 89c71f9119 | ||
|  | a4f6db6719 | ||
|  | 2d8e65ea32 | ||
|  | 98aa597510 | ||
|  | de56d48b2f | ||
|  | 4aeb9a7c56 | ||
|  | b9b52b7c8b | 
| @@ -59,6 +59,11 @@ KeyboardMachine::Machine *MultiMachine::keyboard_machine() { | |||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
|  | MouseMachine::Machine *MultiMachine::mouse_machine() { | ||||||
|  | 	// TODO. | ||||||
|  | 	return nullptr; | ||||||
|  | } | ||||||
|  |  | ||||||
| Configurable::Device *MultiMachine::configurable_device() { | Configurable::Device *MultiMachine::configurable_device() { | ||||||
| 	if(has_picked_) { | 	if(has_picked_) { | ||||||
| 		return machines_.front()->configurable_device(); | 		return machines_.front()->configurable_device(); | ||||||
|   | |||||||
| @@ -54,6 +54,7 @@ class MultiMachine: public ::Machine::DynamicMachine, public MultiCRTMachine::De | |||||||
| 		Configurable::Device *configurable_device() override; | 		Configurable::Device *configurable_device() override; | ||||||
| 		CRTMachine::Machine *crt_machine() override; | 		CRTMachine::Machine *crt_machine() override; | ||||||
| 		JoystickMachine::Machine *joystick_machine() override; | 		JoystickMachine::Machine *joystick_machine() override; | ||||||
|  | 		MouseMachine::Machine *mouse_machine() override; | ||||||
| 		KeyboardMachine::Machine *keyboard_machine() override; | 		KeyboardMachine::Machine *keyboard_machine() override; | ||||||
| 		MediaTarget::Machine *media_target() override; | 		MediaTarget::Machine *media_target() override; | ||||||
| 		void *raw_pointer() override; | 		void *raw_pointer() override; | ||||||
|   | |||||||
| @@ -17,6 +17,7 @@ enum class Machine { | |||||||
| 	Atari2600, | 	Atari2600, | ||||||
| 	ColecoVision, | 	ColecoVision, | ||||||
| 	Electron, | 	Electron, | ||||||
|  | 	Macintosh, | ||||||
| 	MasterSystem, | 	MasterSystem, | ||||||
| 	MSX, | 	MSX, | ||||||
| 	Oric, | 	Oric, | ||||||
|   | |||||||
| @@ -290,6 +290,7 @@ Analyser::Static::TargetList Analyser::Static::MSX::GetTargets(const Media &medi | |||||||
| 	target->region = target->media.tapes.empty() ? Target::Region::USA : Target::Region::Europe; | 	target->region = target->media.tapes.empty() ? Target::Region::USA : Target::Region::Europe; | ||||||
|  |  | ||||||
| 	// Blindly accept disks for now. | 	// Blindly accept disks for now. | ||||||
|  | 	// TODO: how to spot an MSX disk? | ||||||
| 	target->media.disks = media.disks; | 	target->media.disks = media.disks; | ||||||
| 	target->has_disk_drive = !media.disks.empty(); | 	target->has_disk_drive = !media.disks.empty(); | ||||||
|  |  | ||||||
|   | |||||||
							
								
								
									
										26
									
								
								Analyser/Static/Macintosh/StaticAnalyser.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								Analyser/Static/Macintosh/StaticAnalyser.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,26 @@ | |||||||
|  | // | ||||||
|  | //  StaticAnalyser.cpp | ||||||
|  | //  Clock Signal | ||||||
|  | // | ||||||
|  | //  Created by Thomas Harte on 02/06/2019. | ||||||
|  | //  Copyright © 2019 Thomas Harte. All rights reserved. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | #include "StaticAnalyser.hpp" | ||||||
|  | #include "Target.hpp" | ||||||
|  |  | ||||||
|  | Analyser::Static::TargetList Analyser::Static::Macintosh::GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms) { | ||||||
|  | 	// This analyser can comprehend disks only. | ||||||
|  | 	if(media.disks.empty()) return {}; | ||||||
|  |  | ||||||
|  | 	// If there is at least one disk, wave it through. | ||||||
|  | 	Analyser::Static::TargetList targets; | ||||||
|  |  | ||||||
|  | 	using Target = Analyser::Static::Macintosh::Target; | ||||||
|  | 	auto *target = new Target; | ||||||
|  | 	target->machine = Analyser::Machine::Macintosh; | ||||||
|  | 	target->media = media; | ||||||
|  | 	targets.push_back(std::unique_ptr<Analyser::Static::Target>(target)); | ||||||
|  |  | ||||||
|  | 	return targets; | ||||||
|  | } | ||||||
							
								
								
									
										27
									
								
								Analyser/Static/Macintosh/StaticAnalyser.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								Analyser/Static/Macintosh/StaticAnalyser.hpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,27 @@ | |||||||
|  | // | ||||||
|  | //  StaticAnalyser.hpp | ||||||
|  | //  Clock Signal | ||||||
|  | // | ||||||
|  | //  Created by Thomas Harte on 02/06/2019. | ||||||
|  | //  Copyright © 2019 Thomas Harte. All rights reserved. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | #ifndef Analyser_Static_Macintosh_StaticAnalyser_hpp | ||||||
|  | #define Analyser_Static_Macintosh_StaticAnalyser_hpp | ||||||
|  |  | ||||||
|  | #include "../StaticAnalyser.hpp" | ||||||
|  | #include "../../../Storage/TargetPlatforms.hpp" | ||||||
|  | #include <string> | ||||||
|  |  | ||||||
|  | namespace Analyser { | ||||||
|  | namespace Static { | ||||||
|  | namespace Macintosh { | ||||||
|  |  | ||||||
|  | TargetList GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms); | ||||||
|  |  | ||||||
|  | } | ||||||
|  | } | ||||||
|  | } | ||||||
|  |  | ||||||
|  |  | ||||||
|  | #endif /* Analyser_Static_Macintosh_StaticAnalyser_hpp */ | ||||||
							
								
								
									
										31
									
								
								Analyser/Static/Macintosh/Target.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								Analyser/Static/Macintosh/Target.hpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,31 @@ | |||||||
|  | // | ||||||
|  | //  Target.hpp | ||||||
|  | //  Clock Signal | ||||||
|  | // | ||||||
|  | //  Created by Thomas Harte on 03/06/2019. | ||||||
|  | //  Copyright © 2019 Thomas Harte. All rights reserved. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | #ifndef Analyser_Static_Macintosh_Target_h | ||||||
|  | #define Analyser_Static_Macintosh_Target_h | ||||||
|  |  | ||||||
|  | namespace Analyser { | ||||||
|  | namespace Static { | ||||||
|  | namespace Macintosh { | ||||||
|  |  | ||||||
|  | struct Target: public ::Analyser::Static::Target { | ||||||
|  | 	enum class Model { | ||||||
|  | 		Mac128k, | ||||||
|  | 		Mac512k, | ||||||
|  | 		Mac512ke, | ||||||
|  | 		MacPlus | ||||||
|  | 	}; | ||||||
|  |  | ||||||
|  | 	Model model = Model::Mac512ke; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | } | ||||||
|  | } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #endif /* Analyser_Static_Macintosh_Target_h */ | ||||||
| @@ -21,6 +21,7 @@ | |||||||
| #include "Coleco/StaticAnalyser.hpp" | #include "Coleco/StaticAnalyser.hpp" | ||||||
| #include "Commodore/StaticAnalyser.hpp" | #include "Commodore/StaticAnalyser.hpp" | ||||||
| #include "DiskII/StaticAnalyser.hpp" | #include "DiskII/StaticAnalyser.hpp" | ||||||
|  | #include "Macintosh/StaticAnalyser.hpp" | ||||||
| #include "MSX/StaticAnalyser.hpp" | #include "MSX/StaticAnalyser.hpp" | ||||||
| #include "Oric/StaticAnalyser.hpp" | #include "Oric/StaticAnalyser.hpp" | ||||||
| #include "Sega/StaticAnalyser.hpp" | #include "Sega/StaticAnalyser.hpp" | ||||||
| @@ -35,6 +36,7 @@ | |||||||
| #include "../../Storage/Disk/DiskImage/Formats/AppleDSK.hpp" | #include "../../Storage/Disk/DiskImage/Formats/AppleDSK.hpp" | ||||||
| #include "../../Storage/Disk/DiskImage/Formats/CPCDSK.hpp" | #include "../../Storage/Disk/DiskImage/Formats/CPCDSK.hpp" | ||||||
| #include "../../Storage/Disk/DiskImage/Formats/D64.hpp" | #include "../../Storage/Disk/DiskImage/Formats/D64.hpp" | ||||||
|  | #include "../../Storage/Disk/DiskImage/Formats/MacintoshIMG.hpp" | ||||||
| #include "../../Storage/Disk/DiskImage/Formats/G64.hpp" | #include "../../Storage/Disk/DiskImage/Formats/G64.hpp" | ||||||
| #include "../../Storage/Disk/DiskImage/Formats/DMK.hpp" | #include "../../Storage/Disk/DiskImage/Formats/DMK.hpp" | ||||||
| #include "../../Storage/Disk/DiskImage/Formats/HFE.hpp" | #include "../../Storage/Disk/DiskImage/Formats/HFE.hpp" | ||||||
| @@ -89,7 +91,7 @@ static Media GetMediaAndPlatforms(const std::string &file_name, TargetPlatform:: | |||||||
| 	Format("81", result.tapes, Tape::ZX80O81P, TargetPlatform::ZX8081)											// 81 | 	Format("81", result.tapes, Tape::ZX80O81P, TargetPlatform::ZX8081)											// 81 | ||||||
| 	Format("a26", result.cartridges, Cartridge::BinaryDump, TargetPlatform::Atari2600)							// A26 | 	Format("a26", result.cartridges, Cartridge::BinaryDump, TargetPlatform::Atari2600)							// A26 | ||||||
| 	Format("adf", result.disks, Disk::DiskImageHolder<Storage::Disk::AcornADF>, TargetPlatform::Acorn)			// ADF | 	Format("adf", result.disks, Disk::DiskImageHolder<Storage::Disk::AcornADF>, TargetPlatform::Acorn)			// ADF | ||||||
| 	Format("bin", result.cartridges, Cartridge::BinaryDump, TargetPlatform::AllCartridge)					// BIN | 	Format("bin", result.cartridges, Cartridge::BinaryDump, TargetPlatform::AllCartridge)						// BIN (cartridge dump) | ||||||
| 	Format("cas", result.tapes, Tape::CAS, TargetPlatform::MSX)													// CAS | 	Format("cas", result.tapes, Tape::CAS, TargetPlatform::MSX)													// CAS | ||||||
| 	Format("cdt", result.tapes, Tape::TZX, TargetPlatform::AmstradCPC)											// CDT | 	Format("cdt", result.tapes, Tape::TZX, TargetPlatform::AmstradCPC)											// CDT | ||||||
| 	Format("col", result.cartridges, Cartridge::BinaryDump, TargetPlatform::ColecoVision)						// COL | 	Format("col", result.cartridges, Cartridge::BinaryDump, TargetPlatform::ColecoVision)						// COL | ||||||
| @@ -99,7 +101,8 @@ static Media GetMediaAndPlatforms(const std::string &file_name, TargetPlatform:: | |||||||
| 	Format("do", result.disks, Disk::DiskImageHolder<Storage::Disk::AppleDSK>, TargetPlatform::DiskII)			// DO | 	Format("do", result.disks, Disk::DiskImageHolder<Storage::Disk::AppleDSK>, TargetPlatform::DiskII)			// DO | ||||||
| 	Format("dsd", result.disks, Disk::DiskImageHolder<Storage::Disk::SSD>, TargetPlatform::Acorn)				// DSD | 	Format("dsd", result.disks, Disk::DiskImageHolder<Storage::Disk::SSD>, TargetPlatform::Acorn)				// DSD | ||||||
| 	Format("dsk", result.disks, Disk::DiskImageHolder<Storage::Disk::CPCDSK>, TargetPlatform::AmstradCPC)		// DSK (Amstrad CPC) | 	Format("dsk", result.disks, Disk::DiskImageHolder<Storage::Disk::CPCDSK>, TargetPlatform::AmstradCPC)		// DSK (Amstrad CPC) | ||||||
| 	Format("dsk", result.disks, Disk::DiskImageHolder<Storage::Disk::AppleDSK>, TargetPlatform::DiskII)		// DSK (Apple) | 	Format("dsk", result.disks, Disk::DiskImageHolder<Storage::Disk::AppleDSK>, TargetPlatform::DiskII)			// DSK (Apple II) | ||||||
|  | 	Format("dsk", result.disks, Disk::DiskImageHolder<Storage::Disk::MacintoshIMG>, TargetPlatform::Macintosh)	// DSK (Macintosh) | ||||||
| 	Format("dsk", result.disks, Disk::DiskImageHolder<Storage::Disk::MSXDSK>, TargetPlatform::MSX)				// DSK (MSX) | 	Format("dsk", result.disks, Disk::DiskImageHolder<Storage::Disk::MSXDSK>, TargetPlatform::MSX)				// DSK (MSX) | ||||||
| 	Format("dsk", result.disks, Disk::DiskImageHolder<Storage::Disk::OricMFMDSK>, TargetPlatform::Oric)			// DSK (Oric) | 	Format("dsk", result.disks, Disk::DiskImageHolder<Storage::Disk::OricMFMDSK>, TargetPlatform::Oric)			// DSK (Oric) | ||||||
| 	Format("g64", result.disks, Disk::DiskImageHolder<Storage::Disk::G64>, TargetPlatform::Commodore)			// G64 | 	Format("g64", result.disks, Disk::DiskImageHolder<Storage::Disk::G64>, TargetPlatform::Commodore)			// G64 | ||||||
| @@ -108,6 +111,8 @@ static Media GetMediaAndPlatforms(const std::string &file_name, TargetPlatform:: | |||||||
| 			Disk::DiskImageHolder<Storage::Disk::HFE>, | 			Disk::DiskImageHolder<Storage::Disk::HFE>, | ||||||
| 			TargetPlatform::Acorn | TargetPlatform::AmstradCPC | TargetPlatform::Commodore | TargetPlatform::Oric) | 			TargetPlatform::Acorn | TargetPlatform::AmstradCPC | TargetPlatform::Commodore | TargetPlatform::Oric) | ||||||
| 			// HFE (TODO: switch to AllDisk once the MSX stops being so greedy) | 			// 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("nib", result.disks, Disk::DiskImageHolder<Storage::Disk::NIB>, TargetPlatform::DiskII)				// NIB | 	Format("nib", result.disks, Disk::DiskImageHolder<Storage::Disk::NIB>, TargetPlatform::DiskII)				// NIB | ||||||
| 	Format("o", result.tapes, Tape::ZX80O81P, TargetPlatform::ZX8081)											// O | 	Format("o", result.tapes, Tape::ZX80O81P, TargetPlatform::ZX8081)											// O | ||||||
| 	Format("p", result.tapes, Tape::ZX80O81P, TargetPlatform::ZX8081)											// P | 	Format("p", result.tapes, Tape::ZX80O81P, TargetPlatform::ZX8081)											// P | ||||||
| @@ -173,9 +178,10 @@ TargetList Analyser::Static::GetTargets(const std::string &file_name) { | |||||||
| 	if(potential_platforms & TargetPlatform::ColecoVision)	Append(Coleco); | 	if(potential_platforms & TargetPlatform::ColecoVision)	Append(Coleco); | ||||||
| 	if(potential_platforms & TargetPlatform::Commodore)		Append(Commodore); | 	if(potential_platforms & TargetPlatform::Commodore)		Append(Commodore); | ||||||
| 	if(potential_platforms & TargetPlatform::DiskII)		Append(DiskII); | 	if(potential_platforms & TargetPlatform::DiskII)		Append(DiskII); | ||||||
| 	if(potential_platforms & TargetPlatform::Sega)			Append(Sega); | 	if(potential_platforms & TargetPlatform::Macintosh)		Append(Macintosh); | ||||||
| 	if(potential_platforms & TargetPlatform::MSX)			Append(MSX); | 	if(potential_platforms & TargetPlatform::MSX)			Append(MSX); | ||||||
| 	if(potential_platforms & TargetPlatform::Oric)			Append(Oric); | 	if(potential_platforms & TargetPlatform::Oric)			Append(Oric); | ||||||
|  | 	if(potential_platforms & TargetPlatform::Sega)			Append(Sega); | ||||||
| 	if(potential_platforms & TargetPlatform::ZX8081)		Append(ZX8081); | 	if(potential_platforms & TargetPlatform::ZX8081)		Append(ZX8081); | ||||||
| 	#undef Append | 	#undef Append | ||||||
|  |  | ||||||
|   | |||||||
| @@ -9,6 +9,8 @@ | |||||||
| #ifndef ClockReceiver_hpp | #ifndef ClockReceiver_hpp | ||||||
| #define ClockReceiver_hpp | #define ClockReceiver_hpp | ||||||
|  |  | ||||||
|  | #include "ForceInline.hpp" | ||||||
|  |  | ||||||
| /* | /* | ||||||
| 	Informal pattern for all classes that run from a clock cycle: | 	Informal pattern for all classes that run from a clock cycle: | ||||||
|  |  | ||||||
| @@ -52,79 +54,92 @@ | |||||||
| */ | */ | ||||||
| template <class T> class WrappedInt { | template <class T> class WrappedInt { | ||||||
| 	public: | 	public: | ||||||
| 		constexpr WrappedInt(int l) : length_(l) {} | 		forceinline constexpr WrappedInt(int l) noexcept : length_(l) {} | ||||||
| 		constexpr WrappedInt() : length_(0) {} | 		forceinline constexpr WrappedInt() noexcept : length_(0) {} | ||||||
|  |  | ||||||
| 		T &operator =(const T &rhs) { | 		forceinline T &operator =(const T &rhs) { | ||||||
| 			length_ = rhs.length_; | 			length_ = rhs.length_; | ||||||
| 			return *this; | 			return *this; | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		T &operator +=(const T &rhs) { | 		forceinline T &operator +=(const T &rhs) { | ||||||
| 			length_ += rhs.length_; | 			length_ += rhs.length_; | ||||||
| 			return *static_cast<T *>(this); | 			return *static_cast<T *>(this); | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		T &operator -=(const T &rhs) { | 		forceinline T &operator -=(const T &rhs) { | ||||||
| 			length_ -= rhs.length_; | 			length_ -= rhs.length_; | ||||||
| 			return *static_cast<T *>(this); | 			return *static_cast<T *>(this); | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		T &operator ++() { | 		forceinline T &operator ++() { | ||||||
| 			++ length_; | 			++ length_; | ||||||
| 			return *static_cast<T *>(this); | 			return *static_cast<T *>(this); | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		T &operator ++(int) { | 		forceinline T &operator ++(int) { | ||||||
| 			length_ ++; | 			length_ ++; | ||||||
| 			return *static_cast<T *>(this); | 			return *static_cast<T *>(this); | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		T &operator --() { | 		forceinline T &operator --() { | ||||||
| 			-- length_; | 			-- length_; | ||||||
| 			return *static_cast<T *>(this); | 			return *static_cast<T *>(this); | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		T &operator --(int) { | 		forceinline T &operator --(int) { | ||||||
| 			length_ --; | 			length_ --; | ||||||
| 			return *static_cast<T *>(this); | 			return *static_cast<T *>(this); | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		T &operator %=(const T &rhs) { | 		forceinline T &operator *=(const T &rhs) { | ||||||
|  | 			length_ *= rhs.length_; | ||||||
|  | 			return *static_cast<T *>(this); | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		forceinline T &operator /=(const T &rhs) { | ||||||
|  | 			length_ /= rhs.length_; | ||||||
|  | 			return *static_cast<T *>(this); | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		forceinline T &operator %=(const T &rhs) { | ||||||
| 			length_ %= rhs.length_; | 			length_ %= rhs.length_; | ||||||
| 			return *static_cast<T *>(this); | 			return *static_cast<T *>(this); | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		T &operator &=(const T &rhs) { | 		forceinline T &operator &=(const T &rhs) { | ||||||
| 			length_ &= rhs.length_; | 			length_ &= rhs.length_; | ||||||
| 			return *static_cast<T *>(this); | 			return *static_cast<T *>(this); | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		constexpr T operator +(const T &rhs) const			{	return T(length_ + rhs.length_);	} | 		forceinline constexpr T operator +(const T &rhs) const			{	return T(length_ + rhs.length_);	} | ||||||
| 		constexpr T operator -(const T &rhs) const			{	return T(length_ - rhs.length_);	} | 		forceinline constexpr T operator -(const T &rhs) const			{	return T(length_ - rhs.length_);	} | ||||||
|  |  | ||||||
| 		constexpr T operator %(const T &rhs) const			{	return T(length_ % rhs.length_);	} | 		forceinline constexpr T operator *(const T &rhs) const			{	return T(length_ * rhs.length_);	} | ||||||
| 		constexpr T operator &(const T &rhs) const			{	return T(length_ & rhs.length_);	} | 		forceinline constexpr T operator /(const T &rhs) const			{	return T(length_ / rhs.length_);	} | ||||||
|  |  | ||||||
| 		constexpr T operator -() const						{	return T(- length_);				} | 		forceinline constexpr T operator %(const T &rhs) const			{	return T(length_ % rhs.length_);	} | ||||||
|  | 		forceinline constexpr T operator &(const T &rhs) const			{	return T(length_ & rhs.length_);	} | ||||||
|  |  | ||||||
| 		constexpr bool operator <(const T &rhs) const		{	return length_ < rhs.length_;		} | 		forceinline constexpr T operator -() const						{	return T(- length_);				} | ||||||
| 		constexpr bool operator >(const T &rhs) const		{	return length_ > rhs.length_;		} |  | ||||||
| 		constexpr bool operator <=(const T &rhs) const		{	return length_ <= rhs.length_;		} |  | ||||||
| 		constexpr bool operator >=(const T &rhs) const		{	return length_ >= rhs.length_;		} |  | ||||||
| 		constexpr bool operator ==(const T &rhs) const		{	return length_ == rhs.length_;		} |  | ||||||
| 		constexpr bool operator !=(const T &rhs) const		{	return length_ != rhs.length_;		} |  | ||||||
|  |  | ||||||
| 		constexpr bool operator !() const					{	return !length_;					} | 		forceinline constexpr bool operator <(const T &rhs) const		{	return length_ < rhs.length_;		} | ||||||
|  | 		forceinline constexpr bool operator >(const T &rhs) const		{	return length_ > rhs.length_;		} | ||||||
|  | 		forceinline constexpr bool operator <=(const T &rhs) const		{	return length_ <= rhs.length_;		} | ||||||
|  | 		forceinline constexpr bool operator >=(const T &rhs) const		{	return length_ >= rhs.length_;		} | ||||||
|  | 		forceinline constexpr bool operator ==(const T &rhs) const		{	return length_ == rhs.length_;		} | ||||||
|  | 		forceinline constexpr bool operator !=(const T &rhs) const		{	return length_ != rhs.length_;		} | ||||||
|  |  | ||||||
|  | 		forceinline constexpr bool operator !() const					{	return !length_;					} | ||||||
| 		// bool operator () is not supported because it offers an implicit cast to int, which is prone silently to permit misuse | 		// bool operator () is not supported because it offers an implicit cast to int, which is prone silently to permit misuse | ||||||
|  |  | ||||||
| 		constexpr int as_int() const { return length_; } | 		forceinline constexpr int as_int() const { return length_; } | ||||||
|  |  | ||||||
| 		/*! | 		/*! | ||||||
| 			Severs from @c this the effect of dividing by @c divisor; @c this will end up with | 			Severs from @c this the effect of dividing by @c divisor; @c this will end up with | ||||||
| 			the value of @c this modulo @c divisor and @c divided by @c divisor is returned. | 			the value of @c this modulo @c divisor and @c divided by @c divisor is returned. | ||||||
| 		*/ | 		*/ | ||||||
| 		T divide(const T &divisor) { | 		forceinline T divide(const T &divisor) { | ||||||
| 			T result(length_ / divisor.length_); | 			T result(length_ / divisor.length_); | ||||||
| 			length_ %= divisor.length_; | 			length_ %= divisor.length_; | ||||||
| 			return result; | 			return result; | ||||||
| @@ -134,10 +149,12 @@ template <class T> class WrappedInt { | |||||||
| 			Flushes the value in @c this. The current value is returned, and the internal value | 			Flushes the value in @c this. The current value is returned, and the internal value | ||||||
| 			is reset to zero. | 			is reset to zero. | ||||||
| 		*/ | 		*/ | ||||||
| 		T flush() { | 		template <typename Result> Result flush() { | ||||||
| 			T result(length_); | 			// Jiggery pokery here; switching to function overloading avoids | ||||||
| 			length_ = 0; | 			// the namespace-level requirement for template specialisation. | ||||||
| 			return result; | 			Result r; | ||||||
|  | 			static_cast<T *>(this)->fill(r); | ||||||
|  | 			return r; | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		// operator int() is deliberately not provided, to avoid accidental subtitution of | 		// operator int() is deliberately not provided, to avoid accidental subtitution of | ||||||
| @@ -150,51 +167,59 @@ template <class T> class WrappedInt { | |||||||
| /// Describes an integer number of whole cycles: pairs of clock signal transitions. | /// Describes an integer number of whole cycles: pairs of clock signal transitions. | ||||||
| class Cycles: public WrappedInt<Cycles> { | class Cycles: public WrappedInt<Cycles> { | ||||||
| 	public: | 	public: | ||||||
| 		constexpr Cycles(int l) : WrappedInt<Cycles>(l) {} | 		forceinline constexpr Cycles(int l) noexcept : WrappedInt<Cycles>(l) {} | ||||||
| 		constexpr Cycles() : WrappedInt<Cycles>() {} | 		forceinline constexpr Cycles() noexcept : WrappedInt<Cycles>() {} | ||||||
| 		constexpr Cycles(const Cycles &cycles) : WrappedInt<Cycles>(cycles.length_) {} | 		forceinline constexpr Cycles(const Cycles &cycles) noexcept : WrappedInt<Cycles>(cycles.length_) {} | ||||||
|  |  | ||||||
|  | 	private: | ||||||
|  | 		friend WrappedInt; | ||||||
|  | 		void fill(Cycles &result) { | ||||||
|  | 			result.length_ = length_; | ||||||
|  | 			length_ = 0; | ||||||
|  | 		} | ||||||
| }; | }; | ||||||
|  |  | ||||||
| /// Describes an integer number of half cycles: single clock signal transitions. | /// Describes an integer number of half cycles: single clock signal transitions. | ||||||
| class HalfCycles: public WrappedInt<HalfCycles> { | class HalfCycles: public WrappedInt<HalfCycles> { | ||||||
| 	public: | 	public: | ||||||
| 		constexpr HalfCycles(int l) : WrappedInt<HalfCycles>(l) {} | 		forceinline constexpr HalfCycles(int l) noexcept : WrappedInt<HalfCycles>(l) {} | ||||||
| 		constexpr HalfCycles() : WrappedInt<HalfCycles>() {} | 		forceinline constexpr HalfCycles() noexcept : WrappedInt<HalfCycles>() {} | ||||||
|  |  | ||||||
| 		constexpr HalfCycles(const Cycles cycles) : WrappedInt<HalfCycles>(cycles.as_int() * 2) {} | 		forceinline constexpr HalfCycles(const Cycles &cycles) noexcept : WrappedInt<HalfCycles>(cycles.as_int() * 2) {} | ||||||
| 		constexpr HalfCycles(const HalfCycles &half_cycles) : WrappedInt<HalfCycles>(half_cycles.length_) {} | 		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. | 		/// @returns The number of whole cycles completely covered by this span of half cycles. | ||||||
| 		constexpr Cycles cycles() const { | 		forceinline constexpr Cycles cycles() const { | ||||||
| 			return Cycles(length_ >> 1); | 			return Cycles(length_ >> 1); | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		/// Flushes the whole cycles in @c this, subtracting that many from the total stored here. |  | ||||||
| 		Cycles flush_cycles() { |  | ||||||
| 			Cycles result(length_ >> 1); |  | ||||||
| 			length_ &= 1; |  | ||||||
| 			return result; |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		/// Flushes the half cycles in @c this, returning the number stored and setting this total to zero. |  | ||||||
| 		HalfCycles flush() { |  | ||||||
| 			HalfCycles result(length_); |  | ||||||
| 			length_ = 0; |  | ||||||
| 			return result; |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		/*! | 		/*! | ||||||
| 			Severs from @c this the effect of dividing by @c divisor; @c this will end up with | 			Severs from @c this the effect of dividing by @c divisor; @c this will end up with | ||||||
| 			the value of @c this modulo @c divisor and @c divided by @c divisor is returned. | 			the value of @c this modulo @c divisor and @c divided by @c divisor is returned. | ||||||
| 		*/ | 		*/ | ||||||
| 		Cycles divide_cycles(const Cycles &divisor) { | 		forceinline Cycles divide_cycles(const Cycles &divisor) { | ||||||
| 			HalfCycles half_divisor = HalfCycles(divisor); | 			const HalfCycles half_divisor = HalfCycles(divisor); | ||||||
| 			Cycles result(length_ / half_divisor.length_); | 			const Cycles result(length_ / half_divisor.length_); | ||||||
| 			length_ %= half_divisor.length_; | 			length_ %= half_divisor.length_; | ||||||
| 			return result; | 			return result; | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
|  | 	private: | ||||||
|  | 		friend WrappedInt; | ||||||
|  | 		void fill(Cycles &result) { | ||||||
|  | 			result = Cycles(length_ >> 1); | ||||||
|  | 			length_ &= 1; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		void fill(HalfCycles &result) { | ||||||
|  | 			result.length_ = length_; | ||||||
|  | 			length_ = 0; | ||||||
|  | 		} | ||||||
| }; | }; | ||||||
|  |  | ||||||
|  | // Create a specialisation of WrappedInt::flush for converting HalfCycles to Cycles | ||||||
|  | // without losing the fractional part. | ||||||
|  |  | ||||||
| /*! | /*! | ||||||
| 	If a component implements only run_for(Cycles), an owner can wrap it in HalfClockReceiver | 	If a component implements only run_for(Cycles), an owner can wrap it in HalfClockReceiver | ||||||
| 	automatically to gain run_for(HalfCycles). | 	automatically to gain run_for(HalfCycles). | ||||||
| @@ -203,9 +228,9 @@ template <class T> class HalfClockReceiver: public T { | |||||||
| 	public: | 	public: | ||||||
| 		using T::T; | 		using T::T; | ||||||
|  |  | ||||||
| 		inline void run_for(const HalfCycles half_cycles) { | 		forceinline void run_for(const HalfCycles half_cycles) { | ||||||
| 			half_cycles_ += half_cycles; | 			half_cycles_ += half_cycles; | ||||||
| 			T::run_for(half_cycles_.flush_cycles()); | 			T::run_for(half_cycles_.flush<Cycles>()); | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 	private: | 	private: | ||||||
|   | |||||||
| @@ -1,26 +1,26 @@ | |||||||
| //
 | //
 | ||||||
| //  ClockDeferrer.hpp
 | //  DeferredQueue.hpp
 | ||||||
| //  Clock Signal
 | //  Clock Signal
 | ||||||
| //
 | //
 | ||||||
| //  Created by Thomas Harte on 23/08/2018.
 | //  Created by Thomas Harte on 23/08/2018.
 | ||||||
| //  Copyright © 2018 Thomas Harte. All rights reserved.
 | //  Copyright © 2018 Thomas Harte. All rights reserved.
 | ||||||
| //
 | //
 | ||||||
| 
 | 
 | ||||||
| #ifndef ClockDeferrer_h | #ifndef DeferredQueue_h | ||||||
| #define ClockDeferrer_h | #define DeferredQueue_h | ||||||
| 
 | 
 | ||||||
| #include <functional> | #include <functional> | ||||||
| #include <vector> | #include <vector> | ||||||
| 
 | 
 | ||||||
| /*!
 | /*!
 | ||||||
| 	A ClockDeferrer maintains a list of ordered actions and the times at which | 	A DeferredQueue maintains a list of ordered actions and the times at which | ||||||
| 	they should happen, and divides a total execution period up into the portions | 	they should happen, and divides a total execution period up into the portions | ||||||
| 	that occur between those actions, triggering each action when it is reached. | 	that occur between those actions, triggering each action when it is reached. | ||||||
| */ | */ | ||||||
| template <typename TimeUnit> class ClockDeferrer { | template <typename TimeUnit> class DeferredQueue { | ||||||
| 	public: | 	public: | ||||||
| 		/// Constructs a ClockDeferrer that will call target(period) in between deferred actions.
 | 		/// Constructs a DeferredQueue that will call target(period) in between deferred actions.
 | ||||||
| 		ClockDeferrer(std::function<void(TimeUnit)> &&target) : target_(std::move(target)) {} | 		DeferredQueue(std::function<void(TimeUnit)> &&target) : target_(std::move(target)) {} | ||||||
| 
 | 
 | ||||||
| 		/*!
 | 		/*!
 | ||||||
| 			Schedules @c action to occur in @c delay units of time. | 			Schedules @c action to occur in @c delay units of time. | ||||||
| @@ -79,4 +79,4 @@ template <typename TimeUnit> class ClockDeferrer { | |||||||
| 		std::vector<DeferredAction> pending_actions_; | 		std::vector<DeferredAction> pending_actions_; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| #endif /* ClockDeferrer_h */ | #endif /* DeferredQueue_h */ | ||||||
							
								
								
									
										110
									
								
								ClockReceiver/JustInTime.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										110
									
								
								ClockReceiver/JustInTime.hpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,110 @@ | |||||||
|  | // | ||||||
|  | //  JustInTime.hpp | ||||||
|  | //  Clock Signal | ||||||
|  | // | ||||||
|  | //  Created by Thomas Harte on 28/07/2019. | ||||||
|  | //  Copyright © 2019 Thomas Harte. All rights reserved. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | #ifndef JustInTime_h | ||||||
|  | #define JustInTime_h | ||||||
|  |  | ||||||
|  | #include "../Concurrency/AsyncTaskQueue.hpp" | ||||||
|  |  | ||||||
|  | /*! | ||||||
|  | 	A JustInTimeActor holds (i) an embedded object with a run_for method; and (ii) an amount | ||||||
|  | 	of time since run_for was last called. | ||||||
|  |  | ||||||
|  | 	Time can be added using the += operator. The -> operator can be used to access the | ||||||
|  | 	embedded object. All time accumulated will be pushed to object before the pointer is returned. | ||||||
|  |  | ||||||
|  | 	Machines that accumulate HalfCycle time but supply to a Cycle-counted device may supply a | ||||||
|  | 	separate @c TargetTimeScale at template declaration. | ||||||
|  | */ | ||||||
|  | template <class T, class LocalTimeScale, class TargetTimeScale = LocalTimeScale> class JustInTimeActor { | ||||||
|  | 	public: | ||||||
|  | 		/// Constructs a new JustInTimeActor using the same construction arguments as the included object. | ||||||
|  | 		template<typename... Args> JustInTimeActor(Args&&... args) : object_(std::forward<Args>(args)...) {} | ||||||
|  |  | ||||||
|  | 		/// Adds time to the actor. | ||||||
|  | 		inline void operator += (const LocalTimeScale &rhs) { | ||||||
|  | 			time_since_update_ += rhs; | ||||||
|  | 			is_flushed_ = false; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		/// Flushes all accumulated time and returns a pointer to the included object. | ||||||
|  | 		inline T *operator->() { | ||||||
|  | 			flush(); | ||||||
|  | 			return &object_; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		/// Returns a pointer to the included object without flushing time. | ||||||
|  | 		inline T *last_valid() { | ||||||
|  | 			return &object_; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		/// Flushes all accumulated time. | ||||||
|  | 		inline void flush() { | ||||||
|  | 			if(!is_flushed_) object_.run_for(time_since_update_.template flush<TargetTimeScale>()); | ||||||
|  | 			is_flushed_ = true; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 	private: | ||||||
|  | 		T object_; | ||||||
|  | 		LocalTimeScale time_since_update_; | ||||||
|  | 		bool is_flushed_ = true; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | /*! | ||||||
|  | 	A AsyncJustInTimeActor acts like a JustInTimeActor but additionally contains an AsyncTaskQueue. | ||||||
|  | 	Any time the amount of accumulated time crosses a threshold provided at construction time, | ||||||
|  | 	the object will be updated on the AsyncTaskQueue. | ||||||
|  | */ | ||||||
|  | template <class T, class LocalTimeScale, class TargetTimeScale = LocalTimeScale> class AsyncJustInTimeActor { | ||||||
|  | 	public: | ||||||
|  | 		/// Constructs a new AsyncJustInTimeActor using the same construction arguments as the included object. | ||||||
|  | 		template<typename... Args> AsyncJustInTimeActor(TargetTimeScale threshold, Args&&... args) : | ||||||
|  | 			object_(std::forward<Args>(args)...), | ||||||
|  | 		 	threshold_(threshold) {} | ||||||
|  |  | ||||||
|  | 		/// Adds time to the actor. | ||||||
|  | 		inline void operator += (const LocalTimeScale &rhs) { | ||||||
|  | 			time_since_update_ += rhs; | ||||||
|  | 			if(time_since_update_ >= threshold_) { | ||||||
|  | 				time_since_update_ -= threshold_; | ||||||
|  | 				task_queue_.enqueue([this] () { | ||||||
|  | 					object_.run_for(threshold_); | ||||||
|  | 				}); | ||||||
|  | 			} | ||||||
|  | 			is_flushed_ = false; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		/// Flushes all accumulated time and returns a pointer to the included object. | ||||||
|  | 		inline T *operator->() { | ||||||
|  | 			flush(); | ||||||
|  | 			return &object_; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		/// Returns a pointer to the included object without flushing time. | ||||||
|  | 		inline T *last_valid() { | ||||||
|  | 			return &object_; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		/// Flushes all accumulated time. | ||||||
|  | 		inline void flush() { | ||||||
|  | 			if(!is_flushed_) { | ||||||
|  | 				task_queue_.flush(); | ||||||
|  | 				object_.run_for(time_since_update_.template flush<TargetTimeScale>()); | ||||||
|  | 				is_flushed_ = true; | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 	private: | ||||||
|  | 		T object_; | ||||||
|  | 		LocalTimeScale time_since_update_; | ||||||
|  | 		TargetTimeScale threshold_; | ||||||
|  | 		bool is_flushed_ = true; | ||||||
|  | 		Concurrency::AsyncTaskQueue task_queue_; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | #endif /* JustInTime_h */ | ||||||
| @@ -47,6 +47,12 @@ class PortHandler { | |||||||
|  |  | ||||||
| 		/// Sets the current logical value of the interrupt line. | 		/// Sets the current logical value of the interrupt line. | ||||||
| 		void set_interrupt_status(bool status)									{} | 		void set_interrupt_status(bool status)									{} | ||||||
|  |  | ||||||
|  | 		/// Provides a measure of time elapsed between other calls. | ||||||
|  | 		void run_for(HalfCycles duration)										{} | ||||||
|  |  | ||||||
|  | 		/// Receives passed-on flush() calls from the 6522. | ||||||
|  | 		void flush()															{} | ||||||
| }; | }; | ||||||
|  |  | ||||||
| /*! | /*! | ||||||
| @@ -71,26 +77,6 @@ class IRQDelegatePortHandler: public PortHandler { | |||||||
| 		Delegate *delegate_ = nullptr; | 		Delegate *delegate_ = nullptr; | ||||||
| }; | }; | ||||||
|  |  | ||||||
| class MOS6522Base: public MOS6522Storage { |  | ||||||
| 	public: |  | ||||||
| 		/// Sets the input value of line @c line on port @c port. |  | ||||||
| 		void set_control_line_input(Port port, Line line, bool value); |  | ||||||
|  |  | ||||||
| 		/// Runs for a specified number of half cycles. |  | ||||||
| 		void run_for(const HalfCycles half_cycles); |  | ||||||
|  |  | ||||||
| 		/// Runs for a specified number of cycles. |  | ||||||
| 		void run_for(const Cycles cycles); |  | ||||||
|  |  | ||||||
| 		/// @returns @c true if the IRQ line is currently active; @c false otherwise. |  | ||||||
| 		bool get_interrupt_line(); |  | ||||||
|  |  | ||||||
| 	private: |  | ||||||
| 		inline void do_phase1(); |  | ||||||
| 		inline void do_phase2(); |  | ||||||
| 		virtual void reevaluate_interrupts() = 0; |  | ||||||
| }; |  | ||||||
|  |  | ||||||
| /*! | /*! | ||||||
| 	Implements a template for emulation of the MOS 6522 Versatile Interface Adaptor ('VIA'). | 	Implements a template for emulation of the MOS 6522 Versatile Interface Adaptor ('VIA'). | ||||||
|  |  | ||||||
| @@ -102,7 +88,7 @@ class MOS6522Base: public MOS6522Storage { | |||||||
| 	Consumers should derive their own curiously-recurring-template-pattern subclass, | 	Consumers should derive their own curiously-recurring-template-pattern subclass, | ||||||
| 	implementing bus communications as required. | 	implementing bus communications as required. | ||||||
| */ | */ | ||||||
| template <class T> class MOS6522: public MOS6522Base { | template <class T> class MOS6522: public MOS6522Storage { | ||||||
| 	public: | 	public: | ||||||
| 		MOS6522(T &bus_handler) noexcept : bus_handler_(bus_handler) {} | 		MOS6522(T &bus_handler) noexcept : bus_handler_(bus_handler) {} | ||||||
| 		MOS6522(const MOS6522 &) = delete; | 		MOS6522(const MOS6522 &) = delete; | ||||||
| @@ -116,11 +102,39 @@ template <class T> class MOS6522: public MOS6522Base { | |||||||
| 		/*! @returns the bus handler. */ | 		/*! @returns the bus handler. */ | ||||||
| 		T &bus_handler(); | 		T &bus_handler(); | ||||||
|  |  | ||||||
|  | 		/// Sets the input value of line @c line on port @c port. | ||||||
|  | 		void set_control_line_input(Port port, Line line, bool value); | ||||||
|  |  | ||||||
|  | 		/// Runs for a specified number of half cycles. | ||||||
|  | 		void run_for(const HalfCycles half_cycles); | ||||||
|  |  | ||||||
|  | 		/// Runs for a specified number of cycles. | ||||||
|  | 		void run_for(const Cycles cycles); | ||||||
|  |  | ||||||
|  | 		/// @returns @c true if the IRQ line is currently active; @c false otherwise. | ||||||
|  | 		bool get_interrupt_line(); | ||||||
|  |  | ||||||
|  | 		/// Updates the port handler to the current time and then requests that it flush. | ||||||
|  | 		void flush(); | ||||||
|  |  | ||||||
| 	private: | 	private: | ||||||
|  | 		void do_phase1(); | ||||||
|  | 		void do_phase2(); | ||||||
|  | 		void shift_in(); | ||||||
|  | 		void shift_out(); | ||||||
|  |  | ||||||
| 		T &bus_handler_; | 		T &bus_handler_; | ||||||
|  | 		HalfCycles time_since_bus_handler_call_; | ||||||
|  |  | ||||||
|  | 		void access(int address); | ||||||
|  |  | ||||||
| 		uint8_t get_port_input(Port port, uint8_t output_mask, uint8_t output); | 		uint8_t get_port_input(Port port, uint8_t output_mask, uint8_t output); | ||||||
| 		inline void reevaluate_interrupts(); | 		inline void reevaluate_interrupts(); | ||||||
|  |  | ||||||
|  | 		/// Sets the current intended output value for the port and line; | ||||||
|  | 		/// if this affects the visible output, it will be passed to the handler. | ||||||
|  | 		void set_control_line_output(Port port, Line line, LineState value); | ||||||
|  | 		void evaluate_cb2_output(); | ||||||
| }; | }; | ||||||
|  |  | ||||||
| } | } | ||||||
|   | |||||||
| @@ -1,116 +0,0 @@ | |||||||
| // |  | ||||||
| //  6522Base.cpp |  | ||||||
| //  Clock Signal |  | ||||||
| // |  | ||||||
| //  Created by Thomas Harte on 04/09/2017. |  | ||||||
| //  Copyright 2017 Thomas Harte. All rights reserved. |  | ||||||
| // |  | ||||||
|  |  | ||||||
| #include "../6522.hpp" |  | ||||||
|  |  | ||||||
| using namespace MOS::MOS6522; |  | ||||||
|  |  | ||||||
| void MOS6522Base::set_control_line_input(Port port, Line line, bool value) { |  | ||||||
| 	switch(line) { |  | ||||||
| 		case Line::One: |  | ||||||
| 			if(	value != control_inputs_[port].line_one && |  | ||||||
| 				value == !!(registers_.peripheral_control & (port ? 0x10 : 0x01)) |  | ||||||
| 			) { |  | ||||||
| 				registers_.interrupt_flags |= port ? InterruptFlag::CB1ActiveEdge : InterruptFlag::CA1ActiveEdge; |  | ||||||
| 				reevaluate_interrupts(); |  | ||||||
| 			} |  | ||||||
| 			control_inputs_[port].line_one = value; |  | ||||||
| 		break; |  | ||||||
|  |  | ||||||
| 		case Line::Two: |  | ||||||
| 			// TODO: output modes, but probably elsewhere? |  | ||||||
| 			if(	value != control_inputs_[port].line_two &&							// i.e. value has changed ... |  | ||||||
| 				!(registers_.peripheral_control & (port ? 0x80 : 0x08)) &&			// ... and line is input ... |  | ||||||
| 				value == !!(registers_.peripheral_control & (port ? 0x40 : 0x04))	// ... and it's either high or low, as required |  | ||||||
| 			) { |  | ||||||
| 				registers_.interrupt_flags |= port ? InterruptFlag::CB2ActiveEdge : InterruptFlag::CA2ActiveEdge; |  | ||||||
| 				reevaluate_interrupts(); |  | ||||||
| 			} |  | ||||||
| 			control_inputs_[port].line_two = value; |  | ||||||
| 		break; |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void MOS6522Base::do_phase2() { |  | ||||||
| 	registers_.last_timer[0] = registers_.timer[0]; |  | ||||||
| 	registers_.last_timer[1] = registers_.timer[1]; |  | ||||||
|  |  | ||||||
| 	if(registers_.timer_needs_reload) { |  | ||||||
| 		registers_.timer_needs_reload = false; |  | ||||||
| 		registers_.timer[0] = registers_.timer_latch[0]; |  | ||||||
| 	} else { |  | ||||||
| 		registers_.timer[0] --; |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	registers_.timer[1] --; |  | ||||||
| 	if(registers_.next_timer[0] >= 0) { |  | ||||||
| 		registers_.timer[0] = static_cast<uint16_t>(registers_.next_timer[0]); |  | ||||||
| 		registers_.next_timer[0] = -1; |  | ||||||
| 	} |  | ||||||
| 	if(registers_.next_timer[1] >= 0) { |  | ||||||
| 		registers_.timer[1] = static_cast<uint16_t>(registers_.next_timer[1]); |  | ||||||
| 		registers_.next_timer[1] = -1; |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void MOS6522Base::do_phase1() { |  | ||||||
| 	// IRQ is raised on the half cycle after overflow |  | ||||||
| 	if((registers_.timer[1] == 0xffff) && !registers_.last_timer[1] && timer_is_running_[1]) { |  | ||||||
| 		timer_is_running_[1] = false; |  | ||||||
| 		registers_.interrupt_flags |= InterruptFlag::Timer2; |  | ||||||
| 		reevaluate_interrupts(); |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	if((registers_.timer[0] == 0xffff) && !registers_.last_timer[0] && timer_is_running_[0]) { |  | ||||||
| 		registers_.interrupt_flags |= InterruptFlag::Timer1; |  | ||||||
| 		reevaluate_interrupts(); |  | ||||||
|  |  | ||||||
| 		if(registers_.auxiliary_control&0x40) |  | ||||||
| 			registers_.timer_needs_reload = true; |  | ||||||
| 		else |  | ||||||
| 			timer_is_running_[0] = false; |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
|  |  | ||||||
| /*! Runs for a specified number of half cycles. */ |  | ||||||
| void MOS6522Base::run_for(const HalfCycles half_cycles) { |  | ||||||
| 	int number_of_half_cycles = half_cycles.as_int(); |  | ||||||
|  |  | ||||||
| 	if(is_phase2_) { |  | ||||||
| 		do_phase2(); |  | ||||||
| 		number_of_half_cycles--; |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	while(number_of_half_cycles >= 2) { |  | ||||||
| 		do_phase1(); |  | ||||||
| 		do_phase2(); |  | ||||||
| 		number_of_half_cycles -= 2; |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	if(number_of_half_cycles) { |  | ||||||
| 		do_phase1(); |  | ||||||
| 		is_phase2_ = true; |  | ||||||
| 	} else { |  | ||||||
| 		is_phase2_ = false; |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
|  |  | ||||||
| /*! Runs for a specified number of cycles. */ |  | ||||||
| void MOS6522Base::run_for(const Cycles cycles) { |  | ||||||
| 	int number_of_cycles = cycles.as_int(); |  | ||||||
| 	while(number_of_cycles--) { |  | ||||||
| 		do_phase1(); |  | ||||||
| 		do_phase2(); |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
|  |  | ||||||
| /*! @returns @c true if the IRQ line is currently active; @c false otherwise. */ |  | ||||||
| bool MOS6522Base::get_interrupt_line() { |  | ||||||
| 	uint8_t interrupt_status = registers_.interrupt_flags & registers_.interrupt_enable & 0x7f; |  | ||||||
| 	return !!interrupt_status; |  | ||||||
| } |  | ||||||
| @@ -11,29 +11,58 @@ | |||||||
| namespace MOS { | namespace MOS { | ||||||
| namespace MOS6522 { | namespace MOS6522 { | ||||||
|  |  | ||||||
| template <typename T> void MOS6522<T>::set_register(int address, uint8_t value) { | template <typename T> void MOS6522<T>::access(int address) { | ||||||
| 	address &= 0xf; |  | ||||||
| 	switch(address) { | 	switch(address) { | ||||||
| 		case 0x0: | 		case 0x0: | ||||||
|  | 			// In both handshake and pulse modes, CB2 goes low on any read or write of Port B. | ||||||
|  | 			if(handshake_modes_[1] != HandshakeMode::None) { | ||||||
|  | 				set_control_line_output(Port::B, Line::Two, LineState::Off); | ||||||
|  | 			} | ||||||
|  | 		break; | ||||||
|  |  | ||||||
|  | 		case 0xf: | ||||||
|  | 		case 0x1: | ||||||
|  | 			// In both handshake and pulse modes, CA2 goes low on any read or write of Port A. | ||||||
|  | 			if(handshake_modes_[0] != HandshakeMode::None) { | ||||||
|  | 				set_control_line_output(Port::A, Line::Two, LineState::Off); | ||||||
|  | 			} | ||||||
|  | 		break; | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | template <typename T> void MOS6522<T>::set_register(int address, uint8_t value) { | ||||||
|  | 	address &= 0xf; | ||||||
|  | 	access(address); | ||||||
|  | 	switch(address) { | ||||||
|  | 		case 0x0:	// Write Port B. | ||||||
|  | 			// Store locally and communicate outwards. | ||||||
| 			registers_.output[1] = value; | 			registers_.output[1] = value; | ||||||
| 			bus_handler_.set_port_output(Port::B, value, registers_.data_direction[1]);	// TODO: handshake |  | ||||||
|  | 			bus_handler_.run_for(time_since_bus_handler_call_.flush<HalfCycles>()); | ||||||
|  | 			bus_handler_.set_port_output(Port::B, value, registers_.data_direction[1]); | ||||||
|  |  | ||||||
| 			registers_.interrupt_flags &= ~(InterruptFlag::CB1ActiveEdge | ((registers_.peripheral_control&0x20) ? 0 : InterruptFlag::CB2ActiveEdge)); | 			registers_.interrupt_flags &= ~(InterruptFlag::CB1ActiveEdge | ((registers_.peripheral_control&0x20) ? 0 : InterruptFlag::CB2ActiveEdge)); | ||||||
| 			reevaluate_interrupts(); | 			reevaluate_interrupts(); | ||||||
| 		break; | 		break; | ||||||
| 		case 0xf: | 		case 0xf: | ||||||
| 		case 0x1: | 		case 0x1:	// Write Port A. | ||||||
| 			registers_.output[0] = value; | 			registers_.output[0] = value; | ||||||
| 			bus_handler_.set_port_output(Port::A, value, registers_.data_direction[0]);	// TODO: handshake |  | ||||||
|  | 			bus_handler_.run_for(time_since_bus_handler_call_.flush<HalfCycles>()); | ||||||
|  | 			bus_handler_.set_port_output(Port::A, value, registers_.data_direction[0]); | ||||||
|  |  | ||||||
|  | 			if(handshake_modes_[1] != HandshakeMode::None) { | ||||||
|  | 				set_control_line_output(Port::A, Line::Two, LineState::Off); | ||||||
|  | 			} | ||||||
|  |  | ||||||
| 			registers_.interrupt_flags &= ~(InterruptFlag::CA1ActiveEdge | ((registers_.peripheral_control&0x02) ? 0 : InterruptFlag::CB2ActiveEdge)); | 			registers_.interrupt_flags &= ~(InterruptFlag::CA1ActiveEdge | ((registers_.peripheral_control&0x02) ? 0 : InterruptFlag::CB2ActiveEdge)); | ||||||
| 			reevaluate_interrupts(); | 			reevaluate_interrupts(); | ||||||
| 		break; | 		break; | ||||||
|  |  | ||||||
| 		case 0x2: | 		case 0x2:	// Port B direction. | ||||||
| 			registers_.data_direction[1] = value; | 			registers_.data_direction[1] = value; | ||||||
| 		break; | 		break; | ||||||
| 		case 0x3: | 		case 0x3:	// Port A direction. | ||||||
| 			registers_.data_direction[0] = value; | 			registers_.data_direction[0] = value; | ||||||
| 		break; | 		break; | ||||||
|  |  | ||||||
| @@ -59,32 +88,58 @@ template <typename T> void MOS6522<T>::set_register(int address, uint8_t value) | |||||||
| 		break; | 		break; | ||||||
|  |  | ||||||
| 		// Shift | 		// Shift | ||||||
| 		case 0xa:	registers_.shift = value;				break; | 		case 0xa: | ||||||
|  | 			registers_.shift = value; | ||||||
|  | 			shift_bits_remaining_ = 8; | ||||||
|  | 			registers_.interrupt_flags &= ~InterruptFlag::ShiftRegister; | ||||||
|  | 			reevaluate_interrupts(); | ||||||
|  | 		break; | ||||||
|  |  | ||||||
| 		// Control | 		// Control | ||||||
| 		case 0xb: | 		case 0xb: | ||||||
| 			registers_.auxiliary_control = value; | 			registers_.auxiliary_control = value; | ||||||
|  | 			evaluate_cb2_output(); | ||||||
| 		break; | 		break; | ||||||
| 		case 0xc: | 		case 0xc: { | ||||||
|  | //			const auto old_peripheral_control = registers_.peripheral_control; | ||||||
| 			registers_.peripheral_control = value; | 			registers_.peripheral_control = value; | ||||||
|  |  | ||||||
| 			// TODO: simplify below; trying to avoid improper logging of unimplemented warnings in input mode | 			int shift = 0; | ||||||
| 			if(value & 0x08) { | 			for(int port = 0; port < 2; ++port) { | ||||||
| 				switch(value & 0x0e) { | 				handshake_modes_[port] = HandshakeMode::None; | ||||||
| 					default: 	LOG("Unimplemented control line mode " << int((value >> 1)&7));		break; | 				switch((value >> shift) & 0x0e) { | ||||||
| 					case 0x0c:	bus_handler_.set_control_line_output(Port::A, Line::Two, false);	break; | 					default: break; | ||||||
| 					case 0x0e:	bus_handler_.set_control_line_output(Port::A, Line::Two, true);		break; |  | ||||||
| 				} | 					case 0x00:	// Negative interrupt input; set Cx2 interrupt on negative Cx2 transition, clear on access to Port x register. | ||||||
| 			} | 					case 0x02:	// Independent negative interrupt input; set Cx2 interrupt on negative transition, don't clear automatically. | ||||||
| 			if(value & 0x80) { | 					case 0x04:	// Positive interrupt input; set Cx2 interrupt on positive Cx2 transition, clear on access to Port x register. | ||||||
| 				switch(value & 0xe0) { | 					case 0x06:	// Independent positive interrupt input; set Cx2 interrupt on positive transition, don't clear automatically. | ||||||
| 					default: 	LOG("Unimplemented control line mode " << int((value >> 5)&7));		break; | 						set_control_line_output(Port(port), Line::Two, LineState::Input); | ||||||
| 					case 0xc0:	bus_handler_.set_control_line_output(Port::B, Line::Two, false);	break; |  | ||||||
| 					case 0xe0:	bus_handler_.set_control_line_output(Port::B, Line::Two, true);		break; |  | ||||||
| 				} |  | ||||||
| 			} |  | ||||||
| 					break; | 					break; | ||||||
|  |  | ||||||
|  | 					case 0x08:	// Handshake: set Cx2 to low on any read or write of Port x; set to high on an active transition of Cx1. | ||||||
|  | 						handshake_modes_[port] = HandshakeMode::Handshake; | ||||||
|  | 						set_control_line_output(Port(port), Line::Two, LineState::Off);	// At a guess. | ||||||
|  | 					break; | ||||||
|  |  | ||||||
|  | 					case 0x0a:	// Pulse output: Cx2 is low for one cycle following a read or write of Port x. | ||||||
|  | 						handshake_modes_[port] = HandshakeMode::Pulse; | ||||||
|  | 						set_control_line_output(Port(port), Line::Two, LineState::On); | ||||||
|  | 					break; | ||||||
|  |  | ||||||
|  | 					case 0x0c:	// Manual output: Cx2 low. | ||||||
|  | 						set_control_line_output(Port(port), Line::Two, LineState::Off); | ||||||
|  | 					break; | ||||||
|  |  | ||||||
|  | 					case 0x0e:	// Manual output: Cx2 high. | ||||||
|  | 						set_control_line_output(Port(port), Line::Two, LineState::On); | ||||||
|  | 					break; | ||||||
|  | 				} | ||||||
|  |  | ||||||
|  | 				shift += 4; | ||||||
|  | 			} | ||||||
|  | 		} break; | ||||||
|  |  | ||||||
| 		// Interrupt control | 		// Interrupt control | ||||||
| 		case 0xd: | 		case 0xd: | ||||||
| 			registers_.interrupt_flags &= ~value; | 			registers_.interrupt_flags &= ~value; | ||||||
| @@ -102,12 +157,13 @@ template <typename T> void MOS6522<T>::set_register(int address, uint8_t value) | |||||||
|  |  | ||||||
| template <typename T> uint8_t MOS6522<T>::get_register(int address) { | template <typename T> uint8_t MOS6522<T>::get_register(int address) { | ||||||
| 	address &= 0xf; | 	address &= 0xf; | ||||||
|  | 	access(address); | ||||||
| 	switch(address) { | 	switch(address) { | ||||||
| 		case 0x0: | 		case 0x0: | ||||||
| 			registers_.interrupt_flags &= ~(InterruptFlag::CB1ActiveEdge | InterruptFlag::CB2ActiveEdge); | 			registers_.interrupt_flags &= ~(InterruptFlag::CB1ActiveEdge | InterruptFlag::CB2ActiveEdge); | ||||||
| 			reevaluate_interrupts(); | 			reevaluate_interrupts(); | ||||||
| 		return get_port_input(Port::B, registers_.data_direction[1], registers_.output[1]); | 		return get_port_input(Port::B, registers_.data_direction[1], registers_.output[1]); | ||||||
| 		case 0xf:	// TODO: handshake, latching | 		case 0xf: | ||||||
| 		case 0x1: | 		case 0x1: | ||||||
| 			registers_.interrupt_flags &= ~(InterruptFlag::CA1ActiveEdge | InterruptFlag::CA2ActiveEdge); | 			registers_.interrupt_flags &= ~(InterruptFlag::CA1ActiveEdge | InterruptFlag::CA2ActiveEdge); | ||||||
| 			reevaluate_interrupts(); | 			reevaluate_interrupts(); | ||||||
| @@ -132,7 +188,11 @@ template <typename T> uint8_t MOS6522<T>::get_register(int address) { | |||||||
| 		return registers_.timer[1] & 0x00ff; | 		return registers_.timer[1] & 0x00ff; | ||||||
| 		case 0x9:	return registers_.timer[1] >> 8; | 		case 0x9:	return registers_.timer[1] >> 8; | ||||||
|  |  | ||||||
| 		case 0xa:	return registers_.shift; | 		case 0xa: | ||||||
|  | 			shift_bits_remaining_ = 8; | ||||||
|  | 			registers_.interrupt_flags &= ~InterruptFlag::ShiftRegister; | ||||||
|  | 			reevaluate_interrupts(); | ||||||
|  | 		return registers_.shift; | ||||||
|  |  | ||||||
| 		case 0xb:	return registers_.auxiliary_control; | 		case 0xb:	return registers_.auxiliary_control; | ||||||
| 		case 0xc:	return registers_.peripheral_control; | 		case 0xc:	return registers_.peripheral_control; | ||||||
| @@ -145,7 +205,8 @@ template <typename T> uint8_t MOS6522<T>::get_register(int address) { | |||||||
| } | } | ||||||
|  |  | ||||||
| template <typename T> uint8_t MOS6522<T>::get_port_input(Port port, uint8_t output_mask, uint8_t output) { | template <typename T> uint8_t MOS6522<T>::get_port_input(Port port, uint8_t output_mask, uint8_t output) { | ||||||
| 	uint8_t input = bus_handler_.get_port_input(port); | 	bus_handler_.run_for(time_since_bus_handler_call_.flush<HalfCycles>()); | ||||||
|  | 	const uint8_t input = bus_handler_.get_port_input(port); | ||||||
| 	return (input & ~output_mask) | (output & output_mask); | 	return (input & ~output_mask) | (output & output_mask); | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -158,9 +219,238 @@ template <typename T> void MOS6522<T>::reevaluate_interrupts() { | |||||||
| 	bool new_interrupt_status = get_interrupt_line(); | 	bool new_interrupt_status = get_interrupt_line(); | ||||||
| 	if(new_interrupt_status != last_posted_interrupt_status_) { | 	if(new_interrupt_status != last_posted_interrupt_status_) { | ||||||
| 		last_posted_interrupt_status_ = new_interrupt_status; | 		last_posted_interrupt_status_ = new_interrupt_status; | ||||||
|  |  | ||||||
|  | 		bus_handler_.run_for(time_since_bus_handler_call_.flush<HalfCycles>()); | ||||||
| 		bus_handler_.set_interrupt_status(new_interrupt_status); | 		bus_handler_.set_interrupt_status(new_interrupt_status); | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
|  | template <typename T> void MOS6522<T>::set_control_line_input(Port port, Line line, bool value) { | ||||||
|  | 	switch(line) { | ||||||
|  | 		case Line::One: | ||||||
|  | 			if(value != control_inputs_[port].lines[line]) { | ||||||
|  | 				// In handshake mode, any transition on C[A/B]1 sets output high on C[A/B]2. | ||||||
|  | 				if(handshake_modes_[port] == HandshakeMode::Handshake) { | ||||||
|  | 					set_control_line_output(port, Line::Two, LineState::On); | ||||||
|  | 				} | ||||||
|  |  | ||||||
|  | 				// Set the proper transition interrupt bit if enabled. | ||||||
|  | 				if(value == !!(registers_.peripheral_control & (port ? 0x10 : 0x01))) { | ||||||
|  | 					registers_.interrupt_flags |= port ? InterruptFlag::CB1ActiveEdge : InterruptFlag::CA1ActiveEdge; | ||||||
|  | 					reevaluate_interrupts(); | ||||||
|  | 				} | ||||||
|  |  | ||||||
|  | 				// If this is a transition on CB1, consider updating the shift register. | ||||||
|  | 				// TODO: and at least one full clock since the shift register was written? | ||||||
|  | 				if(port == Port::B) { | ||||||
|  | 					switch(shift_mode()) { | ||||||
|  | 						default: 													break; | ||||||
|  | 						case ShiftMode::InUnderCB1:		if(value)	shift_in();		break;	// Shifts in are captured on a low-to-high transition. | ||||||
|  | 						case ShiftMode::OutUnderCB1:	if(!value)	shift_out();	break;	// Shifts out are updated on a high-to-low transition. | ||||||
|  | 					} | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 			control_inputs_[port].lines[line] = value; | ||||||
|  | 		break; | ||||||
|  |  | ||||||
|  | 		case Line::Two: | ||||||
|  | 			if(	value != control_inputs_[port].lines[line] &&						// i.e. value has changed ... | ||||||
|  | 				!(registers_.peripheral_control & (port ? 0x80 : 0x08)) &&			// ... and line is input ... | ||||||
|  | 				value == !!(registers_.peripheral_control & (port ? 0x40 : 0x04))	// ... and it's either high or low, as required | ||||||
|  | 			) { | ||||||
|  | 				registers_.interrupt_flags |= port ? InterruptFlag::CB2ActiveEdge : InterruptFlag::CA2ActiveEdge; | ||||||
|  | 				reevaluate_interrupts(); | ||||||
|  | 			} | ||||||
|  | 			control_inputs_[port].lines[line] = value; | ||||||
|  | 		break; | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | template <typename T> void MOS6522<T>::do_phase2() { | ||||||
|  | 	++ time_since_bus_handler_call_; | ||||||
|  |  | ||||||
|  | 	registers_.last_timer[0] = registers_.timer[0]; | ||||||
|  | 	registers_.last_timer[1] = registers_.timer[1]; | ||||||
|  |  | ||||||
|  | 	if(registers_.timer_needs_reload) { | ||||||
|  | 		registers_.timer_needs_reload = false; | ||||||
|  | 		registers_.timer[0] = registers_.timer_latch[0]; | ||||||
|  | 	} else { | ||||||
|  | 		registers_.timer[0] --; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	registers_.timer[1] --; | ||||||
|  | 	if(registers_.next_timer[0] >= 0) { | ||||||
|  | 		registers_.timer[0] = static_cast<uint16_t>(registers_.next_timer[0]); | ||||||
|  | 		registers_.next_timer[0] = -1; | ||||||
|  | 	} | ||||||
|  | 	if(registers_.next_timer[1] >= 0) { | ||||||
|  | 		registers_.timer[1] = static_cast<uint16_t>(registers_.next_timer[1]); | ||||||
|  | 		registers_.next_timer[1] = -1; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// In pulse modes, CA2 and CB2 go high again on the next clock edge. | ||||||
|  | 	if(handshake_modes_[1] == HandshakeMode::Pulse) { | ||||||
|  | 		set_control_line_output(Port::B, Line::Two, LineState::On); | ||||||
|  | 	} | ||||||
|  | 	if(handshake_modes_[0] == HandshakeMode::Pulse) { | ||||||
|  | 		set_control_line_output(Port::A, Line::Two, LineState::On); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// If the shift register is shifting according to the input clock, do a shift. | ||||||
|  | 	switch(shift_mode()) { | ||||||
|  | 		default: 											break; | ||||||
|  | 		case ShiftMode::InUnderPhase2:		shift_in();		break; | ||||||
|  | 		case ShiftMode::OutUnderPhase2:		shift_out();	break; | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | template <typename T> void MOS6522<T>::do_phase1() { | ||||||
|  | 	++ time_since_bus_handler_call_; | ||||||
|  |  | ||||||
|  | 	// IRQ is raised on the half cycle after overflow | ||||||
|  | 	if((registers_.timer[1] == 0xffff) && !registers_.last_timer[1] && timer_is_running_[1]) { | ||||||
|  | 		timer_is_running_[1] = false; | ||||||
|  |  | ||||||
|  | 		// If the shift register is shifting according to this timer, do a shift. | ||||||
|  | 		// TODO: "shift register is driven by only the low order 8 bits of timer 2"? | ||||||
|  | 		switch(shift_mode()) { | ||||||
|  | 			default: 												break; | ||||||
|  | 			case ShiftMode::InUnderT2:				shift_in();		break; | ||||||
|  | 			case ShiftMode::OutUnderT2FreeRunning: 	shift_out();	break; | ||||||
|  | 			case ShiftMode::OutUnderT2:				shift_out();	break;	// TODO: present a clock on CB1. | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		registers_.interrupt_flags |= InterruptFlag::Timer2; | ||||||
|  | 		reevaluate_interrupts(); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if((registers_.timer[0] == 0xffff) && !registers_.last_timer[0] && timer_is_running_[0]) { | ||||||
|  | 		registers_.interrupt_flags |= InterruptFlag::Timer1; | ||||||
|  | 		reevaluate_interrupts(); | ||||||
|  |  | ||||||
|  | 		// Determine whether to reload. | ||||||
|  | 		if(registers_.auxiliary_control&0x40) | ||||||
|  | 			registers_.timer_needs_reload = true; | ||||||
|  | 		else | ||||||
|  | 			timer_is_running_[0] = false; | ||||||
|  |  | ||||||
|  | 		// Determine whether to toggle PB7. | ||||||
|  | 		if(registers_.auxiliary_control&0x80) { | ||||||
|  | 			registers_.output[1] ^= 0x80; | ||||||
|  | 			bus_handler_.run_for(time_since_bus_handler_call_.flush<HalfCycles>()); | ||||||
|  | 			bus_handler_.set_port_output(Port::B, registers_.output[1], registers_.data_direction[1]); | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /*! Runs for a specified number of half cycles. */ | ||||||
|  | template <typename T> void MOS6522<T>::run_for(const HalfCycles half_cycles) { | ||||||
|  | 	int number_of_half_cycles = half_cycles.as_int(); | ||||||
|  | 	if(!number_of_half_cycles) return; | ||||||
|  |  | ||||||
|  | 	if(is_phase2_) { | ||||||
|  | 		do_phase2(); | ||||||
|  | 		number_of_half_cycles--; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	while(number_of_half_cycles >= 2) { | ||||||
|  | 		do_phase1(); | ||||||
|  | 		do_phase2(); | ||||||
|  | 		number_of_half_cycles -= 2; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if(number_of_half_cycles) { | ||||||
|  | 		do_phase1(); | ||||||
|  | 		is_phase2_ = true; | ||||||
|  | 	} else { | ||||||
|  | 		is_phase2_ = false; | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | template <typename T> void MOS6522<T>::flush() { | ||||||
|  | 	bus_handler_.run_for(time_since_bus_handler_call_.flush<HalfCycles>()); | ||||||
|  | 	bus_handler_.flush(); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /*! Runs for a specified number of cycles. */ | ||||||
|  | template <typename T> void MOS6522<T>::run_for(const Cycles cycles) { | ||||||
|  | 	int number_of_cycles = cycles.as_int(); | ||||||
|  | 	while(number_of_cycles--) { | ||||||
|  | 		do_phase1(); | ||||||
|  | 		do_phase2(); | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /*! @returns @c true if the IRQ line is currently active; @c false otherwise. */ | ||||||
|  | template <typename T> bool MOS6522<T>::get_interrupt_line() { | ||||||
|  | 	uint8_t interrupt_status = registers_.interrupt_flags & registers_.interrupt_enable & 0x7f; | ||||||
|  | 	return !!interrupt_status; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | template <typename T> void MOS6522<T>::evaluate_cb2_output() { | ||||||
|  | 	// CB2 is a special case, being both the line the shift register can output to, | ||||||
|  | 	// and one that can be used as an input or handshaking output according to the | ||||||
|  | 	// peripheral control register. | ||||||
|  |  | ||||||
|  | 	// My guess: other CB2 functions work only if the shift register is disabled (?). | ||||||
|  | 	if(shift_mode() != ShiftMode::Disabled) { | ||||||
|  | 		// Shift register is enabled, one way or the other; but announce only output. | ||||||
|  | 		if(is_shifting_out()) { | ||||||
|  | 			// Output mode; set the level according to the current top of the shift register. | ||||||
|  | 			bus_handler_.set_control_line_output(Port::B, Line::Two, !!(registers_.shift & 0x80)); | ||||||
|  | 		} else { | ||||||
|  | 			// Input mode. | ||||||
|  | 			bus_handler_.set_control_line_output(Port::B, Line::Two, true); | ||||||
|  | 		} | ||||||
|  | 	} else { | ||||||
|  | 		// Shift register is disabled. | ||||||
|  | 		bus_handler_.set_control_line_output(Port::B, Line::Two, control_outputs_[1].lines[1] != LineState::Off); | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | template <typename T> void MOS6522<T>::set_control_line_output(Port port, Line line, LineState value) { | ||||||
|  | 	if(port == Port::B && line == Line::Two) { | ||||||
|  | 		control_outputs_[port].lines[line] = value; | ||||||
|  | 		evaluate_cb2_output(); | ||||||
|  | 	} else { | ||||||
|  | 		// Do nothing if unchanged. | ||||||
|  | 		if(value == control_outputs_[port].lines[line]) { | ||||||
|  | 			return; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		control_outputs_[port].lines[line] = value; | ||||||
|  |  | ||||||
|  | 		if(value != LineState::Input) { | ||||||
|  | 			bus_handler_.run_for(time_since_bus_handler_call_.flush<HalfCycles>()); | ||||||
|  | 			bus_handler_.set_control_line_output(port, line, value != LineState::Off); | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | template <typename T> void MOS6522<T>::shift_in() { | ||||||
|  | 	registers_.shift = uint8_t((registers_.shift << 1) | (control_inputs_[1].lines[1] ? 1 : 0)); | ||||||
|  | 	--shift_bits_remaining_; | ||||||
|  | 	if(!shift_bits_remaining_) { | ||||||
|  | 		registers_.interrupt_flags |= InterruptFlag::ShiftRegister; | ||||||
|  | 		reevaluate_interrupts(); | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | template <typename T> void MOS6522<T>::shift_out() { | ||||||
|  | 	// When shifting out, the shift register rotates rather than strictly shifts. | ||||||
|  | 	// TODO: is that true for all modes? | ||||||
|  | 	if(shift_mode() == ShiftMode::OutUnderT2FreeRunning || shift_bits_remaining_) { | ||||||
|  | 		registers_.shift = uint8_t((registers_.shift << 1) | (registers_.shift >> 7)); | ||||||
|  | 		evaluate_cb2_output(); | ||||||
|  |  | ||||||
|  | 		--shift_bits_remaining_; | ||||||
|  | 		if(!shift_bits_remaining_) { | ||||||
|  | 			registers_.interrupt_flags |= InterruptFlag::ShiftRegister; | ||||||
|  | 			reevaluate_interrupts(); | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
| } | } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -37,14 +37,27 @@ class MOS6522Storage { | |||||||
| 			bool timer_needs_reload = false; | 			bool timer_needs_reload = false; | ||||||
| 		} registers_; | 		} registers_; | ||||||
|  |  | ||||||
| 		// control state | 		// Control state. | ||||||
| 		struct { | 		struct { | ||||||
| 			bool line_one = false; | 			bool lines[2] = {false, false}; | ||||||
| 			bool line_two = false; |  | ||||||
| 		} control_inputs_[2]; | 		} control_inputs_[2]; | ||||||
|  |  | ||||||
|  | 		enum class LineState { | ||||||
|  | 			On, Off, Input | ||||||
|  | 		}; | ||||||
|  | 		struct { | ||||||
|  | 			LineState lines[2] = {LineState::Input, LineState::Input}; | ||||||
|  | 		} control_outputs_[2]; | ||||||
|  |  | ||||||
|  | 		enum class HandshakeMode { | ||||||
|  | 			None, | ||||||
|  | 			Handshake, | ||||||
|  | 			Pulse | ||||||
|  | 		} handshake_modes_[2] = { HandshakeMode::None, HandshakeMode::None }; | ||||||
|  |  | ||||||
| 		bool timer_is_running_[2] = {false, false}; | 		bool timer_is_running_[2] = {false, false}; | ||||||
| 		bool last_posted_interrupt_status_ = false; | 		bool last_posted_interrupt_status_ = false; | ||||||
|  | 		int shift_bits_remaining_ = 8; | ||||||
|  |  | ||||||
| 		enum InterruptFlag: uint8_t { | 		enum InterruptFlag: uint8_t { | ||||||
| 			CA2ActiveEdge	= 1 << 0, | 			CA2ActiveEdge	= 1 << 0, | ||||||
| @@ -55,6 +68,23 @@ class MOS6522Storage { | |||||||
| 			Timer2			= 1 << 5, | 			Timer2			= 1 << 5, | ||||||
| 			Timer1			= 1 << 6, | 			Timer1			= 1 << 6, | ||||||
| 		}; | 		}; | ||||||
|  |  | ||||||
|  | 		enum class ShiftMode { | ||||||
|  | 			Disabled = 0, | ||||||
|  | 			InUnderT2 = 1, | ||||||
|  | 			InUnderPhase2 = 2, | ||||||
|  | 			InUnderCB1 = 3, | ||||||
|  | 			OutUnderT2FreeRunning = 4, | ||||||
|  | 			OutUnderT2 = 5, | ||||||
|  | 			OutUnderPhase2 = 6, | ||||||
|  | 			OutUnderCB1 = 7 | ||||||
|  | 		}; | ||||||
|  | 		ShiftMode shift_mode() const { | ||||||
|  | 			return ShiftMode((registers_.auxiliary_control >> 2) & 7); | ||||||
|  | 		} | ||||||
|  | 		bool is_shifting_out() const { | ||||||
|  | 			return registers_.auxiliary_control & 0x10; | ||||||
|  | 		} | ||||||
| }; | }; | ||||||
|  |  | ||||||
| } | } | ||||||
|   | |||||||
							
								
								
									
										268
									
								
								Components/8530/z8530.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										268
									
								
								Components/8530/z8530.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,268 @@ | |||||||
|  | // | ||||||
|  | //  8530.cpp | ||||||
|  | //  Clock Signal | ||||||
|  | // | ||||||
|  | //  Created by Thomas Harte on 07/06/2019. | ||||||
|  | //  Copyright © 2019 Thomas Harte. All rights reserved. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | #include "z8530.hpp" | ||||||
|  |  | ||||||
|  | #include "../../Outputs/Log.hpp" | ||||||
|  |  | ||||||
|  | using namespace Zilog::SCC; | ||||||
|  |  | ||||||
|  | void z8530::reset() { | ||||||
|  | 	// TODO. | ||||||
|  | } | ||||||
|  |  | ||||||
|  | bool z8530::get_interrupt_line() { | ||||||
|  | 	return | ||||||
|  | 		(master_interrupt_control_ & 0x8) && | ||||||
|  | 		( | ||||||
|  | 			channels_[0].get_interrupt_line() || | ||||||
|  | 			channels_[1].get_interrupt_line() | ||||||
|  | 		); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | std::uint8_t z8530::read(int address) { | ||||||
|  | 	if(address & 2) { | ||||||
|  | 		// Read data register for channel | ||||||
|  | 		return 0x00; | ||||||
|  | 	} else { | ||||||
|  | 		// Read control register for channel. | ||||||
|  | 		uint8_t result = 0; | ||||||
|  |  | ||||||
|  | 		switch(pointer_) { | ||||||
|  | 			default: | ||||||
|  | 				result = channels_[address & 1].read(address & 2, pointer_); | ||||||
|  | 			break; | ||||||
|  |  | ||||||
|  | 			case 2:		// Handled non-symmetrically between channels. | ||||||
|  | 				if(address & 1) { | ||||||
|  | 					LOG("[SCC] Unimplemented: register 2 status bits"); | ||||||
|  | 				} else { | ||||||
|  | 					result = interrupt_vector_; | ||||||
|  |  | ||||||
|  | 					// Modify the vector if permitted. | ||||||
|  | //					if(master_interrupt_control_ & 1) { | ||||||
|  | 						for(int port = 0; port < 2; ++port) { | ||||||
|  | 							// TODO: the logic below assumes that DCD is the only implemented interrupt. Fix. | ||||||
|  | 							if(channels_[port].get_interrupt_line()) { | ||||||
|  | 								const uint8_t shift = 1 + 3*((master_interrupt_control_ & 0x10) >> 4); | ||||||
|  | 								const uint8_t mask = uint8_t(~(7 << shift)); | ||||||
|  | 								result = uint8_t( | ||||||
|  | 									(result & mask) | | ||||||
|  | 									((1 | ((port == 1) ? 4 : 0)) << shift) | ||||||
|  | 								); | ||||||
|  | 								break; | ||||||
|  | 							} | ||||||
|  | 						} | ||||||
|  | //					} | ||||||
|  | 				} | ||||||
|  | 			break; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		pointer_ = 0; | ||||||
|  | 		update_delegate(); | ||||||
|  | 		return result; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return 0x00; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void z8530::write(int address, std::uint8_t value) { | ||||||
|  | 	if(address & 2) { | ||||||
|  | 		// Write data register for channel. | ||||||
|  | 	} else { | ||||||
|  | 		// Write control register for channel. | ||||||
|  |  | ||||||
|  | 		// Most registers are per channel, but a couple are shared; sever | ||||||
|  | 		// them here. | ||||||
|  | 		switch(pointer_) { | ||||||
|  | 			default: | ||||||
|  | 				channels_[address & 1].write(address & 2, pointer_, value); | ||||||
|  | 			break; | ||||||
|  |  | ||||||
|  | 			case 2:	// Interrupt vector register; shared between both channels. | ||||||
|  | 				interrupt_vector_ = value; | ||||||
|  | 				LOG("[SCC] Interrupt vector set to " << PADHEX(2) << int(value)); | ||||||
|  | 			break; | ||||||
|  |  | ||||||
|  | 			case 9:	// Master interrupt and reset register; also shared between both channels. | ||||||
|  | 				LOG("[SCC] Master interrupt and reset register: " << PADHEX(2) << int(value)); | ||||||
|  | 				master_interrupt_control_ = value; | ||||||
|  | 			break; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		// The pointer number resets to 0 after every access, but if it is zero | ||||||
|  | 		// then crib at least the next set of pointer bits (which, similarly, are shared | ||||||
|  | 		// between the two channels). | ||||||
|  | 		if(pointer_) { | ||||||
|  | 			pointer_ = 0; | ||||||
|  | 		} else { | ||||||
|  | 			// The lowest three bits are the lowest three bits of the pointer. | ||||||
|  | 			pointer_ = value & 7; | ||||||
|  |  | ||||||
|  | 			// If the command part of the byte is a 'point high', also set the | ||||||
|  | 			// top bit of the pointer. | ||||||
|  | 			if(((value >> 3)&7) == 1) { | ||||||
|  | 				pointer_ |= 8; | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	update_delegate(); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void z8530::set_dcd(int port, bool level) { | ||||||
|  | 	channels_[port].set_dcd(level); | ||||||
|  | 	update_delegate(); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // MARK: - Channel implementations | ||||||
|  |  | ||||||
|  | uint8_t z8530::Channel::read(bool data, uint8_t pointer) { | ||||||
|  | 	// If this is a data read, just return it. | ||||||
|  | 	if(data) { | ||||||
|  | 		return data_; | ||||||
|  | 	} else { | ||||||
|  | 		// Otherwise, this is a control read... | ||||||
|  | 		switch(pointer) { | ||||||
|  | 			default: | ||||||
|  | 				LOG("[SCC] Unrecognised control read from register " << int(pointer)); | ||||||
|  | 			return 0x00; | ||||||
|  |  | ||||||
|  | 			case 0: | ||||||
|  | 			return dcd_ ? 0x8 : 0x0; | ||||||
|  |  | ||||||
|  | 			case 0xf: | ||||||
|  | 			return external_interrupt_status_; | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return 0x00; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void z8530::Channel::write(bool data, uint8_t pointer, uint8_t value) { | ||||||
|  | 	if(data) { | ||||||
|  | 		data_ = value; | ||||||
|  | 		return; | ||||||
|  | 	} else { | ||||||
|  | 		switch(pointer) { | ||||||
|  | 			default: | ||||||
|  | 				LOG("[SCC] Unrecognised control write: " << PADHEX(2) << int(value) << " to register " << int(pointer)); | ||||||
|  | 			break; | ||||||
|  |  | ||||||
|  | 			case 0x0:	// Write register 0 — CRC reset and other functions. | ||||||
|  | 				// Decode CRC reset instructions. | ||||||
|  | 				switch(value >> 6) { | ||||||
|  | 					default:	/* Do nothing. */		break; | ||||||
|  | 					case 1: | ||||||
|  | 						LOG("[SCC] TODO: reset Rx CRC checker."); | ||||||
|  | 					break; | ||||||
|  | 					case 2: | ||||||
|  | 						LOG("[SCC] TODO: reset Tx CRC checker."); | ||||||
|  | 					break; | ||||||
|  | 					case 3: | ||||||
|  | 						LOG("[SCC] TODO: reset Tx underrun/EOM latch."); | ||||||
|  | 					break; | ||||||
|  | 				} | ||||||
|  |  | ||||||
|  | 				// Decode command code. | ||||||
|  | 				switch((value >> 3)&7) { | ||||||
|  | 					default:	/* Do nothing. */		break; | ||||||
|  | 					case 2: | ||||||
|  | //						LOG("[SCC] reset ext/status interrupts."); | ||||||
|  | 						external_status_interrupt_ = false; | ||||||
|  | 						external_interrupt_status_ = 0; | ||||||
|  | 					break; | ||||||
|  | 					case 3: | ||||||
|  | 						LOG("[SCC] TODO: send abort (SDLC)."); | ||||||
|  | 					break; | ||||||
|  | 					case 4: | ||||||
|  | 						LOG("[SCC] TODO: enable interrupt on next Rx character."); | ||||||
|  | 					break; | ||||||
|  | 					case 5: | ||||||
|  | 						LOG("[SCC] TODO: reset Tx interrupt pending."); | ||||||
|  | 					break; | ||||||
|  | 					case 6: | ||||||
|  | 						LOG("[SCC] TODO: reset error."); | ||||||
|  | 					break; | ||||||
|  | 					case 7: | ||||||
|  | 						LOG("[SCC] TODO: reset highest IUS."); | ||||||
|  | 					break; | ||||||
|  | 				} | ||||||
|  | 			break; | ||||||
|  |  | ||||||
|  | 			case 0x1:	// Write register 1 — Transmit/Receive Interrupt and Data Transfer Mode Definition. | ||||||
|  | 				interrupt_mask_ = value; | ||||||
|  | 			break; | ||||||
|  |  | ||||||
|  | 			case 0x4:	// Write register 4 — Transmit/Receive Miscellaneous Parameters and Modes. | ||||||
|  | 				// Bits 0 and 1 select parity mode. | ||||||
|  | 				if(!(value&1)) { | ||||||
|  | 					parity_ = Parity::Off; | ||||||
|  | 				} else { | ||||||
|  | 					parity_ = (value&2) ? Parity::Even : Parity::Odd; | ||||||
|  | 				} | ||||||
|  |  | ||||||
|  | 				// Bits 2 and 3 select stop bits. | ||||||
|  | 				switch((value >> 2)&3) { | ||||||
|  | 					default:	stop_bits_ = StopBits::Synchronous;			break; | ||||||
|  | 					case 1:		stop_bits_ = StopBits::OneBit;				break; | ||||||
|  | 					case 2:		stop_bits_ = StopBits::OneAndAHalfBits;		break; | ||||||
|  | 					case 3:		stop_bits_ = StopBits::TwoBits;				break; | ||||||
|  | 				} | ||||||
|  |  | ||||||
|  | 				// Bits 4 and 5 pick a sync mode. | ||||||
|  | 				switch((value >> 4)&3) { | ||||||
|  | 					default:	sync_mode_ = Sync::Monosync;	break; | ||||||
|  | 					case 1:		sync_mode_ = Sync::Bisync;		break; | ||||||
|  | 					case 2:		sync_mode_ = Sync::SDLC;		break; | ||||||
|  | 					case 3:		sync_mode_ = Sync::External;	break; | ||||||
|  | 				} | ||||||
|  |  | ||||||
|  | 				// Bits 6 and 7 select a clock rate multiplier, unless synchronous | ||||||
|  | 				// mode is enabled (and this is ignored if sync mode is external). | ||||||
|  | 				if(stop_bits_ == StopBits::Synchronous) { | ||||||
|  | 					clock_rate_multiplier_ = 1; | ||||||
|  | 				} else { | ||||||
|  | 					switch((value >> 6)&3) { | ||||||
|  | 						default:	clock_rate_multiplier_ = 1;		break; | ||||||
|  | 						case 1:		clock_rate_multiplier_ = 16;	break; | ||||||
|  | 						case 2:		clock_rate_multiplier_ = 32;	break; | ||||||
|  | 						case 3:		clock_rate_multiplier_ = 64;	break; | ||||||
|  | 					} | ||||||
|  | 				} | ||||||
|  | 			break; | ||||||
|  |  | ||||||
|  | 			case 0xf:	// Write register 15 — External/Status Interrupt Control. | ||||||
|  | 				external_interrupt_mask_ = value; | ||||||
|  | 			break; | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void z8530::Channel::set_dcd(bool level) { | ||||||
|  | 	if(dcd_ == level) return; | ||||||
|  | 	dcd_ = level; | ||||||
|  |  | ||||||
|  | 	if(external_interrupt_mask_ & 0x8) { | ||||||
|  | 		external_status_interrupt_ = true; | ||||||
|  | 		external_interrupt_status_ |= 0x8; | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | bool z8530::Channel::get_interrupt_line() { | ||||||
|  | 	return | ||||||
|  | 		(interrupt_mask_ & 1) && external_status_interrupt_; | ||||||
|  | 	// TODO: other potential causes of an interrupt. | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void z8530::update_delegate() { | ||||||
|  | 	const bool interrupt_line = get_interrupt_line(); | ||||||
|  | 	if(interrupt_line != previous_interrupt_line_) { | ||||||
|  | 		previous_interrupt_line_ = interrupt_line; | ||||||
|  | 		if(delegate_) delegate_->did_change_interrupt_status(this, interrupt_line); | ||||||
|  | 	} | ||||||
|  | } | ||||||
							
								
								
									
										99
									
								
								Components/8530/z8530.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										99
									
								
								Components/8530/z8530.hpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,99 @@ | |||||||
|  | // | ||||||
|  | //  z8530.hpp | ||||||
|  | //  Clock Signal | ||||||
|  | // | ||||||
|  | //  Created by Thomas Harte on 07/06/2019. | ||||||
|  | //  Copyright © 2019 Thomas Harte. All rights reserved. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | #ifndef z8530_hpp | ||||||
|  | #define z8530_hpp | ||||||
|  |  | ||||||
|  | #include <cstdint> | ||||||
|  |  | ||||||
|  | namespace Zilog { | ||||||
|  | namespace SCC { | ||||||
|  |  | ||||||
|  | /*! | ||||||
|  | 	Models the Zilog 8530 SCC, a serial adaptor. | ||||||
|  | */ | ||||||
|  | class z8530 { | ||||||
|  | 	public: | ||||||
|  | 		/* | ||||||
|  | 			**Interface for emulated machine.** | ||||||
|  |  | ||||||
|  | 			Notes on addressing below: | ||||||
|  |  | ||||||
|  | 			There's no inherent ordering of the two 'address' lines, | ||||||
|  | 			A/B and C/D, but the methods below assume: | ||||||
|  |  | ||||||
|  | 				A/B = A0 | ||||||
|  | 				C/D = A1 | ||||||
|  | 		*/ | ||||||
|  | 		std::uint8_t read(int address); | ||||||
|  | 		void write(int address, std::uint8_t value); | ||||||
|  | 		void reset(); | ||||||
|  | 		bool get_interrupt_line(); | ||||||
|  |  | ||||||
|  | 		struct Delegate { | ||||||
|  | 			virtual void did_change_interrupt_status(z8530 *, bool new_status) = 0; | ||||||
|  | 		}; | ||||||
|  | 		void set_delegate(Delegate *delegate) { | ||||||
|  | 			delegate_ = delegate; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		/* | ||||||
|  | 			**Interface for serial port input.** | ||||||
|  | 		*/ | ||||||
|  | 		void set_dcd(int port, bool level); | ||||||
|  |  | ||||||
|  | 	private: | ||||||
|  | 		class Channel { | ||||||
|  | 			public: | ||||||
|  | 				uint8_t read(bool data, uint8_t pointer); | ||||||
|  | 				void write(bool data, uint8_t pointer, uint8_t value); | ||||||
|  | 				void set_dcd(bool level); | ||||||
|  | 				bool get_interrupt_line(); | ||||||
|  |  | ||||||
|  | 			private: | ||||||
|  | 				uint8_t data_ = 0xff; | ||||||
|  |  | ||||||
|  | 				enum class Parity { | ||||||
|  | 					Even, Odd, Off | ||||||
|  | 				} parity_ = Parity::Off; | ||||||
|  |  | ||||||
|  | 				enum class StopBits { | ||||||
|  | 					Synchronous, OneBit, OneAndAHalfBits, TwoBits | ||||||
|  | 				} stop_bits_ = StopBits::Synchronous; | ||||||
|  |  | ||||||
|  | 				enum class Sync { | ||||||
|  | 					Monosync, Bisync, SDLC, External | ||||||
|  | 				} sync_mode_ = Sync::Monosync; | ||||||
|  |  | ||||||
|  | 				int clock_rate_multiplier_ = 1; | ||||||
|  |  | ||||||
|  | 				uint8_t interrupt_mask_ = 0;			// i.e. Write Register 0x1. | ||||||
|  |  | ||||||
|  | 				uint8_t external_interrupt_mask_ = 0;	// i.e. Write Register 0xf. | ||||||
|  | 				bool external_status_interrupt_ = false; | ||||||
|  | 				uint8_t external_interrupt_status_ = 0; | ||||||
|  |  | ||||||
|  | 				bool dcd_ = false; | ||||||
|  | 		} channels_[2]; | ||||||
|  |  | ||||||
|  | 		uint8_t pointer_ = 0; | ||||||
|  |  | ||||||
|  | 		uint8_t interrupt_vector_ = 0; | ||||||
|  |  | ||||||
|  | 		uint8_t master_interrupt_control_ = 0; | ||||||
|  |  | ||||||
|  | 		bool previous_interrupt_line_ = false; | ||||||
|  | 		void update_delegate(); | ||||||
|  | 		Delegate *delegate_ = nullptr; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | } | ||||||
|  | } | ||||||
|  |  | ||||||
|  |  | ||||||
|  | #endif /* z8530_hpp */ | ||||||
| @@ -22,7 +22,7 @@ namespace  { | |||||||
| DiskII::DiskII(int clock_rate) : | DiskII::DiskII(int clock_rate) : | ||||||
| 	clock_rate_(clock_rate), | 	clock_rate_(clock_rate), | ||||||
| 	inputs_(input_command), | 	inputs_(input_command), | ||||||
| 	drives_{{static_cast<unsigned int>(clock_rate), 300, 1}, {static_cast<unsigned int>(clock_rate), 300, 1}} | 	drives_{{clock_rate, 300, 1}, {clock_rate, 300, 1}} | ||||||
| { | { | ||||||
| 	drives_[0].set_clocking_hint_observer(this); | 	drives_[0].set_clocking_hint_observer(this); | ||||||
| 	drives_[1].set_clocking_hint_observer(this); | 	drives_[1].set_clocking_hint_observer(this); | ||||||
| @@ -211,7 +211,7 @@ void DiskII::set_disk(const std::shared_ptr<Storage::Disk::Disk> &disk, int driv | |||||||
| 	drives_[drive].set_disk(disk); | 	drives_[drive].set_disk(disk); | ||||||
| } | } | ||||||
|  |  | ||||||
| void DiskII::process_event(const Storage::Disk::Track::Event &event) { | void DiskII::process_event(const Storage::Disk::Drive::Event &event) { | ||||||
| 	if(event.type == Storage::Disk::Track::Event::FluxTransition) { | 	if(event.type == Storage::Disk::Track::Event::FluxTransition) { | ||||||
| 		inputs_ &= ~input_flux; | 		inputs_ &= ~input_flux; | ||||||
| 		flux_duration_ = 2;	// Upon detection of a flux transition, the flux flag should stay set for 1us. Emulate that as two cycles. | 		flux_duration_ = 2;	// Upon detection of a flux transition, the flux flag should stay set for 1us. Emulate that as two cycles. | ||||||
|   | |||||||
| @@ -98,7 +98,7 @@ class DiskII: | |||||||
| 		void select_drive(int drive); | 		void select_drive(int drive); | ||||||
|  |  | ||||||
| 		uint8_t trigger_address(int address, uint8_t value); | 		uint8_t trigger_address(int address, uint8_t value); | ||||||
| 		void process_event(const Storage::Disk::Track::Event &event) override; | 		void process_event(const Storage::Disk::Drive::Event &event) override; | ||||||
| 		void set_component_prefers_clocking(ClockingHint::Source *component, ClockingHint::Preference preference) override; | 		void set_component_prefers_clocking(ClockingHint::Source *component, ClockingHint::Preference preference) override; | ||||||
|  |  | ||||||
| 		const int clock_rate_ = 0; | 		const int clock_rate_ = 0; | ||||||
|   | |||||||
							
								
								
									
										376
									
								
								Components/DiskII/IWM.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										376
									
								
								Components/DiskII/IWM.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,376 @@ | |||||||
|  | // | ||||||
|  | //  IWM.cpp | ||||||
|  | //  Clock Signal | ||||||
|  | // | ||||||
|  | //  Created by Thomas Harte on 05/05/2019. | ||||||
|  | //  Copyright © 2019 Thomas Harte. All rights reserved. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | #include "IWM.hpp" | ||||||
|  |  | ||||||
|  | #include "../../Outputs/Log.hpp" | ||||||
|  |  | ||||||
|  | using namespace Apple; | ||||||
|  |  | ||||||
|  | namespace  { | ||||||
|  | 	const int CA0		= 1 << 0; | ||||||
|  | 	const int CA1		= 1 << 1; | ||||||
|  | 	const int CA2		= 1 << 2; | ||||||
|  | 	const int LSTRB		= 1 << 3; | ||||||
|  | 	const int ENABLE	= 1 << 4; | ||||||
|  | 	const int DRIVESEL	= 1 << 5;	/* This means drive select, like on the original Disk II. */ | ||||||
|  | 	const int Q6		= 1 << 6; | ||||||
|  | 	const int Q7		= 1 << 7; | ||||||
|  | 	const int SEL		= 1 << 8;	/* This is an additional input, not available on a Disk II, with a confusingly-similar name to SELECT but a distinct purpose. */ | ||||||
|  | } | ||||||
|  |  | ||||||
|  | IWM::IWM(int clock_rate) : | ||||||
|  | 	clock_rate_(clock_rate) {} | ||||||
|  |  | ||||||
|  | // MARK: - Bus accessors | ||||||
|  |  | ||||||
|  | uint8_t IWM::read(int address) { | ||||||
|  | 	access(address); | ||||||
|  |  | ||||||
|  | 	// Per Inside Macintosh: | ||||||
|  | 	// | ||||||
|  | 	// "Before you can read from any of the disk registers you must set up the state of the IWM so that it | ||||||
|  | 	// can pass the data through to the MC68000's address space where you'll be able to read it. To do that, | ||||||
|  | 	// you must first turn off Q7 by reading or writing dBase+q7L. Then turn on Q6 by accessing dBase+q6H. | ||||||
|  | 	// After that, the IWM will be able to pass data from the disk's RD/SENSE line through to you." | ||||||
|  | 	// | ||||||
|  | 	// My understanding: | ||||||
|  | 	// | ||||||
|  | 	//	Q6 = 1, Q7 = 0 reads the status register. The meaning of the top 'SENSE' bit is then determined by | ||||||
|  | 	//	the CA0,1,2 and SEL switches as described in Inside Macintosh, summarised above as RD/SENSE. | ||||||
|  |  | ||||||
|  | 	if(address&1) { | ||||||
|  | 		return 0xff; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	switch(state_ & (Q6 | Q7 | ENABLE)) { | ||||||
|  | 		default: | ||||||
|  | 			LOG("[IWM] Invalid read\n"); | ||||||
|  | 		return 0xff; | ||||||
|  |  | ||||||
|  | 		// "Read all 1s". | ||||||
|  | //			printf("Reading all 1s\n"); | ||||||
|  | //		return 0xff; | ||||||
|  |  | ||||||
|  | 		case 0: | ||||||
|  | 		case ENABLE: {				/* Read data register. Zeroing afterwards is a guess. */ | ||||||
|  | 			const auto result = data_register_; | ||||||
|  |  | ||||||
|  | 			if(data_register_ & 0x80) { | ||||||
|  | //				printf("\n\nIWM:%02x\n\n", data_register_); | ||||||
|  | //				printf("."); | ||||||
|  | 				data_register_ = 0; | ||||||
|  | 			} | ||||||
|  | //			LOG("Reading data register: " << PADHEX(2) << int(result)); | ||||||
|  |  | ||||||
|  | 			return result; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		case Q6: case Q6|ENABLE: { | ||||||
|  | 			/* | ||||||
|  | 				[If A = 0], Read status register: | ||||||
|  |  | ||||||
|  | 				bits 0-4: same as mode register. | ||||||
|  | 				bit 5: 1 = either /ENBL1 or /ENBL2 is currently low. | ||||||
|  | 				bit 6: 1 = MZ (reserved for future compatibility; should always be read as 0). | ||||||
|  | 				bit 7: 1 = SENSE input high; 0 = SENSE input low. | ||||||
|  |  | ||||||
|  | 				(/ENBL1 is low when the first drive's motor is on; /ENBL2 is low when the second drive's motor is on. | ||||||
|  | 				If the 1-second timer is enabled, motors remain on for one second after being programmatically disabled.) | ||||||
|  | 			*/ | ||||||
|  |  | ||||||
|  | 			return uint8_t( | ||||||
|  | 				(mode_&0x1f) | | ||||||
|  | 				((state_ & ENABLE) ? 0x20 : 0x00) | | ||||||
|  | 				(sense() & 0x80) | ||||||
|  | 			); | ||||||
|  | 		} break; | ||||||
|  |  | ||||||
|  | 		case Q7: case Q7|ENABLE: | ||||||
|  | 			/* | ||||||
|  | 				Read write-handshake register: | ||||||
|  |  | ||||||
|  | 				bits 0-5: reserved for future use (currently read as 1). | ||||||
|  | 				bit 6: 1 = write state (0 = underrun has occurred; 1 = no underrun so far). | ||||||
|  | 				bit 7: 1 = write data buffer ready for data (1 = ready; 0 = busy). | ||||||
|  | 			*/ | ||||||
|  | //			LOG("Reading write handshake: " << PADHEX(2) << (0x3f | write_handshake_)); | ||||||
|  | 		return 0x3f | write_handshake_; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return 0xff; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void IWM::write(int address, uint8_t input) { | ||||||
|  | 	access(address); | ||||||
|  |  | ||||||
|  | 	switch(state_ & (Q6 | Q7 | ENABLE)) { | ||||||
|  | 		default: break; | ||||||
|  |  | ||||||
|  | 		case Q7|Q6: | ||||||
|  | 			/* | ||||||
|  | 				Write mode register: | ||||||
|  |  | ||||||
|  | 				bit 0: 1 = latch mode (should be set in asynchronous mode). | ||||||
|  | 				bit 1: 0 = synchronous handshake protocol; 1 = asynchronous. | ||||||
|  | 				bit 2: 0 = 1-second on-board timer enable; 1 = timer disable. | ||||||
|  | 				bit 3: 0 = slow mode; 1 = fast mode. | ||||||
|  | 				bit 4: 0 = 7Mhz; 1 = 8Mhz (7 or 8 mHz clock descriptor). | ||||||
|  | 				bit 5: 1 = test mode; 0 = normal operation. | ||||||
|  | 				bit 6: 1 = MZ-reset. | ||||||
|  | 				bit 7: reserved for future expansion. | ||||||
|  | 			*/ | ||||||
|  |  | ||||||
|  | 			mode_ = input; | ||||||
|  |  | ||||||
|  | 			switch(mode_ & 0x18) { | ||||||
|  | 				case 0x00:		bit_length_ = Cycles(24);		break;	// slow mode, 7Mhz | ||||||
|  | 				case 0x08:		bit_length_ = Cycles(12);		break;	// fast mode, 7Mhz | ||||||
|  | 				case 0x10:		bit_length_ = Cycles(32);		break;	// slow mode, 8Mhz | ||||||
|  | 				case 0x18:		bit_length_ = Cycles(16);		break;	// fast mode, 8Mhz | ||||||
|  | 			} | ||||||
|  | 			LOG("IWM mode is now " << PADHEX(2) << int(mode_)); | ||||||
|  | 		break; | ||||||
|  |  | ||||||
|  | 		case Q7|Q6|ENABLE:	// Write data register. | ||||||
|  | 			next_output_ = input; | ||||||
|  | 			write_handshake_ &= ~0x80; | ||||||
|  | 		break; | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // MARK: - Switch access | ||||||
|  |  | ||||||
|  | void IWM::access(int address) { | ||||||
|  | 	// Keep a record of switch state; bits in state_ | ||||||
|  | 	// should correlate with the anonymous namespace constants | ||||||
|  | 	// defined at the top of this file — CA0, CA1, etc. | ||||||
|  | 	address &= 0xf; | ||||||
|  | 	const auto mask = 1 << (address >> 1); | ||||||
|  | 	const auto old_state = state_; | ||||||
|  |  | ||||||
|  | 	if(address & 1) { | ||||||
|  | 		state_ |= mask; | ||||||
|  | 	} else { | ||||||
|  | 		state_ &= ~mask; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// React appropriately to ENABLE and DRIVESEL changes, and changes into/out of write mode. | ||||||
|  | 	if(old_state != state_) { | ||||||
|  | 		push_drive_state(); | ||||||
|  |  | ||||||
|  | 		switch(mask) { | ||||||
|  | 			default: break; | ||||||
|  |  | ||||||
|  | 			case ENABLE: | ||||||
|  | 				if(address & 1) { | ||||||
|  | 					if(drives_[active_drive_]) drives_[active_drive_]->set_enabled(true); | ||||||
|  | 				} else { | ||||||
|  | 					// If the 1-second delay is enabled, set up a timer for that. | ||||||
|  | 					if(!(mode_ & 4)) { | ||||||
|  | 						cycles_until_disable_ = Cycles(clock_rate_); | ||||||
|  | 					} else { | ||||||
|  | 						if(drives_[active_drive_]) drives_[active_drive_]->set_enabled(false); | ||||||
|  | 					} | ||||||
|  | 				} | ||||||
|  | 			break; | ||||||
|  |  | ||||||
|  | 			case DRIVESEL: { | ||||||
|  | 				const int new_drive = address & 1; | ||||||
|  | 				if(new_drive != active_drive_) { | ||||||
|  | 					if(drives_[active_drive_]) drives_[active_drive_]->set_enabled(false); | ||||||
|  | 					active_drive_ = new_drive; | ||||||
|  | 					if(drives_[active_drive_]) { | ||||||
|  | 						drives_[active_drive_]->set_enabled(state_ & ENABLE || (cycles_until_disable_ > Cycles(0))); | ||||||
|  | 						push_drive_state(); | ||||||
|  | 					} | ||||||
|  | 				} | ||||||
|  | 			} break; | ||||||
|  |  | ||||||
|  | 			case Q6: | ||||||
|  | 			case Q7: | ||||||
|  | 				select_shift_mode(); | ||||||
|  | 			break; | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void IWM::set_select(bool enabled) { | ||||||
|  | 	// Store SEL as an extra state bit. | ||||||
|  | 	if(enabled) state_ |= SEL; | ||||||
|  | 	else state_ &= ~SEL; | ||||||
|  | 	push_drive_state(); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void IWM::push_drive_state() { | ||||||
|  | 	if(drives_[active_drive_])  { | ||||||
|  | 		const uint8_t drive_control_lines = | ||||||
|  | 			((state_ & CA0) ? IWMDrive::CA0 : 0) | | ||||||
|  | 			((state_ & CA1) ? IWMDrive::CA1 : 0) | | ||||||
|  | 			((state_ & CA2) ? IWMDrive::CA2 : 0) | | ||||||
|  | 			((state_ & SEL) ? IWMDrive::SEL : 0) | | ||||||
|  | 			((state_ & LSTRB) ? IWMDrive::LSTRB : 0); | ||||||
|  | 		drives_[active_drive_]->set_control_lines(drive_control_lines); | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // MARK: - Active logic | ||||||
|  |  | ||||||
|  | void IWM::run_for(const Cycles cycles) { | ||||||
|  | 	// Check for a timeout of the motor-off timer. | ||||||
|  | 	if(cycles_until_disable_ > Cycles(0)) { | ||||||
|  | 		cycles_until_disable_ -= cycles; | ||||||
|  | 		if(cycles_until_disable_ <= Cycles(0)) { | ||||||
|  | 			cycles_until_disable_ = Cycles(0); | ||||||
|  | 			if(drives_[active_drive_]) | ||||||
|  | 				drives_[active_drive_]->set_enabled(false); | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// Activity otherwise depends on mode and motor state. | ||||||
|  | 	int integer_cycles = cycles.as_int(); | ||||||
|  | 	switch(shift_mode_) { | ||||||
|  | 		case ShiftMode::Reading: | ||||||
|  | 			if(drive_is_rotating_[active_drive_]) { | ||||||
|  | 				while(integer_cycles--) { | ||||||
|  | 					drives_[active_drive_]->run_for(Cycles(1)); | ||||||
|  | 					++cycles_since_shift_; | ||||||
|  | 					if(cycles_since_shift_ == bit_length_ + Cycles(2)) { | ||||||
|  | 						propose_shift(0); | ||||||
|  | 					} | ||||||
|  | 				} | ||||||
|  | 			} else { | ||||||
|  | 				while(cycles_since_shift_ + integer_cycles >= bit_length_ + Cycles(2)) { | ||||||
|  | 					propose_shift(0); | ||||||
|  | 					integer_cycles -= bit_length_.as_int() + 2 - cycles_since_shift_.as_int(); | ||||||
|  | 				} | ||||||
|  | 				cycles_since_shift_ += Cycles(integer_cycles); | ||||||
|  | 			} | ||||||
|  | 		break; | ||||||
|  |  | ||||||
|  | 		case ShiftMode::Writing: | ||||||
|  | 			if(drives_[active_drive_]->is_writing()) { | ||||||
|  | 				while(cycles_since_shift_ + integer_cycles >= bit_length_) { | ||||||
|  | 					const auto cycles_until_write = bit_length_ - cycles_since_shift_; | ||||||
|  | 					drives_[active_drive_]->run_for(cycles_until_write); | ||||||
|  |  | ||||||
|  | 					// Output a flux transition if the top bit is set. | ||||||
|  | 					drives_[active_drive_]->write_bit(shift_register_ & 0x80); | ||||||
|  | 					shift_register_ <<= 1; | ||||||
|  |  | ||||||
|  | 					integer_cycles -= cycles_until_write.as_int(); | ||||||
|  | 					cycles_since_shift_ = Cycles(0); | ||||||
|  |  | ||||||
|  | 					--output_bits_remaining_; | ||||||
|  | 					if(!output_bits_remaining_) { | ||||||
|  | 						if(!(write_handshake_ & 0x80)) { | ||||||
|  | 							write_handshake_ |= 0x80; | ||||||
|  | 							shift_register_ = next_output_; | ||||||
|  | 							output_bits_remaining_ = 8; | ||||||
|  | //							LOG("Next byte: " << PADHEX(2) << int(shift_register_)); | ||||||
|  | 						} else { | ||||||
|  | 							write_handshake_ &= ~0x40; | ||||||
|  | 							drives_[active_drive_]->end_writing(); | ||||||
|  | //							printf("\n"); | ||||||
|  | 							LOG("Overrun; done."); | ||||||
|  | 							select_shift_mode(); | ||||||
|  | 						} | ||||||
|  | 					} | ||||||
|  | 				} | ||||||
|  |  | ||||||
|  | 				cycles_since_shift_ = integer_cycles; | ||||||
|  | 				if(integer_cycles) { | ||||||
|  | 					drives_[active_drive_]->run_for(cycles_since_shift_); | ||||||
|  | 				} | ||||||
|  | 			} else { | ||||||
|  | 				drives_[active_drive_]->run_for(cycles); | ||||||
|  | 			} | ||||||
|  | 		break; | ||||||
|  |  | ||||||
|  | 		case ShiftMode::CheckingWriteProtect: | ||||||
|  | 			if(integer_cycles < 8) { | ||||||
|  | 				shift_register_ = (shift_register_ >> integer_cycles) | (sense() & (0xff << (8 - integer_cycles))); | ||||||
|  | 			} else { | ||||||
|  | 				shift_register_ = sense(); | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 		/* Deliberate fallthrough. */ | ||||||
|  | 		default: | ||||||
|  | 			if(drive_is_rotating_[active_drive_]) drives_[active_drive_]->run_for(cycles); | ||||||
|  | 		break; | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void IWM::select_shift_mode() { | ||||||
|  | 	// Don't allow an ongoing write to be interrupted. | ||||||
|  | 	if(shift_mode_ == ShiftMode::Writing && drives_[active_drive_] && drives_[active_drive_]->is_writing()) return; | ||||||
|  |  | ||||||
|  | 	const auto old_shift_mode = shift_mode_; | ||||||
|  |  | ||||||
|  | 	switch(state_ & (Q6|Q7)) { | ||||||
|  | 		default:	shift_mode_ = ShiftMode::CheckingWriteProtect;		break; | ||||||
|  | 		case 0:		shift_mode_ = ShiftMode::Reading;					break; | ||||||
|  | 		case Q7: | ||||||
|  | 			// "The IWM is put into the write state by a transition from the write protect sense state to the | ||||||
|  | 			// write load state". | ||||||
|  | 			if(shift_mode_ == ShiftMode::CheckingWriteProtect) shift_mode_ = ShiftMode::Writing; | ||||||
|  | 		break; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// If writing mode just began, set the drive into write mode and cue up the first output byte. | ||||||
|  | 	if(drives_[active_drive_] && old_shift_mode != ShiftMode::Writing && shift_mode_ == ShiftMode::Writing) { | ||||||
|  | 		drives_[active_drive_]->begin_writing(Storage::Time(1, clock_rate_ / bit_length_.as_int()), false); | ||||||
|  | 		shift_register_ = next_output_; | ||||||
|  | 		write_handshake_ |= 0x80 | 0x40; | ||||||
|  | 		output_bits_remaining_ = 8; | ||||||
|  | 		LOG("Seeding output with " << PADHEX(2) << shift_register_); | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | uint8_t IWM::sense() { | ||||||
|  | 	return drives_[active_drive_] ? (drives_[active_drive_]->read() ? 0xff : 0x00) : 0xff; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void IWM::process_event(const Storage::Disk::Drive::Event &event) { | ||||||
|  | 	if(shift_mode_ != ShiftMode::Reading) return; | ||||||
|  |  | ||||||
|  | 	switch(event.type) { | ||||||
|  | 		case Storage::Disk::Track::Event::IndexHole: return; | ||||||
|  | 		case Storage::Disk::Track::Event::FluxTransition: | ||||||
|  | 			propose_shift(1); | ||||||
|  | 		break; | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void IWM::propose_shift(uint8_t bit) { | ||||||
|  | 	// TODO: synchronous mode. | ||||||
|  |  | ||||||
|  | //	LOG("Shifting input"); | ||||||
|  | 	shift_register_ = uint8_t((shift_register_ << 1) | bit); | ||||||
|  | 	if(shift_register_ & 0x80) { | ||||||
|  | 		data_register_ = shift_register_; | ||||||
|  | 		shift_register_ = 0; | ||||||
|  | 	} | ||||||
|  | 	cycles_since_shift_ = Cycles(0); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void IWM::set_drive(int slot, IWMDrive *drive) { | ||||||
|  | 	drives_[slot] = drive; | ||||||
|  | 	drive->set_event_delegate(this); | ||||||
|  | 	drive->set_clocking_hint_observer(this); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void IWM::set_component_prefers_clocking(ClockingHint::Source *component, ClockingHint::Preference clocking) { | ||||||
|  | 	const bool is_rotating = clocking != ClockingHint::Preference::None; | ||||||
|  |  | ||||||
|  | 	if(component == static_cast<ClockingHint::Source *>(drives_[0])) { | ||||||
|  | 		drive_is_rotating_[0] = is_rotating; | ||||||
|  | 	} else { | ||||||
|  | 		drive_is_rotating_[1] = is_rotating; | ||||||
|  | 	} | ||||||
|  | } | ||||||
							
								
								
									
										117
									
								
								Components/DiskII/IWM.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										117
									
								
								Components/DiskII/IWM.hpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,117 @@ | |||||||
|  | // | ||||||
|  | //  IWM.hpp | ||||||
|  | //  Clock Signal | ||||||
|  | // | ||||||
|  | //  Created by Thomas Harte on 05/05/2019. | ||||||
|  | //  Copyright © 2019 Thomas Harte. All rights reserved. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | #ifndef IWM_hpp | ||||||
|  | #define IWM_hpp | ||||||
|  |  | ||||||
|  | #include "../../ClockReceiver/ClockReceiver.hpp" | ||||||
|  | #include "../../ClockReceiver/ClockingHintSource.hpp" | ||||||
|  | #include "../../Storage/Disk/Drive.hpp" | ||||||
|  |  | ||||||
|  | #include <cstdint> | ||||||
|  |  | ||||||
|  | namespace Apple { | ||||||
|  |  | ||||||
|  | /*! | ||||||
|  | 	Defines the drive interface used by the IWM, derived from the external pinout as | ||||||
|  | 	per e.g. https://old.pinouts.ru/HD/MacExtDrive_pinout.shtml | ||||||
|  |  | ||||||
|  | 	These are subclassed of Storage::Disk::Drive, so accept any disk the emulator supports, | ||||||
|  | 	and provide the usual read/write interface for on-disk data. | ||||||
|  | */ | ||||||
|  | struct IWMDrive: public Storage::Disk::Drive { | ||||||
|  | 	IWMDrive(int input_clock_rate, int number_of_heads) : Storage::Disk::Drive(input_clock_rate, number_of_heads) {} | ||||||
|  |  | ||||||
|  | 	enum Line: int { | ||||||
|  | 		CA0		= 1 << 0, | ||||||
|  | 		CA1		= 1 << 1, | ||||||
|  | 		CA2		= 1 << 2, | ||||||
|  | 		LSTRB	= 1 << 3, | ||||||
|  | 		SEL		= 1 << 4, | ||||||
|  | 	}; | ||||||
|  |  | ||||||
|  | 	virtual void set_enabled(bool) = 0; | ||||||
|  | 	virtual void set_control_lines(int) = 0; | ||||||
|  | 	virtual bool read() = 0; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | class IWM: | ||||||
|  | 	public Storage::Disk::Drive::EventDelegate, | ||||||
|  | 	public ClockingHint::Observer { | ||||||
|  | 	public: | ||||||
|  | 		IWM(int clock_rate); | ||||||
|  |  | ||||||
|  | 		/// Sets the current external value of the data bus. | ||||||
|  | 		void write(int address, uint8_t value); | ||||||
|  |  | ||||||
|  | 		/*! | ||||||
|  | 			Submits an access to address @c address. | ||||||
|  |  | ||||||
|  | 			@returns The 8-bit value loaded to the data bus by the IWM. | ||||||
|  | 		*/ | ||||||
|  | 		uint8_t read(int address); | ||||||
|  |  | ||||||
|  | 		/*! | ||||||
|  | 			Sets the current input of the IWM's SEL line. | ||||||
|  | 		*/ | ||||||
|  | 		void set_select(bool enabled); | ||||||
|  |  | ||||||
|  | 		/// Advances the controller by @c cycles. | ||||||
|  | 		void run_for(const Cycles cycles); | ||||||
|  |  | ||||||
|  | 		/// Connects a drive to the IWM. | ||||||
|  | 		void set_drive(int slot, IWMDrive *drive); | ||||||
|  |  | ||||||
|  | 	private: | ||||||
|  | 		// Storage::Disk::Drive::EventDelegate. | ||||||
|  | 		void process_event(const Storage::Disk::Drive::Event &event) override; | ||||||
|  |  | ||||||
|  | 		const int clock_rate_; | ||||||
|  |  | ||||||
|  | 		uint8_t data_register_ = 0; | ||||||
|  | 		uint8_t mode_ = 0; | ||||||
|  | 		bool read_write_ready_ = true; | ||||||
|  | 		bool write_overran_ = false; | ||||||
|  |  | ||||||
|  | 		int state_ = 0; | ||||||
|  |  | ||||||
|  | 		int active_drive_ = 0; | ||||||
|  | 		IWMDrive *drives_[2] = {nullptr, nullptr}; | ||||||
|  | 		bool drive_is_rotating_[2] = {false, false}; | ||||||
|  |  | ||||||
|  | 		void set_component_prefers_clocking(ClockingHint::Source *component, ClockingHint::Preference clocking) override; | ||||||
|  |  | ||||||
|  | 		Cycles cycles_until_disable_; | ||||||
|  | 		uint8_t write_handshake_ = 0x80; | ||||||
|  |  | ||||||
|  | 		void access(int address); | ||||||
|  |  | ||||||
|  | 		uint8_t shift_register_ = 0; | ||||||
|  | 		uint8_t next_output_ = 0; | ||||||
|  | 		int output_bits_remaining_ = 0; | ||||||
|  |  | ||||||
|  | 		void propose_shift(uint8_t bit); | ||||||
|  | 		Cycles cycles_since_shift_; | ||||||
|  | 		Cycles bit_length_ = Cycles(16); | ||||||
|  |  | ||||||
|  | 		void push_drive_state(); | ||||||
|  |  | ||||||
|  | 		enum class ShiftMode { | ||||||
|  | 			Reading, | ||||||
|  | 			Writing, | ||||||
|  | 			CheckingWriteProtect | ||||||
|  | 		} shift_mode_; | ||||||
|  |  | ||||||
|  | 		uint8_t sense(); | ||||||
|  | 		void select_shift_mode(); | ||||||
|  | }; | ||||||
|  |  | ||||||
|  |  | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #endif /* IWM_hpp */ | ||||||
							
								
								
									
										179
									
								
								Components/DiskII/MacintoshDoubleDensityDrive.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										179
									
								
								Components/DiskII/MacintoshDoubleDensityDrive.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,179 @@ | |||||||
|  | // | ||||||
|  | //  MacintoshDoubleDensityDrive.cpp | ||||||
|  | //  Clock Signal | ||||||
|  | // | ||||||
|  | //  Created by Thomas Harte on 10/07/2019. | ||||||
|  | //  Copyright © 2019 Thomas Harte. All rights reserved. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | #include "MacintoshDoubleDensityDrive.hpp" | ||||||
|  |  | ||||||
|  | /* | ||||||
|  | 	Sources used pervasively: | ||||||
|  |  | ||||||
|  | 	http://members.iinet.net.au/~kalandi/apple/AUG/1991/11%20NOV.DEC/DISK.STUFF.html | ||||||
|  | 	Apple Guide to the Macintosh Family Hardware | ||||||
|  | 	Inside Macintosh III | ||||||
|  | */ | ||||||
|  |  | ||||||
|  | using namespace Apple::Macintosh; | ||||||
|  |  | ||||||
|  | DoubleDensityDrive::DoubleDensityDrive(int input_clock_rate, bool is_800k) : | ||||||
|  | 	IWMDrive(input_clock_rate, is_800k ? 2 : 1),	// Only 800kb drives are double sided. | ||||||
|  | 	is_800k_(is_800k) { | ||||||
|  | 	// Start with a valid rotation speed. | ||||||
|  | 	if(is_800k) { | ||||||
|  | 		Drive::set_rotation_speed(393.3807f); | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // MARK: - Speed Selection | ||||||
|  |  | ||||||
|  | void DoubleDensityDrive::did_step(Storage::Disk::HeadPosition to_position) { | ||||||
|  | //	printf("At track %d\n", to_position.as_int()); | ||||||
|  | 	// The 800kb drive automatically selects rotation speed as a function of | ||||||
|  | 	// head position; the 400kb drive doesn't do so. | ||||||
|  | 	if(is_800k_) { | ||||||
|  | 		/* | ||||||
|  | 			Numbers below cribbed from the Kryoflux forums; specifically: | ||||||
|  | 			https://forum.kryoflux.com/viewtopic.php?t=1090 | ||||||
|  |  | ||||||
|  | 			They can almost be worked out algorithmically, since the point is to | ||||||
|  | 			produce an almost-constant value for speed*(number of sectors), and: | ||||||
|  |  | ||||||
|  | 			393.3807 * 12 = 4720.5684 | ||||||
|  | 			429.1723 * 11 = 4720.895421 | ||||||
|  | 			472.1435 * 10 = 4721.435 | ||||||
|  | 			524.5672 * 9 = 4721.1048 | ||||||
|  | 			590.1098 * 8 = 4720.8784 | ||||||
|  |  | ||||||
|  | 			So 4721 / (number of sectors per track in zone) would give essentially | ||||||
|  | 			the same results. | ||||||
|  | 		*/ | ||||||
|  | 		const int zone = to_position.as_int() >> 4; | ||||||
|  | 		switch(zone) { | ||||||
|  | 			case 0:		Drive::set_rotation_speed(393.3807f);	break; | ||||||
|  | 			case 1:		Drive::set_rotation_speed(429.1723f);	break; | ||||||
|  | 			case 2:		Drive::set_rotation_speed(472.1435f);	break; | ||||||
|  | 			case 3:		Drive::set_rotation_speed(524.5672f);	break; | ||||||
|  | 			default:	Drive::set_rotation_speed(590.1098f);	break; | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void DoubleDensityDrive::set_rotation_speed(float revolutions_per_minute) { | ||||||
|  | 	if(!is_800k_) { | ||||||
|  | 		// Don't allow drive speeds to drop below 10 RPM, as a temporary sop | ||||||
|  | 		// to sanity. | ||||||
|  | 		Drive::set_rotation_speed(std::max(10.0f, revolutions_per_minute)); | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // MARK: - Control input/output. | ||||||
|  |  | ||||||
|  | void DoubleDensityDrive::set_enabled(bool) { | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void DoubleDensityDrive::set_control_lines(int lines) { | ||||||
|  | 	const auto old_state = control_state_; | ||||||
|  | 	control_state_ = lines; | ||||||
|  |  | ||||||
|  | 	// Catch low-to-high LSTRB transitions. | ||||||
|  | 	if((old_state ^ control_state_) & control_state_ & Line::LSTRB) { | ||||||
|  | 		switch(control_state_ & (Line::CA2 | Line::CA1 | Line::CA0 | Line::SEL)) { | ||||||
|  | 			default: | ||||||
|  | 			break; | ||||||
|  |  | ||||||
|  | 			case 0:						// Set step direction — CA2 set => step outward. | ||||||
|  | 			case Line::CA2: | ||||||
|  | 				step_direction_ = (control_state_ & Line::CA2) ? -1 : 1; | ||||||
|  | 			break; | ||||||
|  |  | ||||||
|  | 			case Line::CA1:				// Set drive motor — CA2 set => motor off. | ||||||
|  | 			case Line::CA1|Line::CA2: | ||||||
|  | 				set_motor_on(!(control_state_ & Line::CA2)); | ||||||
|  | 			break; | ||||||
|  |  | ||||||
|  | 			case Line::CA0:				// Initiate a step. | ||||||
|  | 				step(Storage::Disk::HeadPosition(step_direction_)); | ||||||
|  | 			break; | ||||||
|  |  | ||||||
|  | 			case Line::SEL|Line::CA2:	// Reset new disk flag. | ||||||
|  | 				has_new_disk_ = false; | ||||||
|  | 			break; | ||||||
|  |  | ||||||
|  | 			case Line::CA2 | Line::CA1 | Line::CA0:	// Eject the disk. | ||||||
|  | 				set_disk(nullptr); | ||||||
|  | 			break; | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | bool DoubleDensityDrive::read() { | ||||||
|  | 	switch(control_state_ & (CA2 | CA1 | CA0 | SEL)) { | ||||||
|  | 		default: | ||||||
|  | 		return false; | ||||||
|  |  | ||||||
|  | 		case 0:					// Head step direction. | ||||||
|  | 								// (0 = inward) | ||||||
|  | 		return step_direction_ <= 0; | ||||||
|  |  | ||||||
|  | 		case SEL:				// Disk in place. | ||||||
|  | 								// (0 = disk present) | ||||||
|  | 		return !has_disk(); | ||||||
|  |  | ||||||
|  | 		case CA0:				// Disk head step completed. | ||||||
|  | 								// (0 = still stepping) | ||||||
|  | 		return true;	// TODO: stepping delay. But at the main Drive level. | ||||||
|  |  | ||||||
|  | 		case CA0|SEL:			// Disk locked. | ||||||
|  | 								// (0 = write protected) | ||||||
|  | 		return !get_is_read_only(); | ||||||
|  |  | ||||||
|  | 		case CA1:				// Disk motor running. | ||||||
|  | 								// (0 = motor on) | ||||||
|  | 		return !get_motor_on(); | ||||||
|  |  | ||||||
|  | 		case CA1|SEL:			// Head at track 0. | ||||||
|  | 								// (0 = at track 0) | ||||||
|  | 								// "This bit becomes valid beginning 12 msec after the step that places the head at track 0." | ||||||
|  | 		return !get_is_track_zero(); | ||||||
|  |  | ||||||
|  | 		case CA1|CA0:			// Disk has been ejected. | ||||||
|  | 								// (0 = user has ejected disk) | ||||||
|  | 		return !has_new_disk_; | ||||||
|  |  | ||||||
|  | 		case CA1|CA0|SEL:		// Tachometer. | ||||||
|  | 								// (arbitrary) | ||||||
|  | 		return get_tachometer(); | ||||||
|  |  | ||||||
|  | 		case CA2:				// Read data, lower head. | ||||||
|  | 			set_head(0); | ||||||
|  | 		return false; | ||||||
|  |  | ||||||
|  | 		case CA2|SEL:			// Read data, upper head. | ||||||
|  | 			set_head(1); | ||||||
|  | 		return false; | ||||||
|  |  | ||||||
|  | 		case CA2|CA1:			// Single- or double-sided drive. | ||||||
|  | 								// (0 = single sided) | ||||||
|  | 		return get_head_count() != 1; | ||||||
|  |  | ||||||
|  | 		case CA2|CA1|CA0:		// "Present/HD" (per the Mac Plus ROM) | ||||||
|  | 								// (0 = ??HD??) | ||||||
|  | 								// | ||||||
|  | 								// Alternative explanation: "Disk ready for reading?" | ||||||
|  | 								// (0 = ready) | ||||||
|  | 		return false; | ||||||
|  |  | ||||||
|  | 		case CA2|CA1|CA0|SEL:	// Drive installed. | ||||||
|  | 								// (0 = present, 1 = missing) | ||||||
|  | 								// | ||||||
|  | 								// TODO: why do I need to return this the wrong way around for the Mac Plus? | ||||||
|  | 		return true; | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void DoubleDensityDrive::did_set_disk() { | ||||||
|  | 	has_new_disk_ = true; | ||||||
|  | } | ||||||
							
								
								
									
										53
									
								
								Components/DiskII/MacintoshDoubleDensityDrive.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										53
									
								
								Components/DiskII/MacintoshDoubleDensityDrive.hpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,53 @@ | |||||||
|  | // | ||||||
|  | //  MacintoshDoubleDensityDrive.hpp | ||||||
|  | //  Clock Signal | ||||||
|  | // | ||||||
|  | //  Created by Thomas Harte on 10/07/2019. | ||||||
|  | //  Copyright © 2019 Thomas Harte. All rights reserved. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | #ifndef MacintoshDoubleDensityDrive_hpp | ||||||
|  | #define MacintoshDoubleDensityDrive_hpp | ||||||
|  |  | ||||||
|  | #include "IWM.hpp" | ||||||
|  |  | ||||||
|  | namespace Apple { | ||||||
|  | namespace Macintosh { | ||||||
|  |  | ||||||
|  | class DoubleDensityDrive: public IWMDrive { | ||||||
|  | 	public: | ||||||
|  | 		DoubleDensityDrive(int input_clock_rate, bool is_800k); | ||||||
|  |  | ||||||
|  | 		/*! | ||||||
|  | 			@returns @c true if this is an 800kb drive; @c false otherwise. | ||||||
|  | 		*/ | ||||||
|  | 		bool is_800k() { | ||||||
|  | 			return is_800k_; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		/*! | ||||||
|  | 			Sets the current rotation speed of this drive only if it is a 400kb drive. | ||||||
|  | 			800kb drives select their own rotation speed based on head position, | ||||||
|  | 			and ignore this input. | ||||||
|  | 		*/ | ||||||
|  | 		void set_rotation_speed(float revolutions_per_minute); | ||||||
|  |  | ||||||
|  | 		void set_enabled(bool) override; | ||||||
|  | 		void set_control_lines(int) override; | ||||||
|  | 		bool read() override; | ||||||
|  |  | ||||||
|  | 	private: | ||||||
|  | 		// To receive the proper notifications from Storage::Disk::Drive. | ||||||
|  | 		void did_step(Storage::Disk::HeadPosition to_position) override; | ||||||
|  | 		void did_set_disk() override; | ||||||
|  |  | ||||||
|  | 		const bool is_800k_; | ||||||
|  | 		bool has_new_disk_ = false; | ||||||
|  | 		int control_state_ = 0; | ||||||
|  | 		int step_direction_ = 1; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #endif /* MacintoshDoubleDensityDrive_hpp */ | ||||||
| @@ -6,8 +6,8 @@ | |||||||
| //  Copyright 2017 Thomas Harte. All rights reserved. | //  Copyright 2017 Thomas Harte. All rights reserved. | ||||||
| // | // | ||||||
|  |  | ||||||
| #ifndef Keyboard_hpp | #ifndef Inputs_Keyboard_hpp | ||||||
| #define Keyboard_hpp | #define Inputs_Keyboard_hpp | ||||||
|  |  | ||||||
| #include <vector> | #include <vector> | ||||||
| #include <set> | #include <set> | ||||||
| @@ -75,4 +75,4 @@ class Keyboard { | |||||||
|  |  | ||||||
| } | } | ||||||
|  |  | ||||||
| #endif /* Keyboard_hpp */ | #endif /* Inputs_Keyboard_hpp */ | ||||||
|   | |||||||
							
								
								
									
										47
									
								
								Inputs/Mouse.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										47
									
								
								Inputs/Mouse.hpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,47 @@ | |||||||
|  | // | ||||||
|  | //  Mouse.hpp | ||||||
|  | //  Clock Signal | ||||||
|  | // | ||||||
|  | //  Created by Thomas Harte on 11/06/2019. | ||||||
|  | //  Copyright © 2019 Thomas Harte. All rights reserved. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | #ifndef Mouse_h | ||||||
|  | #define Mouse_h | ||||||
|  |  | ||||||
|  | namespace Inputs { | ||||||
|  |  | ||||||
|  | /*! | ||||||
|  | 	Models a classic-era mouse: something that provides 2d relative motion plus | ||||||
|  | 	some quantity of buttons. | ||||||
|  | */ | ||||||
|  | class Mouse { | ||||||
|  | 	public: | ||||||
|  | 		/*! | ||||||
|  | 			Indicates a movement of the mouse. | ||||||
|  | 		*/ | ||||||
|  | 		virtual void move(int x, int y) {} | ||||||
|  |  | ||||||
|  | 		/*! | ||||||
|  | 			@returns the number of buttons on this mouse. | ||||||
|  | 		*/ | ||||||
|  | 		virtual int get_number_of_buttons() { | ||||||
|  | 			return 1; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		/*! | ||||||
|  | 			Indicates that button @c index is now either pressed or unpressed. | ||||||
|  | 			The intention is that @c index be semantic, not positional: | ||||||
|  | 			0 for the primary button, 1 for the secondary, 2 for the tertiary, etc. | ||||||
|  | 		*/ | ||||||
|  | 		virtual void set_button_pressed(int index, bool is_pressed) {} | ||||||
|  |  | ||||||
|  | 		/*! | ||||||
|  | 			Releases all depressed buttons. | ||||||
|  | 		*/ | ||||||
|  | 		virtual void reset_all_buttons() {} | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #endif /* Mouse_h */ | ||||||
							
								
								
									
										123
									
								
								Inputs/QuadratureMouse/QuadratureMouse.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										123
									
								
								Inputs/QuadratureMouse/QuadratureMouse.hpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,123 @@ | |||||||
|  | // | ||||||
|  | //  QuadratureMouse.hpp | ||||||
|  | //  Clock Signal | ||||||
|  | // | ||||||
|  | //  Created by Thomas Harte on 11/06/2019. | ||||||
|  | //  Copyright © 2019 Thomas Harte. All rights reserved. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | #ifndef QuadratureMouse_hpp | ||||||
|  | #define QuadratureMouse_hpp | ||||||
|  |  | ||||||
|  | #include "../Mouse.hpp" | ||||||
|  | #include <atomic> | ||||||
|  |  | ||||||
|  | namespace Inputs { | ||||||
|  |  | ||||||
|  | /*! | ||||||
|  | 	Provides a simple implementation of a Mouse, designed for simple | ||||||
|  | 	thread-safe feeding to a machine that accepts quadrature-encoded input. | ||||||
|  |  | ||||||
|  | 	TEMPORARY SIMPLIFICATION: it is assumed that the caller will be interested | ||||||
|  | 	in observing a signal that dictates velocity, sampling the other to | ||||||
|  | 	obtain direction only on transitions in the velocity signal. | ||||||
|  |  | ||||||
|  | 	Or, more concretely, of the two channels per axis, one is accurate only when | ||||||
|  | 	the other transitions. Hence the discussion of 'primary' and 'secondary' | ||||||
|  | 	channels below. This is intended to be fixed. | ||||||
|  | */ | ||||||
|  | class QuadratureMouse: public Mouse { | ||||||
|  | 	public: | ||||||
|  | 		QuadratureMouse(int number_of_buttons) : | ||||||
|  | 			number_of_buttons_(number_of_buttons) {} | ||||||
|  |  | ||||||
|  | 		/* | ||||||
|  | 			Inputs, to satisfy the Mouse interface. | ||||||
|  | 		*/ | ||||||
|  | 		void move(int x, int y) override { | ||||||
|  | 			// Accumulate all provided motion. | ||||||
|  | 			axes_[0] += x; | ||||||
|  | 			axes_[1] += y; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		int get_number_of_buttons() override { | ||||||
|  | 			return number_of_buttons_; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		void set_button_pressed(int index, bool is_pressed) override { | ||||||
|  | 			if(is_pressed) | ||||||
|  | 				button_flags_ |= (1 << index); | ||||||
|  | 			else | ||||||
|  | 				button_flags_ &= ~(1 << index); | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		void reset_all_buttons() override { | ||||||
|  | 			button_flags_ = 0; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		/* | ||||||
|  | 			Outputs. | ||||||
|  | 		*/ | ||||||
|  |  | ||||||
|  | 		/*! | ||||||
|  | 			Applies a single step from the current accumulated mouse movement, which | ||||||
|  | 			might involve the mouse moving right, or left, or not at all. | ||||||
|  | 		*/ | ||||||
|  | 		void prepare_step() { | ||||||
|  | 			for(int axis = 0; axis < 2; ++axis) { | ||||||
|  | 				// Do nothing if there's no motion to communicate. | ||||||
|  | 				const int axis_value = axes_[axis]; | ||||||
|  | 				if(!axis_value) continue; | ||||||
|  |  | ||||||
|  | 				// Toggle the primary channel and set the secondary for | ||||||
|  | 				// negative motion. At present the y axis signals the | ||||||
|  | 				// secondary channel the opposite way around from the | ||||||
|  | 				// primary. | ||||||
|  | 				primaries_[axis] ^= 1; | ||||||
|  | 				secondaries_[axis] = primaries_[axis] ^ axis; | ||||||
|  | 				if(axis_value > 0) { | ||||||
|  | 					-- axes_[axis]; | ||||||
|  | 					secondaries_[axis] ^= 1;	// Switch to positive motion. | ||||||
|  | 				} else { | ||||||
|  | 					++ axes_[axis]; | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		/*! | ||||||
|  | 			@returns the two quadrature channels — bit 0 is the 'primary' channel | ||||||
|  | 				(i.e. the one that can be monitored to observe velocity) and | ||||||
|  | 				bit 1 is the 'secondary' (i.e. that which can be queried to | ||||||
|  | 				observe direction). | ||||||
|  | 		*/ | ||||||
|  | 		int get_channel(int axis) { | ||||||
|  | 			return primaries_[axis] | (secondaries_[axis] << 1); | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		/*! | ||||||
|  | 			@returns a bit mask of the currently pressed buttons. | ||||||
|  | 		*/ | ||||||
|  | 		int get_button_mask() { | ||||||
|  | 			return button_flags_; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		/*! | ||||||
|  | 			@returns @c true if any mouse motion is waiting to be communicated; | ||||||
|  | 				@c false otherwise. | ||||||
|  | 		*/ | ||||||
|  | 		bool has_steps() { | ||||||
|  | 			return axes_[0] || axes_[1]; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 	private: | ||||||
|  | 		int number_of_buttons_ = 0; | ||||||
|  | 		std::atomic<int> button_flags_; | ||||||
|  | 		std::atomic<int> axes_[2]; | ||||||
|  |  | ||||||
|  | 		int primaries_[2] = {0, 0}; | ||||||
|  | 		int secondaries_[2] = {0, 0}; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #endif /* QuadratureMouse_hpp */ | ||||||
| @@ -781,27 +781,37 @@ template <bool has_fdc> class ConcreteMachine: | |||||||
| 			ay_.ay().set_port_handler(&key_state_); | 			ay_.ay().set_port_handler(&key_state_); | ||||||
|  |  | ||||||
| 			// construct the list of necessary ROMs | 			// construct the list of necessary ROMs | ||||||
| 			std::vector<std::string> required_roms = {"amsdos.rom"}; | 			const std::string machine_name = "AmstradCPC"; | ||||||
|  | 			std::vector<ROMMachine::ROM> required_roms = { | ||||||
|  | 				ROMMachine::ROM(machine_name, "the Amstrad Disk Operating System", "amsdos.rom", 16*1024, 0x1fe22ecd) | ||||||
|  | 			}; | ||||||
| 			std::string model_number; | 			std::string model_number; | ||||||
|  | 			uint32_t crcs[2]; | ||||||
| 			switch(target.model) { | 			switch(target.model) { | ||||||
| 				default: | 				default: | ||||||
| 					model_number = "6128"; | 					model_number = "6128"; | ||||||
| 					has_128k_ = true; | 					has_128k_ = true; | ||||||
|  | 					crcs[0] = 0x0219bb74; | ||||||
|  | 					crcs[1] = 0xca6af63d; | ||||||
| 				break; | 				break; | ||||||
| 				case Analyser::Static::AmstradCPC::Target::Model::CPC464: | 				case Analyser::Static::AmstradCPC::Target::Model::CPC464: | ||||||
| 					model_number = "464"; | 					model_number = "464"; | ||||||
| 					has_128k_ = false; | 					has_128k_ = false; | ||||||
|  | 					crcs[0] = 0x815752df; | ||||||
|  | 					crcs[1] = 0x7d9a3bac; | ||||||
| 				break; | 				break; | ||||||
| 				case Analyser::Static::AmstradCPC::Target::Model::CPC664: | 				case Analyser::Static::AmstradCPC::Target::Model::CPC664: | ||||||
| 					model_number = "664"; | 					model_number = "664"; | ||||||
| 					has_128k_ = false; | 					has_128k_ = false; | ||||||
|  | 					crcs[0] = 0x3f5a6dc4; | ||||||
|  | 					crcs[1] = 0x32fee492; | ||||||
| 				break; | 				break; | ||||||
| 			} | 			} | ||||||
| 			required_roms.push_back("os" + model_number + ".rom"); | 			required_roms.emplace_back(machine_name, "the CPC " + model_number + " firmware", "os" + model_number + ".rom", 16*1024, crcs[0]); | ||||||
| 			required_roms.push_back("basic" + model_number + ".rom"); | 			required_roms.emplace_back(machine_name, "the CPC " + model_number + " BASIC ROM", "basic" + model_number + ".rom", 16*1024, crcs[1]); | ||||||
|  |  | ||||||
| 			// fetch and verify the ROMs | 			// fetch and verify the ROMs | ||||||
| 			const auto roms = rom_fetcher("AmstradCPC", required_roms); | 			const auto roms = rom_fetcher(required_roms); | ||||||
|  |  | ||||||
| 			for(std::size_t index = 0; index < roms.size(); ++index) { | 			for(std::size_t index = 0; index < roms.size(); ++index) { | ||||||
| 				auto &data = roms[index]; | 				auto &data = roms[index]; | ||||||
|   | |||||||
| @@ -8,33 +8,34 @@ | |||||||
| 
 | 
 | ||||||
| #include "AppleII.hpp" | #include "AppleII.hpp" | ||||||
| 
 | 
 | ||||||
| #include "../../Activity/Source.hpp" | #include "../../../Activity/Source.hpp" | ||||||
| #include "../MediaTarget.hpp" | #include "../../MediaTarget.hpp" | ||||||
| #include "../CRTMachine.hpp" | #include "../../CRTMachine.hpp" | ||||||
| #include "../JoystickMachine.hpp" | #include "../../JoystickMachine.hpp" | ||||||
| #include "../KeyboardMachine.hpp" | #include "../../KeyboardMachine.hpp" | ||||||
| #include "../Utility/MemoryFuzzer.hpp" | #include "../../Utility/MemoryFuzzer.hpp" | ||||||
| #include "../Utility/StringSerialiser.hpp" | #include "../../Utility/StringSerialiser.hpp" | ||||||
| 
 | 
 | ||||||
| #include "../../Processors/6502/6502.hpp" | #include "../../../Processors/6502/6502.hpp" | ||||||
| #include "../../Components/AudioToggle/AudioToggle.hpp" | #include "../../../Components/AudioToggle/AudioToggle.hpp" | ||||||
| 
 | 
 | ||||||
| #include "../../Outputs/Speaker/Implementation/LowpassSpeaker.hpp" | #include "../../../Outputs/Speaker/Implementation/LowpassSpeaker.hpp" | ||||||
| #include "../../Outputs/Log.hpp" | #include "../../../Outputs/Log.hpp" | ||||||
| 
 | 
 | ||||||
| #include "Card.hpp" | #include "Card.hpp" | ||||||
| #include "DiskIICard.hpp" | #include "DiskIICard.hpp" | ||||||
| #include "Video.hpp" | #include "Video.hpp" | ||||||
| 
 | 
 | ||||||
| #include "../../Analyser/Static/AppleII/Target.hpp" | #include "../../../Analyser/Static/AppleII/Target.hpp" | ||||||
| #include "../../ClockReceiver/ForceInline.hpp" | #include "../../../ClockReceiver/ForceInline.hpp" | ||||||
| #include "../../Configurable/StandardOptions.hpp" | #include "../../../Configurable/StandardOptions.hpp" | ||||||
| 
 | 
 | ||||||
| #include <algorithm> | #include <algorithm> | ||||||
| #include <array> | #include <array> | ||||||
| #include <memory> | #include <memory> | ||||||
| 
 | 
 | ||||||
| namespace AppleII { | namespace Apple { | ||||||
|  | namespace II { | ||||||
| 
 | 
 | ||||||
| std::vector<std::unique_ptr<Configurable::Option>> get_options() { | std::vector<std::unique_ptr<Configurable::Option>> get_options() { | ||||||
| 	return Configurable::standard_options( | 	return Configurable::standard_options( | ||||||
| @@ -51,12 +52,12 @@ template <Analyser::Static::AppleII::Target::Model model> class ConcreteMachine: | |||||||
| 	public CPU::MOS6502::BusHandler, | 	public CPU::MOS6502::BusHandler, | ||||||
| 	public Inputs::Keyboard, | 	public Inputs::Keyboard, | ||||||
| 	public Configurable::Device, | 	public Configurable::Device, | ||||||
| 	public AppleII::Machine, | 	public Apple::II::Machine, | ||||||
| 	public Activity::Source, | 	public Activity::Source, | ||||||
| 	public JoystickMachine::Machine, | 	public JoystickMachine::Machine, | ||||||
| 	public AppleII::Card::Delegate { | 	public Apple::II::Card::Delegate { | ||||||
| 	private: | 	private: | ||||||
| 		struct VideoBusHandler : public AppleII::Video::BusHandler { | 		struct VideoBusHandler : public Apple::II::Video::BusHandler { | ||||||
| 			public: | 			public: | ||||||
| 				VideoBusHandler(uint8_t *ram, uint8_t *aux_ram) : ram_(ram), aux_ram_(aux_ram) {} | 				VideoBusHandler(uint8_t *ram, uint8_t *aux_ram) : ram_(ram), aux_ram_(aux_ram) {} | ||||||
| 
 | 
 | ||||||
| @@ -71,12 +72,12 @@ template <Analyser::Static::AppleII::Target::Model model> class ConcreteMachine: | |||||||
| 
 | 
 | ||||||
| 		CPU::MOS6502::Processor<(model == Analyser::Static::AppleII::Target::Model::EnhancedIIe) ? CPU::MOS6502::Personality::PSynertek65C02 : CPU::MOS6502::Personality::P6502, ConcreteMachine, false> m6502_; | 		CPU::MOS6502::Processor<(model == Analyser::Static::AppleII::Target::Model::EnhancedIIe) ? CPU::MOS6502::Personality::PSynertek65C02 : CPU::MOS6502::Personality::P6502, ConcreteMachine, false> m6502_; | ||||||
| 		VideoBusHandler video_bus_handler_; | 		VideoBusHandler video_bus_handler_; | ||||||
| 		AppleII::Video::Video<VideoBusHandler, is_iie()> video_; | 		Apple::II::Video::Video<VideoBusHandler, is_iie()> video_; | ||||||
| 		int cycles_into_current_line_ = 0; | 		int cycles_into_current_line_ = 0; | ||||||
| 		Cycles cycles_since_video_update_; | 		Cycles cycles_since_video_update_; | ||||||
| 
 | 
 | ||||||
| 		void update_video() { | 		void update_video() { | ||||||
| 			video_.run_for(cycles_since_video_update_.flush()); | 			video_.run_for(cycles_since_video_update_.flush<Cycles>()); | ||||||
| 		} | 		} | ||||||
| 		static const int audio_divider = 8; | 		static const int audio_divider = 8; | ||||||
| 		void update_audio() { | 		void update_audio() { | ||||||
| @@ -109,28 +110,28 @@ template <Analyser::Static::AppleII::Target::Model model> class ConcreteMachine: | |||||||
| 		Cycles cycles_since_audio_update_; | 		Cycles cycles_since_audio_update_; | ||||||
| 
 | 
 | ||||||
| 		// MARK: - Cards
 | 		// MARK: - Cards
 | ||||||
| 		std::array<std::unique_ptr<AppleII::Card>, 7> cards_; | 		std::array<std::unique_ptr<Apple::II::Card>, 7> cards_; | ||||||
| 		Cycles cycles_since_card_update_; | 		Cycles cycles_since_card_update_; | ||||||
| 		std::vector<AppleII::Card *> every_cycle_cards_; | 		std::vector<Apple::II::Card *> every_cycle_cards_; | ||||||
| 		std::vector<AppleII::Card *> just_in_time_cards_; | 		std::vector<Apple::II::Card *> just_in_time_cards_; | ||||||
| 
 | 
 | ||||||
| 		int stretched_cycles_since_card_update_ = 0; | 		int stretched_cycles_since_card_update_ = 0; | ||||||
| 
 | 
 | ||||||
| 		void install_card(std::size_t slot, AppleII::Card *card) { | 		void install_card(std::size_t slot, Apple::II::Card *card) { | ||||||
| 			assert(slot >= 1 && slot < 8); | 			assert(slot >= 1 && slot < 8); | ||||||
| 			cards_[slot - 1].reset(card); | 			cards_[slot - 1].reset(card); | ||||||
| 			card->set_delegate(this); | 			card->set_delegate(this); | ||||||
| 			pick_card_messaging_group(card); | 			pick_card_messaging_group(card); | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		bool is_every_cycle_card(AppleII::Card *card) { | 		bool is_every_cycle_card(Apple::II::Card *card) { | ||||||
| 			return !card->get_select_constraints(); | 			return !card->get_select_constraints(); | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		void pick_card_messaging_group(AppleII::Card *card) { | 		void pick_card_messaging_group(Apple::II::Card *card) { | ||||||
| 			const bool is_every_cycle = is_every_cycle_card(card); | 			const bool is_every_cycle = is_every_cycle_card(card); | ||||||
| 			std::vector<AppleII::Card *> &intended = is_every_cycle ? every_cycle_cards_ : just_in_time_cards_; | 			std::vector<Apple::II::Card *> &intended = is_every_cycle ? every_cycle_cards_ : just_in_time_cards_; | ||||||
| 			std::vector<AppleII::Card *> &undesired = is_every_cycle ? just_in_time_cards_ : every_cycle_cards_; | 			std::vector<Apple::II::Card *> &undesired = is_every_cycle ? just_in_time_cards_ : every_cycle_cards_; | ||||||
| 
 | 
 | ||||||
| 			if(std::find(intended.begin(), intended.end(), card) != intended.end()) return; | 			if(std::find(intended.begin(), intended.end(), card) != intended.end()) return; | ||||||
| 			auto old_membership = std::find(undesired.begin(), undesired.end(), card); | 			auto old_membership = std::find(undesired.begin(), undesired.end(), card); | ||||||
| @@ -138,12 +139,12 @@ template <Analyser::Static::AppleII::Target::Model model> class ConcreteMachine: | |||||||
| 			intended.push_back(card); | 			intended.push_back(card); | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		void card_did_change_select_constraints(AppleII::Card *card) override { | 		void card_did_change_select_constraints(Apple::II::Card *card) override { | ||||||
| 			pick_card_messaging_group(card); | 			pick_card_messaging_group(card); | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		AppleII::DiskIICard *diskii_card() { | 		Apple::II::DiskIICard *diskii_card() { | ||||||
| 			return dynamic_cast<AppleII::DiskIICard *>(cards_[5].get()); | 			return dynamic_cast<Apple::II::DiskIICard *>(cards_[5].get()); | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		// MARK: - Memory Map.
 | 		// MARK: - Memory Map.
 | ||||||
| @@ -346,30 +347,39 @@ template <Analyser::Static::AppleII::Target::Model model> class ConcreteMachine: | |||||||
| 
 | 
 | ||||||
| 			// Pick the required ROMs.
 | 			// Pick the required ROMs.
 | ||||||
| 			using Target = Analyser::Static::AppleII::Target; | 			using Target = Analyser::Static::AppleII::Target; | ||||||
| 			std::vector<std::string> rom_names; | 			const std::string machine_name = "AppleII"; | ||||||
|  | 			std::vector<ROMMachine::ROM> rom_descriptions; | ||||||
| 			size_t rom_size = 12*1024; | 			size_t rom_size = 12*1024; | ||||||
| 			switch(target.model) { | 			switch(target.model) { | ||||||
| 				default: | 				default: | ||||||
| 					rom_names.push_back("apple2-character.rom"); | 					rom_descriptions.emplace_back(machine_name, "the basic Apple II character ROM", "apple2-character.rom", 2*1024, 0x64f415c6); | ||||||
| 					rom_names.push_back("apple2o.rom"); | 					rom_descriptions.emplace_back(machine_name, "the original Apple II ROM", "apple2o.rom", 12*1024, 0xba210588); | ||||||
| 				break; | 				break; | ||||||
| 				case Target::Model::IIplus: | 				case Target::Model::IIplus: | ||||||
| 					rom_names.push_back("apple2-character.rom"); | 					rom_descriptions.emplace_back(machine_name, "the basic Apple II character ROM", "apple2-character.rom", 2*1024, 0x64f415c6); | ||||||
| 					rom_names.push_back("apple2.rom"); | 					rom_descriptions.emplace_back(machine_name, "the Apple II+ ROM", "apple2.rom", 12*1024, 0xf66f9c26); | ||||||
| 				break; | 				break; | ||||||
| 				case Target::Model::IIe: | 				case Target::Model::IIe: | ||||||
| 					rom_size += 3840; | 					rom_size += 3840; | ||||||
| 					rom_names.push_back("apple2eu-character.rom"); | 					rom_descriptions.emplace_back(machine_name, "the Apple IIe character ROM", "apple2eu-character.rom", 4*1024, 0x816a86f1); | ||||||
| 					rom_names.push_back("apple2eu.rom"); | 					rom_descriptions.emplace_back(machine_name, "the Apple IIe ROM", "apple2eu.rom", 32*1024, 0xe12be18d); | ||||||
| 				break; | 				break; | ||||||
| 				case Target::Model::EnhancedIIe: | 				case Target::Model::EnhancedIIe: | ||||||
| 					rom_size += 3840; | 					rom_size += 3840; | ||||||
| 					rom_names.push_back("apple2e-character.rom"); | 					rom_descriptions.emplace_back(machine_name, "the Enhanced Apple IIe character ROM", "apple2e-character.rom", 4*1024, 0x2651014d); | ||||||
| 					rom_names.push_back("apple2e.rom"); | 					rom_descriptions.emplace_back(machine_name, "the Enhanced Apple IIe ROM", "apple2e.rom", 32*1024, 0x65989942); | ||||||
| 				break; | 				break; | ||||||
| 			} | 			} | ||||||
| 			const auto roms = rom_fetcher("AppleII", rom_names); | 			const auto roms = rom_fetcher(rom_descriptions); | ||||||
| 
 | 
 | ||||||
|  | 			// Try to install a Disk II card now, before checking the ROM list,
 | ||||||
|  | 			// to make sure that Disk II dependencies have been communicated.
 | ||||||
|  | 			if(target.disk_controller != Target::DiskController::None) { | ||||||
|  | 				// Apple recommended slot 6 for the (first) Disk II.
 | ||||||
|  | 				install_card(6, new Apple::II::DiskIICard(rom_fetcher, target.disk_controller == Target::DiskController::SixteenSector)); | ||||||
|  | 			} | ||||||
|  | 
 | ||||||
|  | 			// Now, check and move the ROMs.
 | ||||||
| 			if(!roms[0] || !roms[1]) { | 			if(!roms[0] || !roms[1]) { | ||||||
| 				throw ROMMachine::Error::MissingROMs; | 				throw ROMMachine::Error::MissingROMs; | ||||||
| 			} | 			} | ||||||
| @@ -381,11 +391,6 @@ template <Analyser::Static::AppleII::Target::Model model> class ConcreteMachine: | |||||||
| 
 | 
 | ||||||
| 			video_.set_character_rom(*roms[0]); | 			video_.set_character_rom(*roms[0]); | ||||||
| 
 | 
 | ||||||
| 			if(target.disk_controller != Target::DiskController::None) { |  | ||||||
| 				// Apple recommended slot 6 for the (first) Disk II.
 |  | ||||||
| 				install_card(6, new AppleII::DiskIICard(rom_fetcher, target.disk_controller == Target::DiskController::SixteenSector)); |  | ||||||
| 			} |  | ||||||
| 
 |  | ||||||
| 			// Set up the default memory blocks. On a II or II+ these values will never change.
 | 			// Set up the default memory blocks. On a II or II+ these values will never change.
 | ||||||
| 			// On a IIe they'll be affected by selection of auxiliary RAM.
 | 			// On a IIe they'll be affected by selection of auxiliary RAM.
 | ||||||
| 			set_main_paging(); | 			set_main_paging(); | ||||||
| @@ -700,7 +705,7 @@ template <Analyser::Static::AppleII::Target::Model model> class ConcreteMachine: | |||||||
| 					// If this is a card access, figure out which card is at play before determining
 | 					// If this is a card access, figure out which card is at play before determining
 | ||||||
| 					// the totality of who needs messaging.
 | 					// the totality of who needs messaging.
 | ||||||
| 					size_t card_number = 0; | 					size_t card_number = 0; | ||||||
| 					AppleII::Card::Select select = AppleII::Card::None; | 					Apple::II::Card::Select select = Apple::II::Card::None; | ||||||
| 
 | 
 | ||||||
| 					if(address >= 0xc100) { | 					if(address >= 0xc100) { | ||||||
| 						/*
 | 						/*
 | ||||||
| @@ -708,20 +713,20 @@ template <Analyser::Static::AppleII::Target::Model model> class ConcreteMachine: | |||||||
| 								0xCn00 to 0xCnff: card n. | 								0xCn00 to 0xCnff: card n. | ||||||
| 						*/ | 						*/ | ||||||
| 						card_number = (address - 0xc100) >> 8; | 						card_number = (address - 0xc100) >> 8; | ||||||
| 						select = AppleII::Card::Device; | 						select = Apple::II::Card::Device; | ||||||
| 					} else { | 					} else { | ||||||
| 						/*
 | 						/*
 | ||||||
| 							Decode the area conventionally used by cards for registers: | 							Decode the area conventionally used by cards for registers: | ||||||
| 								C0n0 to C0nF: card n - 8. | 								C0n0 to C0nF: card n - 8. | ||||||
| 						*/ | 						*/ | ||||||
| 						card_number = (address - 0xc090) >> 4; | 						card_number = (address - 0xc090) >> 4; | ||||||
| 						select = AppleII::Card::IO; | 						select = Apple::II::Card::IO; | ||||||
| 					} | 					} | ||||||
| 
 | 
 | ||||||
| 					// If the selected card is a just-in-time card, update the just-in-time cards,
 | 					// If the selected card is a just-in-time card, update the just-in-time cards,
 | ||||||
| 					// and then message it specifically.
 | 					// and then message it specifically.
 | ||||||
| 					const bool is_read = isReadOperation(operation); | 					const bool is_read = isReadOperation(operation); | ||||||
| 					AppleII::Card *const target = cards_[static_cast<size_t>(card_number)].get(); | 					Apple::II::Card *const target = cards_[static_cast<size_t>(card_number)].get(); | ||||||
| 					if(target && !is_every_cycle_card(target)) { | 					if(target && !is_every_cycle_card(target)) { | ||||||
| 						update_just_in_time_cards(); | 						update_just_in_time_cards(); | ||||||
| 						target->perform_bus_operation(select, is_read, address, value); | 						target->perform_bus_operation(select, is_read, address, value); | ||||||
| @@ -732,7 +737,7 @@ template <Analyser::Static::AppleII::Target::Model model> class ConcreteMachine: | |||||||
| 					for(const auto &card: every_cycle_cards_) { | 					for(const auto &card: every_cycle_cards_) { | ||||||
| 						card->run_for(Cycles(1), is_stretched_cycle); | 						card->run_for(Cycles(1), is_stretched_cycle); | ||||||
| 						card->perform_bus_operation( | 						card->perform_bus_operation( | ||||||
| 							(card == target) ? select : AppleII::Card::None, | 							(card == target) ? select : Apple::II::Card::None, | ||||||
| 							is_read, address, value); | 							is_read, address, value); | ||||||
| 					} | 					} | ||||||
| 					has_updated_cards = true; | 					has_updated_cards = true; | ||||||
| @@ -744,7 +749,7 @@ template <Analyser::Static::AppleII::Target::Model model> class ConcreteMachine: | |||||||
| 				const bool is_read = isReadOperation(operation); | 				const bool is_read = isReadOperation(operation); | ||||||
| 				for(const auto &card: every_cycle_cards_) { | 				for(const auto &card: every_cycle_cards_) { | ||||||
| 					card->run_for(Cycles(1), is_stretched_cycle); | 					card->run_for(Cycles(1), is_stretched_cycle); | ||||||
| 					card->perform_bus_operation(AppleII::Card::None, is_read, address, value); | 					card->perform_bus_operation(Apple::II::Card::None, is_read, address, value); | ||||||
| 				} | 				} | ||||||
| 			} | 			} | ||||||
| 
 | 
 | ||||||
| @@ -818,7 +823,7 @@ template <Analyser::Static::AppleII::Target::Model model> class ConcreteMachine: | |||||||
| 
 | 
 | ||||||
| 		// MARK:: Configuration options.
 | 		// MARK:: Configuration options.
 | ||||||
| 		std::vector<std::unique_ptr<Configurable::Option>> get_options() override { | 		std::vector<std::unique_ptr<Configurable::Option>> get_options() override { | ||||||
| 			return AppleII::get_options(); | 			return Apple::II::get_options(); | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		void set_selections(const Configurable::SelectionSet &selections_by_option) override { | 		void set_selections(const Configurable::SelectionSet &selections_by_option) override { | ||||||
| @@ -860,9 +865,10 @@ template <Analyser::Static::AppleII::Target::Model model> class ConcreteMachine: | |||||||
| 		} | 		} | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
|  | } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| using namespace AppleII; | using namespace Apple::II; | ||||||
| 
 | 
 | ||||||
| Machine *Machine::AppleII(const Analyser::Static::Target *target, const ROMMachine::ROMFetcher &rom_fetcher) { | Machine *Machine::AppleII(const Analyser::Static::Target *target, const ROMMachine::ROMFetcher &rom_fetcher) { | ||||||
| 	using Target = Analyser::Static::AppleII::Target; | 	using Target = Analyser::Static::AppleII::Target; | ||||||
| @@ -9,14 +9,15 @@ | |||||||
| #ifndef AppleII_hpp | #ifndef AppleII_hpp | ||||||
| #define AppleII_hpp | #define AppleII_hpp | ||||||
| 
 | 
 | ||||||
| #include "../../Configurable/Configurable.hpp" | #include "../../../Configurable/Configurable.hpp" | ||||||
| #include "../../Analyser/Static/StaticAnalyser.hpp" | #include "../../../Analyser/Static/StaticAnalyser.hpp" | ||||||
| #include "../ROMMachine.hpp" | #include "../../ROMMachine.hpp" | ||||||
| 
 | 
 | ||||||
| #include <memory> | #include <memory> | ||||||
| #include <vector> | #include <vector> | ||||||
| 
 | 
 | ||||||
| namespace AppleII { | namespace Apple { | ||||||
|  | namespace II { | ||||||
| 
 | 
 | ||||||
| /// @returns The options available for an Apple II.
 | /// @returns The options available for an Apple II.
 | ||||||
| std::vector<std::unique_ptr<Configurable::Option>> get_options(); | std::vector<std::unique_ptr<Configurable::Option>> get_options(); | ||||||
| @@ -29,6 +30,7 @@ class Machine { | |||||||
| 		static Machine *AppleII(const Analyser::Static::Target *target, const ROMMachine::ROMFetcher &rom_fetcher); | 		static Machine *AppleII(const Analyser::Static::Target *target, const ROMMachine::ROMFetcher &rom_fetcher); | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| }; | } | ||||||
|  | } | ||||||
| 
 | 
 | ||||||
| #endif /* AppleII_hpp */ | #endif /* AppleII_hpp */ | ||||||
| @@ -9,11 +9,12 @@ | |||||||
| #ifndef Card_h | #ifndef Card_h | ||||||
| #define Card_h | #define Card_h | ||||||
| 
 | 
 | ||||||
| #include "../../Processors/6502/6502.hpp" | #include "../../../Processors/6502/6502.hpp" | ||||||
| #include "../../ClockReceiver/ClockReceiver.hpp" | #include "../../../ClockReceiver/ClockReceiver.hpp" | ||||||
| #include "../../Activity/Observer.hpp" | #include "../../../Activity/Observer.hpp" | ||||||
| 
 | 
 | ||||||
| namespace AppleII { | namespace Apple { | ||||||
|  | namespace II { | ||||||
| 
 | 
 | ||||||
| /*!
 | /*!
 | ||||||
| 	This provides a small subset of the interface offered to cards installed in | 	This provides a small subset of the interface offered to cards installed in | ||||||
| @@ -109,6 +110,7 @@ class Card { | |||||||
| 		} | 		} | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
|  | } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| #endif /* Card_h */ | #endif /* Card_h */ | ||||||
| @@ -8,15 +8,25 @@ | |||||||
| 
 | 
 | ||||||
| #include "DiskIICard.hpp" | #include "DiskIICard.hpp" | ||||||
| 
 | 
 | ||||||
| using namespace AppleII; | using namespace Apple::II; | ||||||
| 
 | 
 | ||||||
| DiskIICard::DiskIICard(const ROMMachine::ROMFetcher &rom_fetcher, bool is_16_sector) : diskii_(2045454) { | DiskIICard::DiskIICard(const ROMMachine::ROMFetcher &rom_fetcher, bool is_16_sector) : diskii_(2045454) { | ||||||
| 	const auto roms = rom_fetcher( | 	std::vector<std::unique_ptr<std::vector<uint8_t>>> roms; | ||||||
| 		"DiskII", | 	if(is_16_sector) { | ||||||
| 		{ | 		roms = rom_fetcher({ | ||||||
| 			is_16_sector ? "boot-16.rom" : "boot-13.rom", | 			{"DiskII", "the Disk II 16-sector boot ROM", "boot-16.rom", 256, 0xce7144f6}, | ||||||
| 			is_16_sector ? "state-machine-16.rom" : "state-machine-13.rom" | 			{"DiskII", "the Disk II 16-sector state machine ROM", "state-machine-16.rom", 256, { 0x9796a238, 0xb72a2c70 } } | ||||||
| 		}); | 		}); | ||||||
|  | 	} else { | ||||||
|  | 		roms = rom_fetcher({ | ||||||
|  | 			{"DiskII", "the Disk II 13-sector boot ROM", "boot-13.rom", 256, 0xd34eb2ff}, | ||||||
|  | 			{"DiskII", "the Disk II 13-sector state machine ROM", "state-machine-13.rom", 256, 0x62e22620 } | ||||||
|  | 		}); | ||||||
|  | 	} | ||||||
|  | 	if(!roms[0] || !roms[1]) { | ||||||
|  | 		throw ROMMachine::Error::MissingROMs; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
| 	boot_ = std::move(*roms[0]); | 	boot_ = std::move(*roms[0]); | ||||||
| 	diskii_.set_state_machine(*roms[1]); | 	diskii_.set_state_machine(*roms[1]); | ||||||
| 	set_select_constraints(None); | 	set_select_constraints(None); | ||||||
| @@ -10,17 +10,18 @@ | |||||||
| #define DiskIICard_hpp | #define DiskIICard_hpp | ||||||
| 
 | 
 | ||||||
| #include "Card.hpp" | #include "Card.hpp" | ||||||
| #include "../ROMMachine.hpp" | #include "../../ROMMachine.hpp" | ||||||
| 
 | 
 | ||||||
| #include "../../Components/DiskII/DiskII.hpp" | #include "../../../Components/DiskII/DiskII.hpp" | ||||||
| #include "../../Storage/Disk/Disk.hpp" | #include "../../../Storage/Disk/Disk.hpp" | ||||||
| #include "../../ClockReceiver/ClockingHintSource.hpp" | #include "../../../ClockReceiver/ClockingHintSource.hpp" | ||||||
| 
 | 
 | ||||||
| #include <cstdint> | #include <cstdint> | ||||||
| #include <memory> | #include <memory> | ||||||
| #include <vector> | #include <vector> | ||||||
| 
 | 
 | ||||||
| namespace AppleII { | namespace Apple { | ||||||
|  | namespace II { | ||||||
| 
 | 
 | ||||||
| class DiskIICard: public Card, public ClockingHint::Observer { | class DiskIICard: public Card, public ClockingHint::Observer { | ||||||
| 	public: | 	public: | ||||||
| @@ -41,6 +42,7 @@ class DiskIICard: public Card, public ClockingHint::Observer { | |||||||
| 		ClockingHint::Preference diskii_clocking_preference_ = ClockingHint::Preference::RealTime; | 		ClockingHint::Preference diskii_clocking_preference_ = ClockingHint::Preference::RealTime; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
|  | } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| #endif /* DiskIICard_hpp */ | #endif /* DiskIICard_hpp */ | ||||||
| @@ -8,7 +8,7 @@ | |||||||
| 
 | 
 | ||||||
| #include "Video.hpp" | #include "Video.hpp" | ||||||
| 
 | 
 | ||||||
| using namespace AppleII::Video; | using namespace Apple::II::Video; | ||||||
| 
 | 
 | ||||||
| VideoBase::VideoBase(bool is_iie, std::function<void(Cycles)> &&target) : | VideoBase::VideoBase(bool is_iie, std::function<void(Cycles)> &&target) : | ||||||
| 	crt_(910, 1, Outputs::Display::Type::NTSC60, Outputs::Display::InputDataType::Luminance1), | 	crt_(910, 1, Outputs::Display::Type::NTSC60, Outputs::Display::InputDataType::Luminance1), | ||||||
| @@ -9,14 +9,15 @@ | |||||||
| #ifndef Video_hpp | #ifndef Video_hpp | ||||||
| #define Video_hpp | #define Video_hpp | ||||||
| 
 | 
 | ||||||
| #include "../../Outputs/CRT/CRT.hpp" | #include "../../../Outputs/CRT/CRT.hpp" | ||||||
| #include "../../ClockReceiver/ClockReceiver.hpp" | #include "../../../ClockReceiver/ClockReceiver.hpp" | ||||||
| #include "../../ClockReceiver/ClockDeferrer.hpp" | #include "../../../ClockReceiver/DeferredQueue.hpp" | ||||||
| 
 | 
 | ||||||
| #include <array> | #include <array> | ||||||
| #include <vector> | #include <vector> | ||||||
| 
 | 
 | ||||||
| namespace AppleII { | namespace Apple { | ||||||
|  | namespace II { | ||||||
| namespace Video { | namespace Video { | ||||||
| 
 | 
 | ||||||
| class BusHandler { | class BusHandler { | ||||||
| @@ -250,8 +251,8 @@ class VideoBase { | |||||||
| 		*/ | 		*/ | ||||||
| 		void output_fat_low_resolution(uint8_t *target, const uint8_t *source, size_t length, int column, int row) const; | 		void output_fat_low_resolution(uint8_t *target, const uint8_t *source, size_t length, int column, int row) const; | ||||||
| 
 | 
 | ||||||
| 		// Maintain a ClockDeferrer for delayed mode switches.
 | 		// Maintain a DeferredQueue for delayed mode switches.
 | ||||||
| 		ClockDeferrer<Cycles> deferrer_; | 		DeferredQueue<Cycles> deferrer_; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| template <class BusHandler, bool is_iie> class Video: public VideoBase { | template <class BusHandler, bool is_iie> class Video: public VideoBase { | ||||||
| @@ -600,6 +601,7 @@ template <class BusHandler, bool is_iie> class Video: public VideoBase { | |||||||
| 		BusHandler &bus_handler_; | 		BusHandler &bus_handler_; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
|  | } | ||||||
| } | } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
							
								
								
									
										97
									
								
								Machines/Apple/Macintosh/Audio.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										97
									
								
								Machines/Apple/Macintosh/Audio.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,97 @@ | |||||||
|  | // | ||||||
|  | //  Audio.cpp | ||||||
|  | //  Clock Signal | ||||||
|  | // | ||||||
|  | //  Created by Thomas Harte on 31/05/2019. | ||||||
|  | //  Copyright © 2019 Thomas Harte. All rights reserved. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | #include "Audio.hpp" | ||||||
|  |  | ||||||
|  | using namespace Apple::Macintosh; | ||||||
|  |  | ||||||
|  | namespace { | ||||||
|  |  | ||||||
|  | // The sample_length is coupled with the clock rate selected within the Macintosh proper; | ||||||
|  | // as per the header-declaration a divide-by-two clock is expected to arrive here. | ||||||
|  | const std::size_t sample_length = 352 / 2; | ||||||
|  |  | ||||||
|  | } | ||||||
|  |  | ||||||
|  | Audio::Audio(Concurrency::DeferringAsyncTaskQueue &task_queue) : task_queue_(task_queue) {} | ||||||
|  |  | ||||||
|  | // MARK: - Inputs | ||||||
|  |  | ||||||
|  | void Audio::post_sample(uint8_t sample) { | ||||||
|  | 	// Store sample directly indexed by current write pointer; this ensures that collected samples | ||||||
|  | 	// directly map to volume and enabled/disabled states. | ||||||
|  | 	sample_queue_.buffer[sample_queue_.write_pointer] = sample; | ||||||
|  | 	sample_queue_.write_pointer = (sample_queue_.write_pointer + 1) % sample_queue_.buffer.size(); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void Audio::set_volume(int volume) { | ||||||
|  | 	// Do nothing if the volume hasn't changed. | ||||||
|  | 	if(posted_volume_ == volume) return; | ||||||
|  | 	posted_volume_ = volume; | ||||||
|  |  | ||||||
|  | 	// Post the volume change as a deferred event. | ||||||
|  | 	task_queue_.defer([=] () { | ||||||
|  | 		volume_ = volume; | ||||||
|  | 		set_volume_multiplier(); | ||||||
|  | 	}); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void Audio::set_enabled(bool on) { | ||||||
|  | 	// Do nothing if the mask hasn't changed. | ||||||
|  | 	if(posted_enable_mask_ == int(on)) return; | ||||||
|  | 	posted_enable_mask_ = int(on); | ||||||
|  |  | ||||||
|  | 	// Post the enabled mask change as a deferred event. | ||||||
|  | 	task_queue_.defer([=] () { | ||||||
|  | 		enabled_mask_ = int(on); | ||||||
|  | 		set_volume_multiplier(); | ||||||
|  | 	}); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // MARK: - Output generation | ||||||
|  |  | ||||||
|  | bool Audio::is_zero_level() { | ||||||
|  | 	return !volume_ || !enabled_mask_; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void Audio::set_sample_volume_range(std::int16_t range) { | ||||||
|  | 	// Some underflow here doesn't really matter. | ||||||
|  | 	output_volume_ = range / (7 * 255); | ||||||
|  | 	set_volume_multiplier(); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void Audio::set_volume_multiplier() { | ||||||
|  | 	volume_multiplier_ = int16_t(output_volume_ * volume_ * enabled_mask_); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void Audio::get_samples(std::size_t number_of_samples, int16_t *target) { | ||||||
|  | 	// TODO: the implementation below acts as if the hardware uses pulse-amplitude modulation; | ||||||
|  | 	// in fact it uses pulse-width modulation. But the scale for pulses isn't specified, so | ||||||
|  | 	// that's something to return to. | ||||||
|  |  | ||||||
|  | 	while(number_of_samples) { | ||||||
|  | 		// Determine how many output samples will be at the same level. | ||||||
|  | 		const auto cycles_left_in_sample = std::min(number_of_samples, sample_length - subcycle_offset_); | ||||||
|  |  | ||||||
|  | 		// Determine the output level, and output that many samples. | ||||||
|  | 		// (Hoping that the copiler substitutes an effective memset16-type operation here). | ||||||
|  | 		const int16_t output_level = volume_multiplier_ * (int16_t(sample_queue_.buffer[sample_queue_.read_pointer]) - 128); | ||||||
|  | 		for(size_t c = 0; c < cycles_left_in_sample; ++c) { | ||||||
|  | 			target[c] = output_level; | ||||||
|  | 		} | ||||||
|  | 		target += cycles_left_in_sample; | ||||||
|  |  | ||||||
|  | 		// Advance the sample pointer. | ||||||
|  | 		subcycle_offset_ += cycles_left_in_sample; | ||||||
|  | 		sample_queue_.read_pointer = (sample_queue_.read_pointer + (subcycle_offset_ / sample_length)) % sample_queue_.buffer.size(); | ||||||
|  | 		subcycle_offset_ %= sample_length; | ||||||
|  |  | ||||||
|  | 		// Decreate the number of samples left to write. | ||||||
|  | 		number_of_samples -= cycles_left_in_sample; | ||||||
|  | 	} | ||||||
|  | } | ||||||
							
								
								
									
										88
									
								
								Machines/Apple/Macintosh/Audio.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										88
									
								
								Machines/Apple/Macintosh/Audio.hpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,88 @@ | |||||||
|  | // | ||||||
|  | //  Audio.hpp | ||||||
|  | //  Clock Signal | ||||||
|  | // | ||||||
|  | //  Created by Thomas Harte on 31/05/2019. | ||||||
|  | //  Copyright © 2019 Thomas Harte. All rights reserved. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | #ifndef Audio_hpp | ||||||
|  | #define Audio_hpp | ||||||
|  |  | ||||||
|  | #include "../../../Concurrency/AsyncTaskQueue.hpp" | ||||||
|  | #include "../../../ClockReceiver/ClockReceiver.hpp" | ||||||
|  | #include "../../../Outputs/Speaker/Implementation/SampleSource.hpp" | ||||||
|  |  | ||||||
|  | #include <array> | ||||||
|  | #include <atomic> | ||||||
|  |  | ||||||
|  | namespace Apple { | ||||||
|  | namespace Macintosh { | ||||||
|  |  | ||||||
|  | /*! | ||||||
|  | 	Implements the Macintosh's audio output hardware. | ||||||
|  |  | ||||||
|  | 	Designed to be clocked at half the rate of the real hardware — i.e. | ||||||
|  | 	a shade less than 4Mhz. | ||||||
|  | */ | ||||||
|  | class Audio: public ::Outputs::Speaker::SampleSource { | ||||||
|  | 	public: | ||||||
|  | 		Audio(Concurrency::DeferringAsyncTaskQueue &task_queue); | ||||||
|  |  | ||||||
|  | 		/*! | ||||||
|  | 			Macintosh audio is (partly) sourced by the same scanning | ||||||
|  | 			hardware as the video; each line it collects an additional | ||||||
|  | 			word of memory, half of which is used for audio output. | ||||||
|  |  | ||||||
|  | 			Use this method to add a newly-collected sample to the queue. | ||||||
|  | 		*/ | ||||||
|  | 		void post_sample(uint8_t sample); | ||||||
|  |  | ||||||
|  | 		/*! | ||||||
|  | 			Macintosh audio also separately receives an output volume | ||||||
|  | 			level, in the range 0 to 7. | ||||||
|  |  | ||||||
|  | 			Use this method to set the current output volume. | ||||||
|  | 		*/ | ||||||
|  | 		void set_volume(int volume); | ||||||
|  |  | ||||||
|  | 		/*! | ||||||
|  | 			A further factor in audio output is the on-off toggle. | ||||||
|  | 		*/ | ||||||
|  | 		void set_enabled(bool on); | ||||||
|  |  | ||||||
|  | 		// to satisfy ::Outputs::Speaker (included via ::Outputs::Filter. | ||||||
|  | 		void get_samples(std::size_t number_of_samples, int16_t *target); | ||||||
|  | 		bool is_zero_level(); | ||||||
|  | 		void set_sample_volume_range(std::int16_t range); | ||||||
|  |  | ||||||
|  | 	private: | ||||||
|  | 		Concurrency::DeferringAsyncTaskQueue &task_queue_; | ||||||
|  |  | ||||||
|  | 		// A queue of fetched samples; read from by one thread, | ||||||
|  | 		// written to by another. | ||||||
|  | 		struct { | ||||||
|  | 			std::array<uint8_t, 740> buffer; | ||||||
|  | 			size_t read_pointer = 0, write_pointer = 0; | ||||||
|  | 		} sample_queue_; | ||||||
|  |  | ||||||
|  | 		// Emulator-thread stateful variables, to avoid work posting | ||||||
|  | 		// deferral updates if possible. | ||||||
|  | 		int posted_volume_ = 0; | ||||||
|  | 		int posted_enable_mask_ = 0; | ||||||
|  |  | ||||||
|  | 		// Stateful variables, modified from the audio generation | ||||||
|  | 		// thread only. | ||||||
|  | 		int volume_ = 0; | ||||||
|  | 		int enabled_mask_ = 0; | ||||||
|  | 		std::int16_t output_volume_ = 0; | ||||||
|  |  | ||||||
|  | 		std::int16_t volume_multiplier_ = 0; | ||||||
|  | 		std::size_t subcycle_offset_ = 0; | ||||||
|  | 		void set_volume_multiplier(); | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #endif /* Audio_hpp */ | ||||||
							
								
								
									
										34
									
								
								Machines/Apple/Macintosh/DeferredAudio.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								Machines/Apple/Macintosh/DeferredAudio.hpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,34 @@ | |||||||
|  | // | ||||||
|  | //  DeferredAudio.hpp | ||||||
|  | //  Clock Signal | ||||||
|  | // | ||||||
|  | //  Created by Thomas Harte on 01/06/2019. | ||||||
|  | //  Copyright © 2019 Thomas Harte. All rights reserved. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | #ifndef DeferredAudio_h | ||||||
|  | #define DeferredAudio_h | ||||||
|  |  | ||||||
|  | #include "Audio.hpp" | ||||||
|  | #include "../../../Outputs/Speaker/Implementation/LowpassSpeaker.hpp" | ||||||
|  |  | ||||||
|  | namespace Apple { | ||||||
|  | namespace Macintosh { | ||||||
|  |  | ||||||
|  | struct DeferredAudio { | ||||||
|  | 	Concurrency::DeferringAsyncTaskQueue queue; | ||||||
|  | 	Audio audio; | ||||||
|  | 	Outputs::Speaker::LowpassSpeaker<Audio> speaker; | ||||||
|  | 	HalfCycles time_since_update; | ||||||
|  |  | ||||||
|  | 	DeferredAudio() : audio(queue), speaker(audio) {} | ||||||
|  |  | ||||||
|  | 	void flush() { | ||||||
|  | 		speaker.run_for(queue, time_since_update.flush<Cycles>()); | ||||||
|  | 	} | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #endif /* DeferredAudio_h */ | ||||||
							
								
								
									
										63
									
								
								Machines/Apple/Macintosh/DriveSpeedAccumulator.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										63
									
								
								Machines/Apple/Macintosh/DriveSpeedAccumulator.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,63 @@ | |||||||
|  | // | ||||||
|  | //  DriveSpeedAccumulator.cpp | ||||||
|  | //  Clock Signal | ||||||
|  | // | ||||||
|  | //  Created by Thomas Harte on 01/06/2019. | ||||||
|  | //  Copyright © 2019 Thomas Harte. All rights reserved. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | #include "DriveSpeedAccumulator.hpp" | ||||||
|  |  | ||||||
|  | using namespace Apple::Macintosh; | ||||||
|  |  | ||||||
|  | void DriveSpeedAccumulator::post_sample(uint8_t sample) { | ||||||
|  | 	if(!number_of_drives_) return; | ||||||
|  |  | ||||||
|  | 	// An Euler-esque approximation is used here: just collect all | ||||||
|  | 	// the samples until there is a certain small quantity of them, | ||||||
|  | 	// then produce a new estimate of rotation speed and start the | ||||||
|  | 	// buffer afresh. | ||||||
|  | 	samples_[sample_pointer_] = sample; | ||||||
|  | 	++sample_pointer_; | ||||||
|  |  | ||||||
|  | 	if(sample_pointer_ == samples_.size()) { | ||||||
|  | 		sample_pointer_ = 0; | ||||||
|  |  | ||||||
|  | 		// The below fits for a function like `a + bc`; it encapsultes the following | ||||||
|  | 		// beliefs: | ||||||
|  | 		// | ||||||
|  | 		//	(i) motor speed is proportional to voltage supplied; | ||||||
|  | 		//	(ii) with pulse-width modulation it's therefore proportional to the duty cycle; | ||||||
|  | 		//	(iii) the Mac pulse-width modulates whatever it reads from the disk speed buffer; | ||||||
|  | 		//	(iv) ... subject to software pulse-width modulation of that pulse-width modulation. | ||||||
|  | 		// | ||||||
|  | 		// So, I believe current motor speed is proportional to a low-pass filtering of | ||||||
|  | 		// the speed buffer. Which I've implemented very coarsely via 'large' bucketed-averages, | ||||||
|  | 		// noting also that exact disk motor speed is always a little approximate. | ||||||
|  |  | ||||||
|  | 		// Sum all samples. | ||||||
|  | 		// TODO: if the above is the correct test, do it on sample receipt rather than | ||||||
|  | 		// bothering with an intermediate buffer. | ||||||
|  | 		int sum = 0; | ||||||
|  | 		for(auto s: samples_) { | ||||||
|  | 			sum += s; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		// The formula below was derived from observing values the Mac wrote into its | ||||||
|  | 		// disk-speed buffer. Given that it runs a calibration loop before doing so, | ||||||
|  | 		// I cannot guarantee the accuracy of these numbers beyond being within the | ||||||
|  | 		// range that the computer would accept. | ||||||
|  | 		const float normalised_sum = float(sum) / float(samples_.size()); | ||||||
|  | 		const float rotation_speed = (normalised_sum * 27.08f) - 259.0f; | ||||||
|  |  | ||||||
|  | 		for(int c = 0; c < number_of_drives_; ++c) { | ||||||
|  | 			drives_[c]->set_rotation_speed(rotation_speed); | ||||||
|  | 		} | ||||||
|  | //		printf("RPM: %0.2f (%d sum)\n", rotation_speed, sum); | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void DriveSpeedAccumulator::add_drive(Apple::Macintosh::DoubleDensityDrive *drive) { | ||||||
|  | 	drives_[number_of_drives_] = drive; | ||||||
|  | 	++number_of_drives_; | ||||||
|  | } | ||||||
							
								
								
									
										45
									
								
								Machines/Apple/Macintosh/DriveSpeedAccumulator.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										45
									
								
								Machines/Apple/Macintosh/DriveSpeedAccumulator.hpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,45 @@ | |||||||
|  | // | ||||||
|  | //  DriveSpeedAccumulator.hpp | ||||||
|  | //  Clock Signal | ||||||
|  | // | ||||||
|  | //  Created by Thomas Harte on 01/06/2019. | ||||||
|  | //  Copyright © 2019 Thomas Harte. All rights reserved. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | #ifndef DriveSpeedAccumulator_hpp | ||||||
|  | #define DriveSpeedAccumulator_hpp | ||||||
|  |  | ||||||
|  | #include <array> | ||||||
|  | #include <cstddef> | ||||||
|  | #include <cstdint> | ||||||
|  |  | ||||||
|  | #include "../../../Components/DiskII/MacintoshDoubleDensityDrive.hpp" | ||||||
|  |  | ||||||
|  | namespace Apple { | ||||||
|  | namespace Macintosh { | ||||||
|  |  | ||||||
|  | class DriveSpeedAccumulator { | ||||||
|  | 	public: | ||||||
|  | 		/*! | ||||||
|  | 			Accepts fetched motor control values. | ||||||
|  | 		*/ | ||||||
|  | 		void post_sample(uint8_t sample); | ||||||
|  |  | ||||||
|  | 		/*! | ||||||
|  | 			Adds a connected drive. Up to two of these | ||||||
|  | 			can be supplied. Only Macintosh DoubleDensityDrives | ||||||
|  | 			are supported. | ||||||
|  | 		*/ | ||||||
|  | 		void add_drive(Apple::Macintosh::DoubleDensityDrive *drive); | ||||||
|  |  | ||||||
|  | 	private: | ||||||
|  | 		std::array<uint8_t, 20> samples_; | ||||||
|  | 		std::size_t sample_pointer_ = 0; | ||||||
|  | 		Apple::Macintosh::DoubleDensityDrive *drives_[2] = {nullptr, nullptr}; | ||||||
|  | 		int number_of_drives_ = 0; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #endif /* DriveSpeedAccumulator_hpp */ | ||||||
							
								
								
									
										294
									
								
								Machines/Apple/Macintosh/Keyboard.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										294
									
								
								Machines/Apple/Macintosh/Keyboard.hpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,294 @@ | |||||||
|  | // | ||||||
|  | //  Keyboard.h | ||||||
|  | //  Clock Signal | ||||||
|  | // | ||||||
|  | //  Created by Thomas Harte on 08/05/2019. | ||||||
|  | //  Copyright © 2019 Thomas Harte. All rights reserved. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | #ifndef Apple_Macintosh_Keyboard_hpp | ||||||
|  | #define Apple_Macintosh_Keyboard_hpp | ||||||
|  |  | ||||||
|  | #include "../../KeyboardMachine.hpp" | ||||||
|  |  | ||||||
|  | #include <mutex> | ||||||
|  | #include <vector> | ||||||
|  |  | ||||||
|  | namespace Apple { | ||||||
|  | namespace Macintosh { | ||||||
|  |  | ||||||
|  | class Keyboard { | ||||||
|  | 	public: | ||||||
|  | 		void set_input(bool data) { | ||||||
|  | 			switch(mode_) { | ||||||
|  | 				case Mode::Waiting: | ||||||
|  | 					/* | ||||||
|  | 						"Only the computer can initiate communication over the keyboard lines. When the computer and keyboard | ||||||
|  | 						are turned on, the computer is in charge of the keyboard interface and the keyboard is passive. The | ||||||
|  | 						computer signals that it is ready to begin communication by pulling the Keyboard Data line low." | ||||||
|  | 					*/ | ||||||
|  | 					if(!data) { | ||||||
|  | 						mode_ = Mode::AcceptingCommand; | ||||||
|  | 						phase_ = 0; | ||||||
|  | 						command_ = 0; | ||||||
|  | 					} | ||||||
|  | 				break; | ||||||
|  |  | ||||||
|  | 				case Mode::AcceptingCommand: | ||||||
|  | 					/* Note value, so that it can be latched upon a clock transition. */ | ||||||
|  | 					data_input_ = data; | ||||||
|  | 				break; | ||||||
|  |  | ||||||
|  | 				case Mode::AwaitingEndOfCommand: | ||||||
|  | 					/* | ||||||
|  | 						The last bit of the command leaves the Keyboard Data line low; the computer then indicates that it is ready | ||||||
|  | 						to receive the keyboard's response by setting the Keyboard Data line high. | ||||||
|  | 					*/ | ||||||
|  | 					if(data) { | ||||||
|  | 						mode_ = Mode::PerformingCommand; | ||||||
|  | 						phase_ = 0; | ||||||
|  | 					} | ||||||
|  | 				break; | ||||||
|  |  | ||||||
|  | 				default: | ||||||
|  | 				case Mode::SendingResponse: | ||||||
|  | 					/* This line isn't currently an input; do nothing. */ | ||||||
|  | 				break; | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		bool get_clock() { | ||||||
|  | 			return clock_output_; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		bool get_data() { | ||||||
|  | 			return !!(response_ & 0x80); | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		/*! | ||||||
|  | 			The keyboard expects ~10 µs-frequency ticks, i.e. a clock rate of just around 100 kHz. | ||||||
|  | 		*/ | ||||||
|  | 		void run_for(HalfCycles cycle) { | ||||||
|  | 			switch(mode_) { | ||||||
|  | 				default: | ||||||
|  | 				case Mode::Waiting: return; | ||||||
|  |  | ||||||
|  | 				case Mode::AcceptingCommand: { | ||||||
|  | 					/* | ||||||
|  | 						"When the computer is sending data to the keyboard, the keyboard transmits eight cycles of 400 µS each (180 µS low, | ||||||
|  | 						220 µS high) on the Keyboard Clock line. On the falling edge of each keyboard clock cycle, the Macintosh Plus places | ||||||
|  | 						a data bit on the data line and holds it there for 400 µS. The keyboard reads the data bit 80 µS after the rising edge | ||||||
|  | 						of the Keyboard Clock signal." | ||||||
|  | 					*/ | ||||||
|  | 					const auto offset = phase_ % 40; | ||||||
|  | 					clock_output_ = offset >= 18; | ||||||
|  |  | ||||||
|  | 					if(offset == 26) { | ||||||
|  | 						command_ = (command_ << 1) | (data_input_ ? 1 : 0); | ||||||
|  | 					} | ||||||
|  |  | ||||||
|  | 					++phase_; | ||||||
|  | 					if(phase_ == 8*40) { | ||||||
|  | 						mode_ = Mode::AwaitingEndOfCommand; | ||||||
|  | 						phase_ = 0; | ||||||
|  | 						clock_output_ = false; | ||||||
|  | 					} | ||||||
|  | 				} break; | ||||||
|  |  | ||||||
|  | 				case Mode::AwaitingEndOfCommand: | ||||||
|  | 					// Time out if the end-of-command seems not to be forthcoming. | ||||||
|  | 					// This is an elaboration on my part; a guess. | ||||||
|  | 					++phase_; | ||||||
|  | 					if(phase_ == 1000) { | ||||||
|  | 						clock_output_ = false; | ||||||
|  | 						mode_ = Mode::Waiting; | ||||||
|  | 						phase_ = 0; | ||||||
|  | 					} | ||||||
|  | 				return; | ||||||
|  |  | ||||||
|  | 				case Mode::PerformingCommand: { | ||||||
|  | 					response_ = perform_command(command_); | ||||||
|  |  | ||||||
|  | 					// Inquiry has a 0.25-second timeout; everything else is instant. | ||||||
|  | 					++phase_; | ||||||
|  | 					if(phase_ == 25000 || command_ != 0x10 || response_ != 0x7b) { | ||||||
|  | 						mode_ = Mode::SendingResponse; | ||||||
|  | 						phase_ = 0; | ||||||
|  | 					} | ||||||
|  | 				} break; | ||||||
|  |  | ||||||
|  | 				case Mode::SendingResponse: { | ||||||
|  | 					/* | ||||||
|  | 						"When sending data to the computer, the keyboard transmits eight cycles of 330 µS each (160 µS low, 170 µS high) | ||||||
|  | 						on the normally high Keyboard Clock line. It places a data bit on the data line 40 µS before the falling edge of each | ||||||
|  | 						clock cycle and maintains it for 330 µS. The VIA in the computer latches the data bit into its shift register on the | ||||||
|  | 						rising edge of the Keyboard Clock signal." | ||||||
|  | 					*/ | ||||||
|  | 					const auto offset = phase_ % 33; | ||||||
|  | 					clock_output_ = offset >= 16; | ||||||
|  |  | ||||||
|  | 					if(offset == 29) { | ||||||
|  | 						response_ <<= 1; | ||||||
|  | 					} | ||||||
|  |  | ||||||
|  | 					++phase_; | ||||||
|  | 					if(phase_ == 8*33) { | ||||||
|  | 						clock_output_ = false; | ||||||
|  | 						mode_ = Mode::Waiting; | ||||||
|  | 						phase_ = 0; | ||||||
|  | 					} | ||||||
|  | 				} break; | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		void enqueue_key_state(uint16_t key, bool is_pressed) { | ||||||
|  | 			// Front insert; messages will be pop_back'd. | ||||||
|  | 			std::lock_guard<decltype(key_queue_mutex_)> lock(key_queue_mutex_); | ||||||
|  |  | ||||||
|  | 			// Keys on the keypad are preceded by a $79 keycode; in the internal naming scheme | ||||||
|  | 			// they are indicated by having bit 8 set. So add the $79 prefix if required. | ||||||
|  | 			if(key & 0x100) { | ||||||
|  | 				key_queue_.insert(key_queue_.begin(), 0x79); | ||||||
|  | 			} | ||||||
|  | 			key_queue_.insert(key_queue_.begin(), (is_pressed ? 0x00 : 0x80) | uint8_t(key)); | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 	private: | ||||||
|  |  | ||||||
|  | 		int perform_command(int command) { | ||||||
|  | 			switch(command) { | ||||||
|  | 				case 0x10:		// Inquiry. | ||||||
|  | 				case 0x14: {	// Instant. | ||||||
|  | 					std::lock_guard<decltype(key_queue_mutex_)> lock(key_queue_mutex_); | ||||||
|  | 					if(!key_queue_.empty()) { | ||||||
|  | 						const auto new_message = key_queue_.back(); | ||||||
|  | 						key_queue_.pop_back(); | ||||||
|  | 						return new_message; | ||||||
|  | 					} | ||||||
|  | 				} break; | ||||||
|  |  | ||||||
|  | 				case 0x16:	// Model number. | ||||||
|  | 				return | ||||||
|  | 					0x01 |			// b0: always 1 | ||||||
|  | 					(1 << 1) |		// keyboard model number | ||||||
|  | 					(1 << 4);		// next device number | ||||||
|  | 									// (b7 not set => no next device) | ||||||
|  |  | ||||||
|  | 				case 0x36:	// Test | ||||||
|  | 				return 0x7d;		// 0x7d = ACK, 0x77 = not ACK. | ||||||
|  | 			} | ||||||
|  | 			return 0x7b;	// No key transition. | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		enum class Mode { | ||||||
|  | 			Waiting, | ||||||
|  | 			AcceptingCommand, | ||||||
|  | 			AwaitingEndOfCommand, | ||||||
|  | 			SendingResponse, | ||||||
|  | 			PerformingCommand | ||||||
|  | 		} mode_ = Mode::Waiting; | ||||||
|  | 		int phase_ = 0; | ||||||
|  | 		int command_ = 0; | ||||||
|  | 		int response_ = 0; | ||||||
|  |  | ||||||
|  | 		bool data_input_ = false; | ||||||
|  | 		bool clock_output_ = false; | ||||||
|  |  | ||||||
|  | 		// TODO: improve this very, very simple implementation. | ||||||
|  | 		std::mutex key_queue_mutex_; | ||||||
|  | 		std::vector<uint8_t> key_queue_; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | /*! | ||||||
|  | 	Provides a mapping from idiomatic PC keys to Macintosh keys. | ||||||
|  | */ | ||||||
|  | class KeyboardMapper: public KeyboardMachine::MappedMachine::KeyboardMapper { | ||||||
|  | 	uint16_t mapped_key_for_key(Inputs::Keyboard::Key key) override { | ||||||
|  | 		using Key = Inputs::Keyboard::Key; | ||||||
|  | 		switch(key) { | ||||||
|  | 			default: return KeyboardMachine::MappedMachine::KeyNotMapped; | ||||||
|  |  | ||||||
|  | 			/* | ||||||
|  | 				See p284 of the Apple Guide to the Macintosh Family Hardware | ||||||
|  | 				for documentation of the mapping below. | ||||||
|  | 			*/ | ||||||
|  |  | ||||||
|  | 			case Key::BackTick:				return 0x65; | ||||||
|  | 			case Key::k1:					return 0x25; | ||||||
|  | 			case Key::k2:					return 0x27; | ||||||
|  | 			case Key::k3:					return 0x29; | ||||||
|  | 			case Key::k4:					return 0x2b; | ||||||
|  | 			case Key::k5:					return 0x2f; | ||||||
|  | 			case Key::k6:					return 0x2d; | ||||||
|  | 			case Key::k7:					return 0x35; | ||||||
|  | 			case Key::k8:					return 0x39; | ||||||
|  | 			case Key::k9:					return 0x33; | ||||||
|  | 			case Key::k0:					return 0x3b; | ||||||
|  | 			case Key::Hyphen:				return 0x37; | ||||||
|  | 			case Key::Equals:				return 0x31; | ||||||
|  | 			case Key::BackSpace:			return 0x67; | ||||||
|  |  | ||||||
|  | 			case Key::Tab:					return 0x61; | ||||||
|  | 			case Key::Q:					return 0x19; | ||||||
|  | 			case Key::W:					return 0x1b; | ||||||
|  | 			case Key::E:					return 0x1d; | ||||||
|  | 			case Key::R:					return 0x1f; | ||||||
|  | 			case Key::T:					return 0x23; | ||||||
|  | 			case Key::Y:					return 0x21; | ||||||
|  | 			case Key::U:					return 0x41; | ||||||
|  | 			case Key::I:					return 0x45; | ||||||
|  | 			case Key::O:					return 0x3f; | ||||||
|  | 			case Key::P:					return 0x47; | ||||||
|  | 			case Key::OpenSquareBracket:	return 0x43; | ||||||
|  | 			case Key::CloseSquareBracket:	return 0x3d; | ||||||
|  |  | ||||||
|  | 			case Key::CapsLock:				return 0x73; | ||||||
|  | 			case Key::A:					return 0x01; | ||||||
|  | 			case Key::S:					return 0x03; | ||||||
|  | 			case Key::D:					return 0x05; | ||||||
|  | 			case Key::F:					return 0x07; | ||||||
|  | 			case Key::G:					return 0x0b; | ||||||
|  | 			case Key::H:					return 0x09; | ||||||
|  | 			case Key::J:					return 0x4d; | ||||||
|  | 			case Key::K:					return 0x51; | ||||||
|  | 			case Key::L:					return 0x4b; | ||||||
|  | 			case Key::Semicolon:			return 0x53; | ||||||
|  | 			case Key::Quote:				return 0x4f; | ||||||
|  | 			case Key::Enter:				return 0x49; | ||||||
|  |  | ||||||
|  | 			case Key::LeftShift:			return 0x71; | ||||||
|  | 			case Key::Z:					return 0x0d; | ||||||
|  | 			case Key::X:					return 0x0f; | ||||||
|  | 			case Key::C:					return 0x11; | ||||||
|  | 			case Key::V:					return 0x13; | ||||||
|  | 			case Key::B:					return 0x17; | ||||||
|  | 			case Key::N:					return 0x5b; | ||||||
|  | 			case Key::M:					return 0x5d; | ||||||
|  | 			case Key::Comma:				return 0x57; | ||||||
|  | 			case Key::FullStop:				return 0x5f; | ||||||
|  | 			case Key::ForwardSlash:			return 0x59; | ||||||
|  | 			case Key::RightShift:			return 0x71; | ||||||
|  |  | ||||||
|  | 			case Key::Left:					return 0x100 | 0x0d; | ||||||
|  | 			case Key::Right:				return 0x100 | 0x05; | ||||||
|  | 			case Key::Up:					return 0x100 | 0x1b; | ||||||
|  | 			case Key::Down:					return 0x100 | 0x11; | ||||||
|  |  | ||||||
|  | 			case Key::LeftOption: | ||||||
|  | 			case Key::RightOption:			return 0x75; | ||||||
|  | 			case Key::LeftMeta: | ||||||
|  | 			case Key::RightMeta:			return 0x6f; | ||||||
|  |  | ||||||
|  | 			case Key::Space:				return 0x63; | ||||||
|  | 			case Key::BackSlash:			return 0x55; | ||||||
|  |  | ||||||
|  | 			/* TODO: the numeric keypad. */ | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #endif /* Apple_Macintosh_Keyboard_hpp */ | ||||||
							
								
								
									
										653
									
								
								Machines/Apple/Macintosh/Macintosh.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										653
									
								
								Machines/Apple/Macintosh/Macintosh.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,653 @@ | |||||||
|  | // | ||||||
|  | //  Macintosh.cpp | ||||||
|  | //  Clock Signal | ||||||
|  | // | ||||||
|  | //  Created by Thomas Harte on 03/05/2019. | ||||||
|  | //  Copyright © 2019 Thomas Harte. All rights reserved. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | #include "Macintosh.hpp" | ||||||
|  |  | ||||||
|  | #include <array> | ||||||
|  |  | ||||||
|  | #include "DeferredAudio.hpp" | ||||||
|  | #include "DriveSpeedAccumulator.hpp" | ||||||
|  | #include "Keyboard.hpp" | ||||||
|  | #include "RealTimeClock.hpp" | ||||||
|  | #include "Video.hpp" | ||||||
|  |  | ||||||
|  | #include "../../CRTMachine.hpp" | ||||||
|  | #include "../../KeyboardMachine.hpp" | ||||||
|  | #include "../../MediaTarget.hpp" | ||||||
|  | #include "../../MouseMachine.hpp" | ||||||
|  |  | ||||||
|  | #include "../../../Inputs/QuadratureMouse/QuadratureMouse.hpp" | ||||||
|  | #include "../../../Outputs/Log.hpp" | ||||||
|  |  | ||||||
|  | #include "../../../ClockReceiver/JustInTime.hpp" | ||||||
|  |  | ||||||
|  | //#define LOG_TRACE | ||||||
|  |  | ||||||
|  | #include "../../../Components/6522/6522.hpp" | ||||||
|  | #include "../../../Components/8530/z8530.hpp" | ||||||
|  | #include "../../../Components/DiskII/IWM.hpp" | ||||||
|  | #include "../../../Components/DiskII/MacintoshDoubleDensityDrive.hpp" | ||||||
|  | #include "../../../Processors/68000/68000.hpp" | ||||||
|  |  | ||||||
|  | #include "../../../Analyser/Static/Macintosh/Target.hpp" | ||||||
|  |  | ||||||
|  | #include "../../Utility/MemoryPacker.hpp" | ||||||
|  | #include "../../Utility/MemoryFuzzer.hpp" | ||||||
|  |  | ||||||
|  | namespace { | ||||||
|  |  | ||||||
|  | const int CLOCK_RATE = 7833600; | ||||||
|  |  | ||||||
|  | } | ||||||
|  |  | ||||||
|  | namespace Apple { | ||||||
|  | namespace Macintosh { | ||||||
|  |  | ||||||
|  | template <Analyser::Static::Macintosh::Target::Model model> class ConcreteMachine: | ||||||
|  | 	public Machine, | ||||||
|  | 	public CRTMachine::Machine, | ||||||
|  | 	public MediaTarget::Machine, | ||||||
|  | 	public MouseMachine::Machine, | ||||||
|  | 	public CPU::MC68000::BusHandler, | ||||||
|  | 	public KeyboardMachine::MappedMachine, | ||||||
|  | 	public Zilog::SCC::z8530::Delegate { | ||||||
|  | 	public: | ||||||
|  | 		using Target = Analyser::Static::Macintosh::Target; | ||||||
|  |  | ||||||
|  | 		ConcreteMachine(const Target &target, const ROMMachine::ROMFetcher &rom_fetcher) : | ||||||
|  | 		 	mc68000_(*this), | ||||||
|  | 		 	iwm_(CLOCK_RATE), | ||||||
|  | 		 	video_(ram_, audio_, drive_speed_accumulator_), | ||||||
|  | 		 	via_(via_port_handler_), | ||||||
|  | 		 	via_port_handler_(*this, clock_, keyboard_, video_, audio_, iwm_, mouse_), | ||||||
|  | 		 	drives_{ | ||||||
|  | 		 		{CLOCK_RATE, model >= Analyser::Static::Macintosh::Target::Model::Mac512ke}, | ||||||
|  | 		 		{CLOCK_RATE, model >= Analyser::Static::Macintosh::Target::Model::Mac512ke} | ||||||
|  | 			}, | ||||||
|  | 			mouse_(1) { | ||||||
|  |  | ||||||
|  | 			// Select a ROM name and determine the proper ROM and RAM sizes | ||||||
|  | 			// based on the machine model. | ||||||
|  | 			using Model = Analyser::Static::Macintosh::Target::Model; | ||||||
|  | 			const std::string machine_name = "Macintosh"; | ||||||
|  | 			uint32_t ram_size, rom_size; | ||||||
|  | 			std::vector<ROMMachine::ROM> rom_descriptions; | ||||||
|  | 			switch(model) { | ||||||
|  | 				default: | ||||||
|  | 				case Model::Mac128k: | ||||||
|  | 					ram_size = 128*1024; | ||||||
|  | 					rom_size = 64*1024; | ||||||
|  | 					rom_descriptions.emplace_back(machine_name, "the Macintosh 128k ROM", "mac128k.rom", 64*1024, 0x6d0c8a28); | ||||||
|  | 				break; | ||||||
|  | 				case Model::Mac512k: | ||||||
|  | 					ram_size = 512*1024; | ||||||
|  | 					rom_size = 64*1024; | ||||||
|  | 					rom_descriptions.emplace_back(machine_name, "the Macintosh 512k ROM", "mac512k.rom", 64*1024, 0xcf759e0d); | ||||||
|  | 				break; | ||||||
|  | 				case Model::Mac512ke: | ||||||
|  | 				case Model::MacPlus: { | ||||||
|  | 					ram_size = 512*1024; | ||||||
|  | 					rom_size = 128*1024; | ||||||
|  | 					const std::initializer_list<uint32_t> crc32s = { 0x4fa5b399, 0x7cacd18f, 0xb2102e8e }; | ||||||
|  | 					rom_descriptions.emplace_back(machine_name, "the Macintosh Plus ROM", "macplus.rom", 128*1024, crc32s); | ||||||
|  | 				} break; | ||||||
|  | 			} | ||||||
|  | 			ram_mask_ = (ram_size >> 1) - 1; | ||||||
|  | 			rom_mask_ = (rom_size >> 1) - 1; | ||||||
|  | 			video_.set_ram_mask(ram_mask_); | ||||||
|  |  | ||||||
|  | 			// Grab a copy of the ROM and convert it into big-endian data. | ||||||
|  | 			const auto roms = rom_fetcher(rom_descriptions); | ||||||
|  | 			if(!roms[0]) { | ||||||
|  | 				throw ROMMachine::Error::MissingROMs; | ||||||
|  | 			} | ||||||
|  | 			roms[0]->resize(rom_size); | ||||||
|  | 			Memory::PackBigEndian16(*roms[0], rom_); | ||||||
|  |  | ||||||
|  | 			// Randomise memory contents. | ||||||
|  | 			Memory::Fuzz(ram_, sizeof(ram_) / sizeof(*ram_)); | ||||||
|  |  | ||||||
|  | 			// Attach the drives to the IWM. | ||||||
|  | 			iwm_.iwm.set_drive(0, &drives_[0]); | ||||||
|  | 			iwm_.iwm.set_drive(1, &drives_[1]); | ||||||
|  |  | ||||||
|  | 			// If they are 400kb drives, also attach them to the drive-speed accumulator. | ||||||
|  | 			if(!drives_[0].is_800k()) drive_speed_accumulator_.add_drive(&drives_[0]); | ||||||
|  | 			if(!drives_[1].is_800k()) drive_speed_accumulator_.add_drive(&drives_[1]); | ||||||
|  |  | ||||||
|  | 			// Make sure interrupt changes from the SCC are observed. | ||||||
|  | 			scc_.set_delegate(this); | ||||||
|  |  | ||||||
|  | 			// The Mac runs at 7.8336mHz. | ||||||
|  | 			set_clock_rate(double(CLOCK_RATE)); | ||||||
|  | 			audio_.speaker.set_input_rate(float(CLOCK_RATE) / 2.0f); | ||||||
|  |  | ||||||
|  | 			// Insert any supplied media. | ||||||
|  | 			insert_media(target.media); | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		~ConcreteMachine() { | ||||||
|  | 			audio_.queue.flush(); | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		void set_scan_target(Outputs::Display::ScanTarget *scan_target) override { | ||||||
|  | 			video_.set_scan_target(scan_target); | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		Outputs::Speaker::Speaker *get_speaker() override { | ||||||
|  | 			return &audio_.speaker; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		void run_for(const Cycles cycles) override { | ||||||
|  | 			mc68000_.run_for(cycles); | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		using Microcycle = CPU::MC68000::Microcycle; | ||||||
|  |  | ||||||
|  | 		HalfCycles perform_bus_operation(const Microcycle &cycle, int is_supervisor) { | ||||||
|  | 			// TODO: pick a delay if this is a video-clashing memory fetch. | ||||||
|  | 			HalfCycles delay(0); | ||||||
|  |  | ||||||
|  | 			time_since_video_update_ += cycle.length; | ||||||
|  | 			iwm_.time_since_update += cycle.length; | ||||||
|  |  | ||||||
|  | 			// The VIA runs at one-tenth of the 68000's clock speed, in sync with the E clock. | ||||||
|  | 			// See: Guide to the Macintosh Hardware Family p149 (PDF p188). Some extra division | ||||||
|  | 			// may occur here in order to provide VSYNC at a proper moment. | ||||||
|  | 			// Possibly route vsync. | ||||||
|  | 			if(time_since_video_update_ < time_until_video_event_) { | ||||||
|  | 				via_clock_ += cycle.length; | ||||||
|  | 				via_.run_for(via_clock_.divide(HalfCycles(10))); | ||||||
|  | 			} else { | ||||||
|  | 				auto via_time_base = time_since_video_update_ - cycle.length; | ||||||
|  | 				auto via_cycles_outstanding = cycle.length; | ||||||
|  | 				while(time_until_video_event_ < time_since_video_update_) { | ||||||
|  | 					const auto via_cycles = time_until_video_event_ - via_time_base; | ||||||
|  | 					via_time_base = HalfCycles(0); | ||||||
|  | 					via_cycles_outstanding -= via_cycles; | ||||||
|  |  | ||||||
|  | 					via_clock_ += via_cycles; | ||||||
|  | 					via_.run_for(via_clock_.divide(HalfCycles(10))); | ||||||
|  |  | ||||||
|  | 					video_.run_for(time_until_video_event_); | ||||||
|  | 					time_since_video_update_ -= time_until_video_event_; | ||||||
|  | 					time_until_video_event_ = video_.get_next_sequence_point(); | ||||||
|  |  | ||||||
|  | 					via_.set_control_line_input(MOS::MOS6522::Port::A, MOS::MOS6522::Line::One, !video_.vsync()); | ||||||
|  | 				} | ||||||
|  |  | ||||||
|  | 				via_clock_ += via_cycles_outstanding; | ||||||
|  | 				via_.run_for(via_clock_.divide(HalfCycles(10))); | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			// The keyboard also has a clock, albeit a very slow one — 100,000 cycles/second. | ||||||
|  | 			// Its clock and data lines are connected to the VIA. | ||||||
|  | 			keyboard_clock_ += cycle.length; | ||||||
|  | 			const auto keyboard_ticks = keyboard_clock_.divide(HalfCycles(CLOCK_RATE / 100000)); | ||||||
|  | 			if(keyboard_ticks > HalfCycles(0)) { | ||||||
|  | 				keyboard_.run_for(keyboard_ticks); | ||||||
|  | 				via_.set_control_line_input(MOS::MOS6522::Port::B, MOS::MOS6522::Line::Two, keyboard_.get_data()); | ||||||
|  | 				via_.set_control_line_input(MOS::MOS6522::Port::B, MOS::MOS6522::Line::One, keyboard_.get_clock()); | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			// Feed mouse inputs within at most 1250 cycles of each other. | ||||||
|  | 			if(mouse_.has_steps()) { | ||||||
|  | 				time_since_mouse_update_ += cycle.length; | ||||||
|  | 				const auto mouse_ticks = time_since_mouse_update_.divide(HalfCycles(2500)); | ||||||
|  | 				if(mouse_ticks > HalfCycles(0)) { | ||||||
|  | 					mouse_.prepare_step(); | ||||||
|  | 					scc_.set_dcd(0, mouse_.get_channel(1) & 1); | ||||||
|  | 					scc_.set_dcd(1, mouse_.get_channel(0) & 1); | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			// TODO: SCC should be clocked at a divide-by-two, if and when it actually has | ||||||
|  | 			// anything connected. | ||||||
|  |  | ||||||
|  | 			// Consider updating the real-time clock. | ||||||
|  | 			real_time_clock_ += cycle.length; | ||||||
|  | 			auto ticks = real_time_clock_.divide_cycles(Cycles(CLOCK_RATE)).as_int(); | ||||||
|  | 			while(ticks--) { | ||||||
|  | 				clock_.update(); | ||||||
|  | 				// TODO: leave a delay between toggling the input rather than using this coupled hack. | ||||||
|  | 				via_.set_control_line_input(MOS::MOS6522::Port::A, MOS::MOS6522::Line::Two, true); | ||||||
|  | 				via_.set_control_line_input(MOS::MOS6522::Port::A, MOS::MOS6522::Line::Two, false); | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			// A null cycle leaves nothing else to do. | ||||||
|  | 			if(!(cycle.operation & (Microcycle::NewAddress | Microcycle::SameAddress))) return delay; | ||||||
|  |  | ||||||
|  | 			auto word_address = cycle.active_operation_word_address(); | ||||||
|  |  | ||||||
|  | 			// Everything above E0 0000 is signalled as being on the peripheral bus. | ||||||
|  | 			mc68000_.set_is_peripheral_address(word_address >= 0x700000); | ||||||
|  |  | ||||||
|  | 			// All code below deals only with reads and writes — cycles in which a | ||||||
|  | 			// data select is active. So quit now if this is not the active part of | ||||||
|  | 			//  a read or write. | ||||||
|  | 			if(!cycle.data_select_active()) return delay; | ||||||
|  |  | ||||||
|  | 			// Check whether this access maps into the IO area; if so then | ||||||
|  | 			// apply more complicated decoding logic. | ||||||
|  | 			if(word_address >= 0x400000) { | ||||||
|  | 				const int register_address = word_address >> 8; | ||||||
|  |  | ||||||
|  | 				switch(word_address & 0x78f000) { | ||||||
|  | 					case 0x70f000: | ||||||
|  | 						// VIA accesses are via address 0xefe1fe + register*512, | ||||||
|  | 						// which at word precision is 0x77f0ff + register*256. | ||||||
|  | 						if(cycle.operation & Microcycle::Read) { | ||||||
|  | 							cycle.value->halves.low = via_.get_register(register_address); | ||||||
|  | 						} else { | ||||||
|  | 							via_.set_register(register_address, cycle.value->halves.low); | ||||||
|  | 						} | ||||||
|  | 					break; | ||||||
|  |  | ||||||
|  | 					case 0x68f000: | ||||||
|  | 						// The IWM; this is a purely polled device, so can be run on demand. | ||||||
|  | 						iwm_.flush(); | ||||||
|  | 						if(cycle.operation & Microcycle::Read) { | ||||||
|  | 							cycle.value->halves.low = iwm_.iwm.read(register_address); | ||||||
|  | 						} else { | ||||||
|  | 							iwm_.iwm.write(register_address, cycle.value->halves.low); | ||||||
|  | 						} | ||||||
|  | 					break; | ||||||
|  |  | ||||||
|  | 					case 0x780000: | ||||||
|  | 						// Phase read. | ||||||
|  | 						if(cycle.operation & Microcycle::Read) { | ||||||
|  | 							cycle.value->halves.low = phase_ & 7; | ||||||
|  | 						} | ||||||
|  | 					break; | ||||||
|  |  | ||||||
|  | 					case 0x480000: case 0x48f000: | ||||||
|  | 					case 0x580000: case 0x58f000: | ||||||
|  | 						// Any word access here adjusts phase. | ||||||
|  | 						if(cycle.operation & Microcycle::SelectWord) { | ||||||
|  | 							++phase_; | ||||||
|  | 						} else { | ||||||
|  | 							if(word_address < 0x500000) { | ||||||
|  | 								// A0 = 1 => reset; A0 = 0 => read. | ||||||
|  | 								if(*cycle.address & 1) { | ||||||
|  | 									scc_.reset(); | ||||||
|  | 								} else { | ||||||
|  | 									const auto read = scc_.read(int(word_address)); | ||||||
|  | 									if(cycle.operation & Microcycle::Read) { | ||||||
|  | 										cycle.value->halves.low = read; | ||||||
|  | 									} | ||||||
|  | 								} | ||||||
|  | 							} else { | ||||||
|  | 								if(*cycle.address & 1) { | ||||||
|  | 									if(cycle.operation & Microcycle::Read) { | ||||||
|  | 										scc_.write(int(word_address), 0xff); | ||||||
|  | 									} else { | ||||||
|  | 										scc_.write(int(word_address), cycle.value->halves.low); | ||||||
|  | 									} | ||||||
|  | 								} | ||||||
|  | 							} | ||||||
|  | 						} | ||||||
|  | 					break; | ||||||
|  |  | ||||||
|  | 					default: | ||||||
|  | 						if(cycle.operation & Microcycle::Read) { | ||||||
|  | 							LOG("Unrecognised read " << PADHEX(6) << (*cycle.address & 0xffffff)); | ||||||
|  | 							cycle.value->halves.low = 0x00; | ||||||
|  | 						} else { | ||||||
|  | 							LOG("Unrecognised write %06x" << PADHEX(6) << (*cycle.address & 0xffffff)); | ||||||
|  | 						} | ||||||
|  | 					break; | ||||||
|  | 				} | ||||||
|  | 				if(cycle.operation & Microcycle::SelectWord) cycle.value->halves.high = 0xff; | ||||||
|  |  | ||||||
|  | 				return delay; | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			// Having reached here, this is a RAM or ROM access. | ||||||
|  |  | ||||||
|  | 			// When ROM overlay is enabled, the ROM begins at both $000000 and $400000, | ||||||
|  | 			// and RAM is available at $600000. | ||||||
|  | 			// | ||||||
|  | 			// Otherwise RAM is mapped at $000000 and ROM from $400000. | ||||||
|  | 			uint16_t *memory_base; | ||||||
|  | 			if( | ||||||
|  | 				(!ROM_is_overlay_ && word_address < 0x200000) || | ||||||
|  | 				(ROM_is_overlay_ && word_address >= 0x300000) | ||||||
|  | 			) { | ||||||
|  | 				memory_base = ram_; | ||||||
|  | 				word_address &= ram_mask_; | ||||||
|  |  | ||||||
|  | 				// This is coupled with the Macintosh implementation of video; the magic | ||||||
|  | 				// constant should probably be factored into the Video class. | ||||||
|  | 				// It embodies knowledge of the fact that video (and audio) will always | ||||||
|  | 				// be fetched from the final $d900 bytes (i.e. $6c80 words) of memory. | ||||||
|  | 				// (And that ram_mask_ = ram size - 1). | ||||||
|  | //				if(word_address > ram_mask_ - 0x6c80) | ||||||
|  | 					update_video(); | ||||||
|  | 			} else { | ||||||
|  | 				memory_base = rom_; | ||||||
|  | 				word_address &= rom_mask_; | ||||||
|  |  | ||||||
|  | 				// Writes to ROM have no effect, and it doesn't mirror above 0x60000. | ||||||
|  | 				if(!(cycle.operation & Microcycle::Read)) return delay; | ||||||
|  | 				if(word_address >= 0x300000) { | ||||||
|  | 					if(cycle.operation & Microcycle::SelectWord) { | ||||||
|  | 						cycle.value->full = 0xffff; | ||||||
|  | 					} else { | ||||||
|  | 						cycle.value->halves.low = 0xff; | ||||||
|  | 					} | ||||||
|  | 					return delay; | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			switch(cycle.operation & (Microcycle::SelectWord | Microcycle::SelectByte | Microcycle::Read | Microcycle::InterruptAcknowledge)) { | ||||||
|  | 				default: | ||||||
|  | 				break; | ||||||
|  |  | ||||||
|  | 				// Catches the deliberation set of operation to 0 above. | ||||||
|  | 				case 0: break; | ||||||
|  |  | ||||||
|  | 				case Microcycle::InterruptAcknowledge | Microcycle::SelectByte: | ||||||
|  | 					// The Macintosh uses autovectored interrupts. | ||||||
|  | 					mc68000_.set_is_peripheral_address(true); | ||||||
|  | 				break; | ||||||
|  |  | ||||||
|  | 				case Microcycle::SelectWord | Microcycle::Read: | ||||||
|  | 					cycle.value->full = memory_base[word_address]; | ||||||
|  | 				break; | ||||||
|  | 				case Microcycle::SelectByte | Microcycle::Read: | ||||||
|  | 					cycle.value->halves.low = uint8_t(memory_base[word_address] >> cycle.byte_shift()); | ||||||
|  | 				break; | ||||||
|  | 				case Microcycle::SelectWord: | ||||||
|  | 					memory_base[word_address] = cycle.value->full; | ||||||
|  | 				break; | ||||||
|  | 				case Microcycle::SelectByte: | ||||||
|  | 					memory_base[word_address] = uint16_t( | ||||||
|  | 						(cycle.value->halves.low << cycle.byte_shift()) | | ||||||
|  | 						(memory_base[word_address] & cycle.untouched_byte_mask()) | ||||||
|  | 					); | ||||||
|  | 				break; | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			/* | ||||||
|  | 				Normal memory map: | ||||||
|  |  | ||||||
|  | 				000000: 	RAM | ||||||
|  | 				400000: 	ROM | ||||||
|  | 				9FFFF8+:	SCC read operations | ||||||
|  | 				BFFFF8+:	SCC write operations | ||||||
|  | 				DFE1FF+:	IWM | ||||||
|  | 				EFE1FE+:	VIA | ||||||
|  | 			*/ | ||||||
|  |  | ||||||
|  | 			return delay; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		void flush() { | ||||||
|  | 			// Flush the video before the audio queue; in a Mac the | ||||||
|  | 			// video is responsible for providing part of the | ||||||
|  | 			// audio signal, so the two aren't as distinct as in | ||||||
|  | 			// most machines. | ||||||
|  | 			update_video(); | ||||||
|  |  | ||||||
|  | 			// As above: flush audio after video. | ||||||
|  | 			via_.flush(); | ||||||
|  | 			audio_.queue.perform(); | ||||||
|  |  | ||||||
|  | 			// Experimental? | ||||||
|  | 			iwm_.flush(); | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		void set_rom_is_overlay(bool rom_is_overlay) { | ||||||
|  | 			ROM_is_overlay_ = rom_is_overlay; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		bool video_is_outputting() { | ||||||
|  | 			return video_.is_outputting(time_since_video_update_); | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		void set_use_alternate_buffers(bool use_alternate_screen_buffer, bool use_alternate_audio_buffer) { | ||||||
|  | 			update_video(); | ||||||
|  | 			video_.set_use_alternate_buffers(use_alternate_screen_buffer, use_alternate_audio_buffer); | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		bool insert_media(const Analyser::Static::Media &media) override { | ||||||
|  | 			if(media.disks.empty()) | ||||||
|  | 				return false; | ||||||
|  |  | ||||||
|  | 			// TODO: shouldn't allow disks to be replaced like this, as the Mac | ||||||
|  | 			// uses software eject. Will need to expand messaging ability of | ||||||
|  | 			// insert_media. | ||||||
|  | 			if(drives_[0].has_disk()) | ||||||
|  | 				drives_[1].set_disk(media.disks[0]); | ||||||
|  | 			else | ||||||
|  | 				drives_[0].set_disk(media.disks[0]); | ||||||
|  |  | ||||||
|  | 			return true; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		// MARK: Keyboard input. | ||||||
|  |  | ||||||
|  | 		KeyboardMapper *get_keyboard_mapper() override { | ||||||
|  | 			return &keyboard_mapper_; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		void set_key_state(uint16_t key, bool is_pressed) override { | ||||||
|  | 			keyboard_.enqueue_key_state(key, is_pressed); | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		// TODO: clear all keys. | ||||||
|  |  | ||||||
|  | 		// MARK: Interrupt updates. | ||||||
|  |  | ||||||
|  | 		void did_change_interrupt_status(Zilog::SCC::z8530 *sender, bool new_status) override { | ||||||
|  | 			update_interrupt_input(); | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		void update_interrupt_input() { | ||||||
|  | 			// Update interrupt input. | ||||||
|  | 			// TODO: does this really cascade like this? | ||||||
|  | 			if(scc_.get_interrupt_line()) { | ||||||
|  | 				mc68000_.set_interrupt_level(2); | ||||||
|  | 			} else if(via_.get_interrupt_line()) { | ||||||
|  | 				mc68000_.set_interrupt_level(1); | ||||||
|  | 			} else { | ||||||
|  | 				mc68000_.set_interrupt_level(0); | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 	private: | ||||||
|  | 		void update_video() { | ||||||
|  | 			video_.run_for(time_since_video_update_.flush<HalfCycles>()); | ||||||
|  | 			time_until_video_event_ = video_.get_next_sequence_point(); | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		Inputs::Mouse &get_mouse() override { | ||||||
|  | 			return mouse_; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		struct IWM { | ||||||
|  | 			IWM(int clock_rate) : iwm(clock_rate) {} | ||||||
|  |  | ||||||
|  | 			HalfCycles time_since_update; | ||||||
|  | 			Apple::IWM iwm; | ||||||
|  |  | ||||||
|  | 			void flush() { | ||||||
|  | 				iwm.run_for(time_since_update.flush<Cycles>()); | ||||||
|  | 			} | ||||||
|  | 		}; | ||||||
|  |  | ||||||
|  | 		class VIAPortHandler: public MOS::MOS6522::PortHandler { | ||||||
|  | 			public: | ||||||
|  | 				VIAPortHandler(ConcreteMachine &machine, RealTimeClock &clock, Keyboard &keyboard, Video &video, DeferredAudio &audio, IWM &iwm, Inputs::QuadratureMouse &mouse) : | ||||||
|  | 					machine_(machine), clock_(clock), keyboard_(keyboard), video_(video), audio_(audio), iwm_(iwm), mouse_(mouse) {} | ||||||
|  |  | ||||||
|  | 				using Port = MOS::MOS6522::Port; | ||||||
|  | 				using Line = MOS::MOS6522::Line; | ||||||
|  |  | ||||||
|  | 				void set_port_output(Port port, uint8_t value, uint8_t direction_mask) { | ||||||
|  | 					/* | ||||||
|  | 						Peripheral lines: keyboard data, interrupt configuration. | ||||||
|  | 						(See p176 [/215]) | ||||||
|  | 					*/ | ||||||
|  | 					switch(port) { | ||||||
|  | 						case Port::A: | ||||||
|  | 							/* | ||||||
|  | 								Port A: | ||||||
|  | 									b7:	[input] SCC wait/request (/W/REQA and /W/REQB wired together for a logical OR) | ||||||
|  | 									b6:	0 = alternate screen buffer, 1 = main screen buffer | ||||||
|  | 									b5:	floppy disk SEL state control (upper/lower head "among other things") | ||||||
|  | 									b4:	1 = use ROM overlay memory map, 0 = use ordinary memory map | ||||||
|  | 									b3:	0 = use alternate sound buffer, 1 = use ordinary sound buffer | ||||||
|  | 									b2–b0:	audio output volume | ||||||
|  | 							*/ | ||||||
|  | 							iwm_.flush(); | ||||||
|  | 							iwm_.iwm.set_select(!!(value & 0x20)); | ||||||
|  |  | ||||||
|  | 							machine_.set_use_alternate_buffers(!(value & 0x40), !(value&0x08)); | ||||||
|  | 							machine_.set_rom_is_overlay(!!(value & 0x10)); | ||||||
|  |  | ||||||
|  | 							audio_.flush(); | ||||||
|  | 							audio_.audio.set_volume(value & 7); | ||||||
|  | 						break; | ||||||
|  |  | ||||||
|  | 						case Port::B: | ||||||
|  | 							/* | ||||||
|  | 								Port B: | ||||||
|  | 									b7:	0 = sound enabled, 1 = sound disabled | ||||||
|  | 									b6:	[input] 0 = video beam in visible portion of line, 1 = outside | ||||||
|  | 									b5:	[input] mouse y2 | ||||||
|  | 									b4:	[input] mouse x2 | ||||||
|  | 									b3:	[input] 0 = mouse button down, 1 = up | ||||||
|  | 									b2:	0 = real-time clock enabled, 1 = disabled | ||||||
|  | 									b1:	clock's data-clock line | ||||||
|  | 									b0:	clock's serial data line | ||||||
|  | 							*/ | ||||||
|  | 							if(value & 0x4) clock_.abort(); | ||||||
|  | 							else clock_.set_input(!!(value & 0x2), !!(value & 0x1)); | ||||||
|  |  | ||||||
|  | 							audio_.flush(); | ||||||
|  | 							audio_.audio.set_enabled(!(value & 0x80)); | ||||||
|  | 						break; | ||||||
|  | 					} | ||||||
|  | 				} | ||||||
|  |  | ||||||
|  | 				uint8_t get_port_input(Port port) { | ||||||
|  | 					switch(port) { | ||||||
|  | 						case Port::A: | ||||||
|  | //							printf("6522 r A\n"); | ||||||
|  | 						return 0x00;	// TODO: b7 = SCC wait/request | ||||||
|  |  | ||||||
|  | 						case Port::B: | ||||||
|  | 						return uint8_t( | ||||||
|  | 							((mouse_.get_button_mask() & 1) ? 0x00 : 0x08) | | ||||||
|  | 							((mouse_.get_channel(0) & 2) << 3) | | ||||||
|  | 							((mouse_.get_channel(1) & 2) << 4) | | ||||||
|  | 							(clock_.get_data() ? 0x02 : 0x00) | | ||||||
|  | 							(machine_.video_is_outputting() ? 0x00 : 0x40) | ||||||
|  | 						); | ||||||
|  | 					} | ||||||
|  |  | ||||||
|  | 					// Should be unreachable. | ||||||
|  | 					return 0xff; | ||||||
|  | 				} | ||||||
|  |  | ||||||
|  | 				void set_control_line_output(Port port, Line line, bool value) { | ||||||
|  | 					/* | ||||||
|  | 						Keyboard wiring (I believe): | ||||||
|  | 						CB2 = data		(input/output) | ||||||
|  | 						CB1 = clock		(input) | ||||||
|  |  | ||||||
|  | 						CA2 is used for receiving RTC interrupts. | ||||||
|  | 						CA1 is used for receiving vsync. | ||||||
|  | 					*/ | ||||||
|  | 					if(port == Port::B && line == Line::Two) { | ||||||
|  | 						keyboard_.set_input(value); | ||||||
|  | 					} | ||||||
|  | 					else LOG("Unhandled control line output: " << (port ? 'B' : 'A') << int(line)); | ||||||
|  | 				} | ||||||
|  |  | ||||||
|  | 				void run_for(HalfCycles duration) { | ||||||
|  | 					// The 6522 enjoys a divide-by-ten, so multiply back up here to make the | ||||||
|  | 					// divided-by-two clock the audio works on. | ||||||
|  | 					audio_.time_since_update += HalfCycles(duration.as_int() * 5); | ||||||
|  | 				} | ||||||
|  |  | ||||||
|  | 				void flush() { | ||||||
|  | 					audio_.flush(); | ||||||
|  | 				} | ||||||
|  |  | ||||||
|  | 				void set_interrupt_status(bool status) { | ||||||
|  | 					machine_.update_interrupt_input(); | ||||||
|  | 				} | ||||||
|  |  | ||||||
|  | 			private: | ||||||
|  | 				ConcreteMachine &machine_; | ||||||
|  | 				RealTimeClock &clock_; | ||||||
|  | 				Keyboard &keyboard_; | ||||||
|  | 				Video &video_; | ||||||
|  | 				DeferredAudio &audio_; | ||||||
|  | 				IWM &iwm_; | ||||||
|  | 				Inputs::QuadratureMouse &mouse_; | ||||||
|  | 		}; | ||||||
|  |  | ||||||
|  | 		CPU::MC68000::Processor<ConcreteMachine, true> mc68000_; | ||||||
|  |  | ||||||
|  | 		DriveSpeedAccumulator drive_speed_accumulator_; | ||||||
|  | 		IWM iwm_; | ||||||
|  |  | ||||||
|  | 		DeferredAudio audio_; | ||||||
|  | 		Video video_; | ||||||
|  |  | ||||||
|  | 		RealTimeClock clock_; | ||||||
|  | 		Keyboard keyboard_; | ||||||
|  |  | ||||||
|  | 		MOS::MOS6522::MOS6522<VIAPortHandler> via_; | ||||||
|  |  		VIAPortHandler via_port_handler_; | ||||||
|  |  | ||||||
|  |  		Zilog::SCC::z8530 scc_; | ||||||
|  |  | ||||||
|  |  		HalfCycles via_clock_; | ||||||
|  |  		HalfCycles real_time_clock_; | ||||||
|  |  		HalfCycles keyboard_clock_; | ||||||
|  |  		HalfCycles time_since_video_update_; | ||||||
|  |  		HalfCycles time_until_video_event_; | ||||||
|  |  		HalfCycles time_since_mouse_update_; | ||||||
|  |  | ||||||
|  | 		bool ROM_is_overlay_ = true; | ||||||
|  | 		int phase_ = 1; | ||||||
|  |  | ||||||
|  | 		DoubleDensityDrive drives_[2]; | ||||||
|  | 		Inputs::QuadratureMouse mouse_; | ||||||
|  |  | ||||||
|  | 		Apple::Macintosh::KeyboardMapper keyboard_mapper_; | ||||||
|  |  | ||||||
|  | 		uint32_t ram_mask_ = 0; | ||||||
|  | 		uint32_t rom_mask_ = 0; | ||||||
|  | 		uint16_t rom_[64*1024]; | ||||||
|  | 		uint16_t ram_[256*1024]; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | using namespace Apple::Macintosh; | ||||||
|  |  | ||||||
|  | Machine *Machine::Macintosh(const Analyser::Static::Target *target, const ROMMachine::ROMFetcher &rom_fetcher) { | ||||||
|  | 	auto *const mac_target = dynamic_cast<const Analyser::Static::Macintosh::Target *>(target); | ||||||
|  |  | ||||||
|  | 	using Model = Analyser::Static::Macintosh::Target::Model; | ||||||
|  | 	switch(mac_target->model) { | ||||||
|  | 		default: | ||||||
|  | 		case Model::Mac128k:	return new ConcreteMachine<Model::Mac128k>(*mac_target, rom_fetcher); | ||||||
|  | 		case Model::Mac512k:	return new ConcreteMachine<Model::Mac512k>(*mac_target, rom_fetcher); | ||||||
|  | 		case Model::Mac512ke:	return new ConcreteMachine<Model::Mac512ke>(*mac_target, rom_fetcher); | ||||||
|  | 		case Model::MacPlus:	return new ConcreteMachine<Model::MacPlus>(*mac_target, rom_fetcher); | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | Machine::~Machine() {} | ||||||
							
								
								
									
										30
									
								
								Machines/Apple/Macintosh/Macintosh.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								Machines/Apple/Macintosh/Macintosh.hpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,30 @@ | |||||||
|  | // | ||||||
|  | //  Macintosh.hpp | ||||||
|  | //  Clock Signal | ||||||
|  | // | ||||||
|  | //  Created by Thomas Harte on 03/05/2019. | ||||||
|  | //  Copyright © 2019 Thomas Harte. All rights reserved. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | #ifndef Macintosh_hpp | ||||||
|  | #define Macintosh_hpp | ||||||
|  |  | ||||||
|  | #include "../../../Analyser/Static/StaticAnalyser.hpp" | ||||||
|  | #include "../../ROMMachine.hpp" | ||||||
|  |  | ||||||
|  | namespace Apple { | ||||||
|  | namespace Macintosh { | ||||||
|  |  | ||||||
|  | class Machine { | ||||||
|  | 	public: | ||||||
|  | 		virtual ~Machine(); | ||||||
|  |  | ||||||
|  | 		/// Creates and returns a Macintosh. | ||||||
|  | 		static Machine *Macintosh(const Analyser::Static::Target *target, const ROMMachine::ROMFetcher &rom_fetcher); | ||||||
|  | }; | ||||||
|  |  | ||||||
|  |  | ||||||
|  | } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #endif /* Macintosh_hpp */ | ||||||
							
								
								
									
										166
									
								
								Machines/Apple/Macintosh/RealTimeClock.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										166
									
								
								Machines/Apple/Macintosh/RealTimeClock.hpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,166 @@ | |||||||
|  | // | ||||||
|  | //  RealTimeClock.hpp | ||||||
|  | //  Clock Signal | ||||||
|  | // | ||||||
|  | //  Created by Thomas Harte on 07/05/2019. | ||||||
|  | //  Copyright © 2019 Thomas Harte. All rights reserved. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | #ifndef RealTimeClock_hpp | ||||||
|  | #define RealTimeClock_hpp | ||||||
|  |  | ||||||
|  | #include "../../Utility/MemoryFuzzer.hpp" | ||||||
|  |  | ||||||
|  | namespace Apple { | ||||||
|  | namespace Macintosh { | ||||||
|  |  | ||||||
|  | /*! | ||||||
|  | 	Models the storage component of Apple's real-time clock. | ||||||
|  |  | ||||||
|  | 	Since tracking of time is pushed to this class, it is assumed | ||||||
|  | 	that whomever is translating real time into emulated time | ||||||
|  | 	will notify the VIA of a potential interrupt. | ||||||
|  | */ | ||||||
|  | class RealTimeClock { | ||||||
|  | 	public: | ||||||
|  | 		RealTimeClock() { | ||||||
|  | 			// TODO: this should persist, if possible, rather than | ||||||
|  | 			// being randomly initialised. | ||||||
|  | 			Memory::Fuzz(data_, sizeof(data_)); | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		/*! | ||||||
|  | 			Advances the clock by 1 second. | ||||||
|  |  | ||||||
|  | 			The caller should also notify the VIA. | ||||||
|  | 		*/ | ||||||
|  | 		void update() { | ||||||
|  | 			for(int c = 0; c < 4; ++c) { | ||||||
|  | 				++seconds_[c]; | ||||||
|  | 				if(seconds_[c]) break; | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		/*! | ||||||
|  | 			Sets the current clock and data inputs to the clock. | ||||||
|  | 		*/ | ||||||
|  | 		void set_input(bool clock, bool data) { | ||||||
|  | 			/* | ||||||
|  | 				Documented commands: | ||||||
|  |  | ||||||
|  | 					z0000001		Seconds register 0 (lowest order byte) | ||||||
|  | 					z0000101		Seconds register 1 | ||||||
|  | 					z0001001		Seconds register 2 | ||||||
|  | 					z0001101		Seconds register 3 | ||||||
|  | 					00110001		Test register (write only) | ||||||
|  | 					00110101		Write-protect register (write only) | ||||||
|  | 					z010aa01		RAM addresses 0x10 - 0x13 | ||||||
|  | 					z1aaaa01		RAM addresses 0x00 – 0x0f | ||||||
|  |  | ||||||
|  | 					z = 1 => a read; z = 0 => a write. | ||||||
|  |  | ||||||
|  | 				The top bit of the write-protect register enables (0) or disables (1) | ||||||
|  | 				writes to other locations. | ||||||
|  |  | ||||||
|  | 				All the documentation says about the test register is to set the top | ||||||
|  | 				two bits to 0 for normal operation. Abnormal operation is undefined. | ||||||
|  |  | ||||||
|  | 				The data line is valid when the clock transitions to level 0. | ||||||
|  | 			*/ | ||||||
|  |  | ||||||
|  | 			if(clock && !previous_clock_) { | ||||||
|  | 				// Shift into the command_ register, no matter what. | ||||||
|  | 				command_ = uint16_t((command_ << 1) | (data ? 1 : 0)); | ||||||
|  | 				result_ <<= 1; | ||||||
|  |  | ||||||
|  | 				// Increment phase. | ||||||
|  | 				++phase_; | ||||||
|  |  | ||||||
|  | 				// When phase hits 8, inspect the command. | ||||||
|  | 				// If it's a read, prepare a result. | ||||||
|  | 				if(phase_ == 8) { | ||||||
|  | 					if(command_ & 0x80) { | ||||||
|  | 						// A read. | ||||||
|  | 						const auto address = (command_ >> 2) & 0x1f; | ||||||
|  |  | ||||||
|  | 						// Begin pessimistically. | ||||||
|  | 						result_ = 0xff; | ||||||
|  |  | ||||||
|  | 						if(address < 4) { | ||||||
|  | 							result_ = seconds_[address]; | ||||||
|  | 						} else if(address >= 0x10) { | ||||||
|  | 							result_ = data_[address & 0xf]; | ||||||
|  | 						} else if(address >= 0x8 && address <= 0xb) { | ||||||
|  | 							result_ = data_[0x10 + (address & 0x3)]; | ||||||
|  | 						} | ||||||
|  | 					} | ||||||
|  | 				} | ||||||
|  |  | ||||||
|  | 				// If phase hits 16 and this was a read command, | ||||||
|  | 				// just stop. If it was a write command, do the | ||||||
|  | 				// actual write. | ||||||
|  | 				if(phase_ == 16) { | ||||||
|  | 					if(!(command_ & 0x8000)) { | ||||||
|  | 						// A write. | ||||||
|  |  | ||||||
|  | 						const auto address = (command_ >> 10) & 0x1f; | ||||||
|  | 						const uint8_t value = uint8_t(command_ & 0xff); | ||||||
|  |  | ||||||
|  | 						// First test: is this to the write-protect register? | ||||||
|  | 						if(address == 0xd) { | ||||||
|  | 							write_protect_ = value; | ||||||
|  | 						} | ||||||
|  |  | ||||||
|  | 						// No other writing is permitted if the write protect | ||||||
|  | 						// register won't allow it. | ||||||
|  | 						if(!(write_protect_ & 0x80)) { | ||||||
|  | 							if(address < 4) { | ||||||
|  | 								seconds_[address] = value; | ||||||
|  | 							} else if(address >= 0x10) { | ||||||
|  | 								data_[address & 0xf] = value; | ||||||
|  | 							} else if(address >= 0x8 && address <= 0xb) { | ||||||
|  | 								data_[0x10 + (address & 0x3)] = value; | ||||||
|  | 							} | ||||||
|  | 						} | ||||||
|  | 					} | ||||||
|  |  | ||||||
|  | 					// A phase of 16 always ends the command, so reset here. | ||||||
|  | 					abort(); | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			previous_clock_ = clock; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		/*! | ||||||
|  | 			Reads the current data output level from the clock. | ||||||
|  | 		*/ | ||||||
|  | 		bool get_data() { | ||||||
|  | 			return !!(result_ & 0x80); | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		/*! | ||||||
|  | 			Announces that a serial command has been aborted. | ||||||
|  | 		*/ | ||||||
|  | 		void abort() { | ||||||
|  | 			result_ = 0; | ||||||
|  | 			phase_ = 0; | ||||||
|  | 			command_ = 0; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 	private: | ||||||
|  | 		uint8_t data_[0x14]; | ||||||
|  | 		uint8_t seconds_[4]; | ||||||
|  | 		uint8_t write_protect_; | ||||||
|  |  | ||||||
|  | 		int phase_ = 0; | ||||||
|  | 		uint16_t command_; | ||||||
|  | 		uint8_t result_ = 0; | ||||||
|  |  | ||||||
|  | 		bool previous_clock_ = false; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #endif /* RealTimeClock_hpp */ | ||||||
							
								
								
									
										201
									
								
								Machines/Apple/Macintosh/Video.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										201
									
								
								Machines/Apple/Macintosh/Video.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,201 @@ | |||||||
|  | // | ||||||
|  | //  Video.cpp | ||||||
|  | //  Clock Signal | ||||||
|  | // | ||||||
|  | //  Created by Thomas Harte on 03/05/2019. | ||||||
|  | //  Copyright © 2019 Thomas Harte. All rights reserved. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | #include "Video.hpp" | ||||||
|  |  | ||||||
|  | #include <algorithm> | ||||||
|  |  | ||||||
|  | using namespace Apple::Macintosh; | ||||||
|  |  | ||||||
|  | namespace { | ||||||
|  |  | ||||||
|  | const HalfCycles line_length(704); | ||||||
|  | const int number_of_lines = 370; | ||||||
|  | const HalfCycles frame_length(line_length * HalfCycles(number_of_lines)); | ||||||
|  | const int sync_start = 36; | ||||||
|  | const int sync_end = 38; | ||||||
|  |  | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Re: CRT timings, see the Apple Guide to the Macintosh Hardware Family, | ||||||
|  | // bottom of page 400: | ||||||
|  | // | ||||||
|  | //	"For each scan line, 512 pixels are drawn on the screen ... | ||||||
|  | //	The horizontal blanking interval takes the time of an additional 192 pixels" | ||||||
|  | // | ||||||
|  | // And, at the top of 401: | ||||||
|  | // | ||||||
|  | //	"The visible portion of a full-screen display consists of 342 horizontal scan lines... | ||||||
|  | //	During the vertical blanking interval, the turned-off beam ... traces out an additional 28 scan lines," | ||||||
|  | // | ||||||
|  | Video::Video(uint16_t *ram, DeferredAudio &audio, DriveSpeedAccumulator &drive_speed_accumulator) : | ||||||
|  | 	audio_(audio), | ||||||
|  | 	drive_speed_accumulator_(drive_speed_accumulator), | ||||||
|  |  	crt_(704, 1, 370, Outputs::Display::ColourSpace::YIQ, 1, 1, 6, false, Outputs::Display::InputDataType::Luminance1), | ||||||
|  |  	ram_(ram) { | ||||||
|  |  | ||||||
|  |  	crt_.set_display_type(Outputs::Display::DisplayType::RGB); | ||||||
|  | 	crt_.set_visible_area(Outputs::Display::Rect(0.08f, -0.025f, 0.82f, 0.82f)); | ||||||
|  | 	crt_.set_aspect_ratio(1.73f);	// The Mac uses a non-standard scanning area. | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void Video::set_scan_target(Outputs::Display::ScanTarget *scan_target) { | ||||||
|  | 	crt_.set_scan_target(scan_target); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void Video::run_for(HalfCycles duration) { | ||||||
|  | 	// Determine the current video and audio bases. These values don't appear to be latched, they apply immediately. | ||||||
|  | 	const size_t video_base = (use_alternate_screen_buffer_ ? (0xffff2700 >> 1) : (0xffffa700 >> 1)) & ram_mask_; | ||||||
|  | 	const size_t audio_base = (use_alternate_audio_buffer_ ? (0xffffa100 >> 1) : (0xfffffd00 >> 1)) & ram_mask_; | ||||||
|  |  | ||||||
|  | 	// The number of HalfCycles is literally the number of pixel clocks to move through, | ||||||
|  | 	// since pixel output occurs at twice the processor clock. So divide by 16 to get | ||||||
|  | 	// the number of fetches. | ||||||
|  | 	while(duration > HalfCycles(0)) { | ||||||
|  | 		const auto pixel_start = frame_position_ % line_length; | ||||||
|  | 		const int line = (frame_position_ / line_length).as_int(); | ||||||
|  |  | ||||||
|  | 		const auto cycles_left_in_line = std::min(line_length - pixel_start, duration); | ||||||
|  |  | ||||||
|  | 		// Line timing, entirely invented as I can find exactly zero words of documentation: | ||||||
|  | 		// | ||||||
|  | 		//	First 342 lines: | ||||||
|  | 		// | ||||||
|  | 		//	First 32 words = pixels; | ||||||
|  | 		//	next 5 words = right border; | ||||||
|  | 		//	next 2 words = sync level; | ||||||
|  | 		//	final 5 words = left border. | ||||||
|  | 		// | ||||||
|  | 		//	Then 12 lines of border, 3 of sync, 11 more of border. | ||||||
|  |  | ||||||
|  | 		const int first_word = pixel_start.as_int() >> 4; | ||||||
|  | 		const int final_word = (pixel_start + cycles_left_in_line).as_int() >> 4; | ||||||
|  |  | ||||||
|  | 		if(first_word != final_word) { | ||||||
|  | 			if(line < 342) { | ||||||
|  | 				// If there are any pixels left to output, do so. | ||||||
|  | 				if(first_word < 32) { | ||||||
|  | 					const int final_pixel_word = std::min(final_word, 32); | ||||||
|  |  | ||||||
|  | 					if(!first_word) { | ||||||
|  | 						pixel_buffer_ = crt_.begin_data(512); | ||||||
|  | 					} | ||||||
|  |  | ||||||
|  | 					if(pixel_buffer_) { | ||||||
|  | 						for(int c = first_word; c < final_pixel_word; ++c) { | ||||||
|  | 							uint16_t pixels = ram_[video_base + video_address_] ^ 0xffff; | ||||||
|  | 							++video_address_; | ||||||
|  |  | ||||||
|  | 							pixel_buffer_[15] = pixels & 0x01; | ||||||
|  | 							pixel_buffer_[14] = pixels & 0x02; | ||||||
|  | 							pixel_buffer_[13] = pixels & 0x04; | ||||||
|  | 							pixel_buffer_[12] = pixels & 0x08; | ||||||
|  | 							pixel_buffer_[11] = pixels & 0x10; | ||||||
|  | 							pixel_buffer_[10] = pixels & 0x20; | ||||||
|  | 							pixel_buffer_[9] = pixels & 0x40; | ||||||
|  | 							pixel_buffer_[8] = pixels & 0x80; | ||||||
|  |  | ||||||
|  | 							pixels >>= 8; | ||||||
|  | 							pixel_buffer_[7] = pixels & 0x01; | ||||||
|  | 							pixel_buffer_[6] = pixels & 0x02; | ||||||
|  | 							pixel_buffer_[5] = pixels & 0x04; | ||||||
|  | 							pixel_buffer_[4] = pixels & 0x08; | ||||||
|  | 							pixel_buffer_[3] = pixels & 0x10; | ||||||
|  | 							pixel_buffer_[2] = pixels & 0x20; | ||||||
|  | 							pixel_buffer_[1] = pixels & 0x40; | ||||||
|  | 							pixel_buffer_[0] = pixels & 0x80; | ||||||
|  |  | ||||||
|  | 							pixel_buffer_ += 16; | ||||||
|  | 						} | ||||||
|  | 					} | ||||||
|  |  | ||||||
|  | 					if(final_pixel_word == 32) { | ||||||
|  | 						crt_.output_data(512); | ||||||
|  | 					} | ||||||
|  | 				} | ||||||
|  |  | ||||||
|  | 				if(first_word < sync_start && final_word >= sync_start)	crt_.output_blank((sync_start - 32) * 16); | ||||||
|  | 				if(first_word < sync_end && final_word >= sync_end)		crt_.output_sync((sync_end - sync_start) * 16); | ||||||
|  | 				if(final_word == 44)									crt_.output_blank((44 - sync_end) * 16); | ||||||
|  | 			} else if(final_word == 44) { | ||||||
|  | 				if(line >= 353 && line < 356) { | ||||||
|  | 					/* Output a sync line. */ | ||||||
|  | 					crt_.output_sync(sync_start * 16); | ||||||
|  | 					crt_.output_blank((sync_end - sync_start) * 16); | ||||||
|  | 					crt_.output_sync((44 - sync_end) * 16); | ||||||
|  | 				} else { | ||||||
|  | 					/* Output a blank line. */ | ||||||
|  | 					crt_.output_blank(sync_start * 16); | ||||||
|  | 					crt_.output_sync((sync_end - sync_start) * 16); | ||||||
|  | 					crt_.output_blank((44 - sync_end) * 16); | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			// Audio and disk fetches occur "just before video data". | ||||||
|  | 			if(final_word == 44) { | ||||||
|  | 				const uint16_t audio_word = ram_[audio_address_ + audio_base]; | ||||||
|  | 				++audio_address_; | ||||||
|  | 				audio_.audio.post_sample(audio_word >> 8); | ||||||
|  | 				drive_speed_accumulator_.post_sample(audio_word & 0xff); | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		duration -= cycles_left_in_line; | ||||||
|  | 		frame_position_ = frame_position_ + cycles_left_in_line; | ||||||
|  | 		if(frame_position_ == frame_length) { | ||||||
|  | 			frame_position_ = HalfCycles(0); | ||||||
|  | 			/* | ||||||
|  | 				Video: $1A700 and the alternate buffer starts at $12700; for a 512K Macintosh, add $60000 to these numbers. | ||||||
|  | 			*/ | ||||||
|  | 			video_address_ = 0; | ||||||
|  |  | ||||||
|  | 			/* | ||||||
|  | 				"The main sound buffer is at $1FD00 in a 128K Macintosh, and the alternate buffer is at $1A100; | ||||||
|  | 				for a 512K Macintosh, add $60000 to these values." | ||||||
|  | 			*/ | ||||||
|  | 			audio_address_ = 0; | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | bool Video::vsync() { | ||||||
|  | 	const int line = (frame_position_ / line_length).as_int(); | ||||||
|  | 	return line >= 353 && line < 356; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | HalfCycles Video::get_next_sequence_point() { | ||||||
|  | 	const int line = (frame_position_ / line_length).as_int(); | ||||||
|  | 	if(line >= 353 && line < 356) { | ||||||
|  | 		// Currently in vsync, so get time until start of line 357, | ||||||
|  | 		// when vsync will end. | ||||||
|  | 		return HalfCycles(356) * line_length - frame_position_; | ||||||
|  | 	} else { | ||||||
|  | 		// Not currently in vsync, so get time until start of line 353. | ||||||
|  | 		const auto start_of_vsync = HalfCycles(353) * line_length; | ||||||
|  | 		if(frame_position_ < start_of_vsync) | ||||||
|  | 			return start_of_vsync - frame_position_; | ||||||
|  | 		else | ||||||
|  | 			return start_of_vsync + HalfCycles(number_of_lines) * line_length - frame_position_; | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | bool Video::is_outputting(HalfCycles offset) { | ||||||
|  | 	const auto offset_position = frame_position_ + offset % frame_length; | ||||||
|  | 	const int column = (offset_position % line_length).as_int() >> 4; | ||||||
|  | 	const int line = (offset_position / line_length).as_int(); | ||||||
|  | 	return line < 342 && column < 32; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void Video::set_use_alternate_buffers(bool use_alternate_screen_buffer, bool use_alternate_audio_buffer) { | ||||||
|  | 	use_alternate_screen_buffer_ = use_alternate_screen_buffer; | ||||||
|  | 	use_alternate_audio_buffer_ = use_alternate_audio_buffer; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void Video::set_ram_mask(uint32_t mask) { | ||||||
|  | 	ram_mask_ = mask; | ||||||
|  | } | ||||||
							
								
								
									
										94
									
								
								Machines/Apple/Macintosh/Video.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										94
									
								
								Machines/Apple/Macintosh/Video.hpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,94 @@ | |||||||
|  | // | ||||||
|  | //  Video.hpp | ||||||
|  | //  Clock Signal | ||||||
|  | // | ||||||
|  | //  Created by Thomas Harte on 03/05/2019. | ||||||
|  | //  Copyright © 2019 Thomas Harte. All rights reserved. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | #ifndef Video_hpp | ||||||
|  | #define Video_hpp | ||||||
|  |  | ||||||
|  | #include "../../../Outputs/CRT/CRT.hpp" | ||||||
|  | #include "../../../ClockReceiver/ClockReceiver.hpp" | ||||||
|  | #include "DeferredAudio.hpp" | ||||||
|  | #include "DriveSpeedAccumulator.hpp" | ||||||
|  |  | ||||||
|  | namespace Apple { | ||||||
|  | namespace Macintosh { | ||||||
|  |  | ||||||
|  | /*! | ||||||
|  | 	Models the 68000-era Macintosh video hardware, producing a 512x348 pixel image, | ||||||
|  | 	within a total scanning area of 370 lines, at 352 cycles per line. | ||||||
|  |  | ||||||
|  | 	This class also collects audio and 400kb drive-speed data, forwarding those values. | ||||||
|  | */ | ||||||
|  | class Video { | ||||||
|  | 	public: | ||||||
|  | 		/*! | ||||||
|  | 			Constructs an instance of @c Video sourcing its pixel data from @c ram and | ||||||
|  | 			providing audio and drive-speed bytes to @c audio and @c drive_speed_accumulator. | ||||||
|  | 		*/ | ||||||
|  | 		Video(uint16_t *ram, DeferredAudio &audio, DriveSpeedAccumulator &drive_speed_accumulator); | ||||||
|  |  | ||||||
|  | 		/*! | ||||||
|  | 			Sets the target device for video data. | ||||||
|  | 		*/ | ||||||
|  | 		void set_scan_target(Outputs::Display::ScanTarget *scan_target); | ||||||
|  |  | ||||||
|  | 		/*! | ||||||
|  | 			Produces the next @c duration period of pixels. | ||||||
|  | 		*/ | ||||||
|  | 		void run_for(HalfCycles duration); | ||||||
|  |  | ||||||
|  | 		/*! | ||||||
|  | 			Sets whether the alternate screen and/or audio buffers should be used to source data. | ||||||
|  | 		*/ | ||||||
|  | 		void set_use_alternate_buffers(bool use_alternate_screen_buffer, bool use_alternate_audio_buffer); | ||||||
|  |  | ||||||
|  | 		/*! | ||||||
|  | 			Provides a mask indicating which parts of the generated video and audio/drive addresses are | ||||||
|  | 			actually decoded, accessing *word-sized memory*; e.g. for a 128kb Macintosh this should be (1 << 16) - 1 = 0xffff. | ||||||
|  | 		*/ | ||||||
|  | 		void set_ram_mask(uint32_t); | ||||||
|  |  | ||||||
|  | 		/*! | ||||||
|  | 			@returns @c true if the video is currently outputting a vertical sync, @c false otherwise. | ||||||
|  | 		*/ | ||||||
|  | 		bool vsync(); | ||||||
|  |  | ||||||
|  | 		/* | ||||||
|  | 			@returns @c true if in @c offset half cycles from now, the video will be outputting pixels; | ||||||
|  | 				@c false otherwise. | ||||||
|  | 		*/ | ||||||
|  | 		bool is_outputting(HalfCycles offset = HalfCycles(0)); | ||||||
|  |  | ||||||
|  | 		/*! | ||||||
|  | 			@returns the amount of time until there is next a transition on the | ||||||
|  | 				vsync signal. | ||||||
|  | 		*/ | ||||||
|  | 		HalfCycles get_next_sequence_point(); | ||||||
|  |  | ||||||
|  | 	private: | ||||||
|  | 		DeferredAudio &audio_; | ||||||
|  | 		DriveSpeedAccumulator &drive_speed_accumulator_; | ||||||
|  |  | ||||||
|  | 		Outputs::CRT::CRT crt_; | ||||||
|  | 		uint16_t *ram_ = nullptr; | ||||||
|  | 		uint32_t ram_mask_ = 0; | ||||||
|  |  | ||||||
|  | 		HalfCycles frame_position_; | ||||||
|  |  | ||||||
|  | 		size_t video_address_ = 0; | ||||||
|  | 		size_t audio_address_ = 0; | ||||||
|  |  | ||||||
|  | 		uint8_t *pixel_buffer_ = nullptr; | ||||||
|  |  | ||||||
|  | 		bool use_alternate_screen_buffer_ = false; | ||||||
|  | 		bool use_alternate_audio_buffer_ = false; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #endif /* Video_hpp */ | ||||||
| @@ -55,13 +55,13 @@ class Bus { | |||||||
| 		// video backlog accumulation counter | 		// video backlog accumulation counter | ||||||
| 		Cycles cycles_since_video_update_; | 		Cycles cycles_since_video_update_; | ||||||
| 		inline void update_video() { | 		inline void update_video() { | ||||||
| 			tia_.run_for(cycles_since_video_update_.flush()); | 			tia_.run_for(cycles_since_video_update_.flush<Cycles>()); | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		// RIOT backlog accumulation counter | 		// RIOT backlog accumulation counter | ||||||
| 		Cycles cycles_since_6532_update_; | 		Cycles cycles_since_6532_update_; | ||||||
| 		inline void update_6532() { | 		inline void update_6532() { | ||||||
| 			mos6532_.run_for(cycles_since_6532_update_.flush()); | 			mos6532_.run_for(cycles_since_6532_update_.flush<Cycles>()); | ||||||
| 		} | 		} | ||||||
| }; | }; | ||||||
|  |  | ||||||
|   | |||||||
| @@ -19,6 +19,7 @@ | |||||||
|  |  | ||||||
| #include "../../Configurable/StandardOptions.hpp" | #include "../../Configurable/StandardOptions.hpp" | ||||||
| #include "../../ClockReceiver/ForceInline.hpp" | #include "../../ClockReceiver/ForceInline.hpp" | ||||||
|  | #include "../../ClockReceiver/JustInTime.hpp" | ||||||
|  |  | ||||||
| #include "../../Outputs/Speaker/Implementation/CompoundSource.hpp" | #include "../../Outputs/Speaker/Implementation/CompoundSource.hpp" | ||||||
| #include "../../Outputs/Speaker/Implementation/LowpassSpeaker.hpp" | #include "../../Outputs/Speaker/Implementation/LowpassSpeaker.hpp" | ||||||
| @@ -131,8 +132,7 @@ class ConcreteMachine: | |||||||
| 			joysticks_.emplace_back(new Joystick); | 			joysticks_.emplace_back(new Joystick); | ||||||
|  |  | ||||||
| 			const auto roms = rom_fetcher( | 			const auto roms = rom_fetcher( | ||||||
| 				"ColecoVision", | 				{ {"ColecoVision", "the ColecoVision BIOS", "coleco.rom", 8*1024, 0x3aa93ef3} }); | ||||||
| 				{ "coleco.rom" }); |  | ||||||
|  |  | ||||||
| 			if(!roms[0]) { | 			if(!roms[0]) { | ||||||
| 				throw ROMMachine::Error::MissingROMs; | 				throw ROMMachine::Error::MissingROMs; | ||||||
| @@ -170,7 +170,7 @@ class ConcreteMachine: | |||||||
| 			} | 			} | ||||||
|  |  | ||||||
| 			// ColecoVisions have composite output only. | 			// ColecoVisions have composite output only. | ||||||
| 			vdp_.set_display_type(Outputs::Display::DisplayType::CompositeColour); | 			vdp_->set_display_type(Outputs::Display::DisplayType::CompositeColour); | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		~ConcreteMachine() { | 		~ConcreteMachine() { | ||||||
| @@ -182,11 +182,11 @@ class ConcreteMachine: | |||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		void set_scan_target(Outputs::Display::ScanTarget *scan_target) override { | 		void set_scan_target(Outputs::Display::ScanTarget *scan_target) override { | ||||||
| 			vdp_.set_scan_target(scan_target); | 			vdp_->set_scan_target(scan_target); | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		void set_display_type(Outputs::Display::DisplayType display_type) override { | 		void set_display_type(Outputs::Display::DisplayType display_type) override { | ||||||
| 			vdp_.set_display_type(display_type); | 			vdp_->set_display_type(display_type); | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		Outputs::Speaker::Speaker *get_speaker() override { | 		Outputs::Speaker::Speaker *get_speaker() override { | ||||||
| @@ -211,7 +211,7 @@ class ConcreteMachine: | |||||||
| 			} | 			} | ||||||
| 			const HalfCycles length = cycle.length + penalty; | 			const HalfCycles length = cycle.length + penalty; | ||||||
|  |  | ||||||
| 			time_since_vdp_update_ += length; | 			vdp_ += length; | ||||||
| 			time_since_sn76489_update_ += length; | 			time_since_sn76489_update_ += length; | ||||||
|  |  | ||||||
| 			// Act only if necessary. | 			// Act only if necessary. | ||||||
| @@ -256,10 +256,9 @@ class ConcreteMachine: | |||||||
| 					case CPU::Z80::PartialMachineCycle::Input: | 					case CPU::Z80::PartialMachineCycle::Input: | ||||||
| 						switch((address >> 5) & 7) { | 						switch((address >> 5) & 7) { | ||||||
| 							case 5: | 							case 5: | ||||||
| 								update_video(); | 								*cycle.value = vdp_->get_register(address); | ||||||
| 								*cycle.value = vdp_.get_register(address); | 								z80_.set_non_maskable_interrupt_line(vdp_->get_interrupt_line()); | ||||||
| 								z80_.set_non_maskable_interrupt_line(vdp_.get_interrupt_line()); | 								time_until_interrupt_ = vdp_->get_time_until_interrupt(); | ||||||
| 								time_until_interrupt_ = vdp_.get_time_until_interrupt(); |  | ||||||
| 							break; | 							break; | ||||||
|  |  | ||||||
| 							case 7: { | 							case 7: { | ||||||
| @@ -300,10 +299,9 @@ class ConcreteMachine: | |||||||
| 							break; | 							break; | ||||||
|  |  | ||||||
| 							case 5: | 							case 5: | ||||||
| 								update_video(); | 								vdp_->set_register(address, *cycle.value); | ||||||
| 								vdp_.set_register(address, *cycle.value); | 								z80_.set_non_maskable_interrupt_line(vdp_->get_interrupt_line()); | ||||||
| 								z80_.set_non_maskable_interrupt_line(vdp_.get_interrupt_line()); | 								time_until_interrupt_ = vdp_->get_time_until_interrupt(); | ||||||
| 								time_until_interrupt_ = vdp_.get_time_until_interrupt(); |  | ||||||
| 							break; | 							break; | ||||||
|  |  | ||||||
| 							case 7: | 							case 7: | ||||||
| @@ -355,7 +353,7 @@ class ConcreteMachine: | |||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		void flush() { | 		void flush() { | ||||||
| 			update_video(); | 			vdp_.flush(); | ||||||
| 			update_audio(); | 			update_audio(); | ||||||
| 			audio_queue_.perform(); | 			audio_queue_.perform(); | ||||||
| 		} | 		} | ||||||
| @@ -397,12 +395,9 @@ class ConcreteMachine: | |||||||
| 		inline void update_audio() { | 		inline void update_audio() { | ||||||
| 			speaker_.run_for(audio_queue_, time_since_sn76489_update_.divide_cycles(Cycles(sn76489_divider))); | 			speaker_.run_for(audio_queue_, time_since_sn76489_update_.divide_cycles(Cycles(sn76489_divider))); | ||||||
| 		} | 		} | ||||||
| 		inline void update_video() { |  | ||||||
| 			vdp_.run_for(time_since_vdp_update_.flush()); |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		CPU::Z80::Processor<ConcreteMachine, false, false> z80_; | 		CPU::Z80::Processor<ConcreteMachine, false, false> z80_; | ||||||
| 		TI::TMS::TMS9918 vdp_; | 		JustInTimeActor<TI::TMS::TMS9918, HalfCycles> vdp_; | ||||||
|  |  | ||||||
| 		Concurrency::DeferringAsyncTaskQueue audio_queue_; | 		Concurrency::DeferringAsyncTaskQueue audio_queue_; | ||||||
| 		TI::SN76489 sn76489_; | 		TI::SN76489 sn76489_; | ||||||
| @@ -425,7 +420,6 @@ class ConcreteMachine: | |||||||
| 		std::vector<std::unique_ptr<Inputs::Joystick>> joysticks_; | 		std::vector<std::unique_ptr<Inputs::Joystick>> joysticks_; | ||||||
| 		bool joysticks_in_keypad_mode_ = false; | 		bool joysticks_in_keypad_mode_ = false; | ||||||
|  |  | ||||||
| 		HalfCycles time_since_vdp_update_; |  | ||||||
| 		HalfCycles time_since_sn76489_update_; | 		HalfCycles time_since_sn76489_update_; | ||||||
| 		HalfCycles time_until_interrupt_; | 		HalfCycles time_until_interrupt_; | ||||||
|  |  | ||||||
|   | |||||||
| @@ -39,13 +39,20 @@ MachineBase::MachineBase(Personality personality, const ROMMachine::ROMFetcher & | |||||||
| 	// attach the only drive there is | 	// attach the only drive there is | ||||||
| 	set_drive(drive_); | 	set_drive(drive_); | ||||||
|  |  | ||||||
| 	std::string rom_name; | 	std::string device_name; | ||||||
|  | 	uint32_t crc = 0; | ||||||
| 	switch(personality) { | 	switch(personality) { | ||||||
| 		case Personality::C1540:	rom_name = "1540.bin";	break; | 		case Personality::C1540: | ||||||
| 		case Personality::C1541:	rom_name = "1541.bin";	break; | 			device_name = "1540"; | ||||||
|  | 			crc = 0x718d42b1; | ||||||
|  | 		break; | ||||||
|  | 		case Personality::C1541: | ||||||
|  | 			device_name = "1541"; | ||||||
|  | 			crc = 0xfb760019; | ||||||
|  | 		break; | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	auto roms = rom_fetcher("Commodore1540", {rom_name}); | 	auto roms = rom_fetcher({ {"Commodore1540", "the " + device_name + " ROM", device_name + ".bin", 16*1024, crc} }); | ||||||
| 	if(!roms[0]) { | 	if(!roms[0]) { | ||||||
| 		throw ROMMachine::Error::MissingROMs; | 		throw ROMMachine::Error::MissingROMs; | ||||||
| 	} | 	} | ||||||
|   | |||||||
| @@ -323,31 +323,34 @@ class ConcreteMachine: | |||||||
| 			// install a joystick | 			// install a joystick | ||||||
| 			joysticks_.emplace_back(new Joystick(*user_port_via_port_handler_, *keyboard_via_port_handler_)); | 			joysticks_.emplace_back(new Joystick(*user_port_via_port_handler_, *keyboard_via_port_handler_)); | ||||||
|  |  | ||||||
| 			std::vector<std::string> rom_names = { "basic.bin" }; | 			const std::string machine_name = "Vic20"; | ||||||
|  | 			std::vector<ROMMachine::ROM> rom_names = { | ||||||
|  | 				{machine_name, "the VIC-20 BASIC ROM", "basic.bin", 8*1024, 0xdb4c43c1} | ||||||
|  | 			}; | ||||||
| 			switch(target.region) { | 			switch(target.region) { | ||||||
| 				default: | 				default: | ||||||
| 					rom_names.push_back("characters-english.bin"); | 					rom_names.emplace_back(machine_name, "the English-language VIC-20 character ROM", "characters-english.bin", 4*1024, 0x83e032a6); | ||||||
| 					rom_names.push_back("kernel-pal.bin"); | 					rom_names.emplace_back(machine_name, "the English-language PAL VIC-20 kernel ROM", "kernel-pal.bin", 8*1024, 0x4be07cb4); | ||||||
| 				break; | 				break; | ||||||
| 				case Analyser::Static::Commodore::Target::Region::American: | 				case Analyser::Static::Commodore::Target::Region::American: | ||||||
| 					rom_names.push_back("characters-english.bin"); | 					rom_names.emplace_back(machine_name, "the English-language VIC-20 character ROM", "characters-english.bin", 4*1024, 0x83e032a6); | ||||||
| 					rom_names.push_back("kernel-ntsc.bin"); | 					rom_names.emplace_back(machine_name, "the English-language NTSC VIC-20 kernel ROM", "kernel-ntsc.bin", 8*1024, 0xe5e7c174); | ||||||
| 				break; | 				break; | ||||||
| 				case Analyser::Static::Commodore::Target::Region::Danish: | 				case Analyser::Static::Commodore::Target::Region::Danish: | ||||||
| 					rom_names.push_back("characters-danish.bin"); | 					rom_names.emplace_back(machine_name, "the Danish VIC-20 character ROM", "characters-danish.bin", 4*1024, 0x7fc11454); | ||||||
| 					rom_names.push_back("kernel-danish.bin"); | 					rom_names.emplace_back(machine_name, "the Danish VIC-20 kernel ROM", "kernel-danish.bin", 8*1024, 0x02adaf16); | ||||||
| 				break; | 				break; | ||||||
| 				case Analyser::Static::Commodore::Target::Region::Japanese: | 				case Analyser::Static::Commodore::Target::Region::Japanese: | ||||||
| 					rom_names.push_back("characters-japanese.bin"); | 					rom_names.emplace_back(machine_name, "the Japanese VIC-20 character ROM", "characters-japanese.bin", 4*1024, 0xfcfd8a4b); | ||||||
| 					rom_names.push_back("kernel-japanese.bin"); | 					rom_names.emplace_back(machine_name, "the Japanese VIC-20 kernel ROM", "kernel-japanese.bin", 8*1024, 0x336900d7); | ||||||
| 				break; | 				break; | ||||||
| 				case Analyser::Static::Commodore::Target::Region::Swedish: | 				case Analyser::Static::Commodore::Target::Region::Swedish: | ||||||
| 					rom_names.push_back("characters-swedish.bin"); | 					rom_names.emplace_back(machine_name, "the Swedish VIC-20 character ROM", "characters-swedish.bin", 4*1024, 0xd808551d); | ||||||
| 					rom_names.push_back("kernel-japanese.bin"); | 					rom_names.emplace_back(machine_name, "the Swedish VIC-20 kernel ROM", "kernel-swedish.bin", 8*1024, 0xb2a60662); | ||||||
| 				break; | 				break; | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
| 			const auto roms = rom_fetcher("Vic20", rom_names); | 			const auto roms = rom_fetcher(rom_names); | ||||||
|  |  | ||||||
| 			for(const auto &rom: roms) { | 			for(const auto &rom: roms) { | ||||||
| 				if(!rom) { | 				if(!rom) { | ||||||
| @@ -699,7 +702,7 @@ class ConcreteMachine: | |||||||
|  |  | ||||||
| 	private: | 	private: | ||||||
| 		void update_video() { | 		void update_video() { | ||||||
| 			mos6560_.run_for(cycles_since_mos6560_update_.flush()); | 			mos6560_.run_for(cycles_since_mos6560_update_.flush<Cycles>()); | ||||||
| 		} | 		} | ||||||
| 		CPU::MOS6502::Processor<CPU::MOS6502::Personality::P6502, ConcreteMachine, false> m6502_; | 		CPU::MOS6502::Processor<CPU::MOS6502::Personality::P6502, ConcreteMachine, false> m6502_; | ||||||
|  |  | ||||||
|   | |||||||
| @@ -11,10 +11,13 @@ | |||||||
|  |  | ||||||
| #include "../Configurable/Configurable.hpp" | #include "../Configurable/Configurable.hpp" | ||||||
| #include "../Activity/Source.hpp" | #include "../Activity/Source.hpp" | ||||||
| #include "MediaTarget.hpp" |  | ||||||
| #include "CRTMachine.hpp" | #include "CRTMachine.hpp" | ||||||
| #include "JoystickMachine.hpp" | #include "JoystickMachine.hpp" | ||||||
| #include "KeyboardMachine.hpp" | #include "KeyboardMachine.hpp" | ||||||
|  | #include "MediaTarget.hpp" | ||||||
|  | #include "MouseMachine.hpp" | ||||||
|  |  | ||||||
| #include "Utility/Typer.hpp" | #include "Utility/Typer.hpp" | ||||||
|  |  | ||||||
| namespace Machine { | namespace Machine { | ||||||
| @@ -31,6 +34,7 @@ struct DynamicMachine { | |||||||
| 	virtual CRTMachine::Machine *crt_machine() = 0; | 	virtual CRTMachine::Machine *crt_machine() = 0; | ||||||
| 	virtual JoystickMachine::Machine *joystick_machine() = 0; | 	virtual JoystickMachine::Machine *joystick_machine() = 0; | ||||||
| 	virtual KeyboardMachine::Machine *keyboard_machine() = 0; | 	virtual KeyboardMachine::Machine *keyboard_machine() = 0; | ||||||
|  | 	virtual MouseMachine::Machine *mouse_machine() = 0; | ||||||
| 	virtual MediaTarget::Machine *media_target() = 0; | 	virtual MediaTarget::Machine *media_target() = 0; | ||||||
|  |  | ||||||
| 	/*! | 	/*! | ||||||
|   | |||||||
| @@ -62,17 +62,22 @@ class ConcreteMachine: | |||||||
| 			set_clock_rate(2000000); | 			set_clock_rate(2000000); | ||||||
|  |  | ||||||
| 			speaker_.set_input_rate(2000000 / SoundGenerator::clock_rate_divider); | 			speaker_.set_input_rate(2000000 / SoundGenerator::clock_rate_divider); | ||||||
|  | 			speaker_.set_high_frequency_cutoff(7000); | ||||||
|  |  | ||||||
| 			std::vector<std::string> rom_names = {"basic.rom", "os.rom"}; | 			const std::string machine_name = "Electron"; | ||||||
|  | 			std::vector<ROMMachine::ROM> required_roms = { | ||||||
|  | 				{machine_name, "the Acorn BASIC II ROM", "basic.rom", 16*1024, 0x79434781}, | ||||||
|  | 				{machine_name, "the Electron MOS ROM", "os.rom", 16*1024, 0xbf63fb1f} | ||||||
|  | 			}; | ||||||
| 			if(target.has_adfs) { | 			if(target.has_adfs) { | ||||||
| 				rom_names.push_back("ADFS-E00_1.rom"); | 				required_roms.emplace_back(machine_name, "the E00 ADFS ROM, first slot", "ADFS-E00_1.rom", 16*1024, 0x51523993); | ||||||
| 				rom_names.push_back("ADFS-E00_2.rom"); | 				required_roms.emplace_back(machine_name, "the E00 ADFS ROM, second slot", "ADFS-E00_2.rom", 16*1024, 0x8d17de0e); | ||||||
| 			} | 			} | ||||||
| 			const size_t dfs_rom_position = rom_names.size(); | 			const size_t dfs_rom_position = required_roms.size(); | ||||||
| 			if(target.has_dfs) { | 			if(target.has_dfs) { | ||||||
| 				rom_names.push_back("DFS-1770-2.20.rom"); | 				required_roms.emplace_back(machine_name, "the 1770 DFS ROM", "DFS-1770-2.20.rom", 16*1024, 0xf3dc9bc5); | ||||||
| 			} | 			} | ||||||
| 			const auto roms = rom_fetcher("Electron", rom_names); | 			const auto roms = rom_fetcher(required_roms); | ||||||
|  |  | ||||||
| 			for(const auto &rom: roms) { | 			for(const auto &rom: roms) { | ||||||
| 				if(!rom) { | 				if(!rom) { | ||||||
| @@ -505,7 +510,7 @@ class ConcreteMachine: | |||||||
| 		// MARK: - Work deferral updates. | 		// MARK: - Work deferral updates. | ||||||
| 		inline void update_display() { | 		inline void update_display() { | ||||||
| 			if(cycles_since_display_update_ > 0) { | 			if(cycles_since_display_update_ > 0) { | ||||||
| 				video_output_.run_for(cycles_since_display_update_.flush()); | 				video_output_.run_for(cycles_since_display_update_.flush<Cycles>()); | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
|   | |||||||
| @@ -34,7 +34,7 @@ enum Key: uint16_t { | |||||||
| }; | }; | ||||||
|  |  | ||||||
| struct KeyboardMapper: public KeyboardMachine::MappedMachine::KeyboardMapper { | struct KeyboardMapper: public KeyboardMachine::MappedMachine::KeyboardMapper { | ||||||
| 	uint16_t mapped_key_for_key(Inputs::Keyboard::Key key); | 	uint16_t mapped_key_for_key(Inputs::Keyboard::Key key) override; | ||||||
| }; | }; | ||||||
|  |  | ||||||
| struct CharacterMapper: public ::Utility::CharacterMapper { | struct CharacterMapper: public ::Utility::CharacterMapper { | ||||||
|   | |||||||
| @@ -45,6 +45,7 @@ | |||||||
|  |  | ||||||
| #include "../../Configurable/StandardOptions.hpp" | #include "../../Configurable/StandardOptions.hpp" | ||||||
| #include "../../ClockReceiver/ForceInline.hpp" | #include "../../ClockReceiver/ForceInline.hpp" | ||||||
|  | #include "../../ClockReceiver/JustInTime.hpp" | ||||||
|  |  | ||||||
| #include "../../Analyser/Static/MSX/Target.hpp" | #include "../../Analyser/Static/MSX/Target.hpp" | ||||||
|  |  | ||||||
| @@ -173,33 +174,37 @@ class ConcreteMachine: | |||||||
| 			mixer_.set_relative_volumes({0.5f, 0.1f, 0.4f}); | 			mixer_.set_relative_volumes({0.5f, 0.1f, 0.4f}); | ||||||
|  |  | ||||||
| 			// Install the proper TV standard and select an ideal BIOS name. | 			// Install the proper TV standard and select an ideal BIOS name. | ||||||
| 			std::vector<std::string> rom_names = {"msx.rom"}; | 			const std::string machine_name = "MSX"; | ||||||
|  | 			std::vector<ROMMachine::ROM> required_roms = { | ||||||
|  | 				{machine_name, "any MSX BIOS", "msx.rom", 32*1024, 0x94ee12f3} | ||||||
|  | 			}; | ||||||
|  |  | ||||||
| 			bool is_ntsc = true; | 			bool is_ntsc = true; | ||||||
| 			uint8_t character_generator = 1;	/* 0 = Japan, 1 = USA, etc, 2 = USSR */ | 			uint8_t character_generator = 1;	/* 0 = Japan, 1 = USA, etc, 2 = USSR */ | ||||||
| 			uint8_t date_format = 1;			/* 0 = Y/M/D, 1 = M/D/Y, 2 = D/M/Y */ | 			uint8_t date_format = 1;			/* 0 = Y/M/D, 1 = M/D/Y, 2 = D/M/Y */ | ||||||
| 			uint8_t keyboard = 1;				/* 0 = Japan, 1 = USA, 2 = France, 3 = UK, 4 = Germany, 5 = USSR, 6 = Spain */ | 			uint8_t keyboard = 1;				/* 0 = Japan, 1 = USA, 2 = France, 3 = UK, 4 = Germany, 5 = USSR, 6 = Spain */ | ||||||
|  |  | ||||||
|  | 			// TODO: CRCs below are incomplete, at best. | ||||||
| 			switch(target.region) { | 			switch(target.region) { | ||||||
| 				case Target::Region::Japan: | 				case Target::Region::Japan: | ||||||
| 					rom_names.push_back("msx-japanese.rom"); | 					required_roms.emplace_back(machine_name, "a Japanese MSX BIOS", "msx-japanese.rom", 32*1024, 0xee229390); | ||||||
| 					vdp_.set_tv_standard(TI::TMS::TVStandard::NTSC); | 					vdp_->set_tv_standard(TI::TMS::TVStandard::NTSC); | ||||||
|  |  | ||||||
| 					is_ntsc = true; | 					is_ntsc = true; | ||||||
| 					character_generator = 0; | 					character_generator = 0; | ||||||
| 					date_format = 0; | 					date_format = 0; | ||||||
| 				break; | 				break; | ||||||
| 				case Target::Region::USA: | 				case Target::Region::USA: | ||||||
| 					rom_names.push_back("msx-american.rom"); | 					required_roms.emplace_back(machine_name, "an American MSX BIOS", "msx-american.rom", 32*1024, 0); | ||||||
| 					vdp_.set_tv_standard(TI::TMS::TVStandard::NTSC); | 					vdp_->set_tv_standard(TI::TMS::TVStandard::NTSC); | ||||||
|  |  | ||||||
| 					is_ntsc = true; | 					is_ntsc = true; | ||||||
| 					character_generator = 1; | 					character_generator = 1; | ||||||
| 					date_format = 1; | 					date_format = 1; | ||||||
| 				break; | 				break; | ||||||
| 				case Target::Region::Europe: | 				case Target::Region::Europe: | ||||||
| 					rom_names.push_back("msx-european.rom"); | 					required_roms.emplace_back(machine_name, "a European MSX BIOS", "msx-european.rom", 32*1024, 0); | ||||||
| 					vdp_.set_tv_standard(TI::TMS::TVStandard::PAL); | 					vdp_->set_tv_standard(TI::TMS::TVStandard::PAL); | ||||||
|  |  | ||||||
| 					is_ntsc = false; | 					is_ntsc = false; | ||||||
| 					character_generator = 1; | 					character_generator = 1; | ||||||
| @@ -209,10 +214,12 @@ class ConcreteMachine: | |||||||
|  |  | ||||||
| 			// Fetch the necessary ROMs; try the region-specific ROM first, | 			// Fetch the necessary ROMs; try the region-specific ROM first, | ||||||
| 			// but failing that fall back on patching the main one. | 			// but failing that fall back on patching the main one. | ||||||
|  | 			size_t disk_index = 0; | ||||||
| 			if(target.has_disk_drive) { | 			if(target.has_disk_drive) { | ||||||
| 				rom_names.push_back("disk.rom"); | 				disk_index = required_roms.size(); | ||||||
|  | 				required_roms.emplace_back(machine_name, "the MSX-DOS ROM", "disk.rom", 16*1024, 0x721f61df); | ||||||
| 			} | 			} | ||||||
| 			const auto roms = rom_fetcher("MSX", rom_names); | 			const auto roms = rom_fetcher(required_roms); | ||||||
|  |  | ||||||
| 			if((!roms[0] && !roms[1]) || (target.has_disk_drive && !roms[2])) { | 			if((!roms[0] && !roms[1]) || (target.has_disk_drive && !roms[2])) { | ||||||
| 				throw ROMMachine::Error::MissingROMs; | 				throw ROMMachine::Error::MissingROMs; | ||||||
| @@ -227,7 +234,6 @@ class ConcreteMachine: | |||||||
| 				memory_slots_[0].source = std::move(*roms[0]); | 				memory_slots_[0].source = std::move(*roms[0]); | ||||||
| 				memory_slots_[0].source.resize(32768); | 				memory_slots_[0].source.resize(32768); | ||||||
|  |  | ||||||
|  |  | ||||||
| 				memory_slots_[0].source[0x2b] = uint8_t( | 				memory_slots_[0].source[0x2b] = uint8_t( | ||||||
| 					(is_ntsc ? 0x00 : 0x80) | | 					(is_ntsc ? 0x00 : 0x80) | | ||||||
| 					(date_format << 4) | | 					(date_format << 4) | | ||||||
| @@ -252,7 +258,7 @@ class ConcreteMachine: | |||||||
| 			// Add a disk cartridge if any disks were supplied. | 			// Add a disk cartridge if any disks were supplied. | ||||||
| 			if(target.has_disk_drive) { | 			if(target.has_disk_drive) { | ||||||
| 				memory_slots_[2].set_handler(new DiskROM(memory_slots_[2].source)); | 				memory_slots_[2].set_handler(new DiskROM(memory_slots_[2].source)); | ||||||
| 				memory_slots_[2].source = std::move(*roms[1]); | 				memory_slots_[2].source = std::move(*roms[disk_index]); | ||||||
| 				memory_slots_[2].source.resize(16384); | 				memory_slots_[2].source.resize(16384); | ||||||
|  |  | ||||||
| 				map(2, 0, 0x4000, 0x2000); | 				map(2, 0, 0x4000, 0x2000); | ||||||
| @@ -273,11 +279,11 @@ class ConcreteMachine: | |||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		void set_scan_target(Outputs::Display::ScanTarget *scan_target) override { | 		void set_scan_target(Outputs::Display::ScanTarget *scan_target) override { | ||||||
| 			vdp_.set_scan_target(scan_target); | 			vdp_->set_scan_target(scan_target); | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		void set_display_type(Outputs::Display::DisplayType display_type) override { | 		void set_display_type(Outputs::Display::DisplayType display_type) override { | ||||||
| 			vdp_.set_display_type(display_type); | 			vdp_->set_display_type(display_type); | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		Outputs::Speaker::Speaker *get_speaker() override { | 		Outputs::Speaker::Speaker *get_speaker() override { | ||||||
| @@ -406,7 +412,7 @@ class ConcreteMachine: | |||||||
| 			// but otherwise runs without pause. | 			// but otherwise runs without pause. | ||||||
| 			const HalfCycles addition((cycle.operation == CPU::Z80::PartialMachineCycle::ReadOpcode) ? 2 : 0); | 			const HalfCycles addition((cycle.operation == CPU::Z80::PartialMachineCycle::ReadOpcode) ? 2 : 0); | ||||||
| 			const HalfCycles total_length = addition + cycle.length; | 			const HalfCycles total_length = addition + cycle.length; | ||||||
| 			time_since_vdp_update_ += total_length; | 			vdp_ += total_length; | ||||||
| 			time_since_ay_update_ += total_length; | 			time_since_ay_update_ += total_length; | ||||||
| 			memory_slots_[0].cycles_since_update += total_length; | 			memory_slots_[0].cycles_since_update += total_length; | ||||||
| 			memory_slots_[1].cycles_since_update += total_length; | 			memory_slots_[1].cycles_since_update += total_length; | ||||||
| @@ -484,7 +490,7 @@ class ConcreteMachine: | |||||||
| 							*cycle.value = read_pointers_[address >> 13][address & 8191]; | 							*cycle.value = read_pointers_[address >> 13][address & 8191]; | ||||||
| 						} else { | 						} else { | ||||||
| 							int slot_hit = (paged_memory_ >> ((address >> 14) * 2)) & 3; | 							int slot_hit = (paged_memory_ >> ((address >> 14) * 2)) & 3; | ||||||
| 							memory_slots_[slot_hit].handler->run_for(memory_slots_[slot_hit].cycles_since_update.flush()); | 							memory_slots_[slot_hit].handler->run_for(memory_slots_[slot_hit].cycles_since_update.flush<HalfCycles>()); | ||||||
| 							*cycle.value = memory_slots_[slot_hit].handler->read(address); | 							*cycle.value = memory_slots_[slot_hit].handler->read(address); | ||||||
| 						} | 						} | ||||||
| 					break; | 					break; | ||||||
| @@ -495,7 +501,7 @@ class ConcreteMachine: | |||||||
| 						int slot_hit = (paged_memory_ >> ((address >> 14) * 2)) & 3; | 						int slot_hit = (paged_memory_ >> ((address >> 14) * 2)) & 3; | ||||||
| 						if(memory_slots_[slot_hit].handler) { | 						if(memory_slots_[slot_hit].handler) { | ||||||
| 							update_audio(); | 							update_audio(); | ||||||
| 							memory_slots_[slot_hit].handler->run_for(memory_slots_[slot_hit].cycles_since_update.flush()); | 							memory_slots_[slot_hit].handler->run_for(memory_slots_[slot_hit].cycles_since_update.flush<HalfCycles>()); | ||||||
| 							memory_slots_[slot_hit].handler->write(address, *cycle.value, read_pointers_[pc_address_ >> 13] != memory_slots_[0].read_pointers[pc_address_ >> 13]); | 							memory_slots_[slot_hit].handler->write(address, *cycle.value, read_pointers_[pc_address_ >> 13] != memory_slots_[0].read_pointers[pc_address_ >> 13]); | ||||||
| 						} | 						} | ||||||
| 					} break; | 					} break; | ||||||
| @@ -503,10 +509,9 @@ class ConcreteMachine: | |||||||
| 					case CPU::Z80::PartialMachineCycle::Input: | 					case CPU::Z80::PartialMachineCycle::Input: | ||||||
| 						switch(address & 0xff) { | 						switch(address & 0xff) { | ||||||
| 							case 0x98:	case 0x99: | 							case 0x98:	case 0x99: | ||||||
| 								vdp_.run_for(time_since_vdp_update_.flush()); | 								*cycle.value = vdp_->get_register(address); | ||||||
| 								*cycle.value = vdp_.get_register(address); | 								z80_.set_interrupt_line(vdp_->get_interrupt_line()); | ||||||
| 								z80_.set_interrupt_line(vdp_.get_interrupt_line()); | 								time_until_interrupt_ = vdp_->get_time_until_interrupt(); | ||||||
| 								time_until_interrupt_ = vdp_.get_time_until_interrupt(); |  | ||||||
| 							break; | 							break; | ||||||
|  |  | ||||||
| 							case 0xa2: | 							case 0xa2: | ||||||
| @@ -531,10 +536,9 @@ class ConcreteMachine: | |||||||
| 						const int port = address & 0xff; | 						const int port = address & 0xff; | ||||||
| 						switch(port) { | 						switch(port) { | ||||||
| 							case 0x98:	case 0x99: | 							case 0x98:	case 0x99: | ||||||
| 								vdp_.run_for(time_since_vdp_update_.flush()); | 								vdp_->set_register(address, *cycle.value); | ||||||
| 								vdp_.set_register(address, *cycle.value); | 								z80_.set_interrupt_line(vdp_->get_interrupt_line()); | ||||||
| 								z80_.set_interrupt_line(vdp_.get_interrupt_line()); | 								time_until_interrupt_ = vdp_->get_time_until_interrupt(); | ||||||
| 								time_until_interrupt_ = vdp_.get_time_until_interrupt(); |  | ||||||
| 							break; | 							break; | ||||||
|  |  | ||||||
| 							case 0xa0:	case 0xa1: | 							case 0xa0:	case 0xa1: | ||||||
| @@ -607,7 +611,7 @@ class ConcreteMachine: | |||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		void flush() { | 		void flush() { | ||||||
| 			vdp_.run_for(time_since_vdp_update_.flush()); | 			vdp_.flush(); | ||||||
| 			update_audio(); | 			update_audio(); | ||||||
| 			audio_queue_.perform(); | 			audio_queue_.perform(); | ||||||
| 		} | 		} | ||||||
| @@ -748,7 +752,7 @@ class ConcreteMachine: | |||||||
| 		}; | 		}; | ||||||
|  |  | ||||||
| 		CPU::Z80::Processor<ConcreteMachine, false, false> z80_; | 		CPU::Z80::Processor<ConcreteMachine, false, false> z80_; | ||||||
| 		TI::TMS::TMS9918 vdp_; | 		JustInTimeActor<TI::TMS::TMS9918, HalfCycles> vdp_; | ||||||
| 		Intel::i8255::i8255<i8255PortHandler> i8255_; | 		Intel::i8255::i8255<i8255PortHandler> i8255_; | ||||||
|  |  | ||||||
| 		Concurrency::DeferringAsyncTaskQueue audio_queue_; | 		Concurrency::DeferringAsyncTaskQueue audio_queue_; | ||||||
| @@ -792,7 +796,6 @@ class ConcreteMachine: | |||||||
| 		uint8_t scratch_[8192]; | 		uint8_t scratch_[8192]; | ||||||
| 		uint8_t unpopulated_[8192]; | 		uint8_t unpopulated_[8192]; | ||||||
|  |  | ||||||
| 		HalfCycles time_since_vdp_update_; |  | ||||||
| 		HalfCycles time_since_ay_update_; | 		HalfCycles time_since_ay_update_; | ||||||
| 		HalfCycles time_until_interrupt_; | 		HalfCycles time_until_interrupt_; | ||||||
|  |  | ||||||
|   | |||||||
| @@ -18,6 +18,7 @@ | |||||||
| #include "../KeyboardMachine.hpp" | #include "../KeyboardMachine.hpp" | ||||||
|  |  | ||||||
| #include "../../ClockReceiver/ForceInline.hpp" | #include "../../ClockReceiver/ForceInline.hpp" | ||||||
|  | #include "../../ClockReceiver/JustInTime.hpp" | ||||||
|  |  | ||||||
| #include "../../Outputs/Speaker/Implementation/LowpassSpeaker.hpp" | #include "../../Outputs/Speaker/Implementation/LowpassSpeaker.hpp" | ||||||
| #include "../../Outputs/Log.hpp" | #include "../../Outputs/Log.hpp" | ||||||
| @@ -132,7 +133,12 @@ class ConcreteMachine: | |||||||
|  |  | ||||||
| 			// Load the BIOS if relevant. | 			// Load the BIOS if relevant. | ||||||
| 			if(has_bios()) { | 			if(has_bios()) { | ||||||
| 				const auto roms = rom_fetcher("MasterSystem", {"bios.sms"}); | 				// TODO: there's probably a million other versions of the Master System BIOS; try to build a | ||||||
|  | 				// CRC32 catalogue of those. So far: | ||||||
|  | 				// | ||||||
|  | 				//	0072ed54 = US/European BIOS 1.3 | ||||||
|  | 				//	48d44a13 = Japanese BIOS 2.1 | ||||||
|  | 				const auto roms = rom_fetcher({ {"MasterSystem", "the Master System BIOS", "bios.sms", 8*1024, { 0x0072ed54, 0x48d44a13 } } }); | ||||||
| 				if(!roms[0]) { | 				if(!roms[0]) { | ||||||
| 					// No BIOS found; attempt to boot as though it has already disabled itself. | 					// No BIOS found; attempt to boot as though it has already disabled itself. | ||||||
| 					memory_control_ |= 0x08; | 					memory_control_ |= 0x08; | ||||||
| @@ -163,16 +169,16 @@ class ConcreteMachine: | |||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		void set_scan_target(Outputs::Display::ScanTarget *scan_target) override { | 		void set_scan_target(Outputs::Display::ScanTarget *scan_target) override { | ||||||
| 			vdp_.set_tv_standard( | 			vdp_->set_tv_standard( | ||||||
| 				(region_ == Target::Region::Europe) ? | 				(region_ == Target::Region::Europe) ? | ||||||
| 					TI::TMS::TVStandard::PAL : TI::TMS::TVStandard::NTSC); | 					TI::TMS::TVStandard::PAL : TI::TMS::TVStandard::NTSC); | ||||||
| 			time_until_debounce_ = vdp_.get_time_until_line(-1); | 			time_until_debounce_ = vdp_->get_time_until_line(-1); | ||||||
|  |  | ||||||
| 			vdp_.set_scan_target(scan_target); | 			vdp_->set_scan_target(scan_target); | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		void set_display_type(Outputs::Display::DisplayType display_type) override { | 		void set_display_type(Outputs::Display::DisplayType display_type) override { | ||||||
| 			vdp_.set_display_type(display_type); | 			vdp_->set_display_type(display_type); | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		Outputs::Speaker::Speaker *get_speaker() override { | 		Outputs::Speaker::Speaker *get_speaker() override { | ||||||
| @@ -184,7 +190,7 @@ class ConcreteMachine: | |||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		forceinline HalfCycles perform_machine_cycle(const CPU::Z80::PartialMachineCycle &cycle) { | 		forceinline HalfCycles perform_machine_cycle(const CPU::Z80::PartialMachineCycle &cycle) { | ||||||
| 			time_since_vdp_update_ += cycle.length; | 			vdp_ += cycle.length; | ||||||
| 			time_since_sn76489_update_ += cycle.length; | 			time_since_sn76489_update_ += cycle.length; | ||||||
|  |  | ||||||
| 			if(cycle.is_terminal()) { | 			if(cycle.is_terminal()) { | ||||||
| @@ -228,17 +234,15 @@ class ConcreteMachine: | |||||||
| 								*cycle.value = 0xff; | 								*cycle.value = 0xff; | ||||||
| 							break; | 							break; | ||||||
| 							case 0x40: | 							case 0x40: | ||||||
| 								update_video(); | 								*cycle.value = vdp_->get_current_line(); | ||||||
| 								*cycle.value = vdp_.get_current_line(); |  | ||||||
| 							break; | 							break; | ||||||
| 							case 0x41: | 							case 0x41: | ||||||
| 								*cycle.value = vdp_.get_latched_horizontal_counter(); | 								*cycle.value = vdp_.last_valid()->get_latched_horizontal_counter(); | ||||||
| 							break; | 							break; | ||||||
| 							case 0x80: case 0x81: | 							case 0x80: case 0x81: | ||||||
| 								update_video(); | 								*cycle.value = vdp_->get_register(address); | ||||||
| 								*cycle.value = vdp_.get_register(address); | 								z80_.set_interrupt_line(vdp_->get_interrupt_line()); | ||||||
| 								z80_.set_interrupt_line(vdp_.get_interrupt_line()); | 								time_until_interrupt_ = vdp_->get_time_until_interrupt(); | ||||||
| 								time_until_interrupt_ = vdp_.get_time_until_interrupt(); |  | ||||||
| 							break; | 							break; | ||||||
| 							case 0xc0: { | 							case 0xc0: { | ||||||
| 								Joystick *const joypad1 = static_cast<Joystick *>(joysticks_[0].get()); | 								Joystick *const joypad1 = static_cast<Joystick *>(joysticks_[0].get()); | ||||||
| @@ -279,8 +283,7 @@ class ConcreteMachine: | |||||||
|  |  | ||||||
| 								// Latch if either TH has newly gone to 1. | 								// Latch if either TH has newly gone to 1. | ||||||
| 								if((new_ths^previous_ths)&new_ths) { | 								if((new_ths^previous_ths)&new_ths) { | ||||||
| 									update_video(); | 									vdp_->latch_horizontal_counter(); | ||||||
| 									vdp_.latch_horizontal_counter(); |  | ||||||
| 								} | 								} | ||||||
| 							} break; | 							} break; | ||||||
| 							case 0x40: case 0x41: | 							case 0x40: case 0x41: | ||||||
| @@ -288,10 +291,9 @@ class ConcreteMachine: | |||||||
| 								sn76489_.set_register(*cycle.value); | 								sn76489_.set_register(*cycle.value); | ||||||
| 							break; | 							break; | ||||||
| 							case 0x80: case 0x81: | 							case 0x80: case 0x81: | ||||||
| 								update_video(); | 								vdp_->set_register(address, *cycle.value); | ||||||
| 								vdp_.set_register(address, *cycle.value); | 								z80_.set_interrupt_line(vdp_->get_interrupt_line()); | ||||||
| 								z80_.set_interrupt_line(vdp_.get_interrupt_line()); | 								time_until_interrupt_ = vdp_->get_time_until_interrupt(); | ||||||
| 								time_until_interrupt_ = vdp_.get_time_until_interrupt(); |  | ||||||
| 							break; | 							break; | ||||||
| 							case 0xc0: | 							case 0xc0: | ||||||
| 								LOG("TODO: [output] I/O port A/N; " << int(*cycle.value)); | 								LOG("TODO: [output] I/O port A/N; " << int(*cycle.value)); | ||||||
| @@ -326,15 +328,14 @@ class ConcreteMachine: | |||||||
| 			time_until_debounce_ -= cycle.length; | 			time_until_debounce_ -= cycle.length; | ||||||
| 			if(time_until_debounce_ <= HalfCycles(0)) { | 			if(time_until_debounce_ <= HalfCycles(0)) { | ||||||
| 				z80_.set_non_maskable_interrupt_line(pause_is_pressed_); | 				z80_.set_non_maskable_interrupt_line(pause_is_pressed_); | ||||||
| 				update_video(); | 				time_until_debounce_ = vdp_->get_time_until_line(-1); | ||||||
| 				time_until_debounce_ = vdp_.get_time_until_line(-1); |  | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
| 			return HalfCycles(0); | 			return HalfCycles(0); | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		void flush() { | 		void flush() { | ||||||
| 			update_video(); | 			vdp_.flush(); | ||||||
| 			update_audio(); | 			update_audio(); | ||||||
| 			audio_queue_.perform(); | 			audio_queue_.perform(); | ||||||
| 		} | 		} | ||||||
| @@ -407,16 +408,13 @@ class ConcreteMachine: | |||||||
| 		inline void update_audio() { | 		inline void update_audio() { | ||||||
| 			speaker_.run_for(audio_queue_, time_since_sn76489_update_.divide_cycles(Cycles(sn76489_divider))); | 			speaker_.run_for(audio_queue_, time_since_sn76489_update_.divide_cycles(Cycles(sn76489_divider))); | ||||||
| 		} | 		} | ||||||
| 		inline void update_video() { |  | ||||||
| 			vdp_.run_for(time_since_vdp_update_.flush()); |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		using Target = Analyser::Static::Sega::Target; | 		using Target = Analyser::Static::Sega::Target; | ||||||
| 		Target::Model model_; | 		Target::Model model_; | ||||||
| 		Target::Region region_; | 		Target::Region region_; | ||||||
| 		Target::PagingScheme paging_scheme_; | 		Target::PagingScheme paging_scheme_; | ||||||
| 		CPU::Z80::Processor<ConcreteMachine, false, false> z80_; | 		CPU::Z80::Processor<ConcreteMachine, false, false> z80_; | ||||||
| 		TI::TMS::TMS9918 vdp_; | 		JustInTimeActor<TI::TMS::TMS9918, HalfCycles> vdp_; | ||||||
|  |  | ||||||
| 		Concurrency::DeferringAsyncTaskQueue audio_queue_; | 		Concurrency::DeferringAsyncTaskQueue audio_queue_; | ||||||
| 		TI::SN76489 sn76489_; | 		TI::SN76489 sn76489_; | ||||||
| @@ -426,7 +424,6 @@ class ConcreteMachine: | |||||||
| 		Inputs::Keyboard keyboard_; | 		Inputs::Keyboard keyboard_; | ||||||
| 		bool reset_is_pressed_ = false, pause_is_pressed_ = false; | 		bool reset_is_pressed_ = false, pause_is_pressed_ = false; | ||||||
|  |  | ||||||
| 		HalfCycles time_since_vdp_update_; |  | ||||||
| 		HalfCycles time_since_sn76489_update_; | 		HalfCycles time_since_sn76489_update_; | ||||||
| 		HalfCycles time_until_interrupt_; | 		HalfCycles time_until_interrupt_; | ||||||
| 		HalfCycles time_until_debounce_; | 		HalfCycles time_until_debounce_; | ||||||
|   | |||||||
							
								
								
									
										23
									
								
								Machines/MouseMachine.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								Machines/MouseMachine.hpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,23 @@ | |||||||
|  | // | ||||||
|  | //  MouseMachine.hpp | ||||||
|  | //  Clock Signal | ||||||
|  | // | ||||||
|  | //  Created by Thomas Harte on 11/06/2019. | ||||||
|  | //  Copyright © 2019 Thomas Harte. All rights reserved. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | #ifndef MouseMachine_hpp | ||||||
|  | #define MouseMachine_hpp | ||||||
|  |  | ||||||
|  | #include "../Inputs/Mouse.hpp" | ||||||
|  |  | ||||||
|  | namespace MouseMachine { | ||||||
|  |  | ||||||
|  | class Machine { | ||||||
|  | 	public: | ||||||
|  | 		virtual Inputs::Mouse &get_mouse() = 0; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #endif /* MouseMachine_hpp */ | ||||||
| @@ -170,7 +170,7 @@ class VIAPortHandler: public MOS::MOS6522::IRQDelegatePortHandler { | |||||||
| 		/*! | 		/*! | ||||||
| 			Advances time. This class manages the AY's concept of time to permit updating-on-demand. | 			Advances time. This class manages the AY's concept of time to permit updating-on-demand. | ||||||
| 		*/ | 		*/ | ||||||
| 		inline void run_for(const Cycles cycles) { | 		inline void run_for(const HalfCycles cycles) { | ||||||
| 			cycles_since_ay_update_ += cycles; | 			cycles_since_ay_update_ += cycles; | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| @@ -182,11 +182,11 @@ class VIAPortHandler: public MOS::MOS6522::IRQDelegatePortHandler { | |||||||
|  |  | ||||||
| 	private: | 	private: | ||||||
| 		void update_ay() { | 		void update_ay() { | ||||||
| 			speaker_.run_for(audio_queue_, cycles_since_ay_update_.flush()); | 			speaker_.run_for(audio_queue_, cycles_since_ay_update_.flush<Cycles>()); | ||||||
| 		} | 		} | ||||||
| 		bool ay_bdir_ = false; | 		bool ay_bdir_ = false; | ||||||
| 		bool ay_bc1_ = false; | 		bool ay_bc1_ = false; | ||||||
| 		Cycles cycles_since_ay_update_; | 		HalfCycles cycles_since_ay_update_; | ||||||
|  |  | ||||||
| 		Concurrency::DeferringAsyncTaskQueue &audio_queue_; | 		Concurrency::DeferringAsyncTaskQueue &audio_queue_; | ||||||
| 		GI::AY38910::AY38910 &ay8910_; | 		GI::AY38910::AY38910 &ay8910_; | ||||||
| @@ -228,19 +228,34 @@ template <Analyser::Static::Oric::Target::DiskInterface disk_interface> class Co | |||||||
| 				diskii_.set_clocking_hint_observer(this); | 				diskii_.set_clocking_hint_observer(this); | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
| 			std::vector<std::string> rom_names = {"colour.rom"}; | 			const std::string machine_name = "Oric"; | ||||||
|  | 			std::vector<ROMMachine::ROM> rom_names = { {machine_name, "the Oric colour ROM", "colour.rom", 128, 0xd50fca65} }; | ||||||
| 			switch(target.rom) { | 			switch(target.rom) { | ||||||
| 				case Analyser::Static::Oric::Target::ROM::BASIC10: rom_names.push_back("basic10.rom");	break; | 				case Analyser::Static::Oric::Target::ROM::BASIC10: | ||||||
| 				case Analyser::Static::Oric::Target::ROM::BASIC11: rom_names.push_back("basic11.rom");	break; | 					rom_names.emplace_back(machine_name, "Oric BASIC 1.0", "basic10.rom", 16*1024, 0xf18710b4); | ||||||
| 				case Analyser::Static::Oric::Target::ROM::Pravetz: rom_names.push_back("pravetz.rom");	break; | 				break; | ||||||
|  | 				case Analyser::Static::Oric::Target::ROM::BASIC11: | ||||||
|  | 					rom_names.emplace_back(machine_name, "Oric BASIC 1.1", "basic11.rom", 16*1024, 0xc3a92bef); | ||||||
|  | 				break; | ||||||
|  | 				case Analyser::Static::Oric::Target::ROM::Pravetz: | ||||||
|  | 					rom_names.emplace_back(machine_name, "Pravetz BASIC", "pravetz.rom", 16*1024, 0x58079502); | ||||||
|  | 				break; | ||||||
| 			} | 			} | ||||||
|  | 			size_t diskii_state_machine_index = 0; | ||||||
| 			switch(disk_interface) { | 			switch(disk_interface) { | ||||||
| 				default: break; | 				default: break; | ||||||
| 				case Analyser::Static::Oric::Target::DiskInterface::Microdisc:	rom_names.push_back("microdisc.rom");	break; | 				case Analyser::Static::Oric::Target::DiskInterface::Microdisc: | ||||||
| 				case Analyser::Static::Oric::Target::DiskInterface::Pravetz:	rom_names.push_back("8dos.rom");		break; | 					rom_names.emplace_back(machine_name, "the ORIC Microdisc ROM", "microdisc.rom", 8*1024, 0xa9664a9c); | ||||||
|  | 				break; | ||||||
|  | 				case Analyser::Static::Oric::Target::DiskInterface::Pravetz: | ||||||
|  | 					rom_names.emplace_back(machine_name, "the 8DOS boot ROM", "8dos.rom", 512, 0x49a74c06); | ||||||
|  | 					// These ROM details are coupled with those in the DiskIICard. | ||||||
|  | 					diskii_state_machine_index = rom_names.size(); | ||||||
|  | 					rom_names.push_back({"DiskII", "the Disk II 16-sector state machine ROM", "state-machine-16.rom", 256, { 0x9796a238, 0xb72a2c70 }}); | ||||||
|  | 				break; | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
| 			const auto roms = rom_fetcher("Oric", rom_names); | 			const auto roms = rom_fetcher(rom_names); | ||||||
|  |  | ||||||
| 			for(std::size_t index = 0; index < roms.size(); ++index) { | 			for(std::size_t index = 0; index < roms.size(); ++index) { | ||||||
| 				if(!roms[index]) { | 				if(!roms[index]) { | ||||||
| @@ -261,11 +276,7 @@ template <Analyser::Static::Oric::Target::DiskInterface disk_interface> class Co | |||||||
| 					pravetz_rom_ = std::move(*roms[2]); | 					pravetz_rom_ = std::move(*roms[2]); | ||||||
| 					pravetz_rom_.resize(512); | 					pravetz_rom_.resize(512); | ||||||
|  |  | ||||||
| 					auto state_machine_rom = rom_fetcher("DiskII", {"state-machine-16.rom"}); | 					diskii_.set_state_machine(*roms[diskii_state_machine_index]); | ||||||
| 					if(!state_machine_rom[0]) { |  | ||||||
| 						throw ROMMachine::Error::MissingROMs; |  | ||||||
| 					} |  | ||||||
| 					diskii_.set_state_machine(*state_machine_rom[0]); |  | ||||||
| 				} break; | 				} break; | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
| @@ -434,7 +445,6 @@ template <Analyser::Static::Oric::Target::DiskInterface disk_interface> class Co | |||||||
| 			} | 			} | ||||||
|  |  | ||||||
| 			via_.run_for(Cycles(1)); | 			via_.run_for(Cycles(1)); | ||||||
| 			via_port_handler_.run_for(Cycles(1)); |  | ||||||
| 			tape_player_.run_for(Cycles(1)); | 			tape_player_.run_for(Cycles(1)); | ||||||
| 			switch(disk_interface) { | 			switch(disk_interface) { | ||||||
| 				default: break; | 				default: break; | ||||||
| @@ -456,7 +466,7 @@ template <Analyser::Static::Oric::Target::DiskInterface disk_interface> class Co | |||||||
|  |  | ||||||
| 		forceinline void flush() { | 		forceinline void flush() { | ||||||
| 			update_video(); | 			update_video(); | ||||||
| 			via_port_handler_.flush(); | 			via_.flush(); | ||||||
| 			flush_diskii(); | 			flush_diskii(); | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| @@ -575,7 +585,7 @@ template <Analyser::Static::Oric::Target::DiskInterface disk_interface> class Co | |||||||
| 		uint8_t ram_[65536]; | 		uint8_t ram_[65536]; | ||||||
| 		Cycles cycles_since_video_update_; | 		Cycles cycles_since_video_update_; | ||||||
| 		inline void update_video() { | 		inline void update_video() { | ||||||
| 			video_output_.run_for(cycles_since_video_update_.flush()); | 			video_output_.run_for(cycles_since_video_update_.flush<Cycles>()); | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		// ROM bookkeeping | 		// ROM bookkeeping | ||||||
| @@ -607,7 +617,7 @@ template <Analyser::Static::Oric::Target::DiskInterface disk_interface> class Co | |||||||
| 		Apple::DiskII diskii_; | 		Apple::DiskII diskii_; | ||||||
| 		Cycles cycles_since_diskii_update_; | 		Cycles cycles_since_diskii_update_; | ||||||
| 		void flush_diskii() { | 		void flush_diskii() { | ||||||
| 			diskii_.run_for(cycles_since_diskii_update_.flush()); | 			diskii_.run_for(cycles_since_diskii_update_.flush<Cycles>()); | ||||||
| 		} | 		} | ||||||
| 		std::vector<uint8_t> pravetz_rom_; | 		std::vector<uint8_t> pravetz_rom_; | ||||||
| 		std::size_t pravetz_rom_base_pointer_ = 0; | 		std::size_t pravetz_rom_base_pointer_ = 0; | ||||||
|   | |||||||
| @@ -16,15 +16,41 @@ | |||||||
|  |  | ||||||
| namespace ROMMachine { | namespace ROMMachine { | ||||||
|  |  | ||||||
|  | /*! | ||||||
|  | 	Describes a ROM image; this term is used in this emulator strictly in the sense of firmware — | ||||||
|  | 	system software that is an inherent part of a machine. | ||||||
|  | */ | ||||||
|  | struct ROM { | ||||||
|  | 	/// The machine with which this ROM is associated, in a form that is safe for using as | ||||||
|  | 	/// part of a file name. | ||||||
|  | 	std::string machine_name; | ||||||
|  | 	/// A descriptive name for this ROM, suitable for use in a bullet-point list, a bracket | ||||||
|  | 	/// clause, etc, e.g. "the Electron MOS 1.0". | ||||||
|  | 	std::string descriptive_name; | ||||||
|  | 	/// An idiomatic file name for this ROM, e.g. "os10.rom". | ||||||
|  | 	std::string file_name; | ||||||
|  | 	/// The expected size of this ROM in bytes, e.g. 32768. | ||||||
|  | 	size_t size = 0; | ||||||
|  | 	/// CRC32s for all known acceptable copies of this ROM; intended to allow a host platform | ||||||
|  | 	/// to test user-provided ROMs of unknown provenance. **Not** intended to be used | ||||||
|  | 	/// to exclude ROMs where the user's intent is otherwise clear. | ||||||
|  | 	std::vector<uint32_t> crc32s; | ||||||
|  |  | ||||||
|  | 	ROM(std::string machine_name, std::string descriptive_name, std::string file_name, size_t size, uint32_t crc32) : | ||||||
|  | 		machine_name(machine_name), descriptive_name(descriptive_name), file_name(file_name), size(size), crc32s({crc32}) {} | ||||||
|  | 	ROM(std::string machine_name, std::string descriptive_name, std::string file_name, size_t size, std::initializer_list<uint32_t> crc32s) : | ||||||
|  | 		machine_name(machine_name), descriptive_name(descriptive_name), file_name(file_name), size(size), crc32s(crc32s) {} | ||||||
|  | }; | ||||||
|  |  | ||||||
| /*! | /*! | ||||||
| 	Defines the signature for a function that must be supplied by the host environment in order to give machines | 	Defines the signature for a function that must be supplied by the host environment in order to give machines | ||||||
| 	a route for fetching any system ROMs they might need. | 	a route for fetching any system ROMs they might need. | ||||||
|  |  | ||||||
| 	The caller will supply the idiomatic name of the machine plus a vector of the names of ROM files that it expects | 	The caller will supply a vector of the names of ROM files that it would like to inspect. The recevier should | ||||||
| 	to be present. The recevier should return a vector of unique_ptrs that either contain the contents of the | 	return a vector of unique_ptrs that either contain the contents of the ROM from @c names that corresponds by | ||||||
| 	ROM from @c names that corresponds by index, or else are the nullptr | 	index, or else are @c nullptr. | ||||||
| */ | */ | ||||||
| typedef std::function<std::vector<std::unique_ptr<std::vector<uint8_t>>>(const std::string &machine, const std::vector<std::string> &names)> ROMFetcher; | typedef std::function<std::vector<std::unique_ptr<std::vector<uint8_t>>>(const std::vector<ROM> &roms)> ROMFetcher; | ||||||
|  |  | ||||||
| enum class Error { | enum class Error { | ||||||
| 	MissingROMs | 	MissingROMs | ||||||
|   | |||||||
| @@ -9,7 +9,8 @@ | |||||||
| #include "MachineForTarget.hpp" | #include "MachineForTarget.hpp" | ||||||
|  |  | ||||||
| #include "../AmstradCPC/AmstradCPC.hpp" | #include "../AmstradCPC/AmstradCPC.hpp" | ||||||
| #include "../AppleII/AppleII.hpp" | #include "../Apple/AppleII/AppleII.hpp" | ||||||
|  | #include "../Apple/Macintosh/Macintosh.hpp" | ||||||
| #include "../Atari2600/Atari2600.hpp" | #include "../Atari2600/Atari2600.hpp" | ||||||
| #include "../ColecoVision/ColecoVision.hpp" | #include "../ColecoVision/ColecoVision.hpp" | ||||||
| #include "../Commodore/Vic-20/Vic20.hpp" | #include "../Commodore/Vic-20/Vic20.hpp" | ||||||
| @@ -33,14 +34,15 @@ namespace { | |||||||
| #define Bind(m)	BindD(m, m) | #define Bind(m)	BindD(m, m) | ||||||
| 		switch(target->machine) { | 		switch(target->machine) { | ||||||
| 			Bind(AmstradCPC) | 			Bind(AmstradCPC) | ||||||
| 			Bind(AppleII) | 			BindD(Apple::II, AppleII) | ||||||
|  | 			BindD(Apple::Macintosh, Macintosh) | ||||||
| 			Bind(Atari2600) | 			Bind(Atari2600) | ||||||
| 			BindD(Coleco::Vision, ColecoVision) | 			BindD(Coleco::Vision, ColecoVision) | ||||||
|  | 			BindD(Commodore::Vic20, Vic20) | ||||||
| 			Bind(Electron) | 			Bind(Electron) | ||||||
| 			BindD(Sega::MasterSystem, MasterSystem) |  | ||||||
| 			Bind(MSX) | 			Bind(MSX) | ||||||
| 			Bind(Oric) | 			Bind(Oric) | ||||||
| 			BindD(Commodore::Vic20, Vic20) | 			BindD(Sega::MasterSystem, MasterSystem) | ||||||
| 			Bind(ZX8081) | 			Bind(ZX8081) | ||||||
|  |  | ||||||
| 			default: | 			default: | ||||||
| @@ -103,6 +105,7 @@ std::string Machine::ShortNameForTargetMachine(const Analyser::Machine machine) | |||||||
| 		case Analyser::Machine::Atari2600:		return "Atari2600"; | 		case Analyser::Machine::Atari2600:		return "Atari2600"; | ||||||
| 		case Analyser::Machine::ColecoVision:	return "ColecoVision"; | 		case Analyser::Machine::ColecoVision:	return "ColecoVision"; | ||||||
| 		case Analyser::Machine::Electron:		return "Electron"; | 		case Analyser::Machine::Electron:		return "Electron"; | ||||||
|  | 		case Analyser::Machine::Macintosh:		return "Macintosh"; | ||||||
| 		case Analyser::Machine::MSX:			return "MSX"; | 		case Analyser::Machine::MSX:			return "MSX"; | ||||||
| 		case Analyser::Machine::Oric:			return "Oric"; | 		case Analyser::Machine::Oric:			return "Oric"; | ||||||
| 		case Analyser::Machine::Vic20:			return "Vic20"; | 		case Analyser::Machine::Vic20:			return "Vic20"; | ||||||
| @@ -119,6 +122,7 @@ std::string Machine::LongNameForTargetMachine(Analyser::Machine machine) { | |||||||
| 		case Analyser::Machine::Atari2600:		return "Atari 2600"; | 		case Analyser::Machine::Atari2600:		return "Atari 2600"; | ||||||
| 		case Analyser::Machine::ColecoVision:	return "ColecoVision"; | 		case Analyser::Machine::ColecoVision:	return "ColecoVision"; | ||||||
| 		case Analyser::Machine::Electron:		return "Acorn Electron"; | 		case Analyser::Machine::Electron:		return "Acorn Electron"; | ||||||
|  | 		case Analyser::Machine::Macintosh:		return "Apple Macintosh"; | ||||||
| 		case Analyser::Machine::MSX:			return "MSX"; | 		case Analyser::Machine::MSX:			return "MSX"; | ||||||
| 		case Analyser::Machine::Oric:			return "Oric"; | 		case Analyser::Machine::Oric:			return "Oric"; | ||||||
| 		case Analyser::Machine::Vic20:			return "Vic 20"; | 		case Analyser::Machine::Vic20:			return "Vic 20"; | ||||||
| @@ -132,7 +136,7 @@ std::map<std::string, std::vector<std::unique_ptr<Configurable::Option>>> Machin | |||||||
| 	std::map<std::string, std::vector<std::unique_ptr<Configurable::Option>>> options; | 	std::map<std::string, std::vector<std::unique_ptr<Configurable::Option>>> options; | ||||||
|  |  | ||||||
| 	options.emplace(std::make_pair(LongNameForTargetMachine(Analyser::Machine::AmstradCPC), AmstradCPC::get_options())); | 	options.emplace(std::make_pair(LongNameForTargetMachine(Analyser::Machine::AmstradCPC), AmstradCPC::get_options())); | ||||||
| 	options.emplace(std::make_pair(LongNameForTargetMachine(Analyser::Machine::AppleII), AppleII::get_options())); | 	options.emplace(std::make_pair(LongNameForTargetMachine(Analyser::Machine::AppleII), Apple::II::get_options())); | ||||||
| 	options.emplace(std::make_pair(LongNameForTargetMachine(Analyser::Machine::ColecoVision), Coleco::Vision::get_options())); | 	options.emplace(std::make_pair(LongNameForTargetMachine(Analyser::Machine::ColecoVision), Coleco::Vision::get_options())); | ||||||
| 	options.emplace(std::make_pair(LongNameForTargetMachine(Analyser::Machine::Electron), Electron::get_options())); | 	options.emplace(std::make_pair(LongNameForTargetMachine(Analyser::Machine::Electron), Electron::get_options())); | ||||||
| 	options.emplace(std::make_pair(LongNameForTargetMachine(Analyser::Machine::MSX), MSX::get_options())); | 	options.emplace(std::make_pair(LongNameForTargetMachine(Analyser::Machine::MSX), MSX::get_options())); | ||||||
|   | |||||||
| @@ -23,6 +23,10 @@ void Memory::Fuzz(uint8_t *buffer, std::size_t size) { | |||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
|  | void Memory::Fuzz(uint16_t *buffer, std::size_t size) { | ||||||
|  | 	Fuzz(reinterpret_cast<uint8_t *>(buffer), size * sizeof(uint16_t)); | ||||||
|  | } | ||||||
|  |  | ||||||
| void Memory::Fuzz(std::vector<uint8_t> &buffer) { | void Memory::Fuzz(std::vector<uint8_t> &buffer) { | ||||||
| 	Fuzz(buffer.data(), buffer.size()); | 	Fuzz(buffer.data(), buffer.size()); | ||||||
| } | } | ||||||
|   | |||||||
| @@ -18,7 +18,10 @@ namespace Memory { | |||||||
| /// Stores @c size random bytes from @c buffer onwards. | /// Stores @c size random bytes from @c buffer onwards. | ||||||
| void Fuzz(uint8_t *buffer, std::size_t size); | void Fuzz(uint8_t *buffer, std::size_t size); | ||||||
|  |  | ||||||
| // Replaces all existing vector contents with random bytes. | /// Stores @c size random 16-bit words from @c buffer onwards. | ||||||
|  | void Fuzz(uint16_t *buffer, std::size_t size); | ||||||
|  |  | ||||||
|  | /// Replaces all existing vector contents with random bytes. | ||||||
| void Fuzz(std::vector<uint8_t> &buffer); | void Fuzz(std::vector<uint8_t> &buffer); | ||||||
|  |  | ||||||
| } | } | ||||||
|   | |||||||
							
								
								
									
										15
									
								
								Machines/Utility/MemoryPacker.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								Machines/Utility/MemoryPacker.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,15 @@ | |||||||
|  | // | ||||||
|  | //  MemoryPacker.cpp | ||||||
|  | //  Clock Signal | ||||||
|  | // | ||||||
|  | //  Created by Thomas Harte on 03/05/2019. | ||||||
|  | //  Copyright © 2019 Thomas Harte. All rights reserved. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | #include "MemoryPacker.hpp" | ||||||
|  |  | ||||||
|  | void Memory::PackBigEndian16(const std::vector<uint8_t> &source, uint16_t *target) { | ||||||
|  | 	for(std::size_t c = 0; c < source.size(); c += 2) { | ||||||
|  | 		target[c >> 1] = uint16_t(source[c] << 8) | uint16_t(source[c+1]); | ||||||
|  | 	} | ||||||
|  | } | ||||||
							
								
								
									
										24
									
								
								Machines/Utility/MemoryPacker.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								Machines/Utility/MemoryPacker.hpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,24 @@ | |||||||
|  | // | ||||||
|  | //  MemoryPacker.hpp | ||||||
|  | //  Clock Signal | ||||||
|  | // | ||||||
|  | //  Created by Thomas Harte on 03/05/2019. | ||||||
|  | //  Copyright © 2019 Thomas Harte. All rights reserved. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | #ifndef MemoryPacker_hpp | ||||||
|  | #define MemoryPacker_hpp | ||||||
|  |  | ||||||
|  | #include <cstdint> | ||||||
|  | #include <vector> | ||||||
|  |  | ||||||
|  | namespace Memory { | ||||||
|  |  | ||||||
|  | /*! | ||||||
|  | 	Copies the bytes from @c source into @c target, interpreting them | ||||||
|  | 	as big-endian 16-bit data. | ||||||
|  | */ | ||||||
|  | void PackBigEndian16(const std::vector<uint8_t> &source, uint16_t *target); | ||||||
|  |  | ||||||
|  | } | ||||||
|  | #endif /* MemoryPacker_hpp */ | ||||||
| @@ -45,6 +45,10 @@ template<typename T> class TypedDynamicMachine: public ::Machine::DynamicMachine | |||||||
| 			return get<KeyboardMachine::Machine>(); | 			return get<KeyboardMachine::Machine>(); | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
|  | 		MouseMachine::Machine *mouse_machine() override { | ||||||
|  | 			return get<MouseMachine::Machine>(); | ||||||
|  | 		} | ||||||
|  |  | ||||||
| 		Configurable::Device *configurable_device() override { | 		Configurable::Device *configurable_device() override { | ||||||
| 			return get<Configurable::Device>(); | 			return get<Configurable::Device>(); | ||||||
| 		} | 		} | ||||||
|   | |||||||
| @@ -76,7 +76,11 @@ template<bool is_zx81> class ConcreteMachine: | |||||||
| 			clear_all_keys(); | 			clear_all_keys(); | ||||||
|  |  | ||||||
| 			const bool use_zx81_rom = target.is_ZX81 || target.ZX80_uses_ZX81_ROM; | 			const bool use_zx81_rom = target.is_ZX81 || target.ZX80_uses_ZX81_ROM; | ||||||
| 			const auto roms = rom_fetcher("ZX8081", { use_zx81_rom ? "zx81.rom" : "zx80.rom" }); | 			const auto roms = | ||||||
|  | 				use_zx81_rom ? | ||||||
|  | 					rom_fetcher({ {"ZX8081", "the ZX81 BASIC ROM", "zx81.rom", 8 * 1024, 0x4b1dd6eb} }) : | ||||||
|  | 					rom_fetcher({ {"ZX8081", "the ZX80 BASIC ROM", "zx80.rom", 4 * 1024, 0x4c7fc597} }); | ||||||
|  |  | ||||||
| 			if(!roms[0]) throw ROMMachine::Error::MissingROMs; | 			if(!roms[0]) throw ROMMachine::Error::MissingROMs; | ||||||
|  |  | ||||||
| 			rom_ = std::move(*roms[0]); | 			rom_ = std::move(*roms[0]); | ||||||
|   | |||||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @@ -1,6 +1,9 @@ | |||||||
| <?xml version="1.0" encoding="UTF-8"?> | <?xml version="1.0" encoding="UTF-8"?> | ||||||
| <Workspace | <Workspace | ||||||
|    version = "1.0"> |    version = "1.0"> | ||||||
|  |    <FileRef | ||||||
|  |       location = "group:/Users/thomasharte/Projects/CLK/OSBindings/Mac/Clock SignalTests/68000ArithmeticTests.mm"> | ||||||
|  |    </FileRef> | ||||||
|    <FileRef |    <FileRef | ||||||
|       location = "self:Clock Signal.xcodeproj"> |       location = "self:Clock Signal.xcodeproj"> | ||||||
|    </FileRef> |    </FileRef> | ||||||
|   | |||||||
| @@ -1,6 +1,6 @@ | |||||||
| <?xml version="1.0" encoding="UTF-8"?> | <?xml version="1.0" encoding="UTF-8"?> | ||||||
| <Scheme | <Scheme | ||||||
|    LastUpgradeVersion = "0930" |    LastUpgradeVersion = "1030" | ||||||
|    version = "1.3"> |    version = "1.3"> | ||||||
|    <BuildAction |    <BuildAction | ||||||
|       parallelizeBuildables = "YES" |       parallelizeBuildables = "YES" | ||||||
| @@ -26,6 +26,7 @@ | |||||||
|       buildConfiguration = "Debug" |       buildConfiguration = "Debug" | ||||||
|       selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" |       selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" | ||||||
|       selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" |       selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" | ||||||
|  |       disableMainThreadChecker = "YES" | ||||||
|       codeCoverageEnabled = "YES" |       codeCoverageEnabled = "YES" | ||||||
|       shouldUseLaunchSchemeArgsEnv = "YES"> |       shouldUseLaunchSchemeArgsEnv = "YES"> | ||||||
|       <Testables> |       <Testables> | ||||||
|   | |||||||
| @@ -1,8 +1,8 @@ | |||||||
| <?xml version="1.0" encoding="UTF-8"?> | <?xml version="1.0" encoding="UTF-8"?> | ||||||
| <document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="14113" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct"> | <document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="14490.70" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct"> | ||||||
|     <dependencies> |     <dependencies> | ||||||
|         <deployment identifier="macosx"/> |         <deployment identifier="macosx"/> | ||||||
|         <plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="14113"/> |         <plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="14490.70"/> | ||||||
|         <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/> |         <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/> | ||||||
|     </dependencies> |     </dependencies> | ||||||
|     <objects> |     <objects> | ||||||
| @@ -14,7 +14,7 @@ | |||||||
|         </customObject> |         </customObject> | ||||||
|         <customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/> |         <customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/> | ||||||
|         <customObject id="-3" userLabel="Application" customClass="NSObject"/> |         <customObject id="-3" userLabel="Application" customClass="NSObject"/> | ||||||
|         <window title="Window" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" oneShot="NO" releasedWhenClosed="NO" showsToolbarButton="NO" visibleAtLaunch="NO" animationBehavior="default" id="xOd-HO-29H" userLabel="Window"> |         <window title="Window" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" releasedWhenClosed="NO" visibleAtLaunch="NO" animationBehavior="default" id="xOd-HO-29H" userLabel="Window"> | ||||||
|             <windowStyleMask key="styleMask" titled="YES" closable="YES" miniaturizable="YES" resizable="YES"/> |             <windowStyleMask key="styleMask" titled="YES" closable="YES" miniaturizable="YES" resizable="YES"/> | ||||||
|             <windowCollectionBehavior key="collectionBehavior" fullScreenPrimary="YES"/> |             <windowCollectionBehavior key="collectionBehavior" fullScreenPrimary="YES"/> | ||||||
|             <windowPositionMask key="initialPositionMask" leftStrut="YES" rightStrut="YES" topStrut="YES" bottomStrut="YES"/> |             <windowPositionMask key="initialPositionMask" leftStrut="YES" rightStrut="YES" topStrut="YES" bottomStrut="YES"/> | ||||||
| @@ -25,7 +25,7 @@ | |||||||
|                 <rect key="frame" x="0.0" y="0.0" width="600" height="450"/> |                 <rect key="frame" x="0.0" y="0.0" width="600" height="450"/> | ||||||
|                 <autoresizingMask key="autoresizingMask"/> |                 <autoresizingMask key="autoresizingMask"/> | ||||||
|                 <subviews> |                 <subviews> | ||||||
|                     <openGLView hidden="YES" useAuxiliaryDepthBufferStencil="NO" allowOffline="YES" wantsBestResolutionOpenGLSurface="YES" translatesAutoresizingMaskIntoConstraints="NO" id="DEG-fq-cjd" customClass="CSOpenGLView"> |                     <openGLView hidden="YES" wantsLayer="YES" useAuxiliaryDepthBufferStencil="NO" allowOffline="YES" wantsBestResolutionOpenGLSurface="YES" translatesAutoresizingMaskIntoConstraints="NO" id="DEG-fq-cjd" customClass="CSOpenGLView"> | ||||||
|                         <rect key="frame" x="0.0" y="0.0" width="600" height="450"/> |                         <rect key="frame" x="0.0" y="0.0" width="600" height="450"/> | ||||||
|                     </openGLView> |                     </openGLView> | ||||||
|                 </subviews> |                 </subviews> | ||||||
| @@ -36,6 +36,11 @@ | |||||||
|                     <constraint firstItem="DEG-fq-cjd" firstAttribute="width" secondItem="gIp-Ho-8D9" secondAttribute="width" id="mYS-bH-DST"/> |                     <constraint firstItem="DEG-fq-cjd" firstAttribute="width" secondItem="gIp-Ho-8D9" secondAttribute="width" id="mYS-bH-DST"/> | ||||||
|                 </constraints> |                 </constraints> | ||||||
|             </view> |             </view> | ||||||
|  |             <userDefinedRuntimeAttributes> | ||||||
|  |                 <userDefinedRuntimeAttribute type="color" keyPath="backgroundColor"> | ||||||
|  |                     <color key="value" white="0.0" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/> | ||||||
|  |                 </userDefinedRuntimeAttribute> | ||||||
|  |             </userDefinedRuntimeAttributes> | ||||||
|             <connections> |             <connections> | ||||||
|                 <outlet property="delegate" destination="-2" id="0bl-1N-x8E"/> |                 <outlet property="delegate" destination="-2" id="0bl-1N-x8E"/> | ||||||
|                 <outlet property="initialFirstResponder" destination="DEG-fq-cjd" id="9RI-Kx-QeN"/> |                 <outlet property="initialFirstResponder" destination="DEG-fq-cjd" id="9RI-Kx-QeN"/> | ||||||
|   | |||||||
| @@ -10,10 +10,13 @@ | |||||||
|  |  | ||||||
| #import "CSStaticAnalyser.h" | #import "CSStaticAnalyser.h" | ||||||
|  |  | ||||||
| #import	"CSOpenGLView.h" |  | ||||||
| #import "CSAudioQueue.h" | #import "CSAudioQueue.h" | ||||||
|  | #import	"CSOpenGLView.h" | ||||||
|  | #import "CSROMReceiverView.h" | ||||||
|  |  | ||||||
| #import "CSBestEffortUpdater.h" | #import "CSBestEffortUpdater.h" | ||||||
| #import "CSJoystickManager.h" | #import "CSJoystickManager.h" | ||||||
|  |  | ||||||
|  | #import "NSData+CRC32.h" | ||||||
|  |  | ||||||
| #include "KeyCodes.h" | #include "KeyCodes.h" | ||||||
|   | |||||||
| @@ -16,109 +16,76 @@ class MachineDocument: | |||||||
| 	CSOpenGLViewDelegate, | 	CSOpenGLViewDelegate, | ||||||
| 	CSOpenGLViewResponderDelegate, | 	CSOpenGLViewResponderDelegate, | ||||||
| 	CSBestEffortUpdaterDelegate, | 	CSBestEffortUpdaterDelegate, | ||||||
| 	CSAudioQueueDelegate | 	CSAudioQueueDelegate, | ||||||
|  | 	CSROMReciverViewDelegate | ||||||
| { | { | ||||||
| 	fileprivate let actionLock = NSLock() | 	// MARK: - Mutual Exclusion. | ||||||
| 	fileprivate let drawLock = NSLock() |  | ||||||
| 	fileprivate let bestEffortLock = NSLock() |  | ||||||
|  |  | ||||||
| 	var machine: CSMachine! | 	/// Ensures exclusive access between calls to self.machine.run and close(). | ||||||
| 	var name: String! { | 	private let actionLock = NSLock() | ||||||
| 		get { | 	/// Ensures exclusive access between calls to machine.updateView and machine.drawView, and close(). | ||||||
| 			return nil | 	private let drawLock = NSLock() | ||||||
| 		} | 	/// Ensures exclusive access to the best-effort updater. | ||||||
| 	} | 	private let bestEffortLock = NSLock() | ||||||
| 	var optionsPanelNibName: String? |  | ||||||
|  |  | ||||||
| 	func aspectRatio() -> NSSize { | 	// MARK: - Machine details. | ||||||
|  |  | ||||||
|  | 	/// A description of the machine this document should represent once fully set up. | ||||||
|  | 	private var machineDescription: CSStaticAnalyser? | ||||||
|  |  | ||||||
|  | 	/// The active machine, following its successful creation. | ||||||
|  | 	private var machine: CSMachine! | ||||||
|  |  | ||||||
|  | 	/// @returns the appropriate window content aspect ratio for this @c self.machine. | ||||||
|  | 	private func aspectRatio() -> NSSize { | ||||||
| 		return NSSize(width: 4.0, height: 3.0) | 		return NSSize(width: 4.0, height: 3.0) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | 	/// The output audio queue, if any. | ||||||
|  | 	private var audioQueue: CSAudioQueue! | ||||||
|  |  | ||||||
|  | 	/// The best-effort updater. | ||||||
|  | 	private var bestEffortUpdater: CSBestEffortUpdater? | ||||||
|  |  | ||||||
|  | 	// MARK: - Main NIB connections. | ||||||
|  |  | ||||||
|  | 	/// The OpenGL view to receive this machine's display. | ||||||
| 	@IBOutlet weak var openGLView: CSOpenGLView! | 	@IBOutlet weak var openGLView: CSOpenGLView! | ||||||
|  |  | ||||||
|  | 	/// The options panel, if any. | ||||||
| 	@IBOutlet var optionsPanel: MachinePanel! | 	@IBOutlet var optionsPanel: MachinePanel! | ||||||
|  |  | ||||||
|  | 	/// An action to display the options panel, if there is one. | ||||||
| 	@IBAction func showOptions(_ sender: AnyObject!) { | 	@IBAction func showOptions(_ sender: AnyObject!) { | ||||||
| 		optionsPanel?.setIsVisible(true) | 		optionsPanel?.setIsVisible(true) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | 	/// The activity panel, if one is deemed appropriate. | ||||||
| 	@IBOutlet var activityPanel: NSPanel! | 	@IBOutlet var activityPanel: NSPanel! | ||||||
|  |  | ||||||
|  | 	/// An action to display the activity panel, if there is one. | ||||||
| 	@IBAction func showActivity(_ sender: AnyObject!) { | 	@IBAction func showActivity(_ sender: AnyObject!) { | ||||||
| 		activityPanel.setIsVisible(true) | 		activityPanel.setIsVisible(true) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	fileprivate var audioQueue: CSAudioQueue! = nil | 	// MARK: - NSDocument Overrides and NSWindowDelegate methods. | ||||||
| 	fileprivate var bestEffortUpdater: CSBestEffortUpdater? |  | ||||||
|  |  | ||||||
|  | 	/// Links this class to the MachineDocument NIB. | ||||||
| 	override var windowNibName: NSNib.Name? { | 	override var windowNibName: NSNib.Name? { | ||||||
| 		return NSNib.Name(rawValue: "MachineDocument") | 		return "MachineDocument" | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	override func windowControllerDidLoadNib(_ aController: NSWindowController) { | 	convenience init(type typeName: String) throws { | ||||||
| 		super.windowControllerDidLoadNib(aController) | 		self.init() | ||||||
| 		aController.window?.contentAspectRatio = self.aspectRatio() | 		self.fileType = typeName | ||||||
| 		setupMachineOutput() |  | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	// Attempting to show a sheet before the window is visible (such as when the NIB is loaded) results in | 	override func read(from url: URL, ofType typeName: String) throws { | ||||||
| 	// a sheet mysteriously floating on its own. For now, use windowDidUpdate as a proxy to know that the window | 		if let analyser = CSStaticAnalyser(fileAt: url) { | ||||||
| 	// is visible, though it's a little premature. | 			self.displayName = analyser.displayName | ||||||
| 	func windowDidUpdate(_ notification: Notification) { | 			self.configureAs(analyser) | ||||||
| 		if self.shouldShowNewMachinePanel { | 		} else { | ||||||
| 			self.shouldShowNewMachinePanel = false | 			throw NSError(domain: "MachineDocument", code: -1, userInfo: nil) | ||||||
| 			Bundle.main.loadNibNamed(NSNib.Name(rawValue: "MachinePicker"), owner: self, topLevelObjects: nil) |  | ||||||
| 			self.machinePicker?.establishStoredOptions() |  | ||||||
| 			self.windowControllers[0].window?.beginSheet(self.machinePickerPanel!, completionHandler: nil) |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	fileprivate func setupMachineOutput() { |  | ||||||
| 		if let machine = self.machine, let openGLView = self.openGLView { |  | ||||||
| 			// establish the output aspect ratio and audio |  | ||||||
| 			let aspectRatio = self.aspectRatio() |  | ||||||
| 			openGLView.perform(glContext: { |  | ||||||
| 				machine.setView(openGLView, aspectRatio: Float(aspectRatio.width / aspectRatio.height)) |  | ||||||
| 			}) |  | ||||||
|  |  | ||||||
| 			// attach an options panel if one is available |  | ||||||
| 			if let optionsPanelNibName = self.optionsPanelNibName { |  | ||||||
| 				Bundle.main.loadNibNamed(NSNib.Name(rawValue: optionsPanelNibName), owner: self, topLevelObjects: nil) |  | ||||||
| 				self.optionsPanel.machine = machine |  | ||||||
| 				self.optionsPanel?.establishStoredOptions() |  | ||||||
| 				showOptions(self) |  | ||||||
| 			} |  | ||||||
|  |  | ||||||
| 			machine.delegate = self |  | ||||||
| 			self.bestEffortUpdater = CSBestEffortUpdater() |  | ||||||
|  |  | ||||||
| 			// callbacks from the OpenGL may come on a different thread, immediately following the .delegate set; |  | ||||||
| 			// hence the full setup of the best-effort updater prior to setting self as a delegate |  | ||||||
| 			openGLView.delegate = self |  | ||||||
| 			openGLView.responderDelegate = self |  | ||||||
|  |  | ||||||
| 			setupAudioQueueClockRate() |  | ||||||
|  |  | ||||||
| 			// bring OpenGL view-holding window on top of the options panel and show the content |  | ||||||
| 			openGLView.isHidden = false |  | ||||||
| 			openGLView.window!.makeKeyAndOrderFront(self) |  | ||||||
| 			openGLView.window!.makeFirstResponder(openGLView) |  | ||||||
|  |  | ||||||
| 			// start accepting best effort updates |  | ||||||
| 			self.bestEffortUpdater!.delegate = self |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	func machineSpeakerDidChangeInputClock(_ machine: CSMachine) { |  | ||||||
| 		setupAudioQueueClockRate() |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	fileprivate func setupAudioQueueClockRate() { |  | ||||||
| 		// establish and provide the audio queue, taking advice as to an appropriate sampling rate |  | ||||||
| 		let maximumSamplingRate = CSAudioQueue.preferredSamplingRate() |  | ||||||
| 		let selectedSamplingRate = self.machine.idealSamplingRate(from: NSRange(location: 0, length: NSInteger(maximumSamplingRate))) |  | ||||||
| 		if selectedSamplingRate > 0 { |  | ||||||
| 			audioQueue = CSAudioQueue(samplingRate: Float64(selectedSamplingRate)) |  | ||||||
| 			audioQueue.delegate = self |  | ||||||
| 			self.machine.audioQueue = self.audioQueue |  | ||||||
| 			self.machine.setAudioSamplingRate(selectedSamplingRate, bufferSize:audioQueue.preferredBufferSize) |  | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| @@ -148,56 +115,135 @@ class MachineDocument: | |||||||
| 		super.close() | 		super.close() | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	// MARK: configuring | 	override func data(ofType typeName: String) throws -> Data { | ||||||
|  | 		throw NSError(domain: NSOSStatusErrorDomain, code: unimpErr, userInfo: nil) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	override func windowControllerDidLoadNib(_ aController: NSWindowController) { | ||||||
|  | 		super.windowControllerDidLoadNib(aController) | ||||||
|  | 		aController.window?.contentAspectRatio = self.aspectRatio() | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	private var missingROMs: [CSMissingROM] = [] | ||||||
| 	func configureAs(_ analysis: CSStaticAnalyser) { | 	func configureAs(_ analysis: CSStaticAnalyser) { | ||||||
| 		if let machine = CSMachine(analyser: analysis) { | 		self.machineDescription = analysis | ||||||
|  |  | ||||||
|  | 		let missingROMs = NSMutableArray() | ||||||
|  | 		if let machine = CSMachine(analyser: analysis, missingROMs: missingROMs) { | ||||||
| 			self.machine = machine | 			self.machine = machine | ||||||
| 			self.optionsPanelNibName = analysis.optionsPanelNibName |  | ||||||
| 			setupMachineOutput() | 			setupMachineOutput() | ||||||
| 			setupActivityDisplay() | 			setupActivityDisplay() | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	fileprivate var shouldShowNewMachinePanel = false |  | ||||||
| 	override func read(from url: URL, ofType typeName: String) throws { |  | ||||||
| 		if let analyser = CSStaticAnalyser(fileAt: url) { |  | ||||||
| 			self.displayName = analyser.displayName |  | ||||||
| 			self.configureAs(analyser) |  | ||||||
| 		} else { | 		} else { | ||||||
| 			throw NSError(domain: "MachineDocument", code: -1, userInfo: nil) | 			// Store the selected machine and list of missing ROMs, and | ||||||
|  | 			// show the missing ROMs dialogue. | ||||||
|  | 			self.missingROMs = [] | ||||||
|  | 			for untypedMissingROM in missingROMs { | ||||||
|  | 				self.missingROMs.append(untypedMissingROM as! CSMissingROM) | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			requestRoms() | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	convenience init(type typeName: String) throws { | 	enum InteractionMode { | ||||||
| 		self.init() | 		case notStarted, showingMachinePicker, showingROMRequester, showingMachine | ||||||
| 		self.fileType = typeName | 	} | ||||||
| 		self.shouldShowNewMachinePanel = true | 	private var interactionMode: InteractionMode = .notStarted | ||||||
|  |  | ||||||
|  | 	// Attempting to show a sheet before the window is visible (such as when the NIB is loaded) results in | ||||||
|  | 	// a sheet mysteriously floating on its own. For now, use windowDidUpdate as a proxy to know that the window | ||||||
|  | 	// is visible, though it's a little premature. | ||||||
|  | 	func windowDidUpdate(_ notification: Notification) { | ||||||
|  | 		// If an interaction mode is not yet in effect, pick the proper one and display the relevant thing. | ||||||
|  | 		if self.interactionMode == .notStarted { | ||||||
|  | 			// If a full machine exists, just continue showing it. | ||||||
|  | 			if self.machine != nil { | ||||||
|  | 				self.interactionMode = .showingMachine | ||||||
|  | 				setupMachineOutput() | ||||||
|  | 				return | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
| 	// MARK: the pasteboard | 			// If a machine has been picked but is not showing, there must be ROMs missing. | ||||||
| 	func paste(_ sender: Any) { | 			if self.machineDescription != nil { | ||||||
| 		let pasteboard = NSPasteboard.general | 				self.interactionMode = .showingROMRequester | ||||||
| 		if let string = pasteboard.string(forType: .string) { | 				requestRoms() | ||||||
| 			self.machine.paste(string) | 				return | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			// If a machine hasn't even been picked yet, show the machine picker. | ||||||
|  | 			self.interactionMode = .showingMachinePicker | ||||||
|  | 			Bundle.main.loadNibNamed("MachinePicker", owner: self, topLevelObjects: nil) | ||||||
|  | 			self.machinePicker?.establishStoredOptions() | ||||||
|  | 			self.windowControllers[0].window?.beginSheet(self.machinePickerPanel!, completionHandler: nil) | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	// MARK: CSBestEffortUpdaterDelegate | 	// MARK: - Connections Between Machine and the Outside World | ||||||
| 	final func bestEffortUpdater(_ bestEffortUpdater: CSBestEffortUpdater!, runForInterval duration: TimeInterval, didSkipPreviousUpdate: Bool) { |  | ||||||
| 		if actionLock.try() { | 	private func setupMachineOutput() { | ||||||
| 			self.machine.run(forInterval: duration) | 		if let machine = self.machine, let openGLView = self.openGLView { | ||||||
| 			actionLock.unlock() | 			// Establish the output aspect ratio and audio. | ||||||
|  | 			let aspectRatio = self.aspectRatio() | ||||||
|  | 			openGLView.perform(glContext: { | ||||||
|  | 				machine.setView(openGLView, aspectRatio: Float(aspectRatio.width / aspectRatio.height)) | ||||||
|  | 			}) | ||||||
|  |  | ||||||
|  | 			// Attach an options panel if one is available. | ||||||
|  | 			if let optionsPanelNibName = self.machineDescription?.optionsPanelNibName { | ||||||
|  | 				Bundle.main.loadNibNamed(optionsPanelNibName, owner: self, topLevelObjects: nil) | ||||||
|  | 				self.optionsPanel.machine = machine | ||||||
|  | 				self.optionsPanel?.establishStoredOptions() | ||||||
|  | 				showOptions(self) | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			machine.delegate = self | ||||||
|  | 			self.bestEffortUpdater = CSBestEffortUpdater() | ||||||
|  |  | ||||||
|  | 			// Callbacks from the OpenGL may come on a different thread, immediately following the .delegate set; | ||||||
|  | 			// hence the full setup of the best-effort updater prior to setting self as a delegate. | ||||||
|  | 			openGLView.delegate = self | ||||||
|  | 			openGLView.responderDelegate = self | ||||||
|  |  | ||||||
|  | 			// If this machine has a mouse, enable mouse capture. | ||||||
|  | 			openGLView.shouldCaptureMouse = machine.hasMouse | ||||||
|  |  | ||||||
|  | 			setupAudioQueueClockRate() | ||||||
|  |  | ||||||
|  | 			// Bring OpenGL view-holding window on top of the options panel and show the content. | ||||||
|  | 			openGLView.isHidden = false | ||||||
|  | 			openGLView.window!.makeKeyAndOrderFront(self) | ||||||
|  | 			openGLView.window!.makeFirstResponder(openGLView) | ||||||
|  |  | ||||||
|  | 			// Start accepting best effort updates. | ||||||
|  | 			self.bestEffortUpdater!.delegate = self | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	// MARK: CSAudioQueueDelegate | 	func machineSpeakerDidChangeInputClock(_ machine: CSMachine) { | ||||||
|  | 		setupAudioQueueClockRate() | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	private func setupAudioQueueClockRate() { | ||||||
|  | 		// establish and provide the audio queue, taking advice as to an appropriate sampling rate | ||||||
|  | 		let maximumSamplingRate = CSAudioQueue.preferredSamplingRate() | ||||||
|  | 		let selectedSamplingRate = self.machine.idealSamplingRate(from: NSRange(location: 0, length: NSInteger(maximumSamplingRate))) | ||||||
|  | 		if selectedSamplingRate > 0 { | ||||||
|  | 			audioQueue = CSAudioQueue(samplingRate: Float64(selectedSamplingRate)) | ||||||
|  | 			audioQueue.delegate = self | ||||||
|  | 			self.machine.audioQueue = self.audioQueue | ||||||
|  | 			self.machine.setAudioSamplingRate(selectedSamplingRate, bufferSize:audioQueue.preferredBufferSize) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	/// Responds to the CSAudioQueueDelegate dry-queue warning message by requesting a machine update. | ||||||
| 	final func audioQueueIsRunningDry(_ audioQueue: CSAudioQueue) { | 	final func audioQueueIsRunningDry(_ audioQueue: CSAudioQueue) { | ||||||
| 		bestEffortLock.lock() | 		bestEffortLock.lock() | ||||||
| 		bestEffortUpdater?.update() | 		bestEffortUpdater?.update() | ||||||
| 		bestEffortLock.unlock() | 		bestEffortLock.unlock() | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	// MARK: CSOpenGLViewDelegate | 	/// Responds to the CSOpenGLViewDelegate redraw message by requesting a machine update if this is a timed | ||||||
|  | 	/// request, and ordering a redraw regardless of the motivation. | ||||||
| 	final func openGLViewRedraw(_ view: CSOpenGLView, event redrawEvent: CSOpenGLViewRedrawEvent) { | 	final func openGLViewRedraw(_ view: CSOpenGLView, event redrawEvent: CSOpenGLViewRedrawEvent) { | ||||||
| 		if redrawEvent == .timer { | 		if redrawEvent == .timer { | ||||||
| 			bestEffortLock.lock() | 			bestEffortLock.lock() | ||||||
| @@ -218,7 +264,27 @@ class MachineDocument: | |||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	// MARK: Runtime media insertion. | 	/// Responds to CSBestEffortUpdaterDelegate update message by running the machine. | ||||||
|  | 	final func bestEffortUpdater(_ bestEffortUpdater: CSBestEffortUpdater!, runForInterval duration: TimeInterval, didSkipPreviousUpdate: Bool) { | ||||||
|  | 		if let machine = self.machine, actionLock.try() { | ||||||
|  | 			machine.run(forInterval: duration) | ||||||
|  | 			actionLock.unlock() | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// MARK: - Pasteboard Forwarding. | ||||||
|  |  | ||||||
|  | 	/// Forwards any text currently on the pasteboard into the active machine. | ||||||
|  | 	func paste(_ sender: Any) { | ||||||
|  | 		let pasteboard = NSPasteboard.general | ||||||
|  | 		if let string = pasteboard.string(forType: .string), let machine = self.machine { | ||||||
|  | 			machine.paste(string) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// MARK: - Runtime Media Insertion. | ||||||
|  |  | ||||||
|  | 	/// Delegate message to receive drag and drop files. | ||||||
| 	final func openGLView(_ view: CSOpenGLView, didReceiveFileAt URL: URL) { | 	final func openGLView(_ view: CSOpenGLView, didReceiveFileAt URL: URL) { | ||||||
| 		let mediaSet = CSMediaSet(fileAt: URL) | 		let mediaSet = CSMediaSet(fileAt: URL) | ||||||
| 		if let mediaSet = mediaSet { | 		if let mediaSet = mediaSet { | ||||||
| @@ -226,6 +292,8 @@ class MachineDocument: | |||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | 	/// Action for the insert menu command; displays an NSOpenPanel and then segues into the same process | ||||||
|  | 	/// as if a file had been received via drag and drop. | ||||||
| 	@IBAction final func insertMedia(_ sender: AnyObject!) { | 	@IBAction final func insertMedia(_ sender: AnyObject!) { | ||||||
| 		let openPanel = NSOpenPanel() | 		let openPanel = NSOpenPanel() | ||||||
| 		openPanel.message = "Hint: you can also insert media by dragging and dropping it onto the machine's window." | 		openPanel.message = "Hint: you can also insert media by dragging and dropping it onto the machine's window." | ||||||
| @@ -241,37 +309,40 @@ class MachineDocument: | |||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	// MARK: NSDocument overrides | 	// MARK: - Input Management. | ||||||
| 	override func data(ofType typeName: String) throws -> Data { |  | ||||||
| 		throw NSError(domain: NSOSStatusErrorDomain, code: unimpErr, userInfo: nil) |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	// MARK: Input management | 	/// Upon a resign key, immediately releases all ongoing input mechanisms — any currently pressed keys, | ||||||
|  | 	/// and joystick and mouse inputs. | ||||||
| 	func windowDidResignKey(_ notification: Notification) { | 	func windowDidResignKey(_ notification: Notification) { | ||||||
| 		if let machine = self.machine { | 		if let machine = self.machine { | ||||||
| 			machine.clearAllKeys() | 			machine.clearAllKeys() | ||||||
| 			machine.joystickManager = nil | 			machine.joystickManager = nil | ||||||
| 		} | 		} | ||||||
|  | 		self.openGLView.releaseMouse() | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | 	/// Upon becoming key, attaches joystick input to the machine. | ||||||
| 	func windowDidBecomeKey(_ notification: Notification) { | 	func windowDidBecomeKey(_ notification: Notification) { | ||||||
| 		if let machine = self.machine { | 		if let machine = self.machine { | ||||||
| 			machine.joystickManager = (DocumentController.shared as! DocumentController).joystickManager | 			machine.joystickManager = (DocumentController.shared as! DocumentController).joystickManager | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | 	/// Forwards key down events directly to the machine. | ||||||
| 	func keyDown(_ event: NSEvent) { | 	func keyDown(_ event: NSEvent) { | ||||||
| 		if let machine = self.machine { | 		if let machine = self.machine { | ||||||
| 			machine.setKey(event.keyCode, characters: event.characters, isPressed: true) | 			machine.setKey(event.keyCode, characters: event.characters, isPressed: true) | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | 	/// Forwards key up events directly to the machine. | ||||||
| 	func keyUp(_ event: NSEvent) { | 	func keyUp(_ event: NSEvent) { | ||||||
| 		if let machine = self.machine { | 		if let machine = self.machine { | ||||||
| 			machine.setKey(event.keyCode, characters: event.characters, isPressed: false) | 			machine.setKey(event.keyCode, characters: event.characters, isPressed: false) | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | 	/// Synthesies appropriate key up and key down events upon any change in modifiers. | ||||||
| 	func flagsChanged(_ newModifiers: NSEvent) { | 	func flagsChanged(_ newModifiers: NSEvent) { | ||||||
| 		if let machine = self.machine { | 		if let machine = self.machine { | ||||||
| 			machine.setKey(VK_Shift, characters: nil, isPressed: newModifiers.modifierFlags.contains(.shift)) | 			machine.setKey(VK_Shift, characters: nil, isPressed: newModifiers.modifierFlags.contains(.shift)) | ||||||
| @@ -281,19 +352,199 @@ class MachineDocument: | |||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	// MARK: New machine creation | 	/// Forwards mouse movement events to the mouse. | ||||||
|  | 	func mouseMoved(_ event: NSEvent) { | ||||||
|  | 		if let machine = self.machine { | ||||||
|  | 			machine.addMouseMotionX(event.deltaX, y: event.deltaY) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	/// Forwards mouse button down events to the mouse. | ||||||
|  | 	func mouseUp(_ event: NSEvent) { | ||||||
|  | 		if let machine = self.machine { | ||||||
|  | 			machine.setMouseButton(Int32(event.buttonNumber), isPressed: false) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	/// Forwards mouse button up events to the mouse. | ||||||
|  | 	func mouseDown(_ event: NSEvent) { | ||||||
|  | 		if let machine = self.machine { | ||||||
|  | 			machine.setMouseButton(Int32(event.buttonNumber), isPressed: true) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// MARK: - MachinePicker Outlets and Actions | ||||||
| 	@IBOutlet var machinePicker: MachinePicker? | 	@IBOutlet var machinePicker: MachinePicker? | ||||||
| 	@IBOutlet var machinePickerPanel: NSWindow? | 	@IBOutlet var machinePickerPanel: NSWindow? | ||||||
| 	@IBAction func createMachine(_ sender: NSButton?) { | 	@IBAction func createMachine(_ sender: NSButton?) { | ||||||
| 		self.configureAs(machinePicker!.selectedMachine()) | 		let selectedMachine = machinePicker!.selectedMachine() | ||||||
| 		machinePicker = nil |  | ||||||
| 		self.windowControllers[0].window?.endSheet(self.machinePickerPanel!) | 		self.windowControllers[0].window?.endSheet(self.machinePickerPanel!) | ||||||
|  | 		self.machinePicker = nil | ||||||
|  | 		self.configureAs(selectedMachine) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	@IBAction func cancelCreateMachine(_ sender: NSButton?) { | 	@IBAction func cancelCreateMachine(_ sender: NSButton?) { | ||||||
| 		close() | 		close() | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | 	// MARK: - ROMRequester Outlets and Actions | ||||||
|  | 	@IBOutlet var romRequesterPanel: NSWindow? | ||||||
|  | 	@IBOutlet var romRequesterText: NSTextField? | ||||||
|  | 	@IBOutlet var romReceiverErrorField: NSTextField? | ||||||
|  | 	@IBOutlet var romReceiverView: CSROMReceiverView? | ||||||
|  | 	private var romRequestBaseText = "" | ||||||
|  | 	func requestRoms() { | ||||||
|  | 		// Don't act yet if there's no window controller yet. | ||||||
|  | 		if self.windowControllers.count == 0 { | ||||||
|  | 			return | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		// Load the ROM requester dialogue. | ||||||
|  | 		Bundle.main.loadNibNamed("ROMRequester", owner: self, topLevelObjects: nil) | ||||||
|  | 		self.romReceiverView!.delegate = self | ||||||
|  | 		self.romRequestBaseText = romRequesterText!.stringValue | ||||||
|  | 		romReceiverErrorField?.alphaValue = 0.0 | ||||||
|  |  | ||||||
|  | 		// Populate the current absentee list. | ||||||
|  | 		populateMissingRomList() | ||||||
|  |  | ||||||
|  | 		// Show the thing. | ||||||
|  | 		self.windowControllers[0].window?.beginSheet(self.romRequesterPanel!, completionHandler: nil) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	@IBAction func cancelRequestROMs(_ sender: NSButton?) { | ||||||
|  | 		close() | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	func populateMissingRomList() { | ||||||
|  | 		// Fill in the missing details; first build a list of all the individual | ||||||
|  | 		// line items. | ||||||
|  | 		var requestLines: [String] = [] | ||||||
|  | 		for missingROM in self.missingROMs { | ||||||
|  | 			if let descriptiveName = missingROM.descriptiveName { | ||||||
|  | 				requestLines.append("• " + descriptiveName) | ||||||
|  | 			} else { | ||||||
|  | 				requestLines.append("• " + missingROM.fileName) | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		// Suffix everything up to the penultimate line with a semicolon; | ||||||
|  | 		// the penultimate line with a semicolon and a conjunctive; the final | ||||||
|  | 		// line with a full stop. | ||||||
|  | 		for x in 0 ..< requestLines.count { | ||||||
|  | 			if x < requestLines.count - 2 { | ||||||
|  | 				requestLines[x].append(";") | ||||||
|  | 			} else if x < requestLines.count - 1 { | ||||||
|  | 				requestLines[x].append("; and") | ||||||
|  | 			} else { | ||||||
|  | 				requestLines[x].append(".") | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 		romRequesterText!.stringValue = self.romRequestBaseText + requestLines.joined(separator: "\n") | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	func romReceiverView(_ view: CSROMReceiverView, didReceiveFileAt URL: URL) { | ||||||
|  | 		// Test whether the file identified matches any of the currently missing ROMs. | ||||||
|  | 		// If so then remove that ROM from the missing list and update the request screen. | ||||||
|  | 		// If no ROMs are still missing, start the machine. | ||||||
|  | 		do { | ||||||
|  | 			let fileData = try Data(contentsOf: URL) | ||||||
|  | 			var didInstallRom = false | ||||||
|  |  | ||||||
|  | 			// Try to match by size first, CRC second. Accept that some ROMs may have | ||||||
|  | 			// some additional appended data. Arbitrarily allow them to be up to 10kb | ||||||
|  | 			// too large. | ||||||
|  | 			var index = 0 | ||||||
|  | 			for missingROM in self.missingROMs { | ||||||
|  | 				if fileData.count >= missingROM.size && fileData.count < missingROM.size + 10*1024 { | ||||||
|  | 					// Trim to size. | ||||||
|  | 					let trimmedData = fileData[0 ..< missingROM.size] | ||||||
|  |  | ||||||
|  | 					// Get CRC. | ||||||
|  | 					if missingROM.crc32s.contains( (trimmedData as NSData).crc32 ) { | ||||||
|  | 						// This ROM matches; copy it into the application library, | ||||||
|  | 						// strike it from the missing ROM list and decide how to | ||||||
|  | 						// proceed. | ||||||
|  | 						let fileManager = FileManager.default | ||||||
|  | 						let targetPath = fileManager.urls(for: .applicationSupportDirectory, in: .userDomainMask)[0] | ||||||
|  | 							.appendingPathComponent("ROMImages") | ||||||
|  | 							.appendingPathComponent(missingROM.machineName) | ||||||
|  | 						let targetFile = targetPath | ||||||
|  | 							.appendingPathComponent(missingROM.fileName) | ||||||
|  |  | ||||||
|  | 						do { | ||||||
|  | 							try fileManager.createDirectory(atPath: targetPath.path, withIntermediateDirectories: true, attributes: nil) | ||||||
|  | 							try trimmedData.write(to: targetFile) | ||||||
|  | 						} catch let error { | ||||||
|  | 							showRomReceiverError(error: "Couldn't write to application support directory: \(error)") | ||||||
|  | 						} | ||||||
|  |  | ||||||
|  | 						self.missingROMs.remove(at: index) | ||||||
|  | 						didInstallRom = true | ||||||
|  | 						break | ||||||
|  | 					} | ||||||
|  | 				} | ||||||
|  |  | ||||||
|  | 				index = index + 1 | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			if didInstallRom { | ||||||
|  | 				if self.missingROMs.count == 0 { | ||||||
|  | 					self.windowControllers[0].window?.endSheet(self.romRequesterPanel!) | ||||||
|  | 					configureAs(self.machineDescription!) | ||||||
|  | 				} else { | ||||||
|  | 					populateMissingRomList() | ||||||
|  | 				} | ||||||
|  | 			} else { | ||||||
|  | 				showRomReceiverError(error: "Didn't recognise contents of \(URL.lastPathComponent)") | ||||||
|  | 			} | ||||||
|  | 		} catch let error { | ||||||
|  | 			showRomReceiverError(error: "Couldn't read file at \(URL.absoluteString): \(error)") | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// Yucky ugliness follows; my experience as an iOS developer intersects poorly with | ||||||
|  | 	// NSAnimationContext hence the various stateful diplications below. isShowingError | ||||||
|  | 	// should be essentially a duplicate of the current alphaValue, and animationCount | ||||||
|  | 	// is to resolve my inability to figure out how to cancel scheduled animations. | ||||||
|  | 	private var errorText = "" | ||||||
|  | 	private var isShowingError = false | ||||||
|  | 	private var animationCount = 0 | ||||||
|  | 	private func showRomReceiverError(error: String) { | ||||||
|  | 		// Set or append the new error. | ||||||
|  | 		if self.errorText.count > 0 { | ||||||
|  | 			self.errorText = self.errorText + "\n" + error | ||||||
|  | 		} else { | ||||||
|  | 			self.errorText = error | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		// Apply the new complete text. | ||||||
|  | 		romReceiverErrorField!.stringValue = self.errorText | ||||||
|  |  | ||||||
|  | 		if !isShowingError { | ||||||
|  | 			// Schedule the box's appearance. | ||||||
|  | 			NSAnimationContext.beginGrouping() | ||||||
|  | 			NSAnimationContext.current.duration = 0.1 | ||||||
|  | 			romReceiverErrorField?.animator().alphaValue = 1.0 | ||||||
|  | 			NSAnimationContext.endGrouping() | ||||||
|  | 			isShowingError = true | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		// Schedule the box to disappear. | ||||||
|  | 		self.animationCount = self.animationCount + 1 | ||||||
|  | 		let capturedAnimationCount = animationCount | ||||||
|  | 		DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + .seconds(2)) { | ||||||
|  | 			if self.animationCount == capturedAnimationCount { | ||||||
|  | 				NSAnimationContext.beginGrouping() | ||||||
|  | 				NSAnimationContext.current.duration = 1.0 | ||||||
|  | 				self.romReceiverErrorField?.animator().alphaValue = 0.0 | ||||||
|  | 				NSAnimationContext.endGrouping() | ||||||
|  | 				self.isShowingError = false | ||||||
|  | 				self.errorText = "" | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
| 	// MARK: Joystick-via-the-keyboard selection | 	// MARK: Joystick-via-the-keyboard selection | ||||||
| 	@IBAction func useKeyboardAsKeyboard(_ sender: NSMenuItem?) { | 	@IBAction func useKeyboardAsKeyboard(_ sender: NSMenuItem?) { | ||||||
| 		machine.inputMode = .keyboard | 		machine.inputMode = .keyboard | ||||||
| @@ -303,6 +554,9 @@ class MachineDocument: | |||||||
| 		machine.inputMode = .joystick | 		machine.inputMode = .joystick | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | 	/// Determines which of the menu items to enable and disable based on the ability of the | ||||||
|  | 	/// current machine to handle keyboard and joystick input, accept new media and whether | ||||||
|  | 	/// it has an associted activity window. | ||||||
| 	override func validateUserInterfaceItem(_ item: NSValidatedUserInterfaceItem) -> Bool { | 	override func validateUserInterfaceItem(_ item: NSValidatedUserInterfaceItem) -> Bool { | ||||||
| 		if let menuItem = item as? NSMenuItem { | 		if let menuItem = item as? NSMenuItem { | ||||||
| 			switch item.action { | 			switch item.action { | ||||||
| @@ -336,7 +590,7 @@ class MachineDocument: | |||||||
| 		return super.validateUserInterfaceItem(item) | 		return super.validateUserInterfaceItem(item) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	// Screenshot capture. | 	/// Saves a screenshot of the | ||||||
| 	@IBAction func saveScreenshot(_ sender: AnyObject!) { | 	@IBAction func saveScreenshot(_ sender: AnyObject!) { | ||||||
| 		// Grab a date formatter and form a file name. | 		// Grab a date formatter and form a file name. | ||||||
| 		let dateFormatter = DateFormatter() | 		let dateFormatter = DateFormatter() | ||||||
| @@ -360,7 +614,8 @@ class MachineDocument: | |||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	// MARK: Activity display. | 	// MARK: Activity display. | ||||||
| 	class LED { |  | ||||||
|  | 	private class LED { | ||||||
| 		let levelIndicator: NSLevelIndicator | 		let levelIndicator: NSLevelIndicator | ||||||
| 		init(levelIndicator: NSLevelIndicator) { | 		init(levelIndicator: NSLevelIndicator) { | ||||||
| 			self.levelIndicator = levelIndicator | 			self.levelIndicator = levelIndicator | ||||||
| @@ -368,11 +623,12 @@ class MachineDocument: | |||||||
| 		var isLit = false | 		var isLit = false | ||||||
| 		var isBlinking = false | 		var isBlinking = false | ||||||
| 	} | 	} | ||||||
| 	fileprivate var leds: [String: LED] = [:] | 	private var leds: [String: LED] = [:] | ||||||
|  |  | ||||||
| 	func setupActivityDisplay() { | 	func setupActivityDisplay() { | ||||||
| 		var leds = machine.leds | 		var leds = machine.leds | ||||||
| 		if leds.count > 0 { | 		if leds.count > 0 { | ||||||
| 			Bundle.main.loadNibNamed(NSNib.Name(rawValue: "Activity"), owner: self, topLevelObjects: nil) | 			Bundle.main.loadNibNamed("Activity", owner: self, topLevelObjects: nil) | ||||||
| 			showActivity(nil) | 			showActivity(nil) | ||||||
|  |  | ||||||
| 			// Inspect the activity panel for indicators. | 			// Inspect the activity panel for indicators. | ||||||
|   | |||||||
| @@ -14,42 +14,42 @@ | |||||||
| 			</array> | 			</array> | ||||||
| 			<key>CFBundleTypeIconFile</key> | 			<key>CFBundleTypeIconFile</key> | ||||||
| 			<string>cartridge.png</string> | 			<string>cartridge.png</string> | ||||||
|  | 			<key>CFBundleTypeName</key> | ||||||
|  | 			<string>Atari 2600 Cartridge</string> | ||||||
| 			<key>CFBundleTypeOSTypes</key> | 			<key>CFBundleTypeOSTypes</key> | ||||||
| 			<array> | 			<array> | ||||||
| 				<string>????</string> | 				<string>????</string> | ||||||
| 			</array> | 			</array> | ||||||
| 			<key>CFBundleTypeName</key> |  | ||||||
| 			<string>Atari 2600 Cartridge</string> |  | ||||||
| 			<key>CFBundleTypeRole</key> | 			<key>CFBundleTypeRole</key> | ||||||
| 			<string>Viewer</string> | 			<string>Viewer</string> | ||||||
|  | 			<key>LSHandlerRank</key> | ||||||
|  | 			<string>Owner</string> | ||||||
| 			<key>LSTypeIsPackage</key> | 			<key>LSTypeIsPackage</key> | ||||||
| 			<false/> | 			<false/> | ||||||
| 			<key>NSDocumentClass</key> | 			<key>NSDocumentClass</key> | ||||||
| 			<string>$(PRODUCT_MODULE_NAME).MachineDocument</string> | 			<string>$(PRODUCT_MODULE_NAME).MachineDocument</string> | ||||||
| 			<key>LSHandlerRank</key> |  | ||||||
| 			<string>Owner</string> |  | ||||||
| 		</dict> | 		</dict> | ||||||
| 		<dict> | 		<dict> | ||||||
| 			<key>CFBundleTypeExtensions</key> | 			<key>CFBundleTypeExtensions</key> | ||||||
| 			<array> | 			<array> | ||||||
| 				<string>rom</string> | 				<string>rom</string> | ||||||
| 			</array> | 			</array> | ||||||
| 			<key>CFBundleTypeOSTypes</key> |  | ||||||
| 			<array> |  | ||||||
| 				<string>????</string> |  | ||||||
| 			</array> |  | ||||||
| 			<key>CFBundleTypeIconFile</key> | 			<key>CFBundleTypeIconFile</key> | ||||||
| 			<string>chip.png</string> | 			<string>chip.png</string> | ||||||
| 			<key>CFBundleTypeName</key> | 			<key>CFBundleTypeName</key> | ||||||
| 			<string>ROM Image</string> | 			<string>ROM Image</string> | ||||||
|  | 			<key>CFBundleTypeOSTypes</key> | ||||||
|  | 			<array> | ||||||
|  | 				<string>????</string> | ||||||
|  | 			</array> | ||||||
| 			<key>CFBundleTypeRole</key> | 			<key>CFBundleTypeRole</key> | ||||||
| 			<string>Viewer</string> | 			<string>Viewer</string> | ||||||
|  | 			<key>LSHandlerRank</key> | ||||||
|  | 			<string>Owner</string> | ||||||
| 			<key>LSTypeIsPackage</key> | 			<key>LSTypeIsPackage</key> | ||||||
| 			<false/> | 			<false/> | ||||||
| 			<key>NSDocumentClass</key> | 			<key>NSDocumentClass</key> | ||||||
| 			<string>$(PRODUCT_MODULE_NAME).MachineDocument</string> | 			<string>$(PRODUCT_MODULE_NAME).MachineDocument</string> | ||||||
| 			<key>LSHandlerRank</key> |  | ||||||
| 			<string>Owner</string> |  | ||||||
| 		</dict> | 		</dict> | ||||||
| 		<dict> | 		<dict> | ||||||
| 			<key>CFBundleTypeExtensions</key> | 			<key>CFBundleTypeExtensions</key> | ||||||
| @@ -57,110 +57,110 @@ | |||||||
| 				<string>uef</string> | 				<string>uef</string> | ||||||
| 				<string>uef.gz</string> | 				<string>uef.gz</string> | ||||||
| 			</array> | 			</array> | ||||||
| 			<key>CFBundleTypeOSTypes</key> |  | ||||||
| 			<array> |  | ||||||
| 				<string>????</string> |  | ||||||
| 			</array> |  | ||||||
| 			<key>CFBundleTypeIconFile</key> | 			<key>CFBundleTypeIconFile</key> | ||||||
| 			<string>cassette.png</string> | 			<string>cassette.png</string> | ||||||
| 			<key>CFBundleTypeName</key> | 			<key>CFBundleTypeName</key> | ||||||
| 			<string>Electron/BBC UEF Image</string> | 			<string>Electron/BBC UEF Image</string> | ||||||
|  | 			<key>CFBundleTypeOSTypes</key> | ||||||
|  | 			<array> | ||||||
|  | 				<string>????</string> | ||||||
|  | 			</array> | ||||||
| 			<key>CFBundleTypeRole</key> | 			<key>CFBundleTypeRole</key> | ||||||
| 			<string>Viewer</string> | 			<string>Viewer</string> | ||||||
|  | 			<key>LSHandlerRank</key> | ||||||
|  | 			<string>Owner</string> | ||||||
| 			<key>LSTypeIsPackage</key> | 			<key>LSTypeIsPackage</key> | ||||||
| 			<false/> | 			<false/> | ||||||
| 			<key>NSDocumentClass</key> | 			<key>NSDocumentClass</key> | ||||||
| 			<string>$(PRODUCT_MODULE_NAME).MachineDocument</string> | 			<string>$(PRODUCT_MODULE_NAME).MachineDocument</string> | ||||||
| 			<key>LSHandlerRank</key> |  | ||||||
| 			<string>Owner</string> |  | ||||||
| 		</dict> | 		</dict> | ||||||
| 		<dict> | 		<dict> | ||||||
| 			<key>CFBundleTypeExtensions</key> | 			<key>CFBundleTypeExtensions</key> | ||||||
| 			<array> | 			<array> | ||||||
| 				<string>prg</string> | 				<string>prg</string> | ||||||
| 			</array> | 			</array> | ||||||
| 			<key>CFBundleTypeOSTypes</key> |  | ||||||
| 			<array> |  | ||||||
| 				<string>????</string> |  | ||||||
| 			</array> |  | ||||||
| 			<key>CFBundleTypeIconFile</key> | 			<key>CFBundleTypeIconFile</key> | ||||||
| 			<string>floppy525.png</string> | 			<string>floppy525.png</string> | ||||||
| 			<key>CFBundleTypeName</key> | 			<key>CFBundleTypeName</key> | ||||||
| 			<string>Commodore Program</string> | 			<string>Commodore Program</string> | ||||||
|  | 			<key>CFBundleTypeOSTypes</key> | ||||||
|  | 			<array> | ||||||
|  | 				<string>????</string> | ||||||
|  | 			</array> | ||||||
| 			<key>CFBundleTypeRole</key> | 			<key>CFBundleTypeRole</key> | ||||||
| 			<string>Viewer</string> | 			<string>Viewer</string> | ||||||
|  | 			<key>LSHandlerRank</key> | ||||||
|  | 			<string>Owner</string> | ||||||
| 			<key>LSTypeIsPackage</key> | 			<key>LSTypeIsPackage</key> | ||||||
| 			<false/> | 			<false/> | ||||||
| 			<key>NSDocumentClass</key> | 			<key>NSDocumentClass</key> | ||||||
| 			<string>$(PRODUCT_MODULE_NAME).MachineDocument</string> | 			<string>$(PRODUCT_MODULE_NAME).MachineDocument</string> | ||||||
| 			<key>LSHandlerRank</key> |  | ||||||
| 			<string>Owner</string> |  | ||||||
| 		</dict> | 		</dict> | ||||||
| 		<dict> | 		<dict> | ||||||
| 			<key>CFBundleTypeExtensions</key> | 			<key>CFBundleTypeExtensions</key> | ||||||
| 			<array> | 			<array> | ||||||
| 				<string>tap</string> | 				<string>tap</string> | ||||||
| 			</array> | 			</array> | ||||||
| 			<key>CFBundleTypeOSTypes</key> |  | ||||||
| 			<array> |  | ||||||
| 				<string>????</string> |  | ||||||
| 			</array> |  | ||||||
| 			<key>CFBundleTypeIconFile</key> | 			<key>CFBundleTypeIconFile</key> | ||||||
| 			<string>cassette.png</string> | 			<string>cassette.png</string> | ||||||
| 			<key>CFBundleTypeName</key> | 			<key>CFBundleTypeName</key> | ||||||
| 			<string>Tape Image</string> | 			<string>Tape Image</string> | ||||||
|  | 			<key>CFBundleTypeOSTypes</key> | ||||||
|  | 			<array> | ||||||
|  | 				<string>????</string> | ||||||
|  | 			</array> | ||||||
| 			<key>CFBundleTypeRole</key> | 			<key>CFBundleTypeRole</key> | ||||||
| 			<string>Viewer</string> | 			<string>Viewer</string> | ||||||
|  | 			<key>LSHandlerRank</key> | ||||||
|  | 			<string>Owner</string> | ||||||
| 			<key>LSTypeIsPackage</key> | 			<key>LSTypeIsPackage</key> | ||||||
| 			<false/> | 			<false/> | ||||||
| 			<key>NSDocumentClass</key> | 			<key>NSDocumentClass</key> | ||||||
| 			<string>$(PRODUCT_MODULE_NAME).MachineDocument</string> | 			<string>$(PRODUCT_MODULE_NAME).MachineDocument</string> | ||||||
| 			<key>LSHandlerRank</key> |  | ||||||
| 			<string>Owner</string> |  | ||||||
| 		</dict> | 		</dict> | ||||||
| 		<dict> | 		<dict> | ||||||
| 			<key>CFBundleTypeExtensions</key> | 			<key>CFBundleTypeExtensions</key> | ||||||
| 			<array> | 			<array> | ||||||
| 				<string>g64</string> | 				<string>g64</string> | ||||||
| 			</array> | 			</array> | ||||||
| 			<key>CFBundleTypeOSTypes</key> |  | ||||||
| 			<array> |  | ||||||
| 				<string>????</string> |  | ||||||
| 			</array> |  | ||||||
| 			<key>CFBundleTypeIconFile</key> | 			<key>CFBundleTypeIconFile</key> | ||||||
| 			<string>floppy525.png</string> | 			<string>floppy525.png</string> | ||||||
| 			<key>CFBundleTypeName</key> | 			<key>CFBundleTypeName</key> | ||||||
| 			<string>Commodore Disk</string> | 			<string>Commodore Disk</string> | ||||||
|  | 			<key>CFBundleTypeOSTypes</key> | ||||||
|  | 			<array> | ||||||
|  | 				<string>????</string> | ||||||
|  | 			</array> | ||||||
| 			<key>CFBundleTypeRole</key> | 			<key>CFBundleTypeRole</key> | ||||||
| 			<string>Editor</string> | 			<string>Editor</string> | ||||||
|  | 			<key>LSHandlerRank</key> | ||||||
|  | 			<string>Owner</string> | ||||||
| 			<key>LSTypeIsPackage</key> | 			<key>LSTypeIsPackage</key> | ||||||
| 			<false/> | 			<false/> | ||||||
| 			<key>NSDocumentClass</key> | 			<key>NSDocumentClass</key> | ||||||
| 			<string>$(PRODUCT_MODULE_NAME).MachineDocument</string> | 			<string>$(PRODUCT_MODULE_NAME).MachineDocument</string> | ||||||
| 			<key>LSHandlerRank</key> |  | ||||||
| 			<string>Owner</string> |  | ||||||
| 		</dict> | 		</dict> | ||||||
| 		<dict> | 		<dict> | ||||||
| 			<key>CFBundleTypeExtensions</key> | 			<key>CFBundleTypeExtensions</key> | ||||||
| 			<array> | 			<array> | ||||||
| 				<string>d64</string> | 				<string>d64</string> | ||||||
| 			</array> | 			</array> | ||||||
| 			<key>CFBundleTypeOSTypes</key> |  | ||||||
| 			<array> |  | ||||||
| 				<string>????</string> |  | ||||||
| 			</array> |  | ||||||
| 			<key>CFBundleTypeIconFile</key> | 			<key>CFBundleTypeIconFile</key> | ||||||
| 			<string>floppy525.png</string> | 			<string>floppy525.png</string> | ||||||
| 			<key>CFBundleTypeName</key> | 			<key>CFBundleTypeName</key> | ||||||
| 			<string>Commodore 1540/1 Disk</string> | 			<string>Commodore 1540/1 Disk</string> | ||||||
|  | 			<key>CFBundleTypeOSTypes</key> | ||||||
|  | 			<array> | ||||||
|  | 				<string>????</string> | ||||||
|  | 			</array> | ||||||
| 			<key>CFBundleTypeRole</key> | 			<key>CFBundleTypeRole</key> | ||||||
| 			<string>Editor</string> | 			<string>Editor</string> | ||||||
|  | 			<key>LSHandlerRank</key> | ||||||
|  | 			<string>Owner</string> | ||||||
| 			<key>LSTypeIsPackage</key> | 			<key>LSTypeIsPackage</key> | ||||||
| 			<false/> | 			<false/> | ||||||
| 			<key>NSDocumentClass</key> | 			<key>NSDocumentClass</key> | ||||||
| 			<string>$(PRODUCT_MODULE_NAME).MachineDocument</string> | 			<string>$(PRODUCT_MODULE_NAME).MachineDocument</string> | ||||||
| 			<key>LSHandlerRank</key> |  | ||||||
| 			<string>Owner</string> |  | ||||||
| 		</dict> | 		</dict> | ||||||
| 		<dict> | 		<dict> | ||||||
| 			<key>CFBundleTypeExtensions</key> | 			<key>CFBundleTypeExtensions</key> | ||||||
| @@ -171,44 +171,44 @@ | |||||||
| 				<string>adl</string> | 				<string>adl</string> | ||||||
| 				<string>adm</string> | 				<string>adm</string> | ||||||
| 			</array> | 			</array> | ||||||
| 			<key>CFBundleTypeOSTypes</key> |  | ||||||
| 			<array> |  | ||||||
| 				<string>????</string> |  | ||||||
| 			</array> |  | ||||||
| 			<key>CFBundleTypeIconFile</key> | 			<key>CFBundleTypeIconFile</key> | ||||||
| 			<string>floppy35.png</string> | 			<string>floppy35.png</string> | ||||||
| 			<key>CFBundleTypeName</key> | 			<key>CFBundleTypeName</key> | ||||||
| 			<string>Electron/BBC Disk Image</string> | 			<string>Electron/BBC Disk Image</string> | ||||||
|  | 			<key>CFBundleTypeOSTypes</key> | ||||||
|  | 			<array> | ||||||
|  | 				<string>????</string> | ||||||
|  | 			</array> | ||||||
| 			<key>CFBundleTypeRole</key> | 			<key>CFBundleTypeRole</key> | ||||||
| 			<string>Editor</string> | 			<string>Editor</string> | ||||||
|  | 			<key>LSHandlerRank</key> | ||||||
|  | 			<string>Owner</string> | ||||||
| 			<key>LSTypeIsPackage</key> | 			<key>LSTypeIsPackage</key> | ||||||
| 			<false/> | 			<false/> | ||||||
| 			<key>NSDocumentClass</key> | 			<key>NSDocumentClass</key> | ||||||
| 			<string>$(PRODUCT_MODULE_NAME).MachineDocument</string> | 			<string>$(PRODUCT_MODULE_NAME).MachineDocument</string> | ||||||
| 			<key>LSHandlerRank</key> |  | ||||||
| 			<string>Owner</string> |  | ||||||
| 		</dict> | 		</dict> | ||||||
| 		<dict> | 		<dict> | ||||||
| 			<key>CFBundleTypeExtensions</key> | 			<key>CFBundleTypeExtensions</key> | ||||||
| 			<array> | 			<array> | ||||||
| 				<string>dsk</string> | 				<string>dsk</string> | ||||||
| 			</array> | 			</array> | ||||||
| 			<key>CFBundleTypeOSTypes</key> |  | ||||||
| 			<array> |  | ||||||
| 				<string>????</string> |  | ||||||
| 			</array> |  | ||||||
| 			<key>CFBundleTypeIconFile</key> | 			<key>CFBundleTypeIconFile</key> | ||||||
| 			<string>floppy35.png</string> | 			<string>floppy35.png</string> | ||||||
| 			<key>CFBundleTypeName</key> | 			<key>CFBundleTypeName</key> | ||||||
| 			<string>Disk Image</string> | 			<string>Disk Image</string> | ||||||
|  | 			<key>CFBundleTypeOSTypes</key> | ||||||
|  | 			<array> | ||||||
|  | 				<string>????</string> | ||||||
|  | 			</array> | ||||||
| 			<key>CFBundleTypeRole</key> | 			<key>CFBundleTypeRole</key> | ||||||
| 			<string>Editor</string> | 			<string>Editor</string> | ||||||
|  | 			<key>LSHandlerRank</key> | ||||||
|  | 			<string>Owner</string> | ||||||
| 			<key>LSTypeIsPackage</key> | 			<key>LSTypeIsPackage</key> | ||||||
| 			<false/> | 			<false/> | ||||||
| 			<key>NSDocumentClass</key> | 			<key>NSDocumentClass</key> | ||||||
| 			<string>$(PRODUCT_MODULE_NAME).MachineDocument</string> | 			<string>$(PRODUCT_MODULE_NAME).MachineDocument</string> | ||||||
| 			<key>LSHandlerRank</key> |  | ||||||
| 			<string>Owner</string> |  | ||||||
| 		</dict> | 		</dict> | ||||||
| 		<dict> | 		<dict> | ||||||
| 			<key>CFBundleTypeExtensions</key> | 			<key>CFBundleTypeExtensions</key> | ||||||
| @@ -216,22 +216,22 @@ | |||||||
| 				<string>o</string> | 				<string>o</string> | ||||||
| 				<string>80</string> | 				<string>80</string> | ||||||
| 			</array> | 			</array> | ||||||
| 			<key>CFBundleTypeOSTypes</key> |  | ||||||
| 			<array> |  | ||||||
| 				<string>????</string> |  | ||||||
| 			</array> |  | ||||||
| 			<key>CFBundleTypeIconFile</key> | 			<key>CFBundleTypeIconFile</key> | ||||||
| 			<string>cassette.png</string> | 			<string>cassette.png</string> | ||||||
| 			<key>CFBundleTypeName</key> | 			<key>CFBundleTypeName</key> | ||||||
| 			<string>ZX80 Tape Image</string> | 			<string>ZX80 Tape Image</string> | ||||||
|  | 			<key>CFBundleTypeOSTypes</key> | ||||||
|  | 			<array> | ||||||
|  | 				<string>????</string> | ||||||
|  | 			</array> | ||||||
| 			<key>CFBundleTypeRole</key> | 			<key>CFBundleTypeRole</key> | ||||||
| 			<string>Viewer</string> | 			<string>Viewer</string> | ||||||
|  | 			<key>LSHandlerRank</key> | ||||||
|  | 			<string>Owner</string> | ||||||
| 			<key>LSTypeIsPackage</key> | 			<key>LSTypeIsPackage</key> | ||||||
| 			<false/> | 			<false/> | ||||||
| 			<key>NSDocumentClass</key> | 			<key>NSDocumentClass</key> | ||||||
| 			<string>$(PRODUCT_MODULE_NAME).MachineDocument</string> | 			<string>$(PRODUCT_MODULE_NAME).MachineDocument</string> | ||||||
| 			<key>LSHandlerRank</key> |  | ||||||
| 			<string>Owner</string> |  | ||||||
| 		</dict> | 		</dict> | ||||||
| 		<dict> | 		<dict> | ||||||
| 			<key>CFBundleTypeExtensions</key> | 			<key>CFBundleTypeExtensions</key> | ||||||
| @@ -240,240 +240,240 @@ | |||||||
| 				<string>81</string> | 				<string>81</string> | ||||||
| 				<string>p81</string> | 				<string>p81</string> | ||||||
| 			</array> | 			</array> | ||||||
| 			<key>CFBundleTypeOSTypes</key> |  | ||||||
| 			<array> |  | ||||||
| 				<string>????</string> |  | ||||||
| 			</array> |  | ||||||
| 			<key>CFBundleTypeIconFile</key> | 			<key>CFBundleTypeIconFile</key> | ||||||
| 			<string>cassette.png</string> | 			<string>cassette.png</string> | ||||||
| 			<key>CFBundleTypeName</key> | 			<key>CFBundleTypeName</key> | ||||||
| 			<string>ZX81 Tape Image</string> | 			<string>ZX81 Tape Image</string> | ||||||
|  | 			<key>CFBundleTypeOSTypes</key> | ||||||
|  | 			<array> | ||||||
|  | 				<string>????</string> | ||||||
|  | 			</array> | ||||||
| 			<key>CFBundleTypeRole</key> | 			<key>CFBundleTypeRole</key> | ||||||
| 			<string>Viewer</string> | 			<string>Viewer</string> | ||||||
|  | 			<key>LSHandlerRank</key> | ||||||
|  | 			<string>Owner</string> | ||||||
| 			<key>LSTypeIsPackage</key> | 			<key>LSTypeIsPackage</key> | ||||||
| 			<false/> | 			<false/> | ||||||
| 			<key>NSDocumentClass</key> | 			<key>NSDocumentClass</key> | ||||||
| 			<string>$(PRODUCT_MODULE_NAME).MachineDocument</string> | 			<string>$(PRODUCT_MODULE_NAME).MachineDocument</string> | ||||||
| 			<key>LSHandlerRank</key> |  | ||||||
| 			<string>Owner</string> |  | ||||||
| 		</dict> | 		</dict> | ||||||
| 		<dict> | 		<dict> | ||||||
| 			<key>CFBundleTypeExtensions</key> | 			<key>CFBundleTypeExtensions</key> | ||||||
| 			<array> | 			<array> | ||||||
| 				<string>csw</string> | 				<string>csw</string> | ||||||
| 			</array> | 			</array> | ||||||
| 			<key>CFBundleTypeOSTypes</key> |  | ||||||
| 			<array> |  | ||||||
| 				<string>????</string> |  | ||||||
| 			</array> |  | ||||||
| 			<key>CFBundleTypeIconFile</key> | 			<key>CFBundleTypeIconFile</key> | ||||||
| 			<string>cassette.png</string> | 			<string>cassette.png</string> | ||||||
| 			<key>CFBundleTypeName</key> | 			<key>CFBundleTypeName</key> | ||||||
| 			<string>Tape Image</string> | 			<string>Tape Image</string> | ||||||
|  | 			<key>CFBundleTypeOSTypes</key> | ||||||
|  | 			<array> | ||||||
|  | 				<string>????</string> | ||||||
|  | 			</array> | ||||||
| 			<key>CFBundleTypeRole</key> | 			<key>CFBundleTypeRole</key> | ||||||
| 			<string>Viewer</string> | 			<string>Viewer</string> | ||||||
|  | 			<key>LSHandlerRank</key> | ||||||
|  | 			<string>Owner</string> | ||||||
| 			<key>LSTypeIsPackage</key> | 			<key>LSTypeIsPackage</key> | ||||||
| 			<false/> | 			<false/> | ||||||
| 			<key>NSDocumentClass</key> | 			<key>NSDocumentClass</key> | ||||||
| 			<string>$(PRODUCT_MODULE_NAME).MachineDocument</string> | 			<string>$(PRODUCT_MODULE_NAME).MachineDocument</string> | ||||||
| 			<key>LSHandlerRank</key> |  | ||||||
| 			<string>Owner</string> |  | ||||||
| 		</dict> | 		</dict> | ||||||
| 		<dict> | 		<dict> | ||||||
| 			<key>CFBundleTypeExtensions</key> | 			<key>CFBundleTypeExtensions</key> | ||||||
| 			<array> | 			<array> | ||||||
| 				<string>tzx</string> | 				<string>tzx</string> | ||||||
| 			</array> | 			</array> | ||||||
| 			<key>CFBundleTypeOSTypes</key> |  | ||||||
| 			<array> |  | ||||||
| 				<string>????</string> |  | ||||||
| 			</array> |  | ||||||
| 			<key>CFBundleTypeIconFile</key> | 			<key>CFBundleTypeIconFile</key> | ||||||
| 			<string>cassette.png</string> | 			<string>cassette.png</string> | ||||||
| 			<key>CFBundleTypeName</key> | 			<key>CFBundleTypeName</key> | ||||||
| 			<string>Tape Image</string> | 			<string>Tape Image</string> | ||||||
|  | 			<key>CFBundleTypeOSTypes</key> | ||||||
|  | 			<array> | ||||||
|  | 				<string>????</string> | ||||||
|  | 			</array> | ||||||
| 			<key>CFBundleTypeRole</key> | 			<key>CFBundleTypeRole</key> | ||||||
| 			<string>Viewer</string> | 			<string>Viewer</string> | ||||||
|  | 			<key>LSHandlerRank</key> | ||||||
|  | 			<string>Owner</string> | ||||||
| 			<key>LSTypeIsPackage</key> | 			<key>LSTypeIsPackage</key> | ||||||
| 			<false/> | 			<false/> | ||||||
| 			<key>NSDocumentClass</key> | 			<key>NSDocumentClass</key> | ||||||
| 			<string>$(PRODUCT_MODULE_NAME).MachineDocument</string> | 			<string>$(PRODUCT_MODULE_NAME).MachineDocument</string> | ||||||
| 			<key>LSHandlerRank</key> |  | ||||||
| 			<string>Owner</string> |  | ||||||
| 		</dict> | 		</dict> | ||||||
| 		<dict> | 		<dict> | ||||||
| 			<key>CFBundleTypeExtensions</key> | 			<key>CFBundleTypeExtensions</key> | ||||||
| 			<array> | 			<array> | ||||||
| 				<string>cdt</string> | 				<string>cdt</string> | ||||||
| 			</array> | 			</array> | ||||||
| 			<key>CFBundleTypeOSTypes</key> |  | ||||||
| 			<array> |  | ||||||
| 				<string>????</string> |  | ||||||
| 			</array> |  | ||||||
| 			<key>CFBundleTypeIconFile</key> | 			<key>CFBundleTypeIconFile</key> | ||||||
| 			<string>cassette.png</string> | 			<string>cassette.png</string> | ||||||
| 			<key>CFBundleTypeName</key> | 			<key>CFBundleTypeName</key> | ||||||
| 			<string>Amstrad CPC Tape Image</string> | 			<string>Amstrad CPC Tape Image</string> | ||||||
|  | 			<key>CFBundleTypeOSTypes</key> | ||||||
|  | 			<array> | ||||||
|  | 				<string>????</string> | ||||||
|  | 			</array> | ||||||
| 			<key>CFBundleTypeRole</key> | 			<key>CFBundleTypeRole</key> | ||||||
| 			<string>Viewer</string> | 			<string>Viewer</string> | ||||||
|  | 			<key>LSHandlerRank</key> | ||||||
|  | 			<string>Owner</string> | ||||||
| 			<key>LSTypeIsPackage</key> | 			<key>LSTypeIsPackage</key> | ||||||
| 			<false/> | 			<false/> | ||||||
| 			<key>NSDocumentClass</key> | 			<key>NSDocumentClass</key> | ||||||
| 			<string>$(PRODUCT_MODULE_NAME).MachineDocument</string> | 			<string>$(PRODUCT_MODULE_NAME).MachineDocument</string> | ||||||
| 			<key>LSHandlerRank</key> |  | ||||||
| 			<string>Owner</string> |  | ||||||
| 		</dict> | 		</dict> | ||||||
| 		<dict> | 		<dict> | ||||||
| 			<key>CFBundleTypeExtensions</key> | 			<key>CFBundleTypeExtensions</key> | ||||||
| 			<array> | 			<array> | ||||||
| 				<string>hfe</string> | 				<string>hfe</string> | ||||||
| 			</array> | 			</array> | ||||||
| 			<key>CFBundleTypeOSTypes</key> |  | ||||||
| 			<array> |  | ||||||
| 				<string>????</string> |  | ||||||
| 			</array> |  | ||||||
| 			<key>CFBundleTypeIconFile</key> | 			<key>CFBundleTypeIconFile</key> | ||||||
| 			<string>floppy35.png</string> | 			<string>floppy35.png</string> | ||||||
| 			<key>CFBundleTypeName</key> | 			<key>CFBundleTypeName</key> | ||||||
| 			<string>HxC Disk Image</string> | 			<string>HxC Disk Image</string> | ||||||
|  | 			<key>CFBundleTypeOSTypes</key> | ||||||
|  | 			<array> | ||||||
|  | 				<string>????</string> | ||||||
|  | 			</array> | ||||||
| 			<key>CFBundleTypeRole</key> | 			<key>CFBundleTypeRole</key> | ||||||
| 			<string>Viewer</string> | 			<string>Viewer</string> | ||||||
|  | 			<key>LSHandlerRank</key> | ||||||
|  | 			<string>Owner</string> | ||||||
| 			<key>LSTypeIsPackage</key> | 			<key>LSTypeIsPackage</key> | ||||||
| 			<false/> | 			<false/> | ||||||
| 			<key>NSDocumentClass</key> | 			<key>NSDocumentClass</key> | ||||||
| 			<string>$(PRODUCT_MODULE_NAME).MachineDocument</string> | 			<string>$(PRODUCT_MODULE_NAME).MachineDocument</string> | ||||||
| 			<key>LSHandlerRank</key> |  | ||||||
| 			<string>Owner</string> |  | ||||||
| 		</dict> | 		</dict> | ||||||
| 		<dict> | 		<dict> | ||||||
| 			<key>CFBundleTypeExtensions</key> | 			<key>CFBundleTypeExtensions</key> | ||||||
| 			<array> | 			<array> | ||||||
| 				<string>cas</string> | 				<string>cas</string> | ||||||
| 			</array> | 			</array> | ||||||
| 			<key>CFBundleTypeOSTypes</key> |  | ||||||
| 			<array> |  | ||||||
| 				<string>????</string> |  | ||||||
| 			</array> |  | ||||||
| 			<key>CFBundleTypeIconFile</key> | 			<key>CFBundleTypeIconFile</key> | ||||||
| 			<string>cassette.png</string> | 			<string>cassette.png</string> | ||||||
| 			<key>CFBundleTypeName</key> | 			<key>CFBundleTypeName</key> | ||||||
| 			<string>MSX Tape Image</string> | 			<string>MSX Tape Image</string> | ||||||
|  | 			<key>CFBundleTypeOSTypes</key> | ||||||
|  | 			<array> | ||||||
|  | 				<string>????</string> | ||||||
|  | 			</array> | ||||||
| 			<key>CFBundleTypeRole</key> | 			<key>CFBundleTypeRole</key> | ||||||
| 			<string>Viewer</string> | 			<string>Viewer</string> | ||||||
|  | 			<key>LSHandlerRank</key> | ||||||
|  | 			<string>Owner</string> | ||||||
| 			<key>LSTypeIsPackage</key> | 			<key>LSTypeIsPackage</key> | ||||||
| 			<false/> | 			<false/> | ||||||
| 			<key>NSDocumentClass</key> | 			<key>NSDocumentClass</key> | ||||||
| 			<string>$(PRODUCT_MODULE_NAME).MachineDocument</string> | 			<string>$(PRODUCT_MODULE_NAME).MachineDocument</string> | ||||||
| 			<key>LSHandlerRank</key> |  | ||||||
| 			<string>Owner</string> |  | ||||||
| 		</dict> | 		</dict> | ||||||
| 		<dict> | 		<dict> | ||||||
| 			<key>CFBundleTypeExtensions</key> | 			<key>CFBundleTypeExtensions</key> | ||||||
| 			<array> | 			<array> | ||||||
| 				<string>dmk</string> | 				<string>dmk</string> | ||||||
| 			</array> | 			</array> | ||||||
| 			<key>CFBundleTypeOSTypes</key> |  | ||||||
| 			<array> |  | ||||||
| 				<string>????</string> |  | ||||||
| 			</array> |  | ||||||
| 			<key>CFBundleTypeIconFile</key> | 			<key>CFBundleTypeIconFile</key> | ||||||
| 			<string>floppy35.png</string> | 			<string>floppy35.png</string> | ||||||
| 			<key>CFBundleTypeName</key> | 			<key>CFBundleTypeName</key> | ||||||
| 			<string>Disk Image</string> | 			<string>Disk Image</string> | ||||||
|  | 			<key>CFBundleTypeOSTypes</key> | ||||||
|  | 			<array> | ||||||
|  | 				<string>????</string> | ||||||
|  | 			</array> | ||||||
| 			<key>CFBundleTypeRole</key> | 			<key>CFBundleTypeRole</key> | ||||||
| 			<string>Viewer</string> | 			<string>Viewer</string> | ||||||
| 			<key>LSTypeIsPackage</key> |  | ||||||
| 			<false/> |  | ||||||
| 			<key>LSHandlerRank</key> | 			<key>LSHandlerRank</key> | ||||||
| 			<string>Owner</string> | 			<string>Owner</string> | ||||||
|  | 			<key>LSTypeIsPackage</key> | ||||||
|  | 			<false/> | ||||||
| 		</dict> | 		</dict> | ||||||
| 		<dict> | 		<dict> | ||||||
| 			<key>CFBundleTypeExtensions</key> | 			<key>CFBundleTypeExtensions</key> | ||||||
| 			<array> | 			<array> | ||||||
| 				<string>tsx</string> | 				<string>tsx</string> | ||||||
| 			</array> | 			</array> | ||||||
| 			<key>CFBundleTypeOSTypes</key> |  | ||||||
| 			<array> |  | ||||||
| 				<string>????</string> |  | ||||||
| 			</array> |  | ||||||
| 			<key>CFBundleTypeIconFile</key> | 			<key>CFBundleTypeIconFile</key> | ||||||
| 			<string>cassette.png</string> | 			<string>cassette.png</string> | ||||||
| 			<key>CFBundleTypeName</key> | 			<key>CFBundleTypeName</key> | ||||||
| 			<string>MSX Tape Image</string> | 			<string>MSX Tape Image</string> | ||||||
|  | 			<key>CFBundleTypeOSTypes</key> | ||||||
|  | 			<array> | ||||||
|  | 				<string>????</string> | ||||||
|  | 			</array> | ||||||
| 			<key>CFBundleTypeRole</key> | 			<key>CFBundleTypeRole</key> | ||||||
| 			<string>Viewer</string> | 			<string>Viewer</string> | ||||||
|  | 			<key>LSHandlerRank</key> | ||||||
|  | 			<string>Owner</string> | ||||||
| 			<key>LSTypeIsPackage</key> | 			<key>LSTypeIsPackage</key> | ||||||
| 			<false/> | 			<false/> | ||||||
| 			<key>NSDocumentClass</key> | 			<key>NSDocumentClass</key> | ||||||
| 			<string>$(PRODUCT_MODULE_NAME).MachineDocument</string> | 			<string>$(PRODUCT_MODULE_NAME).MachineDocument</string> | ||||||
| 			<key>LSHandlerRank</key> |  | ||||||
| 			<string>Owner</string> |  | ||||||
| 		</dict> | 		</dict> | ||||||
| 		<dict> | 		<dict> | ||||||
| 			<key>CFBundleTypeExtensions</key> | 			<key>CFBundleTypeExtensions</key> | ||||||
| 			<array> | 			<array> | ||||||
| 				<string>col</string> | 				<string>col</string> | ||||||
| 			</array> | 			</array> | ||||||
| 			<key>CFBundleTypeOSTypes</key> |  | ||||||
| 			<array> |  | ||||||
| 				<string>????</string> |  | ||||||
| 			</array> |  | ||||||
| 			<key>CFBundleTypeIconFile</key> | 			<key>CFBundleTypeIconFile</key> | ||||||
| 			<string>cartridge.png</string> | 			<string>cartridge.png</string> | ||||||
| 			<key>CFBundleTypeName</key> | 			<key>CFBundleTypeName</key> | ||||||
| 			<string>ColecoVision Cartridge</string> | 			<string>ColecoVision Cartridge</string> | ||||||
|  | 			<key>CFBundleTypeOSTypes</key> | ||||||
|  | 			<array> | ||||||
|  | 				<string>????</string> | ||||||
|  | 			</array> | ||||||
| 			<key>CFBundleTypeRole</key> | 			<key>CFBundleTypeRole</key> | ||||||
| 			<string>Viewer</string> | 			<string>Viewer</string> | ||||||
|  | 			<key>LSHandlerRank</key> | ||||||
|  | 			<string>Owner</string> | ||||||
| 			<key>LSTypeIsPackage</key> | 			<key>LSTypeIsPackage</key> | ||||||
| 			<false/> | 			<false/> | ||||||
| 			<key>NSDocumentClass</key> | 			<key>NSDocumentClass</key> | ||||||
| 			<string>$(PRODUCT_MODULE_NAME).MachineDocument</string> | 			<string>$(PRODUCT_MODULE_NAME).MachineDocument</string> | ||||||
| 			<key>LSHandlerRank</key> |  | ||||||
| 			<string>Owner</string> |  | ||||||
| 		</dict> | 		</dict> | ||||||
| 		<dict> | 		<dict> | ||||||
| 			<key>CFBundleTypeExtensions</key> | 			<key>CFBundleTypeExtensions</key> | ||||||
| 			<array> | 			<array> | ||||||
| 				<string>sms</string> | 				<string>sms</string> | ||||||
| 			</array> | 			</array> | ||||||
| 			<key>CFBundleTypeOSTypes</key> |  | ||||||
| 			<array> |  | ||||||
| 				<string>????</string> |  | ||||||
| 			</array> |  | ||||||
| 			<key>CFBundleTypeIconFile</key> | 			<key>CFBundleTypeIconFile</key> | ||||||
| 			<string>cartridge.png</string> | 			<string>cartridge.png</string> | ||||||
| 			<key>CFBundleTypeName</key> | 			<key>CFBundleTypeName</key> | ||||||
| 			<string>Master System Cartridge</string> | 			<string>Master System Cartridge</string> | ||||||
|  | 			<key>CFBundleTypeOSTypes</key> | ||||||
|  | 			<array> | ||||||
|  | 				<string>????</string> | ||||||
|  | 			</array> | ||||||
| 			<key>CFBundleTypeRole</key> | 			<key>CFBundleTypeRole</key> | ||||||
| 			<string>Viewer</string> | 			<string>Viewer</string> | ||||||
|  | 			<key>LSHandlerRank</key> | ||||||
|  | 			<string>Owner</string> | ||||||
| 			<key>LSTypeIsPackage</key> | 			<key>LSTypeIsPackage</key> | ||||||
| 			<false/> | 			<false/> | ||||||
| 			<key>NSDocumentClass</key> | 			<key>NSDocumentClass</key> | ||||||
| 			<string>$(PRODUCT_MODULE_NAME).MachineDocument</string> | 			<string>$(PRODUCT_MODULE_NAME).MachineDocument</string> | ||||||
| 			<key>LSHandlerRank</key> |  | ||||||
| 			<string>Owner</string> |  | ||||||
| 		</dict> | 		</dict> | ||||||
| 		<dict> | 		<dict> | ||||||
| 			<key>CFBundleTypeExtensions</key> | 			<key>CFBundleTypeExtensions</key> | ||||||
| 			<array> | 			<array> | ||||||
| 				<string>sg</string> | 				<string>sg</string> | ||||||
| 			</array> | 			</array> | ||||||
| 			<key>CFBundleTypeOSTypes</key> |  | ||||||
| 			<array> |  | ||||||
| 				<string>????</string> |  | ||||||
| 			</array> |  | ||||||
| 			<key>CFBundleTypeIconFile</key> | 			<key>CFBundleTypeIconFile</key> | ||||||
| 			<string>cartridge.png</string> | 			<string>cartridge.png</string> | ||||||
| 			<key>CFBundleTypeName</key> | 			<key>CFBundleTypeName</key> | ||||||
| 			<string>SG1000 Cartridge</string> | 			<string>SG1000 Cartridge</string> | ||||||
|  | 			<key>CFBundleTypeOSTypes</key> | ||||||
|  | 			<array> | ||||||
|  | 				<string>????</string> | ||||||
|  | 			</array> | ||||||
| 			<key>CFBundleTypeRole</key> | 			<key>CFBundleTypeRole</key> | ||||||
| 			<string>Viewer</string> | 			<string>Viewer</string> | ||||||
|  | 			<key>LSHandlerRank</key> | ||||||
|  | 			<string>Owner</string> | ||||||
| 			<key>LSTypeIsPackage</key> | 			<key>LSTypeIsPackage</key> | ||||||
| 			<false/> | 			<false/> | ||||||
| 			<key>NSDocumentClass</key> | 			<key>NSDocumentClass</key> | ||||||
| 			<string>$(PRODUCT_MODULE_NAME).MachineDocument</string> | 			<string>$(PRODUCT_MODULE_NAME).MachineDocument</string> | ||||||
| 			<key>LSHandlerRank</key> |  | ||||||
| 			<string>Owner</string> |  | ||||||
| 		</dict> | 		</dict> | ||||||
| 		<dict> | 		<dict> | ||||||
| 			<key>CFBundleTypeExtensions</key> | 			<key>CFBundleTypeExtensions</key> | ||||||
| @@ -483,22 +483,48 @@ | |||||||
| 				<string>do</string> | 				<string>do</string> | ||||||
| 				<string>po</string> | 				<string>po</string> | ||||||
| 			</array> | 			</array> | ||||||
| 			<key>CFBundleTypeOSTypes</key> |  | ||||||
| 			<array> |  | ||||||
| 				<string>????</string> |  | ||||||
| 			</array> |  | ||||||
| 			<key>CFBundleTypeIconFile</key> | 			<key>CFBundleTypeIconFile</key> | ||||||
| 			<string>floppy525.png</string> | 			<string>floppy525.png</string> | ||||||
| 			<key>CFBundleTypeName</key> | 			<key>CFBundleTypeName</key> | ||||||
| 			<string>Apple II Disk Image</string> | 			<string>Apple II Disk Image</string> | ||||||
|  | 			<key>CFBundleTypeOSTypes</key> | ||||||
|  | 			<array> | ||||||
|  | 				<string>????</string> | ||||||
|  | 			</array> | ||||||
| 			<key>CFBundleTypeRole</key> | 			<key>CFBundleTypeRole</key> | ||||||
| 			<string>Viewer</string> | 			<string>Viewer</string> | ||||||
|  | 			<key>LSHandlerRank</key> | ||||||
|  | 			<string>Owner</string> | ||||||
| 			<key>LSTypeIsPackage</key> | 			<key>LSTypeIsPackage</key> | ||||||
| 			<false/> | 			<false/> | ||||||
| 			<key>NSDocumentClass</key> | 			<key>NSDocumentClass</key> | ||||||
| 			<string>$(PRODUCT_MODULE_NAME).MachineDocument</string> | 			<string>$(PRODUCT_MODULE_NAME).MachineDocument</string> | ||||||
| 			<key>LSHandlerRank</key> | 		</dict> | ||||||
| 			<string>Owner</string> | 		<dict> | ||||||
|  | 			<key>CFBundleTypeExtensions</key> | ||||||
|  | 			<array> | ||||||
|  | 				<string>img</string> | ||||||
|  | 				<string>hfv</string> | ||||||
|  | 				<string>image</string> | ||||||
|  | 			</array> | ||||||
|  | 			<key>CFBundleTypeIconFile</key> | ||||||
|  | 			<string>floppy35</string> | ||||||
|  | 			<key>CFBundleTypeMIMETypes</key> | ||||||
|  | 			<array> | ||||||
|  | 				<string>application/x-apple-diskimage</string> | ||||||
|  | 			</array> | ||||||
|  | 			<key>CFBundleTypeName</key> | ||||||
|  | 			<string>DiskCopy 4.2 Disk Image</string> | ||||||
|  | 			<key>CFBundleTypeOSTypes</key> | ||||||
|  | 			<array> | ||||||
|  | 				<string>????</string> | ||||||
|  | 			</array> | ||||||
|  | 			<key>CFBundleTypeRole</key> | ||||||
|  | 			<string>Viewer</string> | ||||||
|  | 			<key>LSTypeIsPackage</key> | ||||||
|  | 			<integer>0</integer> | ||||||
|  | 			<key>NSDocumentClass</key> | ||||||
|  | 			<string>$(PRODUCT_MODULE_NAME).MachineDocument</string> | ||||||
| 		</dict> | 		</dict> | ||||||
| 	</array> | 	</array> | ||||||
| 	<key>CFBundleExecutable</key> | 	<key>CFBundleExecutable</key> | ||||||
| @@ -524,7 +550,7 @@ | |||||||
| 	<key>LSMinimumSystemVersion</key> | 	<key>LSMinimumSystemVersion</key> | ||||||
| 	<string>$(MACOSX_DEPLOYMENT_TARGET)</string> | 	<string>$(MACOSX_DEPLOYMENT_TARGET)</string> | ||||||
| 	<key>NSHumanReadableCopyright</key> | 	<key>NSHumanReadableCopyright</key> | ||||||
| 	<string>Copyright 2018 Thomas Harte. All rights reserved.</string> | 	<string>Copyright 2019 Thomas Harte. All rights reserved.</string> | ||||||
| 	<key>NSMainNibFile</key> | 	<key>NSMainNibFile</key> | ||||||
| 	<string>MainMenu</string> | 	<string>MainMenu</string> | ||||||
| 	<key>NSPrincipalClass</key> | 	<key>NSPrincipalClass</key> | ||||||
|   | |||||||
| @@ -33,6 +33,14 @@ typedef NS_ENUM(NSInteger, CSMachineKeyboardInputMode) { | |||||||
| 	CSMachineKeyboardInputModeJoystick | 	CSMachineKeyboardInputModeJoystick | ||||||
| }; | }; | ||||||
|  |  | ||||||
|  | @interface CSMissingROM: NSObject | ||||||
|  | @property (nonatomic, readonly, nonnull) NSString *machineName; | ||||||
|  | @property (nonatomic, readonly, nonnull) NSString *fileName; | ||||||
|  | @property (nonatomic, readonly, nullable) NSString *descriptiveName; | ||||||
|  | @property (nonatomic, readonly) NSUInteger size; | ||||||
|  | @property (nonatomic, readonly, nonnull) NSArray<NSNumber *> *crc32s; | ||||||
|  | @end | ||||||
|  |  | ||||||
| // Deliberately low; to ensure CSMachine has been declared as an @class already. | // Deliberately low; to ensure CSMachine has been declared as an @class already. | ||||||
| #import "CSAtari2600.h" | #import "CSAtari2600.h" | ||||||
| #import "CSZX8081.h" | #import "CSZX8081.h" | ||||||
| @@ -45,8 +53,10 @@ typedef NS_ENUM(NSInteger, CSMachineKeyboardInputMode) { | |||||||
| 	Initialises an instance of CSMachine. | 	Initialises an instance of CSMachine. | ||||||
|  |  | ||||||
| 	@param result The CSStaticAnalyser result that describes the machine needed. | 	@param result The CSStaticAnalyser result that describes the machine needed. | ||||||
|  | 	@param missingROMs An array that is filled with a list of ROMs that the machine requested but which | ||||||
|  | 		were not found; populated only if this `init` has failed. | ||||||
| */ | */ | ||||||
| - (nullable instancetype)initWithAnalyser:(nonnull CSStaticAnalyser *)result NS_DESIGNATED_INITIALIZER; | - (nullable instancetype)initWithAnalyser:(nonnull CSStaticAnalyser *)result missingROMs:(nullable inout NSMutableArray<CSMissingROM *> *)missingROMs NS_DESIGNATED_INITIALIZER; | ||||||
|  |  | ||||||
| - (void)runForInterval:(NSTimeInterval)interval; | - (void)runForInterval:(NSTimeInterval)interval; | ||||||
|  |  | ||||||
| @@ -61,6 +71,9 @@ typedef NS_ENUM(NSInteger, CSMachineKeyboardInputMode) { | |||||||
| - (void)setKey:(uint16_t)key characters:(nullable NSString *)characters isPressed:(BOOL)isPressed; | - (void)setKey:(uint16_t)key characters:(nullable NSString *)characters isPressed:(BOOL)isPressed; | ||||||
| - (void)clearAllKeys; | - (void)clearAllKeys; | ||||||
|  |  | ||||||
|  | - (void)setMouseButton:(int)button isPressed:(BOOL)isPressed; | ||||||
|  | - (void)addMouseMotionX:(CGFloat)deltaX y:(CGFloat)deltaY; | ||||||
|  |  | ||||||
| @property (nonatomic, strong, nullable) CSAudioQueue *audioQueue; | @property (nonatomic, strong, nullable) CSAudioQueue *audioQueue; | ||||||
| @property (nonatomic, readonly, nonnull) CSOpenGLView *view; | @property (nonatomic, readonly, nonnull) CSOpenGLView *view; | ||||||
| @property (nonatomic, weak, nullable) id<CSMachineDelegate> delegate; | @property (nonatomic, weak, nullable) id<CSMachineDelegate> delegate; | ||||||
| @@ -81,6 +94,7 @@ typedef NS_ENUM(NSInteger, CSMachineKeyboardInputMode) { | |||||||
| // Input control. | // Input control. | ||||||
| @property (nonatomic, readonly) BOOL hasExclusiveKeyboard; | @property (nonatomic, readonly) BOOL hasExclusiveKeyboard; | ||||||
| @property (nonatomic, readonly) BOOL hasJoystick; | @property (nonatomic, readonly) BOOL hasJoystick; | ||||||
|  | @property (nonatomic, readonly) BOOL hasMouse; | ||||||
| @property (nonatomic, assign) CSMachineKeyboardInputMode inputMode; | @property (nonatomic, assign) CSMachineKeyboardInputMode inputMode; | ||||||
| @property (nonatomic, nullable) CSJoystickManager *joystickManager; | @property (nonatomic, nullable) CSJoystickManager *joystickManager; | ||||||
|  |  | ||||||
|   | |||||||
| @@ -74,6 +74,68 @@ struct ActivityObserver: public Activity::Observer { | |||||||
| 	__unsafe_unretained CSMachine *machine; | 	__unsafe_unretained CSMachine *machine; | ||||||
| }; | }; | ||||||
|  |  | ||||||
|  | @interface CSMissingROM (Mutability) | ||||||
|  | @property (nonatomic, nonnull, copy) NSString *machineName; | ||||||
|  | @property (nonatomic, nonnull, copy) NSString *fileName; | ||||||
|  | @property (nonatomic, nullable, copy) NSString *descriptiveName; | ||||||
|  | @property (nonatomic, readwrite) NSUInteger size; | ||||||
|  | @property (nonatomic, copy) NSArray<NSNumber *> *crc32s; | ||||||
|  | @end | ||||||
|  |  | ||||||
|  | @implementation CSMissingROM { | ||||||
|  | 	NSString *_machineName; | ||||||
|  | 	NSString *_fileName; | ||||||
|  | 	NSString *_descriptiveName; | ||||||
|  | 	NSUInteger _size; | ||||||
|  | 	NSArray<NSNumber *> *_crc32s; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | - (NSString *)machineName { | ||||||
|  | 	return _machineName; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | - (void)setMachineName:(NSString *)machineName { | ||||||
|  | 	_machineName = [machineName copy]; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | - (NSString *)fileName { | ||||||
|  | 	return _fileName; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | - (void)setFileName:(NSString *)fileName { | ||||||
|  | 	_fileName = [fileName copy]; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | - (NSString *)descriptiveName { | ||||||
|  | 	return _descriptiveName; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | - (void)setDescriptiveName:(NSString *)descriptiveName { | ||||||
|  | 	_descriptiveName = [descriptiveName copy]; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | - (NSUInteger)size { | ||||||
|  | 	return _size; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | - (void)setSize:(NSUInteger)size { | ||||||
|  | 	_size = size; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | - (NSArray<NSNumber *> *)crc32s { | ||||||
|  | 	return _crc32s; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | - (void)setCrc32s:(NSArray<NSNumber *> *)crc32s { | ||||||
|  | 	_crc32s = [crc32s copy]; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | - (NSString *)description { | ||||||
|  | 	return [NSString stringWithFormat:@"%@/%@, %@ bytes, CRCs: %@", _fileName, _descriptiveName, @(_size), _crc32s]; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @end | ||||||
|  |  | ||||||
| @implementation CSMachine { | @implementation CSMachine { | ||||||
| 	SpeakerDelegate _speakerDelegate; | 	SpeakerDelegate _speakerDelegate; | ||||||
| 	ActivityObserver _activityObserver; | 	ActivityObserver _activityObserver; | ||||||
| @@ -90,14 +152,37 @@ struct ActivityObserver: public Activity::Observer { | |||||||
| 	std::unique_ptr<Outputs::Display::OpenGL::ScanTarget> _scanTarget; | 	std::unique_ptr<Outputs::Display::OpenGL::ScanTarget> _scanTarget; | ||||||
| } | } | ||||||
|  |  | ||||||
| - (instancetype)initWithAnalyser:(CSStaticAnalyser *)result { | - (instancetype)initWithAnalyser:(CSStaticAnalyser *)result missingROMs:(inout NSMutableArray<CSMissingROM *> *)missingROMs { | ||||||
| 	self = [super init]; | 	self = [super init]; | ||||||
| 	if(self) { | 	if(self) { | ||||||
| 		_analyser = result; | 		_analyser = result; | ||||||
|  |  | ||||||
| 		Machine::Error error; | 		Machine::Error error; | ||||||
| 		_machine.reset(Machine::MachineForTargets(_analyser.targets, CSROMFetcher(), error)); | 		std::vector<ROMMachine::ROM> missing_roms; | ||||||
| 		if(!_machine) return nil; | 		_machine.reset(Machine::MachineForTargets(_analyser.targets, CSROMFetcher(&missing_roms), error)); | ||||||
|  | 		if(!_machine) { | ||||||
|  | 			for(const auto &missing_rom : missing_roms) { | ||||||
|  | 				CSMissingROM *rom = [[CSMissingROM alloc] init]; | ||||||
|  |  | ||||||
|  | 				// Copy/convert the primitive fields. | ||||||
|  | 				rom.machineName = [NSString stringWithUTF8String:missing_rom.machine_name.c_str()]; | ||||||
|  | 				rom.fileName = [NSString stringWithUTF8String:missing_rom.file_name.c_str()]; | ||||||
|  | 				rom.descriptiveName = missing_rom.descriptive_name.empty() ? nil : [NSString stringWithUTF8String:missing_rom.descriptive_name.c_str()]; | ||||||
|  | 				rom.size = missing_rom.size; | ||||||
|  |  | ||||||
|  | 				// Convert the CRC list. | ||||||
|  | 				NSMutableArray<NSNumber *> *crc32s = [[NSMutableArray alloc] init]; | ||||||
|  | 				for(const auto &crc : missing_rom.crc32s) { | ||||||
|  | 					[crc32s addObject:@(crc)]; | ||||||
|  | 				} | ||||||
|  | 				rom.crc32s = [crc32s copy]; | ||||||
|  |  | ||||||
|  | 				// Add to the missing list. | ||||||
|  | 				[missingROMs addObject:rom]; | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			return nil; | ||||||
|  | 		} | ||||||
|  |  | ||||||
| 		_inputMode = | 		_inputMode = | ||||||
| 			(_machine->keyboard_machine() && _machine->keyboard_machine()->get_keyboard().is_exclusive()) | 			(_machine->keyboard_machine() && _machine->keyboard_machine()->get_keyboard().is_exclusive()) | ||||||
| @@ -410,14 +495,14 @@ struct ActivityObserver: public Activity::Observer { | |||||||
| } | } | ||||||
|  |  | ||||||
| - (void)clearAllKeys { | - (void)clearAllKeys { | ||||||
| 	auto keyboard_machine = _machine->keyboard_machine(); | 	const auto keyboard_machine = _machine->keyboard_machine(); | ||||||
| 	if(keyboard_machine) { | 	if(keyboard_machine) { | ||||||
| 		@synchronized(self) { | 		@synchronized(self) { | ||||||
| 			keyboard_machine->get_keyboard().reset_all_keys(); | 			keyboard_machine->get_keyboard().reset_all_keys(); | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	auto joystick_machine = _machine->joystick_machine(); | 	const auto joystick_machine = _machine->joystick_machine(); | ||||||
| 	if(joystick_machine) { | 	if(joystick_machine) { | ||||||
| 		@synchronized(self) { | 		@synchronized(self) { | ||||||
| 			for(auto &joystick : joystick_machine->get_joysticks()) { | 			for(auto &joystick : joystick_machine->get_joysticks()) { | ||||||
| @@ -425,6 +510,31 @@ struct ActivityObserver: public Activity::Observer { | |||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | 	const auto mouse_machine = _machine->mouse_machine(); | ||||||
|  | 	if(mouse_machine) { | ||||||
|  | 		@synchronized(self) { | ||||||
|  | 			mouse_machine->get_mouse().reset_all_buttons(); | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | - (void)setMouseButton:(int)button isPressed:(BOOL)isPressed { | ||||||
|  | 	auto mouse_machine = _machine->mouse_machine(); | ||||||
|  | 	if(mouse_machine) { | ||||||
|  | 		@synchronized(self) { | ||||||
|  | 			mouse_machine->get_mouse().set_button_pressed(button % mouse_machine->get_mouse().get_number_of_buttons(), isPressed); | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | - (void)addMouseMotionX:(CGFloat)deltaX y:(CGFloat)deltaY { | ||||||
|  | 	auto mouse_machine = _machine->mouse_machine(); | ||||||
|  | 	if(mouse_machine) { | ||||||
|  | 		@synchronized(self) { | ||||||
|  | 			mouse_machine->get_mouse().move(int(deltaX), int(deltaY)); | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| #pragma mark - Options | #pragma mark - Options | ||||||
| @@ -540,6 +650,10 @@ struct ActivityObserver: public Activity::Observer { | |||||||
| 	return !!_machine->joystick_machine(); | 	return !!_machine->joystick_machine(); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | - (BOOL)hasMouse { | ||||||
|  | 	return !!_machine->mouse_machine(); | ||||||
|  | } | ||||||
|  |  | ||||||
| - (BOOL)hasExclusiveKeyboard { | - (BOOL)hasExclusiveKeyboard { | ||||||
| 	return !!_machine->keyboard_machine() && _machine->keyboard_machine()->get_keyboard().is_exclusive(); | 	return !!_machine->keyboard_machine() && _machine->keyboard_machine()->get_keyboard().is_exclusive(); | ||||||
| } | } | ||||||
|   | |||||||
| @@ -8,4 +8,4 @@ | |||||||
|  |  | ||||||
| #include "ROMMachine.hpp" | #include "ROMMachine.hpp" | ||||||
|  |  | ||||||
| ROMMachine::ROMFetcher CSROMFetcher(); | ROMMachine::ROMFetcher CSROMFetcher(std::vector<ROMMachine::ROM> *missing_roms = nullptr); | ||||||
|   | |||||||
| @@ -14,15 +14,39 @@ | |||||||
|  |  | ||||||
| #include <string> | #include <string> | ||||||
|  |  | ||||||
| ROMMachine::ROMFetcher CSROMFetcher() { | ROMMachine::ROMFetcher CSROMFetcher(std::vector<ROMMachine::ROM> *missing_roms) { | ||||||
| 	return [] (const std::string &machine, const std::vector<std::string> &names) -> std::vector<std::unique_ptr<std::vector<std::uint8_t>>> { | 	return [missing_roms] (const std::vector<ROMMachine::ROM> &roms) -> std::vector<std::unique_ptr<std::vector<std::uint8_t>>> { | ||||||
| 		NSString *subDirectory = [@"ROMImages/" stringByAppendingString:[NSString stringWithUTF8String:machine.c_str()]]; | 		NSArray<NSURL *> *const supportURLs = [[NSFileManager defaultManager] URLsForDirectory:NSApplicationSupportDirectory inDomains:NSUserDomainMask]; | ||||||
| 		std::vector<std::unique_ptr<std::vector<std::uint8_t>>> results; |  | ||||||
| 		for(const auto &name: names) { |  | ||||||
| 			NSData *fileData = [[NSBundle mainBundle] dataForResource:[NSString stringWithUTF8String:name.c_str()] withExtension:nil subdirectory:subDirectory]; |  | ||||||
|  |  | ||||||
| 			if(!fileData) | 		std::vector<std::unique_ptr<std::vector<std::uint8_t>>> results; | ||||||
|  | 		for(const auto &rom: roms) { | ||||||
|  | 			NSData *fileData; | ||||||
|  | 			NSString *const subdirectory = [@"ROMImages/" stringByAppendingString:[NSString stringWithUTF8String:rom.machine_name.c_str()]]; | ||||||
|  |  | ||||||
|  | 			// Check for this file first within the application support directories. | ||||||
|  | 			for(NSURL *supportURL in supportURLs) { | ||||||
|  | 				NSURL *const fullURL = [[supportURL URLByAppendingPathComponent:subdirectory] | ||||||
|  | 							URLByAppendingPathComponent:[NSString stringWithUTF8String:rom.file_name.c_str()]]; | ||||||
|  | 				fileData = [NSData dataWithContentsOfURL:fullURL]; | ||||||
|  | 				if(fileData) break; | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			// Failing that, check inside the application bundle. | ||||||
|  | 			if(!fileData) { | ||||||
|  | 				fileData = [[NSBundle mainBundle] | ||||||
|  | 					dataForResource:[NSString stringWithUTF8String:rom.file_name.c_str()] | ||||||
|  | 					withExtension:nil | ||||||
|  | 					subdirectory:subdirectory]; | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			// Store an appropriate result, accumulating a list of the missing if requested. | ||||||
|  | 			if(!fileData) { | ||||||
| 				results.emplace_back(nullptr); | 				results.emplace_back(nullptr); | ||||||
|  |  | ||||||
|  | 				if(missing_roms) { | ||||||
|  | 					missing_roms->push_back(rom); | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
| 			else { | 			else { | ||||||
| 				std::unique_ptr<std::vector<std::uint8_t>> data(new std::vector<std::uint8_t>); | 				std::unique_ptr<std::vector<std::uint8_t>> data(new std::vector<std::uint8_t>); | ||||||
| 				*data = fileData.stdVector8; | 				*data = fileData.stdVector8; | ||||||
|   | |||||||
							
								
								
									
										17
									
								
								OSBindings/Mac/Clock Signal/Machine/NSData+CRC32.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								OSBindings/Mac/Clock Signal/Machine/NSData+CRC32.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,17 @@ | |||||||
|  | // | ||||||
|  | //  NSData+CRC32.m | ||||||
|  | //  Clock Signal | ||||||
|  | // | ||||||
|  | //  Created by Thomas Harte on 22/07/2019. | ||||||
|  | //  Copyright 2019 Thomas Harte. All rights reserved. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | #import <Foundation/Foundation.h> | ||||||
|  |  | ||||||
|  | #include <stdint.h> | ||||||
|  |  | ||||||
|  | @interface NSData (CRC32) | ||||||
|  |  | ||||||
|  | @property(nonnull, nonatomic, readonly) NSNumber *crc32; | ||||||
|  |  | ||||||
|  | @end | ||||||
							
								
								
									
										19
									
								
								OSBindings/Mac/Clock Signal/Machine/NSData+CRC32.m
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								OSBindings/Mac/Clock Signal/Machine/NSData+CRC32.m
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,19 @@ | |||||||
|  | // | ||||||
|  | //  NSData+CRC32.m | ||||||
|  | //  Clock Signal | ||||||
|  | // | ||||||
|  | //  Created by Thomas Harte on 22/07/2019. | ||||||
|  | //  Copyright 2019 Thomas Harte. All rights reserved. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | #import "NSData+CRC32.h" | ||||||
|  |  | ||||||
|  | #include <zlib.h> | ||||||
|  |  | ||||||
|  | @implementation NSData (StdVector) | ||||||
|  |  | ||||||
|  | - (NSNumber *)crc32 { | ||||||
|  |     return @(crc32(crc32(0, Z_NULL, 0), self.bytes, (uInt)self.length)); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @end | ||||||
| @@ -29,6 +29,13 @@ typedef NS_ENUM(NSInteger, CSMachineCPCModel) { | |||||||
| 	CSMachineCPCModel6128 | 	CSMachineCPCModel6128 | ||||||
| }; | }; | ||||||
|  |  | ||||||
|  | typedef NS_ENUM(NSInteger, CSMachineMacintoshModel) { | ||||||
|  | 	CSMachineMacintoshModel128k, | ||||||
|  | 	CSMachineMacintoshModel512k, | ||||||
|  | 	CSMachineMacintoshModel512ke, | ||||||
|  | 	CSMachineMacintoshModelPlus, | ||||||
|  | }; | ||||||
|  |  | ||||||
| typedef NS_ENUM(NSInteger, CSMachineOricModel) { | typedef NS_ENUM(NSInteger, CSMachineOricModel) { | ||||||
| 	CSMachineOricModelOric1, | 	CSMachineOricModelOric1, | ||||||
| 	CSMachineOricModelOricAtmos, | 	CSMachineOricModelOricAtmos, | ||||||
| @@ -69,6 +76,7 @@ typedef int Kilobytes; | |||||||
| - (instancetype)initWithZX80MemorySize:(Kilobytes)memorySize useZX81ROM:(BOOL)useZX81ROM; | - (instancetype)initWithZX80MemorySize:(Kilobytes)memorySize useZX81ROM:(BOOL)useZX81ROM; | ||||||
| - (instancetype)initWithZX81MemorySize:(Kilobytes)memorySize; | - (instancetype)initWithZX81MemorySize:(Kilobytes)memorySize; | ||||||
| - (instancetype)initWithAppleIIModel:(CSMachineAppleIIModel)model diskController:(CSMachineAppleIIDiskController)diskController; | - (instancetype)initWithAppleIIModel:(CSMachineAppleIIModel)model diskController:(CSMachineAppleIIDiskController)diskController; | ||||||
|  | - (instancetype)initWithMacintoshModel:(CSMachineMacintoshModel)model; | ||||||
|  |  | ||||||
| @property(nonatomic, readonly) NSString *optionsPanelNibName; | @property(nonatomic, readonly) NSString *optionsPanelNibName; | ||||||
| @property(nonatomic, readonly) NSString *displayName; | @property(nonatomic, readonly) NSString *displayName; | ||||||
|   | |||||||
| @@ -17,6 +17,7 @@ | |||||||
| #include "../../../../../Analyser/Static/AmstradCPC/Target.hpp" | #include "../../../../../Analyser/Static/AmstradCPC/Target.hpp" | ||||||
| #include "../../../../../Analyser/Static/AppleII/Target.hpp" | #include "../../../../../Analyser/Static/AppleII/Target.hpp" | ||||||
| #include "../../../../../Analyser/Static/Commodore/Target.hpp" | #include "../../../../../Analyser/Static/Commodore/Target.hpp" | ||||||
|  | #include "../../../../../Analyser/Static/Macintosh/Target.hpp" | ||||||
| #include "../../../../../Analyser/Static/MSX/Target.hpp" | #include "../../../../../Analyser/Static/MSX/Target.hpp" | ||||||
| #include "../../../../../Analyser/Static/Oric/Target.hpp" | #include "../../../../../Analyser/Static/Oric/Target.hpp" | ||||||
| #include "../../../../../Analyser/Static/ZX8081/Target.hpp" | #include "../../../../../Analyser/Static/ZX8081/Target.hpp" | ||||||
| @@ -188,7 +189,27 @@ static Analyser::Static::ZX8081::Target::MemoryModel ZX8081MemoryModelFromSize(K | |||||||
| 		_targets.push_back(std::move(target)); | 		_targets.push_back(std::move(target)); | ||||||
| 	} | 	} | ||||||
| 	return self; | 	return self; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | - (instancetype)initWithMacintoshModel:(CSMachineMacintoshModel)model { | ||||||
|  | 	self = [super init]; | ||||||
|  | 	if(self) { | ||||||
|  | 		using Target = Analyser::Static::Macintosh::Target; | ||||||
|  | 		std::unique_ptr<Target> target(new Target); | ||||||
|  | 		target->machine = Analyser::Machine::Macintosh; | ||||||
|  |  | ||||||
|  | 		using Model = Target::Model; | ||||||
|  | 		switch(model) { | ||||||
|  | 			default: | ||||||
|  | 			case CSMachineMacintoshModel128k:	target->model = Model::Mac128k;		break; | ||||||
|  | 			case CSMachineMacintoshModel512k:	target->model = Model::Mac512k;		break; | ||||||
|  | 			case CSMachineMacintoshModel512ke:	target->model = Model::Mac512ke;	break; | ||||||
|  | 			case CSMachineMacintoshModelPlus:	target->model = Model::MacPlus;		break; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		_targets.push_back(std::move(target)); | ||||||
|  | 	} | ||||||
|  | 	return self; | ||||||
| } | } | ||||||
|  |  | ||||||
| - (NSString *)optionsPanelNibName { | - (NSString *)optionsPanelNibName { | ||||||
|   | |||||||
| @@ -1,8 +1,8 @@ | |||||||
| <?xml version="1.0" encoding="UTF-8"?> | <?xml version="1.0" encoding="UTF-8"?> | ||||||
| <document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="14460.31" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct"> | <document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="14490.70" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct"> | ||||||
|     <dependencies> |     <dependencies> | ||||||
|         <deployment identifier="macosx"/> |         <deployment identifier="macosx"/> | ||||||
|         <plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="14460.31"/> |         <plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="14490.70"/> | ||||||
|         <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/> |         <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/> | ||||||
|     </dependencies> |     </dependencies> | ||||||
|     <objects> |     <objects> | ||||||
| @@ -17,14 +17,14 @@ | |||||||
|         <window title="Window" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" releasedWhenClosed="NO" visibleAtLaunch="NO" animationBehavior="default" titleVisibility="hidden" id="QvC-M9-y7g"> |         <window title="Window" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" releasedWhenClosed="NO" visibleAtLaunch="NO" animationBehavior="default" titleVisibility="hidden" id="QvC-M9-y7g"> | ||||||
|             <windowStyleMask key="styleMask" titled="YES" documentModal="YES"/> |             <windowStyleMask key="styleMask" titled="YES" documentModal="YES"/> | ||||||
|             <windowPositionMask key="initialPositionMask" leftStrut="YES" rightStrut="YES" topStrut="YES" bottomStrut="YES"/> |             <windowPositionMask key="initialPositionMask" leftStrut="YES" rightStrut="YES" topStrut="YES" bottomStrut="YES"/> | ||||||
|             <rect key="contentRect" x="196" y="240" width="600" height="205"/> |             <rect key="contentRect" x="196" y="240" width="650" height="205"/> | ||||||
|             <rect key="screenRect" x="0.0" y="0.0" width="1440" height="900"/> |             <rect key="screenRect" x="0.0" y="0.0" width="1440" height="900"/> | ||||||
|             <view key="contentView" wantsLayer="YES" id="EiT-Mj-1SZ"> |             <view key="contentView" wantsLayer="YES" id="EiT-Mj-1SZ"> | ||||||
|                 <rect key="frame" x="0.0" y="0.0" width="600" height="205"/> |                 <rect key="frame" x="0.0" y="0.0" width="650" height="205"/> | ||||||
|                 <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> |                 <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> | ||||||
|                 <subviews> |                 <subviews> | ||||||
|                     <button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="hKn-1l-OSN"> |                     <button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="hKn-1l-OSN"> | ||||||
|                         <rect key="frame" x="499" y="13" width="87" height="32"/> |                         <rect key="frame" x="549" y="13" width="87" height="32"/> | ||||||
|                         <buttonCell key="cell" type="push" title="Choose" bezelStyle="rounded" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="MnM-xo-4Qa"> |                         <buttonCell key="cell" type="push" title="Choose" bezelStyle="rounded" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="MnM-xo-4Qa"> | ||||||
|                             <behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/> |                             <behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/> | ||||||
|                             <font key="font" metaFont="system"/> |                             <font key="font" metaFont="system"/> | ||||||
| @@ -37,7 +37,7 @@ DQ | |||||||
|                         </connections> |                         </connections> | ||||||
|                     </button> |                     </button> | ||||||
|                     <button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="JQy-Cj-AOK"> |                     <button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="JQy-Cj-AOK"> | ||||||
|                         <rect key="frame" x="418" y="13" width="82" height="32"/> |                         <rect key="frame" x="468" y="13" width="82" height="32"/> | ||||||
|                         <buttonCell key="cell" type="push" title="Cancel" bezelStyle="rounded" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="sub-rB-Req"> |                         <buttonCell key="cell" type="push" title="Cancel" bezelStyle="rounded" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="sub-rB-Req"> | ||||||
|                             <behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/> |                             <behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/> | ||||||
|                             <font key="font" metaFont="system"/> |                             <font key="font" metaFont="system"/> | ||||||
| @@ -59,12 +59,12 @@ Gw | |||||||
|                         </textFieldCell> |                         </textFieldCell> | ||||||
|                     </textField> |                     </textField> | ||||||
|                     <tabView translatesAutoresizingMaskIntoConstraints="NO" id="VUb-QG-x7c"> |                     <tabView translatesAutoresizingMaskIntoConstraints="NO" id="VUb-QG-x7c"> | ||||||
|                         <rect key="frame" x="13" y="51" width="574" height="140"/> |                         <rect key="frame" x="13" y="51" width="624" height="140"/> | ||||||
|                         <font key="font" metaFont="system"/> |                         <font key="font" metaFont="system"/> | ||||||
|                         <tabViewItems> |                         <tabViewItems> | ||||||
|                             <tabViewItem label="Apple II" identifier="appleii" id="P59-QG-LOa"> |                             <tabViewItem label="Apple II" identifier="appleii" id="P59-QG-LOa"> | ||||||
|                                 <view key="view" id="dHz-Yv-GNq"> |                                 <view key="view" id="dHz-Yv-GNq"> | ||||||
|                                     <rect key="frame" x="10" y="33" width="554" height="94"/> |                                     <rect key="frame" x="10" y="33" width="604" height="94"/> | ||||||
|                                     <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> |                                     <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> | ||||||
|                                     <subviews> |                                     <subviews> | ||||||
|                                         <textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="V5Z-dX-Ns4"> |                                         <textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="V5Z-dX-Ns4"> | ||||||
| @@ -130,7 +130,7 @@ Gw | |||||||
|                             </tabViewItem> |                             </tabViewItem> | ||||||
|                             <tabViewItem label="Amstrad CPC" identifier="cpc" id="JmB-OF-xcM"> |                             <tabViewItem label="Amstrad CPC" identifier="cpc" id="JmB-OF-xcM"> | ||||||
|                                 <view key="view" id="5zS-Nj-Ynx"> |                                 <view key="view" id="5zS-Nj-Ynx"> | ||||||
|                                     <rect key="frame" x="10" y="33" width="554" height="94"/> |                                     <rect key="frame" x="10" y="33" width="604" height="94"/> | ||||||
|                                     <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> |                                     <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> | ||||||
|                                     <subviews> |                                     <subviews> | ||||||
|                                         <popUpButton verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="00d-sg-Krh"> |                                         <popUpButton verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="00d-sg-Krh"> | ||||||
| @@ -168,18 +168,18 @@ Gw | |||||||
|                             </tabViewItem> |                             </tabViewItem> | ||||||
|                             <tabViewItem label="Electron" identifier="electron" id="muc-z9-Vqc"> |                             <tabViewItem label="Electron" identifier="electron" id="muc-z9-Vqc"> | ||||||
|                                 <view key="view" id="SRc-2D-95G"> |                                 <view key="view" id="SRc-2D-95G"> | ||||||
|                                     <rect key="frame" x="10" y="33" width="554" height="93"/> |                                     <rect key="frame" x="10" y="33" width="604" height="94"/> | ||||||
|                                     <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> |                                     <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> | ||||||
|                                     <subviews> |                                     <subviews> | ||||||
|                                         <button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="JqM-IK-FMP"> |                                         <button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="JqM-IK-FMP"> | ||||||
|                                             <rect key="frame" x="15" y="74" width="164" height="18"/> |                                             <rect key="frame" x="15" y="75" width="164" height="18"/> | ||||||
|                                             <buttonCell key="cell" type="check" title="With Disk Filing System" bezelStyle="regularSquare" imagePosition="left" state="on" inset="2" id="tpW-5C-xKp"> |                                             <buttonCell key="cell" type="check" title="With Disk Filing System" bezelStyle="regularSquare" imagePosition="left" state="on" inset="2" id="tpW-5C-xKp"> | ||||||
|                                                 <behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/> |                                                 <behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/> | ||||||
|                                                 <font key="font" metaFont="system"/> |                                                 <font key="font" metaFont="system"/> | ||||||
|                                             </buttonCell> |                                             </buttonCell> | ||||||
|                                         </button> |                                         </button> | ||||||
|                                         <button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="945-wU-JOH"> |                                         <button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="945-wU-JOH"> | ||||||
|                                             <rect key="frame" x="15" y="54" width="228" height="18"/> |                                             <rect key="frame" x="15" y="55" width="228" height="18"/> | ||||||
|                                             <buttonCell key="cell" type="check" title="With Advanced Disk Filing System" bezelStyle="regularSquare" imagePosition="left" state="on" inset="2" id="S0c-Jg-7Pu"> |                                             <buttonCell key="cell" type="check" title="With Advanced Disk Filing System" bezelStyle="regularSquare" imagePosition="left" state="on" inset="2" id="S0c-Jg-7Pu"> | ||||||
|                                                 <behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/> |                                                 <behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/> | ||||||
|                                                 <font key="font" metaFont="system"/> |                                                 <font key="font" metaFont="system"/> | ||||||
| @@ -197,9 +197,31 @@ Gw | |||||||
|                                     </constraints> |                                     </constraints> | ||||||
|                                 </view> |                                 </view> | ||||||
|                             </tabViewItem> |                             </tabViewItem> | ||||||
|  |                             <tabViewItem label="Macintosh" identifier="mac" id="lmR-z3-xSm"> | ||||||
|  |                                 <view key="view" id="7Yf-vi-Q0W"> | ||||||
|  |                                     <rect key="frame" x="10" y="33" width="604" height="94"/> | ||||||
|  |                                     <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> | ||||||
|  |                                     <subviews> | ||||||
|  |                                         <textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="F6e-UC-25u"> | ||||||
|  |                                             <rect key="frame" x="15" y="74" width="574" height="17"/> | ||||||
|  |                                             <textFieldCell key="cell" lineBreakMode="clipping" title="At present Clock Signal emulates only the Macintosh 512ke." id="IGV-Yp-6Af"> | ||||||
|  |                                                 <font key="font" usesAppearanceFont="YES"/> | ||||||
|  |                                                 <color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/> | ||||||
|  |                                                 <color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/> | ||||||
|  |                                             </textFieldCell> | ||||||
|  |                                         </textField> | ||||||
|  |                                     </subviews> | ||||||
|  |                                     <constraints> | ||||||
|  |                                         <constraint firstAttribute="trailing" secondItem="F6e-UC-25u" secondAttribute="trailing" constant="17" id="42z-hS-aPq"/> | ||||||
|  |                                         <constraint firstItem="F6e-UC-25u" firstAttribute="leading" secondItem="7Yf-vi-Q0W" secondAttribute="leading" constant="17" id="bIg-C3-xdz"/> | ||||||
|  |                                         <constraint firstItem="F6e-UC-25u" firstAttribute="top" secondItem="7Yf-vi-Q0W" secondAttribute="top" constant="3" id="cxs-OP-oH5"/> | ||||||
|  |                                         <constraint firstAttribute="bottom" relation="greaterThanOrEqual" secondItem="F6e-UC-25u" secondAttribute="bottom" constant="17" id="vpF-ER-pmD"/> | ||||||
|  |                                     </constraints> | ||||||
|  |                                 </view> | ||||||
|  |                             </tabViewItem> | ||||||
|                             <tabViewItem label="MSX" identifier="msx" id="6SR-DY-zdI"> |                             <tabViewItem label="MSX" identifier="msx" id="6SR-DY-zdI"> | ||||||
|                                 <view key="view" id="mWD-An-tR7"> |                                 <view key="view" id="mWD-An-tR7"> | ||||||
|                                     <rect key="frame" x="10" y="33" width="554" height="94"/> |                                     <rect key="frame" x="10" y="33" width="604" height="94"/> | ||||||
|                                     <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> |                                     <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> | ||||||
|                                     <subviews> |                                     <subviews> | ||||||
|                                         <button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="8xT-Pr-8SE"> |                                         <button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="8xT-Pr-8SE"> | ||||||
| @@ -233,6 +255,7 @@ Gw | |||||||
|                                         </textField> |                                         </textField> | ||||||
|                                     </subviews> |                                     </subviews> | ||||||
|                                     <constraints> |                                     <constraints> | ||||||
|  |                                         <constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="LG6-mP-SeG" secondAttribute="trailing" constant="17" id="0Oc-n7-gaM"/> | ||||||
|                                         <constraint firstItem="8xT-Pr-8SE" firstAttribute="top" secondItem="LG6-mP-SeG" secondAttribute="bottom" constant="6" id="LBt-4m-GDc"/> |                                         <constraint firstItem="8xT-Pr-8SE" firstAttribute="top" secondItem="LG6-mP-SeG" secondAttribute="bottom" constant="6" id="LBt-4m-GDc"/> | ||||||
|                                         <constraint firstItem="LG6-mP-SeG" firstAttribute="top" secondItem="mWD-An-tR7" secondAttribute="top" constant="3" id="bcb-ZZ-VpQ"/> |                                         <constraint firstItem="LG6-mP-SeG" firstAttribute="top" secondItem="mWD-An-tR7" secondAttribute="top" constant="3" id="bcb-ZZ-VpQ"/> | ||||||
|                                         <constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="8xT-Pr-8SE" secondAttribute="trailing" constant="17" id="l8P-UW-8ig"/> |                                         <constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="8xT-Pr-8SE" secondAttribute="trailing" constant="17" id="l8P-UW-8ig"/> | ||||||
| @@ -246,7 +269,7 @@ Gw | |||||||
|                             </tabViewItem> |                             </tabViewItem> | ||||||
|                             <tabViewItem label="Oric" identifier="oric" id="NSx-DC-p4M"> |                             <tabViewItem label="Oric" identifier="oric" id="NSx-DC-p4M"> | ||||||
|                                 <view key="view" id="sOR-e0-8iZ"> |                                 <view key="view" id="sOR-e0-8iZ"> | ||||||
|                                     <rect key="frame" x="10" y="33" width="554" height="94"/> |                                     <rect key="frame" x="10" y="33" width="604" height="94"/> | ||||||
|                                     <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> |                                     <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> | ||||||
|                                     <subviews> |                                     <subviews> | ||||||
|                                         <textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="0ct-tf-uRH"> |                                         <textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="0ct-tf-uRH"> | ||||||
| @@ -311,7 +334,7 @@ Gw | |||||||
|                             </tabViewItem> |                             </tabViewItem> | ||||||
|                             <tabViewItem label="Vic-20" identifier="vic20" id="cyO-PU-hSU"> |                             <tabViewItem label="Vic-20" identifier="vic20" id="cyO-PU-hSU"> | ||||||
|                                 <view key="view" id="fLI-XB-QCr"> |                                 <view key="view" id="fLI-XB-QCr"> | ||||||
|                                     <rect key="frame" x="10" y="33" width="554" height="94"/> |                                     <rect key="frame" x="10" y="33" width="604" height="94"/> | ||||||
|                                     <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> |                                     <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> | ||||||
|                                     <subviews> |                                     <subviews> | ||||||
|                                         <popUpButton verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="ueK-gq-gaF"> |                                         <popUpButton verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="ueK-gq-gaF"> | ||||||
| @@ -388,11 +411,11 @@ Gw | |||||||
|                             </tabViewItem> |                             </tabViewItem> | ||||||
|                             <tabViewItem label="ZX80" identifier="zx80" id="tMH-kF-GUz"> |                             <tabViewItem label="ZX80" identifier="zx80" id="tMH-kF-GUz"> | ||||||
|                                 <view key="view" id="8hL-Vn-Hg0"> |                                 <view key="view" id="8hL-Vn-Hg0"> | ||||||
|                                     <rect key="frame" x="10" y="33" width="554" height="93"/> |                                     <rect key="frame" x="10" y="33" width="554" height="94"/> | ||||||
|                                     <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> |                                     <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> | ||||||
|                                     <subviews> |                                     <subviews> | ||||||
|                                         <popUpButton verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="I1a-Eu-5UB"> |                                         <popUpButton verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="I1a-Eu-5UB"> | ||||||
|                                             <rect key="frame" x="106" y="66" width="115" height="25"/> |                                             <rect key="frame" x="106" y="67" width="115" height="25"/> | ||||||
|                                             <popUpButtonCell key="cell" type="push" title="Unexpanded" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" imageScaling="proportionallyDown" inset="2" selectedItem="4Sa-jR-xOd" id="B8M-do-Yod"> |                                             <popUpButtonCell key="cell" type="push" title="Unexpanded" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" imageScaling="proportionallyDown" inset="2" selectedItem="4Sa-jR-xOd" id="B8M-do-Yod"> | ||||||
|                                                 <behavior key="behavior" lightByBackground="YES" lightByGray="YES"/> |                                                 <behavior key="behavior" lightByBackground="YES" lightByGray="YES"/> | ||||||
|                                                 <font key="font" metaFont="menu"/> |                                                 <font key="font" metaFont="menu"/> | ||||||
| @@ -406,7 +429,7 @@ Gw | |||||||
|                                             </popUpButtonCell> |                                             </popUpButtonCell> | ||||||
|                                         </popUpButton> |                                         </popUpButton> | ||||||
|                                         <textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="NCX-4e-lSu"> |                                         <textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="NCX-4e-lSu"> | ||||||
|                                             <rect key="frame" x="15" y="71" width="87" height="17"/> |                                             <rect key="frame" x="15" y="72" width="87" height="17"/> | ||||||
|                                             <textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Memory Size:" id="e6x-TE-OC5"> |                                             <textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Memory Size:" id="e6x-TE-OC5"> | ||||||
|                                                 <font key="font" metaFont="system"/> |                                                 <font key="font" metaFont="system"/> | ||||||
|                                                 <color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/> |                                                 <color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/> | ||||||
| @@ -414,7 +437,7 @@ Gw | |||||||
|                                             </textFieldCell> |                                             </textFieldCell> | ||||||
|                                         </textField> |                                         </textField> | ||||||
|                                         <button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="ReP-bV-Thu"> |                                         <button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="ReP-bV-Thu"> | ||||||
|                                             <rect key="frame" x="15" y="47" width="114" height="18"/> |                                             <rect key="frame" x="15" y="48" width="114" height="18"/> | ||||||
|                                             <buttonCell key="cell" type="check" title="Use ZX81 ROM" bezelStyle="regularSquare" imagePosition="left" state="on" inset="2" id="VQH-nv-Pfm"> |                                             <buttonCell key="cell" type="check" title="Use ZX81 ROM" bezelStyle="regularSquare" imagePosition="left" state="on" inset="2" id="VQH-nv-Pfm"> | ||||||
|                                                 <behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/> |                                                 <behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/> | ||||||
|                                                 <font key="font" metaFont="system"/> |                                                 <font key="font" metaFont="system"/> | ||||||
| @@ -436,11 +459,11 @@ Gw | |||||||
|                             </tabViewItem> |                             </tabViewItem> | ||||||
|                             <tabViewItem label="ZX81" identifier="zx81" id="Wnn-nQ-gZ6"> |                             <tabViewItem label="ZX81" identifier="zx81" id="Wnn-nQ-gZ6"> | ||||||
|                                 <view key="view" id="bmd-gL-gzT"> |                                 <view key="view" id="bmd-gL-gzT"> | ||||||
|                                     <rect key="frame" x="10" y="33" width="554" height="93"/> |                                     <rect key="frame" x="10" y="33" width="554" height="94"/> | ||||||
|                                     <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> |                                     <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> | ||||||
|                                     <subviews> |                                     <subviews> | ||||||
|                                         <popUpButton verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="5aO-UX-HnX"> |                                         <popUpButton verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="5aO-UX-HnX"> | ||||||
|                                             <rect key="frame" x="106" y="66" width="115" height="25"/> |                                             <rect key="frame" x="106" y="67" width="115" height="25"/> | ||||||
|                                             <popUpButtonCell key="cell" type="push" title="Unexpanded" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" imageScaling="proportionallyDown" inset="2" selectedItem="7QC-Ij-hES" id="d3W-Gl-3Mf"> |                                             <popUpButtonCell key="cell" type="push" title="Unexpanded" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" imageScaling="proportionallyDown" inset="2" selectedItem="7QC-Ij-hES" id="d3W-Gl-3Mf"> | ||||||
|                                                 <behavior key="behavior" lightByBackground="YES" lightByGray="YES"/> |                                                 <behavior key="behavior" lightByBackground="YES" lightByGray="YES"/> | ||||||
|                                                 <font key="font" metaFont="menu"/> |                                                 <font key="font" metaFont="menu"/> | ||||||
| @@ -454,7 +477,7 @@ Gw | |||||||
|                                             </popUpButtonCell> |                                             </popUpButtonCell> | ||||||
|                                         </popUpButton> |                                         </popUpButton> | ||||||
|                                         <textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="8tU-73-XEE"> |                                         <textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="8tU-73-XEE"> | ||||||
|                                             <rect key="frame" x="15" y="71" width="87" height="17"/> |                                             <rect key="frame" x="15" y="72" width="87" height="17"/> | ||||||
|                                             <textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Memory Size:" id="z4b-oR-Yl2"> |                                             <textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Memory Size:" id="z4b-oR-Yl2"> | ||||||
|                                                 <font key="font" metaFont="system"/> |                                                 <font key="font" metaFont="system"/> | ||||||
|                                                 <color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/> |                                                 <color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/> | ||||||
| @@ -490,7 +513,7 @@ Gw | |||||||
|                     <constraint firstItem="VUb-QG-x7c" firstAttribute="top" secondItem="EiT-Mj-1SZ" secondAttribute="top" constant="20" id="zT3-Ea-QQJ"/> |                     <constraint firstItem="VUb-QG-x7c" firstAttribute="top" secondItem="EiT-Mj-1SZ" secondAttribute="top" constant="20" id="zT3-Ea-QQJ"/> | ||||||
|                 </constraints> |                 </constraints> | ||||||
|             </view> |             </view> | ||||||
|             <point key="canvasLocation" x="34" y="89"/> |             <point key="canvasLocation" x="34" y="88.5"/> | ||||||
|         </window> |         </window> | ||||||
|         <customObject id="192-Eb-Rpg" customClass="MachinePicker" customModule="Clock_Signal" customModuleProvider="target"> |         <customObject id="192-Eb-Rpg" customClass="MachinePicker" customModule="Clock_Signal" customModuleProvider="target"> | ||||||
|             <connections> |             <connections> | ||||||
|   | |||||||
| @@ -157,6 +157,9 @@ class MachinePicker: NSObject { | |||||||
| 					default:	return CSStaticAnalyser(amstradCPCModel: .model6128) | 					default:	return CSStaticAnalyser(amstradCPCModel: .model6128) | ||||||
| 				} | 				} | ||||||
|  |  | ||||||
|  | 			case "mac": | ||||||
|  | 				return CSStaticAnalyser(macintoshModel: .model512ke) | ||||||
|  |  | ||||||
| 			case "msx": | 			case "msx": | ||||||
| 				let hasDiskDrive = msxHasDiskDriveButton!.state == .on | 				let hasDiskDrive = msxHasDiskDriveButton!.state == .on | ||||||
| 				switch msxRegionButton!.selectedItem?.tag { | 				switch msxRegionButton!.selectedItem?.tag { | ||||||
|   | |||||||
							
								
								
									
										27
									
								
								OSBindings/Mac/Clock Signal/ROMRequester/CSROMReceiverView.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								OSBindings/Mac/Clock Signal/ROMRequester/CSROMReceiverView.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,27 @@ | |||||||
|  | // | ||||||
|  | //  CSROMReceiverView.h | ||||||
|  | //  Clock Signal | ||||||
|  | // | ||||||
|  | //  Created by Thomas Harte on 22/07/2019. | ||||||
|  | //  Copyright © 2019 Thomas Harte. All rights reserved. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | #import <Cocoa/Cocoa.h> | ||||||
|  |  | ||||||
|  | @class CSROMReceiverView; | ||||||
|  |  | ||||||
|  | @protocol CSROMReciverViewDelegate | ||||||
|  | /*! | ||||||
|  | 	Announces receipt of a file by drag and drop to the delegate. | ||||||
|  | 	@param view The view making the request. | ||||||
|  | 	@param URL The file URL of the received file. | ||||||
|  | */ | ||||||
|  | - (void)romReceiverView:(nonnull CSROMReceiverView *)view didReceiveFileAtURL:(nonnull NSURL *)URL; | ||||||
|  |  | ||||||
|  | @end | ||||||
|  |  | ||||||
|  | @interface CSROMReceiverView : NSView | ||||||
|  |  | ||||||
|  | @property(nonatomic, weak, nullable) id <CSROMReciverViewDelegate> delegate; | ||||||
|  |  | ||||||
|  | @end | ||||||
							
								
								
									
										40
									
								
								OSBindings/Mac/Clock Signal/ROMRequester/CSROMReceiverView.m
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										40
									
								
								OSBindings/Mac/Clock Signal/ROMRequester/CSROMReceiverView.m
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,40 @@ | |||||||
|  | // | ||||||
|  | //  CSROMReceiverView.m | ||||||
|  | //  Clock Signal | ||||||
|  | // | ||||||
|  | //  Created by Thomas Harte on 22/07/2019. | ||||||
|  | //  Copyright © 2019 Thomas Harte. All rights reserved. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | #import "CSROMReceiverView.h" | ||||||
|  |  | ||||||
|  | @interface CSROMReceiverView () <NSDraggingDestination> | ||||||
|  | @end | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @implementation CSROMReceiverView | ||||||
|  |  | ||||||
|  | - (void)awakeFromNib { | ||||||
|  | 	[super awakeFromNib]; | ||||||
|  |  | ||||||
|  | 	// Accept file URLs by drag and drop. | ||||||
|  | 	[self registerForDraggedTypes:@[(__bridge NSString *)kUTTypeFileURL]]; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #pragma mark - NSDraggingDestination | ||||||
|  |  | ||||||
|  | - (BOOL)performDragOperation:(id <NSDraggingInfo>)sender { | ||||||
|  | 	// Just forward the URLs. | ||||||
|  | 	for(NSPasteboardItem *item in [[sender draggingPasteboard] pasteboardItems]) { | ||||||
|  | 		NSURL *URL = [NSURL URLWithString:[item stringForType:(__bridge NSString *)kUTTypeFileURL]]; | ||||||
|  | 		[self.delegate romReceiverView:self didReceiveFileAtURL:URL]; | ||||||
|  | 	} | ||||||
|  | 	return YES; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | - (NSDragOperation)draggingEntered:(id < NSDraggingInfo >)sender { | ||||||
|  | 	// The emulator will take a copy of any deposited ROM files. | ||||||
|  | 	return NSDragOperationCopy; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @end | ||||||
							
								
								
									
										75
									
								
								OSBindings/Mac/Clock Signal/ROMRequester/ROMRequester.xib
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										75
									
								
								OSBindings/Mac/Clock Signal/ROMRequester/ROMRequester.xib
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,75 @@ | |||||||
|  | <?xml version="1.0" encoding="UTF-8"?> | ||||||
|  | <document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="14490.70" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct"> | ||||||
|  |     <dependencies> | ||||||
|  |         <deployment identifier="macosx"/> | ||||||
|  |         <plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="14490.70"/> | ||||||
|  |         <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/> | ||||||
|  |     </dependencies> | ||||||
|  |     <objects> | ||||||
|  |         <customObject id="-2" userLabel="File's Owner" customClass="MachineDocument" customModule="Clock_Signal" customModuleProvider="target"> | ||||||
|  |             <connections> | ||||||
|  |                 <outlet property="romReceiverErrorField" destination="Bia-0m-GxK" id="wnH-Kz-uXD"/> | ||||||
|  |                 <outlet property="romReceiverView" destination="EiT-Mj-1SZ" id="Y2M-Qd-i7y"/> | ||||||
|  |                 <outlet property="romRequesterPanel" destination="QvC-M9-y7g" id="saI-YH-9NP"/> | ||||||
|  |                 <outlet property="romRequesterText" destination="5qG-I3-Qav" id="J9M-T8-Xyy"/> | ||||||
|  |             </connections> | ||||||
|  |         </customObject> | ||||||
|  |         <customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/> | ||||||
|  |         <customObject id="-3" userLabel="Application" customClass="NSObject"/> | ||||||
|  |         <window title="Window" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" releasedWhenClosed="NO" visibleAtLaunch="NO" animationBehavior="default" titleVisibility="hidden" id="QvC-M9-y7g"> | ||||||
|  |             <windowStyleMask key="styleMask" titled="YES"/> | ||||||
|  |             <windowPositionMask key="initialPositionMask" leftStrut="YES" rightStrut="YES" topStrut="YES" bottomStrut="YES"/> | ||||||
|  |             <rect key="contentRect" x="196" y="240" width="480" height="270"/> | ||||||
|  |             <rect key="screenRect" x="0.0" y="0.0" width="1440" height="900"/> | ||||||
|  |             <view key="contentView" wantsLayer="YES" id="EiT-Mj-1SZ" customClass="CSROMReceiverView"> | ||||||
|  |                 <rect key="frame" x="0.0" y="0.0" width="480" height="270"/> | ||||||
|  |                 <autoresizingMask key="autoresizingMask"/> | ||||||
|  |                 <subviews> | ||||||
|  |                     <textField verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" textCompletion="NO" translatesAutoresizingMaskIntoConstraints="NO" id="5qG-I3-Qav"> | ||||||
|  |                         <rect key="frame" x="18" y="148" width="444" height="102"/> | ||||||
|  |                         <textFieldCell key="cell" enabled="NO" allowsUndo="NO" id="itJ-2T-0ia"> | ||||||
|  |                             <font key="font" usesAppearanceFont="YES"/> | ||||||
|  |                             <string key="title">Clock Signal requires you to provide images of the system ROMs for this machine. They will be stored permanently; you need do this only once.
Please drag and drop the following over this view: | ||||||
|  |  | ||||||
|  | </string> | ||||||
|  |                             <color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/> | ||||||
|  |                             <color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/> | ||||||
|  |                         </textFieldCell> | ||||||
|  |                     </textField> | ||||||
|  |                     <button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="nvl-dm-8bK"> | ||||||
|  |                         <rect key="frame" x="384" y="13" width="82" height="32"/> | ||||||
|  |                         <buttonCell key="cell" type="push" title="Cancel" bezelStyle="rounded" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="oS1-Pk-LsO"> | ||||||
|  |                             <behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/> | ||||||
|  |                             <font key="font" metaFont="system"/> | ||||||
|  |                             <string key="keyEquivalent" base64-UTF8="YES"> | ||||||
|  | DQ | ||||||
|  | </string> | ||||||
|  |                         </buttonCell> | ||||||
|  |                         <connections> | ||||||
|  |                             <action selector="cancelRequestROMs:" target="-2" id="58q-yi-dlv"/> | ||||||
|  |                         </connections> | ||||||
|  |                     </button> | ||||||
|  |                     <textField verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" textCompletion="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Bia-0m-GxK"> | ||||||
|  |                         <rect key="frame" x="20" y="61" width="442" height="17"/> | ||||||
|  |                         <textFieldCell key="cell" allowsUndo="NO" alignment="center" title="Multiline Label" drawsBackground="YES" id="8jl-xs-LjP"> | ||||||
|  |                             <font key="font" metaFont="message"/> | ||||||
|  |                             <color key="textColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/> | ||||||
|  |                             <color key="backgroundColor" name="tertiaryLabelColor" catalog="System" colorSpace="catalog"/> | ||||||
|  |                         </textFieldCell> | ||||||
|  |                     </textField> | ||||||
|  |                 </subviews> | ||||||
|  |                 <constraints> | ||||||
|  |                     <constraint firstItem="Bia-0m-GxK" firstAttribute="leading" secondItem="EiT-Mj-1SZ" secondAttribute="leading" constant="20" id="9Lj-3p-7Vr"/> | ||||||
|  |                     <constraint firstAttribute="trailing" secondItem="5qG-I3-Qav" secondAttribute="trailing" constant="20" id="AwT-WO-Nyf"/> | ||||||
|  |                     <constraint firstAttribute="bottom" secondItem="nvl-dm-8bK" secondAttribute="bottom" constant="20" id="BU0-5T-YRH"/> | ||||||
|  |                     <constraint firstAttribute="bottom" secondItem="Bia-0m-GxK" secondAttribute="bottom" constant="61" id="ENn-7a-NGa"/> | ||||||
|  |                     <constraint firstAttribute="trailing" secondItem="Bia-0m-GxK" secondAttribute="trailing" constant="18" id="UUy-bf-87w"/> | ||||||
|  |                     <constraint firstItem="5qG-I3-Qav" firstAttribute="leading" secondItem="EiT-Mj-1SZ" secondAttribute="leading" constant="20" id="bRM-z1-cRf"/> | ||||||
|  |                     <constraint firstAttribute="trailing" secondItem="nvl-dm-8bK" secondAttribute="trailing" constant="20" id="eQI-Nj-ane"/> | ||||||
|  |                     <constraint firstItem="nvl-dm-8bK" firstAttribute="top" relation="greaterThanOrEqual" secondItem="5qG-I3-Qav" secondAttribute="bottom" constant="17" id="plB-x4-He5"/> | ||||||
|  |                     <constraint firstItem="5qG-I3-Qav" firstAttribute="top" secondItem="EiT-Mj-1SZ" secondAttribute="top" constant="20" id="qeC-Rh-hmr"/> | ||||||
|  |                 </constraints> | ||||||
|  |             </view> | ||||||
|  |         </window> | ||||||
|  |     </objects> | ||||||
|  | </document> | ||||||
| @@ -63,6 +63,29 @@ typedef NS_ENUM(NSInteger, CSOpenGLViewRedrawEvent) { | |||||||
| */ | */ | ||||||
| - (void)paste:(nonnull id)sender; | - (void)paste:(nonnull id)sender; | ||||||
|  |  | ||||||
|  | @optional | ||||||
|  |  | ||||||
|  | /*! | ||||||
|  | 	Supplies a mouse moved event to the delegate. This functions only if | ||||||
|  | 	shouldCaptureMouse is set to YES, in which case the view will ensure it captures | ||||||
|  | 	the mouse and returns only relative motion | ||||||
|  | 	(Cf. CGAssociateMouseAndMouseCursorPosition). It will also elide mouseDragged: | ||||||
|  | 	(and rightMouseDragged:, etc) and mouseMoved: events. | ||||||
|  | */ | ||||||
|  | - (void)mouseMoved:(nonnull NSEvent *)event; | ||||||
|  |  | ||||||
|  | /*! | ||||||
|  | 	Supplies a mouse button down event. This elides mouseDown, rightMouseDown and otherMouseDown. | ||||||
|  | 	@c shouldCaptureMouse must be set to @c YES to receive these events. | ||||||
|  | */ | ||||||
|  | - (void)mouseDown:(nonnull NSEvent *)event; | ||||||
|  |  | ||||||
|  | /*! | ||||||
|  | 	Supplies a mouse button up event. This elides mouseUp, rightMouseUp and otherMouseUp. | ||||||
|  | 	@c shouldCaptureMouse must be set to @c YES to receive these events. | ||||||
|  | */ | ||||||
|  | - (void)mouseUp:(nonnull NSEvent *)event; | ||||||
|  |  | ||||||
| @end | @end | ||||||
|  |  | ||||||
| /*! | /*! | ||||||
| @@ -74,6 +97,8 @@ typedef NS_ENUM(NSInteger, CSOpenGLViewRedrawEvent) { | |||||||
| @property (atomic, weak, nullable) id <CSOpenGLViewDelegate> delegate; | @property (atomic, weak, nullable) id <CSOpenGLViewDelegate> delegate; | ||||||
| @property (nonatomic, weak, nullable) id <CSOpenGLViewResponderDelegate> responderDelegate; | @property (nonatomic, weak, nullable) id <CSOpenGLViewResponderDelegate> responderDelegate; | ||||||
|  |  | ||||||
|  | @property (nonatomic, assign) BOOL shouldCaptureMouse; | ||||||
|  |  | ||||||
| /*! | /*! | ||||||
| 	Ends the timer tracking time; should be called prior to giving up the last owning reference | 	Ends the timer tracking time; should be called prior to giving up the last owning reference | ||||||
| 	to ensure that any retain cycles implied by the timer are resolved. | 	to ensure that any retain cycles implied by the timer are resolved. | ||||||
| @@ -89,4 +114,9 @@ typedef NS_ENUM(NSInteger, CSOpenGLViewRedrawEvent) { | |||||||
| */ | */ | ||||||
| - (void)performWithGLContext:(nonnull dispatch_block_t)action; | - (void)performWithGLContext:(nonnull dispatch_block_t)action; | ||||||
|  |  | ||||||
|  | /*! | ||||||
|  | 	Instructs that the mouse cursor, if currently captured, should be released. | ||||||
|  | */ | ||||||
|  | - (void)releaseMouse; | ||||||
|  |  | ||||||
| @end | @end | ||||||
|   | |||||||
| @@ -19,9 +19,12 @@ | |||||||
|  |  | ||||||
| 	NSTrackingArea *_mouseTrackingArea; | 	NSTrackingArea *_mouseTrackingArea; | ||||||
| 	NSTimer *_mouseHideTimer; | 	NSTimer *_mouseHideTimer; | ||||||
|  | 	BOOL _mouseIsCaptured; | ||||||
| } | } | ||||||
|  |  | ||||||
| - (void)prepareOpenGL { | - (void)prepareOpenGL { | ||||||
|  | 	[super prepareOpenGL]; | ||||||
|  |  | ||||||
| 	// Synchronize buffer swaps with vertical refresh rate | 	// Synchronize buffer swaps with vertical refresh rate | ||||||
| 	GLint swapInt = 1; | 	GLint swapInt = 1; | ||||||
| 	[[self openGLContext] setValues:&swapInt forParameter:NSOpenGLCPSwapInterval]; | 	[[self openGLContext] setValues:&swapInt forParameter:NSOpenGLCPSwapInterval]; | ||||||
| @@ -146,6 +149,13 @@ static CVReturn DisplayLinkCallback(CVDisplayLinkRef displayLink, const CVTimeSt | |||||||
|  |  | ||||||
| - (void)flagsChanged:(NSEvent *)theEvent { | - (void)flagsChanged:(NSEvent *)theEvent { | ||||||
| 	[self.responderDelegate flagsChanged:theEvent]; | 	[self.responderDelegate flagsChanged:theEvent]; | ||||||
|  |  | ||||||
|  | 	// Release the mouse upon a control + command. | ||||||
|  | 	if(_mouseIsCaptured && | ||||||
|  | 		theEvent.modifierFlags & NSEventModifierFlagControl && | ||||||
|  | 		theEvent.modifierFlags & NSEventModifierFlagCommand) { | ||||||
|  | 		[self releaseMouse]; | ||||||
|  | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| - (void)paste:(id)sender { | - (void)paste:(id)sender { | ||||||
| @@ -168,6 +178,10 @@ static CVReturn DisplayLinkCallback(CVDisplayLinkRef displayLink, const CVTimeSt | |||||||
|  |  | ||||||
| #pragma mark - Mouse hiding | #pragma mark - Mouse hiding | ||||||
|  |  | ||||||
|  | - (void)setShouldCaptureMouse:(BOOL)shouldCaptureMouse { | ||||||
|  | 	_shouldCaptureMouse = shouldCaptureMouse; | ||||||
|  | } | ||||||
|  |  | ||||||
| - (void)updateTrackingAreas { | - (void)updateTrackingAreas { | ||||||
| 	[super updateTrackingAreas]; | 	[super updateTrackingAreas]; | ||||||
|  |  | ||||||
| @@ -183,9 +197,14 @@ static CVReturn DisplayLinkCallback(CVDisplayLinkRef displayLink, const CVTimeSt | |||||||
| 	[self addTrackingArea:_mouseTrackingArea]; | 	[self addTrackingArea:_mouseTrackingArea]; | ||||||
| } | } | ||||||
|  |  | ||||||
| - (void)mouseMoved:(NSEvent *)event { | - (void)scheduleMouseHide { | ||||||
| 	[super mouseMoved:event]; | 	if(!self.shouldCaptureMouse) { | ||||||
| 	[self scheduleMouseHide]; | 		[_mouseHideTimer invalidate]; | ||||||
|  |  | ||||||
|  | 		_mouseHideTimer = [NSTimer scheduledTimerWithTimeInterval:3.0 repeats:NO block:^(NSTimer * _Nonnull timer) { | ||||||
|  | 			[NSCursor setHiddenUntilMouseMoves:YES]; | ||||||
|  | 		}]; | ||||||
|  | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| - (void)mouseEntered:(NSEvent *)event { | - (void)mouseEntered:(NSEvent *)event { | ||||||
| @@ -193,18 +212,119 @@ static CVReturn DisplayLinkCallback(CVDisplayLinkRef displayLink, const CVTimeSt | |||||||
| 	[self scheduleMouseHide]; | 	[self scheduleMouseHide]; | ||||||
| } | } | ||||||
|  |  | ||||||
| - (void)scheduleMouseHide { |  | ||||||
| 	[_mouseHideTimer invalidate]; |  | ||||||
| 	_mouseHideTimer = [NSTimer scheduledTimerWithTimeInterval:3.0 repeats:NO block:^(NSTimer * _Nonnull timer) { |  | ||||||
|         [NSCursor setHiddenUntilMouseMoves:YES]; |  | ||||||
| 	}]; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| - (void)mouseExited:(NSEvent *)event { | - (void)mouseExited:(NSEvent *)event { | ||||||
| 	[super mouseExited:event]; | 	[super mouseExited:event]; | ||||||
|  |  | ||||||
| 	[_mouseHideTimer invalidate]; | 	[_mouseHideTimer invalidate]; | ||||||
| 	_mouseHideTimer = nil; | 	_mouseHideTimer = nil; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | - (void)releaseMouse { | ||||||
|  | 	if(_mouseIsCaptured) { | ||||||
|  | 		_mouseIsCaptured = NO; | ||||||
|  | 		CGAssociateMouseAndMouseCursorPosition(true); | ||||||
|  | 		[NSCursor unhide]; | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #pragma mark - Mouse motion | ||||||
|  |  | ||||||
|  | - (void)applyMouseMotion:(NSEvent *)event { | ||||||
|  | 	if(!self.shouldCaptureMouse) { | ||||||
|  | 		// Mouse capture is off, so don't play games with the cursor, just schedule it to | ||||||
|  | 		// hide in the near future. | ||||||
|  | 		[self scheduleMouseHide]; | ||||||
|  | 	} else { | ||||||
|  | 		if(_mouseIsCaptured) { | ||||||
|  | 			// Mouse capture is on, so move the cursor back to the middle of the window, and | ||||||
|  | 			// forward the deltas to the listener. | ||||||
|  | 			// | ||||||
|  | 			// TODO: should I really need to invert the y coordinate myself? It suggests I | ||||||
|  | 			// might have an error in mapping here. | ||||||
|  | 			const NSPoint windowCentre = [self convertPoint:CGPointMake(self.bounds.size.width * 0.5, self.bounds.size.height * 0.5) toView:nil]; | ||||||
|  | 			const NSPoint screenCentre = [self.window convertPointToScreen:windowCentre]; | ||||||
|  | 			const CGRect screenFrame = self.window.screen.frame; | ||||||
|  | 			CGWarpMouseCursorPosition(NSMakePoint( | ||||||
|  | 				screenFrame.origin.x + screenCentre.x, | ||||||
|  | 				screenFrame.origin.y + screenFrame.size.height - screenCentre.y | ||||||
|  | 			)); | ||||||
|  |  | ||||||
|  | 			[self.responderDelegate mouseMoved:event]; | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | - (void)mouseDragged:(NSEvent *)event { | ||||||
|  | 	[self applyMouseMotion:event]; | ||||||
|  | 	[super mouseDragged:event]; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | - (void)rightMouseDragged:(NSEvent *)event { | ||||||
|  | 	[self applyMouseMotion:event]; | ||||||
|  | 	[super rightMouseDragged:event]; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | - (void)otherMouseDragged:(NSEvent *)event { | ||||||
|  | 	[self applyMouseMotion:event]; | ||||||
|  | 	[super otherMouseDragged:event]; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | - (void)mouseMoved:(NSEvent *)event { | ||||||
|  | 	[self applyMouseMotion:event]; | ||||||
|  | 	[super mouseMoved:event]; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #pragma mark - Mouse buttons | ||||||
|  |  | ||||||
|  | - (void)applyButtonDown:(NSEvent *)event { | ||||||
|  | 	if(self.shouldCaptureMouse) { | ||||||
|  | 		if(!_mouseIsCaptured) { | ||||||
|  | 			_mouseIsCaptured = YES; | ||||||
|  | 			[NSCursor hide]; | ||||||
|  | 			CGAssociateMouseAndMouseCursorPosition(false); | ||||||
|  |  | ||||||
|  | 			// Don't report the first click to the delegate; treat that as merely | ||||||
|  | 			// an invitation to capture the cursor. | ||||||
|  | 			return; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		[self.responderDelegate mouseDown:event]; | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | - (void)applyButtonUp:(NSEvent *)event { | ||||||
|  | 	if(self.shouldCaptureMouse) { | ||||||
|  | 		[self.responderDelegate mouseUp:event]; | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | - (void)mouseDown:(NSEvent *)event { | ||||||
|  | 	[self applyButtonDown:event]; | ||||||
|  | 	[super mouseDown:event]; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | - (void)rightMouseDown:(NSEvent *)event { | ||||||
|  | 	[self applyButtonDown:event]; | ||||||
|  | 	[super rightMouseDown:event]; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | - (void)otherMouseDown:(NSEvent *)event { | ||||||
|  | 	[self applyButtonDown:event]; | ||||||
|  | 	[super otherMouseDown:event]; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | - (void)mouseUp:(NSEvent *)event { | ||||||
|  | 	[self applyButtonUp:event]; | ||||||
|  | 	[super mouseUp:event]; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | - (void)rightMouseUp:(NSEvent *)event  { | ||||||
|  | 	[self applyButtonUp:event]; | ||||||
|  | 	[super rightMouseUp:event]; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | - (void)otherMouseUp:(NSEvent *)event { | ||||||
|  | 	[self applyButtonUp:event]; | ||||||
|  | 	[super otherMouseUp:event]; | ||||||
|  | } | ||||||
|  |  | ||||||
| @end | @end | ||||||
|   | |||||||
| @@ -11,118 +11,244 @@ import Foundation | |||||||
|  |  | ||||||
| class MOS6522Tests: XCTestCase { | class MOS6522Tests: XCTestCase { | ||||||
|  |  | ||||||
| 	fileprivate func with6522(_ action: (MOS6522Bridge) -> ()) { | 	private var m6522: MOS6522Bridge! | ||||||
| 		let bridge = MOS6522Bridge() |  | ||||||
| 		action(bridge) | 	override func setUp() { | ||||||
|  | 		m6522 = MOS6522Bridge() | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	// MARK: Timer tests | 	// MARK: Timer tests | ||||||
|  |  | ||||||
| 	func testTimerCount() { | 	func testTimerCount() { | ||||||
| 		with6522 { | 		// set timer 1 to a value of m652200a | ||||||
| 			// set timer 1 to a value of $000a | 		m6522.setValue(10, forRegister: 4) | ||||||
| 			$0.setValue(10, forRegister: 4) | 		m6522.setValue(0, forRegister: 5) | ||||||
| 			$0.setValue(0, forRegister: 5) |  | ||||||
|  |  | ||||||
| 		// complete the setting cycle | 		// complete the setting cycle | ||||||
| 			$0.run(forHalfCycles: 2) | 		m6522.run(forHalfCycles: 2) | ||||||
|  |  | ||||||
| 		// run for 5 cycles | 		// run for 5 cycles | ||||||
| 			$0.run(forHalfCycles: 10) | 		m6522.run(forHalfCycles: 10) | ||||||
|  |  | ||||||
| 		// check that the timer has gone down by 5 | 		// check that the timer has gone down by 5 | ||||||
| 			XCTAssert($0.value(forRegister: 4) == 5, "Low order byte should be 5; was \($0.value(forRegister: 4))") | 		XCTAssert(m6522.value(forRegister: 4) == 5, "Low order byte should be 5; was \(m6522.value(forRegister: 4))") | ||||||
| 			XCTAssert($0.value(forRegister: 5) == 0, "High order byte should be 0; was \($0.value(forRegister: 5))") | 		XCTAssert(m6522.value(forRegister: 5) == 0, "High order byte should be 0; was \(m6522.value(forRegister: 5))") | ||||||
| 		} |  | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	func testTimerLatches() { | 	func testTimerLatches() { | ||||||
| 		with6522 { |  | ||||||
| 		// set timer 2 to $1020 | 		// set timer 2 to $1020 | ||||||
| 			$0.setValue(0x10, forRegister: 8) | 		m6522.setValue(0x10, forRegister: 8) | ||||||
| 			$0.setValue(0x20, forRegister: 9) | 		m6522.setValue(0x20, forRegister: 9) | ||||||
|  |  | ||||||
| 		// change the low-byte latch | 		// change the low-byte latch | ||||||
| 			$0.setValue(0x40, forRegister: 8) | 		m6522.setValue(0x40, forRegister: 8) | ||||||
|  |  | ||||||
| 		// complete the cycle | 		// complete the cycle | ||||||
| 			$0.run(forHalfCycles: 2) | 		m6522.run(forHalfCycles: 2) | ||||||
|  |  | ||||||
| 		// chek that the new latched value hasn't been copied | 		// chek that the new latched value hasn't been copied | ||||||
| 			XCTAssert($0.value(forRegister: 8) == 0x10, "Low order byte should be 0x10; was \($0.value(forRegister: 8))") | 		XCTAssert(m6522.value(forRegister: 8) == 0x10, "Low order byte should be 0x10; was \(m6522.value(forRegister: 8))") | ||||||
| 			XCTAssert($0.value(forRegister: 9) == 0x20, "High order byte should be 0x20; was \($0.value(forRegister: 9))") | 		XCTAssert(m6522.value(forRegister: 9) == 0x20, "High order byte should be 0x20; was \(m6522.value(forRegister: 9))") | ||||||
|  |  | ||||||
| 		// write the low-byte latch | 		// write the low-byte latch | ||||||
| 			$0.setValue(0x50, forRegister: 9) | 		m6522.setValue(0x50, forRegister: 9) | ||||||
|  |  | ||||||
| 		// complete the cycle | 		// complete the cycle | ||||||
| 			$0.run(forHalfCycles: 2) | 		m6522.run(forHalfCycles: 2) | ||||||
|  |  | ||||||
| 		// chek that the latched value has been copied | 		// chek that the latched value has been copied | ||||||
| 			XCTAssert($0.value(forRegister: 8) == 0x40, "Low order byte should be 0x50; was \($0.value(forRegister: 8))") | 		XCTAssert(m6522.value(forRegister: 8) == 0x40, "Low order byte should be 0x50; was \(m6522.value(forRegister: 8))") | ||||||
| 			XCTAssert($0.value(forRegister: 9) == 0x50, "High order byte should be 0x40; was \($0.value(forRegister: 9))") | 		XCTAssert(m6522.value(forRegister: 9) == 0x50, "High order byte should be 0x40; was \(m6522.value(forRegister: 9))") | ||||||
| 		} |  | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	func testTimerReload() { | 	func testTimerReload() { | ||||||
| 		with6522 { | 		// set timer 1 to a value of m6522010, enable repeating mode | ||||||
| 			// set timer 1 to a value of $0010, enable repeating mode | 		m6522.setValue(16, forRegister: 4) | ||||||
| 			$0.setValue(16, forRegister: 4) | 		m6522.setValue(0, forRegister: 5) | ||||||
| 			$0.setValue(0, forRegister: 5) | 		m6522.setValue(0x40, forRegister: 11) | ||||||
| 			$0.setValue(0x40, forRegister: 11) | 		m6522.setValue(0x40 | 0x80, forRegister: 14) | ||||||
| 			$0.setValue(0x40 | 0x80, forRegister: 14) |  | ||||||
|  |  | ||||||
| 		// complete the cycle to set initial values | 		// complete the cycle to set initial values | ||||||
| 			$0.run(forHalfCycles: 2) | 		m6522.run(forHalfCycles: 2) | ||||||
|  |  | ||||||
| 		// run for 16 cycles | 		// run for 16 cycles | ||||||
| 			$0.run(forHalfCycles: 32) | 		m6522.run(forHalfCycles: 32) | ||||||
|  |  | ||||||
| 		// check that the timer has gone down to 0 but not yet triggered an interrupt | 		// check that the timer has gone down to 0 but not yet triggered an interrupt | ||||||
| 			XCTAssert($0.value(forRegister: 4) == 0, "Low order byte should be 0; was \($0.value(forRegister: 4))") | 		XCTAssert(m6522.value(forRegister: 4) == 0, "Low order byte should be 0; was \(m6522.value(forRegister: 4))") | ||||||
| 			XCTAssert($0.value(forRegister: 5) == 0, "High order byte should be 0; was \($0.value(forRegister: 5))") | 		XCTAssert(m6522.value(forRegister: 5) == 0, "High order byte should be 0; was \(m6522.value(forRegister: 5))") | ||||||
| 			XCTAssert(!$0.irqLine, "IRQ should not yet be active") | 		XCTAssert(!m6522.irqLine, "IRQ should not yet be active") | ||||||
|  |  | ||||||
| 		// check that two half-cycles later the timer is $ffff but IRQ still hasn't triggered | 		// check that two half-cycles later the timer is $ffff but IRQ still hasn't triggered | ||||||
| 			$0.run(forHalfCycles: 2) | 		m6522.run(forHalfCycles: 2) | ||||||
| 			XCTAssert($0.value(forRegister: 4) == 0xff, "Low order byte should be 0xff; was \($0.value(forRegister: 4))") | 		XCTAssert(m6522.value(forRegister: 4) == 0xff, "Low order byte should be 0xff; was \(m6522.value(forRegister: 4))") | ||||||
| 			XCTAssert($0.value(forRegister: 5) == 0xff, "High order byte should be 0xff; was \($0.value(forRegister: 5))") | 		XCTAssert(m6522.value(forRegister: 5) == 0xff, "High order byte should be 0xff; was \(m6522.value(forRegister: 5))") | ||||||
| 			XCTAssert(!$0.irqLine, "IRQ should not yet be active") | 		XCTAssert(!m6522.irqLine, "IRQ should not yet be active") | ||||||
|  |  | ||||||
| 		// check that one half-cycle later the timer is still $ffff and IRQ has triggered... | 		// check that one half-cycle later the timer is still $ffff and IRQ has triggered... | ||||||
| 			$0.run(forHalfCycles: 1) | 		m6522.run(forHalfCycles: 1) | ||||||
| 			XCTAssert($0.irqLine, "IRQ should be active") | 		XCTAssert(m6522.irqLine, "IRQ should be active") | ||||||
| 			XCTAssert($0.value(forRegister: 4) == 0xff, "Low order byte should be 0xff; was \($0.value(forRegister: 4))") | 		XCTAssert(m6522.value(forRegister: 4) == 0xff, "Low order byte should be 0xff; was \(m6522.value(forRegister: 4))") | ||||||
| 			XCTAssert($0.value(forRegister: 5) == 0xff, "High order byte should be 0xff; was \($0.value(forRegister: 5))") | 		XCTAssert(m6522.value(forRegister: 5) == 0xff, "High order byte should be 0xff; was \(m6522.value(forRegister: 5))") | ||||||
|  |  | ||||||
| 		// ... but that reading the timer cleared the interrupt | 		// ... but that reading the timer cleared the interrupt | ||||||
| 			XCTAssert(!$0.irqLine, "IRQ should be active") | 		XCTAssert(!m6522.irqLine, "IRQ should be active") | ||||||
|  |  | ||||||
| 		// check that one half-cycles later the timer has reloaded | 		// check that one half-cycles later the timer has reloaded | ||||||
| 			$0.run(forHalfCycles: 1) | 		m6522.run(forHalfCycles: 1) | ||||||
| 			XCTAssert($0.value(forRegister: 4) == 0x10, "Low order byte should be 0x10; was \($0.value(forRegister: 4))") | 		XCTAssert(m6522.value(forRegister: 4) == 0x10, "Low order byte should be 0x10; was \(m6522.value(forRegister: 4))") | ||||||
| 			XCTAssert($0.value(forRegister: 5) == 0x00, "High order byte should be 0x00; was \($0.value(forRegister: 5))") | 		XCTAssert(m6522.value(forRegister: 5) == 0x00, "High order byte should be 0x00; was \(m6522.value(forRegister: 5))") | ||||||
| 		} |  | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  |  | ||||||
| 	// MARK: Data direction tests | 	// MARK: Data direction tests | ||||||
| 	func testDataDirection() { | 	func testDataDirection() { | ||||||
| 		with6522 { |  | ||||||
| 		// set low four bits of register B as output, the top four as input | 		// set low four bits of register B as output, the top four as input | ||||||
| 			$0.setValue(0xf0, forRegister: 2) | 		m6522.setValue(0xf0, forRegister: 2) | ||||||
|  |  | ||||||
| 		// ask to output 0x8c | 		// ask to output 0x8c | ||||||
| 			$0.setValue(0x8c, forRegister: 0) | 		m6522.setValue(0x8c, forRegister: 0) | ||||||
|  |  | ||||||
| 		// complete the cycle | 		// complete the cycle | ||||||
| 			$0.run(forHalfCycles: 2) | 		m6522.run(forHalfCycles: 2) | ||||||
|  |  | ||||||
| 		// set current input as 0xda | 		// set current input as 0xda | ||||||
| 			$0.portBInput = 0xda | 		m6522.portBInput = 0xda | ||||||
|  |  | ||||||
| 		// test that the result of reading register B is therefore 0x8a | 		// test that the result of reading register B is therefore 0x8a | ||||||
| 			XCTAssert($0.value(forRegister: 0) == 0x8a, "Data direction register should mix input and output; got \($0.value(forRegister: 0))") | 		XCTAssert(m6522.value(forRegister: 0) == 0x8a, "Data direction register should mix input and output; got \(m6522.value(forRegister: 0))") | ||||||
| 		} | 	} | ||||||
|  |  | ||||||
|  | 	func testShiftDisabled() { | ||||||
|  | 		/* | ||||||
|  | 			Mode 0 disables the Shift Register. In this mode the microprocessor can | ||||||
|  | 			write or read the SR and the SR will shift on each CB1 positive edge | ||||||
|  | 			shifting in the value on CB2. In this mode the SR Interrupt Flag is | ||||||
|  | 			disabled (held to a logic 0). | ||||||
|  | 		*/ | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	func testShiftInUnderT2() { | ||||||
|  | 		/* | ||||||
|  | 			In mode 1, the shifting rate is controlled by the low order 8 bits of T2 | ||||||
|  | 			(Figure 22). Shift pulses are generated on the CB1 pin to control shifting | ||||||
|  | 			in external devices. The time between transitions of this output clock is a | ||||||
|  | 			function of the system clock period and the contents of the low order T2 | ||||||
|  | 			latch (N). | ||||||
|  |  | ||||||
|  | 			The shifting operation is triggered by the read or write of the SR if the | ||||||
|  | 			SR flag is set in the IFR. Otherwise the first shift will occur at the next | ||||||
|  | 			time-out of T2 after a read or write of the SR. Data is shifted first into | ||||||
|  | 			the low order bit of SR and is then shifted into the next higher order bit | ||||||
|  | 			of the shift register on the negative-going edge of each clock pulse. The | ||||||
|  | 			input data should change before the positive-going edge of the CB1 clock | ||||||
|  | 			pulse. This data is shifted into shift register during the 02 clock cycle | ||||||
|  | 			following the positive-going edge of the CB1 clock pulse. After 8 CB1 clock | ||||||
|  | 			pulses, the shift register interrupt flag will set and IRQ will go low. | ||||||
|  | 		*/ | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	func testShiftInUnderPhase2() { | ||||||
|  | 		/* | ||||||
|  | 			In mode 2, the shift rate is a direct function of the system clock | ||||||
|  | 			frequency (Figure 23). CB1 becomes an output which generates shift pulses | ||||||
|  | 			for controlling external devices. Timer 2 operates as an independent | ||||||
|  | 			interval timer and has no effect on SR. The shifting operation is triggered | ||||||
|  | 			by reading or writing the Shift Register. Data is shifted, first into bit 0 | ||||||
|  | 			and is then shifted into the next higher order bit of the shift register on | ||||||
|  | 			the trailing edge of each 02 clock pulse. After 8 clock pulses, the shift | ||||||
|  | 			register interrupt flag will be set, and the output clock pulses on CB1 | ||||||
|  | 			will stop. | ||||||
|  | 		*/ | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	func testShiftInUnderCB1() { | ||||||
|  | 		/* | ||||||
|  | 			In mode 3, external pin CB1 becomes an input (Figure 24). This allows an | ||||||
|  | 			external device to load the shift register at its own pace. The shift | ||||||
|  | 			register counter will interrupt the processor each time 8 bits have been | ||||||
|  | 			shifted in. However the shift register counter does not stop the shifting | ||||||
|  | 			operation; it acts simply as a pulse counter. Reading or writing the Shift | ||||||
|  | 			Register resets the Interrupt Flag and initializes the SR counter to count | ||||||
|  | 			another 8 pulses. | ||||||
|  |  | ||||||
|  | 			Note that the data is shifted during the first system clock cycle | ||||||
|  | 			following the positive-going edge of the CB1 shift pulse. For this reason, | ||||||
|  | 			data must be held stable during the first full cycle following CB1 going | ||||||
|  | 			high. | ||||||
|  | 		*/ | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	func testShiftOutUnderT2FreeRunning() { | ||||||
|  | 		/* | ||||||
|  | 			Mode 4 is very similar to mode 5 in which the shifting rate is set by T2. | ||||||
|  | 			However, in mode 4 the SR Counter does not stop the shifting operation | ||||||
|  | 			(Figure 25). Since the Shift Register bit 7 (SR7) is recirculated back into | ||||||
|  | 			bit 0, the 8 bits loaded into the Shift Register will be clocked onto CB2 | ||||||
|  | 			repetitively. In this mode the Shift Register Counter is disabled. | ||||||
|  | 		*/ | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	func testShiftOutUnderT2() { | ||||||
|  | 		/* | ||||||
|  | 			In mode 5, the shift rate is controlled by T2 (as in mode 4). The shifting | ||||||
|  | 			operation is triggered by the read or write of the SR if the SR flag is set | ||||||
|  | 			in the IFR (Figure 26). Otherwise the first shift will occur at the next | ||||||
|  | 			time-out of T2 after a read or write of the SR. However, with each read or | ||||||
|  | 			write of the shift register the SR Counter is reset and 8 bits are shifted | ||||||
|  | 			onto CB2. At the same time, 8 shift pulses are generated on CB1 to control | ||||||
|  | 			shifting in external devices. After the 8 shift pulses, the shifting is | ||||||
|  | 			disabled, the SR Interrupt Flag is set and CB2 remains at the last data | ||||||
|  | 			level. | ||||||
|  | 		*/ | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	func testShiftOutUnderPhase2() { | ||||||
|  | 		/* | ||||||
|  | 			In mode 6, the shift rate is controlled by the 02 system clock (Figure 27). | ||||||
|  |  | ||||||
|  | 			(... and I'm assuming the same behaviour as shift out under control of T2 | ||||||
|  | 			otherwise, based on original context) | ||||||
|  | 		*/ | ||||||
|  | 		// Set the shift register to a non-zero something. | ||||||
|  | 		m6522.setValue(0xaa, forRegister: 10) | ||||||
|  |  | ||||||
|  | 		// Set shift register mode 6. | ||||||
|  | 		m6522.setValue(6 << 2, forRegister: 11) | ||||||
|  |  | ||||||
|  | 		// Make sure the shift register's interrupt bit is set. | ||||||
|  | 		m6522.run(forHalfCycles: 16) | ||||||
|  | 		XCTAssertEqual(m6522.value(forRegister: 13) & 0x04, 0x04) | ||||||
|  |  | ||||||
|  | 		// Test that output is now inhibited: CB2 should remain unchanged. | ||||||
|  | 		let initialOutput = m6522.value(forControlLine: .two, port: .B) | ||||||
|  | 		for _ in 1...8 { | ||||||
|  | 			m6522.run(forHalfCycles: 2) | ||||||
|  | 			XCTAssertEqual(m6522.value(forControlLine: .two, port: .B), initialOutput) | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		// Set a new value to the shift register. | ||||||
|  | 		m6522.setValue(0x16, forRegister: 10) | ||||||
|  |  | ||||||
|  | 		// Test that the new value is shifted out. | ||||||
|  | 		var output = 0 | ||||||
|  | 		for _ in 1..<8 { | ||||||
|  | 			m6522.run(forHalfCycles: 2) | ||||||
|  | 			output = (output << 1) | (m6522.value(forControlLine: .two, port: .B) ? 1 : 0) | ||||||
|  | 		} | ||||||
|  | 		XCTAssertEqual(output, 0x16) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	func testShiftOutUnderCB1() { | ||||||
|  | 		/* | ||||||
|  | 			In mode 7, shifting is controlled by pulses applied to the CB1 pin by an | ||||||
|  | 			external device (Figure 28). The SR counter sets the SR Interrupt Flag each | ||||||
|  | 			time it counts 8 pulses but it does not disable the shifting function. Each | ||||||
|  | 			time the microprocessor, writes or reads the shift register, the SR | ||||||
|  | 			Interrupt Flag is reset and the SR counter is initialized to begin counting | ||||||
|  | 			the next 8 shift pulses on pin CB1. After 8 shift pulses, the Interrupt | ||||||
|  | 			Flag is set. The microprocessor can then load the shift register with the | ||||||
|  | 			next byte of data. | ||||||
|  | 		*/ | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|   | |||||||
							
								
								
									
										
											BIN
										
									
								
								OSBindings/Mac/Clock SignalTests/68000 Coverage/OPCLOGR2.BIN
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								OSBindings/Mac/Clock SignalTests/68000 Coverage/OPCLOGR2.BIN
									
									
									
									
									
										Executable file
									
								
							
										
											Binary file not shown.
										
									
								
							| @@ -0,0 +1,5 @@ | |||||||
|  | This file provides a record of which opcodes are valid and which are invalid on a | ||||||
|  | real 68000. It was generated by AtariZoll and made available via | ||||||
|  | http://www.atari-forum.com/viewtopic.php?f=68&t=26820 . | ||||||
|  |  | ||||||
|  | No licence was specified. | ||||||
							
								
								
									
										1580
									
								
								OSBindings/Mac/Clock SignalTests/68000ArithmeticTests.mm
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1580
									
								
								OSBindings/Mac/Clock SignalTests/68000ArithmeticTests.mm
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										291
									
								
								OSBindings/Mac/Clock SignalTests/68000BCDTests.mm
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										291
									
								
								OSBindings/Mac/Clock SignalTests/68000BCDTests.mm
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,291 @@ | |||||||
|  | // | ||||||
|  | //  68000BCDTests.m | ||||||
|  | //  Clock SignalTests | ||||||
|  | // | ||||||
|  | //  Created by Thomas Harte on 29/06/2019. | ||||||
|  | // | ||||||
|  | //  Largely ported from the tests of the Portable 68k Emulator. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | #import <XCTest/XCTest.h> | ||||||
|  |  | ||||||
|  | #include "TestRunner68000.hpp" | ||||||
|  |  | ||||||
|  | @interface M68000BCDTests : XCTestCase | ||||||
|  | @end | ||||||
|  |  | ||||||
|  | @implementation M68000BCDTests { | ||||||
|  | 	std::unique_ptr<RAM68000> _machine; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | - (void)setUp { | ||||||
|  |     _machine.reset(new RAM68000()); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | - (void)tearDown { | ||||||
|  | 	_machine.reset(); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // MARK: ABCD | ||||||
|  |  | ||||||
|  | - (void)testABCD { | ||||||
|  | 	_machine->set_program({ | ||||||
|  | 		0xc302,		// ABCD D2, D1 | ||||||
|  | 	}); | ||||||
|  | 	auto state = _machine->get_processor_state(); | ||||||
|  | 	state.data[1] = 0x1234567a; | ||||||
|  | 	state.data[2] = 0xf745ff78; | ||||||
|  | 	_machine->set_processor_state(state); | ||||||
|  | 	_machine->run_for_instructions(1); | ||||||
|  |  | ||||||
|  | 	state = _machine->get_processor_state(); | ||||||
|  | 	XCTAssert(state.status & Flag::Carry); | ||||||
|  | 	XCTAssertEqual(state.data[1], 0x12345658); | ||||||
|  | 	XCTAssertEqual(state.data[2], 0xf745ff78); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | - (void)testABCDZero { | ||||||
|  | 	_machine->set_program({ | ||||||
|  | 		0xc302,		// ABCD D2, D1 | ||||||
|  | 	}); | ||||||
|  | 	auto state = _machine->get_processor_state(); | ||||||
|  | 	state.data[1] = 0x12345600; | ||||||
|  | 	state.data[2] = 0x12345600; | ||||||
|  | 	state.status = Flag::Zero; | ||||||
|  | 	_machine->set_processor_state(state); | ||||||
|  | 	_machine->run_for_instructions(1); | ||||||
|  |  | ||||||
|  | 	state = _machine->get_processor_state(); | ||||||
|  | 	XCTAssert(state.status & Flag::Zero); | ||||||
|  | 	XCTAssertEqual(state.data[1], 0x12345600); | ||||||
|  | 	XCTAssertEqual(state.data[2], 0x12345600); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | - (void)testABCDNegative { | ||||||
|  | 	_machine->set_program({ | ||||||
|  | 		0xc302,		// ABCD D2, D1 | ||||||
|  | 	}); | ||||||
|  | 	auto state = _machine->get_processor_state(); | ||||||
|  | 	state.data[1] = 0x12345645; | ||||||
|  | 	state.data[2] = 0x12345654; | ||||||
|  | 	state.status = Flag::Zero; | ||||||
|  | 	_machine->set_processor_state(state); | ||||||
|  | 	_machine->run_for_instructions(1); | ||||||
|  |  | ||||||
|  | 	state = _machine->get_processor_state(); | ||||||
|  | 	XCTAssert(state.status & Flag::Negative); | ||||||
|  | 	XCTAssertEqual(state.data[1], 0x12345699); | ||||||
|  | 	XCTAssertEqual(state.data[2], 0x12345654); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | - (void)testABCDWithX { | ||||||
|  | 	_machine->set_program({ | ||||||
|  | 		0xc302,		// ABCD D2, D1 | ||||||
|  | 	}); | ||||||
|  | 	auto state = _machine->get_processor_state(); | ||||||
|  | 	state.data[1] = 0x12345645; | ||||||
|  | 	state.data[2] = 0x12345654; | ||||||
|  | 	state.status = Flag::Extend; | ||||||
|  | 	_machine->set_processor_state(state); | ||||||
|  | 	_machine->run_for_instructions(1); | ||||||
|  |  | ||||||
|  | 	state = _machine->get_processor_state(); | ||||||
|  | 	XCTAssert(state.status & Flag::Carry); | ||||||
|  | 	XCTAssertEqual(state.data[1], 0x12345600); | ||||||
|  | 	XCTAssertEqual(state.data[2], 0x12345654); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | - (void)testABCDOverflow { | ||||||
|  | 	_machine->set_program({ | ||||||
|  | 		0xc302,		// ABCD D2, D1 | ||||||
|  | 	}); | ||||||
|  | 	auto state = _machine->get_processor_state(); | ||||||
|  | 	state.data[1] = 0x1234563e; | ||||||
|  | 	state.data[2] = 0x1234563e; | ||||||
|  | 	state.status = Flag::Extend; | ||||||
|  | 	_machine->set_processor_state(state); | ||||||
|  | 	_machine->run_for_instructions(1); | ||||||
|  |  | ||||||
|  | 	state = _machine->get_processor_state(); | ||||||
|  | 	XCTAssert(state.status & Flag::Overflow); | ||||||
|  | 	XCTAssertEqual(state.data[1], 0x12345683); | ||||||
|  | 	XCTAssertEqual(state.data[2], 0x1234563e); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | - (void)testABCDPredecDifferent { | ||||||
|  | 	_machine->set_program({ | ||||||
|  | 		0xc30a,		// ABCD -(A2), -(A1) | ||||||
|  | 	}); | ||||||
|  | 	*_machine->ram_at(0x3000) = 0xa200; | ||||||
|  | 	*_machine->ram_at(0x4000) = 0x1900; | ||||||
|  |  | ||||||
|  | 	auto state = _machine->get_processor_state(); | ||||||
|  | 	state.address[1] = 0x3001; | ||||||
|  | 	state.address[2] = 0x4001; | ||||||
|  | 	state.status = Flag::Extend; | ||||||
|  | 	_machine->set_processor_state(state); | ||||||
|  | 	_machine->run_for_instructions(1); | ||||||
|  |  | ||||||
|  | 	state = _machine->get_processor_state(); | ||||||
|  | 	XCTAssert(state.status & Flag::Carry); | ||||||
|  | 	XCTAssert(state.status & Flag::Extend); | ||||||
|  | 	XCTAssertEqual(state.address[1], 0x3000); | ||||||
|  | 	XCTAssertEqual(state.address[2], 0x4000); | ||||||
|  | 	XCTAssertEqual(*_machine->ram_at(0x3000), 0x2200); | ||||||
|  | 	XCTAssertEqual(*_machine->ram_at(0x4000), 0x1900); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | - (void)testABCDPredecSame { | ||||||
|  | 	_machine->set_program({ | ||||||
|  | 		0xc309,		// ABCD -(A1), -(A1) | ||||||
|  | 	}); | ||||||
|  | 	*_machine->ram_at(0x3000) = 0x19a2; | ||||||
|  |  | ||||||
|  | 	auto state = _machine->get_processor_state(); | ||||||
|  | 	state.address[1] = 0x3002; | ||||||
|  | 	state.status = Flag::Extend; | ||||||
|  | 	_machine->set_processor_state(state); | ||||||
|  | 	_machine->run_for_instructions(1); | ||||||
|  |  | ||||||
|  | 	state = _machine->get_processor_state(); | ||||||
|  | 	XCTAssert(state.status & Flag::Carry); | ||||||
|  | 	XCTAssert(state.status & Flag::Extend); | ||||||
|  | 	XCTAssertEqual(state.address[1], 0x3000); | ||||||
|  | 	XCTAssertEqual(*_machine->ram_at(0x3000), 0x22a2); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // MARK: NBCD | ||||||
|  |  | ||||||
|  | - (void)performNBCDd1:(uint32_t)d1 ccr:(uint8_t)ccr { | ||||||
|  | 	_machine->set_program({ | ||||||
|  | 		0x4801		// NBCD D1 | ||||||
|  | 	}); | ||||||
|  | 	auto state = _machine->get_processor_state(); | ||||||
|  | 	state.status |= ccr; | ||||||
|  | 	state.data[1] = d1; | ||||||
|  |  | ||||||
|  | 	_machine->set_processor_state(state); | ||||||
|  | 	_machine->run_for_instructions(1); | ||||||
|  |  | ||||||
|  | 	XCTAssertEqual(6, _machine->get_cycle_count()); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | - (void)testNBCD_Dn { | ||||||
|  | 	[self performNBCDd1:0x7a ccr:0]; | ||||||
|  |  | ||||||
|  | 	const auto state = _machine->get_processor_state(); | ||||||
|  | 	XCTAssertEqual(state.data[1], 0x20); | ||||||
|  | 	XCTAssertEqual(state.status & Flag::ConditionCodes, Flag::Extend | Flag::Carry | Flag::Overflow); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | - (void)testNBCD_Dn_extend { | ||||||
|  | 	[self performNBCDd1:0x1234567a ccr:Flag::Extend | Flag::Zero]; | ||||||
|  |  | ||||||
|  | 	const auto state = _machine->get_processor_state(); | ||||||
|  | 	XCTAssertEqual(state.data[1], 0x1234561f); | ||||||
|  | 	XCTAssertEqual(state.status & Flag::ConditionCodes, Flag::Extend | Flag::Carry | Flag::Overflow); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | - (void)testNBCD_Dn_zero { | ||||||
|  | 	[self performNBCDd1:0x12345600 ccr:Flag::Zero]; | ||||||
|  |  | ||||||
|  | 	const auto state = _machine->get_processor_state(); | ||||||
|  | 	XCTAssertEqual(state.data[1], 0x12345600); | ||||||
|  | 	XCTAssertEqual(state.status & Flag::ConditionCodes, Flag::Zero); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | - (void)testNBCD_Dn_negative { | ||||||
|  | 	[self performNBCDd1:0x123456ff ccr:Flag::Extend | Flag::Zero]; | ||||||
|  |  | ||||||
|  | 	const auto state = _machine->get_processor_state(); | ||||||
|  | 	XCTAssertEqual(state.data[1], 0x1234569a); | ||||||
|  | 	XCTAssertEqual(state.status & Flag::ConditionCodes, Flag::Extend | Flag::Carry | Flag::Negative); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | - (void)testNBCD_Dn_XXXw { | ||||||
|  | 	_machine->set_program({ | ||||||
|  | 		0x4838, 0x3000		// NBCD ($3000).w | ||||||
|  | 	}); | ||||||
|  | 	*_machine->ram_at(0x3000) = 0x0100; | ||||||
|  |  | ||||||
|  | 	_machine->run_for_instructions(1); | ||||||
|  |  | ||||||
|  | 	const auto state = _machine->get_processor_state(); | ||||||
|  | 	XCTAssertEqual(16, _machine->get_cycle_count()); | ||||||
|  | 	XCTAssertEqual(*_machine->ram_at(0x3000), 0x9900); | ||||||
|  | 	XCTAssertEqual(state.status & Flag::ConditionCodes, Flag::Extend | Flag::Carry | Flag::Negative); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // MARK: SBCD | ||||||
|  |  | ||||||
|  | - (void)performSBCDd1:(uint32_t)d1 d2:(uint32_t)d2 ccr:(uint8_t)ccr { | ||||||
|  | 	_machine->set_program({ | ||||||
|  | 		0x8302		// SBCD D2, D1 | ||||||
|  | 	}); | ||||||
|  | 	auto state = _machine->get_processor_state(); | ||||||
|  | 	state.status |= ccr; | ||||||
|  | 	state.data[1] = d1; | ||||||
|  | 	state.data[2] = d2; | ||||||
|  |  | ||||||
|  | 	_machine->set_processor_state(state); | ||||||
|  | 	_machine->run_for_instructions(1); | ||||||
|  |  | ||||||
|  | 	state = _machine->get_processor_state(); | ||||||
|  | 	XCTAssertEqual(6, _machine->get_cycle_count()); | ||||||
|  | 	XCTAssertEqual(state.data[2], d2); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | - (void)testSBCD_Dn { | ||||||
|  | 	[self performSBCDd1:0x12345689 d2:0xf745ff78 ccr:Flag::Zero]; | ||||||
|  |  | ||||||
|  | 	const auto state = _machine->get_processor_state(); | ||||||
|  | 	XCTAssertEqual(state.data[1], 0x12345611); | ||||||
|  | 	XCTAssertEqual(state.status & Flag::ConditionCodes, 0); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | - (void)testSBCD_Dn_zero { | ||||||
|  | 	[self performSBCDd1:0x123456ff d2:0xf745ffff ccr:Flag::Zero]; | ||||||
|  |  | ||||||
|  | 	const auto state = _machine->get_processor_state(); | ||||||
|  | 	XCTAssertEqual(state.data[1], 0x12345600); | ||||||
|  | 	XCTAssertEqual(state.status & Flag::ConditionCodes, Flag::Zero); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | - (void)testSBCD_Dn_negative { | ||||||
|  | 	[self performSBCDd1:0x12345634 d2:0xf745ff45 ccr:Flag::Extend]; | ||||||
|  |  | ||||||
|  | 	const auto state = _machine->get_processor_state(); | ||||||
|  | 	XCTAssertEqual(state.data[1], 0x12345688); | ||||||
|  | 	XCTAssertEqual(state.status & Flag::ConditionCodes, Flag::Extend | Flag::Carry | Flag::Negative); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | - (void)testSBCD_Dn_overflow { | ||||||
|  | 	[self performSBCDd1:0x123456a9 d2:0xf745ffff ccr:Flag::Extend]; | ||||||
|  |  | ||||||
|  | 	const auto state = _machine->get_processor_state(); | ||||||
|  | 	XCTAssertEqual(state.data[1], 0x12345643); | ||||||
|  | 	XCTAssertEqual(state.status & Flag::ConditionCodes, Flag::Extend | Flag::Carry | Flag::Overflow); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | - (void)testSBCD_Dn_PreDec { | ||||||
|  | 	_machine->set_program({ | ||||||
|  | 		0x830a		// SBCD -(A2), -(A1) | ||||||
|  | 	}); | ||||||
|  | 	*_machine->ram_at(0x3000) = 0xa200; | ||||||
|  | 	*_machine->ram_at(0x4000) = 0x1900; | ||||||
|  | 	auto state = _machine->get_processor_state(); | ||||||
|  | 	state.address[1] = 0x3001; | ||||||
|  | 	state.address[2] = 0x4001; | ||||||
|  | 	state.status |= Flag::Extend; | ||||||
|  |  | ||||||
|  | 	_machine->set_processor_state(state); | ||||||
|  | 	_machine->run_for_instructions(1); | ||||||
|  |  | ||||||
|  | 	state = _machine->get_processor_state(); | ||||||
|  | 	XCTAssertEqual(18, _machine->get_cycle_count()); | ||||||
|  | 	XCTAssertEqual(*_machine->ram_at(0x3000), 0x8200); | ||||||
|  | 	XCTAssertEqual(*_machine->ram_at(0x4000), 0x1900); | ||||||
|  | 	XCTAssertEqual(state.status & Flag::ConditionCodes, Flag::Negative); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @end | ||||||
							
								
								
									
										1502
									
								
								OSBindings/Mac/Clock SignalTests/68000BitwiseTests.mm
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1502
									
								
								OSBindings/Mac/Clock SignalTests/68000BitwiseTests.mm
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										557
									
								
								OSBindings/Mac/Clock SignalTests/68000ControlFlowTests.mm
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										557
									
								
								OSBindings/Mac/Clock SignalTests/68000ControlFlowTests.mm
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,557 @@ | |||||||
|  | // | ||||||
|  | //  68000ControlFlowTests.m | ||||||
|  | //  Clock SignalTests | ||||||
|  | // | ||||||
|  | //  Created by Thomas Harte on 28/06/2019. | ||||||
|  | // | ||||||
|  | //  Largely ported from the tests of the Portable 68k Emulator. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | #import <XCTest/XCTest.h> | ||||||
|  |  | ||||||
|  | #include "TestRunner68000.hpp" | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @interface M68000ControlFlowTests : XCTestCase | ||||||
|  | @end | ||||||
|  |  | ||||||
|  | @implementation M68000ControlFlowTests { | ||||||
|  | 	std::unique_ptr<RAM68000> _machine; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | - (void)setUp { | ||||||
|  |     _machine.reset(new RAM68000()); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | - (void)tearDown { | ||||||
|  | 	_machine.reset(); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // MARK: Bcc | ||||||
|  |  | ||||||
|  | - (void)performBccb:(uint16_t)opcode { | ||||||
|  | 	_machine->set_program({ | ||||||
|  | 		uint16_t(opcode | 6)	// Bcc.b +6 | ||||||
|  | 	}); | ||||||
|  |  | ||||||
|  | 	_machine->run_for_instructions(1); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | - (void)performBccw:(uint16_t)opcode { | ||||||
|  | 	_machine->set_program({ | ||||||
|  | 		opcode, 0x0006			// Bcc.w +6 | ||||||
|  | 	}); | ||||||
|  |  | ||||||
|  | 	_machine->run_for_instructions(1); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | - (void)testBHIb { | ||||||
|  | 	[self performBccb:0x6200]; | ||||||
|  |  | ||||||
|  | 	const auto state = _machine->get_processor_state(); | ||||||
|  | 	XCTAssertEqual(state.program_counter, 0x1008 + 4); | ||||||
|  | 	XCTAssertEqual(_machine->get_cycle_count(), 10); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | - (void)testBLOb { | ||||||
|  | 	[self performBccb:0x6500]; | ||||||
|  |  | ||||||
|  | 	const auto state = _machine->get_processor_state(); | ||||||
|  | 	XCTAssertEqual(state.program_counter, 0x1002 + 4); | ||||||
|  | 	XCTAssertEqual(_machine->get_cycle_count(), 8); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | - (void)testBHIw { | ||||||
|  | 	[self performBccw:0x6200]; | ||||||
|  |  | ||||||
|  | 	const auto state = _machine->get_processor_state(); | ||||||
|  | 	XCTAssertEqual(state.program_counter, 0x1008 + 4); | ||||||
|  | 	XCTAssertEqual(_machine->get_cycle_count(), 10); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | - (void)testBLOw { | ||||||
|  | 	[self performBccw:0x6500]; | ||||||
|  |  | ||||||
|  | 	const auto state = _machine->get_processor_state(); | ||||||
|  | 	XCTAssertEqual(state.program_counter, 0x1004 + 4); | ||||||
|  | 	XCTAssertEqual(_machine->get_cycle_count(), 12); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // MARK: BRA | ||||||
|  |  | ||||||
|  | - (void)testBRAb { | ||||||
|  | 	_machine->set_program({ | ||||||
|  | 		0x6004		// BRA.b +4 | ||||||
|  | 	}); | ||||||
|  |  | ||||||
|  | 	_machine->run_for_instructions(1); | ||||||
|  |  | ||||||
|  | 	const auto state = _machine->get_processor_state(); | ||||||
|  | 	XCTAssertEqual(state.program_counter, 0x1006 + 4); | ||||||
|  | 	XCTAssertEqual(_machine->get_cycle_count(), 10); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | - (void)testBRAw { | ||||||
|  | 	_machine->set_program({ | ||||||
|  | 		0x6000, 0x0004		// BRA.b +4 | ||||||
|  | 	}); | ||||||
|  |  | ||||||
|  | 	_machine->run_for_instructions(1); | ||||||
|  |  | ||||||
|  | 	const auto state = _machine->get_processor_state(); | ||||||
|  | 	XCTAssertEqual(state.program_counter, 0x1006 + 4); | ||||||
|  | 	XCTAssertEqual(_machine->get_cycle_count(), 10); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // MARK: BSR | ||||||
|  |  | ||||||
|  | - (void)testBSRw { | ||||||
|  | 	_machine->set_program({ | ||||||
|  | 		0x6100, 0x0006		// BSR.w $1008 | ||||||
|  | 	}); | ||||||
|  | 	_machine->set_initial_stack_pointer(0x3000); | ||||||
|  |  | ||||||
|  | 	_machine->run_for_instructions(1); | ||||||
|  |  | ||||||
|  | 	const auto state = _machine->get_processor_state(); | ||||||
|  | 	XCTAssertEqual(state.program_counter, 0x1008 + 4); | ||||||
|  | 	XCTAssertEqual(state.stack_pointer(), 0x2ffc); | ||||||
|  | 	XCTAssertEqual(state.status & Flag::ConditionCodes, 0); | ||||||
|  | 	XCTAssertEqual(*_machine->ram_at(0x2ffc), 0); | ||||||
|  | 	XCTAssertEqual(*_machine->ram_at(0x2ffe), 0x1004); | ||||||
|  |  | ||||||
|  | 	XCTAssertEqual(_machine->get_cycle_count(), 18); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | - (void)testBSRb { | ||||||
|  | 	_machine->set_program({ | ||||||
|  | 		0x6106		// BSR.b $1008 | ||||||
|  | 	}); | ||||||
|  | 	_machine->set_initial_stack_pointer(0x3000); | ||||||
|  |  | ||||||
|  | 	_machine->run_for_instructions(1); | ||||||
|  |  | ||||||
|  | 	const auto state = _machine->get_processor_state(); | ||||||
|  | 	XCTAssertEqual(state.program_counter, 0x1008 + 4); | ||||||
|  | 	XCTAssertEqual(state.stack_pointer(), 0x2ffc); | ||||||
|  | 	XCTAssertEqual(state.status & Flag::ConditionCodes, 0); | ||||||
|  | 	XCTAssertEqual(*_machine->ram_at(0x2ffc), 0); | ||||||
|  | 	XCTAssertEqual(*_machine->ram_at(0x2ffe), 0x1002); | ||||||
|  |  | ||||||
|  | 	XCTAssertEqual(_machine->get_cycle_count(), 18); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // MARK: CHK | ||||||
|  |  | ||||||
|  | - (void)performCHKd1:(uint32_t)d1 d2:(uint32_t)d2 { | ||||||
|  | 	_machine->set_program({ | ||||||
|  | 		0x4581		// CHK D1, D2 | ||||||
|  | 	}); | ||||||
|  | 	auto state = _machine->get_processor_state(); | ||||||
|  | 	state.data[1] = d1; | ||||||
|  | 	state.data[2] = d2; | ||||||
|  | 	state.status |= Flag::ConditionCodes; | ||||||
|  |  | ||||||
|  | 	_machine->set_initial_stack_pointer(0); | ||||||
|  | 	_machine->set_processor_state(state); | ||||||
|  | 	_machine->run_for_instructions(1); | ||||||
|  |  | ||||||
|  | 	state = _machine->get_processor_state(); | ||||||
|  | 	XCTAssertEqual(state.data[1], d1); | ||||||
|  | 	XCTAssertEqual(state.data[2], d2); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | - (void)testCHK_1111v1111 { | ||||||
|  | 	[self performCHKd1:0x1111 d2:0x1111]; | ||||||
|  |  | ||||||
|  | 	const auto state = _machine->get_processor_state(); | ||||||
|  | 	XCTAssertEqual(state.program_counter, 0x1002 + 4); | ||||||
|  | 	XCTAssertEqual(state.status & Flag::ConditionCodes, Flag::Extend); | ||||||
|  | 	XCTAssertEqual(10, _machine->get_cycle_count()); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | - (void)testCHK_1111v0000 { | ||||||
|  | 	[self performCHKd1:0x1111 d2:0x0000]; | ||||||
|  |  | ||||||
|  | 	const auto state = _machine->get_processor_state(); | ||||||
|  | 	XCTAssertEqual(state.program_counter, 0x1002 + 4); | ||||||
|  | 	XCTAssertEqual(state.status & Flag::ConditionCodes, Flag::Extend | Flag::Zero); | ||||||
|  | 	XCTAssertEqual(10, _machine->get_cycle_count()); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | - (void)testCHK_8000v8001 { | ||||||
|  | 	[self performCHKd1:0x8000 d2:0x8001]; | ||||||
|  |  | ||||||
|  | 	const auto state = _machine->get_processor_state(); | ||||||
|  | 	XCTAssertNotEqual(state.program_counter, 0x1002 + 4); | ||||||
|  | 	XCTAssertEqual(state.stack_pointer(), 0xfffffffa); | ||||||
|  | 	XCTAssertEqual(state.status & Flag::ConditionCodes, Flag::Extend); | ||||||
|  | 	XCTAssertEqual(42, _machine->get_cycle_count()); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | - (void)testCHK_8000v8000 { | ||||||
|  | 	[self performCHKd1:0x8000 d2:0x8000]; | ||||||
|  |  | ||||||
|  | 	const auto state = _machine->get_processor_state(); | ||||||
|  | 	XCTAssertNotEqual(state.program_counter, 0x1002 + 4); | ||||||
|  | 	XCTAssertEqual(state.status & Flag::ConditionCodes, Flag::Extend | Flag::Negative); | ||||||
|  | 	XCTAssertEqual(44, _machine->get_cycle_count()); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // MARK: DBcc | ||||||
|  |  | ||||||
|  | - (void)performDBccTestOpcode:(uint16_t)opcode status:(uint16_t)status d2Outcome:(uint32_t)d2Output { | ||||||
|  | 	_machine->set_program({ | ||||||
|  | 		opcode, 0x0008		// DBcc D2, +8 | ||||||
|  | 	}); | ||||||
|  | 	auto state = _machine->get_processor_state(); | ||||||
|  | 	state.status = status; | ||||||
|  | 	state.data[2] = 1; | ||||||
|  |  | ||||||
|  | 	_machine->set_processor_state(state); | ||||||
|  | 	_machine->run_for_instructions(1); | ||||||
|  |  | ||||||
|  | 	state = _machine->get_processor_state(); | ||||||
|  | 	XCTAssertEqual(state.data[2], d2Output); | ||||||
|  | 	XCTAssertEqual(state.status, status); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | - (void)testDBT { | ||||||
|  | 	[self performDBccTestOpcode:0x50ca status:0 d2Outcome:1]; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | - (void)testDBF { | ||||||
|  | 	[self performDBccTestOpcode:0x51ca status:0 d2Outcome:0]; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | - (void)testDBHI { | ||||||
|  | 	[self performDBccTestOpcode:0x52ca status:0 d2Outcome:1]; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | - (void)testDBHI_Carry { | ||||||
|  | 	[self performDBccTestOpcode:0x52ca status:Flag::Carry d2Outcome:0]; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | - (void)testDBHI_Zero { | ||||||
|  | 	[self performDBccTestOpcode:0x52ca status:Flag::Zero d2Outcome:0]; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | - (void)testDBLS_CarryOverflow { | ||||||
|  | 	[self performDBccTestOpcode:0x53ca status:Flag::Carry | Flag::Overflow d2Outcome:1]; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | - (void)testDBLS_Carry { | ||||||
|  | 	[self performDBccTestOpcode:0x53ca status:Flag::Carry d2Outcome:1]; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | - (void)testDBLS_Overflow { | ||||||
|  | 	[self performDBccTestOpcode:0x53ca status:Flag::Overflow d2Outcome:0]; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | - (void)testDBCC_Carry { | ||||||
|  | 	[self performDBccTestOpcode:0x54ca status:Flag::Carry d2Outcome:0]; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | - (void)testDBCC { | ||||||
|  | 	[self performDBccTestOpcode:0x54ca status:0 d2Outcome:1]; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | - (void)testDBCS { | ||||||
|  | 	[self performDBccTestOpcode:0x55ca status:0 d2Outcome:0]; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | - (void)testDBCS_Carry { | ||||||
|  | 	[self performDBccTestOpcode:0x55ca status:Flag::Carry d2Outcome:1]; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | - (void)testDBNE { | ||||||
|  | 	[self performDBccTestOpcode:0x56ca status:0 d2Outcome:1]; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | - (void)testDBNE_Zero { | ||||||
|  | 	[self performDBccTestOpcode:0x56ca status:Flag::Zero d2Outcome:0]; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | - (void)testDBEQ { | ||||||
|  | 	[self performDBccTestOpcode:0x57ca status:0 d2Outcome:0]; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | - (void)testDBEQ_Zero { | ||||||
|  | 	[self performDBccTestOpcode:0x57ca status:Flag::Zero d2Outcome:1]; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | - (void)testDBVC { | ||||||
|  | 	[self performDBccTestOpcode:0x58ca status:0 d2Outcome:1]; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | - (void)testDBVC_Overflow { | ||||||
|  | 	[self performDBccTestOpcode:0x58ca status:Flag::Overflow d2Outcome:0]; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | - (void)testDBVS { | ||||||
|  | 	[self performDBccTestOpcode:0x59ca status:0 d2Outcome:0]; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | - (void)testDBVS_Overflow { | ||||||
|  | 	[self performDBccTestOpcode:0x59ca status:Flag::Overflow d2Outcome:1]; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | - (void)testDBPL { | ||||||
|  | 	[self performDBccTestOpcode:0x5aca status:0 d2Outcome:1]; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | - (void)testDBPL_Negative { | ||||||
|  | 	[self performDBccTestOpcode:0x5aca status:Flag::Negative d2Outcome:0]; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | - (void)testDBMI { | ||||||
|  | 	[self performDBccTestOpcode:0x5bca status:0 d2Outcome:0]; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | - (void)testDBMI_Negative { | ||||||
|  | 	[self performDBccTestOpcode:0x5bca status:Flag::Negative d2Outcome:1]; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | - (void)testDBGE_NegativeOverflow { | ||||||
|  | 	[self performDBccTestOpcode:0x5cca status:Flag::Negative | Flag::Overflow d2Outcome:1]; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | - (void)testDBGE { | ||||||
|  | 	[self performDBccTestOpcode:0x5cca status:0 d2Outcome:1]; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | - (void)testDBGE_Negative { | ||||||
|  | 	[self performDBccTestOpcode:0x5cca status:Flag::Negative d2Outcome:0]; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | - (void)testDBGE_Overflow { | ||||||
|  | 	[self performDBccTestOpcode:0x5cca status:Flag::Overflow d2Outcome:0]; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | - (void)testDBLT_NegativeOverflow { | ||||||
|  | 	[self performDBccTestOpcode:0x5dca status:Flag::Negative | Flag::Overflow d2Outcome:0]; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | - (void)testDBLT { | ||||||
|  | 	[self performDBccTestOpcode:0x5dca status:0 d2Outcome:0]; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | - (void)testDBLT_Negative { | ||||||
|  | 	[self performDBccTestOpcode:0x5dca status:Flag::Negative d2Outcome:1]; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | - (void)testDBLT_Overflow { | ||||||
|  | 	[self performDBccTestOpcode:0x5dca status:Flag::Overflow d2Outcome:1]; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | - (void)testDBGT { | ||||||
|  | 	[self performDBccTestOpcode:0x5eca status:0 d2Outcome:1]; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | - (void)testDBGT_ZeroNegativeOverflow { | ||||||
|  | 	[self performDBccTestOpcode:0x5eca status:Flag::Zero | Flag::Negative | Flag::Overflow d2Outcome:0]; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | - (void)testDBGT_NegativeOverflow { | ||||||
|  | 	[self performDBccTestOpcode:0x5eca status:Flag::Negative | Flag::Overflow d2Outcome:1]; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | - (void)testDBGT_Zero { | ||||||
|  | 	[self performDBccTestOpcode:0x5eca status:Flag::Zero d2Outcome:0]; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | - (void)testDBLE { | ||||||
|  | 	[self performDBccTestOpcode:0x5fca status:0 d2Outcome:0]; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | - (void)testDBLE_Zero { | ||||||
|  | 	[self performDBccTestOpcode:0x5fca status:Flag::Zero d2Outcome:1]; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | - (void)testDBLE_Negative { | ||||||
|  | 	[self performDBccTestOpcode:0x5fca status:Flag::Negative d2Outcome:1]; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | - (void)testDBLE_NegativeOverflow { | ||||||
|  | 	[self performDBccTestOpcode:0x5fca status:Flag::Negative | Flag::Overflow d2Outcome:0]; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /* Further DBF tests omitted; they seemed to be duplicative, assuming I'm not suffering a failure of comprehension. */ | ||||||
|  |  | ||||||
|  | // MARK: JMP | ||||||
|  |  | ||||||
|  | - (void)testJMP_A1 { | ||||||
|  | 	_machine->set_program({ | ||||||
|  | 		0x4ed1		// JMP (A1) | ||||||
|  | 	}); | ||||||
|  |  | ||||||
|  | 	auto state = _machine->get_processor_state(); | ||||||
|  | 	state.address[1] = 0x3000; | ||||||
|  |  | ||||||
|  | 	_machine->set_processor_state(state); | ||||||
|  | 	_machine->run_for_instructions(1); | ||||||
|  |  | ||||||
|  | 	state = _machine->get_processor_state(); | ||||||
|  | 	XCTAssertEqual(state.address[1], 0x3000); | ||||||
|  | 	XCTAssertEqual(state.program_counter, 0x3000 + 4); | ||||||
|  | 	XCTAssertEqual(8, _machine->get_cycle_count()); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | - (void)testJMP_PC { | ||||||
|  | 	_machine->set_program({ | ||||||
|  | 		0x4efa, 0x000a		// JMP PC+a (i.e. to 0x100c) | ||||||
|  | 	}); | ||||||
|  |  | ||||||
|  | 	_machine->run_for_instructions(1); | ||||||
|  |  | ||||||
|  | 	const auto state = _machine->get_processor_state(); | ||||||
|  | 	XCTAssertEqual(state.program_counter, 0x100c + 4); | ||||||
|  | 	XCTAssertEqual(10, _machine->get_cycle_count()); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // MARK: JSR | ||||||
|  |  | ||||||
|  | - (void)testJSR_PC { | ||||||
|  | 	_machine->set_program({ | ||||||
|  | 		0x4eba, 0x000a		// JSR (+a)PC		; JSR to $100c | ||||||
|  | 	}); | ||||||
|  | 	_machine->set_initial_stack_pointer(0x2000); | ||||||
|  |  | ||||||
|  | 	_machine->run_for_instructions(1); | ||||||
|  |  | ||||||
|  | 	const auto state = _machine->get_processor_state(); | ||||||
|  | 	XCTAssertEqual(state.stack_pointer(), 0x1ffc); | ||||||
|  | 	XCTAssertEqual(state.program_counter, 0x100c + 4); | ||||||
|  | 	XCTAssertEqual(*_machine->ram_at(0x1ffc), 0x0000); | ||||||
|  | 	XCTAssertEqual(*_machine->ram_at(0x1ffe), 0x1004); | ||||||
|  | 	XCTAssertEqual(18, _machine->get_cycle_count()); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | - (void)testJSR_XXXl { | ||||||
|  | 	_machine->set_program({ | ||||||
|  | 		0x4eb9, 0x0000, 0x1008		// JSR ($1008).l | ||||||
|  | 	}); | ||||||
|  | 	_machine->set_initial_stack_pointer(0x2000); | ||||||
|  |  | ||||||
|  | 	_machine->run_for_instructions(1); | ||||||
|  |  | ||||||
|  | 	const auto state = _machine->get_processor_state(); | ||||||
|  | 	XCTAssertEqual(state.stack_pointer(), 0x1ffc); | ||||||
|  | 	XCTAssertEqual(state.program_counter, 0x1008 + 4); | ||||||
|  | 	XCTAssertEqual(*_machine->ram_at(0x1ffc), 0x0000); | ||||||
|  | 	XCTAssertEqual(*_machine->ram_at(0x1ffe), 0x1006); | ||||||
|  | 	XCTAssertEqual(20, _machine->get_cycle_count()); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // MARK: NOP | ||||||
|  |  | ||||||
|  | - (void)testNOP { | ||||||
|  | 	_machine->set_program({ | ||||||
|  | 		0x4e71		// NOP | ||||||
|  | 	}); | ||||||
|  | 	_machine->run_for_instructions(1); | ||||||
|  | 	XCTAssertEqual(4, _machine->get_cycle_count()); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // MARK: RTR | ||||||
|  |  | ||||||
|  | - (void)testRTR { | ||||||
|  | 	_machine->set_program({ | ||||||
|  | 		0x4e77		// RTR | ||||||
|  | 	}); | ||||||
|  | 	_machine->set_initial_stack_pointer(0x2000); | ||||||
|  | 	*_machine->ram_at(0x2000) = 0x7fff; | ||||||
|  | 	*_machine->ram_at(0x2002) = 0; | ||||||
|  | 	*_machine->ram_at(0x2004) = 0xc; | ||||||
|  |  | ||||||
|  | 	_machine->run_for_instructions(1); | ||||||
|  |  | ||||||
|  | 	const auto state = _machine->get_processor_state(); | ||||||
|  | 	XCTAssertEqual(state.stack_pointer(), 0x2006); | ||||||
|  | 	XCTAssertEqual(state.program_counter, 0x10); | ||||||
|  | 	XCTAssertEqual(state.status & Flag::ConditionCodes, Flag::ConditionCodes); | ||||||
|  | 	XCTAssertEqual(20, _machine->get_cycle_count()); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // MARK: RTS | ||||||
|  |  | ||||||
|  | - (void)testRTS { | ||||||
|  | 	_machine->set_program({ | ||||||
|  | 		0x4e75		// RTS | ||||||
|  | 	}); | ||||||
|  | 	_machine->set_initial_stack_pointer(0x2000); | ||||||
|  | 	*_machine->ram_at(0x2000) = 0x0000; | ||||||
|  | 	*_machine->ram_at(0x2002) = 0x000c; | ||||||
|  |  | ||||||
|  | 	_machine->run_for_instructions(1); | ||||||
|  |  | ||||||
|  | 	const auto state = _machine->get_processor_state(); | ||||||
|  | 	XCTAssertEqual(state.stack_pointer(), 0x2004); | ||||||
|  | 	XCTAssertEqual(state.program_counter, 0x000c + 4); | ||||||
|  | 	XCTAssertEqual(16, _machine->get_cycle_count()); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // MARK: TRAP | ||||||
|  |  | ||||||
|  | - (void)testTRAP { | ||||||
|  | 	_machine->set_program({ | ||||||
|  | 		0x4e41		// TRAP #1 | ||||||
|  | 	}); | ||||||
|  | 	auto state = _machine->get_processor_state(); | ||||||
|  | 	state.status = 0x700; | ||||||
|  | 	state.user_stack_pointer = 0x200; | ||||||
|  | 	state.supervisor_stack_pointer = 0x206; | ||||||
|  | 	*_machine->ram_at(0x84) = 0xfffe; | ||||||
|  | 	*_machine->ram_at(0xfffe) = 0x4e71; | ||||||
|  |  | ||||||
|  | 	_machine->set_processor_state(state); | ||||||
|  | 	_machine->run_for_instructions(1); | ||||||
|  |  | ||||||
|  | 	state = _machine->get_processor_state(); | ||||||
|  | 	XCTAssertEqual(state.status, 0x2700); | ||||||
|  | 	XCTAssertEqual(*_machine->ram_at(0x200), 0x700); | ||||||
|  | 	XCTAssertEqual(*_machine->ram_at(0x202), 0x0000); | ||||||
|  | 	XCTAssertEqual(*_machine->ram_at(0x204), 0x1002); | ||||||
|  | 	XCTAssertEqual(state.supervisor_stack_pointer, 0x200); | ||||||
|  | 	XCTAssertEqual(34, _machine->get_cycle_count()); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // MARK: TRAPV | ||||||
|  |  | ||||||
|  | - (void)testTRAPV_taken { | ||||||
|  | 	_machine->set_program({ | ||||||
|  | 		0x4e76		// TRAPV | ||||||
|  | 	}); | ||||||
|  | 	_machine->set_initial_stack_pointer(0x206); | ||||||
|  |  | ||||||
|  | 	auto state = _machine->get_processor_state(); | ||||||
|  | 	state.status = 0x702; | ||||||
|  | 	state.supervisor_stack_pointer = 0x206; | ||||||
|  | 	*_machine->ram_at(0x1e) = 0xfffe; | ||||||
|  | 	*_machine->ram_at(0xfffe) = 0x4e71; | ||||||
|  |  | ||||||
|  | 	_machine->set_processor_state(state); | ||||||
|  | 	_machine->run_for_instructions(1); | ||||||
|  |  | ||||||
|  | 	state = _machine->get_processor_state(); | ||||||
|  | 	XCTAssertEqual(state.status, 0x2702); | ||||||
|  | 	XCTAssertEqual(state.stack_pointer(), 0x200); | ||||||
|  | 	XCTAssertEqual(*_machine->ram_at(0x202), 0x0000); | ||||||
|  | 	XCTAssertEqual(*_machine->ram_at(0x204), 0x1002); | ||||||
|  | 	XCTAssertEqual(*_machine->ram_at(0x200), 0x702); | ||||||
|  | 	XCTAssertEqual(34, _machine->get_cycle_count()); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | - (void)testTRAPV_untaken { | ||||||
|  | 	_machine->set_program({ | ||||||
|  | 		0x4e76		// TRAPV | ||||||
|  | 	}); | ||||||
|  |  | ||||||
|  | 	_machine->run_for_instructions(1); | ||||||
|  |  | ||||||
|  | 	const auto state = _machine->get_processor_state(); | ||||||
|  | 	XCTAssertEqual(state.program_counter, 0x1002 + 4); | ||||||
|  | 	XCTAssertEqual(4, _machine->get_cycle_count()); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @end | ||||||
							
								
								
									
										1250
									
								
								OSBindings/Mac/Clock SignalTests/68000MoveTests.mm
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1250
									
								
								OSBindings/Mac/Clock SignalTests/68000MoveTests.mm
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										1122
									
								
								OSBindings/Mac/Clock SignalTests/68000RollShiftTests.mm
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1122
									
								
								OSBindings/Mac/Clock SignalTests/68000RollShiftTests.mm
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										425
									
								
								OSBindings/Mac/Clock SignalTests/68000Tests.mm
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										425
									
								
								OSBindings/Mac/Clock SignalTests/68000Tests.mm
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,425 @@ | |||||||
|  | // | ||||||
|  | //  68000Tests.m | ||||||
|  | //  Clock SignalTests | ||||||
|  | // | ||||||
|  | //  Created by Thomas Harte on 13/03/2019. | ||||||
|  | //  Copyright © 2019 Thomas Harte. All rights reserved. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | #import <XCTest/XCTest.h> | ||||||
|  |  | ||||||
|  | #include <cassert> | ||||||
|  |  | ||||||
|  | #define LOG_TRACE | ||||||
|  | #include "TestRunner68000.hpp" | ||||||
|  |  | ||||||
|  | class CPU::MC68000::ProcessorStorageTests { | ||||||
|  | 	public: | ||||||
|  | 		ProcessorStorageTests(const CPU::MC68000::ProcessorStorage &storage, const char *coverage_file_name) { | ||||||
|  | 			false_valids_ = [NSMutableSet set]; | ||||||
|  | 			false_invalids_ = [NSMutableSet set]; | ||||||
|  |  | ||||||
|  | 			FILE *source = fopen(coverage_file_name, "rb"); | ||||||
|  |  | ||||||
|  | 			// The file format here is [2 bytes opcode][2 ASCII characters:VA for valid, IN for invalid]... | ||||||
|  | 			// The file terminates with four additional bytes that begin with two zero bytes. | ||||||
|  | 			// | ||||||
|  | 			// The version of the file I grabbed seems to cover all opcodes, making their enumeration | ||||||
|  | 			// arguably redundant; the code below nevertheless uses the codes from the file. | ||||||
|  | 			// | ||||||
|  | 			// Similarly, I'm testing for exactly the strings VA or IN to ensure no further | ||||||
|  | 			// types creep into any updated version of the table that I then deal with incorrectly. | ||||||
|  | 			uint16_t last_observed = 0; | ||||||
|  | 			while(true) { | ||||||
|  | 				// Fetch opcode number. | ||||||
|  | 				uint16_t next_opcode = fgetc(source) << 8; | ||||||
|  | 				next_opcode |= fgetc(source); | ||||||
|  | 				if(next_opcode < last_observed) break; | ||||||
|  | 				last_observed = next_opcode; | ||||||
|  |  | ||||||
|  | 				// Determine whether it's meant to be valid. | ||||||
|  | 				char type[3]; | ||||||
|  | 				type[0] = fgetc(source); | ||||||
|  | 				type[1] = fgetc(source); | ||||||
|  | 				type[2] = '\0'; | ||||||
|  |  | ||||||
|  | 				// TEMPORARY: factor out A- and F-line exceptions. | ||||||
|  | 				if((next_opcode&0xf000) == 0xa000) continue; | ||||||
|  | 				if((next_opcode&0xf000) == 0xf000) continue; | ||||||
|  |  | ||||||
|  | 				if(!strcmp(type, "VA")) { | ||||||
|  | 					// Test for validity. | ||||||
|  | 					if(storage.instructions[next_opcode].micro_operations == std::numeric_limits<uint32_t>::max()) { | ||||||
|  | 						[false_invalids_ addObject:@(next_opcode)]; | ||||||
|  | 					} | ||||||
|  | 					continue; | ||||||
|  | 				} | ||||||
|  |  | ||||||
|  | 				if(!strcmp(type, "IN")) { | ||||||
|  | 					// Test for invalidity. | ||||||
|  | 					if(storage.instructions[next_opcode].micro_operations != std::numeric_limits<uint32_t>::max()) { | ||||||
|  | 						[false_valids_ addObject:@(next_opcode)]; | ||||||
|  | 					} | ||||||
|  | 					continue; | ||||||
|  | 				} | ||||||
|  |  | ||||||
|  | 				assert(false); | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			fclose(source); | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		NSSet<NSNumber *> *false_valids() const { | ||||||
|  | 			return false_valids_; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		NSSet<NSNumber *> *false_invalids() const { | ||||||
|  | 			return false_invalids_; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 	private: | ||||||
|  | 		NSMutableSet<NSNumber *> *false_invalids_; | ||||||
|  | 		NSMutableSet<NSNumber *> *false_valids_; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | @interface NSSet (CSHexDump) | ||||||
|  |  | ||||||
|  | - (NSString *)hexDump; | ||||||
|  |  | ||||||
|  | @end | ||||||
|  |  | ||||||
|  | @implementation NSSet (CSHexDump) | ||||||
|  |  | ||||||
|  | - (NSString *)hexDump { | ||||||
|  | 	NSMutableArray<NSString *> *components = [NSMutableArray array]; | ||||||
|  |  | ||||||
|  | 	for(NSNumber *number in [[self allObjects] sortedArrayUsingSelector:@selector(compare:)]) { | ||||||
|  | 		[components addObject:[NSString stringWithFormat:@"%04x", number.intValue]]; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return [components componentsJoinedByString:@" "]; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @end | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @interface M68000Tests : XCTestCase | ||||||
|  | @end | ||||||
|  |  | ||||||
|  | @implementation M68000Tests { | ||||||
|  | 	std::unique_ptr<RAM68000> _machine; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | - (void)setUp { | ||||||
|  |     _machine.reset(new RAM68000()); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | - (void)tearDown { | ||||||
|  | 	_machine.reset(); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | - (void)testABCDLong { | ||||||
|  | 	for(int d = 0; d < 100; ++d) { | ||||||
|  | 		_machine.reset(new RAM68000()); | ||||||
|  | 		_machine->set_program({ | ||||||
|  | 			0xc100		// ABCD D0, D0 | ||||||
|  | 		}); | ||||||
|  |  | ||||||
|  | 		auto state = _machine->get_processor_state(); | ||||||
|  | 		const uint8_t bcd_d = ((d / 10) * 16) + (d % 10); | ||||||
|  | 		state.data[0] = bcd_d; | ||||||
|  | 		_machine->set_processor_state(state); | ||||||
|  |  | ||||||
|  | 		_machine->run_for_instructions(1); | ||||||
|  |  | ||||||
|  | 		state = _machine->get_processor_state(); | ||||||
|  | 		const uint8_t double_d = (d * 2) % 100; | ||||||
|  | 		const uint8_t bcd_double_d = ((double_d / 10) * 16) + (double_d % 10); | ||||||
|  | 		XCTAssert(state.data[0] == bcd_double_d, "%02x + %02x = %02x; should equal %02x", bcd_d, bcd_d, state.data[0], bcd_double_d); | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | - (void)testDivideByZero { | ||||||
|  | 	_machine->set_program({ | ||||||
|  | 		0x7000,		// MOVE #0, D0;		location 0x400 | ||||||
|  | 		0x3200,		// MOVE D0, D1;		location 0x402 | ||||||
|  |  | ||||||
|  | 		0x82C0,		// DIVU;			location 0x404 | ||||||
|  |  | ||||||
|  | 		/* Next instruction would be at 0x406 */ | ||||||
|  | 	}); | ||||||
|  | 	_machine->set_initial_stack_pointer(0x1000); | ||||||
|  |  | ||||||
|  | 	_machine->run_for_instructions(4); | ||||||
|  |  | ||||||
|  | 	const auto state = _machine->get_processor_state(); | ||||||
|  | 	XCTAssert(state.supervisor_stack_pointer == 0x1000 - 6, @"Exception information should have been pushed to stack."); | ||||||
|  |  | ||||||
|  | 	const uint16_t *const stack_top = _machine->ram_at(state.supervisor_stack_pointer); | ||||||
|  | 	XCTAssert(stack_top[1] == 0x0000 && stack_top[2] == 0x1006, @"Return address should point to instruction after DIVU."); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | - (void)testMOVE { | ||||||
|  | 	_machine->set_program({ | ||||||
|  | 		0x303c, 0xfb2e,		// MOVE #fb2e, D0 | ||||||
|  | 		0x3200,				// MOVE D0, D1 | ||||||
|  |  | ||||||
|  | 		0x3040,				// MOVEA D0, A0 | ||||||
|  | 		0x3278, 0x1000,		// MOVEA.w (0x1000), A1 | ||||||
|  |  | ||||||
|  | 		0x387c, 0x1000,		// MOVE #$1000, A4 | ||||||
|  | 		0x2414,				// MOVE.l (A4), D2 | ||||||
|  | 	}); | ||||||
|  |  | ||||||
|  | 	// run_for_instructions technically runs up to the next instruction | ||||||
|  | 	// fetch; therefore run for '1' to get past the implied RESET. | ||||||
|  | 	_machine->run_for_instructions(1); | ||||||
|  |  | ||||||
|  | 	// Perform MOVE #fb2e, D0 | ||||||
|  | 	_machine->run_for_instructions(1); | ||||||
|  | 	auto state = _machine->get_processor_state(); | ||||||
|  | 	XCTAssert(state.data[0] == 0xfb2e); | ||||||
|  |  | ||||||
|  | 	// Perform MOVE D0, D1 | ||||||
|  | 	_machine->run_for_instructions(1); | ||||||
|  | 	state = _machine->get_processor_state(); | ||||||
|  | 	XCTAssert(state.data[1] == 0xfb2e); | ||||||
|  |  | ||||||
|  | 	// Perform MOVEA D0, A0 | ||||||
|  | 	_machine->run_for_instructions(1); | ||||||
|  | 	state = _machine->get_processor_state(); | ||||||
|  | 	XCTAssert(state.address[0] == 0xfffffb2e, "A0 was %08x instead of 0xfffffb2e", state.address[0]); | ||||||
|  |  | ||||||
|  | 	// Perform MOVEA.w (0x1000), A1 | ||||||
|  | 	_machine->run_for_instructions(1); | ||||||
|  | 	state = _machine->get_processor_state(); | ||||||
|  | 	XCTAssert(state.address[1] == 0x0000303c, "A1 was %08x instead of 0x0000303c", state.address[1]); | ||||||
|  |  | ||||||
|  | 	// Perform MOVE #$400, A4; MOVE.l (A4), D2 | ||||||
|  | 	_machine->run_for_instructions(2); | ||||||
|  | 	state = _machine->get_processor_state(); | ||||||
|  | 	XCTAssert(state.address[4] == 0x1000, "A4 was %08x instead of 0x00001000", state.address[4]); | ||||||
|  | 	XCTAssert(state.data[2] == 0x303cfb2e, "D2 was %08x instead of 0x303cfb2e", state.data[2]); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | - (void)testVectoredInterrupt { | ||||||
|  | 	_machine->set_program({ | ||||||
|  | 		0x46fc,	0x2000,		// MOVE.w #$2000, SR | ||||||
|  | 		0x4e71,				// NOP | ||||||
|  | 		0x4e71,				// NOP | ||||||
|  | 		0x4e71,				// NOP | ||||||
|  | 		0x4e71,				// NOP | ||||||
|  | 		0x4e71,				// NOP | ||||||
|  | 	}); | ||||||
|  |  | ||||||
|  | 	// Set the vector that will be supplied back to the start of the | ||||||
|  | 	// program; this will ensure no further exceptions following | ||||||
|  | 	// the interrupt. | ||||||
|  | 	const auto vector = _machine->ram_at(40); | ||||||
|  | 	vector[0] = 0x0000; | ||||||
|  | 	vector[1] = 0x1004; | ||||||
|  |  | ||||||
|  | 	_machine->run_for_instructions(3); | ||||||
|  | 	_machine->processor().set_interrupt_level(1); | ||||||
|  | 	_machine->run_for_instructions(1); | ||||||
|  |  | ||||||
|  | 	const auto state = _machine->processor().get_state(); | ||||||
|  | 	XCTAssert(state.program_counter == 0x1008);	// i.e. the interrupt happened, the instruction performed was the one at 1004, and therefore | ||||||
|  | 												// by the wonders of prefetch the program counter is now at 1008. | ||||||
|  | } | ||||||
|  |  | ||||||
|  | - (void)testOpcodeCoverage { | ||||||
|  | 	// Perform an audit of implemented instructions. | ||||||
|  | 	CPU::MC68000::ProcessorStorageTests storage_tests( | ||||||
|  | 		_machine->processor(), | ||||||
|  | 		[[NSBundle bundleForClass:[self class]] pathForResource:@"OPCLOGR2" ofType:@"BIN"].UTF8String | ||||||
|  | 	); | ||||||
|  |  | ||||||
|  | 	// This is a list of instructions nominated as valid with OPCLOGR2.BIN but with no obvious decoding — | ||||||
|  | 	// the disassemblers I tried couldn't figure them out, and I didn't spot them anywhere in the PRM. | ||||||
|  | 	NSSet<NSNumber *> *const undecodables = [NSSet setWithArray:@[ | ||||||
|  | 		// These look like malformed MOVEs. | ||||||
|  | 		@(0x2e7d),	@(0x2e7e),	@(0x2e7f),	@(0x2efd),	@(0x2efe),	@(0x2eff),	@(0x2f7d),	@(0x2f7e), | ||||||
|  | 		@(0x2f7f),	@(0x2fc0),	@(0x2fc1),	@(0x2fc2),	@(0x2fc3),	@(0x2fc4),	@(0x2fc5),	@(0x2fc6), | ||||||
|  | 		@(0x2fc7),	@(0x2fc8),	@(0x2fc9),	@(0x2fca),	@(0x2fcb),	@(0x2fcc),	@(0x2fcd),	@(0x2fce), | ||||||
|  | 		@(0x2fcf),	@(0x2fd0),	@(0x2fd1),	@(0x2fd2),	@(0x2fd3),	@(0x2fd4),	@(0x2fd5),	@(0x2fd6), | ||||||
|  | 		@(0x2fd7),	@(0x2fd8),	@(0x2fd9),	@(0x2fda),	@(0x2fdb),	@(0x2fdc),	@(0x2fdd),	@(0x2fde), | ||||||
|  | 		@(0x2fdf),	@(0x2fe0),	@(0x2fe1),	@(0x2fe2),	@(0x2fe3),	@(0x2fe4),	@(0x2fe5),	@(0x2fe6), | ||||||
|  | 		@(0x2fe7),	@(0x2fe8),	@(0x2fe9),	@(0x2fea),	@(0x2feb),	@(0x2fec),	@(0x2fed),	@(0x2fee), | ||||||
|  | 		@(0x2fef),	@(0x2ff0),	@(0x2ff1),	@(0x2ff2),	@(0x2ff3),	@(0x2ff4),	@(0x2ff5),	@(0x2ff6), | ||||||
|  | 		@(0x2ff7),	@(0x2ff8),	@(0x2ff9),	@(0x2ffa),	@(0x2ffb),	@(0x2ffc),	@(0x2ffd),	@(0x2ffe), | ||||||
|  | 		@(0x2fff), | ||||||
|  |  | ||||||
|  | 		@(0x3e7d),	@(0x3e7e),	@(0x3e7f),	@(0x3efd),	@(0x3efe),	@(0x3eff),	@(0x3f7d),	@(0x3f7e), | ||||||
|  | 		@(0x3f7f),	@(0x3fc0),	@(0x3fc1),	@(0x3fc2),	@(0x3fc3),	@(0x3fc4),	@(0x3fc5),	@(0x3fc6), | ||||||
|  | 		@(0x3fc7),	@(0x3fc8),	@(0x3fc9),	@(0x3fca),	@(0x3fcb),	@(0x3fcc),	@(0x3fcd),	@(0x3fce), | ||||||
|  | 		@(0x3fcf),	@(0x3fd0),	@(0x3fd1),	@(0x3fd2),	@(0x3fd3),	@(0x3fd4),	@(0x3fd5),	@(0x3fd6), | ||||||
|  | 		@(0x3fd7),	@(0x3fd8),	@(0x3fd9),	@(0x3fda),	@(0x3fdb),	@(0x3fdc),	@(0x3fdd),	@(0x3fde), | ||||||
|  | 		@(0x3fdf),	@(0x3fe0),	@(0x3fe1),	@(0x3fe2),	@(0x3fe3),	@(0x3fe4),	@(0x3fe5),	@(0x3fe6), | ||||||
|  | 		@(0x3fe7),	@(0x3fe8),	@(0x3fe9),	@(0x3fea),	@(0x3feb),	@(0x3fec),	@(0x3fed),	@(0x3fee), | ||||||
|  | 		@(0x3fef),	@(0x3ff0),	@(0x3ff1),	@(0x3ff2),	@(0x3ff3),	@(0x3ff4),	@(0x3ff5),	@(0x3ff6), | ||||||
|  | 		@(0x3ff7),	@(0x3ff8),	@(0x3ff9),	@(0x3ffa),	@(0x3ffb),	@(0x3ffc),	@(0x3ffd),	@(0x3ffe), | ||||||
|  | 		@(0x3fff), | ||||||
|  |  | ||||||
|  | 		@(0x46c8),	@(0x46c9),	@(0x46ca),	@(0x46cb),	@(0x46cc),	@(0x46cd),	@(0x46ce),	@(0x46cf), | ||||||
|  | 		@(0x46fd),	@(0x46fe),	@(0x46ff),	@(0x47c0),	@(0x47c1),	@(0x47c2),	@(0x47c3),	@(0x47c4), | ||||||
|  | 		@(0x47c5),	@(0x47c6),	@(0x47c7),	@(0x47c8),	@(0x47c9),	@(0x47ca),	@(0x47cb),	@(0x47cc), | ||||||
|  | 		@(0x47cd),	@(0x47ce),	@(0x47cf),	@(0x47d8),	@(0x47d9),	@(0x47da),	@(0x47db),	@(0x47dc), | ||||||
|  | 		@(0x47dd),	@(0x47de),	@(0x47df),	@(0x47e0),	@(0x47e1),	@(0x47e2),	@(0x47e3),	@(0x47e4), | ||||||
|  | 		@(0x47e5),	@(0x47e6),	@(0x47e7),	@(0x47fc),	@(0x47fd),	@(0x47fe),	@(0x47ff),	@(0x4e80), | ||||||
|  | 		@(0x4e81),	@(0x4e82),	@(0x4e83),	@(0x4e84),	@(0x4e85),	@(0x4e86),	@(0x4e87),	@(0x4e88), | ||||||
|  | 		@(0x4e89),	@(0x4e8a),	@(0x4e8b),	@(0x4e8c),	@(0x4e8d),	@(0x4e8e),	@(0x4e8f),	@(0x4e98), | ||||||
|  | 		@(0x4e99),	@(0x4e9a),	@(0x4e9b),	@(0x4e9c),	@(0x4e9d),	@(0x4e9e),	@(0x4e9f),	@(0x4ea0), | ||||||
|  | 		@(0x4ea1),	@(0x4ea2),	@(0x4ea3),	@(0x4ea4),	@(0x4ea5),	@(0x4ea6),	@(0x4ea7),	@(0x4ebc), | ||||||
|  | 		@(0x4ebd),	@(0x4ebe),	@(0x4ebf),	@(0x4ec0),	@(0x4ec1),	@(0x4ec2),	@(0x4ec3),	@(0x4ec4), | ||||||
|  | 		@(0x4ec5),	@(0x4ec6),	@(0x4ec7),	@(0x4ec8),	@(0x4ec9),	@(0x4eca),	@(0x4ecb),	@(0x4ecc), | ||||||
|  | 		@(0x4ecd),	@(0x4ece),	@(0x4ecf),	@(0x4ed8),	@(0x4ed9),	@(0x4eda),	@(0x4edb),	@(0x4edc), | ||||||
|  | 		@(0x4edd),	@(0x4ede),	@(0x4edf),	@(0x4ee0),	@(0x4ee1),	@(0x4ee2),	@(0x4ee3),	@(0x4ee4), | ||||||
|  | 		@(0x4ee5),	@(0x4ee6),	@(0x4ee7),	@(0x4efc),	@(0x4efd),	@(0x4efe),	@(0x4eff),	@(0x4f88), | ||||||
|  | 		@(0x4f89),	@(0x4f8a),	@(0x4f8b),	@(0x4f8c),	@(0x4f8d),	@(0x4f8e),	@(0x4f8f),	@(0x4fbd), | ||||||
|  | 		@(0x4fbe),	@(0x4fbf),	@(0x4fc0),	@(0x4fc1),	@(0x4fc2),	@(0x4fc3),	@(0x4fc4),	@(0x4fc5), | ||||||
|  | 		@(0x4fc6),	@(0x4fc7),	@(0x4fc8),	@(0x4fc9),	@(0x4fca),	@(0x4fcb),	@(0x4fcc),	@(0x4fcd), | ||||||
|  | 		@(0x4fce),	@(0x4fcf),	@(0x4fd8),	@(0x4fd9),	@(0x4fda),	@(0x4fdb),	@(0x4fdc),	@(0x4fdd), | ||||||
|  | 		@(0x4fde),	@(0x4fdf),	@(0x4fe0),	@(0x4fe1),	@(0x4fe2),	@(0x4fe3),	@(0x4fe4),	@(0x4fe5), | ||||||
|  | 		@(0x4fe6),	@(0x4fe7),	@(0x4ffc),	@(0x4ffd),	@(0x4ffe),	@(0x4fff), | ||||||
|  |  | ||||||
|  | 		@(0x50fa),	@(0x50fb),	@(0x50fc),	@(0x50fd),	@(0x50fe),	@(0x50ff),	@(0x51fa),	@(0x51fb), | ||||||
|  | 		@(0x51fc),	@(0x51fd),	@(0x51fe),	@(0x51ff),	@(0x52fa),	@(0x52fb),	@(0x52fc),	@(0x52fd), | ||||||
|  | 		@(0x52fe),	@(0x52ff),	@(0x53fa),	@(0x53fb),	@(0x53fc),	@(0x53fd),	@(0x53fe),	@(0x53ff), | ||||||
|  | 		@(0x54fa),	@(0x54fb),	@(0x54fc),	@(0x54fd),	@(0x54fe),	@(0x54ff),	@(0x55fa),	@(0x55fb), | ||||||
|  | 		@(0x55fc),	@(0x55fd),	@(0x55fe),	@(0x55ff),	@(0x56fa),	@(0x56fb),	@(0x56fc),	@(0x56fd), | ||||||
|  | 		@(0x56fe),	@(0x56ff),	@(0x57fa),	@(0x57fb),	@(0x57fc),	@(0x57fd),	@(0x57fe),	@(0x57ff), | ||||||
|  | 		@(0x58fa),	@(0x58fb),	@(0x58fc),	@(0x58fd),	@(0x58fe),	@(0x58ff),	@(0x59fa),	@(0x59fb), | ||||||
|  | 		@(0x59fc),	@(0x59fd),	@(0x59fe),	@(0x59ff),	@(0x5afa),	@(0x5afb),	@(0x5afc),	@(0x5afd), | ||||||
|  | 		@(0x5afe),	@(0x5aff),	@(0x5bfa),	@(0x5bfb),	@(0x5bfc),	@(0x5bfd),	@(0x5bfe),	@(0x5bff), | ||||||
|  | 		@(0x5cfa),	@(0x5cfb),	@(0x5cfc),	@(0x5cfd),	@(0x5cfe),	@(0x5cff),	@(0x5dfa),	@(0x5dfb), | ||||||
|  | 		@(0x5dfc),	@(0x5dfd),	@(0x5dfe),	@(0x5dff),	@(0x5eba),	@(0x5ebb),	@(0x5ebc),	@(0x5ebd), | ||||||
|  | 		@(0x5ebe),	@(0x5ebf),	@(0x5efa),	@(0x5efb),	@(0x5efc),	@(0x5efd),	@(0x5efe),	@(0x5eff), | ||||||
|  | 		@(0x5fba),	@(0x5fbb),	@(0x5fbc),	@(0x5fbd),	@(0x5fbe),	@(0x5fbf),	@(0x5ffa),	@(0x5ffb), | ||||||
|  | 		@(0x5ffc),	@(0x5ffd),	@(0x5ffe),	@(0x5fff), | ||||||
|  |  | ||||||
|  | 		// These are almost MOVEQs if only bit 8 weren't set. | ||||||
|  | 		@(0x71c8),	@(0x71c9),	@(0x71ca),	@(0x71cb),	@(0x71cc),	@(0x71cd),	@(0x71ce),	@(0x71cf), | ||||||
|  | 		@(0x71d8),	@(0x71d9),	@(0x71da),	@(0x71db),	@(0x71dc),	@(0x71dd),	@(0x71de),	@(0x71df), | ||||||
|  | 		@(0x71e8),	@(0x71e9),	@(0x71ea),	@(0x71eb),	@(0x71ec),	@(0x71ed),	@(0x71ee),	@(0x71ef), | ||||||
|  | 		@(0x71f8),	@(0x71f9),	@(0x71fa),	@(0x71fb),	@(0x71fc),	@(0x71fd),	@(0x71fe),	@(0x71ff), | ||||||
|  | 		@(0x73c8),	@(0x73c9),	@(0x73ca),	@(0x73cb),	@(0x73cc),	@(0x73cd),	@(0x73ce),	@(0x73cf), | ||||||
|  | 		@(0x73d8),	@(0x73d9),	@(0x73da),	@(0x73db),	@(0x73dc),	@(0x73dd),	@(0x73de),	@(0x73df), | ||||||
|  | 		@(0x73e8),	@(0x73e9),	@(0x73ea),	@(0x73eb),	@(0x73ec),	@(0x73ed),	@(0x73ee),	@(0x73ef), | ||||||
|  | 		@(0x73f8),	@(0x73f9),	@(0x73fa),	@(0x73fb),	@(0x73fc),	@(0x73fd),	@(0x73fe),	@(0x73ff), | ||||||
|  | 		@(0x75c8),	@(0x75c9),	@(0x75ca),	@(0x75cb),	@(0x75cc),	@(0x75cd),	@(0x75ce),	@(0x75cf), | ||||||
|  | 		@(0x75d8),	@(0x75d9),	@(0x75da),	@(0x75db),	@(0x75dc),	@(0x75dd),	@(0x75de),	@(0x75df), | ||||||
|  | 		@(0x75e8),	@(0x75e9),	@(0x75ea),	@(0x75eb),	@(0x75ec),	@(0x75ed),	@(0x75ee),	@(0x75ef), | ||||||
|  | 		@(0x75f8),	@(0x75f9),	@(0x75fa),	@(0x75fb),	@(0x75fc),	@(0x75fd),	@(0x75fe),	@(0x75ff), | ||||||
|  | 		@(0x77c0),	@(0x77c1),	@(0x77c2),	@(0x77c3),	@(0x77c4),	@(0x77c5),	@(0x77c6),	@(0x77c7), | ||||||
|  | 		@(0x77c8),	@(0x77c9),	@(0x77ca),	@(0x77cb),	@(0x77cc),	@(0x77cd),	@(0x77ce),	@(0x77cf), | ||||||
|  | 		@(0x77d0),	@(0x77d1),	@(0x77d2),	@(0x77d3),	@(0x77d4),	@(0x77d5),	@(0x77d6),	@(0x77d7), | ||||||
|  | 		@(0x77d8),	@(0x77d9),	@(0x77da),	@(0x77db),	@(0x77dc),	@(0x77dd),	@(0x77de),	@(0x77df), | ||||||
|  | 		@(0x77e0),	@(0x77e1),	@(0x77e2),	@(0x77e3),	@(0x77e4),	@(0x77e5),	@(0x77e6),	@(0x77e7), | ||||||
|  | 		@(0x77e8),	@(0x77e9),	@(0x77ea),	@(0x77eb),	@(0x77ec),	@(0x77ed),	@(0x77ee),	@(0x77ef), | ||||||
|  | 		@(0x77f0),	@(0x77f1),	@(0x77f2),	@(0x77f3),	@(0x77f4),	@(0x77f5),	@(0x77f6),	@(0x77f7), | ||||||
|  | 		@(0x77f8),	@(0x77f9),	@(0x77fa),	@(0x77fb),	@(0x77fc),	@(0x77fd),	@(0x77fe),	@(0x77ff), | ||||||
|  | 		@(0x79c8),	@(0x79c9),	@(0x79ca),	@(0x79cb),	@(0x79cc),	@(0x79cd),	@(0x79ce),	@(0x79cf), | ||||||
|  | 		@(0x79d8),	@(0x79d9),	@(0x79da),	@(0x79db),	@(0x79dc),	@(0x79dd),	@(0x79de),	@(0x79df), | ||||||
|  | 		@(0x79e8),	@(0x79e9),	@(0x79ea),	@(0x79eb),	@(0x79ec),	@(0x79ed),	@(0x79ee),	@(0x79ef), | ||||||
|  | 		@(0x79f8),	@(0x79f9),	@(0x79fa),	@(0x79fb),	@(0x79fc),	@(0x79fd),	@(0x79fe),	@(0x79ff), | ||||||
|  | 		@(0x7bc8),	@(0x7bc9),	@(0x7bca),	@(0x7bcb),	@(0x7bcc),	@(0x7bcd),	@(0x7bce),	@(0x7bcf), | ||||||
|  | 		@(0x7bd8),	@(0x7bd9),	@(0x7bda),	@(0x7bdb),	@(0x7bdc),	@(0x7bdd),	@(0x7bde),	@(0x7bdf), | ||||||
|  | 		@(0x7be8),	@(0x7be9),	@(0x7bea),	@(0x7beb),	@(0x7bec),	@(0x7bed),	@(0x7bee),	@(0x7bef), | ||||||
|  | 		@(0x7bf8),	@(0x7bf9),	@(0x7bfa),	@(0x7bfb),	@(0x7bfc),	@(0x7bfd),	@(0x7bfe),	@(0x7bff), | ||||||
|  | 		@(0x7dc8),	@(0x7dc9),	@(0x7dca),	@(0x7dcb),	@(0x7dcc),	@(0x7dcd),	@(0x7dce),	@(0x7dcf), | ||||||
|  | 		@(0x7dd8),	@(0x7dd9),	@(0x7dda),	@(0x7ddb),	@(0x7ddc),	@(0x7ddd),	@(0x7dde),	@(0x7ddf), | ||||||
|  | 		@(0x7de8),	@(0x7de9),	@(0x7dea),	@(0x7deb),	@(0x7dec),	@(0x7ded),	@(0x7dee),	@(0x7def), | ||||||
|  | 		@(0x7df8),	@(0x7df9),	@(0x7dfa),	@(0x7dfb),	@(0x7dfc),	@(0x7dfd),	@(0x7dfe),	@(0x7dff), | ||||||
|  | 		@(0x7f40),	@(0x7f41),	@(0x7f42),	@(0x7f43),	@(0x7f44),	@(0x7f45),	@(0x7f46),	@(0x7f47), | ||||||
|  | 		@(0x7f48),	@(0x7f49),	@(0x7f4a),	@(0x7f4b),	@(0x7f4c),	@(0x7f4d),	@(0x7f4e),	@(0x7f4f), | ||||||
|  | 		@(0x7f50),	@(0x7f51),	@(0x7f52),	@(0x7f53),	@(0x7f54),	@(0x7f55),	@(0x7f56),	@(0x7f57), | ||||||
|  | 		@(0x7f58),	@(0x7f59),	@(0x7f5a),	@(0x7f5b),	@(0x7f5c),	@(0x7f5d),	@(0x7f5e),	@(0x7f5f), | ||||||
|  | 		@(0x7f60),	@(0x7f61),	@(0x7f62),	@(0x7f63),	@(0x7f64),	@(0x7f65),	@(0x7f66),	@(0x7f67), | ||||||
|  | 		@(0x7f68),	@(0x7f69),	@(0x7f6a),	@(0x7f6b),	@(0x7f6c),	@(0x7f6d),	@(0x7f6e),	@(0x7f6f), | ||||||
|  | 		@(0x7f70),	@(0x7f71),	@(0x7f72),	@(0x7f73),	@(0x7f74),	@(0x7f75),	@(0x7f76),	@(0x7f77), | ||||||
|  | 		@(0x7f78),	@(0x7f79),	@(0x7f7a),	@(0x7f7b),	@(0x7f7c),	@(0x7f7d),	@(0x7f7e),	@(0x7f7f), | ||||||
|  | 		@(0x7f80),	@(0x7f81),	@(0x7f82),	@(0x7f83),	@(0x7f84),	@(0x7f85),	@(0x7f86),	@(0x7f87), | ||||||
|  | 		@(0x7f88),	@(0x7f89),	@(0x7f8a),	@(0x7f8b),	@(0x7f8c),	@(0x7f8d),	@(0x7f8e),	@(0x7f8f), | ||||||
|  | 		@(0x7f90),	@(0x7f91),	@(0x7f92),	@(0x7f93),	@(0x7f94),	@(0x7f95),	@(0x7f96),	@(0x7f97), | ||||||
|  | 		@(0x7f98),	@(0x7f99),	@(0x7f9a),	@(0x7f9b),	@(0x7f9c),	@(0x7f9d),	@(0x7f9e),	@(0x7f9f), | ||||||
|  | 		@(0x7fa0),	@(0x7fa1),	@(0x7fa2),	@(0x7fa3),	@(0x7fa4),	@(0x7fa5),	@(0x7fa6),	@(0x7fa7), | ||||||
|  | 		@(0x7fa8),	@(0x7fa9),	@(0x7faa),	@(0x7fab),	@(0x7fac),	@(0x7fad),	@(0x7fae),	@(0x7faf), | ||||||
|  | 		@(0x7fb0),	@(0x7fb1),	@(0x7fb2),	@(0x7fb3),	@(0x7fb4),	@(0x7fb5),	@(0x7fb6),	@(0x7fb7), | ||||||
|  | 		@(0x7fb8),	@(0x7fb9),	@(0x7fba),	@(0x7fbb),	@(0x7fbc),	@(0x7fbd),	@(0x7fbe),	@(0x7fbf), | ||||||
|  | 		@(0x7fc0),	@(0x7fc1),	@(0x7fc2),	@(0x7fc3),	@(0x7fc4),	@(0x7fc5),	@(0x7fc6),	@(0x7fc7), | ||||||
|  | 		@(0x7fc8),	@(0x7fc9),	@(0x7fca),	@(0x7fcb),	@(0x7fcc),	@(0x7fcd),	@(0x7fce),	@(0x7fcf), | ||||||
|  | 		@(0x7fd0),	@(0x7fd1),	@(0x7fd2),	@(0x7fd3),	@(0x7fd4),	@(0x7fd5),	@(0x7fd6),	@(0x7fd7), | ||||||
|  | 		@(0x7fd8),	@(0x7fd9),	@(0x7fda),	@(0x7fdb),	@(0x7fdc),	@(0x7fdd),	@(0x7fde),	@(0x7fdf), | ||||||
|  | 		@(0x7fe0),	@(0x7fe1),	@(0x7fe2),	@(0x7fe3),	@(0x7fe4),	@(0x7fe5),	@(0x7fe6),	@(0x7fe7), | ||||||
|  | 		@(0x7fe8),	@(0x7fe9),	@(0x7fea),	@(0x7feb),	@(0x7fec),	@(0x7fed),	@(0x7fee),	@(0x7fef), | ||||||
|  | 		@(0x7ff0),	@(0x7ff1),	@(0x7ff2),	@(0x7ff3),	@(0x7ff4),	@(0x7ff5),	@(0x7ff6),	@(0x7ff7), | ||||||
|  | 		@(0x7ff8),	@(0x7ff9),	@(0x7ffa),	@(0x7ffb),	@(0x7ffc),	@(0x7ffd),	@(0x7ffe),	@(0x7fff), | ||||||
|  |  | ||||||
|  | 		@(0xbe7d),	@(0xbe7e),	@(0xbe7f),	@(0xbefd),	@(0xbefe),	@(0xbeff),	@(0xbf7a),	@(0xbf7b), | ||||||
|  | 		@(0xbf7c),	@(0xbf7d),	@(0xbf7e),	@(0xbf7f),	@(0xbffd),	@(0xbffe),	@(0xbfff), | ||||||
|  |  | ||||||
|  | 		// | ||||||
|  | 		@(0xc6c8),	@(0xc6c9),	@(0xc6ca),	@(0xc6cb),	@(0xc6cc),	@(0xc6cd),	@(0xc6ce),	@(0xc6cf), | ||||||
|  | 		@(0xc6fd),	@(0xc6fe),	@(0xc6ff),	@(0xc7c8),	@(0xc7c9),	@(0xc7ca),	@(0xc7cb),	@(0xc7cc), | ||||||
|  | 		@(0xc7cd),	@(0xc7ce),	@(0xc7cf),	@(0xc7fd),	@(0xc7fe),	@(0xc7ff),	@(0xce88),	@(0xce89), | ||||||
|  | 		@(0xce8a),	@(0xce8b),	@(0xce8c),	@(0xce8d),	@(0xce8e),	@(0xce8f),	@(0xcebd),	@(0xcebe), | ||||||
|  | 		@(0xcebf),	@(0xcec8),	@(0xcec9),	@(0xceca),	@(0xcecb),	@(0xcecc),	@(0xcecd),	@(0xcece), | ||||||
|  | 		@(0xcecf),	@(0xcefd),	@(0xcefe),	@(0xceff),	@(0xcf80),	@(0xcf81),	@(0xcf82),	@(0xcf83), | ||||||
|  | 		@(0xcf84),	@(0xcf85),	@(0xcf86),	@(0xcf87),	@(0xcfba),	@(0xcfbb),	@(0xcfbc),	@(0xcfbd), | ||||||
|  | 		@(0xcfbe),	@(0xcfbf),	@(0xcfc8),	@(0xcfc9),	@(0xcfca),	@(0xcfcb),	@(0xcfcc),	@(0xcfcd), | ||||||
|  | 		@(0xcfce),	@(0xcfcf),	@(0xcffd),	@(0xcffe),	@(0xcfff), | ||||||
|  |  | ||||||
|  | 		// These are from the Bcc/BRA/BSR page. | ||||||
|  | 		@(0xd0fd),	@(0xd0fe),	@(0xd0ff),	@(0xd1fd),	@(0xd1fe),	@(0xd1ff),	@(0xd2fd),	@(0xd2fe), | ||||||
|  | 		@(0xd2ff),	@(0xd3fd),	@(0xd3fe),	@(0xd3ff),	@(0xd4fd),	@(0xd4fe),	@(0xd4ff),	@(0xd5fd), | ||||||
|  | 		@(0xd5fe),	@(0xd5ff),	@(0xd6fd),	@(0xd6fe),	@(0xd6ff),	@(0xd7fd),	@(0xd7fe),	@(0xd7ff), | ||||||
|  | 		@(0xd8fd),	@(0xd8fe),	@(0xd8ff),	@(0xd9fd),	@(0xd9fe),	@(0xd9ff),	@(0xdafd),	@(0xdafe), | ||||||
|  | 		@(0xdaff),	@(0xdbfd),	@(0xdbfe),	@(0xdbff),	@(0xdcfd),	@(0xdcfe),	@(0xdcff),	@(0xddfd), | ||||||
|  | 		@(0xddfe),	@(0xddff),	@(0xdebd),	@(0xdebe),	@(0xdebf),	@(0xdefd),	@(0xdefe),	@(0xdeff), | ||||||
|  | 		@(0xdfba),	@(0xdfbb),	@(0xdfbc),	@(0xdfbd),	@(0xdfbe),	@(0xdfbf),	@(0xdffd),	@(0xdffe), | ||||||
|  | 		@(0xdfff), | ||||||
|  |  | ||||||
|  | 		// The E line is for shifts and rolls; none of the those listed below appear to nominate valid | ||||||
|  | 		// addressing modes. | ||||||
|  | 		@(0xe6c0),	@(0xe6c1),	@(0xe6c2),	@(0xe6c3),	@(0xe6c4),	@(0xe6c5),	@(0xe6c6),	@(0xe6c7), | ||||||
|  | 		@(0xe6c8),	@(0xe6c9),	@(0xe6ca),	@(0xe6cb),	@(0xe6cc),	@(0xe6cd),	@(0xe6ce),	@(0xe6cf), | ||||||
|  | 		@(0xe6fa),	@(0xe6fb),	@(0xe6fc),	@(0xe6fd),	@(0xe6fe),	@(0xe6ff),	@(0xe7c0),	@(0xe7c1), | ||||||
|  | 		@(0xe7c2),	@(0xe7c3),	@(0xe7c4),	@(0xe7c5),	@(0xe7c6),	@(0xe7c7),	@(0xe7c8),	@(0xe7c9), | ||||||
|  | 		@(0xe7ca),	@(0xe7cb),	@(0xe7cc),	@(0xe7cd),	@(0xe7ce),	@(0xe7cf),	@(0xe7fa),	@(0xe7fb), | ||||||
|  | 		@(0xe7fc),	@(0xe7fd),	@(0xe7fe),	@(0xe7ff),	@(0xeec0),	@(0xeec1),	@(0xeec2),	@(0xeec3), | ||||||
|  | 		@(0xeec4),	@(0xeec5),	@(0xeec6),	@(0xeec7),	@(0xeec8),	@(0xeec9),	@(0xeeca),	@(0xeecb), | ||||||
|  | 		@(0xeecc),	@(0xeecd),	@(0xeece),	@(0xeecf),	@(0xeed0),	@(0xeed1),	@(0xeed2),	@(0xeed3), | ||||||
|  | 		@(0xeed4),	@(0xeed5),	@(0xeed6),	@(0xeed7),	@(0xeed8),	@(0xeed9),	@(0xeeda),	@(0xeedb), | ||||||
|  | 		@(0xeedc),	@(0xeedd),	@(0xeede),	@(0xeedf),	@(0xeee0),	@(0xeee1),	@(0xeee2),	@(0xeee3), | ||||||
|  | 		@(0xeee4),	@(0xeee5),	@(0xeee6),	@(0xeee7),	@(0xeee8),	@(0xeee9),	@(0xeeea),	@(0xeeeb), | ||||||
|  | 		@(0xeeec),	@(0xeeed),	@(0xeeee),	@(0xeeef),	@(0xeef0),	@(0xeef1),	@(0xeef2),	@(0xeef3), | ||||||
|  | 		@(0xeef4),	@(0xeef5),	@(0xeef6),	@(0xeef7),	@(0xeef8),	@(0xeef9),	@(0xeefa),	@(0xeefb), | ||||||
|  | 		@(0xeefc),	@(0xeefd),	@(0xeefe),	@(0xeeff),	@(0xefc0),	@(0xefc1),	@(0xefc2),	@(0xefc3), | ||||||
|  | 		@(0xefc4),	@(0xefc5),	@(0xefc6),	@(0xefc7),	@(0xefc8),	@(0xefc9),	@(0xefca),	@(0xefcb), | ||||||
|  | 		@(0xefcc),	@(0xefcd),	@(0xefce),	@(0xefcf),	@(0xefd0),	@(0xefd1),	@(0xefd2),	@(0xefd3), | ||||||
|  | 		@(0xefd4),	@(0xefd5),	@(0xefd6),	@(0xefd7),	@(0xefd8),	@(0xefd9),	@(0xefda),	@(0xefdb), | ||||||
|  | 		@(0xefdc),	@(0xefdd),	@(0xefde),	@(0xefdf),	@(0xefe0),	@(0xefe1),	@(0xefe2),	@(0xefe3), | ||||||
|  | 		@(0xefe4),	@(0xefe5),	@(0xefe6),	@(0xefe7),	@(0xefe8),	@(0xefe9),	@(0xefea),	@(0xefeb), | ||||||
|  | 		@(0xefec),	@(0xefed),	@(0xefee),	@(0xefef),	@(0xeff0),	@(0xeff1),	@(0xeff2),	@(0xeff3), | ||||||
|  | 		@(0xeff4),	@(0xeff5),	@(0xeff6),	@(0xeff7),	@(0xeff8),	@(0xeff9),	@(0xeffa),	@(0xeffb), | ||||||
|  | 		@(0xeffc),	@(0xeffd),	@(0xeffe),	@(0xefff) | ||||||
|  | 	]]; | ||||||
|  |  | ||||||
|  | 	NSSet<NSNumber *> *const falseValids = storage_tests.false_valids(); | ||||||
|  | 	NSSet<NSNumber *> *const falseInvalids = storage_tests.false_invalids(); | ||||||
|  |  | ||||||
|  | 	XCTAssert(!falseValids.count, "%@ opcodes should be invalid but aren't: %@", @(falseValids.count), falseValids.hexDump); | ||||||
|  |  | ||||||
|  | 	NSMutableSet<NSNumber *> *const decodedUndecodables = [undecodables mutableCopy]; | ||||||
|  | 	[decodedUndecodables minusSet:falseInvalids]; | ||||||
|  | 	XCTAssert(!decodedUndecodables.count, "This test considers these undecodable but they were decoded: %@", decodedUndecodables.hexDump); | ||||||
|  |  | ||||||
|  | 	NSMutableSet<NSNumber *> *const trimmedInvalids = [falseInvalids mutableCopy]; | ||||||
|  | 	[trimmedInvalids minusSet:undecodables]; | ||||||
|  | 	XCTAssert(!trimmedInvalids.count, "%@ opcodes should be valid but aren't: %@", @(trimmedInvalids.count), trimmedInvalids.hexDump); | ||||||
|  |  | ||||||
|  | //	XCTAssert(!falseInvalids.count, "%@ opcodes should be valid but aren't: %@", @(falseInvalids.count), falseInvalids.hexDump); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @end | ||||||
| @@ -1,140 +0,0 @@ | |||||||
| // |  | ||||||
| //  ArrayBuilderTests.m |  | ||||||
| //  Clock Signal |  | ||||||
| // |  | ||||||
| //  Created by Thomas Harte on 19/11/2016. |  | ||||||
| //  Copyright 2016 Thomas Harte. All rights reserved. |  | ||||||
| // |  | ||||||
|  |  | ||||||
| #import <XCTest/XCTest.h> |  | ||||||
|  |  | ||||||
| #include "ArrayBuilder.hpp" |  | ||||||
|  |  | ||||||
| static NSData *inputData, *outputData; |  | ||||||
|  |  | ||||||
| static void setData(bool is_input, uint8_t *data, size_t size) |  | ||||||
| { |  | ||||||
| 	NSData *dataObject = [NSData dataWithBytes:data length:size]; |  | ||||||
| 	if(is_input) inputData = dataObject; else outputData = dataObject; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| @interface ArrayBuilderTests : XCTestCase |  | ||||||
| @end |  | ||||||
|  |  | ||||||
| @implementation ArrayBuilderTests |  | ||||||
|  |  | ||||||
| + (void)setUp |  | ||||||
| { |  | ||||||
| 	inputData = nil; |  | ||||||
| 	outputData = nil; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| - (void)assertMonotonicForInputSize:(size_t)inputSize outputSize:(size_t)outputSize |  | ||||||
| { |  | ||||||
| 	XCTAssert(inputData != nil, @"Should have received some input data"); |  | ||||||
| 	XCTAssert(outputData != nil, @"Should have received some output data"); |  | ||||||
|  |  | ||||||
| 	XCTAssert(inputData.length == inputSize, @"Input data should be %lu bytes long, was %lu", inputSize, (unsigned long)inputData.length); |  | ||||||
| 	XCTAssert(outputData.length == outputSize, @"Output data should be %lu bytes long, was %lu", outputSize, (unsigned long)outputData.length); |  | ||||||
|  |  | ||||||
| 	if(inputData.length == inputSize && outputData.length == outputSize) |  | ||||||
| 	{ |  | ||||||
| 		uint8_t *input = (uint8_t *)inputData.bytes; |  | ||||||
| 		uint8_t *output = (uint8_t *)outputData.bytes; |  | ||||||
|  |  | ||||||
| 		for(int c = 0; c < inputSize; c++) XCTAssert(input[c] == c, @"Input item %d should be %d, was %d", c, c, input[c]); |  | ||||||
| 		for(int c = 0; c < outputSize; c++) XCTAssert(output[c] == c + 0x80, @"Output item %d should be %d, was %d", c, c+0x80, output[c]); |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
|  |  | ||||||
| - (std::function<void(uint8_t *input, size_t input_size, uint8_t *output, size_t output_size)>)emptyFlushFunction |  | ||||||
| { |  | ||||||
| 	return [=] (uint8_t *input, size_t input_size, uint8_t *output, size_t output_size) {}; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| - (void)testSingleWriteSingleFlush |  | ||||||
| { |  | ||||||
| 	Outputs::CRT::ArrayBuilder arrayBuilder(200, 100, setData); |  | ||||||
|  |  | ||||||
| 	uint8_t *input = arrayBuilder.get_input_storage(5); |  | ||||||
| 	uint8_t *output = arrayBuilder.get_output_storage(3); |  | ||||||
|  |  | ||||||
| 	for(int c = 0; c < 5; c++) input[c] = c; |  | ||||||
| 	for(int c = 0; c < 3; c++) output[c] = c + 0x80; |  | ||||||
|  |  | ||||||
| 	arrayBuilder.flush(self.emptyFlushFunction); |  | ||||||
| 	arrayBuilder.submit(); |  | ||||||
|  |  | ||||||
| 	[self assertMonotonicForInputSize:5 outputSize:3]; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| - (void)testDoubleWriteSingleFlush |  | ||||||
| { |  | ||||||
| 	Outputs::CRT::ArrayBuilder arrayBuilder(200, 100, setData); |  | ||||||
| 	uint8_t *input; |  | ||||||
| 	uint8_t *output; |  | ||||||
|  |  | ||||||
| 	input = arrayBuilder.get_input_storage(2); |  | ||||||
| 	output = arrayBuilder.get_output_storage(2); |  | ||||||
|  |  | ||||||
| 	for(int c = 0; c < 2; c++) input[c] = c; |  | ||||||
| 	for(int c = 0; c < 2; c++) output[c] = c + 0x80; |  | ||||||
|  |  | ||||||
| 	input = arrayBuilder.get_input_storage(2); |  | ||||||
| 	output = arrayBuilder.get_output_storage(2); |  | ||||||
|  |  | ||||||
| 	for(int c = 0; c < 2; c++) input[c] = c+2; |  | ||||||
| 	for(int c = 0; c < 2; c++) output[c] = c+2 + 0x80; |  | ||||||
|  |  | ||||||
| 	arrayBuilder.flush(self.emptyFlushFunction); |  | ||||||
| 	arrayBuilder.submit(); |  | ||||||
|  |  | ||||||
| 	[self assertMonotonicForInputSize:4 outputSize:4]; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| - (void)testSubmitWithoutFlush |  | ||||||
| { |  | ||||||
| 	Outputs::CRT::ArrayBuilder arrayBuilder(200, 100, setData); |  | ||||||
|  |  | ||||||
| 	arrayBuilder.get_input_storage(5); |  | ||||||
| 	arrayBuilder.get_input_storage(8); |  | ||||||
| 	arrayBuilder.get_output_storage(6); |  | ||||||
| 	arrayBuilder.get_input_storage(12); |  | ||||||
| 	arrayBuilder.get_output_storage(3); |  | ||||||
|  |  | ||||||
| 	arrayBuilder.submit(); |  | ||||||
|  |  | ||||||
| 	XCTAssert(inputData.length == 0, @"No input data should have been received; %lu bytes were received", (unsigned long)inputData.length); |  | ||||||
| 	XCTAssert(outputData.length == 0, @"No output data should have been received; %lu bytes were received", (unsigned long)outputData.length); |  | ||||||
|  |  | ||||||
| 	arrayBuilder.flush(self.emptyFlushFunction); |  | ||||||
| 	arrayBuilder.submit(); |  | ||||||
|  |  | ||||||
| 	XCTAssert(inputData.length == 25, @"All input data should have been received; %lu bytes were received", (unsigned long)inputData.length); |  | ||||||
| 	XCTAssert(outputData.length == 9, @"All output data should have been received; %lu bytes were received", (unsigned long)outputData.length); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| - (void)testSubmitContinuity |  | ||||||
| { |  | ||||||
| 	Outputs::CRT::ArrayBuilder arrayBuilder(200, 100, setData); |  | ||||||
|  |  | ||||||
| 	arrayBuilder.get_input_storage(5); |  | ||||||
| 	arrayBuilder.get_output_storage(5); |  | ||||||
|  |  | ||||||
| 	arrayBuilder.flush(self.emptyFlushFunction); |  | ||||||
|  |  | ||||||
| 	uint8_t *input = arrayBuilder.get_input_storage(5); |  | ||||||
| 	uint8_t *output = arrayBuilder.get_output_storage(5); |  | ||||||
|  |  | ||||||
| 	arrayBuilder.submit(); |  | ||||||
|  |  | ||||||
| 	for(int c = 0; c < 5; c++) input[c] = c; |  | ||||||
| 	for(int c = 0; c < 5; c++) output[c] = c + 0x80; |  | ||||||
|  |  | ||||||
| 	arrayBuilder.flush(self.emptyFlushFunction); |  | ||||||
| 	arrayBuilder.submit(); |  | ||||||
|  |  | ||||||
| 	[self assertMonotonicForInputSize:5 outputSize:5]; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| @end |  | ||||||
| @@ -8,6 +8,14 @@ | |||||||
|  |  | ||||||
| #import <Foundation/Foundation.h> | #import <Foundation/Foundation.h> | ||||||
|  |  | ||||||
|  | typedef NS_ENUM(NSInteger, MOS6522BridgePort) { | ||||||
|  | 	MOS6522BridgePortA = 0, MOS6522BridgePortB = 1 | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | typedef NS_ENUM(NSInteger, MOS6522BridgeLine) { | ||||||
|  | 	MOS6522BridgeLineOne = 0, MOS6522BridgeLineTwo = 1 | ||||||
|  | }; | ||||||
|  |  | ||||||
| @interface MOS6522Bridge : NSObject | @interface MOS6522Bridge : NSObject | ||||||
|  |  | ||||||
| @property (nonatomic, readonly) BOOL irqLine; | @property (nonatomic, readonly) BOOL irqLine; | ||||||
| @@ -16,6 +24,7 @@ | |||||||
|  |  | ||||||
| - (void)setValue:(uint8_t)value forRegister:(NSUInteger)registerNumber; | - (void)setValue:(uint8_t)value forRegister:(NSUInteger)registerNumber; | ||||||
| - (uint8_t)valueForRegister:(NSUInteger)registerNumber; | - (uint8_t)valueForRegister:(NSUInteger)registerNumber; | ||||||
|  | - (BOOL)valueForControlLine:(MOS6522BridgeLine)line port:(MOS6522BridgePort)port; | ||||||
|  |  | ||||||
| - (void)runForHalfCycles:(NSUInteger)numberOfHalfCycles; | - (void)runForHalfCycles:(NSUInteger)numberOfHalfCycles; | ||||||
|  |  | ||||||
|   | |||||||
| @@ -19,7 +19,12 @@ class VanillaVIAPortHandler: public MOS::MOS6522::PortHandler { | |||||||
| 		bool irq_line; | 		bool irq_line; | ||||||
| 		uint8_t port_a_value; | 		uint8_t port_a_value; | ||||||
| 		uint8_t port_b_value; | 		uint8_t port_b_value; | ||||||
|  | 		bool control_line_values[2][2]; | ||||||
|  |  | ||||||
|  | 		/* | ||||||
|  | 			All methods below here are to replace those defined by | ||||||
|  | 			MOS::MOS6522::PortHandler. | ||||||
|  | 		*/ | ||||||
| 		void set_interrupt_status(bool new_status) { | 		void set_interrupt_status(bool new_status) { | ||||||
| 			irq_line = new_status; | 			irq_line = new_status; | ||||||
| 		} | 		} | ||||||
| @@ -27,6 +32,10 @@ class VanillaVIAPortHandler: public MOS::MOS6522::PortHandler { | |||||||
| 		uint8_t get_port_input(MOS::MOS6522::Port port) { | 		uint8_t get_port_input(MOS::MOS6522::Port port) { | ||||||
| 			return port ? port_b_value : port_a_value; | 			return port ? port_b_value : port_a_value; | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
|  | 		void set_control_line_output(MOS::MOS6522::Port port, MOS::MOS6522::Line line, bool value) { | ||||||
|  | 			control_line_values[int(port)][int(line)] = value; | ||||||
|  | 		} | ||||||
| }; | }; | ||||||
|  |  | ||||||
| @implementation MOS6522Bridge { | @implementation MOS6522Bridge { | ||||||
| @@ -75,4 +84,8 @@ class VanillaVIAPortHandler: public MOS::MOS6522::PortHandler { | |||||||
| 	return _viaPortHandler.port_b_value; | 	return _viaPortHandler.port_b_value; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | - (BOOL)valueForControlLine:(MOS6522BridgeLine)line port:(MOS6522BridgePort)port { | ||||||
|  | 	return _viaPortHandler.control_line_values[port][line]; | ||||||
|  | } | ||||||
|  |  | ||||||
| @end | @end | ||||||
|   | |||||||
							
								
								
									
										59
									
								
								OSBindings/Mac/Clock SignalTests/Comparative68000.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										59
									
								
								OSBindings/Mac/Clock SignalTests/Comparative68000.hpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,59 @@ | |||||||
|  | // | ||||||
|  | //  Comparative68000.hpp | ||||||
|  | //  Clock SignalTests | ||||||
|  | // | ||||||
|  | //  Created by Thomas Harte on 29/04/2019. | ||||||
|  | //  Copyright © 2019 Thomas Harte. All rights reserved. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | #ifndef Comparative68000_hpp | ||||||
|  | #define Comparative68000_hpp | ||||||
|  |  | ||||||
|  | #include <zlib.h> | ||||||
|  |  | ||||||
|  | #include "68000.hpp" | ||||||
|  |  | ||||||
|  | class ComparativeBusHandler: public CPU::MC68000::BusHandler { | ||||||
|  | 	public: | ||||||
|  | 		ComparativeBusHandler(const char *trace_name) { | ||||||
|  | 			trace = gzopen(trace_name, "rt"); | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		~ComparativeBusHandler() { | ||||||
|  | 			gzclose(trace); | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		void will_perform(uint32_t address, uint16_t opcode) { | ||||||
|  | 			// Obtain the next line from the trace file. | ||||||
|  | 			char correct_state[300] = "\n"; | ||||||
|  | 			gzgets(trace, correct_state, sizeof(correct_state)); | ||||||
|  | 			++line_count; | ||||||
|  |  | ||||||
|  | 			// Generate state locally. | ||||||
|  | 			const auto state = get_state(); | ||||||
|  | 			char local_state[300]; | ||||||
|  | 			sprintf(local_state, "%04x: %02x %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x\n", | ||||||
|  | 				address, | ||||||
|  | 				state.status, | ||||||
|  | 				state.data[0], state.data[1], state.data[2], state.data[3], state.data[4], state.data[5], state.data[6], state.data[7], | ||||||
|  | 				state.address[0], state.address[1], state.address[2], state.address[3], state.address[4], state.address[5], state.address[6], | ||||||
|  | 				(state.status & 0x2000) ? state.supervisor_stack_pointer : state.user_stack_pointer | ||||||
|  | 				); | ||||||
|  |  | ||||||
|  | 			// Check that the two coincide. | ||||||
|  | 			if(strcmp(correct_state, local_state)) { | ||||||
|  | 				fprintf(stderr, "Diverges at line %d\n", line_count); | ||||||
|  | 				fprintf(stderr, "Good: %s", correct_state); | ||||||
|  | 				fprintf(stderr, "Bad:  %s", local_state); | ||||||
|  | 				assert(false); | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		virtual CPU::MC68000::ProcessorState get_state() = 0; | ||||||
|  |  | ||||||
|  | 	private: | ||||||
|  | 		int line_count = 0; | ||||||
|  | 		gzFile trace; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | #endif /* Comparative68000_hpp */ | ||||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user