mirror of
				https://github.com/TomHarte/CLK.git
				synced 2025-10-31 05:16:08 +00:00 
			
		
		
		
	Compare commits
	
		
			619 Commits
		
	
	
		
			2017-08-16
			...
			2018-01-15
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|  | 4cf2e16b5c | ||
|  | 9cbd61e709 | ||
|  | 0202c7afb2 | ||
|  | c187c5a637 | ||
|  | 23c34a8c14 | ||
|  | 93ece2aec7 | ||
|  | e12ab8fe2e | ||
|  | 2fe0ceb52a | ||
|  | f354c12c81 | ||
|  | def82cba49 | ||
|  | e7bc7b94c9 | ||
|  | aafdff49be | ||
|  | 4ef583813a | ||
|  | 9f97fb738e | ||
|  | 4e124047c6 | ||
|  | 6eb56a1564 | ||
|  | 35fc0a5c16 | ||
|  | b36c917810 | ||
|  | a5ac8c824e | ||
|  | 0ccc104027 | ||
|  | 8be6cb827b | ||
|  | 2f59226300 | ||
|  | 793ef68206 | ||
|  | 513c067f94 | ||
|  | 999a0c22d4 | ||
|  | 5d0832613f | ||
|  | 2ffde4c3c2 | ||
|  | 57ddfcd645 | ||
|  | fc16e8eb8c | ||
|  | 655b971976 | ||
|  | 3e1d8ea082 | ||
|  | 772c320d5a | ||
|  | bcc7ad0c30 | ||
|  | 73b4e1722b | ||
|  | 185cd3c123 | ||
|  | ed564cb810 | ||
|  | b78ece1f1e | ||
|  | c8367a017f | ||
|  | 344a12566b | ||
|  | c07113ea95 | ||
|  | bc2879c412 | ||
|  | 1d47b55729 | ||
|  | db25b4554b | ||
|  | 05b95ea2e0 | ||
|  | 250f7bf6b0 | ||
|  | 34db35b500 | ||
|  | f75590253d | ||
|  | 4f6abc9059 | ||
|  | c70dbc6a49 | ||
|  | 1c255b9e7d | ||
|  | 188bfa9c18 | ||
|  | c7f8f37822 | ||
|  | 4a19dbb8cb | ||
|  | bf0601123b | ||
|  | 9339f3413f | ||
|  | c18517be4b | ||
|  | eef34adcbd | ||
|  | 769d9dfbb9 | ||
|  | 6a0bb83716 | ||
|  | 6da8a3e24b | ||
|  | e349161a53 | ||
|  | d5b1a9d918 | ||
|  | 76af0228dd | ||
|  | 2cc1a2684a | ||
|  | 98a9d57c0b | ||
|  | c481293aca | ||
|  | 5fd0a2b9ea | ||
|  | 11b73a9c0b | ||
|  | c4950574ea | ||
|  | 0b297f2972 | ||
|  | f9f870ad2d | ||
|  | cbba6a5595 | ||
|  | 0a079b0f94 | ||
|  | 9a7e974579 | ||
|  | f4d414d6e4 | ||
|  | b4bfcd4279 | ||
|  | e8ddff0ee0 | ||
|  | b61fab9df7 | ||
|  | 28fb1ce2ae | ||
|  | b9b107ee85 | ||
|  | f17758e7f9 | ||
|  | 0bb24075b6 | ||
|  | db6d9b59d0 | ||
|  | 51e82c10c5 | ||
|  | 2d892da225 | ||
|  | b99ba2bc02 | ||
|  | d36e9d0b0d | ||
|  | 2dc1d4443e | ||
|  | f8a2459c91 | ||
|  | ac80d10cd8 | ||
|  | eb6b612052 | ||
|  | d66a33f249 | ||
|  | ec4c259695 | ||
|  | ad50b6b1fb | ||
|  | 3da323c657 | ||
|  | aca7842ca4 | ||
|  | 38c912b968 | ||
|  | 7a52e7d6d2 | ||
|  | c36de4f640 | ||
|  | 504772bcda | ||
|  | 5d0c33d545 | ||
|  | 7bc1bcd493 | ||
|  | b0616ee10c | ||
|  | da57df55e8 | ||
|  | 4daea1121b | ||
|  | afcdd64d5e | ||
|  | 798cdba979 | ||
|  | f957344ac4 | ||
|  | b3fbd0f352 | ||
|  | 042edc72f7 | ||
|  | 943418c434 | ||
|  | 7d7e2538bd | ||
|  | 7a544731e2 | ||
|  | e1914b4f16 | ||
|  | 202958303e | ||
|  | 57b060ac3c | ||
|  | 8653eb8b55 | ||
|  | a4f0a260fd | ||
|  | d4a53e82bb | ||
|  | 6eedc99286 | ||
|  | ec266d6c8e | ||
|  | e3a5218e78 | ||
|  | a473338abe | ||
|  | ae21782adc | ||
|  | ee44d671e7 | ||
|  | 3766bef962 | ||
|  | ad3df36c20 | ||
|  | 38b11893e8 | ||
|  | e4534775b0 | ||
|  | fe7fc6b22e | ||
|  | fe0cdc8d69 | ||
|  | 7f8a13a409 | ||
|  | ca26ce8400 | ||
|  | d3dd8f3f2a | ||
|  | 3c8d2d579d | ||
|  | edcbb3dfed | ||
|  | 9c8158753e | ||
|  | 5da9cb2957 | ||
|  | 54c845b6e2 | ||
|  | ee84f33ab5 | ||
|  | f0f149c018 | ||
|  | 7dfbe4bb93 | ||
|  | aa4eef41d8 | ||
|  | 69ec8a362e | ||
|  | ecd7d4731b | ||
|  | 563aa051e4 | ||
|  | 642bb8333f | ||
|  | c558e86e03 | ||
|  | dbb14ea2e2 | ||
|  | 173e16b107 | ||
|  | 7d2adad67e | ||
|  | d33612def5 | ||
|  | 9cb6ca3440 | ||
|  | e957e40b14 | ||
|  | 7a8a43a96a | ||
|  | 0eb5dd9688 | ||
|  | a14b53a9ab | ||
|  | 576d554a2c | ||
|  | 68a2895753 | ||
|  | f90b3f06aa | ||
|  | f067fa9923 | ||
|  | ee9f89ccb5 | ||
|  | 573a9c6fb2 | ||
|  | a46a37fba9 | ||
|  | 324b57c054 | ||
|  | ae50ca9ab2 | ||
|  | 6e4bde00d3 | ||
|  | d4d0dd87c9 | ||
|  | 221c05ca76 | ||
|  | ff21ff90eb | ||
|  | fcf295fd68 | ||
|  | 2008dec1ed | ||
|  | b4f3c41aae | ||
|  | 90c4e3726f | ||
|  | c83b3cefbc | ||
|  | a8ac51da73 | ||
|  | bc65ba3f9b | ||
|  | 79674fdbd3 | ||
|  | adea4711f1 | ||
|  | bded406caa | ||
|  | 85085a6375 | ||
|  | d122d598d3 | ||
|  | d6192b8c58 | ||
|  | f02d4dbb59 | ||
|  | f3818991f6 | ||
|  | c7dd6247f0 | ||
|  | 99e17600d7 | ||
|  | 1d821ad459 | ||
|  | c60a9ee3c3 | ||
|  | ffcbd1e94d | ||
|  | 6c8b503402 | ||
|  | 55e1d25966 | ||
|  | 0bdd776114 | ||
|  | c1b7bceec8 | ||
|  | dc4f58e40c | ||
|  | 3b8cdd620c | ||
|  | 3365ff0200 | ||
|  | 89c3e2ba5a | ||
|  | c6306db47c | ||
|  | 8ddc64c82a | ||
|  | b887cb7255 | ||
|  | d54ee2af82 | ||
|  | 723c113186 | ||
|  | c368c4443e | ||
|  | 7b25b03cd5 | ||
|  | 9961d13e2d | ||
|  | 29b5ccc767 | ||
|  | 90af395df2 | ||
|  | 6f8d4d6c5c | ||
|  | 63381ff505 | ||
|  | 2ea050556b | ||
|  | 8dcac6561e | ||
|  | 90d33949f9 | ||
|  | d3e68914dd | ||
|  | 82ad0354c4 | ||
|  | 073e439518 | ||
|  | 27b123549b | ||
|  | de9db724a7 | ||
|  | 532ea35ee9 | ||
|  | e9ddec35d6 | ||
|  | 7647f8089b | ||
|  | f00f0353a6 | ||
|  | e19ae5d43d | ||
|  | 0a9622435c | ||
|  | f704932475 | ||
|  | d0f096a20b | ||
|  | 949d0f3928 | ||
|  | a2d48223c3 | ||
|  | fc080c773f | ||
|  | adb3811847 | ||
|  | dbbea78b76 | ||
|  | fd96e3e657 | ||
|  | 06d81b3a97 | ||
|  | 88551607a6 | ||
|  | 2a9dccff26 | ||
|  | 1027f85683 | ||
|  | 9bb9cb4a65 | ||
|  | 2de80646ec | ||
|  | bf4ed57f68 | ||
|  | 9578f3dc44 | ||
|  | a97c478a34 | ||
|  | e0113d5dce | ||
|  | 980cf541d2 | ||
|  | 69c983f9ee | ||
|  | 70039d22f1 | ||
|  | ebdb80c908 | ||
|  | 0eaac99d74 | ||
|  | 792061a82b | ||
|  | d2ba7d7430 | ||
|  | 8713cfa613 | ||
|  | aa77be1c10 | ||
|  | e6aa2321cd | ||
|  | c827d14d97 | ||
|  | 2979d19621 | ||
|  | 282e5c9d3e | ||
|  | ede47d4ba7 | ||
|  | 5408efe9b5 | ||
|  | d6141cb020 | ||
|  | 198d0fd1de | ||
|  | 6d80856f02 | ||
|  | 4778616fd7 | ||
|  | 2e025d85eb | ||
|  | 61f2191c86 | ||
|  | c1eab8d5f3 | ||
|  | 91d2d59ae5 | ||
|  | 5aef81cf24 | ||
|  | 3550196bed | ||
|  | bce58683fa | ||
|  | c91a5875b2 | ||
|  | 2e15fab651 | ||
|  | 6a176082a0 | ||
|  | fd346bac3e | ||
|  | 25e9dcc800 | ||
|  | 792cbb1536 | ||
|  | 2e12370251 | ||
|  | 7adc25694a | ||
|  | ca80da7fbe | ||
|  | f853d87884 | ||
|  | 524087805f | ||
|  | 916eb96b47 | ||
|  | 4add2c1051 | ||
|  | cb0f58ab7a | ||
|  | d9e56711ce | ||
|  | d60692b6fd | ||
|  | 5b6ea35d96 | ||
|  | 4cbc87a17d | ||
|  | 46e7c199b2 | ||
|  | ff7ba526fb | ||
|  | a825da3715 | ||
|  | fabaf4e607 | ||
|  | 153067c018 | ||
|  | f7f2736d4d | ||
|  | a16ca65825 | ||
|  | cb015c83e1 | ||
|  | 2203499215 | ||
|  | c0055a5a5f | ||
|  | 62218e81bf | ||
|  | c45d4831ec | ||
|  | 9fd33bdfde | ||
|  | 6e1d69581c | ||
|  | f95515ae81 | ||
|  | 09c855a659 | ||
|  | 16c96b605a | ||
|  | e10d369e53 | ||
|  | 0d1b63a8c5 | ||
|  | ddcdd07dd0 | ||
|  | 35da3edf60 | ||
|  | d605022ea3 | ||
|  | 0da78065ce | ||
|  | 4b68c372c6 | ||
|  | 13406fedd8 | ||
|  | a209ae76ca | ||
|  | 0116d7f071 | ||
|  | 512e877d06 | ||
|  | 1e1efcdcb8 | ||
|  | bc2f58e9de | ||
|  | fd10c42433 | ||
|  | 794437f20f | ||
|  | 23d5849cda | ||
|  | 5070a8414f | ||
|  | 5a3ca0e447 | ||
|  | e384c50580 | ||
|  | b9734278f6 | ||
|  | f807a6b608 | ||
|  | 833f8c02a4 | ||
|  | 0248c6a282 | ||
|  | 218b976dbc | ||
|  | 513903890e | ||
|  | 1157bde453 | ||
|  | 46345c6a3e | ||
|  | c13f8e5390 | ||
|  | ad9df4bb90 | ||
|  | e983854e71 | ||
|  | ec999446e8 | ||
|  | 5e3e91373a | ||
|  | c52348d8d7 | ||
|  | 9e0907ee76 | ||
|  | 9ad4025138 | ||
|  | 405f58d6a3 | ||
|  | afbd1c425c | ||
|  | b2c1b83fcd | ||
|  | 8d2b9a581a | ||
|  | 1825af0dd3 | ||
|  | c2f6799f0c | ||
|  | b5b6219cb7 | ||
|  | 185a699279 | ||
|  | 96b8f9ae9f | ||
|  | 88e2350b8f | ||
|  | 5c141af734 | ||
|  | da580e4186 | ||
|  | 57ee09dffb | ||
|  | 7c8e830b90 | ||
|  | ba5f668338 | ||
|  | 2c1e99858b | ||
|  | 7f2febeec9 | ||
|  | 2d7a4fe5f0 | ||
|  | 91b867a7b3 | ||
|  | 3944e734d3 | ||
|  | ce78d9d12c | ||
|  | edbc60a3fb | ||
|  | 6ea3ff62df | ||
|  | 88959571f1 | ||
|  | b4583e976e | ||
|  | 92d9805f09 | ||
|  | 0c2dd62328 | ||
|  | 3f4d90d775 | ||
|  | 542ec4312f | ||
|  | 18798c9886 | ||
|  | 7aaf27389c | ||
|  | ee179aa7bd | ||
|  | 3a05ce36de | ||
|  | 4f289ab10b | ||
|  | 78ee46270b | ||
|  | edb632af52 | ||
|  | 19c03a08a6 | ||
|  | 44cdc124af | ||
|  | b37787a414 | ||
|  | 53b99ea248 | ||
|  | 97a2be71e3 | ||
|  | f623bff5c3 | ||
|  | 2511fc8401 | ||
|  | d37ec9e5b0 | ||
|  | 95c82f5b36 | ||
|  | ec202ed8be | ||
|  | 7190225603 | ||
|  | 52e7cabd4e | ||
|  | 064f1dfdbc | ||
|  | f40e1fd840 | ||
|  | e194a2a015 | ||
|  | c39759333a | ||
|  | edb9fd301c | ||
|  | ea5023ac26 | ||
|  | 0fb363ea0e | ||
|  | 1cc85615d5 | ||
|  | 7b01c1bee6 | ||
|  | 35705c5345 | ||
|  | f41da83d97 | ||
|  | cd1e5dea4d | ||
|  | ef605eda51 | ||
|  | 2f48ee59fa | ||
|  | f86729c4ac | ||
|  | 5f99f4442c | ||
|  | 326857a84d | ||
|  | 5dd3945695 | ||
|  | 19eb975c73 | ||
|  | 698ffca51b | ||
|  | fe3cc5c57c | ||
|  | f488854720 | ||
|  | 51c0c45e04 | ||
|  | c3e1489a8e | ||
|  | e3420f62c6 | ||
|  | 970c80f2e3 | ||
|  | 9f4a407f94 | ||
|  | 5dda897334 | ||
|  | 3982e375e3 | ||
|  | a8524daecb | ||
|  | d1ce764201 | ||
|  | 8875982e1f | ||
|  | 3319a4f589 | ||
|  | c7f27b2db4 | ||
|  | 631f630549 | ||
|  | 2a08bd9ecc | ||
|  | f789ee4ff0 | ||
|  | a295b42497 | ||
|  | d8337492cc | ||
|  | 15c8debc16 | ||
|  | 67af153c16 | ||
|  | d72dad2d1a | ||
|  | 698e4fe550 | ||
|  | b5406b90cd | ||
|  | 05a93ba237 | ||
|  | 77548d14db | ||
|  | b85dd608e7 | ||
|  | 231f13d810 | ||
|  | 704bfa114c | ||
|  | 44a56724cb | ||
|  | 5fbea625ae | ||
|  | ac57b37e96 | ||
|  | e3e9baeaa4 | ||
|  | e071123f90 | ||
|  | 98adb01721 | ||
|  | d6a5f9a29e | ||
|  | 0d84b4b9dd | ||
|  | a85909198f | ||
|  | 98751e6ac8 | ||
|  | da082673d7 | ||
|  | 35fe4d50d4 | ||
|  | b835cb73e2 | ||
|  | 662d031e3c | ||
|  | bf20c717fb | ||
|  | 4d4a0cf1d2 | ||
|  | b62f3e726a | ||
|  | 82b13e98f2 | ||
|  | 9ac831b09c | ||
|  | 42616da7ff | ||
|  | 2f13517f38 | ||
|  | fb9fd26af7 | ||
|  | d3c385b471 | ||
|  | 96bf133924 | ||
|  | 6d6cac429d | ||
|  | dc0b65f9c9 | ||
|  | 8882aa496f | ||
|  | 0622187ddf | ||
|  | 523e1288fa | ||
|  | 1a96cce26f | ||
|  | a4e275e1fc | ||
|  | 6075064400 | ||
|  | ff6e65cca9 | ||
|  | 90d2347c90 | ||
|  | 90c7056d12 | ||
|  | fed2bc9fc9 | ||
|  | ff510f3b84 | ||
|  | 3b12fca417 | ||
|  | 8eeb7e73cd | ||
|  | 7fd6699e0b | ||
|  | ed70b15fc9 | ||
|  | ff24e1de31 | ||
|  | 6547102511 | ||
|  | d538ff5039 | ||
|  | a49594c6a3 | ||
|  | 3544c0f014 | ||
|  | f26fe3756c | ||
|  | a42ca290cb | ||
|  | da09098e49 | ||
|  | 450712f39c | ||
|  | 24b3faa427 | ||
|  | 40d11ea0e3 | ||
|  | ab2bcb939f | ||
|  | 45499050b6 | ||
|  | 0c9197df30 | ||
|  | a1e200cc65 | ||
|  | 8a612bb6ab | ||
|  | e6ac939ae0 | ||
|  | b034d4e6f8 | ||
|  | de218611e4 | ||
|  | 615f7ce176 | ||
|  | b306776ba9 | ||
|  | 0f85cffc78 | ||
|  | 96648df5fe | ||
|  | 2c99a2d6ec | ||
|  | 4af333d5ec | ||
|  | a5f9869769 | ||
|  | f10be2a18a | ||
|  | c88d627b4e | ||
|  | b30bb2a234 | ||
|  | d498080eb4 | ||
|  | 334afbc710 | ||
|  | 17c13624e5 | ||
|  | 113349d272 | ||
|  | 0ced7866fc | ||
|  | d06031dfcb | ||
|  | 3f22a71276 | ||
|  | 53a88a7e12 | ||
|  | 4a66dd9e82 | ||
|  | 522839143f | ||
|  | b4c532c0d5 | ||
|  | a3e2d142e3 | ||
|  | 63ee8c9d58 | ||
|  | 437023bff6 | ||
|  | 4465098157 | ||
|  | 56dd677e9c | ||
|  | 9aa150c338 | ||
|  | fab6908129 | ||
|  | e34d4ce903 | ||
|  | d411827733 | ||
|  | f1ba7755dd | ||
|  | 57bfec285f | ||
|  | bdda701207 | ||
|  | 487fe83dca | ||
|  | 6c5a03187b | ||
|  | 97f57a3948 | ||
|  | 7d7aa2f5d5 | ||
|  | e7ad79c79a | ||
|  | 28550c0227 | ||
|  | 6e99169348 | ||
|  | 1017bb9f6b | ||
|  | 3caa4705ca | ||
|  | 039aed1bd1 | ||
|  | d77d7fdd78 | ||
|  | c6e6c3fcfb | ||
|  | ecd3350a6f | ||
|  | fa19e2d9c2 | ||
|  | 95d360251d | ||
|  | 7af3de010e | ||
|  | cefd421992 | ||
|  | a914eadc85 | ||
|  | 131b340d75 | ||
|  | e956740c56 | ||
|  | 8afd83b91f | ||
|  | 40d7a603db | ||
|  | ee71be0e7e | ||
|  | cde29c4bf4 | ||
|  | e1aded0d95 | ||
|  | 1237f174fe | ||
|  | 0cbc1753b9 | ||
|  | 5cf0395936 | ||
|  | 6315c22b80 | ||
|  | 4614a56843 | ||
|  | 8f5ae4a326 | ||
|  | 8fdc5012e4 | ||
|  | e88a51e75e | ||
|  | 49285e9caa | ||
|  | e3f2118757 | ||
|  | daeaa4752f | ||
|  | 5344e3098b | ||
|  | cedb809c21 | ||
|  | 2d9efccc98 | ||
|  | 8ce46b6e49 | ||
|  | f2699a3f2b | ||
|  | 85253a5876 | ||
|  | 911ee5a0d3 | ||
|  | 57c5b38a6d | ||
|  | 669e0caff5 | ||
|  | b24d04fc09 | ||
|  | ef07c33741 | ||
|  | e559a65ede | ||
|  | 5bdd24d93f | ||
|  | af61a7fa28 | ||
|  | c8c1792c3f | ||
|  | e6683e7f2d | ||
|  | 0c1714b695 | ||
|  | dc0ca83003 | ||
|  | 2c2dd8073c | ||
|  | 4f8b89772e | ||
|  | 733ee5a5c3 | ||
|  | fedf5a44a6 | ||
|  | 6a09022896 | ||
|  | 5b3c707959 | ||
|  | 9b21ef1507 | ||
|  | da3e8655e9 | ||
|  | 41e4386164 | ||
|  | b0a98bd239 | ||
|  | 42ad670ec8 | ||
|  | 58063b69a6 | ||
|  | 378f231499 | ||
|  | f68565a33f | ||
|  | 175faebdc9 | ||
|  | 76c6b715a2 | ||
|  | b476f06524 | ||
|  | 48290a8bbe | ||
|  | 9d9a1c341d | ||
|  | 952da1e581 | ||
|  | a988255558 | ||
|  | cca66ab450 | ||
|  | bcd7a312a4 | ||
|  | 925e774015 | ||
|  | 4c15e46fd1 | ||
|  | 3c50903a2b | ||
|  | 75208b0762 | ||
|  | f1e64169cd | ||
|  | 903a17ae11 | ||
|  | de1c526789 | ||
|  | b7e0f64892 | ||
|  | 148591b7f2 | ||
|  | 2105597910 | ||
|  | 3c148f5721 | ||
|  | 360c8a99a3 | ||
|  | 06e31f5102 | ||
|  | 42b5b66305 | 
							
								
								
									
										9
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										9
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @@ -18,9 +18,14 @@ DerivedData | |||||||
| *.xcuserstate | *.xcuserstate | ||||||
| .DS_Store | .DS_Store | ||||||
|  |  | ||||||
| # Exclude system ROMs | # Exclude system ROMs and unit test ROMs | ||||||
| ROMImages/* | ROMImages/* | ||||||
| OSBindings/Mac/Clock SignalTests/Atari\ ROMs | OSBindings/Mac/Clock SignalTests/Atari ROMs | ||||||
|  | OSBindings/Mac/Clock SignalTests/MSX ROMs | ||||||
|  |  | ||||||
|  | # Exclude intermediate build products | ||||||
|  | *.o | ||||||
|  | .sconsign.dblite | ||||||
|  |  | ||||||
| # CocoaPods | # CocoaPods | ||||||
| # | # | ||||||
|   | |||||||
							
								
								
									
										30
									
								
								BUILD.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								BUILD.txt
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,30 @@ | |||||||
|  | Linux, BSD | ||||||
|  | ========== | ||||||
|  |  | ||||||
|  | Prerequisites are SDL 2, ZLib and OpenGL (or Mesa), and SCons for the provided build script. OpenGL 3.2 or better is required at runtime. | ||||||
|  |  | ||||||
|  | Build: | ||||||
|  |  | ||||||
|  | 	cd OSBindings/SDL | ||||||
|  | 	scons | ||||||
|  |  | ||||||
|  | Optionally: | ||||||
|  |  | ||||||
|  | 	cp clksignal /usr/bin | ||||||
|  |  | ||||||
|  | To launch: | ||||||
|  |  | ||||||
|  | 	clksignal file | ||||||
|  |  | ||||||
|  | Setting up clksignal as the associated program for supported file types in your favoured filesystem browser is recommended; it has no file navigation abilities of its own. | ||||||
|  |  | ||||||
|  | Some emulated systems require the provision of original machine ROMs. These are not included and may be located in either /usr/local/share/CLK/ or /usr/share/CLK/. You will be prompted for them if they are found to be missing. The structure should mirror that under OSBindings in the source archive; see the readme.txt in each folder to determine the proper files and names ahead of time. | ||||||
|  |  | ||||||
|  | macOS | ||||||
|  | ===== | ||||||
|  |  | ||||||
|  | There are no prerequisites beyond the normal system libraries; the macOS build is a native Cocoa application. | ||||||
|  |  | ||||||
|  | Build: open the Xcode project in OSBindings/Mac and press command+b. | ||||||
|  |  | ||||||
|  | Machine ROMs are intended to be built into the application bundle; populate the dummy folders below ROMImages before building. | ||||||
| @@ -161,7 +161,7 @@ class HalfCycles: public WrappedInt<HalfCycles> { | |||||||
| 		inline HalfCycles(int l) : WrappedInt<HalfCycles>(l) {} | 		inline HalfCycles(int l) : WrappedInt<HalfCycles>(l) {} | ||||||
| 		inline HalfCycles() : WrappedInt<HalfCycles>() {} | 		inline HalfCycles() : WrappedInt<HalfCycles>() {} | ||||||
|  |  | ||||||
| 		inline HalfCycles(const Cycles cycles) : WrappedInt<HalfCycles>(cycles.as_int() << 1) {} | 		inline HalfCycles(const Cycles cycles) : WrappedInt<HalfCycles>(cycles.as_int() * 2) {} | ||||||
| 		inline HalfCycles(const HalfCycles &half_cycles) : WrappedInt<HalfCycles>(half_cycles.length_) {} | 		inline HalfCycles(const HalfCycles &half_cycles) : 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. | ||||||
| @@ -169,13 +169,20 @@ class HalfCycles: public WrappedInt<HalfCycles> { | |||||||
| 			return Cycles(length_ >> 1); | 			return Cycles(length_ >> 1); | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		///Flushes the whole cycles in @c this, subtracting that many from the total stored here. | 		/// Flushes the whole cycles in @c this, subtracting that many from the total stored here. | ||||||
| 		inline Cycles flush_cycles() { | 		inline Cycles flush_cycles() { | ||||||
| 			Cycles result(length_ >> 1); | 			Cycles result(length_ >> 1); | ||||||
| 			length_ &= 1; | 			length_ &= 1; | ||||||
| 			return result; | 			return result; | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
|  | 		/// Flushes the half cycles in @c this, returning the number stored and setting this total to zero. | ||||||
|  | 		inline 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. | ||||||
|   | |||||||
| @@ -6,8 +6,14 @@ | |||||||
| //  Copyright © 2017 Thomas Harte. All rights reserved.
 | //  Copyright © 2017 Thomas Harte. All rights reserved.
 | ||||||
| //
 | //
 | ||||||
| 
 | 
 | ||||||
| #ifndef ForceInline_h | #ifndef ForceInline_hpp | ||||||
| #define ForceInline_h | #define ForceInline_hpp | ||||||
|  | 
 | ||||||
|  | #ifdef DEBUG | ||||||
|  | 
 | ||||||
|  | #define forceinline | ||||||
|  | 
 | ||||||
|  | #else | ||||||
| 
 | 
 | ||||||
| #ifdef __GNUC__ | #ifdef __GNUC__ | ||||||
| #define forceinline __attribute__((always_inline)) inline | #define forceinline __attribute__((always_inline)) inline | ||||||
| @@ -15,4 +21,6 @@ | |||||||
| #define forceinline __forceinline | #define forceinline __forceinline | ||||||
| #endif | #endif | ||||||
| 
 | 
 | ||||||
|  | #endif | ||||||
|  | 
 | ||||||
| #endif /* ForceInline_h */ | #endif /* ForceInline_h */ | ||||||
							
								
								
									
										60
									
								
								ClockReceiver/Sleeper.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										60
									
								
								ClockReceiver/Sleeper.hpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,60 @@ | |||||||
|  | // | ||||||
|  | //  Sleeper.h | ||||||
|  | //  Clock Signal | ||||||
|  | // | ||||||
|  | //  Created by Thomas Harte on 20/08/2017. | ||||||
|  | //  Copyright © 2017 Thomas Harte. All rights reserved. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | #ifndef Sleeper_hpp | ||||||
|  | #define Sleeper_hpp | ||||||
|  |  | ||||||
|  | /*! | ||||||
|  | 	A sleeper is any component that sometimes requires a clock but at other times is 'asleep' — i.e. is not doing | ||||||
|  | 	any clock-derived work, so needn't receive a clock. A disk controller is an archetypal example. | ||||||
|  |  | ||||||
|  | 	A sleeper will signal sleeps and wakes to an observer. | ||||||
|  |  | ||||||
|  | 	This is intended to allow for performance improvements to machines with components that can sleep. The observer | ||||||
|  | 	callout is virtual so the intended use case is that a machine holds a component that might sleep. Its transitions | ||||||
|  | 	into and out of sleep are sufficiently infrequent that a virtual call to announce them costs sufficiently little that | ||||||
|  | 	the saved ::run_fors add up to a substantial amount. | ||||||
|  |  | ||||||
|  | 	By convention, sleeper components must be willing to accept ::run_for even after announcing sleep. It's a hint, | ||||||
|  | 	not a command. | ||||||
|  | */ | ||||||
|  | class Sleeper { | ||||||
|  | 	public: | ||||||
|  | 		Sleeper() : sleep_observer_(nullptr) {} | ||||||
|  |  | ||||||
|  | 		class SleepObserver { | ||||||
|  | 			public: | ||||||
|  | 				/// Called to inform an observer that the component @c component has either gone to sleep or become awake. | ||||||
|  | 				virtual void set_component_is_sleeping(void *component, bool is_sleeping) = 0; | ||||||
|  | 		}; | ||||||
|  |  | ||||||
|  | 		/// Registers @c observer as the new sleep observer; | ||||||
|  | 		void set_sleep_observer(SleepObserver *observer) { | ||||||
|  | 			sleep_observer_ = observer; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		/// @returns @c true if the component is currently sleeping; @c false otherwise. | ||||||
|  | 		virtual bool is_sleeping() = 0; | ||||||
|  |  | ||||||
|  | 	protected: | ||||||
|  | 		/// Provided for subclasses; send sleep announcements to the sleep_observer_. | ||||||
|  | 		SleepObserver *sleep_observer_; | ||||||
|  |  | ||||||
|  | 		/*! | ||||||
|  | 			Provided for subclasses; call this whenever is_sleeping might have changed, and the observer will be notified, | ||||||
|  | 			if one exists. | ||||||
|  |  | ||||||
|  | 			@c is_sleeping will be called only if there is an observer. | ||||||
|  | 		*/ | ||||||
|  | 		void update_sleep_observer() { | ||||||
|  | 			if(!sleep_observer_) return; | ||||||
|  | 			sleep_observer_->set_component_is_sleeping(this, is_sleeping()); | ||||||
|  | 		} | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | #endif /* Sleeper_h */ | ||||||
| @@ -7,47 +7,35 @@ | |||||||
| // | // | ||||||
|  |  | ||||||
| #include "1770.hpp" | #include "1770.hpp" | ||||||
| #include "../../Storage/Disk/Encodings/MFM.hpp" | #include "../../Storage/Disk/Encodings/MFM/Constants.hpp" | ||||||
|  |  | ||||||
| using namespace WD; | using namespace WD; | ||||||
|  |  | ||||||
| WD1770::Status::Status() : |  | ||||||
| 		type(Status::One), |  | ||||||
| 		write_protect(false), |  | ||||||
| 		record_type(false), |  | ||||||
| 		spin_up(false), |  | ||||||
| 		record_not_found(false), |  | ||||||
| 		crc_error(false), |  | ||||||
| 		seek_error(false), |  | ||||||
| 		lost_data(false), |  | ||||||
| 		data_request(false), |  | ||||||
| 		interrupt_request(false), |  | ||||||
| 		busy(false) {} |  | ||||||
|  |  | ||||||
| WD1770::WD1770(Personality p) : | WD1770::WD1770(Personality p) : | ||||||
| 		Storage::Disk::MFMController(8000000, 16, 300), | 		Storage::Disk::MFMController(8000000), | ||||||
| 		interesting_event_mask_((int)Event1770::Command), |  | ||||||
| 		resume_point_(0), |  | ||||||
| 		delay_time_(0), |  | ||||||
| 		index_hole_count_target_(-1), |  | ||||||
| 		delegate_(nullptr), |  | ||||||
| 		personality_(p), | 		personality_(p), | ||||||
| 		head_is_loaded_(false) { | 		interesting_event_mask_(static_cast<int>(Event1770::Command)) { | ||||||
| 	set_is_double_density(false); | 	set_is_double_density(false); | ||||||
| 	posit_event((int)Event1770::Command); | 	posit_event(static_cast<int>(Event1770::Command)); | ||||||
| } | } | ||||||
|  |  | ||||||
| void WD1770::set_register(int address, uint8_t value) { | void WD1770::set_register(int address, uint8_t value) { | ||||||
| 	switch(address&3) { | 	switch(address&3) { | ||||||
| 		case 0: { | 		case 0: { | ||||||
| 			if((value&0xf0) == 0xd0) { | 			if((value&0xf0) == 0xd0) { | ||||||
|  | 				if(value == 0xd0) { | ||||||
|  | 					// Force interrupt **immediately**. | ||||||
|  | 					printf("Force interrupt immediately\n"); | ||||||
|  | 					posit_event(static_cast<int>(Event1770::ForceInterrupt)); | ||||||
|  | 				} else { | ||||||
| 					printf("!!!TODO: force interrupt!!!\n"); | 					printf("!!!TODO: force interrupt!!!\n"); | ||||||
| 					update_status([] (Status &status) { | 					update_status([] (Status &status) { | ||||||
| 						status.type = Status::One; | 						status.type = Status::One; | ||||||
| 					}); | 					}); | ||||||
|  | 				} | ||||||
| 			} else { | 			} else { | ||||||
| 				command_ = value; | 				command_ = value; | ||||||
| 				posit_event((int)Event1770::Command); | 				posit_event(static_cast<int>(Event1770::Command)); | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
| 		break; | 		break; | ||||||
| @@ -75,7 +63,7 @@ uint8_t WD1770::get_register(int address) { | |||||||
| 			switch(status_.type) { | 			switch(status_.type) { | ||||||
| 				case Status::One: | 				case Status::One: | ||||||
| 					status |= | 					status |= | ||||||
| 						(get_is_track_zero() ? Flag::TrackZero : 0) | | 						(get_drive().get_is_track_zero() ? Flag::TrackZero : 0) | | ||||||
| 						(status_.seek_error ? Flag::SeekError : 0); | 						(status_.seek_error ? Flag::SeekError : 0); | ||||||
| 						// TODO: index hole | 						// TODO: index hole | ||||||
| 				break; | 				break; | ||||||
| @@ -91,11 +79,11 @@ uint8_t WD1770::get_register(int address) { | |||||||
| 			} | 			} | ||||||
|  |  | ||||||
| 			if(!has_motor_on_line()) { | 			if(!has_motor_on_line()) { | ||||||
| 				status |= get_drive_is_ready() ? 0 : Flag::NotReady; | 				status |= get_drive().get_is_ready() ? 0 : Flag::NotReady; | ||||||
| 				if(status_.type == Status::One) | 				if(status_.type == Status::One) | ||||||
| 					status |= (head_is_loaded_ ? Flag::HeadLoaded : 0); | 					status |= (head_is_loaded_ ? Flag::HeadLoaded : 0); | ||||||
| 			} else { | 			} else { | ||||||
| 				status |= (get_motor_on() ? Flag::MotorOn : 0); | 				status |= (get_drive().get_motor_on() ? Flag::MotorOn : 0); | ||||||
| 				if(status_.type == Status::One) | 				if(status_.type == Status::One) | ||||||
| 					status |= (status_.spin_up ? Flag::SpinUp : 0); | 					status |= (status_.spin_up ? Flag::SpinUp : 0); | ||||||
| 			} | 			} | ||||||
| @@ -115,24 +103,24 @@ void WD1770::run_for(const Cycles cycles) { | |||||||
| 	Storage::Disk::Controller::run_for(cycles); | 	Storage::Disk::Controller::run_for(cycles); | ||||||
|  |  | ||||||
| 	if(delay_time_) { | 	if(delay_time_) { | ||||||
| 		unsigned int number_of_cycles = (unsigned int)cycles.as_int(); | 		unsigned int number_of_cycles = static_cast<unsigned int>(cycles.as_int()); | ||||||
| 		if(delay_time_ <= number_of_cycles) { | 		if(delay_time_ <= number_of_cycles) { | ||||||
| 			delay_time_ = 0; | 			delay_time_ = 0; | ||||||
| 			posit_event((int)Event1770::Timer); | 			posit_event(static_cast<int>(Event1770::Timer)); | ||||||
| 		} else { | 		} else { | ||||||
| 			delay_time_ -= number_of_cycles; | 			delay_time_ -= number_of_cycles; | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| #define WAIT_FOR_EVENT(mask)	resume_point_ = __LINE__; interesting_event_mask_ = (int)mask; return; case __LINE__: | #define WAIT_FOR_EVENT(mask)	resume_point_ = __LINE__; interesting_event_mask_ = static_cast<int>(mask); return; case __LINE__: | ||||||
| #define WAIT_FOR_TIME(ms)		resume_point_ = __LINE__; delay_time_ = ms * 8000; WAIT_FOR_EVENT(Event1770::Timer); | #define WAIT_FOR_TIME(ms)		resume_point_ = __LINE__; delay_time_ = ms * 8000; WAIT_FOR_EVENT(Event1770::Timer); | ||||||
| #define WAIT_FOR_BYTES(count)	resume_point_ = __LINE__; distance_into_section_ = 0; WAIT_FOR_EVENT(Event::Token); if(get_latest_token().type == Token::Byte) distance_into_section_++; if(distance_into_section_ < count) { interesting_event_mask_ = (int)Event::Token; return; } | #define WAIT_FOR_BYTES(count)	resume_point_ = __LINE__; distance_into_section_ = 0; WAIT_FOR_EVENT(Event::Token); if(get_latest_token().type == Token::Byte) distance_into_section_++; if(distance_into_section_ < count) { interesting_event_mask_ = static_cast<int>(Event::Token); return; } | ||||||
| #define BEGIN_SECTION()	switch(resume_point_) { default: | #define BEGIN_SECTION()	switch(resume_point_) { default: | ||||||
| #define END_SECTION()	0; } | #define END_SECTION()	(void)0; } | ||||||
|  |  | ||||||
| #define READ_ID()	\ | #define READ_ID()	\ | ||||||
| 		if(new_event_type == (int)Event::Token) {	\ | 		if(new_event_type == static_cast<int>(Event::Token)) {	\ | ||||||
| 			if(!distance_into_section_ && get_latest_token().type == Token::ID) {set_data_mode(DataMode::Reading); distance_into_section_++; }	\ | 			if(!distance_into_section_ && get_latest_token().type == Token::ID) {set_data_mode(DataMode::Reading); distance_into_section_++; }	\ | ||||||
| 			else if(distance_into_section_ && distance_into_section_ < 7 && get_latest_token().type == Token::Byte) {	\ | 			else if(distance_into_section_ && distance_into_section_ < 7 && get_latest_token().type == Token::Byte) {	\ | ||||||
| 				header_[distance_into_section_ - 1] = get_latest_token().byte_value;	\ | 				header_[distance_into_section_ - 1] = get_latest_token().byte_value;	\ | ||||||
| @@ -169,10 +157,10 @@ void WD1770::run_for(const Cycles cycles) { | |||||||
| //     +--------+----------+-------------------------+ | //     +--------+----------+-------------------------+ | ||||||
|  |  | ||||||
| void WD1770::posit_event(int new_event_type) { | void WD1770::posit_event(int new_event_type) { | ||||||
| 	if(new_event_type == (int)Event::IndexHole) { | 	if(new_event_type == static_cast<int>(Event::IndexHole)) { | ||||||
| 		index_hole_count_++; | 		index_hole_count_++; | ||||||
| 		if(index_hole_count_target_ == index_hole_count_) { | 		if(index_hole_count_target_ == index_hole_count_) { | ||||||
| 			posit_event((int)Event1770::IndexHoleTarget); | 			posit_event(static_cast<int>(Event1770::IndexHoleTarget)); | ||||||
| 			index_hole_count_target_ = -1; | 			index_hole_count_target_ = -1; | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| @@ -187,13 +175,23 @@ void WD1770::posit_event(int new_event_type) { | |||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	if(!(interesting_event_mask_ & (int)new_event_type)) return; | 	if(new_event_type == static_cast<int>(Event1770::ForceInterrupt)) { | ||||||
|  | 		interesting_event_mask_ = 0; | ||||||
|  | 		resume_point_ = 0; | ||||||
|  | 		update_status([] (Status &status) { | ||||||
|  | 			status.type = Status::One; | ||||||
|  | 			status.data_request = false; | ||||||
|  | 		}); | ||||||
|  | 	} else { | ||||||
|  | 		if(!(interesting_event_mask_ & static_cast<int>(new_event_type))) return; | ||||||
| 		interesting_event_mask_ &= ~new_event_type; | 		interesting_event_mask_ &= ~new_event_type; | ||||||
|  | 	} | ||||||
|  |  | ||||||
| 	Status new_status; | 	Status new_status; | ||||||
| 	BEGIN_SECTION() | 	BEGIN_SECTION() | ||||||
|  |  | ||||||
| 	// Wait for a new command, branch to the appropriate handler. | 	// Wait for a new command, branch to the appropriate handler. | ||||||
|  | 	case 0: | ||||||
| 	wait_for_command: | 	wait_for_command: | ||||||
| 		printf("Idle...\n"); | 		printf("Idle...\n"); | ||||||
| 		set_data_mode(DataMode::Scanning); | 		set_data_mode(DataMode::Scanning); | ||||||
| @@ -257,7 +255,7 @@ void WD1770::posit_event(int new_event_type) { | |||||||
| 		goto test_type1_type; | 		goto test_type1_type; | ||||||
|  |  | ||||||
| 	begin_type1_spin_up: | 	begin_type1_spin_up: | ||||||
| 		if((command_&0x08) || get_motor_on()) goto test_type1_type; | 		if((command_&0x08) || get_drive().get_motor_on()) goto test_type1_type; | ||||||
| 		SPIN_UP(); | 		SPIN_UP(); | ||||||
|  |  | ||||||
| 	test_type1_type: | 	test_type1_type: | ||||||
| @@ -280,11 +278,11 @@ void WD1770::posit_event(int new_event_type) { | |||||||
| 		if(step_direction_) track_++; else track_--; | 		if(step_direction_) track_++; else track_--; | ||||||
|  |  | ||||||
| 	perform_step: | 	perform_step: | ||||||
| 		if(!step_direction_ && get_is_track_zero()) { | 		if(!step_direction_ && get_drive().get_is_track_zero()) { | ||||||
| 			track_ = 0; | 			track_ = 0; | ||||||
| 			goto verify; | 			goto verify; | ||||||
| 		} | 		} | ||||||
| 		step(step_direction_ ? 1 : -1); | 		get_drive().step(step_direction_ ? 1 : -1); | ||||||
| 		unsigned int time_to_wait; | 		unsigned int time_to_wait; | ||||||
| 		switch(command_ & 3) { | 		switch(command_ & 3) { | ||||||
| 			default: | 			default: | ||||||
| @@ -310,7 +308,7 @@ void WD1770::posit_event(int new_event_type) { | |||||||
| 		distance_into_section_ = 0; | 		distance_into_section_ = 0; | ||||||
|  |  | ||||||
| 	verify_read_data: | 	verify_read_data: | ||||||
| 		WAIT_FOR_EVENT((int)Event::IndexHole | (int)Event::Token); | 		WAIT_FOR_EVENT(static_cast<int>(Event::IndexHole) | static_cast<int>(Event::Token)); | ||||||
| 		READ_ID(); | 		READ_ID(); | ||||||
|  |  | ||||||
| 		if(index_hole_count_ == 6) { | 		if(index_hole_count_ == 6) { | ||||||
| @@ -376,7 +374,7 @@ void WD1770::posit_event(int new_event_type) { | |||||||
| 		goto test_type2_delay; | 		goto test_type2_delay; | ||||||
|  |  | ||||||
| 	begin_type2_spin_up: | 	begin_type2_spin_up: | ||||||
| 		if(get_motor_on()) goto test_type2_delay; | 		if(get_drive().get_motor_on()) goto test_type2_delay; | ||||||
| 		// Perform spin up. | 		// Perform spin up. | ||||||
| 		SPIN_UP(); | 		SPIN_UP(); | ||||||
|  |  | ||||||
| @@ -386,7 +384,7 @@ void WD1770::posit_event(int new_event_type) { | |||||||
| 		WAIT_FOR_TIME(30); | 		WAIT_FOR_TIME(30); | ||||||
|  |  | ||||||
| 	test_type2_write_protection: | 	test_type2_write_protection: | ||||||
| 		if(command_&0x20 && get_drive_is_read_only()) { | 		if(command_&0x20 && get_drive().get_is_read_only()) { | ||||||
| 			update_status([] (Status &status) { | 			update_status([] (Status &status) { | ||||||
| 				status.write_protect = true; | 				status.write_protect = true; | ||||||
| 			}); | 			}); | ||||||
| @@ -394,7 +392,7 @@ void WD1770::posit_event(int new_event_type) { | |||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 	type2_get_header: | 	type2_get_header: | ||||||
| 		WAIT_FOR_EVENT((int)Event::IndexHole | (int)Event::Token); | 		WAIT_FOR_EVENT(static_cast<int>(Event::IndexHole) | static_cast<int>(Event::Token)); | ||||||
| 		READ_ID(); | 		READ_ID(); | ||||||
|  |  | ||||||
| 		if(index_hole_count_ == 5) { | 		if(index_hole_count_ == 5) { | ||||||
| @@ -478,7 +476,7 @@ void WD1770::posit_event(int new_event_type) { | |||||||
| 				sector_++; | 				sector_++; | ||||||
| 				goto test_type2_write_protection; | 				goto test_type2_write_protection; | ||||||
| 			} | 			} | ||||||
| 			printf("Read sector %d\n", sector_); | 			printf("Finished reading sector %d\n", sector_); | ||||||
| 			goto wait_for_command; | 			goto wait_for_command; | ||||||
| 		} | 		} | ||||||
| 		goto type2_check_crc; | 		goto type2_check_crc; | ||||||
| @@ -594,7 +592,7 @@ void WD1770::posit_event(int new_event_type) { | |||||||
| 		goto type3_test_delay; | 		goto type3_test_delay; | ||||||
|  |  | ||||||
| 	begin_type3_spin_up: | 	begin_type3_spin_up: | ||||||
| 		if((command_&0x08) || get_motor_on()) goto type3_test_delay; | 		if((command_&0x08) || get_drive().get_motor_on()) goto type3_test_delay; | ||||||
| 		SPIN_UP(); | 		SPIN_UP(); | ||||||
|  |  | ||||||
| 	type3_test_delay: | 	type3_test_delay: | ||||||
| @@ -611,8 +609,8 @@ void WD1770::posit_event(int new_event_type) { | |||||||
| 		distance_into_section_ = 0; | 		distance_into_section_ = 0; | ||||||
|  |  | ||||||
| 	read_address_get_header: | 	read_address_get_header: | ||||||
| 		WAIT_FOR_EVENT((int)Event::IndexHole | (int)Event::Token); | 		WAIT_FOR_EVENT(static_cast<int>(Event::IndexHole) | static_cast<int>(Event::Token)); | ||||||
| 		if(new_event_type == (int)Event::Token) { | 		if(new_event_type == static_cast<int>(Event::Token)) { | ||||||
| 			if(!distance_into_section_ && get_latest_token().type == Token::ID) {set_data_mode(DataMode::Reading); distance_into_section_++; } | 			if(!distance_into_section_ && get_latest_token().type == Token::ID) {set_data_mode(DataMode::Reading); distance_into_section_++; } | ||||||
| 			else if(distance_into_section_ && distance_into_section_ < 7 && get_latest_token().type == Token::Byte) { | 			else if(distance_into_section_ && distance_into_section_ < 7 && get_latest_token().type == Token::Byte) { | ||||||
| 				if(status_.data_request) { | 				if(status_.data_request) { | ||||||
| @@ -652,7 +650,7 @@ void WD1770::posit_event(int new_event_type) { | |||||||
| 		index_hole_count_ = 0; | 		index_hole_count_ = 0; | ||||||
|  |  | ||||||
| 	read_track_read_byte: | 	read_track_read_byte: | ||||||
| 		WAIT_FOR_EVENT((int)Event::Token | (int)Event::IndexHole); | 		WAIT_FOR_EVENT(static_cast<int>(Event::Token) | static_cast<int>(Event::IndexHole)); | ||||||
| 		if(index_hole_count_) { | 		if(index_hole_count_) { | ||||||
| 			goto wait_for_command; | 			goto wait_for_command; | ||||||
| 		} | 		} | ||||||
| @@ -674,8 +672,7 @@ void WD1770::posit_event(int new_event_type) { | |||||||
| 			status.lost_data = false; | 			status.lost_data = false; | ||||||
| 		}); | 		}); | ||||||
|  |  | ||||||
| 	write_track_test_write_protect: | 		if(get_drive().get_is_read_only()) { | ||||||
| 		if(get_drive_is_read_only()) { |  | ||||||
| 			update_status([] (Status &status) { | 			update_status([] (Status &status) { | ||||||
| 				status.write_protect = true; | 				status.write_protect = true; | ||||||
| 			}); | 			}); | ||||||
| @@ -720,7 +717,7 @@ void WD1770::posit_event(int new_event_type) { | |||||||
| 				case 0xfd: case 0xfe: | 				case 0xfd: case 0xfe: | ||||||
| 					// clock is 0xc7 = 1010 0000 0010 1010 = 0xa022 | 					// clock is 0xc7 = 1010 0000 0010 1010 = 0xa022 | ||||||
| 					write_raw_short( | 					write_raw_short( | ||||||
| 						(uint16_t)( | 						static_cast<uint16_t>( | ||||||
| 							0xa022 | | 							0xa022 | | ||||||
| 							((data_ & 0x80) << 7) | | 							((data_ & 0x80) << 7) | | ||||||
| 							((data_ & 0x40) << 6) | | 							((data_ & 0x40) << 6) | | ||||||
| @@ -781,8 +778,9 @@ void WD1770::update_status(std::function<void(Status &)> updater) { | |||||||
| } | } | ||||||
|  |  | ||||||
| void WD1770::set_head_load_request(bool head_load) {} | void WD1770::set_head_load_request(bool head_load) {} | ||||||
|  | void WD1770::set_motor_on(bool motor_on) {} | ||||||
|  |  | ||||||
| void WD1770::set_head_loaded(bool head_loaded) { | void WD1770::set_head_loaded(bool head_loaded) { | ||||||
| 	head_is_loaded_ = head_loaded; | 	head_is_loaded_ = head_loaded; | ||||||
| 	if(head_loaded) posit_event((int)Event1770::HeadLoad); | 	if(head_loaded) posit_event(static_cast<int>(Event1770::HeadLoad)); | ||||||
| } | } | ||||||
|   | |||||||
| @@ -9,7 +9,7 @@ | |||||||
| #ifndef _770_hpp | #ifndef _770_hpp | ||||||
| #define _770_hpp | #define _770_hpp | ||||||
|  |  | ||||||
| #include "../../Storage/Disk/MFMDiskController.hpp" | #include "../../Storage/Disk/Controller/MFMDiskController.hpp" | ||||||
|  |  | ||||||
| namespace WD { | namespace WD { | ||||||
|  |  | ||||||
| @@ -76,6 +76,7 @@ class WD1770: public Storage::Disk::MFMController { | |||||||
|  |  | ||||||
| 	protected: | 	protected: | ||||||
| 		virtual void set_head_load_request(bool head_load); | 		virtual void set_head_load_request(bool head_load); | ||||||
|  | 		virtual void set_motor_on(bool motor_on); | ||||||
| 		void set_head_loaded(bool head_loaded); | 		void set_head_loaded(bool head_loaded); | ||||||
|  |  | ||||||
| 	private: | 	private: | ||||||
| @@ -84,20 +85,19 @@ class WD1770: public Storage::Disk::MFMController { | |||||||
| 		inline bool has_head_load_line() { return (personality_ == P1793 ); } | 		inline bool has_head_load_line() { return (personality_ == P1793 ); } | ||||||
|  |  | ||||||
| 		struct Status { | 		struct Status { | ||||||
| 			Status(); | 			bool write_protect = false; | ||||||
| 			bool write_protect; | 			bool record_type = false; | ||||||
| 			bool record_type; | 			bool spin_up = false; | ||||||
| 			bool spin_up; | 			bool record_not_found = false; | ||||||
| 			bool record_not_found; | 			bool crc_error = false; | ||||||
| 			bool crc_error; | 			bool seek_error = false; | ||||||
| 			bool seek_error; | 			bool lost_data = false; | ||||||
| 			bool lost_data; | 			bool data_request = false; | ||||||
| 			bool data_request; | 			bool interrupt_request = false; | ||||||
| 			bool interrupt_request; | 			bool busy = false; | ||||||
| 			bool busy; |  | ||||||
| 			enum { | 			enum { | ||||||
| 				One, Two, Three | 				One, Two, Three | ||||||
| 			} type; | 			} type = One; | ||||||
| 		} status_; | 		} status_; | ||||||
| 		uint8_t track_; | 		uint8_t track_; | ||||||
| 		uint8_t sector_; | 		uint8_t sector_; | ||||||
| @@ -105,7 +105,7 @@ class WD1770: public Storage::Disk::MFMController { | |||||||
| 		uint8_t command_; | 		uint8_t command_; | ||||||
|  |  | ||||||
| 		int index_hole_count_; | 		int index_hole_count_; | ||||||
| 		int index_hole_count_target_; | 		int index_hole_count_target_ = -1; | ||||||
| 		int distance_into_section_; | 		int distance_into_section_; | ||||||
|  |  | ||||||
| 		int step_direction_; | 		int step_direction_; | ||||||
| @@ -116,21 +116,22 @@ class WD1770: public Storage::Disk::MFMController { | |||||||
| 			Command			= (1 << 3),	// Indicates receipt of a new command. | 			Command			= (1 << 3),	// Indicates receipt of a new command. | ||||||
| 			HeadLoad		= (1 << 4),	// Indicates the head has been loaded (1973 only). | 			HeadLoad		= (1 << 4),	// Indicates the head has been loaded (1973 only). | ||||||
| 			Timer			= (1 << 5),	// Indicates that the delay_time_-powered timer has timed out. | 			Timer			= (1 << 5),	// Indicates that the delay_time_-powered timer has timed out. | ||||||
| 			IndexHoleTarget	= (1 << 6)	// Indicates that index_hole_count_ has reached index_hole_count_target_. | 			IndexHoleTarget	= (1 << 6),	// Indicates that index_hole_count_ has reached index_hole_count_target_. | ||||||
|  | 			ForceInterrupt	= (1 << 7)	// Indicates a forced interrupt. | ||||||
| 		}; | 		}; | ||||||
| 		void posit_event(int type); | 		void posit_event(int type); | ||||||
| 		int interesting_event_mask_; | 		int interesting_event_mask_; | ||||||
| 		int resume_point_; | 		int resume_point_ = 0; | ||||||
| 		unsigned int delay_time_; | 		unsigned int delay_time_ = 0; | ||||||
|  |  | ||||||
| 		// ID buffer | 		// ID buffer | ||||||
| 		uint8_t header_[6]; | 		uint8_t header_[6]; | ||||||
|  |  | ||||||
| 		// 1793 head-loading logic | 		// 1793 head-loading logic | ||||||
| 		bool head_is_loaded_; | 		bool head_is_loaded_ = false; | ||||||
|  |  | ||||||
| 		// delegate | 		// delegate | ||||||
| 		Delegate *delegate_; | 		Delegate *delegate_ = nullptr; | ||||||
| }; | }; | ||||||
|  |  | ||||||
| } | } | ||||||
|   | |||||||
| @@ -13,9 +13,83 @@ | |||||||
| #include <typeinfo> | #include <typeinfo> | ||||||
| #include <cstdio> | #include <cstdio> | ||||||
|  |  | ||||||
|  | #include "Implementation/6522Storage.hpp" | ||||||
|  |  | ||||||
| #include "../../ClockReceiver/ClockReceiver.hpp" | #include "../../ClockReceiver/ClockReceiver.hpp" | ||||||
|  |  | ||||||
| namespace MOS { | namespace MOS { | ||||||
|  | namespace MOS6522 { | ||||||
|  |  | ||||||
|  | enum Port { | ||||||
|  | 	A = 0, | ||||||
|  | 	B = 1 | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | enum Line { | ||||||
|  | 	One = 0, | ||||||
|  | 	Two = 1 | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | /*! | ||||||
|  | 	Provides the mechanism for just-int-time communication from a 6522; the normal use case is to compose a | ||||||
|  | 	6522 and a subclass of PortHandler in order to reproduce a 6522 and its original bus wiring. | ||||||
|  | */ | ||||||
|  | class PortHandler { | ||||||
|  | 	public: | ||||||
|  | 		/// Requests the current input value of @c port from the port handler. | ||||||
|  | 		uint8_t get_port_input(Port port)										{	return 0xff;	} | ||||||
|  |  | ||||||
|  | 		/// Sets the current output value of @c port and provides @c direction_mask, indicating which pins are marked as output. | ||||||
|  | 		void set_port_output(Port port, uint8_t value, uint8_t direction_mask)	{} | ||||||
|  |  | ||||||
|  | 		/// Sets the current logical output level for line @c line on port @c port. | ||||||
|  | 		void set_control_line_output(Port port, Line line, bool value)			{} | ||||||
|  |  | ||||||
|  | 		/// Sets the current logical value of the interrupt line. | ||||||
|  | 		void set_interrupt_status(bool status)									{} | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | /*! | ||||||
|  | 	Provided as an optional alternative base to @c PortHandler for port handlers; via the delegate pattern adds | ||||||
|  | 	a virtual level of indirection for receiving changes to the interrupt line. | ||||||
|  | */ | ||||||
|  | class IRQDelegatePortHandler: public PortHandler { | ||||||
|  | 	public: | ||||||
|  | 		class Delegate { | ||||||
|  | 			public: | ||||||
|  | 				/// Indicates that the interrupt status has changed for the IRQDelegatePortHandler provided. | ||||||
|  | 				virtual void mos6522_did_change_interrupt_status(void *irq_delegate) = 0; | ||||||
|  | 		}; | ||||||
|  |  | ||||||
|  | 		/// Sets the delegate that will receive notification of changes in the interrupt line. | ||||||
|  | 		void set_interrupt_delegate(Delegate *delegate); | ||||||
|  |  | ||||||
|  | 		/// Overrides PortHandler::set_interrupt_status, notifying the delegate if one is set. | ||||||
|  | 		void set_interrupt_status(bool new_status); | ||||||
|  |  | ||||||
|  | 	private: | ||||||
|  | 		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'). | ||||||
| @@ -28,353 +102,27 @@ namespace MOS { | |||||||
| 	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 { | template <class T> class MOS6522: public MOS6522Base { | ||||||
| 	private: |  | ||||||
| 		enum InterruptFlag: uint8_t { |  | ||||||
| 			CA2ActiveEdge	= 1 << 0, |  | ||||||
| 			CA1ActiveEdge	= 1 << 1, |  | ||||||
| 			ShiftRegister	= 1 << 2, |  | ||||||
| 			CB2ActiveEdge	= 1 << 3, |  | ||||||
| 			CB1ActiveEdge	= 1 << 4, |  | ||||||
| 			Timer2			= 1 << 5, |  | ||||||
| 			Timer1			= 1 << 6, |  | ||||||
| 		}; |  | ||||||
|  |  | ||||||
| 	public: | 	public: | ||||||
| 		enum Port { | 		MOS6522(T &bus_handler) noexcept : bus_handler_(bus_handler) {} | ||||||
| 			A = 0, | 		MOS6522(const MOS6522 &) = delete; | ||||||
| 			B = 1 |  | ||||||
| 		}; |  | ||||||
|  |  | ||||||
| 		enum Line { |  | ||||||
| 			One = 0, |  | ||||||
| 			Two = 1 |  | ||||||
| 		}; |  | ||||||
|  |  | ||||||
| 		/*! Sets a register value. */ | 		/*! Sets a register value. */ | ||||||
| 		inline void set_register(int address, uint8_t value) { | 		void set_register(int address, uint8_t value); | ||||||
| 			address &= 0xf; |  | ||||||
| //			printf("6522 [%s]: %0x <- %02x\n", typeid(*this).name(), address, value); |  | ||||||
| 			switch(address) { |  | ||||||
| 				case 0x0: |  | ||||||
| 					registers_.output[1] = value; |  | ||||||
| 					static_cast<T *>(this)->set_port_output(Port::B, value, registers_.data_direction[1]);	// TODO: handshake |  | ||||||
|  |  | ||||||
| 					registers_.interrupt_flags &= ~(InterruptFlag::CB1ActiveEdge | ((registers_.peripheral_control&0x20) ? 0 : InterruptFlag::CB2ActiveEdge)); |  | ||||||
| 					reevaluate_interrupts(); |  | ||||||
| 				break; |  | ||||||
| 				case 0xf: |  | ||||||
| 				case 0x1: |  | ||||||
| 					registers_.output[0] = value; |  | ||||||
| 					static_cast<T *>(this)->set_port_output(Port::A, value, registers_.data_direction[0]);	// TODO: handshake |  | ||||||
|  |  | ||||||
| 					registers_.interrupt_flags &= ~(InterruptFlag::CA1ActiveEdge | ((registers_.peripheral_control&0x02) ? 0 : InterruptFlag::CB2ActiveEdge)); |  | ||||||
| 					reevaluate_interrupts(); |  | ||||||
| 				break; |  | ||||||
| //					// No handshake, so write directly |  | ||||||
| //					registers_.output[0] = value; |  | ||||||
| //					static_cast<T *>(this)->set_port_output(0, value); |  | ||||||
| //				break; |  | ||||||
|  |  | ||||||
| 				case 0x2: |  | ||||||
| 					registers_.data_direction[1] = value; |  | ||||||
| 				break; |  | ||||||
| 				case 0x3: |  | ||||||
| 					registers_.data_direction[0] = value; |  | ||||||
| 				break; |  | ||||||
|  |  | ||||||
| 				// Timer 1 |  | ||||||
| 				case 0x6:	case 0x4:	registers_.timer_latch[0] = (registers_.timer_latch[0]&0xff00) | value;	break; |  | ||||||
| 				case 0x5:	case 0x7: |  | ||||||
| 					registers_.timer_latch[0] = (registers_.timer_latch[0]&0x00ff) | (uint16_t)(value << 8); |  | ||||||
| 					registers_.interrupt_flags &= ~InterruptFlag::Timer1; |  | ||||||
| 					if(address == 0x05) { |  | ||||||
| 						registers_.next_timer[0] = registers_.timer_latch[0]; |  | ||||||
| 						timer_is_running_[0] = true; |  | ||||||
| 					} |  | ||||||
| 					reevaluate_interrupts(); |  | ||||||
| 				break; |  | ||||||
|  |  | ||||||
| 				// Timer 2 |  | ||||||
| 				case 0x8:	registers_.timer_latch[1] = value;	break; |  | ||||||
| 				case 0x9: |  | ||||||
| 					registers_.interrupt_flags &= ~InterruptFlag::Timer2; |  | ||||||
| 					registers_.next_timer[1] = registers_.timer_latch[1] | (uint16_t)(value << 8); |  | ||||||
| 					timer_is_running_[1] = true; |  | ||||||
| 					reevaluate_interrupts(); |  | ||||||
| 				break; |  | ||||||
|  |  | ||||||
| 				// Shift |  | ||||||
| 				case 0xa:	registers_.shift = value;				break; |  | ||||||
|  |  | ||||||
| 				// Control |  | ||||||
| 				case 0xb: |  | ||||||
| 					registers_.auxiliary_control = value; |  | ||||||
| 				break; |  | ||||||
| 				case 0xc: |  | ||||||
| //					printf("Peripheral control %02x\n", value); |  | ||||||
| 					registers_.peripheral_control = value; |  | ||||||
|  |  | ||||||
| 					// TODO: simplify below; trying to avoid improper logging of unimplemented warnings in input mode |  | ||||||
| 					if(value & 0x08) { |  | ||||||
| 						switch(value & 0x0e) { |  | ||||||
| 							default: printf("Unimplemented control line mode %d\n", (value >> 1)&7); break; |  | ||||||
| 							case 0x0c:	static_cast<T *>(this)->set_control_line_output(Port::A, Line::Two, false);		break; |  | ||||||
| 							case 0x0e:	static_cast<T *>(this)->set_control_line_output(Port::A, Line::Two, true);		break; |  | ||||||
| 						} |  | ||||||
| 					} |  | ||||||
| 					if(value & 0x80) { |  | ||||||
| 						switch(value & 0xe0) { |  | ||||||
| 							default: printf("Unimplemented control line mode %d\n", (value >> 5)&7); break; |  | ||||||
| 							case 0xc0:	static_cast<T *>(this)->set_control_line_output(Port::B, Line::Two, false);		break; |  | ||||||
| 							case 0xe0:	static_cast<T *>(this)->set_control_line_output(Port::B, Line::Two, true);		break; |  | ||||||
| 						} |  | ||||||
| 					} |  | ||||||
| 				break; |  | ||||||
|  |  | ||||||
| 				// Interrupt control |  | ||||||
| 				case 0xd: |  | ||||||
| 					registers_.interrupt_flags &= ~value; |  | ||||||
| 					reevaluate_interrupts(); |  | ||||||
| 				break; |  | ||||||
| 				case 0xe: |  | ||||||
| 					if(value&0x80) |  | ||||||
| 						registers_.interrupt_enable |= value; |  | ||||||
| 					else |  | ||||||
| 						registers_.interrupt_enable &= ~value; |  | ||||||
| 					reevaluate_interrupts(); |  | ||||||
| 				break; |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		/*! Gets a register value. */ | 		/*! Gets a register value. */ | ||||||
| 		inline uint8_t get_register(int address) { | 		uint8_t get_register(int address); | ||||||
| 			address &= 0xf; |  | ||||||
| //			printf("6522 %p: %d\n", this, address); |  | ||||||
| 			switch(address) { |  | ||||||
| 				case 0x0: |  | ||||||
| 					registers_.interrupt_flags &= ~(InterruptFlag::CB1ActiveEdge | InterruptFlag::CB2ActiveEdge); |  | ||||||
| 					reevaluate_interrupts(); |  | ||||||
| 				return get_port_input(Port::B, registers_.data_direction[1], registers_.output[1]); |  | ||||||
| 				case 0xf:	// TODO: handshake, latching |  | ||||||
| 				case 0x1: |  | ||||||
| 					registers_.interrupt_flags &= ~(InterruptFlag::CA1ActiveEdge | InterruptFlag::CA2ActiveEdge); |  | ||||||
| 					reevaluate_interrupts(); |  | ||||||
| 				return get_port_input(Port::A, registers_.data_direction[0], registers_.output[0]); |  | ||||||
|  |  | ||||||
| 				case 0x2:	return registers_.data_direction[1]; |  | ||||||
| 				case 0x3:	return registers_.data_direction[0]; |  | ||||||
|  |  | ||||||
| 				// Timer 1 |  | ||||||
| 				case 0x4: |  | ||||||
| 					registers_.interrupt_flags &= ~InterruptFlag::Timer1; |  | ||||||
| 					reevaluate_interrupts(); |  | ||||||
| 				return registers_.timer[0] & 0x00ff; |  | ||||||
| 				case 0x5:	return registers_.timer[0] >> 8; |  | ||||||
| 				case 0x6:	return registers_.timer_latch[0] & 0x00ff; |  | ||||||
| 				case 0x7:	return registers_.timer_latch[0] >> 8; |  | ||||||
|  |  | ||||||
| 				// Timer 2 |  | ||||||
| 				case 0x8: |  | ||||||
| 					registers_.interrupt_flags &= ~InterruptFlag::Timer2; |  | ||||||
| 					reevaluate_interrupts(); |  | ||||||
| 				return registers_.timer[1] & 0x00ff; |  | ||||||
| 				case 0x9:	return registers_.timer[1] >> 8; |  | ||||||
|  |  | ||||||
| 				case 0xa:	return registers_.shift; |  | ||||||
|  |  | ||||||
| 				case 0xb:	return registers_.auxiliary_control; |  | ||||||
| 				case 0xc:	return registers_.peripheral_control; |  | ||||||
|  |  | ||||||
| 				case 0xd:	return registers_.interrupt_flags | (get_interrupt_line() ? 0x80 : 0x00); |  | ||||||
| 				case 0xe:	return registers_.interrupt_enable | 0x80; |  | ||||||
| 			} |  | ||||||
|  |  | ||||||
| 			return 0xff; |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		inline void 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; |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| #define 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] = (uint16_t)registers_.next_timer[0]; registers_.next_timer[0] = -1; }\ |  | ||||||
| 	if(registers_.next_timer[1] >= 0) { registers_.timer[1] = (uint16_t)registers_.next_timer[1]; registers_.next_timer[1] = -1; }\ |  | ||||||
|  |  | ||||||
| 	// IRQ is raised on the half cycle after overflow |  | ||||||
| #define phase1()	\ |  | ||||||
| 	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. */ |  | ||||||
| 		inline void run_for(const HalfCycles half_cycles) { |  | ||||||
| 			int number_of_half_cycles = half_cycles.as_int(); |  | ||||||
|  |  | ||||||
| 			if(is_phase2_) { |  | ||||||
| 				phase2(); |  | ||||||
| 				number_of_half_cycles--; |  | ||||||
| 			} |  | ||||||
|  |  | ||||||
| 			while(number_of_half_cycles >= 2) { |  | ||||||
| 				phase1(); |  | ||||||
| 				phase2(); |  | ||||||
| 				number_of_half_cycles -= 2; |  | ||||||
| 			} |  | ||||||
|  |  | ||||||
| 			if(number_of_half_cycles) { |  | ||||||
| 				phase1(); |  | ||||||
| 				is_phase2_ = true; |  | ||||||
| 			} else { |  | ||||||
| 				is_phase2_ = false; |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		/*! Runs for a specified number of cycles. */ |  | ||||||
| 		inline void run_for(const Cycles cycles) { |  | ||||||
| 			int number_of_cycles = cycles.as_int(); |  | ||||||
| 			while(number_of_cycles--) { |  | ||||||
| 				phase1(); |  | ||||||
| 				phase2(); |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| #undef phase1 |  | ||||||
| #undef phase2 |  | ||||||
|  |  | ||||||
| 		/*! @returns @c true if the IRQ line is currently active; @c false otherwise. */ |  | ||||||
| 		inline bool get_interrupt_line() { |  | ||||||
| 			uint8_t interrupt_status = registers_.interrupt_flags & registers_.interrupt_enable & 0x7f; |  | ||||||
| 			return !!interrupt_status; |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		MOS6522() : |  | ||||||
| 			timer_is_running_{false, false}, |  | ||||||
| 			last_posted_interrupt_status_(false), |  | ||||||
| 			is_phase2_(false) {} |  | ||||||
|  |  | ||||||
| 	private: | 	private: | ||||||
| 		// Expected to be overridden | 		T &bus_handler_; | ||||||
| 		uint8_t get_port_input(Port port)										{	return 0xff;	} |  | ||||||
| 		void set_port_output(Port port, uint8_t value, uint8_t direction_mask)	{} |  | ||||||
| 		void set_control_line_output(Port port, Line line, bool value)			{} |  | ||||||
| 		void set_interrupt_status(bool status)									{} |  | ||||||
|  |  | ||||||
| 		// Input/output multiplexer | 		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(); | ||||||
| 			uint8_t input = static_cast<T *>(this)->get_port_input(port); |  | ||||||
| 			return (input & ~output_mask) | (output & output_mask); |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		// Phase toggle |  | ||||||
| 		bool is_phase2_; |  | ||||||
|  |  | ||||||
| 		// Delegate and communications |  | ||||||
| 		bool last_posted_interrupt_status_; |  | ||||||
| 		inline void reevaluate_interrupts() { |  | ||||||
| 			bool new_interrupt_status = get_interrupt_line(); |  | ||||||
| 			if(new_interrupt_status != last_posted_interrupt_status_) { |  | ||||||
| 				last_posted_interrupt_status_ = new_interrupt_status; |  | ||||||
| 				static_cast<T *>(this)->set_interrupt_status(new_interrupt_status); |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		// The registers |  | ||||||
| 		struct Registers { |  | ||||||
| 			uint8_t output[2], input[2], data_direction[2]; |  | ||||||
| 			uint16_t timer[2], timer_latch[2], last_timer[2]; |  | ||||||
| 			int next_timer[2]; |  | ||||||
| 			uint8_t shift; |  | ||||||
| 			uint8_t auxiliary_control, peripheral_control; |  | ||||||
| 			uint8_t interrupt_flags, interrupt_enable; |  | ||||||
| 			bool timer_needs_reload; |  | ||||||
|  |  | ||||||
| 			// "A  low  reset  (RES)  input  clears  all  R6522  internal registers to logic 0" |  | ||||||
| 			Registers() : |  | ||||||
| 				output{0, 0}, input{0, 0}, data_direction{0, 0}, |  | ||||||
| 				auxiliary_control(0), peripheral_control(0), |  | ||||||
| 				interrupt_flags(0), interrupt_enable(0), |  | ||||||
| 				last_timer{0, 0}, timer_needs_reload(false), |  | ||||||
| 				next_timer{-1, -1} {} |  | ||||||
| 		} registers_; |  | ||||||
|  |  | ||||||
| 		// control state |  | ||||||
| 		struct { |  | ||||||
| 			bool line_one, line_two; |  | ||||||
| 		} control_inputs_[2]; |  | ||||||
|  |  | ||||||
| 		// Internal state other than the registers |  | ||||||
| 		bool timer_is_running_[2]; |  | ||||||
| }; | }; | ||||||
|  |  | ||||||
| /*! | #include "Implementation/6522Implementation.hpp" | ||||||
| 	Provided for optional composition with @c MOS6522, @c MOS6522IRQDelegate provides for a delegate |  | ||||||
| 	that will receive IRQ line change notifications. |  | ||||||
| */ |  | ||||||
| class MOS6522IRQDelegate { |  | ||||||
| 	public: |  | ||||||
| 		class Delegate { |  | ||||||
| 			public: |  | ||||||
| 				virtual void mos6522_did_change_interrupt_status(void *mos6522) = 0; |  | ||||||
| 		}; |  | ||||||
|  |  | ||||||
| 		inline void set_interrupt_delegate(Delegate *delegate) { |  | ||||||
| 			delegate_ = delegate; |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		inline void set_interrupt_status(bool new_status) { |  | ||||||
| 			if(delegate_) delegate_->mos6522_did_change_interrupt_status(this); |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 	private: |  | ||||||
| 		Delegate *delegate_; |  | ||||||
| }; |  | ||||||
|  |  | ||||||
|  | } | ||||||
| } | } | ||||||
|  |  | ||||||
| #endif /* _522_hpp */ | #endif /* _522_hpp */ | ||||||
|   | |||||||
							
								
								
									
										116
									
								
								Components/6522/Implementation/6522Base.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										116
									
								
								Components/6522/Implementation/6522Base.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,116 @@ | |||||||
|  | // | ||||||
|  | //  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; | ||||||
|  | } | ||||||
							
								
								
									
										155
									
								
								Components/6522/Implementation/6522Implementation.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										155
									
								
								Components/6522/Implementation/6522Implementation.hpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,155 @@ | |||||||
|  | // | ||||||
|  | //  Implementation.hpp | ||||||
|  | //  Clock Signal | ||||||
|  | // | ||||||
|  | //  Created by Thomas Harte on 04/09/2017. | ||||||
|  | //  Copyright © 2017 Thomas Harte. All rights reserved. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | template <typename T> void MOS6522<T>::set_register(int address, uint8_t value) { | ||||||
|  | 	address &= 0xf; | ||||||
|  | 	switch(address) { | ||||||
|  | 		case 0x0: | ||||||
|  | 			registers_.output[1] = value; | ||||||
|  | 			bus_handler_.set_port_output(Port::B, value, registers_.data_direction[1]);	// TODO: handshake | ||||||
|  |  | ||||||
|  | 			registers_.interrupt_flags &= ~(InterruptFlag::CB1ActiveEdge | ((registers_.peripheral_control&0x20) ? 0 : InterruptFlag::CB2ActiveEdge)); | ||||||
|  | 			reevaluate_interrupts(); | ||||||
|  | 		break; | ||||||
|  | 		case 0xf: | ||||||
|  | 		case 0x1: | ||||||
|  | 			registers_.output[0] = value; | ||||||
|  | 			bus_handler_.set_port_output(Port::A, value, registers_.data_direction[0]);	// TODO: handshake | ||||||
|  |  | ||||||
|  | 			registers_.interrupt_flags &= ~(InterruptFlag::CA1ActiveEdge | ((registers_.peripheral_control&0x02) ? 0 : InterruptFlag::CB2ActiveEdge)); | ||||||
|  | 			reevaluate_interrupts(); | ||||||
|  | 		break; | ||||||
|  |  | ||||||
|  | 		case 0x2: | ||||||
|  | 			registers_.data_direction[1] = value; | ||||||
|  | 		break; | ||||||
|  | 		case 0x3: | ||||||
|  | 			registers_.data_direction[0] = value; | ||||||
|  | 		break; | ||||||
|  |  | ||||||
|  | 		// Timer 1 | ||||||
|  | 		case 0x6:	case 0x4:	registers_.timer_latch[0] = (registers_.timer_latch[0]&0xff00) | value;	break; | ||||||
|  | 		case 0x5:	case 0x7: | ||||||
|  | 			registers_.timer_latch[0] = (registers_.timer_latch[0]&0x00ff) | static_cast<uint16_t>(value << 8); | ||||||
|  | 			registers_.interrupt_flags &= ~InterruptFlag::Timer1; | ||||||
|  | 			if(address == 0x05) { | ||||||
|  | 				registers_.next_timer[0] = registers_.timer_latch[0]; | ||||||
|  | 				timer_is_running_[0] = true; | ||||||
|  | 			} | ||||||
|  | 			reevaluate_interrupts(); | ||||||
|  | 		break; | ||||||
|  |  | ||||||
|  | 		// Timer 2 | ||||||
|  | 		case 0x8:	registers_.timer_latch[1] = value;	break; | ||||||
|  | 		case 0x9: | ||||||
|  | 			registers_.interrupt_flags &= ~InterruptFlag::Timer2; | ||||||
|  | 			registers_.next_timer[1] = registers_.timer_latch[1] | static_cast<uint16_t>(value << 8); | ||||||
|  | 			timer_is_running_[1] = true; | ||||||
|  | 			reevaluate_interrupts(); | ||||||
|  | 		break; | ||||||
|  |  | ||||||
|  | 		// Shift | ||||||
|  | 		case 0xa:	registers_.shift = value;				break; | ||||||
|  |  | ||||||
|  | 		// Control | ||||||
|  | 		case 0xb: | ||||||
|  | 			registers_.auxiliary_control = value; | ||||||
|  | 		break; | ||||||
|  | 		case 0xc: | ||||||
|  | //			printf("Peripheral control %02x\n", value); | ||||||
|  | 			registers_.peripheral_control = value; | ||||||
|  |  | ||||||
|  | 			// TODO: simplify below; trying to avoid improper logging of unimplemented warnings in input mode | ||||||
|  | 			if(value & 0x08) { | ||||||
|  | 				switch(value & 0x0e) { | ||||||
|  | 					default: printf("Unimplemented control line mode %d\n", (value >> 1)&7);		break; | ||||||
|  | 					case 0x0c:	bus_handler_.set_control_line_output(Port::A, Line::Two, false);	break; | ||||||
|  | 					case 0x0e:	bus_handler_.set_control_line_output(Port::A, Line::Two, true);		break; | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 			if(value & 0x80) { | ||||||
|  | 				switch(value & 0xe0) { | ||||||
|  | 					default: printf("Unimplemented control line mode %d\n", (value >> 5)&7);		break; | ||||||
|  | 					case 0xc0:	bus_handler_.set_control_line_output(Port::B, Line::Two, false);	break; | ||||||
|  | 					case 0xe0:	bus_handler_.set_control_line_output(Port::B, Line::Two, true);		break; | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 		break; | ||||||
|  |  | ||||||
|  | 		// Interrupt control | ||||||
|  | 		case 0xd: | ||||||
|  | 			registers_.interrupt_flags &= ~value; | ||||||
|  | 			reevaluate_interrupts(); | ||||||
|  | 		break; | ||||||
|  | 		case 0xe: | ||||||
|  | 			if(value&0x80) | ||||||
|  | 				registers_.interrupt_enable |= value; | ||||||
|  | 			else | ||||||
|  | 				registers_.interrupt_enable &= ~value; | ||||||
|  | 			reevaluate_interrupts(); | ||||||
|  | 		break; | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | template <typename T> uint8_t MOS6522<T>::get_register(int address) { | ||||||
|  | 	address &= 0xf; | ||||||
|  | 	switch(address) { | ||||||
|  | 		case 0x0: | ||||||
|  | 			registers_.interrupt_flags &= ~(InterruptFlag::CB1ActiveEdge | InterruptFlag::CB2ActiveEdge); | ||||||
|  | 			reevaluate_interrupts(); | ||||||
|  | 		return get_port_input(Port::B, registers_.data_direction[1], registers_.output[1]); | ||||||
|  | 		case 0xf:	// TODO: handshake, latching | ||||||
|  | 		case 0x1: | ||||||
|  | 			registers_.interrupt_flags &= ~(InterruptFlag::CA1ActiveEdge | InterruptFlag::CA2ActiveEdge); | ||||||
|  | 			reevaluate_interrupts(); | ||||||
|  | 		return get_port_input(Port::A, registers_.data_direction[0], registers_.output[0]); | ||||||
|  |  | ||||||
|  | 		case 0x2:	return registers_.data_direction[1]; | ||||||
|  | 		case 0x3:	return registers_.data_direction[0]; | ||||||
|  |  | ||||||
|  | 		// Timer 1 | ||||||
|  | 		case 0x4: | ||||||
|  | 			registers_.interrupt_flags &= ~InterruptFlag::Timer1; | ||||||
|  | 			reevaluate_interrupts(); | ||||||
|  | 		return registers_.timer[0] & 0x00ff; | ||||||
|  | 		case 0x5:	return registers_.timer[0] >> 8; | ||||||
|  | 		case 0x6:	return registers_.timer_latch[0] & 0x00ff; | ||||||
|  | 		case 0x7:	return registers_.timer_latch[0] >> 8; | ||||||
|  |  | ||||||
|  | 		// Timer 2 | ||||||
|  | 		case 0x8: | ||||||
|  | 			registers_.interrupt_flags &= ~InterruptFlag::Timer2; | ||||||
|  | 			reevaluate_interrupts(); | ||||||
|  | 		return registers_.timer[1] & 0x00ff; | ||||||
|  | 		case 0x9:	return registers_.timer[1] >> 8; | ||||||
|  |  | ||||||
|  | 		case 0xa:	return registers_.shift; | ||||||
|  |  | ||||||
|  | 		case 0xb:	return registers_.auxiliary_control; | ||||||
|  | 		case 0xc:	return registers_.peripheral_control; | ||||||
|  |  | ||||||
|  | 		case 0xd:	return registers_.interrupt_flags | (get_interrupt_line() ? 0x80 : 0x00); | ||||||
|  | 		case 0xe:	return registers_.interrupt_enable | 0x80; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return 0xff; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | 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); | ||||||
|  | 	return (input & ~output_mask) | (output & output_mask); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Delegate and communications | ||||||
|  | template <typename T> void MOS6522<T>::reevaluate_interrupts() { | ||||||
|  | 	bool new_interrupt_status = get_interrupt_line(); | ||||||
|  | 	if(new_interrupt_status != last_posted_interrupt_status_) { | ||||||
|  | 		last_posted_interrupt_status_ = new_interrupt_status; | ||||||
|  | 		bus_handler_.set_interrupt_status(new_interrupt_status); | ||||||
|  | 	} | ||||||
|  | } | ||||||
							
								
								
									
										63
									
								
								Components/6522/Implementation/6522Storage.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										63
									
								
								Components/6522/Implementation/6522Storage.hpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,63 @@ | |||||||
|  | // | ||||||
|  | //  6522Storage.hpp | ||||||
|  | //  Clock Signal | ||||||
|  | // | ||||||
|  | //  Created by Thomas Harte on 04/09/2017. | ||||||
|  | //  Copyright © 2017 Thomas Harte. All rights reserved. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | #ifndef _522Storage_hpp | ||||||
|  | #define _522Storage_hpp | ||||||
|  |  | ||||||
|  | #include <cstdint> | ||||||
|  |  | ||||||
|  | namespace MOS { | ||||||
|  | namespace MOS6522 { | ||||||
|  |  | ||||||
|  | class MOS6522Storage { | ||||||
|  | 	protected: | ||||||
|  | 		// Phase toggle | ||||||
|  | 		bool is_phase2_ = false; | ||||||
|  |  | ||||||
|  | 		// The registers | ||||||
|  | 		struct Registers { | ||||||
|  | 			// "A  low  reset  (RES)  input  clears  all  R6522  internal registers to logic 0" | ||||||
|  | 			uint8_t output[2] = {0, 0}; | ||||||
|  | 			uint8_t input[2] = {0, 0}; | ||||||
|  | 			uint8_t data_direction[2] = {0, 0}; | ||||||
|  | 			uint16_t timer[2] = {0, 0}; | ||||||
|  | 			uint16_t timer_latch[2] = {0, 0}; | ||||||
|  | 			uint16_t last_timer[2] = {0, 0}; | ||||||
|  | 			int next_timer[2] = {-1, -1}; | ||||||
|  | 			uint8_t shift = 0; | ||||||
|  | 			uint8_t auxiliary_control = 0; | ||||||
|  | 			uint8_t peripheral_control = 0; | ||||||
|  | 			uint8_t interrupt_flags = 0; | ||||||
|  | 			uint8_t interrupt_enable = 0; | ||||||
|  | 			bool timer_needs_reload = false; | ||||||
|  | 		} registers_; | ||||||
|  |  | ||||||
|  | 		// control state | ||||||
|  | 		struct { | ||||||
|  | 			bool line_one = false; | ||||||
|  | 			bool line_two = false; | ||||||
|  | 		} control_inputs_[2]; | ||||||
|  |  | ||||||
|  | 		bool timer_is_running_[2] = {false, false}; | ||||||
|  | 		bool last_posted_interrupt_status_ = false; | ||||||
|  |  | ||||||
|  | 		enum InterruptFlag: uint8_t { | ||||||
|  | 			CA2ActiveEdge	= 1 << 0, | ||||||
|  | 			CA1ActiveEdge	= 1 << 1, | ||||||
|  | 			ShiftRegister	= 1 << 2, | ||||||
|  | 			CB2ActiveEdge	= 1 << 3, | ||||||
|  | 			CB1ActiveEdge	= 1 << 4, | ||||||
|  | 			Timer2			= 1 << 5, | ||||||
|  | 			Timer1			= 1 << 6, | ||||||
|  | 		}; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #endif /* _522Storage_hpp */ | ||||||
							
								
								
									
										19
									
								
								Components/6522/Implementation/IRQDelegatePortHandler.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								Components/6522/Implementation/IRQDelegatePortHandler.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,19 @@ | |||||||
|  | // | ||||||
|  | //  IRQDelegatePortHandler.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 IRQDelegatePortHandler::set_interrupt_delegate(Delegate *delegate) { | ||||||
|  | 	delegate_ = delegate; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void IRQDelegatePortHandler::set_interrupt_status(bool new_status) { | ||||||
|  | 	if(delegate_) delegate_->mos6522_did_change_interrupt_status(this); | ||||||
|  | } | ||||||
| @@ -51,7 +51,7 @@ template <class T> class MOS6532 { | |||||||
| 				case 0x04: case 0x05: case 0x06: case 0x07: | 				case 0x04: case 0x05: case 0x06: case 0x07: | ||||||
| 					if(address & 0x10) { | 					if(address & 0x10) { | ||||||
| 						timer_.writtenShift = timer_.activeShift = (decodedAddress - 0x04) * 3 + (decodedAddress / 0x07);	// i.e. 0, 3, 6, 10 | 						timer_.writtenShift = timer_.activeShift = (decodedAddress - 0x04) * 3 + (decodedAddress / 0x07);	// i.e. 0, 3, 6, 10 | ||||||
| 						timer_.value = ((unsigned int)value << timer_.activeShift) ; | 						timer_.value = (static_cast<unsigned int>(value) << timer_.activeShift) ; | ||||||
| 						timer_.interrupt_enabled = !!(address&0x08); | 						timer_.interrupt_enabled = !!(address&0x08); | ||||||
| 						interrupt_status_ &= ~InterruptFlag::Timer; | 						interrupt_status_ &= ~InterruptFlag::Timer; | ||||||
| 						evaluate_interrupts(); | 						evaluate_interrupts(); | ||||||
| @@ -79,7 +79,7 @@ template <class T> class MOS6532 { | |||||||
|  |  | ||||||
| 				// Timer and interrupt control | 				// Timer and interrupt control | ||||||
| 				case 0x04: case 0x06: { | 				case 0x04: case 0x06: { | ||||||
| 					uint8_t value = (uint8_t)(timer_.value >> timer_.activeShift); | 					uint8_t value = static_cast<uint8_t>(timer_.value >> timer_.activeShift); | ||||||
| 					timer_.interrupt_enabled = !!(address&0x08); | 					timer_.interrupt_enabled = !!(address&0x08); | ||||||
| 					interrupt_status_ &= ~InterruptFlag::Timer; | 					interrupt_status_ &= ~InterruptFlag::Timer; | ||||||
| 					evaluate_interrupts(); | 					evaluate_interrupts(); | ||||||
| @@ -107,7 +107,7 @@ template <class T> class MOS6532 { | |||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		inline void run_for(const Cycles cycles) { | 		inline void run_for(const Cycles cycles) { | ||||||
| 			unsigned int number_of_cycles = (unsigned int)cycles.as_int(); | 			unsigned int number_of_cycles = static_cast<unsigned int>(cycles.as_int()); | ||||||
|  |  | ||||||
| 			// permit counting _to_ zero; counting _through_ zero initiates the other behaviour | 			// permit counting _to_ zero; counting _through_ zero initiates the other behaviour | ||||||
| 			if(timer_.value >= number_of_cycles) { | 			if(timer_.value >= number_of_cycles) { | ||||||
| @@ -121,12 +121,9 @@ template <class T> class MOS6532 { | |||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		MOS6532() : | 		MOS6532() { | ||||||
| 			interrupt_status_(0), | 			timer_.value = static_cast<unsigned int>((rand() & 0xff) << 10); | ||||||
| 			port_{{.output_mask = 0, .output = 0}, {.output_mask = 0, .output = 0}}, | 		} | ||||||
| 			a7_interrupt_({.last_port_value = 0, .enabled = false}), |  | ||||||
| 			interrupt_line_(false), |  | ||||||
| 			timer_{.value = (unsigned int)((rand() & 0xff) << 10), .activeShift = 10, .writtenShift = 10, .interrupt_enabled = false} {} |  | ||||||
|  |  | ||||||
| 		inline void set_port_did_change(int port) { | 		inline void set_port_did_change(int port) { | ||||||
| 			if(!port) { | 			if(!port) { | ||||||
| @@ -154,26 +151,26 @@ template <class T> class MOS6532 { | |||||||
|  |  | ||||||
| 		struct { | 		struct { | ||||||
| 			unsigned int value; | 			unsigned int value; | ||||||
| 			unsigned int activeShift, writtenShift; | 			unsigned int activeShift = 10, writtenShift = 10; | ||||||
| 			bool interrupt_enabled; | 			bool interrupt_enabled = false; | ||||||
| 		} timer_; | 		} timer_; | ||||||
|  |  | ||||||
| 		struct { | 		struct { | ||||||
| 			bool enabled; | 			bool enabled = false; | ||||||
| 			bool active_on_positive; | 			bool active_on_positive = false; | ||||||
| 			uint8_t last_port_value; | 			uint8_t last_port_value = 0; | ||||||
| 		} a7_interrupt_; | 		} a7_interrupt_; | ||||||
|  |  | ||||||
| 		struct { | 		struct { | ||||||
| 			uint8_t output_mask, output; | 			uint8_t output_mask = 0, output = 0; | ||||||
| 		} port_[2]; | 		} port_[2]; | ||||||
|  |  | ||||||
| 		uint8_t interrupt_status_; | 		uint8_t interrupt_status_ = 0; | ||||||
| 		enum InterruptFlag: uint8_t { | 		enum InterruptFlag: uint8_t { | ||||||
| 			Timer = 0x80, | 			Timer = 0x80, | ||||||
| 			PA7 = 0x40 | 			PA7 = 0x40 | ||||||
| 		}; | 		}; | ||||||
| 		bool interrupt_line_; | 		bool interrupt_line_ = false; | ||||||
|  |  | ||||||
| 		// expected to be overridden | 		// expected to be overridden | ||||||
| 		uint8_t get_port_input(int port)										{	return 0xff;	} | 		uint8_t get_port_input(int port)										{	return 0xff;	} | ||||||
|   | |||||||
| @@ -8,22 +8,22 @@ | |||||||
|  |  | ||||||
| #include "6560.hpp" | #include "6560.hpp" | ||||||
|  |  | ||||||
|  | #include <cstring> | ||||||
|  |  | ||||||
| using namespace MOS; | using namespace MOS; | ||||||
|  |  | ||||||
| Speaker::Speaker() : | AudioGenerator::AudioGenerator(Concurrency::DeferringAsyncTaskQueue &audio_queue) : | ||||||
| 	volume_(0), | 	audio_queue_(audio_queue) {} | ||||||
| 	control_registers_{0, 0, 0, 0}, |  | ||||||
| 	shift_registers_{0, 0, 0, 0}, |  | ||||||
| 	counters_{2, 1, 0, 0} {}	// create a slight phase offset for the three channels |  | ||||||
|  |  | ||||||
| void Speaker::set_volume(uint8_t volume) { |  | ||||||
| 	enqueue([=]() { | void AudioGenerator::set_volume(uint8_t volume) { | ||||||
|  | 	audio_queue_.defer([=]() { | ||||||
| 		volume_ = volume; | 		volume_ = volume; | ||||||
| 	}); | 	}); | ||||||
| } | } | ||||||
|  |  | ||||||
| void Speaker::set_control(int channel, uint8_t value) { | void AudioGenerator::set_control(int channel, uint8_t value) { | ||||||
| 	enqueue([=]() { | 	audio_queue_.defer([=]() { | ||||||
| 		control_registers_[channel] = value; | 		control_registers_[channel] = value; | ||||||
| 	}); | 	}); | ||||||
| } | } | ||||||
| @@ -98,14 +98,14 @@ static uint8_t noise_pattern[] = { | |||||||
|  |  | ||||||
| #define shift(r) shift_registers_[r] = (shift_registers_[r] << 1) | (((shift_registers_[r]^0x80)&control_registers_[r]) >> 7) | #define shift(r) shift_registers_[r] = (shift_registers_[r] << 1) | (((shift_registers_[r]^0x80)&control_registers_[r]) >> 7) | ||||||
| #define increment(r) shift_registers_[r] = (shift_registers_[r]+1)%8191 | #define increment(r) shift_registers_[r] = (shift_registers_[r]+1)%8191 | ||||||
| #define update(r, m, up) counters_[r]++; if((counters_[r] >> m) == 0x80) { up(r); counters_[r] = (unsigned int)(control_registers_[r]&0x7f) << m; } | #define update(r, m, up) counters_[r]++; if((counters_[r] >> m) == 0x80) { up(r); counters_[r] = static_cast<unsigned int>(control_registers_[r]&0x7f) << m; } | ||||||
| // Note on slightly askew test: as far as I can make out, if the value in the register is 0x7f then what's supposed to happen | // Note on slightly askew test: as far as I can make out, if the value in the register is 0x7f then what's supposed to happen | ||||||
| // is that the 0x7f is loaded, on the next clocked cycle the Vic spots a 0x7f, pumps the output, reloads, etc. No increment | // is that the 0x7f is loaded, on the next clocked cycle the Vic spots a 0x7f, pumps the output, reloads, etc. No increment | ||||||
| // ever occurs. It's conditional. I don't really want two conditionals if I can avoid it so I'm incrementing regardless and | // ever occurs. It's conditional. I don't really want two conditionals if I can avoid it so I'm incrementing regardless and | ||||||
| // testing against 0x80. The effect should be the same: loading with 0x7f means an output update every cycle, loading with 0x7e | // testing against 0x80. The effect should be the same: loading with 0x7f means an output update every cycle, loading with 0x7e | ||||||
| // means every second cycle, etc. | // means every second cycle, etc. | ||||||
|  |  | ||||||
| void Speaker::get_samples(unsigned int number_of_samples, int16_t *target) { | void AudioGenerator::get_samples(std::size_t number_of_samples, int16_t *target) { | ||||||
| 	for(unsigned int c = 0; c < number_of_samples; c++) { | 	for(unsigned int c = 0; c < number_of_samples; c++) { | ||||||
| 		update(0, 2, shift); | 		update(0, 2, shift); | ||||||
| 		update(1, 1, shift); | 		update(1, 1, shift); | ||||||
| @@ -123,7 +123,7 @@ void Speaker::get_samples(unsigned int number_of_samples, int16_t *target) { | |||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| void Speaker::skip_samples(unsigned int number_of_samples) { | void AudioGenerator::skip_samples(std::size_t number_of_samples) { | ||||||
| 	for(unsigned int c = 0; c < number_of_samples; c++) { | 	for(unsigned int c = 0; c < number_of_samples; c++) { | ||||||
| 		update(0, 2, shift); | 		update(0, 2, shift); | ||||||
| 		update(1, 1, shift); | 		update(1, 1, shift); | ||||||
|   | |||||||
| @@ -9,28 +9,32 @@ | |||||||
| #ifndef _560_hpp | #ifndef _560_hpp | ||||||
| #define _560_hpp | #define _560_hpp | ||||||
|  |  | ||||||
| #include "../../Outputs/CRT/CRT.hpp" |  | ||||||
| #include "../../Outputs/Speaker.hpp" |  | ||||||
| #include "../../ClockReceiver/ClockReceiver.hpp" | #include "../../ClockReceiver/ClockReceiver.hpp" | ||||||
|  | #include "../../Concurrency/AsyncTaskQueue.hpp" | ||||||
|  | #include "../../Outputs/CRT/CRT.hpp" | ||||||
|  | #include "../../Outputs/Speaker/Implementation/LowpassSpeaker.hpp" | ||||||
|  | #include "../../Outputs/Speaker/Implementation/SampleSource.hpp" | ||||||
|  |  | ||||||
| namespace MOS { | namespace MOS { | ||||||
|  |  | ||||||
| // audio state | // audio state | ||||||
| class Speaker: public ::Outputs::Filter<Speaker> { | class AudioGenerator: public ::Outputs::Speaker::SampleSource { | ||||||
| 	public: | 	public: | ||||||
| 		Speaker(); | 		AudioGenerator(Concurrency::DeferringAsyncTaskQueue &audio_queue); | ||||||
|  |  | ||||||
| 		void set_volume(uint8_t volume); | 		void set_volume(uint8_t volume); | ||||||
| 		void set_control(int channel, uint8_t value); | 		void set_control(int channel, uint8_t value); | ||||||
|  |  | ||||||
| 		void get_samples(unsigned int number_of_samples, int16_t *target); | 		void get_samples(std::size_t number_of_samples, int16_t *target); | ||||||
| 		void skip_samples(unsigned int number_of_samples); | 		void skip_samples(std::size_t number_of_samples); | ||||||
|  |  | ||||||
| 	private: | 	private: | ||||||
| 		unsigned int counters_[4]; | 		Concurrency::DeferringAsyncTaskQueue &audio_queue_; | ||||||
| 		unsigned int shift_registers_[4]; |  | ||||||
| 		uint8_t control_registers_[4]; | 		unsigned int counters_[4] = {2, 1, 0, 0}; 	// create a slight phase offset for the three channels | ||||||
| 		uint8_t volume_; | 		unsigned int shift_registers_[4] = {0, 0, 0, 0}; | ||||||
|  | 		uint8_t control_registers_[4] = {0, 0, 0, 0}; | ||||||
|  | 		uint8_t volume_ = 0; | ||||||
| }; | }; | ||||||
|  |  | ||||||
| /*! | /*! | ||||||
| @@ -44,13 +48,10 @@ class Speaker: public ::Outputs::Filter<Speaker> { | |||||||
| template <class T> class MOS6560 { | template <class T> class MOS6560 { | ||||||
| 	public: | 	public: | ||||||
| 		MOS6560() : | 		MOS6560() : | ||||||
| 				crt_(new Outputs::CRT::CRT(65*4, 4, Outputs::CRT::NTSC60, 2)), | 				crt_(new Outputs::CRT::CRT(65*4, 4, Outputs::CRT::DisplayType::NTSC60, 2)), | ||||||
| 				speaker_(new Speaker), | 				audio_generator_(audio_queue_), | ||||||
| 				horizontal_counter_(0), | 				speaker_(audio_generator_) | ||||||
| 				vertical_counter_(0), | 		{ | ||||||
| 				cycles_since_speaker_update_(0), |  | ||||||
| 				is_odd_frame_(false), |  | ||||||
| 				is_odd_line_(false) { |  | ||||||
| 			crt_->set_composite_sampling_function( | 			crt_->set_composite_sampling_function( | ||||||
| 				"float composite_sample(usampler2D texID, vec2 coordinate, vec2 iCoordinate, float phase, float amplitude)" | 				"float composite_sample(usampler2D texID, vec2 coordinate, vec2 iCoordinate, float phase, float amplitude)" | ||||||
| 				"{" | 				"{" | ||||||
| @@ -66,11 +67,15 @@ template <class T> class MOS6560 { | |||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		void set_clock_rate(double clock_rate) { | 		void set_clock_rate(double clock_rate) { | ||||||
| 			speaker_->set_input_rate((float)(clock_rate / 4.0)); | 			speaker_.set_input_rate(static_cast<float>(clock_rate / 4.0)); | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		std::shared_ptr<Outputs::CRT::CRT> get_crt() { return crt_; } | 		Outputs::CRT::CRT *get_crt() { return crt_.get(); } | ||||||
| 		std::shared_ptr<Outputs::Speaker> get_speaker() { return speaker_; } | 		Outputs::Speaker::Speaker *get_speaker() { return &speaker_; } | ||||||
|  |  | ||||||
|  | 		void set_high_frequency_cutoff(float cutoff) { | ||||||
|  | 			speaker_.set_high_frequency_cutoff(cutoff); | ||||||
|  | 		} | ||||||
|  |  | ||||||
| 		enum OutputMode { | 		enum OutputMode { | ||||||
| 			PAL, NTSC | 			PAL, NTSC | ||||||
| @@ -109,9 +114,9 @@ template <class T> class MOS6560 { | |||||||
| 			Outputs::CRT::DisplayType display_type; | 			Outputs::CRT::DisplayType display_type; | ||||||
|  |  | ||||||
| 			switch(output_mode) { | 			switch(output_mode) { | ||||||
| 				case OutputMode::PAL: | 				default: | ||||||
| 					chrominances = pal_chrominances; | 					chrominances = pal_chrominances; | ||||||
| 					display_type = Outputs::CRT::PAL50; | 					display_type = Outputs::CRT::DisplayType::PAL50; | ||||||
| 					timing_.cycles_per_line = 71; | 					timing_.cycles_per_line = 71; | ||||||
| 					timing_.line_counter_increment_offset = 0; | 					timing_.line_counter_increment_offset = 0; | ||||||
| 					timing_.lines_per_progressive_field = 312; | 					timing_.lines_per_progressive_field = 312; | ||||||
| @@ -120,7 +125,7 @@ template <class T> class MOS6560 { | |||||||
|  |  | ||||||
| 				case OutputMode::NTSC: | 				case OutputMode::NTSC: | ||||||
| 					chrominances = ntsc_chrominances; | 					chrominances = ntsc_chrominances; | ||||||
| 					display_type = Outputs::CRT::NTSC60; | 					display_type = Outputs::CRT::DisplayType::NTSC60; | ||||||
| 					timing_.cycles_per_line = 65; | 					timing_.cycles_per_line = 65; | ||||||
| 					timing_.line_counter_increment_offset = 65 - 33;	// TODO: this is a bit of a hack; separate vertical and horizontal counting | 					timing_.line_counter_increment_offset = 65 - 33;	// TODO: this is a bit of a hack; separate vertical and horizontal counting | ||||||
| 					timing_.lines_per_progressive_field = 261; | 					timing_.lines_per_progressive_field = 261; | ||||||
| @@ -128,7 +133,7 @@ template <class T> class MOS6560 { | |||||||
| 				break; | 				break; | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
| 			crt_->set_new_display_type((unsigned int)(timing_.cycles_per_line*4), display_type); | 			crt_->set_new_display_type(static_cast<unsigned int>(timing_.cycles_per_line*4), display_type); | ||||||
| 			crt_->set_visible_area(Outputs::CRT::Rect(0.05f, 0.05f, 0.9f, 0.9f)); | 			crt_->set_visible_area(Outputs::CRT::Rect(0.05f, 0.05f, 0.9f, 0.9f)); | ||||||
|  |  | ||||||
| //			switch(output_mode) { | //			switch(output_mode) { | ||||||
| @@ -141,7 +146,7 @@ template <class T> class MOS6560 { | |||||||
| //			} | //			} | ||||||
|  |  | ||||||
| 			for(int c = 0; c < 16; c++) { | 			for(int c = 0; c < 16; c++) { | ||||||
| 				uint8_t *colour = (uint8_t *)&colours_[c]; | 				uint8_t *colour = reinterpret_cast<uint8_t *>(&colours_[c]); | ||||||
| 				colour[0] = luminances[c]; | 				colour[0] = luminances[c]; | ||||||
| 				colour[1] = chrominances[c]; | 				colour[1] = chrominances[c]; | ||||||
| 			} | 			} | ||||||
| @@ -218,7 +223,7 @@ template <class T> class MOS6560 { | |||||||
| 					if(column_counter_&1) { | 					if(column_counter_&1) { | ||||||
| 						fetch_address = registers_.character_cell_start_address + (character_code_*(registers_.tall_characters ? 16 : 8)) + current_character_row_; | 						fetch_address = registers_.character_cell_start_address + (character_code_*(registers_.tall_characters ? 16 : 8)) + current_character_row_; | ||||||
| 					} else { | 					} else { | ||||||
| 						fetch_address = (uint16_t)(registers_.video_matrix_start_address + video_matrix_address_counter_); | 						fetch_address = static_cast<uint16_t>(registers_.video_matrix_start_address + video_matrix_address_counter_); | ||||||
| 						video_matrix_address_counter_++; | 						video_matrix_address_counter_++; | ||||||
| 						if( | 						if( | ||||||
| 							(current_character_row_ == 15) || | 							(current_character_row_ == 15) || | ||||||
| @@ -270,7 +275,7 @@ template <class T> class MOS6560 { | |||||||
|  |  | ||||||
| 					pixel_pointer = nullptr; | 					pixel_pointer = nullptr; | ||||||
| 					if(output_state_ == State::Pixels) { | 					if(output_state_ == State::Pixels) { | ||||||
| 						pixel_pointer = (uint16_t *)crt_->allocate_write_area(260); | 						pixel_pointer = reinterpret_cast<uint16_t *>(crt_->allocate_write_area(260)); | ||||||
| 					} | 					} | ||||||
| 				} | 				} | ||||||
| 				cycles_in_state_++; | 				cycles_in_state_++; | ||||||
| @@ -325,7 +330,10 @@ template <class T> class MOS6560 { | |||||||
| 		/*! | 		/*! | ||||||
| 			Causes the 6560 to flush as much pending CRT and speaker communications as possible. | 			Causes the 6560 to flush as much pending CRT and speaker communications as possible. | ||||||
| 		*/ | 		*/ | ||||||
| 		inline void flush() { update_audio(); speaker_->flush(); } | 		inline void flush() { | ||||||
|  | 			update_audio(); | ||||||
|  | 			audio_queue_.perform(); | ||||||
|  | 		} | ||||||
|  |  | ||||||
| 		/*! | 		/*! | ||||||
| 			Writes to a 6560 register. | 			Writes to a 6560 register. | ||||||
| @@ -345,7 +353,7 @@ template <class T> class MOS6560 { | |||||||
|  |  | ||||||
| 				case 0x2: | 				case 0x2: | ||||||
| 					registers_.number_of_columns = value & 0x7f; | 					registers_.number_of_columns = value & 0x7f; | ||||||
| 					registers_.video_matrix_start_address = (uint16_t)((registers_.video_matrix_start_address & 0x3c00) | ((value & 0x80) << 2)); | 					registers_.video_matrix_start_address = static_cast<uint16_t>((registers_.video_matrix_start_address & 0x3c00) | ((value & 0x80) << 2)); | ||||||
| 				break; | 				break; | ||||||
|  |  | ||||||
| 				case 0x3: | 				case 0x3: | ||||||
| @@ -354,8 +362,8 @@ template <class T> class MOS6560 { | |||||||
| 				break; | 				break; | ||||||
|  |  | ||||||
| 				case 0x5: | 				case 0x5: | ||||||
| 					registers_.character_cell_start_address = (uint16_t)((value & 0x0f) << 10); | 					registers_.character_cell_start_address = static_cast<uint16_t>((value & 0x0f) << 10); | ||||||
| 					registers_.video_matrix_start_address = (uint16_t)((registers_.video_matrix_start_address & 0x0200) | ((value & 0xf0) << 6)); | 					registers_.video_matrix_start_address = static_cast<uint16_t>((registers_.video_matrix_start_address & 0x0200) | ((value & 0xf0) << 6)); | ||||||
| 				break; | 				break; | ||||||
|  |  | ||||||
| 				case 0xa: | 				case 0xa: | ||||||
| @@ -363,13 +371,13 @@ template <class T> class MOS6560 { | |||||||
| 				case 0xc: | 				case 0xc: | ||||||
| 				case 0xd: | 				case 0xd: | ||||||
| 					update_audio(); | 					update_audio(); | ||||||
| 					speaker_->set_control(address - 0xa, value); | 					audio_generator_.set_control(address - 0xa, value); | ||||||
| 				break; | 				break; | ||||||
|  |  | ||||||
| 				case 0xe: | 				case 0xe: | ||||||
| 					update_audio(); | 					update_audio(); | ||||||
| 					registers_.auxiliary_colour = colours_[value >> 4]; | 					registers_.auxiliary_colour = colours_[value >> 4]; | ||||||
| 					speaker_->set_volume(value & 0xf); | 					audio_generator_.set_volume(value & 0xf); | ||||||
| 				break; | 				break; | ||||||
|  |  | ||||||
| 				case 0xf: { | 				case 0xf: { | ||||||
| @@ -399,18 +407,21 @@ template <class T> class MOS6560 { | |||||||
| 			int current_line = (full_frame_counter_ + timing_.line_counter_increment_offset) / timing_.cycles_per_line; | 			int current_line = (full_frame_counter_ + timing_.line_counter_increment_offset) / timing_.cycles_per_line; | ||||||
| 			switch(address) { | 			switch(address) { | ||||||
| 				default: return registers_.direct_values[address]; | 				default: return registers_.direct_values[address]; | ||||||
| 				case 0x03: return (uint8_t)(current_line << 7) | (registers_.direct_values[3] & 0x7f); | 				case 0x03: return static_cast<uint8_t>(current_line << 7) | (registers_.direct_values[3] & 0x7f); | ||||||
| 				case 0x04: return (current_line >> 1) & 0xff; | 				case 0x04: return (current_line >> 1) & 0xff; | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 	private: | 	private: | ||||||
| 		std::shared_ptr<Outputs::CRT::CRT> crt_; | 		std::unique_ptr<Outputs::CRT::CRT> crt_; | ||||||
|  |  | ||||||
|  | 		Concurrency::DeferringAsyncTaskQueue audio_queue_; | ||||||
|  | 		AudioGenerator audio_generator_; | ||||||
|  | 		Outputs::Speaker::LowpassSpeaker<AudioGenerator> speaker_; | ||||||
|  |  | ||||||
| 		std::shared_ptr<Speaker> speaker_; |  | ||||||
| 		Cycles cycles_since_speaker_update_; | 		Cycles cycles_since_speaker_update_; | ||||||
| 		void update_audio() { | 		void update_audio() { | ||||||
| 			speaker_->run_for(Cycles(cycles_since_speaker_update_.divide(Cycles(4)))); | 			speaker_.run_for(audio_queue_, Cycles(cycles_since_speaker_update_.divide(Cycles(4)))); | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		// register state | 		// register state | ||||||
| @@ -432,7 +443,7 @@ template <class T> class MOS6560 { | |||||||
| 		unsigned int cycles_in_state_; | 		unsigned int cycles_in_state_; | ||||||
|  |  | ||||||
| 		// counters that cover an entire field | 		// counters that cover an entire field | ||||||
| 		int horizontal_counter_, vertical_counter_, full_frame_counter_; | 		int horizontal_counter_ = 0, vertical_counter_ = 0, full_frame_counter_; | ||||||
|  |  | ||||||
| 		// latches dictating start and length of drawing | 		// latches dictating start and length of drawing | ||||||
| 		bool vertical_drawing_latch_, horizontal_drawing_latch_; | 		bool vertical_drawing_latch_, horizontal_drawing_latch_; | ||||||
| @@ -447,14 +458,14 @@ template <class T> class MOS6560 { | |||||||
| 		// data latched from the bus | 		// data latched from the bus | ||||||
| 		uint8_t character_code_, character_colour_, character_value_; | 		uint8_t character_code_, character_colour_, character_value_; | ||||||
|  |  | ||||||
| 		bool is_odd_frame_, is_odd_line_; | 		bool is_odd_frame_ = false, is_odd_line_ = false; | ||||||
|  |  | ||||||
| 		// lookup table from 6560 colour index to appropriate PAL/NTSC value | 		// lookup table from 6560 colour index to appropriate PAL/NTSC value | ||||||
| 		uint16_t colours_[16]; | 		uint16_t colours_[16]; | ||||||
|  |  | ||||||
| 		uint16_t *pixel_pointer; | 		uint16_t *pixel_pointer; | ||||||
| 		void output_border(unsigned int number_of_cycles) { | 		void output_border(unsigned int number_of_cycles) { | ||||||
| 			uint16_t *colour_pointer = (uint16_t *)crt_->allocate_write_area(1); | 			uint16_t *colour_pointer = reinterpret_cast<uint16_t *>(crt_->allocate_write_area(1)); | ||||||
| 			if(colour_pointer) *colour_pointer = registers_.borderColour; | 			if(colour_pointer) *colour_pointer = registers_.borderColour; | ||||||
| 			crt_->output_level(number_of_cycles); | 			crt_->output_level(number_of_cycles); | ||||||
| 		} | 		} | ||||||
|   | |||||||
| @@ -18,141 +18,65 @@ namespace Motorola { | |||||||
| namespace CRTC { | namespace CRTC { | ||||||
|  |  | ||||||
| struct BusState { | struct BusState { | ||||||
| 	bool display_enable; | 	bool display_enable = false; | ||||||
| 	bool hsync; | 	bool hsync = false; | ||||||
| 	bool vsync; | 	bool vsync = false; | ||||||
| 	bool cursor; | 	bool cursor = false; | ||||||
| 	uint16_t refresh_address; | 	uint16_t refresh_address = 0; | ||||||
| 	uint16_t row_address; | 	uint16_t row_address = 0; | ||||||
| }; | }; | ||||||
|  |  | ||||||
| class BusHandler { | class BusHandler { | ||||||
| 	public: | 	public: | ||||||
| 		void perform_bus_cycle(const BusState &) {} | 		/*! | ||||||
|  | 			Performs the first phase of a 6845 bus cycle; this is the phase in which it is intended that | ||||||
|  | 			systems using the 6845 respect the bus state and produce pixels, sync or whatever they require. | ||||||
|  | 		*/ | ||||||
|  | 		void perform_bus_cycle_phase1(const BusState &) {} | ||||||
|  |  | ||||||
|  | 		/*! | ||||||
|  | 			Performs the second phase of a 6845 bus cycle. Some bus state — including sync — is updated | ||||||
|  | 			directly after phase 1 and hence is visible to an observer during phase 2. Handlers may therefore | ||||||
|  | 			implement @c perform_bus_cycle_phase2 to be notified of the availability of that state without | ||||||
|  | 			having to wait until the next cycle has begun. | ||||||
|  | 		*/ | ||||||
|  | 		void perform_bus_cycle_phase2(const BusState &) {} | ||||||
| }; | }; | ||||||
|  |  | ||||||
| enum Personality { | enum Personality { | ||||||
| 	HD6845S,	// | 	HD6845S,	// Type 0 in CPC parlance. Zero-width HSYNC available, no status, programmable VSYNC length. | ||||||
| 	UM6845R,	// | 				// Considered exactly identical to the UM6845, so this enum covers both. | ||||||
| 	MC6845,		// | 	UM6845R,	// Type 1 in CPC parlance. Status register, fixed-length VSYNC. | ||||||
| 	AMS40226	// | 	MC6845,		// Type 2. No status register, fixed-length VSYNC, no zero-length HSYNC. | ||||||
|  | 	AMS40226	// Type 3. Status is get register, fixed-length VSYNC, no zero-length HSYNC. | ||||||
| }; | }; | ||||||
|  |  | ||||||
|  | // TODO UM6845R and R12/R13; see http://www.cpcwiki.eu/index.php/CRTC#CRTC_Differences | ||||||
|  |  | ||||||
| template <class T> class CRTC6845 { | template <class T> class CRTC6845 { | ||||||
| 	public: | 	public: | ||||||
|  |  | ||||||
| 		CRTC6845(Personality p, T &bus_handler) : | 		CRTC6845(Personality p, T &bus_handler) noexcept : | ||||||
| 			personality_(p), bus_handler_(bus_handler) {} | 			personality_(p), bus_handler_(bus_handler), status_(0) {} | ||||||
|  |  | ||||||
| 		void run_for(Cycles cycles) { |  | ||||||
| 			int cyles_remaining = cycles.as_int(); |  | ||||||
| 			while(cyles_remaining--) { |  | ||||||
| 				// check for end of horizontal sync |  | ||||||
| 				if(hsync_down_counter_) { |  | ||||||
| 					hsync_down_counter_--; |  | ||||||
| 					if(!hsync_down_counter_) { |  | ||||||
| 						bus_state_.hsync = false; |  | ||||||
| 					} |  | ||||||
| 				} |  | ||||||
|  |  | ||||||
| 				// check for end of line |  | ||||||
| 				bool is_end_of_line = character_counter_ == registers_[0]; |  | ||||||
|  |  | ||||||
| 				// increment counter |  | ||||||
| 				character_counter_++; |  | ||||||
|  |  | ||||||
| 				// check for start of horizontal sync |  | ||||||
| 				if(character_counter_ == registers_[2]) { |  | ||||||
| 					hsync_down_counter_ = registers_[3] & 15; |  | ||||||
| 					if(hsync_down_counter_) bus_state_.hsync = true; |  | ||||||
| 				} |  | ||||||
|  |  | ||||||
| 				// update refresh address |  | ||||||
| 				if(character_is_visible_) { |  | ||||||
| 					bus_state_.refresh_address++; |  | ||||||
| 				} |  | ||||||
|  |  | ||||||
| 				// check for end of visible characters |  | ||||||
| 				if(character_counter_ == registers_[1]) { |  | ||||||
| 					character_is_visible_ = false; |  | ||||||
| 				} |  | ||||||
|  |  | ||||||
| 				// check for end-of-line |  | ||||||
| 				if(is_end_of_line) { |  | ||||||
| 					// check for end of vertical sync |  | ||||||
| 					if(vsync_down_counter_) { |  | ||||||
| 						vsync_down_counter_--; |  | ||||||
| 						if(!vsync_down_counter_) { |  | ||||||
| 							bus_state_.vsync = false; |  | ||||||
| 						} |  | ||||||
| 					} |  | ||||||
|  |  | ||||||
| 					if(is_in_adjustment_period_) { |  | ||||||
| 						line_counter_++; |  | ||||||
| 						if(line_counter_ == registers_[5]) { |  | ||||||
| 							line_counter_ = 0; |  | ||||||
| 							is_in_adjustment_period_ = false; |  | ||||||
| 							line_is_visible_ = true; |  | ||||||
| 							line_address_ = (uint16_t)((registers_[12] << 8) | registers_[13]); |  | ||||||
| 							bus_state_.refresh_address = line_address_; |  | ||||||
| 						} |  | ||||||
| 					} else { |  | ||||||
| 						// advance vertical counter |  | ||||||
| 						if(bus_state_.row_address == registers_[9]) { |  | ||||||
| 							if(!character_is_visible_) |  | ||||||
| 								line_address_ = bus_state_.refresh_address; |  | ||||||
| 							bus_state_.row_address = 0; |  | ||||||
|  |  | ||||||
| 							bool is_at_end_of_frame = line_counter_ == registers_[4]; |  | ||||||
| 							line_counter_ = (line_counter_ + 1) & 0x7f; |  | ||||||
|  |  | ||||||
| 							// check for end of visible lines |  | ||||||
| 							if(line_counter_ == registers_[6]) { |  | ||||||
| 								line_is_visible_ = false; |  | ||||||
| 							} |  | ||||||
|  |  | ||||||
| 							// check for start of vertical sync |  | ||||||
| 							if(line_counter_ == registers_[7]) { |  | ||||||
| 								bus_state_.vsync = true; |  | ||||||
| 								vsync_down_counter_ = registers_[3] >> 4; |  | ||||||
| 								if(!vsync_down_counter_) vsync_down_counter_ = 16; |  | ||||||
| 							} |  | ||||||
|  |  | ||||||
| 							// check for entry into the overflow area |  | ||||||
| 							if(is_at_end_of_frame) { |  | ||||||
| 								if(registers_[5]) { |  | ||||||
| 									is_in_adjustment_period_ = true; |  | ||||||
| 								} else { |  | ||||||
| 									line_is_visible_ = true; |  | ||||||
| 									line_address_ = (uint16_t)((registers_[12] << 8) | registers_[13]); |  | ||||||
| 									bus_state_.refresh_address = line_address_; |  | ||||||
| 								} |  | ||||||
| 								bus_state_.row_address = 0; |  | ||||||
| 								line_counter_ = 0; |  | ||||||
| 							} |  | ||||||
| 						} else { |  | ||||||
| 							bus_state_.row_address = (bus_state_.row_address + 1) & 0x1f; |  | ||||||
| 						} |  | ||||||
| 						bus_state_.refresh_address = line_address_; |  | ||||||
| 					} |  | ||||||
|  |  | ||||||
| 					character_counter_ = 0; |  | ||||||
| 					character_is_visible_ = (registers_[1] != 0); |  | ||||||
| 				} |  | ||||||
|  |  | ||||||
| 				perform_bus_cycle(); |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		void select_register(uint8_t r) { | 		void select_register(uint8_t r) { | ||||||
| 			selected_register_ = r; | 			selected_register_ = r; | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		uint8_t get_status() { | 		uint8_t get_status() { | ||||||
|  | 			switch(personality_) { | ||||||
|  | 				case UM6845R:	return status_ | (bus_state_.vsync ? 0x20 : 0x00); | ||||||
|  | 				case AMS40226:	return get_register(); | ||||||
|  | 				default:		return 0xff; | ||||||
|  | 			} | ||||||
| 			return 0xff; | 			return 0xff; | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		uint8_t get_register() { | 		uint8_t get_register() { | ||||||
|  | 			if(selected_register_ == 31) status_ &= ~0x80; | ||||||
|  | 			if(selected_register_ == 16 || selected_register_ == 17) status_ &= ~0x40; | ||||||
|  |  | ||||||
|  | 			if(personality_ == UM6845R && selected_register_ == 31) return dummy_register_; | ||||||
| 			if(selected_register_ < 12 || selected_register_ > 17) return 0xff; | 			if(selected_register_ < 12 || selected_register_ > 17) return 0xff; | ||||||
| 			return registers_[selected_register_]; | 			return registers_[selected_register_]; | ||||||
| 		} | 		} | ||||||
| @@ -163,38 +87,186 @@ template <class T> class CRTC6845 { | |||||||
| 				0xff, 0x1f, 0x7f, 0x1f, 0x3f, 0xff, 0x3f, 0xff | 				0xff, 0x1f, 0x7f, 0x1f, 0x3f, 0xff, 0x3f, 0xff | ||||||
| 			}; | 			}; | ||||||
|  |  | ||||||
| 			if(selected_register_ < 16) | 			// Per CPC documentation, skew doesn't work on a "type 1 or 2", i.e. an MC6845 or a UM6845R. | ||||||
|  | 			if(selected_register_ == 8 && personality_ != UM6845R && personality_ != MC6845) { | ||||||
|  | 				switch((value >> 4)&3) { | ||||||
|  | 					default:	display_skew_mask_ = 1;		break; | ||||||
|  | 					case 1:		display_skew_mask_ = 2;		break; | ||||||
|  | 					case 2:		display_skew_mask_ = 4;		break; | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			if(selected_register_ < 16) { | ||||||
| 				registers_[selected_register_] = value & masks[selected_register_]; | 				registers_[selected_register_] = value & masks[selected_register_]; | ||||||
| 			} | 			} | ||||||
|  | 			if(selected_register_ == 31 && personality_ == UM6845R) { | ||||||
|  | 				dummy_register_ = value; | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  |  | ||||||
| 		void trigger_light_pen() { | 		void trigger_light_pen() { | ||||||
| 			registers_[17] = bus_state_.refresh_address & 0xff; | 			registers_[17] = bus_state_.refresh_address & 0xff; | ||||||
| 			registers_[16] = bus_state_.refresh_address >> 8; | 			registers_[16] = bus_state_.refresh_address >> 8; | ||||||
|  | 			status_ |= 0x40; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		void run_for(Cycles cycles) { | ||||||
|  | 			int cyles_remaining = cycles.as_int(); | ||||||
|  | 			while(cyles_remaining--) { | ||||||
|  | 				// check for end of visible characters | ||||||
|  | 				if(character_counter_ == registers_[1]) { | ||||||
|  | 					// TODO: consider skew in character_is_visible_. Or maybe defer until perform_bus_cycle? | ||||||
|  | 					character_is_visible_ = false; | ||||||
|  | 					end_of_line_address_ = bus_state_.refresh_address; | ||||||
|  | 				} | ||||||
|  |  | ||||||
|  | 				perform_bus_cycle_phase1(); | ||||||
|  | 				bus_state_.refresh_address = (bus_state_.refresh_address + 1) & 0x3fff; | ||||||
|  |  | ||||||
|  | 				// check for end-of-line | ||||||
|  | 				if(character_counter_ == registers_[0]) { | ||||||
|  | 					character_counter_ = 0; | ||||||
|  | 					do_end_of_line(); | ||||||
|  | 					character_is_visible_ = true; | ||||||
|  | 				} else { | ||||||
|  | 					// increment counter | ||||||
|  | 					character_counter_++; | ||||||
|  | 				} | ||||||
|  |  | ||||||
|  | 				// check for start of horizontal sync | ||||||
|  | 				if(character_counter_ == registers_[2]) { | ||||||
|  | 					hsync_counter_ = 0; | ||||||
|  | 					bus_state_.hsync = true; | ||||||
|  | 				} | ||||||
|  |  | ||||||
|  | 				// check for end of horizontal sync; note that a sync time of zero will result in an immediate | ||||||
|  | 				// cancellation of the plan to perform sync if this is an HD6845S or UM6845R; otherwise zero | ||||||
|  | 				// will end up counting as 16 as it won't be checked until after overflow. | ||||||
|  | 				if(bus_state_.hsync) { | ||||||
|  | 					switch(personality_) { | ||||||
|  | 						case HD6845S: | ||||||
|  | 						case UM6845R: | ||||||
|  | 							bus_state_.hsync = hsync_counter_ != (registers_[3] & 15); | ||||||
|  | 							hsync_counter_ = (hsync_counter_ + 1) & 15; | ||||||
|  | 						break; | ||||||
|  | 						default: | ||||||
|  | 							hsync_counter_ = (hsync_counter_ + 1) & 15; | ||||||
|  | 							bus_state_.hsync = hsync_counter_ != (registers_[3] & 15); | ||||||
|  | 						break; | ||||||
|  | 					} | ||||||
|  | 				} | ||||||
|  |  | ||||||
|  | 				perform_bus_cycle_phase2(); | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		const BusState &get_bus_state() const { | ||||||
|  | 			return bus_state_; | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 	private: | 	private: | ||||||
| 		inline void perform_bus_cycle() { | 		inline void perform_bus_cycle_phase1() { | ||||||
| 			bus_state_.display_enable = character_is_visible_ && line_is_visible_; | 			// Skew theory of operation: keep a history of the last three states, and apply whichever is selected. | ||||||
| 			bus_state_.refresh_address &= 0x3fff; | 			character_is_visible_shifter_ = (character_is_visible_shifter_ << 1) | static_cast<unsigned int>(character_is_visible_); | ||||||
| 			bus_handler_.perform_bus_cycle(bus_state_); | 			bus_state_.display_enable = (static_cast<int>(character_is_visible_shifter_) & display_skew_mask_) && line_is_visible_; | ||||||
|  | 			bus_handler_.perform_bus_cycle_phase1(bus_state_); | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		inline void perform_bus_cycle_phase2() { | ||||||
|  | 			bus_handler_.perform_bus_cycle_phase2(bus_state_); | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		inline void do_end_of_line() { | ||||||
|  | 			// check for end of vertical sync | ||||||
|  | 			if(bus_state_.vsync) { | ||||||
|  | 				vsync_counter_ = (vsync_counter_ + 1) & 15; | ||||||
|  | 				// on the UM6845R and AMS40226, honour the programmed vertical sync time; on the other CRTCs | ||||||
|  | 				// always use a vertical sync count of 16. | ||||||
|  | 				switch(personality_) { | ||||||
|  | 					case HD6845S: | ||||||
|  | 					case AMS40226: | ||||||
|  | 						bus_state_.vsync = vsync_counter_ != (registers_[3] >> 4); | ||||||
|  | 					break; | ||||||
|  | 					default: | ||||||
|  | 						bus_state_.vsync = vsync_counter_ != 0; | ||||||
|  | 					break; | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			if(is_in_adjustment_period_) { | ||||||
|  | 				line_counter_++; | ||||||
|  | 				if(line_counter_ == registers_[5]) { | ||||||
|  | 					is_in_adjustment_period_ = false; | ||||||
|  | 					do_end_of_frame(); | ||||||
|  | 				} | ||||||
|  | 			} else { | ||||||
|  | 				// advance vertical counter | ||||||
|  | 				if(bus_state_.row_address == registers_[9]) { | ||||||
|  | 					bus_state_.row_address = 0; | ||||||
|  | 					line_address_ = end_of_line_address_; | ||||||
|  |  | ||||||
|  | 					// check for entry into the overflow area | ||||||
|  | 					if(line_counter_ == registers_[4]) { | ||||||
|  | 						if(registers_[5]) { | ||||||
|  | 							line_counter_ = 0; | ||||||
|  | 							is_in_adjustment_period_ = true; | ||||||
|  | 						} else { | ||||||
|  | 							do_end_of_frame(); | ||||||
|  | 						} | ||||||
|  | 					} else { | ||||||
|  | 						line_counter_ = (line_counter_ + 1) & 0x7f; | ||||||
|  |  | ||||||
|  | 						// check for start of vertical sync | ||||||
|  | 						if(line_counter_ == registers_[7]) { | ||||||
|  | 							bus_state_.vsync = true; | ||||||
|  | 							vsync_counter_ = 0; | ||||||
|  | 						} | ||||||
|  |  | ||||||
|  | 						// check for end of visible lines | ||||||
|  | 						if(line_counter_ == registers_[6]) { | ||||||
|  | 							line_is_visible_ = false; | ||||||
|  | 						} | ||||||
|  | 					} | ||||||
|  | 				} else { | ||||||
|  | 					bus_state_.row_address = (bus_state_.row_address + 1) & 0x1f; | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			bus_state_.refresh_address = line_address_; | ||||||
|  | 			character_counter_ = 0; | ||||||
|  | 			character_is_visible_ = (registers_[1] != 0); | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		inline void do_end_of_frame() { | ||||||
|  | 			line_counter_ = 0; | ||||||
|  | 			line_is_visible_ = true; | ||||||
|  | 			line_address_ = static_cast<uint16_t>((registers_[12] << 8) | registers_[13]); | ||||||
|  | 			bus_state_.refresh_address = line_address_; | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		Personality personality_; | 		Personality personality_; | ||||||
| 		T &bus_handler_; | 		T &bus_handler_; | ||||||
| 		BusState bus_state_; | 		BusState bus_state_; | ||||||
|  |  | ||||||
| 		uint8_t registers_[18]; | 		uint8_t registers_[18] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; | ||||||
| 		int selected_register_; | 		uint8_t dummy_register_ = 0; | ||||||
|  | 		int selected_register_ = 0; | ||||||
|  |  | ||||||
| 		uint8_t character_counter_; | 		uint8_t character_counter_ = 0; | ||||||
| 		uint8_t line_counter_; | 		uint8_t line_counter_ = 0; | ||||||
|  |  | ||||||
| 		bool character_is_visible_, line_is_visible_; | 		bool character_is_visible_ = false, line_is_visible_ = false; | ||||||
|  |  | ||||||
| 		int hsync_down_counter_; | 		int hsync_counter_ = 0; | ||||||
| 		int vsync_down_counter_; | 		int vsync_counter_ = 0; | ||||||
| 		bool is_in_adjustment_period_; | 		bool is_in_adjustment_period_ = false; | ||||||
| 		uint16_t line_address_; |  | ||||||
|  | 		uint16_t line_address_ = 0; | ||||||
|  | 		uint16_t end_of_line_address_ = 0; | ||||||
|  | 		uint8_t status_ = 0; | ||||||
|  |  | ||||||
|  | 		int display_skew_mask_ = 1; | ||||||
|  | 		unsigned int character_is_visible_shifter_ = 0; | ||||||
| }; | }; | ||||||
|  |  | ||||||
| } | } | ||||||
|   | |||||||
| @@ -76,8 +76,8 @@ template <class T> class i8255 { | |||||||
|  |  | ||||||
| 	private: | 	private: | ||||||
| 		void update_outputs() { | 		void update_outputs() { | ||||||
| 			port_handler_.set_value(0, outputs_[0]); | 			if(!(control_ & 0x10)) port_handler_.set_value(0, outputs_[0]); | ||||||
| 			port_handler_.set_value(1, outputs_[1]); | 			if(!(control_ & 0x02)) port_handler_.set_value(1, outputs_[1]); | ||||||
| 			port_handler_.set_value(2, outputs_[2]); | 			port_handler_.set_value(2, outputs_[2]); | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
|   | |||||||
| @@ -7,7 +7,7 @@ | |||||||
| // | // | ||||||
|  |  | ||||||
| #include "i8272.hpp" | #include "i8272.hpp" | ||||||
| #include "../../Storage/Disk/Encodings/MFM.hpp" | //#include "../../Storage/Disk/Encodings/MFM/Encoder.hpp" | ||||||
|  |  | ||||||
| #include <cstdio> | #include <cstdio> | ||||||
|  |  | ||||||
| @@ -34,6 +34,7 @@ using namespace Intel::i8272; | |||||||
| #define SetSeekEnd()					(status_[0] |= 0x20) | #define SetSeekEnd()					(status_[0] |= 0x20) | ||||||
| #define SetEquipmentCheck()				(status_[0] |= 0x10) | #define SetEquipmentCheck()				(status_[0] |= 0x10) | ||||||
| #define SetNotReady()					(status_[0] |= 0x08) | #define SetNotReady()					(status_[0] |= 0x08) | ||||||
|  | #define SetSide2()						(status_[0] |= 0x04) | ||||||
|  |  | ||||||
| #define SetEndOfCylinder()				(status_[1] |= 0x80) | #define SetEndOfCylinder()				(status_[1] |= 0x80) | ||||||
| #define SetDataError()					(status_[1] |= 0x20) | #define SetDataError()					(status_[1] |= 0x20) | ||||||
| @@ -43,6 +44,7 @@ using namespace Intel::i8272; | |||||||
| #define SetMissingAddressMark()			(status_[1] |= 0x01) | #define SetMissingAddressMark()			(status_[1] |= 0x01) | ||||||
|  |  | ||||||
| #define SetControlMark()				(status_[2] |= 0x40) | #define SetControlMark()				(status_[2] |= 0x40) | ||||||
|  | #define ClearControlMark()				(status_[2] &= ~0x40) | ||||||
| #define ControlMark()					(status_[2] & 0x40) | #define ControlMark()					(status_[2] & 0x40) | ||||||
|  |  | ||||||
| #define SetDataFieldDataError()			(status_[2] |= 0x20) | #define SetDataFieldDataError()			(status_[2] |= 0x20) | ||||||
| @@ -75,27 +77,26 @@ namespace { | |||||||
| 	const uint8_t CommandSenseDriveStatus = 0x04; | 	const uint8_t CommandSenseDriveStatus = 0x04; | ||||||
| } | } | ||||||
|  |  | ||||||
| i8272::i8272(BusHandler &bus_handler, Cycles clock_rate, int clock_rate_multiplier, int revolutions_per_minute) : | i8272::i8272(BusHandler &bus_handler, Cycles clock_rate) : | ||||||
| 	Storage::Disk::MFMController(clock_rate, clock_rate_multiplier, revolutions_per_minute), | 	Storage::Disk::MFMController(clock_rate), | ||||||
| 	bus_handler_(bus_handler), | 	bus_handler_(bus_handler) { | ||||||
| 	main_status_(0), | 	posit_event(static_cast<int>(Event8272::CommandByte)); | ||||||
| 	interesting_event_mask_((int)Event8272::CommandByte), | } | ||||||
| 	resume_point_(0), |  | ||||||
| 	delay_time_(0), | bool i8272::is_sleeping() { | ||||||
| 	head_timers_running_(0), | 	return is_sleeping_ && Storage::Disk::MFMController::is_sleeping(); | ||||||
| 	expects_input_(false), |  | ||||||
| 	drives_seeking_(0) { |  | ||||||
| 	posit_event((int)Event8272::CommandByte); |  | ||||||
| } | } | ||||||
|  |  | ||||||
| void i8272::run_for(Cycles cycles) { | void i8272::run_for(Cycles cycles) { | ||||||
| 	Storage::Disk::MFMController::run_for(cycles); | 	Storage::Disk::MFMController::run_for(cycles); | ||||||
|  |  | ||||||
|  | 	if(is_sleeping_) return; | ||||||
|  |  | ||||||
| 	// check for an expired timer | 	// check for an expired timer | ||||||
| 	if(delay_time_ > 0) { | 	if(delay_time_ > 0) { | ||||||
| 		if(cycles.as_int() >= delay_time_) { | 		if(cycles.as_int() >= delay_time_) { | ||||||
| 			delay_time_ = 0; | 			delay_time_ = 0; | ||||||
| 			posit_event((int)Event8272::Timer); | 			posit_event(static_cast<int>(Event8272::Timer)); | ||||||
| 		} else { | 		} else { | ||||||
| 			delay_time_ -= cycles.as_int(); | 			delay_time_ -= cycles.as_int(); | ||||||
| 		} | 		} | ||||||
| @@ -103,6 +104,7 @@ void i8272::run_for(Cycles cycles) { | |||||||
|  |  | ||||||
| 	// update seek status of any drives presently seeking | 	// update seek status of any drives presently seeking | ||||||
| 	if(drives_seeking_) { | 	if(drives_seeking_) { | ||||||
|  | 		int drives_left = drives_seeking_; | ||||||
| 		for(int c = 0; c < 4; c++) { | 		for(int c = 0; c < 4; c++) { | ||||||
| 			if(drives_[c].phase == Drive::Seeking) { | 			if(drives_[c].phase == Drive::Seeking) { | ||||||
| 				drives_[c].step_rate_counter += cycles.as_int(); | 				drives_[c].step_rate_counter += cycles.as_int(); | ||||||
| @@ -112,37 +114,52 @@ void i8272::run_for(Cycles cycles) { | |||||||
| 					// Perform a step. | 					// Perform a step. | ||||||
| 					int direction = (drives_[c].target_head_position < drives_[c].head_position) ? -1 : 1; | 					int direction = (drives_[c].target_head_position < drives_[c].head_position) ? -1 : 1; | ||||||
| 					printf("Target %d versus believed %d\n", drives_[c].target_head_position, drives_[c].head_position); | 					printf("Target %d versus believed %d\n", drives_[c].target_head_position, drives_[c].head_position); | ||||||
| 					drives_[c].drive->step(direction); | 					select_drive(c); | ||||||
|  | 					get_drive().step(direction); | ||||||
| 					if(drives_[c].target_head_position >= 0) drives_[c].head_position += direction; | 					if(drives_[c].target_head_position >= 0) drives_[c].head_position += direction; | ||||||
|  |  | ||||||
| 					// Check for completion. | 					// Check for completion. | ||||||
| 					if(drives_[c].seek_is_satisfied()) { | 					if(seek_is_satisfied(c)) { | ||||||
| 						drives_[c].phase = Drive::CompletedSeeking; | 						drives_[c].phase = Drive::CompletedSeeking; | ||||||
| 						drives_seeking_--; | 						drives_seeking_--; | ||||||
| 						break; | 						break; | ||||||
| 					} | 					} | ||||||
| 				} | 				} | ||||||
|  |  | ||||||
|  | 				drives_left--; | ||||||
|  | 				if(!drives_left) break; | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	// check for any head unloads | 	// check for any head unloads | ||||||
| 	if(head_timers_running_) { | 	if(head_timers_running_) { | ||||||
| 		for(int c = 0; c < 4; c++) { | 		int timers_left = head_timers_running_; | ||||||
| 			for(int h = 0; h < 2; h++) { | 		for(int c = 0; c < 8; c++) { | ||||||
| 				if(drives_[c].head_unload_delay[c] > 0) { | 			int drive = (c >> 1); | ||||||
| 					if(cycles.as_int() >= drives_[c].head_unload_delay[c]) { | 			int head = c&1; | ||||||
| 						drives_[c].head_unload_delay[c] = 0; |  | ||||||
| 						drives_[c].head_is_loaded[c] = false; | 			if(drives_[drive].head_unload_delay[head] > 0) { | ||||||
|  | 				if(cycles.as_int() >= drives_[drive].head_unload_delay[head]) { | ||||||
|  | 					drives_[drive].head_unload_delay[head] = 0; | ||||||
|  | 					drives_[drive].head_is_loaded[head] = false; | ||||||
| 					head_timers_running_--; | 					head_timers_running_--; | ||||||
| 						if(!head_timers_running_) return; |  | ||||||
| 				} else { | 				} else { | ||||||
| 						drives_[c].head_unload_delay[c] -= cycles.as_int(); | 					drives_[drive].head_unload_delay[head] -= cycles.as_int(); | ||||||
| 					} | 				} | ||||||
|  | 				timers_left--; | ||||||
|  | 				if(!timers_left) break; | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | 	// check for busy plus ready disabled | ||||||
|  | 	if(is_executing_ && !get_drive().get_is_ready()) { | ||||||
|  | 		posit_event(static_cast<int>(Event8272::NoLongerReady)); | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | 	is_sleeping_ = !delay_time_ && !drives_seeking_ && !head_timers_running_; | ||||||
|  | 	if(is_sleeping_) update_sleep_observer(); | ||||||
| } | } | ||||||
|  |  | ||||||
| void i8272::set_register(int address, uint8_t value) { | void i8272::set_register(int address, uint8_t value) { | ||||||
| @@ -159,7 +176,7 @@ void i8272::set_register(int address, uint8_t value) { | |||||||
| 	} else { | 	} else { | ||||||
| 		// accumulate latest byte in the command byte sequence | 		// accumulate latest byte in the command byte sequence | ||||||
| 		command_.push_back(value); | 		command_.push_back(value); | ||||||
| 		posit_event((int)Event8272::CommandByte); | 		posit_event(static_cast<int>(Event8272::CommandByte)); | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -168,7 +185,7 @@ uint8_t i8272::get_register(int address) { | |||||||
| 		if(result_stack_.empty()) return 0xff; | 		if(result_stack_.empty()) return 0xff; | ||||||
| 		uint8_t result = result_stack_.back(); | 		uint8_t result = result_stack_.back(); | ||||||
| 		result_stack_.pop_back(); | 		result_stack_.pop_back(); | ||||||
| 		if(result_stack_.empty()) posit_event((int)Event8272::ResultEmpty); | 		if(result_stack_.empty()) posit_event(static_cast<int>(Event8272::ResultEmpty)); | ||||||
|  |  | ||||||
| 		return result; | 		return result; | ||||||
| 	} else { | 	} else { | ||||||
| @@ -176,35 +193,29 @@ uint8_t i8272::get_register(int address) { | |||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| void i8272::set_disk(std::shared_ptr<Storage::Disk::Disk> disk, int drive) { |  | ||||||
| 	if(drive < 4 && drive >= 0) { |  | ||||||
| 		drives_[drive].drive->set_disk(disk); |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
|  |  | ||||||
| #define BEGIN_SECTION()	switch(resume_point_) { default: | #define BEGIN_SECTION()	switch(resume_point_) { default: | ||||||
| #define END_SECTION()	} | #define END_SECTION()	} | ||||||
|  |  | ||||||
| #define MS_TO_CYCLES(x)			x * 8000 | #define MS_TO_CYCLES(x)			x * 8000 | ||||||
| #define WAIT_FOR_EVENT(mask)	resume_point_ = __LINE__; interesting_event_mask_ = (int)mask; return; case __LINE__: | #define WAIT_FOR_EVENT(mask)	resume_point_ = __LINE__; interesting_event_mask_ = static_cast<int>(mask); return; case __LINE__: | ||||||
| #define WAIT_FOR_TIME(ms)		resume_point_ = __LINE__; interesting_event_mask_ = (int)Event8272::Timer; delay_time_ = MS_TO_CYCLES(ms); case __LINE__: if(delay_time_) return; | #define WAIT_FOR_TIME(ms)		resume_point_ = __LINE__; interesting_event_mask_ = static_cast<int>(Event8272::Timer); delay_time_ = MS_TO_CYCLES(ms); is_sleeping_ = false; update_sleep_observer(); case __LINE__: if(delay_time_) return; | ||||||
|  |  | ||||||
| #define PASTE(x, y) x##y | #define PASTE(x, y) x##y | ||||||
| #define CONCAT(x, y) PASTE(x, y) | #define CONCAT(x, y) PASTE(x, y) | ||||||
|  |  | ||||||
| #define FIND_HEADER()	\ | #define FIND_HEADER()	\ | ||||||
| 	set_data_mode(DataMode::Scanning);	\ | 	set_data_mode(DataMode::Scanning);	\ | ||||||
| 	CONCAT(find_header, __LINE__): WAIT_FOR_EVENT((int)Event::Token | (int)Event::IndexHole); \ | 	CONCAT(find_header, __LINE__): WAIT_FOR_EVENT(static_cast<int>(Event::Token) | static_cast<int>(Event::IndexHole)); \ | ||||||
| 	if(event_type == (int)Event::IndexHole) { index_hole_limit_--; }	\ | 	if(event_type == static_cast<int>(Event::IndexHole)) { index_hole_limit_--; }	\ | ||||||
| 	else if(get_latest_token().type == Token::ID) goto CONCAT(header_found, __LINE__);	\ | 	else if(get_latest_token().type == Token::ID) goto CONCAT(header_found, __LINE__);	\ | ||||||
| 	\ | 	\ | ||||||
| 	if(index_hole_limit_) goto CONCAT(find_header, __LINE__);	\ | 	if(index_hole_limit_) goto CONCAT(find_header, __LINE__);	\ | ||||||
| 	CONCAT(header_found, __LINE__):	0;\ | 	CONCAT(header_found, __LINE__):	(void)0;\ | ||||||
|  |  | ||||||
| #define FIND_DATA()	\ | #define FIND_DATA()	\ | ||||||
| 	set_data_mode(DataMode::Scanning);	\ | 	set_data_mode(DataMode::Scanning);	\ | ||||||
| 	CONCAT(find_data, __LINE__): WAIT_FOR_EVENT((int)Event::Token | (int)Event::IndexHole); \ | 	CONCAT(find_data, __LINE__): WAIT_FOR_EVENT(static_cast<int>(Event::Token) | static_cast<int>(Event::IndexHole)); \ | ||||||
| 	if(event_type == (int)Event::Token) { \ | 	if(event_type == static_cast<int>(Event::Token)) { \ | ||||||
| 		if(get_latest_token().type == Token::Byte || get_latest_token().type == Token::Sync) goto CONCAT(find_data, __LINE__);	\ | 		if(get_latest_token().type == Token::Byte || get_latest_token().type == Token::Sync) goto CONCAT(find_data, __LINE__);	\ | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| @@ -219,10 +230,10 @@ void i8272::set_disk(std::shared_ptr<Storage::Disk::Disk> disk, int drive) { | |||||||
| #define SET_DRIVE_HEAD_MFM()	\ | #define SET_DRIVE_HEAD_MFM()	\ | ||||||
| 	active_drive_ = command_[1]&3;	\ | 	active_drive_ = command_[1]&3;	\ | ||||||
| 	active_head_ = (command_[1] >> 2)&1;	\ | 	active_head_ = (command_[1] >> 2)&1;	\ | ||||||
| 	set_drive(drives_[active_drive_].drive);	\ | 	status_[0] = (command_[1]&7);	\ | ||||||
| 	drives_[active_drive_].drive->set_head((unsigned int)active_head_);	\ | 	select_drive(active_drive_);	\ | ||||||
| 	set_is_double_density(command_[0] & 0x40);	\ | 	get_drive().set_head(active_head_);	\ | ||||||
| 	invalidate_track(); | 	set_is_double_density(command_[0] & 0x40); | ||||||
|  |  | ||||||
| #define WAIT_FOR_BYTES(n) \ | #define WAIT_FOR_BYTES(n) \ | ||||||
| 	distance_into_section_ = 0;	\ | 	distance_into_section_ = 0;	\ | ||||||
| @@ -245,12 +256,18 @@ void i8272::set_disk(std::shared_ptr<Storage::Disk::Disk> disk, int drive) { | |||||||
| 	if(drives_[active_drive_].head_is_loaded[active_head_]) {\ | 	if(drives_[active_drive_].head_is_loaded[active_head_]) {\ | ||||||
| 		if(drives_[active_drive_].head_unload_delay[active_head_] == 0) {	\ | 		if(drives_[active_drive_].head_unload_delay[active_head_] == 0) {	\ | ||||||
| 			head_timers_running_++;	\ | 			head_timers_running_++;	\ | ||||||
|  | 			is_sleeping_ = false;	\ | ||||||
|  | 			update_sleep_observer();	\ | ||||||
| 		}	\ | 		}	\ | ||||||
| 		drives_[active_drive_].head_unload_delay[active_head_] = MS_TO_CYCLES(head_unload_time_);\ | 		drives_[active_drive_].head_unload_delay[active_head_] = MS_TO_CYCLES(head_unload_time_);\ | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| void i8272::posit_event(int event_type) { | void i8272::posit_event(int event_type) { | ||||||
| 	if(event_type == (int)Event::IndexHole) index_hole_count_++; | 	if(event_type == static_cast<int>(Event::IndexHole)) index_hole_count_++; | ||||||
|  | 	if(event_type == static_cast<int>(Event8272::NoLongerReady)) { | ||||||
|  | 		SetNotReady(); | ||||||
|  | 		goto abort; | ||||||
|  | 	} | ||||||
| 	if(!(interesting_event_mask_ & event_type)) return; | 	if(!(interesting_event_mask_ & event_type)) return; | ||||||
| 	interesting_event_mask_ &= ~event_type; | 	interesting_event_mask_ &= ~event_type; | ||||||
|  |  | ||||||
| @@ -274,7 +291,7 @@ void i8272::posit_event(int event_type) { | |||||||
| 			WAIT_FOR_EVENT(Event8272::CommandByte) | 			WAIT_FOR_EVENT(Event8272::CommandByte) | ||||||
| 			SetBusy(); | 			SetBusy(); | ||||||
|  |  | ||||||
| 			static const size_t required_lengths[32] = { | 			static const std::size_t required_lengths[32] = { | ||||||
| 				0,	0,	9,	3,	2,	9,	9,	2, | 				0,	0,	9,	3,	2,	9,	9,	2, | ||||||
| 				1,	9,	2,	0,	9,	6,	0,	3, | 				1,	9,	2,	0,	9,	6,	0,	3, | ||||||
| 				0,	9,	0,	0,	0,	0,	0,	0, | 				0,	9,	0,	0,	0,	0,	0,	0, | ||||||
| @@ -320,9 +337,14 @@ void i8272::posit_event(int event_type) { | |||||||
| 				} | 				} | ||||||
| 				// Establishes the drive and head being addressed, and whether in double density mode; populates the internal | 				// Establishes the drive and head being addressed, and whether in double density mode; populates the internal | ||||||
| 				// cylinder, head, sector and size registers from the command stream. | 				// cylinder, head, sector and size registers from the command stream. | ||||||
|  | 				is_executing_ = true; | ||||||
| 				if(!dma_mode_) SetNonDMAExecution(); | 				if(!dma_mode_) SetNonDMAExecution(); | ||||||
| 				SET_DRIVE_HEAD_MFM(); | 				SET_DRIVE_HEAD_MFM(); | ||||||
| 				LOAD_HEAD(); | 				LOAD_HEAD(); | ||||||
|  | 				if(!get_drive().get_is_ready()) { | ||||||
|  | 					SetNotReady(); | ||||||
|  | 					goto abort; | ||||||
|  | 				} | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
| 			// Jump to the proper place. | 			// Jump to the proper place. | ||||||
| @@ -409,7 +431,8 @@ void i8272::posit_event(int event_type) { | |||||||
| 		// flag doesn't match the sort the command was looking for. | 		// flag doesn't match the sort the command was looking for. | ||||||
| 		read_data_found_header: | 		read_data_found_header: | ||||||
| 			FIND_DATA(); | 			FIND_DATA(); | ||||||
| 			if(event_type == (int)Event::Token) { | 			ClearControlMark(); | ||||||
|  | 			if(event_type == static_cast<int>(Event::Token)) { | ||||||
| 				if(get_latest_token().type != Token::Data && get_latest_token().type != Token::DeletedData) { | 				if(get_latest_token().type != Token::Data && get_latest_token().type != Token::DeletedData) { | ||||||
| 					// Something other than a data mark came next — impliedly an ID or index mark. | 					// Something other than a data mark came next — impliedly an ID or index mark. | ||||||
| 					SetMissingAddressMark(); | 					SetMissingAddressMark(); | ||||||
| @@ -440,24 +463,24 @@ void i8272::posit_event(int event_type) { | |||||||
| 		// | 		// | ||||||
| 		// TODO: consider DTL. | 		// TODO: consider DTL. | ||||||
| 		read_data_get_byte: | 		read_data_get_byte: | ||||||
| 			WAIT_FOR_EVENT((int)Event::Token | (int)Event::IndexHole); | 			WAIT_FOR_EVENT(static_cast<int>(Event::Token) | static_cast<int>(Event::IndexHole)); | ||||||
| 			if(event_type == (int)Event::Token) { | 			if(event_type == static_cast<int>(Event::Token)) { | ||||||
| 				result_stack_.push_back(get_latest_token().byte_value); | 				result_stack_.push_back(get_latest_token().byte_value); | ||||||
| 				distance_into_section_++; | 				distance_into_section_++; | ||||||
| 				SetDataRequest(); | 				SetDataRequest(); | ||||||
| 				SetDataDirectionToProcessor(); | 				SetDataDirectionToProcessor(); | ||||||
| 				WAIT_FOR_EVENT((int)Event8272::ResultEmpty | (int)Event::Token | (int)Event::IndexHole); | 				WAIT_FOR_EVENT(static_cast<int>(Event8272::ResultEmpty) | static_cast<int>(Event::Token) | static_cast<int>(Event::IndexHole)); | ||||||
| 			} | 			} | ||||||
| 			switch(event_type) { | 			switch(event_type) { | ||||||
| 				case (int)Event8272::ResultEmpty:	// The caller read the byte in time; proceed as normal. | 				case static_cast<int>(Event8272::ResultEmpty):	// The caller read the byte in time; proceed as normal. | ||||||
| 					ResetDataRequest(); | 					ResetDataRequest(); | ||||||
| 					if(distance_into_section_ < (128 << size_)) goto read_data_get_byte; | 					if(distance_into_section_ < (128 << size_)) goto read_data_get_byte; | ||||||
| 				break; | 				break; | ||||||
| 				case (int)Event::Token:				// The caller hasn't read the old byte yet and a new one has arrived | 				case static_cast<int>(Event::Token):				// The caller hasn't read the old byte yet and a new one has arrived | ||||||
| 					SetOverrun(); | 					SetOverrun(); | ||||||
| 					goto abort; | 					goto abort; | ||||||
| 				break; | 				break; | ||||||
| 				case (int)Event::IndexHole: | 				case static_cast<int>(Event::IndexHole): | ||||||
| 					SetEndOfCylinder(); | 					SetEndOfCylinder(); | ||||||
| 					goto abort; | 					goto abort; | ||||||
| 				break; | 				break; | ||||||
| @@ -486,7 +509,7 @@ void i8272::posit_event(int event_type) { | |||||||
| 	write_data: | 	write_data: | ||||||
| 			printf("Write [deleted] data [%02x %02x %02x %02x ... %02x %02x]\n", command_[2], command_[3], command_[4], command_[5], command_[6], command_[8]); | 			printf("Write [deleted] data [%02x %02x %02x %02x ... %02x %02x]\n", command_[2], command_[3], command_[4], command_[5], command_[6], command_[8]); | ||||||
|  |  | ||||||
| 			if(drives_[active_drive_].drive->get_is_read_only()) { | 			if(get_drive().get_is_read_only()) { | ||||||
| 				SetNotWriteable(); | 				SetNotWriteable(); | ||||||
| 				goto abort; | 				goto abort; | ||||||
| 			} | 			} | ||||||
| @@ -509,7 +532,6 @@ void i8272::posit_event(int event_type) { | |||||||
| 			WAIT_FOR_EVENT(Event::DataWritten); | 			WAIT_FOR_EVENT(Event::DataWritten); | ||||||
| 			if(!has_input_) { | 			if(!has_input_) { | ||||||
| 				SetOverrun(); | 				SetOverrun(); | ||||||
| 				end_writing(); |  | ||||||
| 				goto abort; | 				goto abort; | ||||||
| 			} | 			} | ||||||
| 			write_byte(input_); | 			write_byte(input_); | ||||||
| @@ -541,7 +563,6 @@ void i8272::posit_event(int event_type) { | |||||||
| 		// Sets a maximum index hole limit of 2 then waits either until it finds a header mark or sees too many index holes. | 		// Sets a maximum index hole limit of 2 then waits either until it finds a header mark or sees too many index holes. | ||||||
| 		// If a header mark is found, reads in the following bytes that produce a header. Otherwise branches to data not found. | 		// If a header mark is found, reads in the following bytes that produce a header. Otherwise branches to data not found. | ||||||
| 			index_hole_limit_ = 2; | 			index_hole_limit_ = 2; | ||||||
| 		read_id_find_next_sector: |  | ||||||
| 			FIND_HEADER(); | 			FIND_HEADER(); | ||||||
| 			if(!index_hole_limit_) { | 			if(!index_hole_limit_) { | ||||||
| 				SetMissingAddressMark(); | 				SetMissingAddressMark(); | ||||||
| @@ -589,7 +610,7 @@ void i8272::posit_event(int event_type) { | |||||||
| 			distance_into_section_++; | 			distance_into_section_++; | ||||||
| 			SetDataRequest(); | 			SetDataRequest(); | ||||||
| 			// TODO: other possible exit conditions; find a way to merge with the read_data version of this. | 			// TODO: other possible exit conditions; find a way to merge with the read_data version of this. | ||||||
| 			WAIT_FOR_EVENT((int)Event8272::ResultEmpty); | 			WAIT_FOR_EVENT(static_cast<int>(Event8272::ResultEmpty)); | ||||||
| 			ResetDataRequest(); | 			ResetDataRequest(); | ||||||
| 			if(distance_into_section_ < (128 << header_[2])) goto read_track_get_byte; | 			if(distance_into_section_ < (128 << header_[2])) goto read_track_get_byte; | ||||||
|  |  | ||||||
| @@ -601,7 +622,7 @@ void i8272::posit_event(int event_type) { | |||||||
| 	// Performs format [/write] track. | 	// Performs format [/write] track. | ||||||
| 	format_track: | 	format_track: | ||||||
| 			printf("Format track\n"); | 			printf("Format track\n"); | ||||||
| 			if(drives_[active_drive_].drive->get_is_read_only()) { | 			if(get_drive().get_is_read_only()) { | ||||||
| 				SetNotWriteable(); | 				SetNotWriteable(); | ||||||
| 				goto abort; | 				goto abort; | ||||||
| 			} | 			} | ||||||
| @@ -626,14 +647,13 @@ void i8272::posit_event(int event_type) { | |||||||
| 			expects_input_ = true; | 			expects_input_ = true; | ||||||
| 			distance_into_section_ = 0; | 			distance_into_section_ = 0; | ||||||
| 		format_track_write_header: | 		format_track_write_header: | ||||||
| 			WAIT_FOR_EVENT((int)Event::DataWritten | (int)Event::IndexHole); | 			WAIT_FOR_EVENT(static_cast<int>(Event::DataWritten) | static_cast<int>(Event::IndexHole)); | ||||||
| 			switch(event_type) { | 			switch(event_type) { | ||||||
| 				case (int)Event::IndexHole: | 				case static_cast<int>(Event::IndexHole): | ||||||
| 					SetOverrun(); | 					SetOverrun(); | ||||||
| 					end_writing(); |  | ||||||
| 					goto abort; | 					goto abort; | ||||||
| 				break; | 				break; | ||||||
| 				case (int)Event::DataWritten: | 				case static_cast<int>(Event::DataWritten): | ||||||
| 					header_[distance_into_section_] = input_; | 					header_[distance_into_section_] = input_; | ||||||
| 					write_byte(input_); | 					write_byte(input_); | ||||||
| 					has_input_ = false; | 					has_input_ = false; | ||||||
| @@ -664,8 +684,8 @@ void i8272::posit_event(int event_type) { | |||||||
| 			// Otherwise, pad out to the index hole. | 			// Otherwise, pad out to the index hole. | ||||||
| 		format_track_pad: | 		format_track_pad: | ||||||
| 			write_byte(get_is_double_density() ? 0x4e : 0xff); | 			write_byte(get_is_double_density() ? 0x4e : 0xff); | ||||||
| 			WAIT_FOR_EVENT((int)Event::DataWritten | (int)Event::IndexHole); | 			WAIT_FOR_EVENT(static_cast<int>(Event::DataWritten) | static_cast<int>(Event::IndexHole)); | ||||||
| 			if(event_type != (int)Event::IndexHole) goto format_track_pad; | 			if(event_type != static_cast<int>(Event::IndexHole)) goto format_track_pad; | ||||||
|  |  | ||||||
| 			end_writing(); | 			end_writing(); | ||||||
|  |  | ||||||
| @@ -694,10 +714,13 @@ void i8272::posit_event(int event_type) { | |||||||
| 	seek: | 	seek: | ||||||
| 			{ | 			{ | ||||||
| 				int drive = command_[1]&3; | 				int drive = command_[1]&3; | ||||||
|  | 				select_drive(drive); | ||||||
|  |  | ||||||
| 				// Increment the seeking count if this drive wasn't already seeking. | 				// Increment the seeking count if this drive wasn't already seeking. | ||||||
| 				if(drives_[drive].phase != Drive::Seeking) { | 				if(drives_[drive].phase != Drive::Seeking) { | ||||||
| 					drives_seeking_++; | 					drives_seeking_++; | ||||||
|  | 					is_sleeping_ = false; | ||||||
|  | 					update_sleep_observer(); | ||||||
| 				} | 				} | ||||||
|  |  | ||||||
| 				// Set currently seeking, with a step to occur right now (yes, it sounds like jamming these | 				// Set currently seeking, with a step to occur right now (yes, it sounds like jamming these | ||||||
| @@ -721,7 +744,7 @@ void i8272::posit_event(int event_type) { | |||||||
| 				} | 				} | ||||||
|  |  | ||||||
| 				// Check whether any steps are even needed; if not then mark as completed already. | 				// Check whether any steps are even needed; if not then mark as completed already. | ||||||
| 				if(drives_[drive].seek_is_satisfied()) { | 				if(seek_is_satisfied(drive)) { | ||||||
| 					drives_[drive].phase = Drive::CompletedSeeking; | 					drives_[drive].phase = Drive::CompletedSeeking; | ||||||
| 					drives_seeking_--; | 					drives_seeking_--; | ||||||
| 				} | 				} | ||||||
| @@ -744,14 +767,13 @@ void i8272::posit_event(int event_type) { | |||||||
| 				// If a drive was found, return its results. Otherwise return a single 0x80. | 				// If a drive was found, return its results. Otherwise return a single 0x80. | ||||||
| 				if(found_drive != -1) { | 				if(found_drive != -1) { | ||||||
| 					drives_[found_drive].phase = Drive::NotSeeking; | 					drives_[found_drive].phase = Drive::NotSeeking; | ||||||
| 					status_[0] = (uint8_t)found_drive; | 					status_[0] = static_cast<uint8_t>(found_drive); | ||||||
| 					main_status_ &= ~(1 << found_drive); | 					main_status_ &= ~(1 << found_drive); | ||||||
| 					SetSeekEnd(); | 					SetSeekEnd(); | ||||||
|  |  | ||||||
| 					result_stack_.push_back(drives_[found_drive].head_position); | 					result_stack_ = { drives_[found_drive].head_position, status_[0]}; | ||||||
| 					result_stack_.push_back(status_[0]); |  | ||||||
| 				} else { | 				} else { | ||||||
| 					result_stack_.push_back(0x80); | 					result_stack_ = { 0x80 }; | ||||||
| 				} | 				} | ||||||
| 			} | 			} | ||||||
| 			goto post_result; | 			goto post_result; | ||||||
| @@ -773,24 +795,28 @@ void i8272::posit_event(int event_type) { | |||||||
| 			printf("Sense drive status\n"); | 			printf("Sense drive status\n"); | ||||||
| 			{ | 			{ | ||||||
| 				int drive = command_[1] & 3; | 				int drive = command_[1] & 3; | ||||||
| 				result_stack_.push_back( | 				select_drive(drive); | ||||||
|  | 				result_stack_= { | ||||||
|  | 					static_cast<uint8_t>( | ||||||
| 						(command_[1] & 7) |	// drive and head number | 						(command_[1] & 7) |	// drive and head number | ||||||
| 						0x08 |				// single sided | 						0x08 |				// single sided | ||||||
| 					(drives_[drive].drive->get_is_track_zero() ? 0x10 : 0x00)	| | 						(get_drive().get_is_track_zero() ? 0x10 : 0x00)	| | ||||||
| 					(drives_[drive].drive->get_is_ready() ? 0x20 : 0x00)		| | 						(get_drive().get_is_ready() ? 0x20 : 0x00)		| | ||||||
| 					(drives_[drive].drive->get_is_read_only() ? 0x40 : 0x00) | 						(get_drive().get_is_read_only() ? 0x40 : 0x00) | ||||||
| 				); | 					) | ||||||
|  | 				}; | ||||||
| 			} | 			} | ||||||
| 			goto post_result; | 			goto post_result; | ||||||
|  |  | ||||||
| 	// Performs any invalid command. | 	// Performs any invalid command. | ||||||
| 	invalid: | 	invalid: | ||||||
| 			// A no-op, but posts ST0 (but which ST0?) | 			// A no-op, but posts ST0 (but which ST0?) | ||||||
| 			result_stack_.push_back(0x80); | 			result_stack_ = {0x80}; | ||||||
| 			goto post_result; | 			goto post_result; | ||||||
|  |  | ||||||
| 	// Sets abnormal termination of the current command and proceeds to an ST0, ST1, ST2, C, H, R, N result phase. | 	// Sets abnormal termination of the current command and proceeds to an ST0, ST1, ST2, C, H, R, N result phase. | ||||||
| 	abort: | 	abort: | ||||||
|  | 		end_writing(); | ||||||
| 		SetAbnormalTermination(); | 		SetAbnormalTermination(); | ||||||
| 		goto post_st012chrn; | 		goto post_st012chrn; | ||||||
|  |  | ||||||
| @@ -798,14 +824,7 @@ void i8272::posit_event(int event_type) { | |||||||
| 	post_st012chrn: | 	post_st012chrn: | ||||||
| 			SCHEDULE_HEAD_UNLOAD(); | 			SCHEDULE_HEAD_UNLOAD(); | ||||||
|  |  | ||||||
| 			result_stack_.push_back(size_); | 			result_stack_ = {size_, sector_, head_, cylinder_, status_[2], status_[1], status_[0]}; | ||||||
| 			result_stack_.push_back(sector_); |  | ||||||
| 			result_stack_.push_back(head_); |  | ||||||
| 			result_stack_.push_back(cylinder_); |  | ||||||
|  |  | ||||||
| 			result_stack_.push_back(status_[2]); |  | ||||||
| 			result_stack_.push_back(status_[1]); |  | ||||||
| 			result_stack_.push_back(status_[0]); |  | ||||||
|  |  | ||||||
| 			goto post_result; | 			goto post_result; | ||||||
|  |  | ||||||
| @@ -813,12 +832,13 @@ void i8272::posit_event(int event_type) { | |||||||
| 	// last thing in it will be returned first. | 	// last thing in it will be returned first. | ||||||
| 	post_result: | 	post_result: | ||||||
| 			printf("Result to %02x, main %02x: ", command_[0] & 0x1f, main_status_); | 			printf("Result to %02x, main %02x: ", command_[0] & 0x1f, main_status_); | ||||||
| 			for(size_t c = 0; c < result_stack_.size(); c++) { | 			for(std::size_t c = 0; c < result_stack_.size(); c++) { | ||||||
| 				printf("%02x ", result_stack_[result_stack_.size() - 1 - c]); | 				printf("%02x ", result_stack_[result_stack_.size() - 1 - c]); | ||||||
| 			} | 			} | ||||||
| 			printf("\n"); | 			printf("\n"); | ||||||
|  |  | ||||||
| 			// Set ready to send data to the processor, no longer in non-DMA execution phase. | 			// Set ready to send data to the processor, no longer in non-DMA execution phase. | ||||||
|  | 			is_executing_ = false; | ||||||
| 			ResetNonDMAExecution(); | 			ResetNonDMAExecution(); | ||||||
| 			SetDataRequest(); | 			SetDataRequest(); | ||||||
| 			SetDataDirectionToProcessor(); | 			SetDataDirectionToProcessor(); | ||||||
| @@ -833,9 +853,9 @@ void i8272::posit_event(int event_type) { | |||||||
| 	END_SECTION() | 	END_SECTION() | ||||||
| } | } | ||||||
|  |  | ||||||
| bool i8272::Drive::seek_is_satisfied() { | bool i8272::seek_is_satisfied(int drive) { | ||||||
| 	return	(target_head_position == head_position) || | 	return	(drives_[drive].target_head_position == drives_[drive].head_position) || | ||||||
| 			(target_head_position == -1 && drive->get_is_track_zero()); | 			(drives_[drive].target_head_position == -1 && get_drive().get_is_track_zero()); | ||||||
| } | } | ||||||
|  |  | ||||||
| void i8272::set_dma_acknowledge(bool dack) { | void i8272::set_dma_acknowledge(bool dack) { | ||||||
|   | |||||||
| @@ -9,7 +9,7 @@ | |||||||
| #ifndef i8272_hpp | #ifndef i8272_hpp | ||||||
| #define i8272_hpp | #define i8272_hpp | ||||||
|  |  | ||||||
| #include "../../Storage/Disk/MFMDiskController.hpp" | #include "../../Storage/Disk/Controller/MFMDiskController.hpp" | ||||||
|  |  | ||||||
| #include <cstdint> | #include <cstdint> | ||||||
| #include <memory> | #include <memory> | ||||||
| @@ -26,7 +26,7 @@ class BusHandler { | |||||||
|  |  | ||||||
| class i8272: public Storage::Disk::MFMController { | class i8272: public Storage::Disk::MFMController { | ||||||
| 	public: | 	public: | ||||||
| 		i8272(BusHandler &bus_handler, Cycles clock_rate, int clock_rate_multiplier, int revolutions_per_minute); | 		i8272(BusHandler &bus_handler, Cycles clock_rate); | ||||||
|  |  | ||||||
| 		void run_for(Cycles); | 		void run_for(Cycles); | ||||||
|  |  | ||||||
| @@ -39,7 +39,10 @@ class i8272: public Storage::Disk::MFMController { | |||||||
| 		void set_dma_acknowledge(bool dack); | 		void set_dma_acknowledge(bool dack); | ||||||
| 		void set_terminal_count(bool tc); | 		void set_terminal_count(bool tc); | ||||||
|  |  | ||||||
| 		void set_disk(std::shared_ptr<Storage::Disk::Disk> disk, int drive); | 		bool is_sleeping(); | ||||||
|  |  | ||||||
|  | 	protected: | ||||||
|  | 		virtual void select_drive(int number) = 0; | ||||||
|  |  | ||||||
| 	private: | 	private: | ||||||
| 		// The bus handler, for interrupt and DMA-driven usage. | 		// The bus handler, for interrupt and DMA-driven usage. | ||||||
| @@ -47,86 +50,83 @@ class i8272: public Storage::Disk::MFMController { | |||||||
| 		std::unique_ptr<BusHandler> allocated_bus_handler_; | 		std::unique_ptr<BusHandler> allocated_bus_handler_; | ||||||
|  |  | ||||||
| 		// Status registers. | 		// Status registers. | ||||||
| 		uint8_t main_status_; | 		uint8_t main_status_ = 0; | ||||||
| 		uint8_t status_[3]; | 		uint8_t status_[3] = {0, 0, 0}; | ||||||
|  |  | ||||||
| 		// A buffer for accumulating the incoming command, and one for accumulating the result. | 		// A buffer for accumulating the incoming command, and one for accumulating the result. | ||||||
| 		std::vector<uint8_t> command_; | 		std::vector<uint8_t> command_; | ||||||
| 		std::vector<uint8_t> result_stack_; | 		std::vector<uint8_t> result_stack_; | ||||||
| 		uint8_t input_; | 		uint8_t input_ = 0; | ||||||
| 		bool has_input_; | 		bool has_input_ = false; | ||||||
| 		bool expects_input_; | 		bool expects_input_ = false; | ||||||
|  |  | ||||||
| 		// Event stream: the 8272-specific events, plus the current event state. | 		// Event stream: the 8272-specific events, plus the current event state. | ||||||
| 		enum class Event8272: int { | 		enum class Event8272: int { | ||||||
| 			CommandByte	= (1 << 3), | 			CommandByte	= (1 << 3), | ||||||
| 			Timer = (1 << 4), | 			Timer = (1 << 4), | ||||||
| 			ResultEmpty = (1 << 5), | 			ResultEmpty = (1 << 5), | ||||||
|  | 			NoLongerReady = (1 << 6) | ||||||
| 		}; | 		}; | ||||||
| 		void posit_event(int type); | 		void posit_event(int type); | ||||||
| 		int interesting_event_mask_; | 		int interesting_event_mask_ = static_cast<int>(Event8272::CommandByte); | ||||||
| 		int resume_point_; | 		int resume_point_ = 0; | ||||||
| 		bool is_access_command_; | 		bool is_access_command_ = false; | ||||||
|  |  | ||||||
| 		// The counter used for ::Timer events. | 		// The counter used for ::Timer events. | ||||||
| 		int delay_time_; | 		int delay_time_ = 0; | ||||||
|  |  | ||||||
| 		// The connected drives. | 		// The connected drives. | ||||||
| 		struct Drive { | 		struct Drive { | ||||||
| 			uint8_t head_position; | 			uint8_t head_position = 0; | ||||||
|  |  | ||||||
| 			// Seeking: persistent state. | 			// Seeking: persistent state. | ||||||
| 			enum Phase { | 			enum Phase { | ||||||
| 				NotSeeking, | 				NotSeeking, | ||||||
| 				Seeking, | 				Seeking, | ||||||
| 				CompletedSeeking | 				CompletedSeeking | ||||||
| 			} phase; | 			} phase = NotSeeking; | ||||||
| 			bool did_seek; | 			bool did_seek = false; | ||||||
| 			bool seek_failed; | 			bool seek_failed = false; | ||||||
|  |  | ||||||
| 			// Seeking: transient state. | 			// Seeking: transient state. | ||||||
| 			int step_rate_counter; | 			int step_rate_counter = 0; | ||||||
| 			int steps_taken; | 			int steps_taken = 0; | ||||||
| 			int target_head_position;	// either an actual number, or -1 to indicate to step until track zero | 			int target_head_position = 0;	// either an actual number, or -1 to indicate to step until track zero | ||||||
|  |  | ||||||
| 			/// @returns @c true if the currently queued-up seek or recalibrate has reached where it should be. |  | ||||||
| 			bool seek_is_satisfied(); |  | ||||||
|  |  | ||||||
| 			// Head state. | 			// Head state. | ||||||
| 			int head_unload_delay[2]; | 			int head_unload_delay[2] = {0, 0}; | ||||||
| 			bool head_is_loaded[2]; | 			bool head_is_loaded[2] = {false, false}; | ||||||
|  |  | ||||||
| 			// The connected drive. |  | ||||||
| 			std::shared_ptr<Storage::Disk::Drive> drive; |  | ||||||
|  |  | ||||||
| 			Drive() : |  | ||||||
| 				head_position(0), phase(NotSeeking), |  | ||||||
| 				drive(new Storage::Disk::Drive), |  | ||||||
| 				head_is_loaded{false, false} {}; |  | ||||||
| 		} drives_[4]; | 		} drives_[4]; | ||||||
| 		int drives_seeking_; | 		int drives_seeking_ = 0; | ||||||
|  |  | ||||||
|  | 		/// @returns @c true if the selected drive, which is number @c drive, can stop seeking. | ||||||
|  | 		bool seek_is_satisfied(int drive); | ||||||
|  |  | ||||||
| 		// User-supplied parameters; as per the specify command. | 		// User-supplied parameters; as per the specify command. | ||||||
| 		int step_rate_time_; | 		int step_rate_time_ = 1; | ||||||
| 		int head_unload_time_; | 		int head_unload_time_ = 1; | ||||||
| 		int head_load_time_; | 		int head_load_time_ = 1; | ||||||
| 		bool dma_mode_; | 		bool dma_mode_ = false; | ||||||
|  | 		bool is_executing_ = false; | ||||||
|  |  | ||||||
| 		// A count of head unload timers currently running. | 		// A count of head unload timers currently running. | ||||||
| 		int head_timers_running_; | 		int head_timers_running_ = 0; | ||||||
|  |  | ||||||
| 		// Transient storage and counters used while reading the disk. | 		// Transient storage and counters used while reading the disk. | ||||||
| 		uint8_t header_[6]; | 		uint8_t header_[6] = {0, 0, 0, 0, 0, 0}; | ||||||
| 		int distance_into_section_; | 		int distance_into_section_ = 0; | ||||||
| 		int index_hole_count_, index_hole_limit_; | 		int index_hole_count_ = 0, index_hole_limit_ = 0; | ||||||
|  |  | ||||||
| 		// Keeps track of the drive and head in use during commands. | 		// Keeps track of the drive and head in use during commands. | ||||||
| 		int active_drive_; | 		int active_drive_ = 0; | ||||||
| 		int active_head_; | 		int active_head_ = 0; | ||||||
|  |  | ||||||
| 		// Internal registers. | 		// Internal registers. | ||||||
| 		uint8_t cylinder_, head_, sector_, size_; | 		uint8_t cylinder_ = 0, head_ = 0, sector_ = 0, size_ = 0; | ||||||
|  |  | ||||||
|  | 		// Master switch on not performing any work. | ||||||
|  | 		bool is_sleeping_ = false; | ||||||
| }; | }; | ||||||
|  |  | ||||||
| } | } | ||||||
|   | |||||||
							
								
								
									
										664
									
								
								Components/9918/9918.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										664
									
								
								Components/9918/9918.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,664 @@ | |||||||
|  | // | ||||||
|  | //  9918.cpp | ||||||
|  | //  Clock Signal | ||||||
|  | // | ||||||
|  | //  Created by Thomas Harte on 25/11/2017. | ||||||
|  | //  Copyright © 2017 Thomas Harte. All rights reserved. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | #include "9918.hpp" | ||||||
|  |  | ||||||
|  | #include <cassert> | ||||||
|  | #include <cstring> | ||||||
|  |  | ||||||
|  | using namespace TI; | ||||||
|  |  | ||||||
|  | namespace { | ||||||
|  |  | ||||||
|  | const uint32_t palette_pack(uint8_t r, uint8_t g, uint8_t b) { | ||||||
|  | 	uint32_t result = 0; | ||||||
|  | 	uint8_t *const result_ptr = reinterpret_cast<uint8_t *>(&result); | ||||||
|  | 	result_ptr[0] = r; | ||||||
|  | 	result_ptr[1] = g; | ||||||
|  | 	result_ptr[2] = b; | ||||||
|  | 	result_ptr[3] = 0; | ||||||
|  | 	return result; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | const uint32_t palette[16] = { | ||||||
|  | 	palette_pack(0, 0, 0), | ||||||
|  | 	palette_pack(0, 0, 0), | ||||||
|  | 	palette_pack(33, 200, 66), | ||||||
|  | 	palette_pack(94, 220, 120), | ||||||
|  |  | ||||||
|  | 	palette_pack(84, 85, 237), | ||||||
|  | 	palette_pack(125, 118, 252), | ||||||
|  | 	palette_pack(212, 82, 77), | ||||||
|  | 	palette_pack(66, 235, 245), | ||||||
|  |  | ||||||
|  | 	palette_pack(252, 85, 84), | ||||||
|  | 	palette_pack(255, 121, 120), | ||||||
|  | 	palette_pack(212, 193, 84), | ||||||
|  | 	palette_pack(230, 206, 128), | ||||||
|  |  | ||||||
|  | 	palette_pack(33, 176, 59), | ||||||
|  | 	palette_pack(201, 91, 186), | ||||||
|  | 	palette_pack(204, 204, 204), | ||||||
|  | 	palette_pack(255, 255, 255) | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | const uint8_t StatusInterrupt = 0x80; | ||||||
|  | const uint8_t StatusFifthSprite = 0x40; | ||||||
|  |  | ||||||
|  | const int StatusSpriteCollisionShift = 5; | ||||||
|  | const uint8_t StatusSpriteCollision = 0x20; | ||||||
|  |  | ||||||
|  | } | ||||||
|  |  | ||||||
|  | TMS9918Base::TMS9918Base() : | ||||||
|  | 	// 342 internal cycles are 228/227.5ths of a line, so 341.25 cycles should be a whole | ||||||
|  | 	// line. Therefore multiply everything by four, but set line length to 1365 rather than 342*4 = 1368. | ||||||
|  | 	crt_(new Outputs::CRT::CRT(1365, 4, Outputs::CRT::DisplayType::NTSC60, 4)) {} | ||||||
|  |  | ||||||
|  | TMS9918::TMS9918(Personality p) { | ||||||
|  | 	// Unimaginatively, this class just passes RGB through to the shader. Investigation is needed | ||||||
|  | 	// into whether there's a more natural form. | ||||||
|  | 	crt_->set_rgb_sampling_function( | ||||||
|  | 		"vec3 rgb_sample(usampler2D sampler, vec2 coordinate, vec2 icoordinate)" | ||||||
|  | 		"{" | ||||||
|  | 			"return texture(sampler, coordinate).rgb / vec3(255.0);" | ||||||
|  | 		"}"); | ||||||
|  | 	crt_->set_output_device(Outputs::CRT::OutputDevice::Monitor); | ||||||
|  | 	crt_->set_visible_area(Outputs::CRT::Rect(0.055f, 0.025f, 0.9f, 0.9f)); | ||||||
|  | 	crt_->set_input_gamma(2.8f); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | Outputs::CRT::CRT *TMS9918::get_crt() { | ||||||
|  | 	return crt_.get(); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void TMS9918Base::test_sprite(int sprite_number, int screen_row) { | ||||||
|  | 	if(!(status_ & StatusFifthSprite)) { | ||||||
|  | 		status_ = static_cast<uint8_t>((status_ & ~31) | sprite_number); | ||||||
|  | 	} | ||||||
|  | 	if(sprites_stopped_) | ||||||
|  | 		return; | ||||||
|  |  | ||||||
|  | 	const int sprite_position = ram_[sprite_attribute_table_address_ + (sprite_number << 2)]; | ||||||
|  | 	// A sprite Y of 208 means "don't scan the list any further". | ||||||
|  | 	if(sprite_position == 208) { | ||||||
|  | 		sprites_stopped_ = true; | ||||||
|  | 		return; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	const int sprite_row = (screen_row - sprite_position)&255; | ||||||
|  | 	if(sprite_row < 0 || sprite_row >= sprite_height_) return; | ||||||
|  |  | ||||||
|  | 	const int active_sprite_slot = sprite_sets_[active_sprite_set_].active_sprite_slot; | ||||||
|  | 	if(active_sprite_slot == 4) { | ||||||
|  | 		status_ |= StatusFifthSprite; | ||||||
|  | 		return; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	SpriteSet::ActiveSprite &sprite = sprite_sets_[active_sprite_set_].active_sprites[active_sprite_slot]; | ||||||
|  | 	sprite.index = sprite_number; | ||||||
|  | 	sprite.row = sprite_row >> (sprites_magnified_ ? 1 : 0); | ||||||
|  | 	sprite_sets_[active_sprite_set_].active_sprite_slot++; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void TMS9918Base::get_sprite_contents(int field, int cycles_left, int screen_row) { | ||||||
|  | 	int sprite_id = field / 6; | ||||||
|  | 	field %= 6; | ||||||
|  |  | ||||||
|  | 	while(true) { | ||||||
|  | 		const int cycles_in_sprite = std::min(cycles_left, 6 - field); | ||||||
|  | 		cycles_left -= cycles_in_sprite; | ||||||
|  | 		const int final_field = cycles_in_sprite + field; | ||||||
|  |  | ||||||
|  | 		assert(sprite_id < 4); | ||||||
|  | 		SpriteSet::ActiveSprite &sprite = sprite_sets_[active_sprite_set_].active_sprites[sprite_id]; | ||||||
|  |  | ||||||
|  | 		if(field < 4) { | ||||||
|  | 			std::memcpy( | ||||||
|  | 				&sprite.info[field], | ||||||
|  | 				&ram_[sprite_attribute_table_address_ + (sprite.index << 2) + field], | ||||||
|  | 				static_cast<size_t>(std::min(4, final_field) - field)); | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		field = std::min(4, final_field); | ||||||
|  | 		const int sprite_offset = sprite.info[2] & ~(sprites_16x16_ ? 3 : 0); | ||||||
|  | 		const int sprite_address = sprite_generator_table_address_ + (sprite_offset << 3) + sprite.row; // TODO: recalclate sprite.row from screen_row (?) | ||||||
|  | 		while(field < final_field) { | ||||||
|  | 			sprite.image[field - 4] = ram_[sprite_address + ((field - 4) << 4)]; | ||||||
|  | 			field++; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		if(!cycles_left) return; | ||||||
|  | 		field = 0; | ||||||
|  | 		sprite_id++; | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void TMS9918::run_for(const HalfCycles cycles) { | ||||||
|  | 	// As specific as I've been able to get: | ||||||
|  | 	// Scanline time is always 228 cycles. | ||||||
|  | 	// PAL output is 313 lines total. NTSC output is 262 lines total. | ||||||
|  | 	// Interrupt is signalled upon entering the lower border. | ||||||
|  |  | ||||||
|  | 	// Keep a count of cycles separate from internal counts to avoid | ||||||
|  | 	// potential errors mapping back and forth. | ||||||
|  | 	half_cycles_into_frame_ = (half_cycles_into_frame_ + cycles) % HalfCycles(frame_lines_ * 228 * 2); | ||||||
|  |  | ||||||
|  | 	// Convert 456 clocked half cycles per line to 342 internal cycles per line; | ||||||
|  | 	// the internal clock is 1.5 times the nominal 3.579545 Mhz that I've advertised | ||||||
|  | 	// for this part. So multiply by three quarters. | ||||||
|  | 	int int_cycles = (cycles.as_int() * 3) + cycles_error_; | ||||||
|  | 	cycles_error_ = int_cycles & 3; | ||||||
|  | 	int_cycles >>= 2; | ||||||
|  | 	if(!int_cycles) return; | ||||||
|  |  | ||||||
|  | 	while(int_cycles) { | ||||||
|  | 		// Determine how much time has passed in the remainder of this line, and proceed. | ||||||
|  | 		int cycles_left = std::min(342 - column_, int_cycles); | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  | 		// ------------------------------------ | ||||||
|  | 		// Potentially perform a memory access. | ||||||
|  | 		// ------------------------------------ | ||||||
|  | 		if(queued_access_ != MemoryAccess::None) { | ||||||
|  | 			int time_until_access_slot = 0; | ||||||
|  | 			switch(line_mode_) { | ||||||
|  | 				case LineMode::Refresh: | ||||||
|  | 					if(column_ < 53 || column_ >= 307) time_until_access_slot = column_&1; | ||||||
|  | 					else time_until_access_slot = 3 - ((column_ - 53)&3); | ||||||
|  | 					// i.e. 53 -> 3, 52 -> 2, 51 -> 1, 50 -> 0, etc | ||||||
|  | 				break; | ||||||
|  | 				case LineMode::Text: | ||||||
|  | 					if(column_ < 59 || column_ >= 299) time_until_access_slot = column_&1; | ||||||
|  | 					else time_until_access_slot = 5 - ((column_ + 3)%6); | ||||||
|  | 					// i.e. 59 -> 3, 60 -> 2, 61 -> 1, etc | ||||||
|  | 				break; | ||||||
|  | 				case LineMode::Character: | ||||||
|  | 					if(column_ < 9) time_until_access_slot = column_&1; | ||||||
|  | 					else if(column_ < 30) time_until_access_slot = 30 - column_; | ||||||
|  | 					else if(column_ < 37) time_until_access_slot = column_&1; | ||||||
|  | 					else if(column_ < 311) time_until_access_slot = 31 - ((column_ + 7)&31); | ||||||
|  | 					// i.e. 53 -> 3, 54 -> 2, 55 -> 1, 56 -> 0, 57 -> 31, etc | ||||||
|  | 					else if(column_ < 313) time_until_access_slot = column_&1; | ||||||
|  | 					else time_until_access_slot = 342 - column_; | ||||||
|  | 				break; | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			if(cycles_left >= time_until_access_slot) { | ||||||
|  | 				if(queued_access_ == MemoryAccess::Write) { | ||||||
|  | 					ram_[ram_pointer_ & 16383] = read_ahead_buffer_; | ||||||
|  | 				} else { | ||||||
|  | 					read_ahead_buffer_ = ram_[ram_pointer_ & 16383]; | ||||||
|  | 				} | ||||||
|  | 				ram_pointer_++; | ||||||
|  | 				queued_access_ = MemoryAccess::None; | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  | 		column_ += cycles_left;		// column_ is now the column that has been reached in this line. | ||||||
|  | 		int_cycles -= cycles_left;	// Count down duration to run for. | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  | 		// ------------------------------ | ||||||
|  | 		// Perform video memory accesses. | ||||||
|  | 		// ------------------------------ | ||||||
|  | 		if(((row_ < 192) || (row_ == frame_lines_-1)) && !blank_screen_) { | ||||||
|  | 			const int sprite_row = (row_ < 192) ? row_ : -1; | ||||||
|  | 			const int access_slot = column_ >> 1;	// There are only 171 available memory accesses per line. | ||||||
|  | 			switch(line_mode_) { | ||||||
|  | 				default: break; | ||||||
|  |  | ||||||
|  | 				case LineMode::Text: | ||||||
|  | 					access_pointer_ = std::min(30, access_slot); | ||||||
|  | 					if(access_pointer_ >= 30 && access_pointer_ < 150) { | ||||||
|  | 						const int row_base = pattern_name_address_ + (row_ >> 3) * 40; | ||||||
|  | 						const int end = std::min(150, access_slot); | ||||||
|  |  | ||||||
|  | 						// Pattern names are collected every third window starting from window 30. | ||||||
|  | 						const int pattern_names_start = (access_pointer_ - 30 + 2) / 3; | ||||||
|  | 						const int pattern_names_end = (end - 30 + 2) / 3; | ||||||
|  | 						std::memcpy(&pattern_names_[pattern_names_start], &ram_[row_base + pattern_names_start], static_cast<size_t>(pattern_names_end - pattern_names_start)); | ||||||
|  |  | ||||||
|  | 						// Patterns are collected every third window starting from window 32. | ||||||
|  | 						const int pattern_buffer_start = (access_pointer_ - 32 + 2) / 3; | ||||||
|  | 						const int pattern_buffer_end = (end - 32 + 2) / 3; | ||||||
|  | 						for(int column = pattern_buffer_start; column < pattern_buffer_end; ++column) { | ||||||
|  | 							pattern_buffer_[column] = ram_[pattern_generator_table_address_ + (pattern_names_[column] << 3) + (row_ & 7)]; | ||||||
|  | 						} | ||||||
|  | 					} | ||||||
|  | 				break; | ||||||
|  |  | ||||||
|  | 				case LineMode::Character: | ||||||
|  | 					// Four access windows: no collection. | ||||||
|  | 					if(access_pointer_ < 5) | ||||||
|  | 						access_pointer_ = std::min(5, access_slot); | ||||||
|  |  | ||||||
|  | 					// Then ten access windows are filled with collection of sprite 3 and 4 details. | ||||||
|  | 					if(access_pointer_ >= 5 && access_pointer_ < 15) { | ||||||
|  | 						int end = std::min(15, access_slot); | ||||||
|  | 						get_sprite_contents(access_pointer_ - 5 + 14, end - access_pointer_, sprite_row - 1); | ||||||
|  | 						access_pointer_ = std::min(15, access_slot); | ||||||
|  | 					} | ||||||
|  |  | ||||||
|  | 					// Four more access windows: no collection. | ||||||
|  | 					if(access_pointer_ >= 15 && access_pointer_ < 19) { | ||||||
|  | 						access_pointer_ = std::min(19, access_slot); | ||||||
|  |  | ||||||
|  | 						// Start new sprite set if this is location 19. | ||||||
|  | 						if(access_pointer_ == 19) { | ||||||
|  | 							active_sprite_set_ ^= 1; | ||||||
|  | 							sprite_sets_[active_sprite_set_].active_sprite_slot = 0; | ||||||
|  | 							sprites_stopped_ = false; | ||||||
|  | 						} | ||||||
|  | 					} | ||||||
|  |  | ||||||
|  | 					// Then eight access windows fetch the y position for the first eight sprites. | ||||||
|  | 					while(access_pointer_ < 27 && access_pointer_ < access_slot) { | ||||||
|  | 						test_sprite(access_pointer_ - 19, sprite_row); | ||||||
|  | 						access_pointer_++; | ||||||
|  | 					} | ||||||
|  |  | ||||||
|  | 					// The next 128 access slots are video and sprite collection interleaved. | ||||||
|  | 					if(access_pointer_ >= 27 && access_pointer_ < 155) { | ||||||
|  | 						int end = std::min(155, access_slot); | ||||||
|  |  | ||||||
|  | 						int row_base = pattern_name_address_; | ||||||
|  | 						int pattern_base = pattern_generator_table_address_; | ||||||
|  | 						int colour_base = colour_table_address_; | ||||||
|  | 						if(screen_mode_ == 1) { | ||||||
|  | 							pattern_base &= 0x2000 | ((row_ & 0xc0) << 5); | ||||||
|  | 							colour_base &= 0x2000 | ((row_ & 0xc0) << 5); | ||||||
|  | 						} | ||||||
|  | 						row_base += (row_ << 2)&~31; | ||||||
|  |  | ||||||
|  | 						// Pattern names are collected every fourth window starting from window 27. | ||||||
|  | 						const int pattern_names_start = (access_pointer_ - 27 + 3) >> 2; | ||||||
|  | 						const int pattern_names_end = (end - 27 + 3) >> 2; | ||||||
|  | 						std::memcpy(&pattern_names_[pattern_names_start], &ram_[row_base + pattern_names_start], static_cast<size_t>(pattern_names_end - pattern_names_start)); | ||||||
|  |  | ||||||
|  | 						// Colours are collected ever fourth window starting from window 29. | ||||||
|  | 						const int colours_start = (access_pointer_ - 29 + 3) >> 2; | ||||||
|  | 						const int colours_end = (end - 29 + 3) >> 2; | ||||||
|  | 						if(screen_mode_ != 1) { | ||||||
|  | 							for(int column = colours_start; column < colours_end; ++column) { | ||||||
|  | 								colour_buffer_[column] = ram_[colour_base + (pattern_names_[column] >> 3)]; | ||||||
|  | 							} | ||||||
|  | 						} else { | ||||||
|  | 							for(int column = colours_start; column < colours_end; ++column) { | ||||||
|  | 								colour_buffer_[column] = ram_[colour_base + (pattern_names_[column] << 3) + (row_ & 7)]; | ||||||
|  | 							} | ||||||
|  | 						} | ||||||
|  |  | ||||||
|  | 						// Patterns are collected ever fourth window starting from window 30. | ||||||
|  | 						const int pattern_buffer_start = (access_pointer_ - 30 + 3) >> 2; | ||||||
|  | 						const int pattern_buffer_end = (end - 30 + 3) >> 2; | ||||||
|  | 						for(int column = pattern_buffer_start; column < pattern_buffer_end; ++column) { | ||||||
|  | 							pattern_buffer_[column] = ram_[pattern_base + (pattern_names_[column] << 3) + (row_ & 7)]; | ||||||
|  | 						} | ||||||
|  |  | ||||||
|  | 						// Sprite slots occur in three quarters of ever fourth window starting from window 28. | ||||||
|  | 						const int sprite_start = (access_pointer_ - 28 + 3) >> 2; | ||||||
|  | 						const int sprite_end = (end - 28 + 3) >> 2; | ||||||
|  | 						for(int column = sprite_start; column < sprite_end; ++column) { | ||||||
|  | 							if(column&3) { | ||||||
|  | 								test_sprite(7 + column - (column >> 2), sprite_row); | ||||||
|  | 							} | ||||||
|  | 						} | ||||||
|  |  | ||||||
|  | 						access_pointer_ = std::min(155, access_slot); | ||||||
|  | 					} | ||||||
|  |  | ||||||
|  | 					// Two access windows: no collection. | ||||||
|  | 					if(access_pointer_ < 157) | ||||||
|  | 						access_pointer_ = std::min(157, access_slot); | ||||||
|  |  | ||||||
|  | 					// Fourteen access windows: collect initial sprite information. | ||||||
|  | 					if(access_pointer_ >= 157 && access_pointer_ < 171) { | ||||||
|  | 						int end = std::min(171, access_slot); | ||||||
|  | 						get_sprite_contents(access_pointer_ - 157, end - access_pointer_, sprite_row); | ||||||
|  | 						access_pointer_ = std::min(171, access_slot); | ||||||
|  | 					} | ||||||
|  | 				break; | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 		// -------------------------- | ||||||
|  | 		// End video memory accesses. | ||||||
|  | 		// -------------------------- | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  | 		// -------------------- | ||||||
|  | 		// Output video stream. | ||||||
|  | 		// -------------------- | ||||||
|  | 		if(row_	< 192 && !blank_screen_) { | ||||||
|  | 			// ---------------------- | ||||||
|  | 			// Output horizontal sync | ||||||
|  | 			// ---------------------- | ||||||
|  | 			if(!output_column_ && column_ >= 26) { | ||||||
|  | 				crt_->output_sync(13 * 4); | ||||||
|  | 				crt_->output_default_colour_burst(13 * 4); | ||||||
|  | 				output_column_ = 26; | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			// ------------------- | ||||||
|  | 			// Output left border. | ||||||
|  | 			// ------------------- | ||||||
|  | 			if(output_column_ >= 26) { | ||||||
|  | 				int pixels_end = std::min(first_pixel_column_, column_); | ||||||
|  | 				if(output_column_ < pixels_end) { | ||||||
|  | 					output_border(pixels_end - output_column_); | ||||||
|  | 					output_column_ = pixels_end; | ||||||
|  |  | ||||||
|  | 					// Grab a pointer for drawing pixels to, if the moment has arrived. | ||||||
|  | 					if(pixels_end == first_pixel_column_) { | ||||||
|  | 						pixel_base_ = pixel_target_ = reinterpret_cast<uint32_t *>(crt_->allocate_write_area(static_cast<unsigned int>(first_right_border_column_ - first_pixel_column_))); | ||||||
|  | 					} | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			// -------------- | ||||||
|  | 			// Output pixels. | ||||||
|  | 			// -------------- | ||||||
|  | 			if(output_column_ >= first_pixel_column_) { | ||||||
|  | 				int pixels_end = std::min(first_right_border_column_, column_); | ||||||
|  |  | ||||||
|  | 				if(output_column_ < pixels_end) { | ||||||
|  | 					switch(line_mode_) { | ||||||
|  | 						default: break; | ||||||
|  |  | ||||||
|  | 						case LineMode::Text: { | ||||||
|  | 							const uint32_t colours[2] = { palette[background_colour_], palette[text_colour_] }; | ||||||
|  |  | ||||||
|  | 							const int shift = (output_column_ - first_pixel_column_) % 6; | ||||||
|  | 							int byte_column = (output_column_ - first_pixel_column_) / 6; | ||||||
|  | 							int pattern = pattern_buffer_[byte_column] << shift; | ||||||
|  | 							int pixels_left = pixels_end - output_column_; | ||||||
|  | 							int length = std::min(pixels_left, 6 - shift); | ||||||
|  | 							while(true) { | ||||||
|  | 								pixels_left -= length; | ||||||
|  | 								while(length--) { | ||||||
|  | 									*pixel_target_ = colours[(pattern >> 7)&0x01]; | ||||||
|  | 									pixel_target_++; | ||||||
|  | 									pattern <<= 1; | ||||||
|  | 								} | ||||||
|  |  | ||||||
|  | 								if(!pixels_left) break; | ||||||
|  | 								length = std::min(6, pixels_left); | ||||||
|  | 								byte_column++; | ||||||
|  | 								pattern = pattern_buffer_[byte_column]; | ||||||
|  | 							} | ||||||
|  | 							output_column_ = pixels_end; | ||||||
|  | 						} break; | ||||||
|  |  | ||||||
|  | 						case LineMode::Character: { | ||||||
|  | 							// If this is the start of the visible area, seed sprite shifter positions. | ||||||
|  | 							SpriteSet &sprite_set = sprite_sets_[active_sprite_set_ ^ 1]; | ||||||
|  | 							if(line_mode_ == LineMode::Character && output_column_ == first_pixel_column_) { | ||||||
|  | 								int c = sprite_set.active_sprite_slot; | ||||||
|  | 								while(c--) { | ||||||
|  | 									SpriteSet::ActiveSprite &sprite = sprite_set.active_sprites[c]; | ||||||
|  | 									sprite.shift_position = -sprite.info[1]; | ||||||
|  | 									if(sprite.info[3] & 0x80) { | ||||||
|  | 										sprite.shift_position += 32; | ||||||
|  | 										if(sprite.shift_position > 0 && !sprites_magnified_) | ||||||
|  | 											sprite.shift_position *= 2; | ||||||
|  | 									} | ||||||
|  | 								} | ||||||
|  | 							} | ||||||
|  |  | ||||||
|  | 							// Paint the background tiles. | ||||||
|  | 							const int shift = (output_column_ - first_pixel_column_) & 7; | ||||||
|  | 							int byte_column = (output_column_ - first_pixel_column_) >> 3; | ||||||
|  |  | ||||||
|  | 							const int pixels_left = pixels_end - output_column_; | ||||||
|  | 							int length = std::min(pixels_left, 8 - shift); | ||||||
|  |  | ||||||
|  | 							int pattern = pattern_buffer_[byte_column] << shift; | ||||||
|  | 							uint8_t colour = colour_buffer_[byte_column]; | ||||||
|  | 							uint32_t colours[2] = { | ||||||
|  | 								palette[(colour & 15) ? (colour & 15) : background_colour_], | ||||||
|  | 								palette[(colour >> 4) ? (colour >> 4) : background_colour_] | ||||||
|  | 							}; | ||||||
|  |  | ||||||
|  | 							int background_pixels_left = pixels_left; | ||||||
|  | 							while(true) { | ||||||
|  | 								background_pixels_left -= length; | ||||||
|  | 								while(length--) { | ||||||
|  | 									*pixel_target_ = colours[(pattern >> 7)&0x01]; | ||||||
|  | 									pixel_target_++; | ||||||
|  | 									pattern <<= 1; | ||||||
|  | 								} | ||||||
|  |  | ||||||
|  | 								if(!background_pixels_left) break; | ||||||
|  | 								length = std::min(8, background_pixels_left); | ||||||
|  | 								byte_column++; | ||||||
|  |  | ||||||
|  | 								pattern = pattern_buffer_[byte_column]; | ||||||
|  | 								colour = colour_buffer_[byte_column]; | ||||||
|  | 								colours[0] = palette[(colour & 15) ? (colour & 15) : background_colour_]; | ||||||
|  | 								colours[1] = palette[(colour >> 4) ? (colour >> 4) : background_colour_]; | ||||||
|  | 							} | ||||||
|  |  | ||||||
|  | 							// Paint sprites and check for collisions. | ||||||
|  | 							if(sprite_set.active_sprite_slot) { | ||||||
|  | 								int sprite_pixels_left = pixels_left; | ||||||
|  | 								const int shift_advance = sprites_magnified_ ? 1 : 2; | ||||||
|  |  | ||||||
|  | 								const uint32_t sprite_colour_selection_masks[2] = {0x00000000, 0xffffffff}; | ||||||
|  | 								const int colour_masks[16] = {0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}; | ||||||
|  |  | ||||||
|  | 								while(sprite_pixels_left--) { | ||||||
|  | 									uint32_t sprite_colour = pixel_base_[output_column_ - first_pixel_column_]; | ||||||
|  | 									int sprite_mask = 0; | ||||||
|  | 									int c = sprite_set.active_sprite_slot; | ||||||
|  | 									while(c--) { | ||||||
|  | 										SpriteSet::ActiveSprite &sprite = sprite_set.active_sprites[c]; | ||||||
|  |  | ||||||
|  | 										if(sprite.shift_position < 0) { | ||||||
|  | 											sprite.shift_position++; | ||||||
|  | 											continue; | ||||||
|  | 										} else if(sprite.shift_position < 32) { | ||||||
|  | 											int mask = sprite.image[sprite.shift_position >> 4] << ((sprite.shift_position&15) >> 1); | ||||||
|  | 											mask = (mask >> 7) & 1; | ||||||
|  | 											status_ |= (mask & sprite_mask) << StatusSpriteCollisionShift; | ||||||
|  | 											sprite_mask |= mask; | ||||||
|  | 											sprite.shift_position += shift_advance; | ||||||
|  |  | ||||||
|  | 											mask &= colour_masks[sprite.info[3]&15]; | ||||||
|  | 											sprite_colour = (sprite_colour & sprite_colour_selection_masks[mask^1]) | (palette[sprite.info[3]&15] & sprite_colour_selection_masks[mask]); | ||||||
|  | 										} | ||||||
|  | 									} | ||||||
|  |  | ||||||
|  | 									pixel_base_[output_column_ - first_pixel_column_] = sprite_colour; | ||||||
|  | 									output_column_++; | ||||||
|  | 								} | ||||||
|  | 							} | ||||||
|  |  | ||||||
|  | 							output_column_ = pixels_end; | ||||||
|  | 						} break; | ||||||
|  | 					} | ||||||
|  |  | ||||||
|  | 					if(output_column_ == first_right_border_column_) { | ||||||
|  | 						crt_->output_data(static_cast<unsigned int>(first_right_border_column_ - first_pixel_column_) * 4, 4); | ||||||
|  | 						pixel_target_ = nullptr; | ||||||
|  | 					} | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			// -------------------- | ||||||
|  | 			// Output right border. | ||||||
|  | 			// -------------------- | ||||||
|  | 			if(output_column_ >= first_right_border_column_) { | ||||||
|  | 				output_border(column_ - output_column_); | ||||||
|  | 				output_column_ = column_; | ||||||
|  | 			} | ||||||
|  | 		} else if(row_ >= first_vsync_line_ && row_ < first_vsync_line_+3) { | ||||||
|  | 			// Vertical sync. | ||||||
|  | 			if(column_ == 342) { | ||||||
|  | 				crt_->output_sync(342 * 4); | ||||||
|  | 			} | ||||||
|  | 		} else { | ||||||
|  | 			// Blank. | ||||||
|  | 			if(!output_column_ && column_ >= 26) { | ||||||
|  | 				crt_->output_sync(13 * 4); | ||||||
|  | 				crt_->output_default_colour_burst(13 * 4); | ||||||
|  | 				output_column_ = 26; | ||||||
|  | 			} | ||||||
|  | 			if(output_column_ >= 26) { | ||||||
|  | 				output_border(column_ - output_column_); | ||||||
|  | 				output_column_ = column_; | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 		// ----------------- | ||||||
|  | 		// End video stream. | ||||||
|  | 		// ----------------- | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  | 		// ----------------------------------- | ||||||
|  | 		// Prepare for next line, potentially. | ||||||
|  | 		// ----------------------------------- | ||||||
|  | 		if(column_ == 342) { | ||||||
|  | 			access_pointer_ = column_ = output_column_ = 0; | ||||||
|  | 			row_ = (row_ + 1) % frame_lines_; | ||||||
|  | 			if(row_ == 192) status_ |= StatusInterrupt; | ||||||
|  |  | ||||||
|  | 			screen_mode_ = next_screen_mode_; | ||||||
|  | 			blank_screen_ = next_blank_screen_; | ||||||
|  | 			switch(screen_mode_) { | ||||||
|  | 				case 2: | ||||||
|  | 					line_mode_ = LineMode::Text; | ||||||
|  | 					first_pixel_column_ = 69; | ||||||
|  | 					first_right_border_column_ = 309; | ||||||
|  | 				break; | ||||||
|  | 				default: | ||||||
|  | 					line_mode_ = LineMode::Character; | ||||||
|  | 					first_pixel_column_ = 63; | ||||||
|  | 					first_right_border_column_ = 319; | ||||||
|  | 				break; | ||||||
|  | 			} | ||||||
|  | 			if(blank_screen_ || (row_ >= 192 && row_ != frame_lines_-1)) line_mode_ = LineMode::Refresh; | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void TMS9918Base::output_border(int cycles) { | ||||||
|  | 	pixel_target_ = reinterpret_cast<uint32_t *>(crt_->allocate_write_area(1)); | ||||||
|  | 	if(pixel_target_) *pixel_target_ = palette[background_colour_]; | ||||||
|  | 	crt_->output_level(static_cast<unsigned int>(cycles) * 4); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void TMS9918::set_register(int address, uint8_t value) { | ||||||
|  | 	// Writes to address 0 are writes to the video RAM. Store | ||||||
|  | 	// the value and return. | ||||||
|  | 	if(!(address & 1)) { | ||||||
|  | 		write_phase_ = false; | ||||||
|  |  | ||||||
|  | 		// Enqueue the write to occur at the next available slot. | ||||||
|  | 		read_ahead_buffer_ = value; | ||||||
|  | 		queued_access_ = MemoryAccess::Write; | ||||||
|  |  | ||||||
|  | 		return; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// Writes to address 1 are performed in pairs; if this is the | ||||||
|  | 	// low byte of a value, store it and wait for the high byte. | ||||||
|  | 	if(!write_phase_) { | ||||||
|  | 		low_write_ = value; | ||||||
|  | 		write_phase_ = true; | ||||||
|  | 		return; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	write_phase_ = false; | ||||||
|  | 	if(value & 0x80) { | ||||||
|  | 		// This is a write to a register. | ||||||
|  | 		switch(value & 7) { | ||||||
|  | 			case 0: | ||||||
|  | 				next_screen_mode_ = (next_screen_mode_ & 6) | ((low_write_ & 2) >> 1); | ||||||
|  | 			break; | ||||||
|  |  | ||||||
|  | 			case 1: | ||||||
|  | 				next_blank_screen_ = !(low_write_ & 0x40); | ||||||
|  | 				generate_interrupts_ = !!(low_write_ & 0x20); | ||||||
|  | 				next_screen_mode_ = (next_screen_mode_ & 1) | ((low_write_ & 0x18) >> 3); | ||||||
|  | 				sprites_16x16_ = !!(low_write_ & 0x02); | ||||||
|  | 				sprites_magnified_ = !!(low_write_ & 0x01); | ||||||
|  |  | ||||||
|  | 				sprite_height_ = 8; | ||||||
|  | 				if(sprites_16x16_) sprite_height_ <<= 1; | ||||||
|  | 				if(sprites_magnified_) sprite_height_ <<= 1; | ||||||
|  | 			break; | ||||||
|  |  | ||||||
|  | 			case 2: | ||||||
|  | 				pattern_name_address_ = static_cast<uint16_t>((low_write_ & 0xf) << 10); | ||||||
|  | 			break; | ||||||
|  |  | ||||||
|  | 			case 3: | ||||||
|  | 				colour_table_address_ = static_cast<uint16_t>(low_write_ << 6); | ||||||
|  | 			break; | ||||||
|  |  | ||||||
|  | 			case 4: | ||||||
|  | 				pattern_generator_table_address_ = static_cast<uint16_t>((low_write_ & 0x07) << 11); | ||||||
|  | 			break; | ||||||
|  |  | ||||||
|  | 			case 5: | ||||||
|  | 				sprite_attribute_table_address_ = static_cast<uint16_t>((low_write_ & 0x7f) << 7); | ||||||
|  | 			break; | ||||||
|  |  | ||||||
|  | 			case 6: | ||||||
|  | 				sprite_generator_table_address_ = static_cast<uint16_t>((low_write_ & 0x07) << 11); | ||||||
|  | 			break; | ||||||
|  |  | ||||||
|  | 			case 7: | ||||||
|  | 				text_colour_ = low_write_ >> 4; | ||||||
|  | 				background_colour_ = low_write_ & 0xf; | ||||||
|  | 			break; | ||||||
|  | 		} | ||||||
|  | 	} else { | ||||||
|  | 		// This is a write to the RAM pointer. | ||||||
|  | 		ram_pointer_ = static_cast<uint16_t>(low_write_ | (value << 8)); | ||||||
|  | 		if(!(value & 0x40)) { | ||||||
|  | 			// Officially a 'read' set, so perform lookahead. | ||||||
|  | 			queued_access_ = MemoryAccess::Read; | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | uint8_t TMS9918::get_register(int address) { | ||||||
|  | 	write_phase_ = false; | ||||||
|  |  | ||||||
|  | 	// Reads from address 0 read video RAM, via the read-ahead buffer. | ||||||
|  | 	if(!(address & 1)) { | ||||||
|  | 		// Enqueue the write to occur at the next available slot. | ||||||
|  | 		uint8_t result = read_ahead_buffer_; | ||||||
|  | 		queued_access_ = MemoryAccess::Read; | ||||||
|  | 		return result; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// Reads from address 1 get the status register. | ||||||
|  | 	uint8_t result = status_; | ||||||
|  | 	status_ &= ~(StatusInterrupt | StatusFifthSprite | StatusSpriteCollision); | ||||||
|  | 	return result; | ||||||
|  | } | ||||||
|  |  | ||||||
|  |  HalfCycles TMS9918::get_time_until_interrupt() { | ||||||
|  | 	if(!generate_interrupts_) return HalfCycles(-1); | ||||||
|  | 	if(get_interrupt_line()) return HalfCycles(0); | ||||||
|  |  | ||||||
|  | 	const int half_cycles_per_frame = frame_lines_ * 228 * 2; | ||||||
|  | 	int half_cycles_remaining = (192 * 228 * 2 + half_cycles_per_frame - half_cycles_into_frame_.as_int()) % half_cycles_per_frame; | ||||||
|  | 	return HalfCycles(half_cycles_remaining ? half_cycles_remaining : half_cycles_per_frame); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | bool TMS9918::get_interrupt_line() { | ||||||
|  | 	return (status_ & StatusInterrupt) && generate_interrupts_; | ||||||
|  | } | ||||||
							
								
								
									
										86
									
								
								Components/9918/9918.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										86
									
								
								Components/9918/9918.hpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,86 @@ | |||||||
|  | // | ||||||
|  | //  9918.hpp | ||||||
|  | //  Clock Signal | ||||||
|  | // | ||||||
|  | //  Created by Thomas Harte on 25/11/2017. | ||||||
|  | //  Copyright © 2017 Thomas Harte. All rights reserved. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | #ifndef TMS9918_hpp | ||||||
|  | #define TMS9918_hpp | ||||||
|  |  | ||||||
|  | #include "../../Outputs/CRT/CRT.hpp" | ||||||
|  | #include "../../ClockReceiver/ClockReceiver.hpp" | ||||||
|  |  | ||||||
|  | #include "Implementation/9918Base.hpp" | ||||||
|  |  | ||||||
|  | #include <cstdint> | ||||||
|  |  | ||||||
|  | namespace TI { | ||||||
|  |  | ||||||
|  | /*! | ||||||
|  | 	Provides emulation of the TMS9918a, TMS9928 and TMS9929. Likely in the future to be the | ||||||
|  | 	vessel for emulation of sufficiently close derivatives, such as the Master System VDP. | ||||||
|  |  | ||||||
|  | 	The TMS9918 and descendants are video display generators that own their own RAM, making it | ||||||
|  | 	accessible through an implicitly-timed register interface, and (depending on model) can generate | ||||||
|  | 	PAL and NTSC component and composite video. | ||||||
|  |  | ||||||
|  | 	These chips have only one non-on-demand interaction with the outside world: an interrupt line. | ||||||
|  | 	See get_time_until_interrupt and get_interrupt_line for asynchronous operation options. | ||||||
|  | */ | ||||||
|  | class TMS9918: public TMS9918Base { | ||||||
|  | 	public: | ||||||
|  | 		enum Personality { | ||||||
|  | 			TMS9918A,	// includes the 9928 and 9929; set TV standard and output device as desired. | ||||||
|  | 		}; | ||||||
|  |  | ||||||
|  | 		/*! | ||||||
|  | 			Constructs an instance of the drive controller that behaves according to personality @c p. | ||||||
|  | 			@param p The type of controller to emulate. | ||||||
|  | 		*/ | ||||||
|  | 		TMS9918(Personality p); | ||||||
|  |  | ||||||
|  | 		enum TVStandard { | ||||||
|  | 			/*! i.e. 50Hz output at around 312.5 lines/field */ | ||||||
|  | 			PAL, | ||||||
|  | 			/*! i.e. 60Hz output at around 262.5 lines/field */ | ||||||
|  | 			NTSC | ||||||
|  | 		}; | ||||||
|  |  | ||||||
|  | 		/*! Sets the TV standard for this TMS, if that is hard-coded in hardware. */ | ||||||
|  | 		void set_tv_standard(TVStandard standard); | ||||||
|  |  | ||||||
|  | 		/*! Provides the CRT this TMS is connected to. */ | ||||||
|  | 		Outputs::CRT::CRT *get_crt(); | ||||||
|  |  | ||||||
|  | 		/*! | ||||||
|  | 			Runs the VCP for the number of cycles indicate; it is an implicit assumption of the code | ||||||
|  | 			that the input clock rate is 3579545 Hz — the NTSC colour clock rate. | ||||||
|  | 		*/ | ||||||
|  | 		void run_for(const HalfCycles cycles); | ||||||
|  |  | ||||||
|  | 		/*! Sets a register value. */ | ||||||
|  | 		void set_register(int address, uint8_t value); | ||||||
|  |  | ||||||
|  | 		/*! Gets a register value. */ | ||||||
|  | 		uint8_t get_register(int address); | ||||||
|  |  | ||||||
|  | 		/*! | ||||||
|  | 			Returns the amount of time until get_interrupt_line would next return true if | ||||||
|  | 			there are no interceding calls to set_register or get_register. | ||||||
|  |  | ||||||
|  | 			If get_interrupt_line is true now, returns zero. If get_interrupt_line would | ||||||
|  | 			never return true, returns -1. | ||||||
|  | 		*/ | ||||||
|  | 		HalfCycles get_time_until_interrupt(); | ||||||
|  |  | ||||||
|  | 		/*! | ||||||
|  | 			@returns @c true if the interrupt line is currently active; @c false otherwise. | ||||||
|  | 		*/ | ||||||
|  | 		bool get_interrupt_line(); | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | #endif /* TMS9918_hpp */ | ||||||
							
								
								
									
										101
									
								
								Components/9918/Implementation/9918Base.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										101
									
								
								Components/9918/Implementation/9918Base.hpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,101 @@ | |||||||
|  | // | ||||||
|  | //  9918Base.hpp | ||||||
|  | //  Clock Signal | ||||||
|  | // | ||||||
|  | //  Created by Thomas Harte on 14/12/2017. | ||||||
|  | //  Copyright © 2017 Thomas Harte. All rights reserved. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | #ifndef TMS9918Base_hpp | ||||||
|  | #define TMS9918Base_hpp | ||||||
|  |  | ||||||
|  | #include "../../../Outputs/CRT/CRT.hpp" | ||||||
|  | #include "../../../ClockReceiver/ClockReceiver.hpp" | ||||||
|  |  | ||||||
|  | #include <cstdint> | ||||||
|  | #include <memory> | ||||||
|  |  | ||||||
|  | namespace TI { | ||||||
|  |  | ||||||
|  | class TMS9918Base { | ||||||
|  | 	protected: | ||||||
|  | 		TMS9918Base(); | ||||||
|  |  | ||||||
|  | 		std::unique_ptr<Outputs::CRT::CRT> crt_; | ||||||
|  |  | ||||||
|  | 		uint8_t ram_[16384]; | ||||||
|  |  | ||||||
|  | 		uint16_t ram_pointer_ = 0; | ||||||
|  | 		uint8_t read_ahead_buffer_ = 0; | ||||||
|  | 		enum class MemoryAccess { | ||||||
|  | 			Read, Write, None | ||||||
|  | 		} queued_access_ = MemoryAccess::None; | ||||||
|  |  | ||||||
|  | 		uint8_t status_ = 0; | ||||||
|  |  | ||||||
|  | 		bool write_phase_ = false; | ||||||
|  | 		uint8_t low_write_ = 0; | ||||||
|  |  | ||||||
|  | 		// The various register flags. | ||||||
|  | 		int next_screen_mode_ = 0, screen_mode_ = 0; | ||||||
|  | 		bool next_blank_screen_ = true, blank_screen_ = true; | ||||||
|  | 		bool sprites_16x16_ = false; | ||||||
|  | 		bool sprites_magnified_ = false; | ||||||
|  | 		bool generate_interrupts_ = false; | ||||||
|  | 		int sprite_height_ = 8; | ||||||
|  | 		uint16_t pattern_name_address_ = 0; | ||||||
|  | 		uint16_t colour_table_address_ = 0; | ||||||
|  | 		uint16_t pattern_generator_table_address_ = 0; | ||||||
|  | 		uint16_t sprite_attribute_table_address_ = 0; | ||||||
|  | 		uint16_t sprite_generator_table_address_ = 0; | ||||||
|  |  | ||||||
|  | 		uint8_t text_colour_ = 0; | ||||||
|  | 		uint8_t background_colour_ = 0; | ||||||
|  |  | ||||||
|  | 		HalfCycles half_cycles_into_frame_; | ||||||
|  | 		int column_ = 0, row_ = 0, output_column_ = 0; | ||||||
|  | 		int cycles_error_ = 0; | ||||||
|  | 		uint32_t *pixel_target_ = nullptr, *pixel_base_ = nullptr; | ||||||
|  |  | ||||||
|  | 		void output_border(int cycles); | ||||||
|  |  | ||||||
|  | 		// Vertical timing details. | ||||||
|  | 		int frame_lines_ = 262; | ||||||
|  | 		int first_vsync_line_ = 227; | ||||||
|  |  | ||||||
|  | 		// Horizontal selections. | ||||||
|  | 		enum class LineMode { | ||||||
|  | 			Text = 0, | ||||||
|  | 			Character = 1, | ||||||
|  | 			Refresh = 2 | ||||||
|  | 		} line_mode_ = LineMode::Text; | ||||||
|  | 		int first_pixel_column_, first_right_border_column_; | ||||||
|  |  | ||||||
|  | 		uint8_t pattern_names_[40]; | ||||||
|  | 		uint8_t pattern_buffer_[40]; | ||||||
|  | 		uint8_t colour_buffer_[40]; | ||||||
|  |  | ||||||
|  | 		struct SpriteSet { | ||||||
|  | 			struct ActiveSprite { | ||||||
|  | 				int index = 0; | ||||||
|  | 				int row = 0; | ||||||
|  |  | ||||||
|  | 				uint8_t info[4]; | ||||||
|  | 				uint8_t image[2]; | ||||||
|  |  | ||||||
|  | 				int shift_position = 0; | ||||||
|  | 			} active_sprites[4]; | ||||||
|  | 			int active_sprite_slot = 0; | ||||||
|  | 		} sprite_sets_[2]; | ||||||
|  | 		int active_sprite_set_ = 0; | ||||||
|  | 		bool sprites_stopped_ = false; | ||||||
|  |  | ||||||
|  | 		int access_pointer_ = 0; | ||||||
|  |  | ||||||
|  | 		inline void test_sprite(int sprite_number, int screen_row); | ||||||
|  | 		inline void get_sprite_contents(int start, int cycles, int screen_row); | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #endif /* TMS9918Base_hpp */ | ||||||
| @@ -8,18 +8,11 @@ | |||||||
|  |  | ||||||
| #include "AY38910.hpp" | #include "AY38910.hpp" | ||||||
|  |  | ||||||
|  | #include <cmath> | ||||||
|  |  | ||||||
| using namespace GI::AY38910; | using namespace GI::AY38910; | ||||||
|  |  | ||||||
| AY38910::AY38910() : | AY38910::AY38910(Concurrency::DeferringAsyncTaskQueue &task_queue) : task_queue_(task_queue) { | ||||||
| 		selected_register_(0), |  | ||||||
| 		tone_counters_{0, 0, 0}, tone_periods_{0, 0, 0}, tone_outputs_{0, 0, 0}, |  | ||||||
| 		noise_shift_register_(0xffff), noise_period_(0), noise_counter_(0), noise_output_(0), |  | ||||||
| 		envelope_divider_(0), envelope_period_(0), envelope_position_(0), |  | ||||||
| 		master_divider_(0), |  | ||||||
| 		output_registers_{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, |  | ||||||
| 		port_handler_(nullptr) { |  | ||||||
| 	output_registers_[8] = output_registers_[9] = output_registers_[10] = 0; |  | ||||||
|  |  | ||||||
| 	// set up envelope lookup tables | 	// set up envelope lookup tables | ||||||
| 	for(int c = 0; c < 16; c++) { | 	for(int c = 0; c < 16; c++) { | ||||||
| 		for(int p = 0; p < 32; p++) { | 		for(int p = 0; p < 32; p++) { | ||||||
| @@ -67,17 +60,13 @@ AY38910::AY38910() : | |||||||
| 	float max_volume = 8192; | 	float max_volume = 8192; | ||||||
| 	float root_two = sqrtf(2.0f); | 	float root_two = sqrtf(2.0f); | ||||||
| 	for(int v = 0; v < 16; v++) { | 	for(int v = 0; v < 16; v++) { | ||||||
| 		volumes_[v] = (int)(max_volume / powf(root_two, (float)(v ^ 0xf))); | 		volumes_[v] = static_cast<int>(max_volume / powf(root_two, static_cast<float>(v ^ 0xf))); | ||||||
| 	} | 	} | ||||||
| 	volumes_[0] = 0; | 	volumes_[0] = 0; | ||||||
| } | } | ||||||
|  |  | ||||||
| void AY38910::set_clock_rate(double clock_rate) { | void AY38910::get_samples(std::size_t number_of_samples, int16_t *target) { | ||||||
| 	set_input_rate((float)clock_rate); | 	std::size_t c = 0; | ||||||
| } |  | ||||||
|  |  | ||||||
| void AY38910::get_samples(unsigned int number_of_samples, int16_t *target) { |  | ||||||
| 	unsigned int c = 0; |  | ||||||
| 	while((master_divider_&7) && c < number_of_samples) { | 	while((master_divider_&7) && c < number_of_samples) { | ||||||
| 		target[c] = output_volume_; | 		target[c] = output_volume_; | ||||||
| 		master_divider_++; | 		master_divider_++; | ||||||
| @@ -160,14 +149,14 @@ void AY38910::evaluate_output_volume() { | |||||||
| #undef channel_volume | #undef channel_volume | ||||||
|  |  | ||||||
| 	// Mix additively. | 	// Mix additively. | ||||||
| 	output_volume_ = (int16_t)( | 	output_volume_ = static_cast<int16_t>( | ||||||
| 		volumes_[volumes[0]] * channel_levels[0] + | 		volumes_[volumes[0]] * channel_levels[0] + | ||||||
| 		volumes_[volumes[1]] * channel_levels[1] + | 		volumes_[volumes[1]] * channel_levels[1] + | ||||||
| 		volumes_[volumes[2]] * channel_levels[2] | 		volumes_[volumes[2]] * channel_levels[2] | ||||||
| 	); | 	); | ||||||
| } | } | ||||||
|  |  | ||||||
| #pragma mark - Register manipulation | // MARK: - Register manipulation | ||||||
|  |  | ||||||
| void AY38910::select_register(uint8_t r) { | void AY38910::select_register(uint8_t r) { | ||||||
| 	selected_register_ = r; | 	selected_register_ = r; | ||||||
| @@ -178,7 +167,7 @@ void AY38910::set_register_value(uint8_t value) { | |||||||
| 	registers_[selected_register_] = value; | 	registers_[selected_register_] = value; | ||||||
| 	if(selected_register_ < 14) { | 	if(selected_register_ < 14) { | ||||||
| 		int selected_register = selected_register_; | 		int selected_register = selected_register_; | ||||||
| 		enqueue([=] () { | 		task_queue_.defer([=] () { | ||||||
| 			uint8_t masked_value = value; | 			uint8_t masked_value = value; | ||||||
| 			switch(selected_register) { | 			switch(selected_register) { | ||||||
| 				case 0: case 2: case 4: | 				case 0: case 2: case 4: | ||||||
| @@ -186,7 +175,7 @@ void AY38910::set_register_value(uint8_t value) { | |||||||
| 					int channel = selected_register >> 1; | 					int channel = selected_register >> 1; | ||||||
|  |  | ||||||
| 					if(selected_register & 1) | 					if(selected_register & 1) | ||||||
| 						tone_periods_[channel] = (tone_periods_[channel] & 0xff) | (uint16_t)((value&0xf) << 8); | 						tone_periods_[channel] = (tone_periods_[channel] & 0xff) | static_cast<uint16_t>((value&0xf) << 8); | ||||||
| 					else | 					else | ||||||
| 						tone_periods_[channel] = (tone_periods_[channel] & ~0xff) | value; | 						tone_periods_[channel] = (tone_periods_[channel] & ~0xff) | value; | ||||||
| 				} | 				} | ||||||
| @@ -201,7 +190,7 @@ void AY38910::set_register_value(uint8_t value) { | |||||||
| 				break; | 				break; | ||||||
|  |  | ||||||
| 				case 12: | 				case 12: | ||||||
| 					envelope_period_ = (envelope_period_ & 0xff) | (int)(value << 8); | 					envelope_period_ = (envelope_period_ & 0xff) | static_cast<int>(value << 8); | ||||||
| 				break; | 				break; | ||||||
|  |  | ||||||
| 				case 13: | 				case 13: | ||||||
| @@ -233,13 +222,13 @@ uint8_t AY38910::get_register_value() { | |||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| #pragma mark - Port handling | // MARK: - Port handling | ||||||
|  |  | ||||||
| uint8_t AY38910::get_port_output(bool port_b) { | uint8_t AY38910::get_port_output(bool port_b) { | ||||||
| 	return registers_[port_b ? 15 : 14]; | 	return registers_[port_b ? 15 : 14]; | ||||||
| } | } | ||||||
|  |  | ||||||
| #pragma mark - Bus handling | // MARK: - Bus handling | ||||||
|  |  | ||||||
| void AY38910::set_port_handler(PortHandler *handler) { | void AY38910::set_port_handler(PortHandler *handler) { | ||||||
| 	port_handler_ = handler; | 	port_handler_ = handler; | ||||||
| @@ -262,15 +251,15 @@ uint8_t AY38910::get_data_output() { | |||||||
| } | } | ||||||
|  |  | ||||||
| void AY38910::set_control_lines(ControlLines control_lines) { | void AY38910::set_control_lines(ControlLines control_lines) { | ||||||
| 	switch((int)control_lines) { | 	switch(static_cast<int>(control_lines)) { | ||||||
| 		default:					control_state_ = Inactive;		break; | 		default:					control_state_ = Inactive;		break; | ||||||
|  |  | ||||||
| 		case (int)(BDIR | BC2 | BC1): | 		case static_cast<int>(BDIR | BC2 | BC1): | ||||||
| 		case BDIR: | 		case BDIR: | ||||||
| 		case BC1:					control_state_ = LatchAddress;	break; | 		case BC1:					control_state_ = LatchAddress;	break; | ||||||
|  |  | ||||||
| 		case (int)(BC2 | BC1):		control_state_ = Read;			break; | 		case static_cast<int>(BC2 | BC1):		control_state_ = Read;			break; | ||||||
| 		case (int)(BDIR | BC2):		control_state_ = Write;			break; | 		case static_cast<int>(BDIR | BC2):		control_state_ = Write;			break; | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	update_bus(); | 	update_bus(); | ||||||
|   | |||||||
| @@ -9,7 +9,8 @@ | |||||||
| #ifndef AY_3_8910_hpp | #ifndef AY_3_8910_hpp | ||||||
| #define AY_3_8910_hpp | #define AY_3_8910_hpp | ||||||
|  |  | ||||||
| #include "../../Outputs/Speaker.hpp" | #include "../../Outputs/Speaker/Implementation/SampleSource.hpp" | ||||||
|  | #include "../../Concurrency/AsyncTaskQueue.hpp" | ||||||
|  |  | ||||||
| namespace GI { | namespace GI { | ||||||
| namespace AY38910 { | namespace AY38910 { | ||||||
| @@ -55,13 +56,10 @@ enum ControlLines { | |||||||
| 	noise generator and a volume envelope generator, which also provides two bidirectional | 	noise generator and a volume envelope generator, which also provides two bidirectional | ||||||
| 	interface ports. | 	interface ports. | ||||||
| */ | */ | ||||||
| class AY38910: public ::Outputs::Filter<AY38910> { | class AY38910: public ::Outputs::Speaker::SampleSource { | ||||||
| 	public: | 	public: | ||||||
| 		/// Creates a new AY38910. | 		/// Creates a new AY38910. | ||||||
| 		AY38910(); | 		AY38910(Concurrency::DeferringAsyncTaskQueue &task_queue); | ||||||
|  |  | ||||||
| 		/// Sets the clock rate at which this AY38910 will be run. |  | ||||||
| 		void set_clock_rate(double clock_rate); |  | ||||||
|  |  | ||||||
| 		/// Sets the value the AY would read from its data lines if it were not outputting. | 		/// Sets the value the AY would read from its data lines if it were not outputting. | ||||||
| 		void set_data_input(uint8_t r); | 		void set_data_input(uint8_t r); | ||||||
| @@ -86,27 +84,30 @@ class AY38910: public ::Outputs::Filter<AY38910> { | |||||||
| 		void set_port_handler(PortHandler *); | 		void set_port_handler(PortHandler *); | ||||||
|  |  | ||||||
| 		// to satisfy ::Outputs::Speaker (included via ::Outputs::Filter; not for public consumption | 		// to satisfy ::Outputs::Speaker (included via ::Outputs::Filter; not for public consumption | ||||||
| 		void get_samples(unsigned int number_of_samples, int16_t *target); | 		void get_samples(std::size_t number_of_samples, int16_t *target); | ||||||
|  |  | ||||||
| 	private: | 	private: | ||||||
| 		int selected_register_; | 		Concurrency::DeferringAsyncTaskQueue &task_queue_; | ||||||
| 		uint8_t registers_[16], output_registers_[16]; |  | ||||||
|  | 		int selected_register_ = 0; | ||||||
|  | 		uint8_t registers_[16]; | ||||||
|  | 		uint8_t output_registers_[16] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; | ||||||
| 		uint8_t port_inputs_[2]; | 		uint8_t port_inputs_[2]; | ||||||
|  |  | ||||||
| 		int master_divider_; | 		int master_divider_ = 0; | ||||||
|  |  | ||||||
| 		int tone_periods_[3]; | 		int tone_periods_[3] = {0, 0, 0}; | ||||||
| 		int tone_counters_[3]; | 		int tone_counters_[3] = {0, 0, 0}; | ||||||
| 		int tone_outputs_[3]; | 		int tone_outputs_[3] = {0, 0, 0}; | ||||||
|  |  | ||||||
| 		int noise_period_; | 		int noise_period_ = 0; | ||||||
| 		int noise_counter_; | 		int noise_counter_ = 0; | ||||||
| 		int noise_shift_register_; | 		int noise_shift_register_ = 0xffff; | ||||||
| 		int noise_output_; | 		int noise_output_ = 0; | ||||||
|  |  | ||||||
| 		int envelope_period_; | 		int envelope_period_ = 0; | ||||||
| 		int envelope_divider_; | 		int envelope_divider_ = 0; | ||||||
| 		int envelope_position_; | 		int envelope_position_ = 0; | ||||||
| 		int envelope_shapes_[16][32]; | 		int envelope_shapes_[16][32]; | ||||||
| 		int envelope_overflow_masks_[16]; | 		int envelope_overflow_masks_[16]; | ||||||
|  |  | ||||||
| @@ -129,7 +130,7 @@ class AY38910: public ::Outputs::Filter<AY38910> { | |||||||
| 		inline void evaluate_output_volume(); | 		inline void evaluate_output_volume(); | ||||||
|  |  | ||||||
| 		inline void update_bus(); | 		inline void update_bus(); | ||||||
| 		PortHandler *port_handler_; | 		PortHandler *port_handler_ = nullptr; | ||||||
| }; | }; | ||||||
|  |  | ||||||
| } | } | ||||||
|   | |||||||
							
								
								
									
										107
									
								
								Components/KonamiSCC/KonamiSCC.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										107
									
								
								Components/KonamiSCC/KonamiSCC.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,107 @@ | |||||||
|  | // | ||||||
|  | //  KonamiSCC.cpp | ||||||
|  | //  Clock Signal | ||||||
|  | // | ||||||
|  | //  Created by Thomas Harte on 06/01/2018. | ||||||
|  | //  Copyright © 2018 Thomas Harte. All rights reserved. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | #include "KonamiSCC.hpp" | ||||||
|  |  | ||||||
|  | #include <cstring> | ||||||
|  |  | ||||||
|  | using namespace Konami; | ||||||
|  |  | ||||||
|  | SCC::SCC(Concurrency::DeferringAsyncTaskQueue &task_queue) : | ||||||
|  | 	task_queue_(task_queue) {} | ||||||
|  |  | ||||||
|  | bool SCC::is_silent() { | ||||||
|  | 	return !(channel_enable_ & 0x1f); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void SCC::get_samples(std::size_t number_of_samples, std::int16_t *target) { | ||||||
|  | 	if(is_silent()) { | ||||||
|  | 		std::memset(target, 0, sizeof(std::int16_t) * number_of_samples); | ||||||
|  | 		return; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	std::size_t c = 0; | ||||||
|  | 	while((master_divider_&7) && c < number_of_samples) { | ||||||
|  | 		target[c] = output_volume_; | ||||||
|  | 		master_divider_++; | ||||||
|  | 		c++; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	while(c < number_of_samples) { | ||||||
|  | 		for(int channel = 0; channel < 5; ++channel) { | ||||||
|  | 			if(channels_[channel].tone_counter) channels_[channel].tone_counter--; | ||||||
|  | 			else { | ||||||
|  | 				channels_[channel].offset = (channels_[channel].offset + 1) & 0x1f; | ||||||
|  | 				channels_[channel].tone_counter = channels_[channel].period; | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		evaluate_output_volume(); | ||||||
|  |  | ||||||
|  | 		for(int ic = 0; ic < 8 && c < number_of_samples; ++ic) { | ||||||
|  | 			target[c] = output_volume_; | ||||||
|  | 			c++; | ||||||
|  | 			master_divider_++; | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void SCC::write(uint16_t address, uint8_t value) { | ||||||
|  | 	address &= 0xff; | ||||||
|  | 	if(address < 0x80) ram_[address] = value; | ||||||
|  |  | ||||||
|  | 	task_queue_.defer([=] { | ||||||
|  | 		// Check for a write into waveform memory. | ||||||
|  | 		if(address < 0x80) { | ||||||
|  | 			waves_[address >> 5].samples[address & 0x1f] = value; | ||||||
|  | 		} else switch(address) { | ||||||
|  | 			default: break; | ||||||
|  |  | ||||||
|  | 			case 0x80: case 0x82: case 0x84: case 0x86: case 0x88: { | ||||||
|  | 				int channel = (address - 0x80) >> 1; | ||||||
|  | 				channels_[channel].period = (channels_[channel].period & ~0xff) | value; | ||||||
|  | 			} break; | ||||||
|  |  | ||||||
|  | 			case 0x81: case 0x83: case 0x85: case 0x87: case 0x89: { | ||||||
|  | 				int channel = (address - 0x80) >> 1; | ||||||
|  | 				channels_[channel].period = (channels_[channel].period & 0xff) | ((value & 0xf) << 8); | ||||||
|  | 			} break; | ||||||
|  |  | ||||||
|  | 			case 0x8a: case 0x8b: case 0x8c: case 0x8d: case 0x8e: | ||||||
|  | 				channels_[address - 0x8a].amplitude = value & 0xf; | ||||||
|  | 			break; | ||||||
|  |  | ||||||
|  | 			case 0x8f: | ||||||
|  | 				channel_enable_ = value; | ||||||
|  | 			break; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		evaluate_output_volume(); | ||||||
|  | 	}); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void SCC::evaluate_output_volume() { | ||||||
|  | 	output_volume_ = | ||||||
|  | 		static_cast<int16_t>( | ||||||
|  | 			( | ||||||
|  | 				(channel_enable_ & 0x01) ? static_cast<int8_t>(waves_[0].samples[channels_[0].offset]) * channels_[0].amplitude : 0 + | ||||||
|  | 				(channel_enable_ & 0x02) ? static_cast<int8_t>(waves_[1].samples[channels_[1].offset]) * channels_[1].amplitude : 0 + | ||||||
|  | 				(channel_enable_ & 0x04) ? static_cast<int8_t>(waves_[2].samples[channels_[2].offset]) * channels_[2].amplitude : 0 + | ||||||
|  | 				(channel_enable_ & 0x08) ? static_cast<int8_t>(waves_[3].samples[channels_[3].offset]) * channels_[3].amplitude : 0 + | ||||||
|  | 				(channel_enable_ & 0x10) ? static_cast<int8_t>(waves_[3].samples[channels_[4].offset]) * channels_[4].amplitude : 0 | ||||||
|  | 			) | ||||||
|  | 		); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | uint8_t SCC::read(uint16_t address) { | ||||||
|  | 	address &= 0xff; | ||||||
|  | 	if(address < 0x80) { | ||||||
|  | 		return ram_[address]; | ||||||
|  | 	} | ||||||
|  | 	return 0xff; | ||||||
|  | } | ||||||
							
								
								
									
										72
									
								
								Components/KonamiSCC/KonamiSCC.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										72
									
								
								Components/KonamiSCC/KonamiSCC.hpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,72 @@ | |||||||
|  | // | ||||||
|  | //  KonamiSCC.hpp | ||||||
|  | //  Clock Signal | ||||||
|  | // | ||||||
|  | //  Created by Thomas Harte on 06/01/2018. | ||||||
|  | //  Copyright © 2018 Thomas Harte. All rights reserved. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | #ifndef KonamiSCC_hpp | ||||||
|  | #define KonamiSCC_hpp | ||||||
|  |  | ||||||
|  | #include "../../Outputs/Speaker/Implementation/SampleSource.hpp" | ||||||
|  | #include "../../Concurrency/AsyncTaskQueue.hpp" | ||||||
|  |  | ||||||
|  | namespace Konami { | ||||||
|  |  | ||||||
|  | /*! | ||||||
|  | 	Provides an emulation of Konami's Sound Creative Chip ('SCC'). | ||||||
|  |  | ||||||
|  | 	The SCC is a primitive wavetable synthesis chip, offering 32-sample tables, | ||||||
|  | 	and five channels of output. The original SCC uses the same wave for channels | ||||||
|  | 	four and five, the SCC+ supports different waves for the two channels. | ||||||
|  | */ | ||||||
|  | class SCC: public ::Outputs::Speaker::SampleSource { | ||||||
|  | 	public: | ||||||
|  | 		/// Creates a new SCC. | ||||||
|  | 		SCC(Concurrency::DeferringAsyncTaskQueue &task_queue); | ||||||
|  |  | ||||||
|  | 		/// As per ::SampleSource; provides a broadphase test for silence. | ||||||
|  | 		bool is_silent(); | ||||||
|  |  | ||||||
|  | 		/// As per ::SampleSource; provides audio output. | ||||||
|  | 		void get_samples(std::size_t number_of_samples, std::int16_t *target); | ||||||
|  |  | ||||||
|  | 		/// Writes to the SCC. | ||||||
|  | 		void write(uint16_t address, uint8_t value); | ||||||
|  |  | ||||||
|  | 		/// Reads from the SCC. | ||||||
|  | 		uint8_t read(uint16_t address); | ||||||
|  |  | ||||||
|  | 	private: | ||||||
|  | 		Concurrency::DeferringAsyncTaskQueue &task_queue_; | ||||||
|  |  | ||||||
|  | 		// State from here on down is accessed ony from the audio thread. | ||||||
|  | 		int master_divider_ = 0; | ||||||
|  | 		int16_t output_volume_ = 0; | ||||||
|  |  | ||||||
|  | 		struct Channel { | ||||||
|  | 			int period = 0; | ||||||
|  | 			int amplitude = 0; | ||||||
|  |  | ||||||
|  | 			int tone_counter = 0; | ||||||
|  | 			int offset = 0; | ||||||
|  | 		} channels_[5]; | ||||||
|  |  | ||||||
|  | 		struct Wavetable { | ||||||
|  | 			std::uint8_t samples[32]; | ||||||
|  | 		} waves_[4]; | ||||||
|  |  | ||||||
|  | 		std::uint8_t channel_enable_ = 0; | ||||||
|  | 		std::uint8_t test_register_ = 0; | ||||||
|  |  | ||||||
|  | 		void evaluate_output_volume(); | ||||||
|  |  | ||||||
|  | 		// This keeps a copy of wave memory that is accessed from the | ||||||
|  | 		// main emulation thread. | ||||||
|  | 		std::uint8_t ram_[128]; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #endif /* KonamiSCC_hpp */ | ||||||
| @@ -79,3 +79,21 @@ void AsyncTaskQueue::flush() { | |||||||
| 	flush_condition->wait(lock); | 	flush_condition->wait(lock); | ||||||
| #endif | #endif | ||||||
| } | } | ||||||
|  |  | ||||||
|  | void DeferringAsyncTaskQueue::defer(std::function<void(void)> function) { | ||||||
|  | 	if(!deferred_tasks_) { | ||||||
|  | 		deferred_tasks_.reset(new std::list<std::function<void(void)>>); | ||||||
|  | 	} | ||||||
|  | 	deferred_tasks_->push_back(function); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void DeferringAsyncTaskQueue::perform() { | ||||||
|  | 	if(!deferred_tasks_) return; | ||||||
|  | 	std::shared_ptr<std::list<std::function<void(void)>>> deferred_tasks = deferred_tasks_; | ||||||
|  | 	deferred_tasks_.reset(); | ||||||
|  | 	enqueue([deferred_tasks] { | ||||||
|  | 		for(auto &function : *deferred_tasks) { | ||||||
|  | 			function(); | ||||||
|  | 		} | ||||||
|  | 	}); | ||||||
|  | } | ||||||
|   | |||||||
| @@ -9,10 +9,12 @@ | |||||||
| #ifndef AsyncTaskQueue_hpp | #ifndef AsyncTaskQueue_hpp | ||||||
| #define AsyncTaskQueue_hpp | #define AsyncTaskQueue_hpp | ||||||
|  |  | ||||||
|  | #include <atomic> | ||||||
|  | #include <condition_variable> | ||||||
|  | #include <functional> | ||||||
|  | #include <list> | ||||||
| #include <memory> | #include <memory> | ||||||
| #include <thread> | #include <thread> | ||||||
| #include <list> |  | ||||||
| #include <condition_variable> |  | ||||||
|  |  | ||||||
| #ifdef __APPLE__ | #ifdef __APPLE__ | ||||||
| #include <dispatch/dispatch.h> | #include <dispatch/dispatch.h> | ||||||
| @@ -57,6 +59,37 @@ class AsyncTaskQueue { | |||||||
| #endif | #endif | ||||||
| }; | }; | ||||||
|  |  | ||||||
|  | /*! | ||||||
|  | 	A deferring async task queue is one that accepts a list of functions to be performed but defers | ||||||
|  | 	any action until told to perform. It performs them by enquing a single asynchronous task that will | ||||||
|  | 	perform the deferred tasks in order. | ||||||
|  |  | ||||||
|  | 	It therefore offers similar semantics to an asynchronous task queue, but allows for management of | ||||||
|  | 	synchronisation costs, since neither defer nor perform make any effort to be thread safe. | ||||||
|  | */ | ||||||
|  | class DeferringAsyncTaskQueue: public AsyncTaskQueue { | ||||||
|  | 	public: | ||||||
|  | 		/*! | ||||||
|  | 			Adds a function to the deferral list. | ||||||
|  |  | ||||||
|  | 			This is not thread safe; it should be serialised with other calls to itself and to perform. | ||||||
|  | 		*/ | ||||||
|  | 		void defer(std::function<void(void)> function); | ||||||
|  |  | ||||||
|  | 		/*! | ||||||
|  | 			Enqueues a function that will perform all currently deferred functions, in the | ||||||
|  | 			order that they were deferred. | ||||||
|  |  | ||||||
|  | 			This is not thread safe; it should be serialised with other calls to itself and to defer. | ||||||
|  | 		*/ | ||||||
|  | 		void perform(); | ||||||
|  |  | ||||||
|  | 	private: | ||||||
|  | 		// TODO: this is a shared_ptr because of the issues capturing moveables in C++11; | ||||||
|  | 		// switch to a unique_ptr if/when adapting to C++14 | ||||||
|  | 		std::shared_ptr<std::list<std::function<void(void)>>> deferred_tasks_; | ||||||
|  | }; | ||||||
|  |  | ||||||
| } | } | ||||||
|  |  | ||||||
| #endif /* Concurrency_hpp */ | #endif /* Concurrency_hpp */ | ||||||
|   | |||||||
							
								
								
									
										72
									
								
								Concurrency/BestEffortUpdater.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										72
									
								
								Concurrency/BestEffortUpdater.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,72 @@ | |||||||
|  | // | ||||||
|  | //  BestEffortUpdater.cpp | ||||||
|  | //  Clock Signal | ||||||
|  | // | ||||||
|  | //  Created by Thomas Harte on 04/10/2017. | ||||||
|  | //  Copyright © 2017 Thomas Harte. All rights reserved. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | #include "BestEffortUpdater.hpp" | ||||||
|  |  | ||||||
|  | #include <cmath> | ||||||
|  |  | ||||||
|  | using namespace Concurrency; | ||||||
|  |  | ||||||
|  | BestEffortUpdater::BestEffortUpdater() { | ||||||
|  | 	// ATOMIC_FLAG_INIT isn't necessarily safe to use, so establish default state by other means. | ||||||
|  | 	update_is_ongoing_.clear(); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void BestEffortUpdater::update() { | ||||||
|  | 	// Perform an update only if one is not currently ongoing. | ||||||
|  | 	if(!update_is_ongoing_.test_and_set()) { | ||||||
|  | 		async_task_queue_.enqueue([this]() { | ||||||
|  | 			// Get time now using the highest-resolution clock provided by the implementation, and determine | ||||||
|  | 			// the duration since the last time this section was entered. | ||||||
|  | 			const std::chrono::time_point<std::chrono::high_resolution_clock> now = std::chrono::high_resolution_clock::now(); | ||||||
|  | 			const auto elapsed = now - previous_time_point_; | ||||||
|  | 			previous_time_point_ = now; | ||||||
|  |  | ||||||
|  | 			if(has_previous_time_point_) { | ||||||
|  | 				// If the duration is valid, convert it to integer cycles, maintaining a rolling error and call the delegate | ||||||
|  | 				// if there is one. Proceed only if the number of cycles is positive, and cap it to the per-second maximum — | ||||||
|  | 				// it's possible this is an adjustable clock so be ready to swallow unexpected adjustments. | ||||||
|  | 				const int64_t duration = std::chrono::duration_cast<std::chrono::nanoseconds>(elapsed).count(); | ||||||
|  | 				if(duration > 0) { | ||||||
|  | 					double cycles = ((static_cast<double>(duration) * clock_rate_) / 1e9) + error_; | ||||||
|  | 					error_ = fmod(cycles, 1.0); | ||||||
|  |  | ||||||
|  | 					if(delegate_) { | ||||||
|  | 						delegate_->update(this, static_cast<int>(std::min(cycles, clock_rate_)), has_skipped_); | ||||||
|  | 					} | ||||||
|  | 					has_skipped_ = false; | ||||||
|  | 				} | ||||||
|  | 			} else { | ||||||
|  | 				has_previous_time_point_ = true; | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			// Allow furthers updates to occur. | ||||||
|  | 			update_is_ongoing_.clear(); | ||||||
|  | 		}); | ||||||
|  | 	} else { | ||||||
|  | 		async_task_queue_.enqueue([this]() { | ||||||
|  | 			has_skipped_ = true; | ||||||
|  | 		}); | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void BestEffortUpdater::flush() { | ||||||
|  | 	async_task_queue_.flush(); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void BestEffortUpdater::set_delegate(Delegate *const delegate) { | ||||||
|  | 	async_task_queue_.enqueue([this, delegate]() { | ||||||
|  | 		delegate_ = delegate; | ||||||
|  | 	}); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void BestEffortUpdater::set_clock_rate(const double clock_rate) { | ||||||
|  | 	async_task_queue_.enqueue([this, clock_rate]() { | ||||||
|  | 		this->clock_rate_ = clock_rate; | ||||||
|  | 	}); | ||||||
|  | } | ||||||
							
								
								
									
										65
									
								
								Concurrency/BestEffortUpdater.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										65
									
								
								Concurrency/BestEffortUpdater.hpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,65 @@ | |||||||
|  | // | ||||||
|  | //  BestEffortUpdater.hpp | ||||||
|  | //  Clock Signal | ||||||
|  | // | ||||||
|  | //  Created by Thomas Harte on 04/10/2017. | ||||||
|  | //  Copyright © 2017 Thomas Harte. All rights reserved. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | #ifndef BestEffortUpdater_hpp | ||||||
|  | #define BestEffortUpdater_hpp | ||||||
|  |  | ||||||
|  | #include <atomic> | ||||||
|  | #include <chrono> | ||||||
|  |  | ||||||
|  | #include "AsyncTaskQueue.hpp" | ||||||
|  |  | ||||||
|  | namespace Concurrency { | ||||||
|  |  | ||||||
|  | /*! | ||||||
|  | 	Accepts timing cues from multiple threads and ensures that a delegate receives calls to total | ||||||
|  | 	a certain number of cycles per second, that those calls are strictly serialised, and that no | ||||||
|  | 	backlog of calls accrues. | ||||||
|  |  | ||||||
|  | 	No guarantees about the thread that the delegate will be called on are made. | ||||||
|  | */ | ||||||
|  | class BestEffortUpdater { | ||||||
|  | 	public: | ||||||
|  | 		BestEffortUpdater(); | ||||||
|  |  | ||||||
|  | 		/// A delegate receives timing cues. | ||||||
|  | 		struct Delegate { | ||||||
|  | 			virtual void update(BestEffortUpdater *updater, int cycles, bool did_skip_previous_update) = 0; | ||||||
|  | 		}; | ||||||
|  |  | ||||||
|  | 		/// Sets the current delegate. | ||||||
|  | 		void set_delegate(Delegate *); | ||||||
|  |  | ||||||
|  | 		/// Sets the clock rate of the delegate. | ||||||
|  | 		void set_clock_rate(double clock_rate); | ||||||
|  |  | ||||||
|  | 		/*! | ||||||
|  | 			If the delegate is not currently in the process of an `update` call, calls it now to catch up to the current time. | ||||||
|  | 			The call is asynchronous; this method will return immediately. | ||||||
|  | 		*/ | ||||||
|  | 		void update(); | ||||||
|  |  | ||||||
|  | 		/// Blocks until any ongoing update is complete. | ||||||
|  | 		void flush(); | ||||||
|  |  | ||||||
|  | 	private: | ||||||
|  | 		std::atomic_flag update_is_ongoing_; | ||||||
|  | 		AsyncTaskQueue async_task_queue_; | ||||||
|  |  | ||||||
|  | 		std::chrono::time_point<std::chrono::high_resolution_clock> previous_time_point_; | ||||||
|  | 		bool has_previous_time_point_ = false; | ||||||
|  | 		double error_ = 0.0; | ||||||
|  | 		bool has_skipped_ = false; | ||||||
|  |  | ||||||
|  | 		Delegate *delegate_ = nullptr; | ||||||
|  | 		double clock_rate_ = 1.0; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #endif /* BestEffortUpdater_hpp */ | ||||||
							
								
								
									
										27
									
								
								Configurable/Configurable.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								Configurable/Configurable.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,27 @@ | |||||||
|  | // | ||||||
|  | //  Configurable.cpp | ||||||
|  | //  Clock Signal | ||||||
|  | // | ||||||
|  | //  Created by Thomas Harte on 18/11/2017. | ||||||
|  | //  Copyright © 2017 Thomas Harte. All rights reserved. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | #include "Configurable.hpp" | ||||||
|  |  | ||||||
|  | using namespace Configurable; | ||||||
|  |  | ||||||
|  | ListSelection *BooleanSelection::list_selection() { | ||||||
|  | 	return new ListSelection(value ? "yes" : "no"); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | ListSelection *ListSelection::list_selection() { | ||||||
|  | 	return new ListSelection(value); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | BooleanSelection *ListSelection::boolean_selection() { | ||||||
|  | 	return new BooleanSelection(value != "no" && value != "n"); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | BooleanSelection *BooleanSelection::boolean_selection() { | ||||||
|  | 	return new BooleanSelection(value); | ||||||
|  | } | ||||||
							
								
								
									
										88
									
								
								Configurable/Configurable.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										88
									
								
								Configurable/Configurable.hpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,88 @@ | |||||||
|  | // | ||||||
|  | //  Configurable.h | ||||||
|  | //  Clock Signal | ||||||
|  | // | ||||||
|  | //  Created by Thomas Harte on 17/11/2017. | ||||||
|  | //  Copyright © 2017 Thomas Harte. All rights reserved. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | #ifndef Configurable_h | ||||||
|  | #define Configurable_h | ||||||
|  |  | ||||||
|  | #include <map> | ||||||
|  | #include <memory> | ||||||
|  | #include <string> | ||||||
|  | #include <vector> | ||||||
|  |  | ||||||
|  | namespace Configurable { | ||||||
|  |  | ||||||
|  | /*! | ||||||
|  | 	The Option class hierarchy provides a way for components, machines, etc, to provide a named | ||||||
|  | 	list of typed options to which they can respond. | ||||||
|  | */ | ||||||
|  | struct Option { | ||||||
|  | 	std::string long_name; | ||||||
|  | 	std::string short_name; | ||||||
|  | 	virtual ~Option() {} | ||||||
|  |  | ||||||
|  | 	Option(const std::string &long_name, const std::string &short_name) : long_name(long_name), short_name(short_name) {} | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | struct BooleanOption: public Option { | ||||||
|  | 	BooleanOption(const std::string &long_name, const std::string &short_name) : Option(long_name, short_name) {} | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | struct ListOption: public Option { | ||||||
|  | 	std::vector<std::string> options; | ||||||
|  | 	ListOption(const std::string &long_name, const std::string &short_name, const std::vector<std::string> &options) : Option(long_name, short_name), options(options) {} | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | struct BooleanSelection; | ||||||
|  | struct ListSelection; | ||||||
|  |  | ||||||
|  | /*! | ||||||
|  | 	Selections are responses to Options. | ||||||
|  | */ | ||||||
|  | struct Selection { | ||||||
|  | 	virtual ~Selection() {} | ||||||
|  | 	virtual ListSelection *list_selection() = 0; | ||||||
|  | 	virtual BooleanSelection *boolean_selection() = 0; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | struct BooleanSelection: public Selection { | ||||||
|  | 	bool value; | ||||||
|  |  | ||||||
|  | 	ListSelection *list_selection(); | ||||||
|  | 	BooleanSelection *boolean_selection(); | ||||||
|  | 	BooleanSelection(bool value) : value(value) {} | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | struct ListSelection: public Selection { | ||||||
|  | 	std::string value; | ||||||
|  | 	 | ||||||
|  | 	ListSelection *list_selection(); | ||||||
|  | 	BooleanSelection *boolean_selection(); | ||||||
|  | 	ListSelection(const std::string value) : value(value) {} | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | using SelectionSet = std::map<std::string, std::unique_ptr<Selection>>; | ||||||
|  |  | ||||||
|  | /*! | ||||||
|  | 	A Configuratble provides the options that it responds to and allows selections to be set. | ||||||
|  | */ | ||||||
|  | struct Device { | ||||||
|  | 	virtual std::vector<std::unique_ptr<Option>> get_options() = 0; | ||||||
|  | 	virtual void set_selections(const SelectionSet &selection_by_option) = 0; | ||||||
|  | 	virtual SelectionSet get_accurate_selections() = 0; | ||||||
|  | 	virtual SelectionSet get_user_friendly_selections() = 0; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | template <typename T> T *selection(const Configurable::SelectionSet &selections_by_option, const std::string &name) { | ||||||
|  | 	auto selection = selections_by_option.find(name); | ||||||
|  | 	if(selection == selections_by_option.end()) return nullptr; | ||||||
|  | 	return dynamic_cast<T *>(selection->second.get()); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #endif /* Configurable_h */ | ||||||
							
								
								
									
										76
									
								
								Configurable/StandardOptions.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										76
									
								
								Configurable/StandardOptions.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,76 @@ | |||||||
|  | // | ||||||
|  | //  StandardOptions.cpp | ||||||
|  | //  Clock Signal | ||||||
|  | // | ||||||
|  | //  Created by Thomas Harte on 20/11/2017. | ||||||
|  | //  Copyright © 2017 Thomas Harte. All rights reserved. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | #include "StandardOptions.hpp" | ||||||
|  |  | ||||||
|  | namespace { | ||||||
|  |  | ||||||
|  | /*! | ||||||
|  | 	Appends a Boolean selection of @c selection for option @c name to @c selection_set. | ||||||
|  | */ | ||||||
|  | void append_bool(Configurable::SelectionSet &selection_set, const std::string &name, bool selection) { | ||||||
|  | 	selection_set[name] = std::unique_ptr<Configurable::Selection>(new Configurable::BooleanSelection(selection)); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /*! | ||||||
|  | 	Enquires for a Boolean selection for option @c name from @c selections_by_option, storing it to @c result if found. | ||||||
|  | */ | ||||||
|  | bool get_bool(const Configurable::SelectionSet &selections_by_option, const std::string &name, bool &result) { | ||||||
|  | 	auto quickload = Configurable::selection<Configurable::BooleanSelection>(selections_by_option, "quickload"); | ||||||
|  | 	if(!quickload) return false; | ||||||
|  | 	result = quickload->value; | ||||||
|  | 	return true; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // MARK: - Standard option list builder | ||||||
|  | std::vector<std::unique_ptr<Configurable::Option>> Configurable::standard_options(Configurable::StandardOptions mask) { | ||||||
|  | 	std::vector<std::unique_ptr<Configurable::Option>> options; | ||||||
|  | 	if(mask & QuickLoadTape)				options.emplace_back(new Configurable::BooleanOption("Load Tapes Quickly", "quickload")); | ||||||
|  | 	if(mask & DisplayRGBComposite)			options.emplace_back(new Configurable::ListOption("Display", "display", {"composite", "rgb"})); | ||||||
|  | 	if(mask & AutomaticTapeMotorControl)	options.emplace_back(new Configurable::BooleanOption("Automatic Tape Motor Control", "autotapemotor")); | ||||||
|  | 	return options; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // MARK: - Selection appenders | ||||||
|  | void Configurable::append_quick_load_tape_selection(Configurable::SelectionSet &selection_set, bool selection) { | ||||||
|  | 	append_bool(selection_set, "quickload", selection); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void Configurable::append_automatic_tape_motor_control_selection(SelectionSet &selection_set, bool selection) { | ||||||
|  | 	append_bool(selection_set, "autotapemotor", selection); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void Configurable::append_display_selection(Configurable::SelectionSet &selection_set, Display selection) { | ||||||
|  | 	selection_set["display"] = std::unique_ptr<Configurable::Selection>(new Configurable::ListSelection((selection == Display::RGB) ? "rgb" : "composite")); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // MARK: - Selection parsers | ||||||
|  | bool Configurable::get_quick_load_tape(const Configurable::SelectionSet &selections_by_option, bool &result) { | ||||||
|  | 	return get_bool(selections_by_option, "quickload", result); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | bool Configurable::get_automatic_tape_motor_control_selection(const SelectionSet &selections_by_option, bool &result) { | ||||||
|  | 	return get_bool(selections_by_option, "autotapemotor", result); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | bool Configurable::get_display(const Configurable::SelectionSet &selections_by_option, Configurable::Display &result) { | ||||||
|  | 	auto display = Configurable::selection<Configurable::ListSelection>(selections_by_option, "display"); | ||||||
|  | 	if(display) { | ||||||
|  | 		if(display->value == "rgb") { | ||||||
|  | 			result = Configurable::Display::RGB; | ||||||
|  | 			return true; | ||||||
|  | 		} | ||||||
|  | 		if(display->value == "composite") { | ||||||
|  | 			result = Configurable::Display::Composite; | ||||||
|  | 			return true; | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	return false; | ||||||
|  | } | ||||||
							
								
								
									
										76
									
								
								Configurable/StandardOptions.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										76
									
								
								Configurable/StandardOptions.hpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,76 @@ | |||||||
|  | // | ||||||
|  | //  StandardOptions.hpp | ||||||
|  | //  Clock Signal | ||||||
|  | // | ||||||
|  | //  Created by Thomas Harte on 20/11/2017. | ||||||
|  | //  Copyright © 2017 Thomas Harte. All rights reserved. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | #ifndef StandardOptions_hpp | ||||||
|  | #define StandardOptions_hpp | ||||||
|  |  | ||||||
|  | #include "Configurable.hpp" | ||||||
|  |  | ||||||
|  | namespace Configurable { | ||||||
|  |  | ||||||
|  | enum StandardOptions { | ||||||
|  | 	DisplayRGBComposite			= (1 << 0), | ||||||
|  | 	QuickLoadTape				= (1 << 1), | ||||||
|  | 	AutomaticTapeMotorControl	= (1 << 2) | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | enum class Display { | ||||||
|  | 	RGB, | ||||||
|  | 	Composite | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | /*! | ||||||
|  | 	@returns An option list comprised of the standard names for all the options indicated by @c mask. | ||||||
|  | */ | ||||||
|  | std::vector<std::unique_ptr<Option>> standard_options(StandardOptions mask); | ||||||
|  |  | ||||||
|  | /*! | ||||||
|  | 	Appends to @c selection_set a selection of @c selection for QuickLoadTape. | ||||||
|  | */ | ||||||
|  | void append_quick_load_tape_selection(SelectionSet &selection_set, bool selection); | ||||||
|  |  | ||||||
|  | /*! | ||||||
|  | 	Appends to @c selection_set a selection of @c selection for AutomaticTapeMotorControl. | ||||||
|  | */ | ||||||
|  | void append_automatic_tape_motor_control_selection(SelectionSet &selection_set, bool selection); | ||||||
|  |  | ||||||
|  | /*! | ||||||
|  | 	Appends to @c selection_set a selection of @c selection for DisplayRGBComposite. | ||||||
|  | */ | ||||||
|  | void append_display_selection(SelectionSet &selection_set, Display selection); | ||||||
|  |  | ||||||
|  | /*! | ||||||
|  | 	Attempts to discern a QuickLoadTape selection from @c selections_by_option. | ||||||
|  |   | ||||||
|  | 	@param selections_by_option The user selections. | ||||||
|  | 	@param result The location to which the selection will be stored if found. | ||||||
|  | 	@returns @c true if a selection is found; @c false otherwise. | ||||||
|  | */ | ||||||
|  | bool get_quick_load_tape(const SelectionSet &selections_by_option, bool &result); | ||||||
|  |  | ||||||
|  | /*! | ||||||
|  | 	Attempts to discern an AutomaticTapeMotorControl selection from @c selections_by_option. | ||||||
|  |   | ||||||
|  | 	@param selections_by_option The user selections. | ||||||
|  | 	@param result The location to which the selection will be stored if found. | ||||||
|  | 	@returns @c true if a selection is found; @c false otherwise. | ||||||
|  | */ | ||||||
|  | bool get_automatic_tape_motor_control_selection(const SelectionSet &selections_by_option, bool &result); | ||||||
|  |  | ||||||
|  | /*! | ||||||
|  | 	Attempts to discern a display RGB/composite selection from @c selections_by_option. | ||||||
|  |   | ||||||
|  | 	@param selections_by_option The user selections. | ||||||
|  | 	@param result The location to which the selection will be stored if found. | ||||||
|  | 	@returns @c true if a selection is found; @c false otherwise. | ||||||
|  | */ | ||||||
|  | bool get_display(const SelectionSet &selections_by_option, Display &result); | ||||||
|  |  | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #endif /* StandardOptions_hpp */ | ||||||
							
								
								
									
										41
									
								
								Inputs/Joystick.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										41
									
								
								Inputs/Joystick.hpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,41 @@ | |||||||
|  | // | ||||||
|  | //  Joystick.hpp | ||||||
|  | //  Clock Signal | ||||||
|  | // | ||||||
|  | //  Created by Thomas Harte on 14/10/2017. | ||||||
|  | //  Copyright © 2017 Thomas Harte. All rights reserved. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | #ifndef Joystick_hpp | ||||||
|  | #define Joystick_hpp | ||||||
|  |  | ||||||
|  | #include <vector> | ||||||
|  |  | ||||||
|  | namespace Inputs { | ||||||
|  |  | ||||||
|  | /*! | ||||||
|  | 	Provides an intermediate idealised model of a simple joystick, allowing a host | ||||||
|  | 	machine to toggle states, while an interested party either observes or polls. | ||||||
|  | */ | ||||||
|  | class Joystick { | ||||||
|  | 	public: | ||||||
|  | 		virtual ~Joystick() {} | ||||||
|  |  | ||||||
|  | 		enum class DigitalInput { | ||||||
|  | 			Up, Down, Left, Right, Fire | ||||||
|  | 		}; | ||||||
|  |  | ||||||
|  | 		// Host interface. | ||||||
|  | 		virtual void set_digital_input(DigitalInput digital_input, bool is_active) = 0; | ||||||
|  | 		virtual void reset_all_inputs() { | ||||||
|  | 			set_digital_input(DigitalInput::Up, false); | ||||||
|  | 			set_digital_input(DigitalInput::Down, false); | ||||||
|  | 			set_digital_input(DigitalInput::Left, false); | ||||||
|  | 			set_digital_input(DigitalInput::Right, false); | ||||||
|  | 			set_digital_input(DigitalInput::Fire, false); | ||||||
|  | 		} | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #endif /* Joystick_hpp */ | ||||||
							
								
								
									
										38
									
								
								Inputs/Keyboard.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								Inputs/Keyboard.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,38 @@ | |||||||
|  | // | ||||||
|  | //  Keyboard.cpp | ||||||
|  | //  Clock Signal | ||||||
|  | // | ||||||
|  | //  Created by Thomas Harte on 10/9/17. | ||||||
|  | //  Copyright © 2017 Thomas Harte. All rights reserved. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | #include "Keyboard.hpp" | ||||||
|  |  | ||||||
|  | using namespace Inputs; | ||||||
|  |  | ||||||
|  | Keyboard::Keyboard() {} | ||||||
|  |  | ||||||
|  | void Keyboard::set_key_pressed(Key key, bool is_pressed) { | ||||||
|  | 	std::size_t key_offset = static_cast<std::size_t>(key); | ||||||
|  | 	if(key_offset >= key_states_.size()) { | ||||||
|  | 		key_states_.resize(key_offset+1, false); | ||||||
|  | 	} | ||||||
|  | 	key_states_[key_offset] = is_pressed; | ||||||
|  |  | ||||||
|  | 	if(delegate_) delegate_->keyboard_did_change_key(this, key, is_pressed); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void Keyboard::reset_all_keys() { | ||||||
|  | 	std::fill(key_states_.begin(), key_states_.end(), false); | ||||||
|  | 	if(delegate_) delegate_->reset_all_keys(this); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void Keyboard::set_delegate(Delegate *delegate) { | ||||||
|  | 	delegate_ = delegate; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | bool Keyboard::get_key_state(Key key) { | ||||||
|  | 	std::size_t key_offset = static_cast<std::size_t>(key); | ||||||
|  | 	if(key_offset >= key_states_.size()) return false; | ||||||
|  | 	return key_states_[key_offset]; | ||||||
|  | } | ||||||
							
								
								
									
										61
									
								
								Inputs/Keyboard.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										61
									
								
								Inputs/Keyboard.hpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,61 @@ | |||||||
|  | // | ||||||
|  | //  Keyboard.hpp | ||||||
|  | //  Clock Signal | ||||||
|  | // | ||||||
|  | //  Created by Thomas Harte on 10/9/17. | ||||||
|  | //  Copyright © 2017 Thomas Harte. All rights reserved. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | #ifndef Keyboard_hpp | ||||||
|  | #define Keyboard_hpp | ||||||
|  |  | ||||||
|  | #include <vector> | ||||||
|  |  | ||||||
|  | namespace Inputs { | ||||||
|  |  | ||||||
|  | /*! | ||||||
|  | 	Provides an intermediate idealised model of a modern-era computer keyboard | ||||||
|  | 	(so, heavily indebted to the current Windows and Mac layouts), allowing a host | ||||||
|  | 	machine to toggle states, while an interested party either observes or polls. | ||||||
|  | */ | ||||||
|  | class Keyboard { | ||||||
|  | 	public: | ||||||
|  | 		Keyboard(); | ||||||
|  |  | ||||||
|  | 		enum class Key { | ||||||
|  | 			Escape, F1, F2, F3, F4, F5, F6, F7, F8, F9, F10, F11, F12, PrintScreen, ScrollLock, Pause, | ||||||
|  | 			BackTick, k1, k2, k3, k4, k5, k6, k7, k8, k9, k0, Hyphen, Equals, BackSpace, | ||||||
|  | 			Tab, Q, W, E, R, T, Y, U, I, O, P, OpenSquareBracket, CloseSquareBracket, BackSlash, | ||||||
|  | 			CapsLock, A, S, D, F, G, H, J, K, L, Semicolon, Quote, Hash, Enter, | ||||||
|  | 			LeftShift, Z, X, C, V, B, N, M, Comma, FullStop, ForwardSlash, RightShift, | ||||||
|  | 			LeftControl, LeftOption, LeftMeta, Space, RightMeta, RightOption, RightControl, | ||||||
|  | 			Left, Right, Up, Down, | ||||||
|  | 			Insert, Home, PageUp, Delete, End, PageDown, | ||||||
|  | 			NumLock, KeyPadSlash, KeyPadAsterisk, KeyPadDelete, | ||||||
|  | 			KeyPad7, KeyPad8, KeyPad9, KeyPadPlus, | ||||||
|  | 			KeyPad4, KeyPad5, KeyPad6, KeyPadMinus, | ||||||
|  | 			KeyPad1, KeyPad2, KeyPad3, KeyPadEnter, | ||||||
|  | 			KeyPad0, KeyPadDecimalPoint, KeyPadEquals, | ||||||
|  | 			Help | ||||||
|  | 		}; | ||||||
|  |  | ||||||
|  | 		// Host interface. | ||||||
|  | 		virtual void set_key_pressed(Key key, bool is_pressed); | ||||||
|  | 		virtual void reset_all_keys(); | ||||||
|  |  | ||||||
|  | 		// Delegate interface. | ||||||
|  | 		struct Delegate { | ||||||
|  | 			virtual void keyboard_did_change_key(Keyboard *keyboard, Key key, bool is_pressed) = 0; | ||||||
|  | 			virtual void reset_all_keys(Keyboard *keyboard) = 0; | ||||||
|  | 		}; | ||||||
|  | 		void set_delegate(Delegate *delegate); | ||||||
|  | 		bool get_key_state(Key key); | ||||||
|  |  | ||||||
|  | 	private: | ||||||
|  | 		std::vector<bool> key_states_; | ||||||
|  | 		Delegate *delegate_ = nullptr; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #endif /* Keyboard_hpp */ | ||||||
| @@ -8,7 +8,7 @@ | |||||||
|  |  | ||||||
| #include "AmstradCPC.hpp" | #include "AmstradCPC.hpp" | ||||||
|  |  | ||||||
| #include "CharacterMapper.hpp" | #include "Keyboard.hpp" | ||||||
|  |  | ||||||
| #include "../../Processors/Z80/Z80.hpp" | #include "../../Processors/Z80/Z80.hpp" | ||||||
|  |  | ||||||
| @@ -17,13 +17,26 @@ | |||||||
| #include "../../Components/8272/i8272.hpp" | #include "../../Components/8272/i8272.hpp" | ||||||
| #include "../../Components/AY38910/AY38910.hpp" | #include "../../Components/AY38910/AY38910.hpp" | ||||||
|  |  | ||||||
| #include "../MemoryFuzzer.hpp" | #include "../Utility/MemoryFuzzer.hpp" | ||||||
| #include "../Typer.hpp" | #include "../Utility/Typer.hpp" | ||||||
|  |  | ||||||
| #include "../../Storage/Tape/Tape.hpp" | #include "../../Storage/Tape/Tape.hpp" | ||||||
|  |  | ||||||
|  | #include "../../ClockReceiver/ForceInline.hpp" | ||||||
|  | #include "../../Outputs/Speaker/Implementation/LowpassSpeaker.hpp" | ||||||
|  |  | ||||||
|  | #include <cstdint> | ||||||
|  | #include <vector> | ||||||
|  |  | ||||||
| namespace AmstradCPC { | namespace AmstradCPC { | ||||||
|  |  | ||||||
|  | enum ROMType: int { | ||||||
|  | 	OS464 = 0,	BASIC464, | ||||||
|  | 	OS664,		BASIC664, | ||||||
|  | 	OS6128,		BASIC6128, | ||||||
|  | 	AMSDOS | ||||||
|  | }; | ||||||
|  |  | ||||||
| /*! | /*! | ||||||
| 	Models the CPC's interrupt timer. Inputs are vsync, hsync, interrupt acknowledge and reset, and its output | 	Models the CPC's interrupt timer. Inputs are vsync, hsync, interrupt acknowledge and reset, and its output | ||||||
| 	is simply yes or no on whether an interupt is currently requested. Internally it uses a counter with a period | 	is simply yes or no on whether an interupt is currently requested. Internally it uses a counter with a period | ||||||
| @@ -33,8 +46,6 @@ namespace AmstradCPC { | |||||||
| */ | */ | ||||||
| class InterruptTimer { | class InterruptTimer { | ||||||
| 	public: | 	public: | ||||||
| 		InterruptTimer() : timer_(0), interrupt_request_(false) {} |  | ||||||
|  |  | ||||||
| 		/*! | 		/*! | ||||||
| 			Indicates that a new hsync pulse has been recognised. This should be | 			Indicates that a new hsync pulse has been recognised. This should be | ||||||
| 			supplied on the falling edge of the CRTC HSYNC signal, which is the | 			supplied on the falling edge of the CRTC HSYNC signal, which is the | ||||||
| @@ -76,7 +87,12 @@ class InterruptTimer { | |||||||
|  |  | ||||||
| 		/// @returns @c true if an interrupt is currently requested; @c false otherwise. | 		/// @returns @c true if an interrupt is currently requested; @c false otherwise. | ||||||
| 		inline bool get_request() { | 		inline bool get_request() { | ||||||
| 			return interrupt_request_; | 			return last_interrupt_request_ = interrupt_request_; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		/// Asks whether the interrupt status has changed. | ||||||
|  | 		inline bool request_has_changed() { | ||||||
|  | 			return last_interrupt_request_ != interrupt_request_; | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		/// Resets the timer. | 		/// Resets the timer. | ||||||
| @@ -86,9 +102,10 @@ class InterruptTimer { | |||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 	private: | 	private: | ||||||
| 		int reset_counter_; | 		int reset_counter_ = 0; | ||||||
| 		bool interrupt_request_; | 		bool interrupt_request_ = false; | ||||||
| 		int timer_; | 		bool last_interrupt_request_ = false; | ||||||
|  | 		int timer_ = 0; | ||||||
| }; | }; | ||||||
|  |  | ||||||
| /*! | /*! | ||||||
| @@ -99,14 +116,8 @@ class InterruptTimer { | |||||||
| class AYDeferrer { | class AYDeferrer { | ||||||
| 	public: | 	public: | ||||||
| 		/// Constructs a new AY instance and sets its clock rate. | 		/// Constructs a new AY instance and sets its clock rate. | ||||||
| 		inline void setup_output() { | 		AYDeferrer() : ay_(audio_queue_), speaker_(ay_) { | ||||||
| 			ay_.reset(new GI::AY38910::AY38910); | 			speaker_.set_input_rate(1000000); | ||||||
| 			ay_->set_input_rate(1000000); |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		/// Destructs the AY. |  | ||||||
| 		inline void close_output() { |  | ||||||
| 			ay_.reset(); |  | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		/// Adds @c half_cycles half cycles to the amount of time that has passed. | 		/// Adds @c half_cycles half cycles to the amount of time that has passed. | ||||||
| @@ -116,26 +127,28 @@ class AYDeferrer { | |||||||
|  |  | ||||||
| 		/// Enqueues an update-to-now into the AY's deferred queue. | 		/// Enqueues an update-to-now into the AY's deferred queue. | ||||||
| 		inline void update() { | 		inline void update() { | ||||||
| 			ay_->run_for(cycles_since_update_.divide_cycles(Cycles(4))); | 			speaker_.run_for(audio_queue_, cycles_since_update_.divide_cycles(Cycles(4))); | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		/// Issues a request to the AY to perform all processing up to the current time. | 		/// Issues a request to the AY to perform all processing up to the current time. | ||||||
| 		inline void flush() { | 		inline void flush() { | ||||||
| 			ay_->flush(); | 			audio_queue_.perform(); | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		/// @returns the speaker the AY is using for output. | 		/// @returns the speaker the AY is using for output. | ||||||
| 		std::shared_ptr<Outputs::Speaker> get_speaker() { | 		Outputs::Speaker::Speaker *get_speaker() { | ||||||
| 			return ay_; | 			return &speaker_; | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		/// @returns the AY itself. | 		/// @returns the AY itself. | ||||||
| 		GI::AY38910::AY38910 *ay() { | 		GI::AY38910::AY38910 &ay() { | ||||||
| 			return ay_.get(); | 			return ay_; | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 	private: | 	private: | ||||||
| 		std::shared_ptr<GI::AY38910::AY38910> ay_; | 		Concurrency::DeferringAsyncTaskQueue audio_queue_; | ||||||
|  | 		GI::AY38910::AY38910 ay_; | ||||||
|  | 		Outputs::Speaker::LowpassSpeaker<GI::AY38910::AY38910> speaker_; | ||||||
| 		HalfCycles cycles_since_update_; | 		HalfCycles cycles_since_update_; | ||||||
| }; | }; | ||||||
|  |  | ||||||
| @@ -147,26 +160,17 @@ class AYDeferrer { | |||||||
| class CRTCBusHandler { | class CRTCBusHandler { | ||||||
| 	public: | 	public: | ||||||
| 		CRTCBusHandler(uint8_t *ram, InterruptTimer &interrupt_timer) : | 		CRTCBusHandler(uint8_t *ram, InterruptTimer &interrupt_timer) : | ||||||
| 			cycles_(0), |  | ||||||
| 			was_enabled_(false), |  | ||||||
| 			was_sync_(false), |  | ||||||
| 			pixel_data_(nullptr), |  | ||||||
| 			pixel_pointer_(nullptr), |  | ||||||
| 			was_hsync_(false), |  | ||||||
| 			ram_(ram), | 			ram_(ram), | ||||||
| 			interrupt_timer_(interrupt_timer), | 			interrupt_timer_(interrupt_timer) { | ||||||
| 			pixel_divider_(1), | 				establish_palette_hits(); | ||||||
| 			mode_(2), | 				build_mode_table(); | ||||||
| 			next_mode_(2), |  | ||||||
| 			cycles_into_hsync_(0) { |  | ||||||
| 				build_mode_tables(); |  | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
| 		/*! | 		/*! | ||||||
| 			The CRTC entry function; takes the current bus state and determines what output  | 			The CRTC entry function for the main part of each clock cycle; takes the current | ||||||
| 			to produce based on the current palette and mode. | 			bus state and determines what output to produce based on the current palette and mode. | ||||||
| 		*/ | 		*/ | ||||||
| 		inline void perform_bus_cycle(const Motorola::CRTC::BusState &state) { | 		forceinline void perform_bus_cycle_phase1(const Motorola::CRTC::BusState &state) { | ||||||
| 			// The gate array waits 2µs to react to the CRTC's vsync signal, and then | 			// The gate array waits 2µs to react to the CRTC's vsync signal, and then | ||||||
| 			// caps output at 4µs. Since the clock rate is 1Mhz, that's 2 and 4 cycles, | 			// caps output at 4µs. Since the clock rate is 1Mhz, that's 2 and 4 cycles, | ||||||
| 			// respectively. | 			// respectively. | ||||||
| @@ -208,14 +212,14 @@ class CRTCBusHandler { | |||||||
| 			// collect some more pixels if output is ongoing | 			// collect some more pixels if output is ongoing | ||||||
| 			if(!is_sync && state.display_enable) { | 			if(!is_sync && state.display_enable) { | ||||||
| 				if(!pixel_data_) { | 				if(!pixel_data_) { | ||||||
| 					pixel_pointer_ = pixel_data_ = crt_->allocate_write_area(320); | 					pixel_pointer_ = pixel_data_ = crt_->allocate_write_area(320, 8); | ||||||
| 				} | 				} | ||||||
| 				if(pixel_pointer_) { | 				if(pixel_pointer_) { | ||||||
| 					// the CPC shuffles output lines as: | 					// the CPC shuffles output lines as: | ||||||
| 					//	MA13 MA12	RA2 RA1 RA0		MA9 MA8 MA7 MA6 MA5 MA4 MA3 MA2 MA1 MA0		CCLK | 					//	MA13 MA12	RA2 RA1 RA0		MA9 MA8 MA7 MA6 MA5 MA4 MA3 MA2 MA1 MA0		CCLK | ||||||
| 					// ... so form the real access address. | 					// ... so form the real access address. | ||||||
| 					uint16_t address = | 					uint16_t address = | ||||||
| 						(uint16_t)( | 						static_cast<uint16_t>( | ||||||
| 							((state.refresh_address & 0x3ff) << 1) | | 							((state.refresh_address & 0x3ff) << 1) | | ||||||
| 							((state.row_address & 0x7) << 11) | | 							((state.row_address & 0x7) << 11) | | ||||||
| 							((state.refresh_address & 0x3000) << 2) | 							((state.refresh_address & 0x3000) << 2) | ||||||
| @@ -224,26 +228,26 @@ class CRTCBusHandler { | |||||||
| 					// fetch two bytes and translate into pixels | 					// fetch two bytes and translate into pixels | ||||||
| 					switch(mode_) { | 					switch(mode_) { | ||||||
| 						case 0: | 						case 0: | ||||||
| 							((uint16_t *)pixel_pointer_)[0] = mode0_output_[ram_[address]]; | 							reinterpret_cast<uint16_t *>(pixel_pointer_)[0] = mode0_output_[ram_[address]]; | ||||||
| 							((uint16_t *)pixel_pointer_)[1] = mode0_output_[ram_[address+1]]; | 							reinterpret_cast<uint16_t *>(pixel_pointer_)[1] = mode0_output_[ram_[address+1]]; | ||||||
| 							pixel_pointer_ += 4; | 							pixel_pointer_ += 4; | ||||||
| 						break; | 						break; | ||||||
|  |  | ||||||
| 						case 1: | 						case 1: | ||||||
| 							((uint32_t *)pixel_pointer_)[0] = mode1_output_[ram_[address]]; | 							reinterpret_cast<uint32_t *>(pixel_pointer_)[0] = mode1_output_[ram_[address]]; | ||||||
| 							((uint32_t *)pixel_pointer_)[1] = mode1_output_[ram_[address+1]]; | 							reinterpret_cast<uint32_t *>(pixel_pointer_)[1] = mode1_output_[ram_[address+1]]; | ||||||
| 							pixel_pointer_ += 8; | 							pixel_pointer_ += 8; | ||||||
| 						break; | 						break; | ||||||
|  |  | ||||||
| 						case 2: | 						case 2: | ||||||
| 							((uint64_t *)pixel_pointer_)[0] = mode2_output_[ram_[address]]; | 							reinterpret_cast<uint64_t *>(pixel_pointer_)[0] = mode2_output_[ram_[address]]; | ||||||
| 							((uint64_t *)pixel_pointer_)[1] = mode2_output_[ram_[address+1]]; | 							reinterpret_cast<uint64_t *>(pixel_pointer_)[1] = mode2_output_[ram_[address+1]]; | ||||||
| 							pixel_pointer_ += 16; | 							pixel_pointer_ += 16; | ||||||
| 						break; | 						break; | ||||||
|  |  | ||||||
| 						case 3: | 						case 3: | ||||||
| 							((uint16_t *)pixel_pointer_)[0] = mode3_output_[ram_[address]]; | 							reinterpret_cast<uint16_t *>(pixel_pointer_)[0] = mode3_output_[ram_[address]]; | ||||||
| 							((uint16_t *)pixel_pointer_)[1] = mode3_output_[ram_[address+1]]; | 							reinterpret_cast<uint16_t *>(pixel_pointer_)[1] = mode3_output_[ram_[address+1]]; | ||||||
| 							pixel_pointer_ += 4; | 							pixel_pointer_ += 4; | ||||||
| 						break; | 						break; | ||||||
|  |  | ||||||
| @@ -259,7 +263,13 @@ class CRTCBusHandler { | |||||||
| 					} | 					} | ||||||
| 				} | 				} | ||||||
| 			} | 			} | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		/*! | ||||||
|  | 			The CRTC entry function for phase 2 of each bus cycle — in which the next sync line state becomes | ||||||
|  | 			visible early. The CPC uses changes in sync to clock the interrupt timer. | ||||||
|  | 		*/ | ||||||
|  | 		void perform_bus_cycle_phase2(const Motorola::CRTC::BusState &state) { | ||||||
| 			// check for a trailing CRTC hsync; if one occurred then that's the trigger potentially to change | 			// check for a trailing CRTC hsync; if one occurred then that's the trigger potentially to change | ||||||
| 			// modes, and should also be sent on to the interrupt timer | 			// modes, and should also be sent on to the interrupt timer | ||||||
| 			if(was_hsync_ && !state.hsync) { | 			if(was_hsync_ && !state.hsync) { | ||||||
| @@ -271,6 +281,7 @@ class CRTCBusHandler { | |||||||
| 						case 1:		pixel_divider_ = 2;	break; | 						case 1:		pixel_divider_ = 2;	break; | ||||||
| 						case 2:		pixel_divider_ = 1;	break; | 						case 2:		pixel_divider_ = 1;	break; | ||||||
| 					} | 					} | ||||||
|  | 					build_mode_table(); | ||||||
| 				} | 				} | ||||||
|  |  | ||||||
| 				interrupt_timer_.signal_hsync(); | 				interrupt_timer_.signal_hsync(); | ||||||
| @@ -296,7 +307,7 @@ class CRTCBusHandler { | |||||||
| 					"return vec3(float((sample >> 4) & 3u), float((sample >> 2) & 3u), float(sample & 3u)) / 2.0;" | 					"return vec3(float((sample >> 4) & 3u), float((sample >> 2) & 3u), float(sample & 3u)) / 2.0;" | ||||||
| 				"}"); | 				"}"); | ||||||
| 			crt_->set_visible_area(Outputs::CRT::Rect(0.075f, 0.05f, 0.9f, 0.9f)); | 			crt_->set_visible_area(Outputs::CRT::Rect(0.075f, 0.05f, 0.9f, 0.9f)); | ||||||
| 			crt_->set_output_device(Outputs::CRT::Monitor); | 			crt_->set_output_device(Outputs::CRT::OutputDevice::Monitor); | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		/// Destructs the CRT. | 		/// Destructs the CRT. | ||||||
| @@ -305,8 +316,8 @@ class CRTCBusHandler { | |||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		/// @returns the CRT. | 		/// @returns the CRT. | ||||||
| 		std::shared_ptr<Outputs::CRT::CRT> get_crt() { | 		Outputs::CRT::CRT *get_crt() { | ||||||
| 			return crt_; | 			return crt_.get(); | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		/*! | 		/*! | ||||||
| @@ -317,11 +328,6 @@ class CRTCBusHandler { | |||||||
| 			next_mode_ = mode; | 			next_mode_ = mode; | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		/// @returns the current value of the CRTC's vertical sync output. |  | ||||||
| 		bool get_vsync() const { |  | ||||||
| 			return was_vsync_; |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		/// Palette management: selects a pen to modify. | 		/// Palette management: selects a pen to modify. | ||||||
| 		void select_pen(int pen) { | 		void select_pen(int pen) { | ||||||
| 			pen_ = pen; | 			pen_ = pen; | ||||||
| @@ -339,34 +345,70 @@ class CRTCBusHandler { | |||||||
| 				border_ = mapped_palette_value(colour); | 				border_ = mapped_palette_value(colour); | ||||||
| 			} else { | 			} else { | ||||||
| 				palette_[pen_] = mapped_palette_value(colour); | 				palette_[pen_] = mapped_palette_value(colour); | ||||||
| 				// TODO: no need for a full regeneration, of every mode, every time | 				patch_mode_table(pen_); | ||||||
| 				build_mode_tables(); |  | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 	private: | 	private: | ||||||
| 		void output_border(unsigned int length) { | 		void output_border(unsigned int length) { | ||||||
| 			uint8_t *colour_pointer = (uint8_t *)crt_->allocate_write_area(1); | 			uint8_t *colour_pointer = static_cast<uint8_t *>(crt_->allocate_write_area(1)); | ||||||
| 			if(colour_pointer) *colour_pointer = border_; | 			if(colour_pointer) *colour_pointer = border_; | ||||||
| 			crt_->output_level(length * 16); | 			crt_->output_level(length * 16); | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		void build_mode_tables() { | #define Mode0Colour0(c) ((c & 0x80) >> 7) | ((c & 0x20) >> 3) | ((c & 0x08) >> 2) | ((c & 0x02) << 2) | ||||||
|  | #define Mode0Colour1(c) ((c & 0x40) >> 6) | ((c & 0x10) >> 2) | ((c & 0x04) >> 1) | ((c & 0x01) << 3) | ||||||
|  |  | ||||||
|  | #define Mode1Colour0(c) ((c & 0x80) >> 7) | ((c & 0x08) >> 2) | ||||||
|  | #define Mode1Colour1(c) ((c & 0x40) >> 6) | ((c & 0x04) >> 1) | ||||||
|  | #define Mode1Colour2(c) ((c & 0x20) >> 5) | ((c & 0x02) >> 0) | ||||||
|  | #define Mode1Colour3(c) ((c & 0x10) >> 4) | ((c & 0x01) << 1) | ||||||
|  |  | ||||||
|  | #define Mode3Colour0(c)	((c & 0x80) >> 7) | ((c & 0x08) >> 2) | ||||||
|  | #define Mode3Colour1(c) ((c & 0x40) >> 6) | ((c & 0x04) >> 1) | ||||||
|  |  | ||||||
|  | 		void establish_palette_hits() { | ||||||
|  | 			for(int c = 0; c < 256; c++) { | ||||||
|  | 				mode0_palette_hits_[Mode0Colour0(c)].push_back(static_cast<uint8_t>(c)); | ||||||
|  | 				mode0_palette_hits_[Mode0Colour1(c)].push_back(static_cast<uint8_t>(c)); | ||||||
|  |  | ||||||
|  | 				mode1_palette_hits_[Mode1Colour0(c)].push_back(static_cast<uint8_t>(c)); | ||||||
|  | 				mode1_palette_hits_[Mode1Colour1(c)].push_back(static_cast<uint8_t>(c)); | ||||||
|  | 				mode1_palette_hits_[Mode1Colour2(c)].push_back(static_cast<uint8_t>(c)); | ||||||
|  | 				mode1_palette_hits_[Mode1Colour3(c)].push_back(static_cast<uint8_t>(c)); | ||||||
|  |  | ||||||
|  | 				mode3_palette_hits_[Mode3Colour0(c)].push_back(static_cast<uint8_t>(c)); | ||||||
|  | 				mode3_palette_hits_[Mode3Colour1(c)].push_back(static_cast<uint8_t>(c)); | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		void build_mode_table() { | ||||||
|  | 			switch(mode_) { | ||||||
|  | 				case 0: | ||||||
|  | 					// Mode 0: abcdefgh -> [gcea] [hdfb] | ||||||
| 					for(int c = 0; c < 256; c++) { | 					for(int c = 0; c < 256; c++) { | ||||||
| 						// prepare mode 0 | 						// prepare mode 0 | ||||||
| 				uint8_t *mode0_pixels = (uint8_t *)&mode0_output_[c]; | 						uint8_t *mode0_pixels = reinterpret_cast<uint8_t *>(&mode0_output_[c]); | ||||||
| 				mode0_pixels[0] = palette_[((c & 0x80) >> 7) | ((c & 0x20) >> 3) | ((c & 0x08) >> 2) | ((c & 0x02) << 2)]; | 						mode0_pixels[0] = palette_[Mode0Colour0(c)]; | ||||||
| 				mode0_pixels[1] = palette_[((c & 0x40) >> 6) | ((c & 0x10) >> 2) | ((c & 0x04) >> 1) | ((c & 0x01) << 3)]; | 						mode0_pixels[1] = palette_[Mode0Colour1(c)]; | ||||||
|  | 					} | ||||||
|  | 				break; | ||||||
|  |  | ||||||
|  | 				case 1: | ||||||
|  | 					for(int c = 0; c < 256; c++) { | ||||||
| 						// prepare mode 1 | 						// prepare mode 1 | ||||||
| 				uint8_t *mode1_pixels = (uint8_t *)&mode1_output_[c]; | 						uint8_t *mode1_pixels = reinterpret_cast<uint8_t *>(&mode1_output_[c]); | ||||||
| 				mode1_pixels[0] = palette_[((c & 0x80) >> 7) | ((c & 0x08) >> 2)]; | 						mode1_pixels[0] = palette_[Mode1Colour0(c)]; | ||||||
| 				mode1_pixels[1] = palette_[((c & 0x40) >> 6) | ((c & 0x04) >> 1)]; | 						mode1_pixels[1] = palette_[Mode1Colour1(c)]; | ||||||
| 				mode1_pixels[2] = palette_[((c & 0x20) >> 5) | ((c & 0x02) >> 0)]; | 						mode1_pixels[2] = palette_[Mode1Colour2(c)]; | ||||||
| 				mode1_pixels[3] = palette_[((c & 0x10) >> 4) | ((c & 0x01) << 1)]; | 						mode1_pixels[3] = palette_[Mode1Colour3(c)]; | ||||||
|  | 					} | ||||||
|  | 				break; | ||||||
|  |  | ||||||
|  | 				case 2: | ||||||
|  | 					for(int c = 0; c < 256; c++) { | ||||||
| 						// prepare mode 2 | 						// prepare mode 2 | ||||||
| 				uint8_t *mode2_pixels = (uint8_t *)&mode2_output_[c]; | 						uint8_t *mode2_pixels = reinterpret_cast<uint8_t *>(&mode2_output_[c]); | ||||||
| 						mode2_pixels[0] = palette_[((c & 0x80) >> 7)]; | 						mode2_pixels[0] = palette_[((c & 0x80) >> 7)]; | ||||||
| 						mode2_pixels[1] = palette_[((c & 0x40) >> 6)]; | 						mode2_pixels[1] = palette_[((c & 0x40) >> 6)]; | ||||||
| 						mode2_pixels[2] = palette_[((c & 0x20) >> 5)]; | 						mode2_pixels[2] = palette_[((c & 0x20) >> 5)]; | ||||||
| @@ -375,14 +417,68 @@ class CRTCBusHandler { | |||||||
| 						mode2_pixels[5] = palette_[((c & 0x04) >> 2)]; | 						mode2_pixels[5] = palette_[((c & 0x04) >> 2)]; | ||||||
| 						mode2_pixels[6] = palette_[((c & 0x03) >> 1)]; | 						mode2_pixels[6] = palette_[((c & 0x03) >> 1)]; | ||||||
| 						mode2_pixels[7] = palette_[((c & 0x01) >> 0)]; | 						mode2_pixels[7] = palette_[((c & 0x01) >> 0)]; | ||||||
|  | 					} | ||||||
|  | 				break; | ||||||
|  |  | ||||||
|  | 				case 3: | ||||||
|  | 					for(int c = 0; c < 256; c++) { | ||||||
| 						// prepare mode 3 | 						// prepare mode 3 | ||||||
| 				uint8_t *mode3_pixels = (uint8_t *)&mode3_output_[c]; | 						uint8_t *mode3_pixels = reinterpret_cast<uint8_t *>(&mode3_output_[c]); | ||||||
| 				mode3_pixels[0] = palette_[((c & 0x80) >> 7) | ((c & 0x08) >> 2)]; | 						mode3_pixels[0] = palette_[Mode3Colour0(c)]; | ||||||
| 				mode3_pixels[1] = palette_[((c & 0x40) >> 6) | ((c & 0x04) >> 1)]; | 						mode3_pixels[1] = palette_[Mode3Colour1(c)]; | ||||||
|  | 					} | ||||||
|  | 				break; | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
|  | 		void patch_mode_table(int pen) { | ||||||
|  | 			switch(mode_) { | ||||||
|  | 				case 0: { | ||||||
|  | 					for(uint8_t c : mode0_palette_hits_[pen]) { | ||||||
|  | 						uint8_t *mode0_pixels = reinterpret_cast<uint8_t *>(&mode0_output_[c]); | ||||||
|  | 						mode0_pixels[0] = palette_[Mode0Colour0(c)]; | ||||||
|  | 						mode0_pixels[1] = palette_[Mode0Colour1(c)]; | ||||||
|  | 					} | ||||||
|  | 				} break; | ||||||
|  | 				case 1: | ||||||
|  | 					if(pen > 3) return; | ||||||
|  | 					for(uint8_t c : mode1_palette_hits_[pen]) { | ||||||
|  | 						uint8_t *mode1_pixels = reinterpret_cast<uint8_t *>(&mode1_output_[c]); | ||||||
|  | 						mode1_pixels[0] = palette_[Mode1Colour0(c)]; | ||||||
|  | 						mode1_pixels[1] = palette_[Mode1Colour1(c)]; | ||||||
|  | 						mode1_pixels[2] = palette_[Mode1Colour2(c)]; | ||||||
|  | 						mode1_pixels[3] = palette_[Mode1Colour3(c)]; | ||||||
|  | 					} | ||||||
|  | 				break; | ||||||
|  | 				case 2: | ||||||
|  | 					if(pen > 1) return; | ||||||
|  | 					// Whichever pen this is, there's only one table entry it doesn't touch, so just | ||||||
|  | 					// rebuild the whole thing. | ||||||
|  | 					build_mode_table(); | ||||||
|  | 				break; | ||||||
|  | 				case 3: | ||||||
|  | 					if(pen > 3) return; | ||||||
|  | 					// Same argument applies here as to case 1, as the unused bits aren't masked out. | ||||||
|  | 					for(uint8_t c : mode3_palette_hits_[pen]) { | ||||||
|  | 						uint8_t *mode3_pixels = reinterpret_cast<uint8_t *>(&mode3_output_[c]); | ||||||
|  | 						mode3_pixels[0] = palette_[Mode3Colour0(c)]; | ||||||
|  | 						mode3_pixels[1] = palette_[Mode3Colour1(c)]; | ||||||
|  | 					} | ||||||
|  | 				break; | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | #undef Mode0Colour0 | ||||||
|  | #undef Mode0Colour1 | ||||||
|  |  | ||||||
|  | #undef Mode1Colour0 | ||||||
|  | #undef Mode1Colour1 | ||||||
|  | #undef Mode1Colour2 | ||||||
|  | #undef Mode1Colour3 | ||||||
|  |  | ||||||
|  | #undef Mode3Colour0 | ||||||
|  | #undef Mode3Colour1 | ||||||
|  |  | ||||||
| 		uint8_t mapped_palette_value(uint8_t colour) { | 		uint8_t mapped_palette_value(uint8_t colour) { | ||||||
| #define COL(r, g, b) (r << 4) | (g << 2) | b | #define COL(r, g, b) (r << 4) | (g << 2) | b | ||||||
| 			static const uint8_t mapping[32] = { | 			static const uint8_t mapping[32] = { | ||||||
| @@ -399,27 +495,31 @@ class CRTCBusHandler { | |||||||
| 			return mapping[colour]; | 			return mapping[colour]; | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		unsigned int cycles_; | 		unsigned int cycles_ = 0; | ||||||
|  |  | ||||||
| 		bool was_enabled_, was_sync_, was_hsync_, was_vsync_; | 		bool was_enabled_ = false, was_sync_ = false, was_hsync_ = false, was_vsync_ = false; | ||||||
| 		int cycles_into_hsync_; | 		int cycles_into_hsync_ = 0; | ||||||
|  |  | ||||||
| 		std::shared_ptr<Outputs::CRT::CRT> crt_; | 		std::unique_ptr<Outputs::CRT::CRT> crt_; | ||||||
| 		uint8_t *pixel_data_, *pixel_pointer_; | 		uint8_t *pixel_data_ = nullptr, *pixel_pointer_ = nullptr; | ||||||
|  |  | ||||||
| 		uint8_t *ram_; | 		uint8_t *ram_ = nullptr; | ||||||
|  |  | ||||||
| 		int next_mode_, mode_; | 		int next_mode_ = 2, mode_ = 2; | ||||||
|  |  | ||||||
| 		unsigned int pixel_divider_; | 		unsigned int pixel_divider_ = 1; | ||||||
| 		uint16_t mode0_output_[256]; | 		uint16_t mode0_output_[256]; | ||||||
| 		uint32_t mode1_output_[256]; | 		uint32_t mode1_output_[256]; | ||||||
| 		uint64_t mode2_output_[256]; | 		uint64_t mode2_output_[256]; | ||||||
| 		uint16_t mode3_output_[256]; | 		uint16_t mode3_output_[256]; | ||||||
|  |  | ||||||
| 		int pen_; | 		std::vector<uint8_t> mode0_palette_hits_[16]; | ||||||
|  | 		std::vector<uint8_t> mode1_palette_hits_[4]; | ||||||
|  | 		std::vector<uint8_t> mode3_palette_hits_[4]; | ||||||
|  |  | ||||||
|  | 		int pen_ = 0; | ||||||
| 		uint8_t palette_[16]; | 		uint8_t palette_[16]; | ||||||
| 		uint8_t border_; | 		uint8_t border_ = 0; | ||||||
|  |  | ||||||
| 		InterruptTimer &interrupt_timer_; | 		InterruptTimer &interrupt_timer_; | ||||||
| }; | }; | ||||||
| @@ -477,12 +577,25 @@ class KeyboardState: public GI::AY38910::PortHandler { | |||||||
| class FDC: public Intel::i8272::i8272 { | class FDC: public Intel::i8272::i8272 { | ||||||
| 	private: | 	private: | ||||||
| 		Intel::i8272::BusHandler bus_handler_; | 		Intel::i8272::BusHandler bus_handler_; | ||||||
|  | 		std::shared_ptr<Storage::Disk::Drive> drive_; | ||||||
|  |  | ||||||
| 	public: | 	public: | ||||||
| 		FDC() : i8272(bus_handler_, Cycles(8000000), 16, 300) {} | 		FDC() : | ||||||
|  | 			i8272(bus_handler_, Cycles(8000000)), | ||||||
|  | 			drive_(new Storage::Disk::Drive(8000000, 300, 1)) { | ||||||
|  | 			set_drive(drive_); | ||||||
|  | 		} | ||||||
|  |  | ||||||
| 		void set_motor_on(bool on) { | 		void set_motor_on(bool on) { | ||||||
| 			Intel::i8272::i8272::set_motor_on(on); | 			drive_->set_motor_on(on); | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		void select_drive(int c) { | ||||||
|  | 			// TODO: support more than one drive. | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		void set_disk(std::shared_ptr<Storage::Disk::Disk> disk, int drive) { | ||||||
|  | 			drive_->set_disk(disk); | ||||||
| 		} | 		} | ||||||
| }; | }; | ||||||
|  |  | ||||||
| @@ -493,12 +606,12 @@ class i8255PortHandler : public Intel::i8255::PortHandler { | |||||||
| 	public: | 	public: | ||||||
| 		i8255PortHandler( | 		i8255PortHandler( | ||||||
| 			KeyboardState &key_state, | 			KeyboardState &key_state, | ||||||
| 			const CRTCBusHandler &crtc_bus_handler, | 			const Motorola::CRTC::CRTC6845<CRTCBusHandler> &crtc, | ||||||
| 			AYDeferrer &ay, | 			AYDeferrer &ay, | ||||||
| 			Storage::Tape::BinaryTapePlayer &tape_player) : | 			Storage::Tape::BinaryTapePlayer &tape_player) : | ||||||
| 				key_state_(key_state), |  | ||||||
| 				crtc_bus_handler_(crtc_bus_handler), |  | ||||||
| 				ay_(ay), | 				ay_(ay), | ||||||
|  | 				crtc_(crtc), | ||||||
|  | 				key_state_(key_state), | ||||||
| 				tape_player_(tape_player) {} | 				tape_player_(tape_player) {} | ||||||
|  |  | ||||||
| 		/// The i8255 will call this to set a new output value of @c value for @c port. | 		/// The i8255 will call this to set a new output value of @c value for @c port. | ||||||
| @@ -507,7 +620,7 @@ class i8255PortHandler : public Intel::i8255::PortHandler { | |||||||
| 				case 0: | 				case 0: | ||||||
| 					// Port A is connected to the AY's data bus. | 					// Port A is connected to the AY's data bus. | ||||||
| 					ay_.update(); | 					ay_.update(); | ||||||
| 					ay_.ay()->set_data_input(value); | 					ay_.ay().set_data_input(value); | ||||||
| 				break; | 				break; | ||||||
| 				case 1: | 				case 1: | ||||||
| 					// Port B is an input only. So output goes nowehere. | 					// Port B is an input only. So output goes nowehere. | ||||||
| @@ -523,7 +636,7 @@ class i8255PortHandler : public Intel::i8255::PortHandler { | |||||||
| 					tape_player_.set_tape_output((value & 0x20) ? true : false); | 					tape_player_.set_tape_output((value & 0x20) ? true : false); | ||||||
|  |  | ||||||
| 					// Bits 6 and 7 set BDIR and BC1 for the AY. | 					// Bits 6 and 7 set BDIR and BC1 for the AY. | ||||||
| 					ay_.ay()->set_control_lines( | 					ay_.ay().set_control_lines( | ||||||
| 						(GI::AY38910::ControlLines)( | 						(GI::AY38910::ControlLines)( | ||||||
| 							((value & 0x80) ? GI::AY38910::BDIR : 0) | | 							((value & 0x80) ? GI::AY38910::BDIR : 0) | | ||||||
| 							((value & 0x40) ? GI::AY38910::BC1 : 0) | | 							((value & 0x40) ? GI::AY38910::BC1 : 0) | | ||||||
| @@ -536,9 +649,9 @@ class i8255PortHandler : public Intel::i8255::PortHandler { | |||||||
| 		/// The i8255 will call this to obtain a new input for @c port. | 		/// The i8255 will call this to obtain a new input for @c port. | ||||||
| 		uint8_t get_value(int port) { | 		uint8_t get_value(int port) { | ||||||
| 			switch(port) { | 			switch(port) { | ||||||
| 				case 0: return ay_.ay()->get_data_output();	// Port A is wired to the AY | 				case 0: return ay_.ay().get_data_output();	// Port A is wired to the AY | ||||||
| 				case 1:	return | 				case 1:	return | ||||||
| 					(crtc_bus_handler_.get_vsync() ? 0x01 : 0x00) |	// Bit 0 returns CRTC vsync. | 					(crtc_.get_bus_state().vsync ? 0x01 : 0x00) |	// Bit 0 returns CRTC vsync. | ||||||
| 					(tape_player_.get_input() ? 0x80 : 0x00) |		// Bit 7 returns cassette input. | 					(tape_player_.get_input() ? 0x80 : 0x00) |		// Bit 7 returns cassette input. | ||||||
| 					0x7e;	// Bits unimplemented: | 					0x7e;	// Bits unimplemented: | ||||||
| 							// | 							// | ||||||
| @@ -552,8 +665,8 @@ class i8255PortHandler : public Intel::i8255::PortHandler { | |||||||
|  |  | ||||||
| 	private: | 	private: | ||||||
| 		AYDeferrer &ay_; | 		AYDeferrer &ay_; | ||||||
|  | 		const Motorola::CRTC::CRTC6845<CRTCBusHandler> &crtc_; | ||||||
| 		KeyboardState &key_state_; | 		KeyboardState &key_state_; | ||||||
| 		const CRTCBusHandler &crtc_bus_handler_; |  | ||||||
| 		Storage::Tape::BinaryTapePlayer &tape_player_; | 		Storage::Tape::BinaryTapePlayer &tape_player_; | ||||||
| }; | }; | ||||||
|  |  | ||||||
| @@ -563,25 +676,36 @@ class i8255PortHandler : public Intel::i8255::PortHandler { | |||||||
| class ConcreteMachine: | class ConcreteMachine: | ||||||
| 	public Utility::TypeRecipient, | 	public Utility::TypeRecipient, | ||||||
| 	public CPU::Z80::BusHandler, | 	public CPU::Z80::BusHandler, | ||||||
|  | 	public Sleeper::SleepObserver, | ||||||
| 	public Machine { | 	public Machine { | ||||||
| 	public: | 	public: | ||||||
| 		ConcreteMachine() : | 		ConcreteMachine() : | ||||||
| 			z80_(*this), | 			z80_(*this), | ||||||
| 			crtc_counter_(HalfCycles(4)),	// This starts the CRTC exactly out of phase with the CPU's memory accesses |  | ||||||
| 			crtc_(Motorola::CRTC::HD6845S, crtc_bus_handler_), |  | ||||||
| 			crtc_bus_handler_(ram_, interrupt_timer_), | 			crtc_bus_handler_(ram_, interrupt_timer_), | ||||||
|  | 			crtc_(Motorola::CRTC::HD6845S, crtc_bus_handler_), | ||||||
|  | 			i8255_port_handler_(key_state_, crtc_, ay_, tape_player_), | ||||||
| 			i8255_(i8255_port_handler_), | 			i8255_(i8255_port_handler_), | ||||||
| 			i8255_port_handler_(key_state_, crtc_bus_handler_, ay_, tape_player_), | 			tape_player_(8000000), | ||||||
| 			tape_player_(8000000) { | 			crtc_counter_(HalfCycles(4))	// This starts the CRTC exactly out of phase with the CPU's memory accesses | ||||||
|  | 		{ | ||||||
| 			// primary clock is 4Mhz | 			// primary clock is 4Mhz | ||||||
| 			set_clock_rate(4000000); | 			set_clock_rate(4000000); | ||||||
|  |  | ||||||
| 			// ensure memory starts in a random state | 			// ensure memory starts in a random state | ||||||
| 			Memory::Fuzz(ram_, sizeof(ram_)); | 			Memory::Fuzz(ram_, sizeof(ram_)); | ||||||
|  |  | ||||||
|  | 			// register this class as the sleep observer for the FDC and tape | ||||||
|  | 			fdc_.set_sleep_observer(this); | ||||||
|  | 			fdc_is_sleeping_ = fdc_.is_sleeping(); | ||||||
|  |  | ||||||
|  | 			tape_player_.set_sleep_observer(this); | ||||||
|  | 			tape_player_is_sleeping_ = tape_player_.is_sleeping(); | ||||||
|  |  | ||||||
|  | 			ay_.ay().set_port_handler(&key_state_); | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		/// The entry point for performing a partial Z80 machine cycle. | 		/// The entry point for performing a partial Z80 machine cycle. | ||||||
| 		inline HalfCycles perform_machine_cycle(const CPU::Z80::PartialMachineCycle &cycle) { | 		forceinline HalfCycles perform_machine_cycle(const CPU::Z80::PartialMachineCycle &cycle) { | ||||||
| 			// Amstrad CPC timing scheme: assert WAIT for three out of four cycles | 			// Amstrad CPC timing scheme: assert WAIT for three out of four cycles | ||||||
| 			clock_offset_ = (clock_offset_ + cycle.length) & HalfCycles(7); | 			clock_offset_ = (clock_offset_ + cycle.length) & HalfCycles(7); | ||||||
| 			z80_.set_wait_line(clock_offset_ >= HalfCycles(2)); | 			z80_.set_wait_line(clock_offset_ >= HalfCycles(2)); | ||||||
| @@ -593,17 +717,20 @@ class ConcreteMachine: | |||||||
| 			crtc_counter_ += cycle.length; | 			crtc_counter_ += cycle.length; | ||||||
| 			Cycles crtc_cycles = crtc_counter_.divide_cycles(Cycles(4)); | 			Cycles crtc_cycles = crtc_counter_.divide_cycles(Cycles(4)); | ||||||
| 			if(crtc_cycles > Cycles(0)) crtc_.run_for(crtc_cycles); | 			if(crtc_cycles > Cycles(0)) crtc_.run_for(crtc_cycles); | ||||||
| 			z80_.set_interrupt_line(interrupt_timer_.get_request()); |  | ||||||
|  | 			// Check whether that prompted a change in the interrupt line. If so then date | ||||||
|  | 			// it to whenever the cycle was triggered. | ||||||
|  | 			if(interrupt_timer_.request_has_changed()) z80_.set_interrupt_line(interrupt_timer_.get_request(), -crtc_counter_); | ||||||
|  |  | ||||||
| 			// TODO (in the player, not here): adapt it to accept an input clock rate and | 			// TODO (in the player, not here): adapt it to accept an input clock rate and | ||||||
| 			// run_for as HalfCycles | 			// run_for as HalfCycles | ||||||
| 			tape_player_.run_for(cycle.length.as_int()); | 			if(!tape_player_is_sleeping_) tape_player_.run_for(cycle.length.as_int()); | ||||||
|  |  | ||||||
| 			// Pump the AY | 			// Pump the AY | ||||||
| 			ay_.run_for(cycle.length); | 			ay_.run_for(cycle.length); | ||||||
|  |  | ||||||
| 			// Clock the FDC, if connected, using a lazy scale by two | 			// Clock the FDC, if connected, using a lazy scale by two | ||||||
| 			if(has_fdc_) fdc_.run_for(Cycles(cycle.length.as_int())); | 			if(has_fdc_ && !fdc_is_sleeping_) fdc_.run_for(Cycles(cycle.length.as_int())); | ||||||
|  |  | ||||||
| 			// Update typing activity | 			// Update typing activity | ||||||
| 			if(typer_) typer_->run_for(cycle.length); | 			if(typer_) typer_->run_for(cycle.length); | ||||||
| @@ -715,35 +842,32 @@ class ConcreteMachine: | |||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		/// A CRTMachine function; indicates that outputs should be created now. | 		/// A CRTMachine function; indicates that outputs should be created now. | ||||||
| 		void setup_output(float aspect_ratio) { | 		void setup_output(float aspect_ratio) override final { | ||||||
| 			crtc_bus_handler_.setup_output(aspect_ratio); | 			crtc_bus_handler_.setup_output(aspect_ratio); | ||||||
| 			ay_.setup_output(); |  | ||||||
| 			ay_.ay()->set_port_handler(&key_state_); |  | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		/// A CRTMachine function; indicates that outputs should be destroyed now. | 		/// A CRTMachine function; indicates that outputs should be destroyed now. | ||||||
| 		void close_output() { | 		void close_output() override final { | ||||||
| 			crtc_bus_handler_.close_output(); | 			crtc_bus_handler_.close_output(); | ||||||
| 			ay_.close_output(); |  | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		/// @returns the CRT in use. | 		/// @returns the CRT in use. | ||||||
| 		std::shared_ptr<Outputs::CRT::CRT> get_crt() { | 		Outputs::CRT::CRT *get_crt() override final { | ||||||
| 			return crtc_bus_handler_.get_crt(); | 			return crtc_bus_handler_.get_crt(); | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		/// @returns the speaker in use. | 		/// @returns the speaker in use. | ||||||
| 		std::shared_ptr<Outputs::Speaker> get_speaker() { | 		Outputs::Speaker::Speaker *get_speaker() override final { | ||||||
| 			return ay_.get_speaker(); | 			return ay_.get_speaker(); | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		/// Wires virtual-dispatched CRTMachine run_for requests to the static Z80 method. | 		/// Wires virtual-dispatched CRTMachine run_for requests to the static Z80 method. | ||||||
| 		void run_for(const Cycles cycles) { | 		void run_for(const Cycles cycles) override final { | ||||||
| 			z80_.run_for(cycles); | 			z80_.run_for(cycles); | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		/// The ConfigurationTarget entry point; should configure this meachine as described by @c target. | 		/// The ConfigurationTarget entry point; should configure this meachine as described by @c target. | ||||||
| 		void configure_as_target(const StaticAnalyser::Target &target) { | 		void configure_as_target(const StaticAnalyser::Target &target) override final  { | ||||||
| 			switch(target.amstradcpc.model) { | 			switch(target.amstradcpc.model) { | ||||||
| 				case StaticAnalyser::AmstradCPCModel::CPC464: | 				case StaticAnalyser::AmstradCPCModel::CPC464: | ||||||
| 					rom_model_ = ROMType::OS464; | 					rom_model_ = ROMType::OS464; | ||||||
| @@ -776,55 +900,86 @@ class ConcreteMachine: | |||||||
| 			read_pointers_[2] = write_pointers_[2]; | 			read_pointers_[2] = write_pointers_[2]; | ||||||
| 			read_pointers_[3] = roms_[upper_rom_].data(); | 			read_pointers_[3] = roms_[upper_rom_].data(); | ||||||
|  |  | ||||||
|  | 			// Type whatever is required. | ||||||
|  | 			if(target.loading_command.length()) { | ||||||
|  | 				type_string(target.loading_command); | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			insert_media(target.media); | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		bool insert_media(const StaticAnalyser::Media &media) override final { | ||||||
| 			// If there are any tapes supplied, use the first of them. | 			// If there are any tapes supplied, use the first of them. | ||||||
| 			if(!target.tapes.empty()) { | 			if(!media.tapes.empty()) { | ||||||
| 				tape_player_.set_tape(target.tapes.front()); | 				tape_player_.set_tape(media.tapes.front()); | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
| 			// Insert up to four disks. | 			// Insert up to four disks. | ||||||
| 			int c = 0; | 			int c = 0; | ||||||
| 			for(auto &disk : target.disks) { | 			for(auto &disk : media.disks) { | ||||||
| 				fdc_.set_disk(disk, c); | 				fdc_.set_disk(disk, c); | ||||||
| 				c++; | 				c++; | ||||||
| 				if(c == 4) break; | 				if(c == 4) break; | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
| 			// Type whatever is required. | 			return !media.tapes.empty() || (!media.disks.empty() && has_fdc_); | ||||||
| 			if(target.loadingCommand.length()) { |  | ||||||
| 				set_typer_for_string(target.loadingCommand.c_str()); |  | ||||||
| 			} |  | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		// See header; provides the system ROMs. | 		// Obtains the system ROMs. | ||||||
| 		void set_rom(ROMType type, std::vector<uint8_t> data) { | 		bool set_rom_fetcher(const std::function<std::vector<std::unique_ptr<std::vector<uint8_t>>>(const std::string &machine, const std::vector<std::string> &names)> &roms_with_names) override { | ||||||
| 			roms_[(int)type] = data; | 			auto roms = roms_with_names( | ||||||
|  | 				"AmstradCPC", | ||||||
|  | 				{ | ||||||
|  | 					"os464.rom",	"basic464.rom", | ||||||
|  | 					"os664.rom",	"basic664.rom", | ||||||
|  | 					"os6128.rom",	"basic6128.rom", | ||||||
|  | 					"amsdos.rom" | ||||||
|  | 				}); | ||||||
|  |  | ||||||
|  | 			for(std::size_t index = 0; index < roms.size(); ++index) { | ||||||
|  | 				auto &data = roms[index]; | ||||||
|  | 				if(!data) return false; | ||||||
|  | 				roms_[static_cast<int>(index)] = std::move(*data); | ||||||
|  | 				roms_[static_cast<int>(index)].resize(16384); | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
| #pragma mark - Keyboard | 			return true; | ||||||
|  | 		} | ||||||
|  |  | ||||||
| 		void set_typer_for_string(const char *string) { | 		void set_component_is_sleeping(void *component, bool is_sleeping) override final { | ||||||
|  | 			fdc_is_sleeping_ = fdc_.is_sleeping(); | ||||||
|  | 			tape_player_is_sleeping_ = tape_player_.is_sleeping(); | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | // MARK: - Keyboard | ||||||
|  |  | ||||||
|  | 		void type_string(const std::string &string) override final { | ||||||
| 			std::unique_ptr<CharacterMapper> mapper(new CharacterMapper()); | 			std::unique_ptr<CharacterMapper> mapper(new CharacterMapper()); | ||||||
| 			Utility::TypeRecipient::set_typer_for_string(string, std::move(mapper)); | 			Utility::TypeRecipient::add_typer(string, std::move(mapper)); | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		HalfCycles get_typer_delay() { | 		HalfCycles get_typer_delay() override final { | ||||||
| 			return Cycles(4000000);	// Wait 1 second before typing. | 			return Cycles(4000000);	// Wait 1 second before typing. | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		HalfCycles get_typer_frequency() { | 		HalfCycles get_typer_frequency() override final { | ||||||
| 			return Cycles(80000);	// Type one character per frame. | 			return Cycles(160000);	// Type one character per frame. | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		// See header; sets a key as either pressed or released. | 		// See header; sets a key as either pressed or released. | ||||||
| 		void set_key_state(uint16_t key, bool isPressed) { | 		void set_key_state(uint16_t key, bool isPressed) override final { | ||||||
| 			key_state_.set_is_pressed(isPressed, key >> 4, key & 7); | 			key_state_.set_is_pressed(isPressed, key >> 4, key & 7); | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		// See header; sets all keys to released. | 		// See header; sets all keys to released. | ||||||
| 		void clear_all_keys() { | 		void clear_all_keys() override final { | ||||||
| 			key_state_.clear_all_keys(); | 			key_state_.clear_all_keys(); | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
|  | 		KeyboardMapper &get_keyboard_mapper() override { | ||||||
|  | 			return keyboard_mapper_; | ||||||
|  | 		} | ||||||
|  |  | ||||||
| 	private: | 	private: | ||||||
| 		inline void write_to_gate_array(uint8_t value) { | 		inline void write_to_gate_array(uint8_t value) { | ||||||
| 			switch(value >> 6) { | 			switch(value >> 6) { | ||||||
| @@ -871,7 +1026,7 @@ class ConcreteMachine: | |||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		CPU::Z80::Processor<ConcreteMachine> z80_; | 		CPU::Z80::Processor<ConcreteMachine, false, true> z80_; | ||||||
|  |  | ||||||
| 		CRTCBusHandler crtc_bus_handler_; | 		CRTCBusHandler crtc_bus_handler_; | ||||||
| 		Motorola::CRTC::CRTC6845<CRTCBusHandler> crtc_; | 		Motorola::CRTC::CRTC6845<CRTCBusHandler> crtc_; | ||||||
| @@ -893,7 +1048,8 @@ class ConcreteMachine: | |||||||
|  |  | ||||||
| 		std::vector<uint8_t> roms_[7]; | 		std::vector<uint8_t> roms_[7]; | ||||||
| 		int rom_model_; | 		int rom_model_; | ||||||
| 		bool has_fdc_; | 		bool has_fdc_, fdc_is_sleeping_; | ||||||
|  | 		bool tape_player_is_sleeping_; | ||||||
| 		bool has_128k_; | 		bool has_128k_; | ||||||
| 		bool upper_rom_is_paged_; | 		bool upper_rom_is_paged_; | ||||||
| 		int upper_rom_; | 		int upper_rom_; | ||||||
| @@ -903,6 +1059,7 @@ class ConcreteMachine: | |||||||
| 		uint8_t *write_pointers_[4]; | 		uint8_t *write_pointers_[4]; | ||||||
|  |  | ||||||
| 		KeyboardState key_state_; | 		KeyboardState key_state_; | ||||||
|  | 		AmstradCPC::KeyboardMapper keyboard_mapper_; | ||||||
| }; | }; | ||||||
|  |  | ||||||
| } | } | ||||||
|   | |||||||
| @@ -13,39 +13,10 @@ | |||||||
| #include "../CRTMachine.hpp" | #include "../CRTMachine.hpp" | ||||||
| #include "../KeyboardMachine.hpp" | #include "../KeyboardMachine.hpp" | ||||||
|  |  | ||||||
| #include <cstdint> |  | ||||||
| #include <vector> |  | ||||||
|  |  | ||||||
| namespace AmstradCPC { | namespace AmstradCPC { | ||||||
|  |  | ||||||
| enum ROMType: int { |  | ||||||
| 	OS464 = 0,	BASIC464, |  | ||||||
| 	OS664,		BASIC664, |  | ||||||
| 	OS6128,		BASIC6128, |  | ||||||
| 	AMSDOS |  | ||||||
| }; |  | ||||||
|  |  | ||||||
| enum Key: uint16_t { |  | ||||||
| #define Line(l, k1, k2, k3, k4, k5, k6, k7, k8)	\ |  | ||||||
| 	k1 = (l << 4) | 0x07,	k2 = (l << 4) | 0x06,	k3 = (l << 4) | 0x05,	k4 = (l << 4) | 0x04,\ |  | ||||||
| 	k5 = (l << 4) | 0x03,	k6 = (l << 4) | 0x02,	k7 = (l << 4) | 0x01,	k8 = (l << 4) | 0x00, |  | ||||||
|  |  | ||||||
| 	Line(0, KeyFDot,		KeyEnter,			KeyF3,			KeyF6,			KeyF9,					KeyDown,		KeyRight,				KeyUp) |  | ||||||
| 	Line(1, KeyF0,			KeyF2,				KeyF1,			KeyF5,			KeyF8,					KeyF7,			KeyCopy,				KeyLeft) |  | ||||||
| 	Line(2, KeyControl,		KeyBackSlash,		KeyShift,		KeyF4,			KeyRightSquareBracket,	KeyReturn,		KeyLeftSquareBracket,	KeyClear) |  | ||||||
| 	Line(3, KeyFullStop,	KeyForwardSlash,	KeyColon,		KeySemicolon,	KeyP,					KeyAt,			KeyMinus,				KeyCaret) |  | ||||||
| 	Line(4, KeyComma,		KeyM,				KeyK,			KeyL,			KeyI,					KeyO,			Key9,					Key0) |  | ||||||
| 	Line(5, KeySpace,		KeyN,				KeyJ,			KeyH,			KeyY,					KeyU,			Key7,					Key8) |  | ||||||
| 	Line(6, KeyV,			KeyB,				KeyF,			KeyG,			KeyT,					KeyR,			Key5,					Key6) |  | ||||||
| 	Line(7, KeyX,			KeyC,				KeyD,			KeyS,			KeyW,					KeyE,			Key3,					Key4) |  | ||||||
| 	Line(8, KeyZ,			KeyCapsLock,		KeyA,			KeyTab,			KeyQ,					KeyEscape,		Key2,					Key1) |  | ||||||
| 	Line(9, KeyDelete,		KeyJoy1Fire3,		KeyJoy2Fire2,	KeyJoy1Fire1,	KeyJoy1Right,			KeyJoy1Left,	KeyJoy1Down,			KeyJoy1Up) |  | ||||||
|  |  | ||||||
| #undef Line |  | ||||||
| }; |  | ||||||
|  |  | ||||||
| /*! | /*! | ||||||
| 	Models an Amstrad CPC, a CRT-outputting machine with a keyboard that can accept configuration targets. | 	Models an Amstrad CPC. | ||||||
| */ | */ | ||||||
| class Machine: | class Machine: | ||||||
| 	public CRTMachine::Machine, | 	public CRTMachine::Machine, | ||||||
| @@ -54,11 +25,8 @@ class Machine: | |||||||
| 	public: | 	public: | ||||||
| 		virtual ~Machine(); | 		virtual ~Machine(); | ||||||
|  |  | ||||||
| 		/// Creates an returns an Amstrad CPC on the heap. | 		/// Creates and returns an Amstrad CPC. | ||||||
| 		static Machine *AmstradCPC(); | 		static Machine *AmstradCPC(); | ||||||
|  |  | ||||||
| 		/// Sets the contents of rom @c type to @c data. Assumed to be a setup step; has no effect once a machine is running. |  | ||||||
| 		virtual void set_rom(ROMType type, std::vector<uint8_t> data) = 0; |  | ||||||
| }; | }; | ||||||
|  |  | ||||||
| } | } | ||||||
|   | |||||||
| @@ -1,23 +0,0 @@ | |||||||
| // |  | ||||||
| //  CharacterMapper.hpp |  | ||||||
| //  Clock Signal |  | ||||||
| // |  | ||||||
| //  Created by Thomas Harte on 11/08/2017. |  | ||||||
| //  Copyright © 2017 Thomas Harte. All rights reserved. |  | ||||||
| // |  | ||||||
|  |  | ||||||
| #ifndef Machines_AmstradCPC_CharacterMapper_hpp |  | ||||||
| #define Machines_AmstradCPC_CharacterMapper_hpp |  | ||||||
|  |  | ||||||
| #include "../Typer.hpp" |  | ||||||
|  |  | ||||||
| namespace AmstradCPC { |  | ||||||
|  |  | ||||||
| class CharacterMapper: public ::Utility::CharacterMapper { |  | ||||||
| 	public: |  | ||||||
| 		uint16_t *sequence_for_character(char character); |  | ||||||
| }; |  | ||||||
|  |  | ||||||
| } |  | ||||||
|  |  | ||||||
| #endif /* CharacterMapper_hpp */ |  | ||||||
| @@ -1,20 +1,83 @@ | |||||||
| //
 | //
 | ||||||
| //  CharacterMapper.cpp
 | //  Keyboard.cpp
 | ||||||
| //  Clock Signal
 | //  Clock Signal
 | ||||||
| //
 | //
 | ||||||
| //  Created by Thomas Harte on 11/08/2017.
 | //  Created by Thomas Harte on 10/10/2017.
 | ||||||
| //  Copyright © 2017 Thomas Harte. All rights reserved.
 | //  Copyright © 2017 Thomas Harte. All rights reserved.
 | ||||||
| //
 | //
 | ||||||
| 
 | 
 | ||||||
| #include "CharacterMapper.hpp" | #include "Keyboard.hpp" | ||||||
| #include "AmstradCPC.hpp" |  | ||||||
| 
 | 
 | ||||||
| using namespace AmstradCPC; | using namespace AmstradCPC; | ||||||
| 
 | 
 | ||||||
|  | uint16_t KeyboardMapper::mapped_key_for_key(Inputs::Keyboard::Key key) { | ||||||
|  | #define BIND(source, dest)	case Inputs::Keyboard::Key::source:	return dest | ||||||
|  | 	switch(key) { | ||||||
|  | 		default: return KeyCopy; | ||||||
|  | 
 | ||||||
|  | 		BIND(k0, Key0);		BIND(k1, Key1);		BIND(k2, Key2);		BIND(k3, Key3);		BIND(k4, Key4); | ||||||
|  | 		BIND(k5, Key5);		BIND(k6, Key6);		BIND(k7, Key7);		BIND(k8, Key8);		BIND(k9, Key9); | ||||||
|  | 		BIND(Q, KeyQ);		BIND(W, KeyW);		BIND(E, KeyE);		BIND(R, KeyR);		BIND(T, KeyT); | ||||||
|  | 		BIND(Y, KeyY);		BIND(U, KeyU);		BIND(I, KeyI);		BIND(O, KeyO);		BIND(P, KeyP); | ||||||
|  | 		BIND(A, KeyA);		BIND(S, KeyS);		BIND(D, KeyD);		BIND(F, KeyF);		BIND(G, KeyG); | ||||||
|  | 		BIND(H, KeyH);		BIND(J, KeyJ);		BIND(K, KeyK);		BIND(L, KeyL); | ||||||
|  | 		BIND(Z, KeyZ);		BIND(X, KeyX);		BIND(C, KeyC);		BIND(V, KeyV); | ||||||
|  | 		BIND(B, KeyB);		BIND(N, KeyN);		BIND(M, KeyM); | ||||||
|  | 
 | ||||||
|  | 		BIND(Escape, KeyEscape); | ||||||
|  | 		BIND(F1, KeyF1);	BIND(F2, KeyF2);	BIND(F3, KeyF3);	BIND(F4, KeyF4);	BIND(F5, KeyF5); | ||||||
|  | 		BIND(F6, KeyF6);	BIND(F7, KeyF7);	BIND(F8, KeyF8);	BIND(F9, KeyF9);	BIND(F10, KeyF0); | ||||||
|  | 
 | ||||||
|  | 		BIND(F11, KeyRightSquareBracket); | ||||||
|  | 		BIND(F12, KeyClear); | ||||||
|  | 
 | ||||||
|  | 		BIND(Hyphen, KeyMinus);		BIND(Equals, KeyCaret);		BIND(BackSpace, KeyDelete); | ||||||
|  | 		BIND(Tab, KeyTab); | ||||||
|  | 
 | ||||||
|  | 		BIND(OpenSquareBracket, KeyAt); | ||||||
|  | 		BIND(CloseSquareBracket, KeyLeftSquareBracket); | ||||||
|  | 		BIND(BackSlash, KeyBackSlash); | ||||||
|  | 
 | ||||||
|  | 		BIND(CapsLock, KeyCapsLock); | ||||||
|  | 		BIND(Semicolon, KeyColon); | ||||||
|  | 		BIND(Quote, KeySemicolon); | ||||||
|  | 		BIND(Hash, KeyRightSquareBracket); | ||||||
|  | 		BIND(Enter, KeyReturn); | ||||||
|  | 
 | ||||||
|  | 		BIND(LeftShift, KeyShift); | ||||||
|  | 		BIND(Comma, KeyComma); | ||||||
|  | 		BIND(FullStop, KeyFullStop); | ||||||
|  | 		BIND(ForwardSlash, KeyForwardSlash); | ||||||
|  | 		BIND(RightShift, KeyShift); | ||||||
|  | 
 | ||||||
|  | 		BIND(LeftControl, KeyControl);	BIND(LeftOption, KeyControl);	BIND(LeftMeta, KeyControl); | ||||||
|  | 		BIND(Space, KeySpace); | ||||||
|  | 		BIND(RightMeta, KeyControl);	BIND(RightOption, KeyControl);	BIND(RightControl, KeyControl); | ||||||
|  | 
 | ||||||
|  | 		BIND(Left, KeyLeft);	BIND(Right, KeyRight); | ||||||
|  | 		BIND(Up, KeyUp);		BIND(Down, KeyDown); | ||||||
|  | 
 | ||||||
|  | 		BIND(KeyPad0, KeyF0); | ||||||
|  | 		BIND(KeyPad1, KeyF1);		BIND(KeyPad2, KeyF2);		BIND(KeyPad3, KeyF3); | ||||||
|  | 		BIND(KeyPad4, KeyF4);		BIND(KeyPad5, KeyF5);		BIND(KeyPad6, KeyF6); | ||||||
|  | 		BIND(KeyPad7, KeyF7);		BIND(KeyPad8, KeyF8);		BIND(KeyPad9, KeyF9); | ||||||
|  | 		BIND(KeyPadPlus, KeySemicolon); | ||||||
|  | 		BIND(KeyPadMinus, KeyMinus); | ||||||
|  | 
 | ||||||
|  | 		BIND(KeyPadEnter, KeyEnter); | ||||||
|  | 		BIND(KeyPadDecimalPoint, KeyFullStop); | ||||||
|  | 		BIND(KeyPadEquals, KeyMinus); | ||||||
|  | 		BIND(KeyPadSlash, KeyForwardSlash); | ||||||
|  | 		BIND(KeyPadAsterisk, KeyColon); | ||||||
|  | 		BIND(KeyPadDelete, KeyDelete); | ||||||
|  | 	} | ||||||
|  | #undef BIND | ||||||
|  | } | ||||||
|  | 
 | ||||||
| uint16_t *CharacterMapper::sequence_for_character(char character) { | uint16_t *CharacterMapper::sequence_for_character(char character) { | ||||||
| #define KEYS(...)	{__VA_ARGS__, EndSequence} | #define KEYS(...)	{__VA_ARGS__, KeyboardMachine::Machine::KeyEndSequence} | ||||||
| #define SHIFT(...)	{KeyShift, __VA_ARGS__, EndSequence} | #define SHIFT(...)	{KeyShift, __VA_ARGS__, KeyboardMachine::Machine::KeyEndSequence} | ||||||
| #define X			{NotMapped} | #define X			{KeyboardMachine::Machine::KeyNotMapped} | ||||||
| 	static KeySequence key_sequences[] = { | 	static KeySequence key_sequences[] = { | ||||||
| 		/* NUL */	X,							/* SOH */	X, | 		/* NUL */	X,							/* SOH */	X, | ||||||
| 		/* STX */	X,							/* ETX */	X, | 		/* STX */	X,							/* ETX */	X, | ||||||
							
								
								
									
										46
									
								
								Machines/AmstradCPC/Keyboard.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										46
									
								
								Machines/AmstradCPC/Keyboard.hpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,46 @@ | |||||||
|  | // | ||||||
|  | //  Keyboard.hpp | ||||||
|  | //  Clock Signal | ||||||
|  | // | ||||||
|  | //  Created by Thomas Harte on 10/10/2017. | ||||||
|  | //  Copyright © 2017 Thomas Harte. All rights reserved. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | #ifndef Machines_AmstradCPC_Keyboard_hpp | ||||||
|  | #define Machines_AmstradCPC_Keyboard_hpp | ||||||
|  |  | ||||||
|  | #include "../KeyboardMachine.hpp" | ||||||
|  | #include "../Utility/Typer.hpp" | ||||||
|  |  | ||||||
|  | namespace AmstradCPC { | ||||||
|  |  | ||||||
|  | enum Key: uint16_t { | ||||||
|  | #define Line(l, k1, k2, k3, k4, k5, k6, k7, k8)	\ | ||||||
|  | 	k1 = (l << 4) | 0x07,	k2 = (l << 4) | 0x06,	k3 = (l << 4) | 0x05,	k4 = (l << 4) | 0x04,\ | ||||||
|  | 	k5 = (l << 4) | 0x03,	k6 = (l << 4) | 0x02,	k7 = (l << 4) | 0x01,	k8 = (l << 4) | 0x00, | ||||||
|  |  | ||||||
|  | 	Line(0, KeyFDot,		KeyEnter,			KeyF3,			KeyF6,			KeyF9,					KeyDown,		KeyRight,				KeyUp) | ||||||
|  | 	Line(1, KeyF0,			KeyF2,				KeyF1,			KeyF5,			KeyF8,					KeyF7,			KeyCopy,				KeyLeft) | ||||||
|  | 	Line(2, KeyControl,		KeyBackSlash,		KeyShift,		KeyF4,			KeyRightSquareBracket,	KeyReturn,		KeyLeftSquareBracket,	KeyClear) | ||||||
|  | 	Line(3, KeyFullStop,	KeyForwardSlash,	KeyColon,		KeySemicolon,	KeyP,					KeyAt,			KeyMinus,				KeyCaret) | ||||||
|  | 	Line(4, KeyComma,		KeyM,				KeyK,			KeyL,			KeyI,					KeyO,			Key9,					Key0) | ||||||
|  | 	Line(5, KeySpace,		KeyN,				KeyJ,			KeyH,			KeyY,					KeyU,			Key7,					Key8) | ||||||
|  | 	Line(6, KeyV,			KeyB,				KeyF,			KeyG,			KeyT,					KeyR,			Key5,					Key6) | ||||||
|  | 	Line(7, KeyX,			KeyC,				KeyD,			KeyS,			KeyW,					KeyE,			Key3,					Key4) | ||||||
|  | 	Line(8, KeyZ,			KeyCapsLock,		KeyA,			KeyTab,			KeyQ,					KeyEscape,		Key2,					Key1) | ||||||
|  | 	Line(9, KeyDelete,		KeyJoy1Fire3,		KeyJoy2Fire2,	KeyJoy1Fire1,	KeyJoy1Right,			KeyJoy1Left,	KeyJoy1Down,			KeyJoy1Up) | ||||||
|  |  | ||||||
|  | #undef Line | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | struct KeyboardMapper: public KeyboardMachine::Machine::KeyboardMapper { | ||||||
|  | 	uint16_t mapped_key_for_key(Inputs::Keyboard::Key key); | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | struct CharacterMapper: public ::Utility::CharacterMapper { | ||||||
|  | 	uint16_t *sequence_for_character(char character); | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | #endif /* KeyboardMapper_hpp */ | ||||||
| @@ -7,70 +7,120 @@ | |||||||
| // | // | ||||||
|  |  | ||||||
| #include "Atari2600.hpp" | #include "Atari2600.hpp" | ||||||
|  |  | ||||||
| #include <algorithm> | #include <algorithm> | ||||||
| #include <stdio.h> | #include <cstdio> | ||||||
|  |  | ||||||
| #include "Cartridges/CartridgeAtari8k.hpp" | #include "Cartridges/Atari8k.hpp" | ||||||
| #include "Cartridges/CartridgeAtari16k.hpp" | #include "Cartridges/Atari16k.hpp" | ||||||
| #include "Cartridges/CartridgeAtari32k.hpp" | #include "Cartridges/Atari32k.hpp" | ||||||
| #include "Cartridges/CartridgeActivisionStack.hpp" | #include "Cartridges/ActivisionStack.hpp" | ||||||
| #include "Cartridges/CartridgeCBSRAMPlus.hpp" | #include "Cartridges/CBSRAMPlus.hpp" | ||||||
| #include "Cartridges/CartridgeCommaVid.hpp" | #include "Cartridges/CommaVid.hpp" | ||||||
| #include "Cartridges/CartridgeMegaBoy.hpp" | #include "Cartridges/MegaBoy.hpp" | ||||||
| #include "Cartridges/CartridgeMNetwork.hpp" | #include "Cartridges/MNetwork.hpp" | ||||||
| #include "Cartridges/CartridgeParkerBros.hpp" | #include "Cartridges/ParkerBros.hpp" | ||||||
| #include "Cartridges/CartridgePitfall2.hpp" | #include "Cartridges/Pitfall2.hpp" | ||||||
| #include "Cartridges/CartridgeTigervision.hpp" | #include "Cartridges/Tigervision.hpp" | ||||||
| #include "Cartridges/CartridgeUnpaged.hpp" | #include "Cartridges/Unpaged.hpp" | ||||||
|  |  | ||||||
| using namespace Atari2600; |  | ||||||
| namespace { | namespace { | ||||||
| 	static const double NTSC_clock_rate = 1194720; | 	static const double NTSC_clock_rate = 1194720; | ||||||
| 	static const double PAL_clock_rate = 1182298; | 	static const double PAL_clock_rate = 1182298; | ||||||
| } | } | ||||||
|  |  | ||||||
| Machine::Machine() : | namespace Atari2600 { | ||||||
|  |  | ||||||
|  | class Joystick: public Inputs::Joystick { | ||||||
|  | 	public: | ||||||
|  | 		Joystick(Bus *bus, std::size_t shift, std::size_t fire_tia_input) : | ||||||
|  | 			bus_(bus), shift_(shift), fire_tia_input_(fire_tia_input) {} | ||||||
|  |  | ||||||
|  | 		void set_digital_input(DigitalInput digital_input, bool is_active) { | ||||||
|  | 			switch(digital_input) { | ||||||
|  | 				case DigitalInput::Up:		bus_->mos6532_.update_port_input(0, 0x10 >> shift_, is_active);		break; | ||||||
|  | 				case DigitalInput::Down:	bus_->mos6532_.update_port_input(0, 0x20 >> shift_, is_active);		break; | ||||||
|  | 				case DigitalInput::Left:	bus_->mos6532_.update_port_input(0, 0x40 >> shift_, is_active);		break; | ||||||
|  | 				case DigitalInput::Right:	bus_->mos6532_.update_port_input(0, 0x80 >> shift_, is_active);		break; | ||||||
|  |  | ||||||
|  | 				// TODO: latching | ||||||
|  | 				case DigitalInput::Fire: | ||||||
|  | 					if(is_active) | ||||||
|  | 						bus_->tia_input_value_[fire_tia_input_] &= ~0x80; | ||||||
|  | 					else | ||||||
|  | 						bus_->tia_input_value_[fire_tia_input_] |= 0x80; | ||||||
|  | 				break; | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 	private: | ||||||
|  | 		Bus *bus_; | ||||||
|  | 		std::size_t shift_, fire_tia_input_; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | class ConcreteMachine: | ||||||
|  | 	public Machine, | ||||||
|  | 	public Outputs::CRT::Delegate { | ||||||
|  | 	public: | ||||||
|  | 		ConcreteMachine() : | ||||||
| 			frame_record_pointer_(0), | 			frame_record_pointer_(0), | ||||||
| 			is_ntsc_(true) { | 			is_ntsc_(true) { | ||||||
| 			set_clock_rate(NTSC_clock_rate); | 			set_clock_rate(NTSC_clock_rate); | ||||||
| } |  | ||||||
|  |  | ||||||
| void Machine::setup_output(float aspect_ratio) { |  | ||||||
| 	bus_->tia_.reset(new TIA); |  | ||||||
| 	bus_->speaker_.reset(new Speaker); |  | ||||||
| 	bus_->speaker_->set_input_rate((float)(get_clock_rate() / (double)CPUTicksPerAudioTick)); |  | ||||||
| 	bus_->tia_->get_crt()->set_delegate(this); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void Machine::close_output() { |  | ||||||
| 	bus_.reset(); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| Machine::~Machine() { |  | ||||||
| 	close_output(); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void Machine::set_digital_input(Atari2600DigitalInput input, bool state) { |  | ||||||
| 	switch (input) { |  | ||||||
| 		case Atari2600DigitalInputJoy1Up:		bus_->mos6532_.update_port_input(0, 0x10, state);	break; |  | ||||||
| 		case Atari2600DigitalInputJoy1Down:		bus_->mos6532_.update_port_input(0, 0x20, state);	break; |  | ||||||
| 		case Atari2600DigitalInputJoy1Left:		bus_->mos6532_.update_port_input(0, 0x40, state);	break; |  | ||||||
| 		case Atari2600DigitalInputJoy1Right:	bus_->mos6532_.update_port_input(0, 0x80, state);	break; |  | ||||||
|  |  | ||||||
| 		case Atari2600DigitalInputJoy2Up:		bus_->mos6532_.update_port_input(0, 0x01, state);	break; |  | ||||||
| 		case Atari2600DigitalInputJoy2Down:		bus_->mos6532_.update_port_input(0, 0x02, state);	break; |  | ||||||
| 		case Atari2600DigitalInputJoy2Left:		bus_->mos6532_.update_port_input(0, 0x04, state);	break; |  | ||||||
| 		case Atari2600DigitalInputJoy2Right:	bus_->mos6532_.update_port_input(0, 0x08, state);	break; |  | ||||||
|  |  | ||||||
| 		// TODO: latching |  | ||||||
| 		case Atari2600DigitalInputJoy1Fire:		if(state) bus_->tia_input_value_[0] &= ~0x80; else bus_->tia_input_value_[0] |= 0x80; break; |  | ||||||
| 		case Atari2600DigitalInputJoy2Fire:		if(state) bus_->tia_input_value_[1] &= ~0x80; else bus_->tia_input_value_[1] |= 0x80; break; |  | ||||||
|  |  | ||||||
| 		default: break; |  | ||||||
| 		} | 		} | ||||||
| } |  | ||||||
|  |  | ||||||
| void Machine::set_switch_is_enabled(Atari2600Switch input, bool state) { | 		~ConcreteMachine() { | ||||||
|  | 			close_output(); | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		void configure_as_target(const StaticAnalyser::Target &target) override { | ||||||
|  | 			const std::vector<uint8_t> &rom = target.media.cartridges.front()->get_segments().front().data; | ||||||
|  | 			switch(target.atari.paging_model) { | ||||||
|  | 				case StaticAnalyser::Atari2600PagingModel::ActivisionStack:	bus_.reset(new Cartridge::Cartridge<Cartridge::ActivisionStack>(rom));	break; | ||||||
|  | 				case StaticAnalyser::Atari2600PagingModel::CBSRamPlus:		bus_.reset(new Cartridge::Cartridge<Cartridge::CBSRAMPlus>(rom));		break; | ||||||
|  | 				case StaticAnalyser::Atari2600PagingModel::CommaVid:		bus_.reset(new Cartridge::Cartridge<Cartridge::CommaVid>(rom));			break; | ||||||
|  | 				case StaticAnalyser::Atari2600PagingModel::MegaBoy:			bus_.reset(new Cartridge::Cartridge<Cartridge::MegaBoy>(rom));			break; | ||||||
|  | 				case StaticAnalyser::Atari2600PagingModel::MNetwork:		bus_.reset(new Cartridge::Cartridge<Cartridge::MNetwork>(rom));			break; | ||||||
|  | 				case StaticAnalyser::Atari2600PagingModel::None:			bus_.reset(new Cartridge::Cartridge<Cartridge::Unpaged>(rom));			break; | ||||||
|  | 				case StaticAnalyser::Atari2600PagingModel::ParkerBros:		bus_.reset(new Cartridge::Cartridge<Cartridge::ParkerBros>(rom));		break; | ||||||
|  | 				case StaticAnalyser::Atari2600PagingModel::Pitfall2:		bus_.reset(new Cartridge::Cartridge<Cartridge::Pitfall2>(rom));			break; | ||||||
|  | 				case StaticAnalyser::Atari2600PagingModel::Tigervision:		bus_.reset(new Cartridge::Cartridge<Cartridge::Tigervision>(rom));		break; | ||||||
|  |  | ||||||
|  | 				case StaticAnalyser::Atari2600PagingModel::Atari8k: | ||||||
|  | 					if(target.atari.uses_superchip) { | ||||||
|  | 						bus_.reset(new Cartridge::Cartridge<Cartridge::Atari8kSuperChip>(rom)); | ||||||
|  | 					} else { | ||||||
|  | 						bus_.reset(new Cartridge::Cartridge<Cartridge::Atari8k>(rom)); | ||||||
|  | 					} | ||||||
|  | 				break; | ||||||
|  | 				case StaticAnalyser::Atari2600PagingModel::Atari16k: | ||||||
|  | 					if(target.atari.uses_superchip) { | ||||||
|  | 						bus_.reset(new Cartridge::Cartridge<Cartridge::Atari16kSuperChip>(rom)); | ||||||
|  | 					} else { | ||||||
|  | 						bus_.reset(new Cartridge::Cartridge<Cartridge::Atari16k>(rom)); | ||||||
|  | 					} | ||||||
|  | 				break; | ||||||
|  | 				case StaticAnalyser::Atari2600PagingModel::Atari32k: | ||||||
|  | 					if(target.atari.uses_superchip) { | ||||||
|  | 						bus_.reset(new Cartridge::Cartridge<Cartridge::Atari32kSuperChip>(rom)); | ||||||
|  | 					} else { | ||||||
|  | 						bus_.reset(new Cartridge::Cartridge<Cartridge::Atari32k>(rom)); | ||||||
|  | 					} | ||||||
|  | 				break; | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			joysticks_.emplace_back(new Joystick(bus_.get(), 0, 0)); | ||||||
|  | 			joysticks_.emplace_back(new Joystick(bus_.get(), 4, 1)); | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		bool insert_media(const StaticAnalyser::Media &media) override { | ||||||
|  | 			return false; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		std::vector<std::unique_ptr<Inputs::Joystick>> &get_joysticks() override { | ||||||
|  | 			return joysticks_; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		void set_switch_is_enabled(Atari2600Switch input, bool state) override { | ||||||
| 			switch(input) { | 			switch(input) { | ||||||
| 				case Atari2600SwitchReset:					bus_->mos6532_.update_port_input(1, 0x01, state);	break; | 				case Atari2600SwitchReset:					bus_->mos6532_.update_port_input(1, 0x01, state);	break; | ||||||
| 				case Atari2600SwitchSelect:					bus_->mos6532_.update_port_input(1, 0x02, state);	break; | 				case Atari2600SwitchSelect:					bus_->mos6532_.update_port_input(1, 0x02, state);	break; | ||||||
| @@ -78,49 +128,38 @@ void Machine::set_switch_is_enabled(Atari2600Switch input, bool state) { | |||||||
| 				case Atari2600SwitchLeftPlayerDifficulty:	bus_->mos6532_.update_port_input(1, 0x40, state);	break; | 				case Atari2600SwitchLeftPlayerDifficulty:	bus_->mos6532_.update_port_input(1, 0x40, state);	break; | ||||||
| 				case Atari2600SwitchRightPlayerDifficulty:	bus_->mos6532_.update_port_input(1, 0x80, state);	break; | 				case Atari2600SwitchRightPlayerDifficulty:	bus_->mos6532_.update_port_input(1, 0x80, state);	break; | ||||||
| 			} | 			} | ||||||
| } |  | ||||||
|  |  | ||||||
| void Machine::configure_as_target(const StaticAnalyser::Target &target) { |  | ||||||
| 	const std::vector<uint8_t> &rom = target.cartridges.front()->get_segments().front().data; |  | ||||||
| 	switch(target.atari.paging_model) { |  | ||||||
| 		case StaticAnalyser::Atari2600PagingModel::ActivisionStack:	bus_.reset(new CartridgeActivisionStack(rom));	break; |  | ||||||
| 		case StaticAnalyser::Atari2600PagingModel::CBSRamPlus:		bus_.reset(new CartridgeCBSRAMPlus(rom));		break; |  | ||||||
| 		case StaticAnalyser::Atari2600PagingModel::CommaVid:		bus_.reset(new CartridgeCommaVid(rom));			break; |  | ||||||
| 		case StaticAnalyser::Atari2600PagingModel::MegaBoy:			bus_.reset(new CartridgeMegaBoy(rom));			break; |  | ||||||
| 		case StaticAnalyser::Atari2600PagingModel::MNetwork:		bus_.reset(new CartridgeMNetwork(rom));			break; |  | ||||||
| 		case StaticAnalyser::Atari2600PagingModel::None:			bus_.reset(new CartridgeUnpaged(rom));			break; |  | ||||||
| 		case StaticAnalyser::Atari2600PagingModel::ParkerBros:		bus_.reset(new CartridgeParkerBros(rom));		break; |  | ||||||
| 		case StaticAnalyser::Atari2600PagingModel::Pitfall2:		bus_.reset(new CartridgePitfall2(rom));			break; |  | ||||||
| 		case StaticAnalyser::Atari2600PagingModel::Tigervision:		bus_.reset(new CartridgeTigervision(rom));		break; |  | ||||||
|  |  | ||||||
| 		case StaticAnalyser::Atari2600PagingModel::Atari8k: |  | ||||||
| 			if(target.atari.uses_superchip) { |  | ||||||
| 				bus_.reset(new CartridgeAtari8kSuperChip(rom)); |  | ||||||
| 			} else { |  | ||||||
| 				bus_.reset(new CartridgeAtari8k(rom)); |  | ||||||
| 		} | 		} | ||||||
| 		break; |  | ||||||
| 		case StaticAnalyser::Atari2600PagingModel::Atari16k: |  | ||||||
| 			if(target.atari.uses_superchip) { |  | ||||||
| 				bus_.reset(new CartridgeAtari16kSuperChip(rom)); |  | ||||||
| 			} else { |  | ||||||
| 				bus_.reset(new CartridgeAtari16k(rom)); |  | ||||||
| 			} |  | ||||||
| 		break; |  | ||||||
| 		case StaticAnalyser::Atari2600PagingModel::Atari32k: |  | ||||||
| 			if(target.atari.uses_superchip) { |  | ||||||
| 				bus_.reset(new CartridgeAtari32kSuperChip(rom)); |  | ||||||
| 			} else { |  | ||||||
| 				bus_.reset(new CartridgeAtari32k(rom)); |  | ||||||
| 			} |  | ||||||
| 		break; |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
|  |  | ||||||
| #pragma mark - CRT delegate | 		void set_reset_switch(bool state) override { | ||||||
|  | 			bus_->set_reset_line(state); | ||||||
|  | 		} | ||||||
|  |  | ||||||
| void Machine::crt_did_end_batch_of_frames(Outputs::CRT::CRT *crt, unsigned int number_of_frames, unsigned int number_of_unexpected_vertical_syncs) { | 		// to satisfy CRTMachine::Machine | ||||||
| 	const size_t number_of_frame_records = sizeof(frame_records_) / sizeof(frame_records_[0]); | 		void setup_output(float aspect_ratio) override { | ||||||
|  | 			bus_->tia_.reset(new TIA); | ||||||
|  | 			bus_->speaker_.set_input_rate(static_cast<float>(get_clock_rate() / static_cast<double>(CPUTicksPerAudioTick))); | ||||||
|  | 			bus_->tia_->get_crt()->set_delegate(this); | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		void close_output() override { | ||||||
|  | 			bus_.reset(); | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		Outputs::CRT::CRT *get_crt() override { | ||||||
|  | 			return bus_->tia_->get_crt(); | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		Outputs::Speaker::Speaker *get_speaker() override { | ||||||
|  | 			return &bus_->speaker_; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		void run_for(const Cycles cycles) override { | ||||||
|  | 			bus_->run_for(cycles); | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		// to satisfy Outputs::CRT::Delegate | ||||||
|  | 		void crt_did_end_batch_of_frames(Outputs::CRT::CRT *crt, unsigned int number_of_frames, unsigned int number_of_unexpected_vertical_syncs) override { | ||||||
|  | 			const std::size_t number_of_frame_records = sizeof(frame_records_) / sizeof(frame_records_[0]); | ||||||
| 			frame_records_[frame_record_pointer_ % number_of_frame_records].number_of_frames = number_of_frames; | 			frame_records_[frame_record_pointer_ % number_of_frame_records].number_of_frames = number_of_frames; | ||||||
| 			frame_records_[frame_record_pointer_ % number_of_frame_records].number_of_unexpected_vertical_syncs = number_of_unexpected_vertical_syncs; | 			frame_records_[frame_record_pointer_ % number_of_frame_records].number_of_unexpected_vertical_syncs = number_of_unexpected_vertical_syncs; | ||||||
| 			frame_record_pointer_ ++; | 			frame_record_pointer_ ++; | ||||||
| @@ -128,13 +167,13 @@ void Machine::crt_did_end_batch_of_frames(Outputs::CRT::CRT *crt, unsigned int n | |||||||
| 			if(frame_record_pointer_ >= 6) { | 			if(frame_record_pointer_ >= 6) { | ||||||
| 				unsigned int total_number_of_frames = 0; | 				unsigned int total_number_of_frames = 0; | ||||||
| 				unsigned int total_number_of_unexpected_vertical_syncs = 0; | 				unsigned int total_number_of_unexpected_vertical_syncs = 0; | ||||||
| 		for(size_t c = 0; c < number_of_frame_records; c++) { | 				for(std::size_t c = 0; c < number_of_frame_records; c++) { | ||||||
| 					total_number_of_frames += frame_records_[c].number_of_frames; | 					total_number_of_frames += frame_records_[c].number_of_frames; | ||||||
| 					total_number_of_unexpected_vertical_syncs += frame_records_[c].number_of_unexpected_vertical_syncs; | 					total_number_of_unexpected_vertical_syncs += frame_records_[c].number_of_unexpected_vertical_syncs; | ||||||
| 				} | 				} | ||||||
|  |  | ||||||
| 				if(total_number_of_unexpected_vertical_syncs >= total_number_of_frames >> 1) { | 				if(total_number_of_unexpected_vertical_syncs >= total_number_of_frames >> 1) { | ||||||
| 			for(size_t c = 0; c < number_of_frame_records; c++) { | 					for(std::size_t c = 0; c < number_of_frame_records; c++) { | ||||||
| 						frame_records_[c].number_of_frames = 0; | 						frame_records_[c].number_of_frames = 0; | ||||||
| 						frame_records_[c].number_of_unexpected_vertical_syncs = 0; | 						frame_records_[c].number_of_unexpected_vertical_syncs = 0; | ||||||
| 					} | 					} | ||||||
| @@ -149,9 +188,35 @@ void Machine::crt_did_end_batch_of_frames(Outputs::CRT::CRT *crt, unsigned int n | |||||||
| 						bus_->tia_->set_output_mode(TIA::OutputMode::PAL); | 						bus_->tia_->set_output_mode(TIA::OutputMode::PAL); | ||||||
| 					} | 					} | ||||||
|  |  | ||||||
| 			bus_->speaker_->set_input_rate((float)(clock_rate / (double)CPUTicksPerAudioTick)); | 					bus_->speaker_.set_input_rate(static_cast<float>(clock_rate / static_cast<double>(CPUTicksPerAudioTick))); | ||||||
| 			bus_->speaker_->set_high_frequency_cut_off((float)(clock_rate / ((double)CPUTicksPerAudioTick * 2.0))); | 					bus_->speaker_.set_high_frequency_cutoff(static_cast<float>(clock_rate / (static_cast<double>(CPUTicksPerAudioTick) * 2.0))); | ||||||
| 					set_clock_rate(clock_rate); | 					set_clock_rate(clock_rate); | ||||||
| 				} | 				} | ||||||
| 			} | 			} | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 	private: | ||||||
|  | 		// the bus | ||||||
|  | 		std::unique_ptr<Bus> bus_; | ||||||
|  |  | ||||||
|  | 		// output frame rate tracker | ||||||
|  | 		struct FrameRecord { | ||||||
|  | 			unsigned int number_of_frames; | ||||||
|  | 			unsigned int number_of_unexpected_vertical_syncs; | ||||||
|  |  | ||||||
|  | 			FrameRecord() : number_of_frames(0), number_of_unexpected_vertical_syncs(0) {} | ||||||
|  | 		} frame_records_[4]; | ||||||
|  | 		unsigned int frame_record_pointer_; | ||||||
|  | 		bool is_ntsc_; | ||||||
|  | 		std::vector<std::unique_ptr<Inputs::Joystick>> joysticks_; | ||||||
|  | }; | ||||||
|  |  | ||||||
| } | } | ||||||
|  |  | ||||||
|  | using namespace Atari2600; | ||||||
|  |  | ||||||
|  | Machine *Machine::Atari2600() { | ||||||
|  | 	return new Atari2600::ConcreteMachine; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | Machine::~Machine() {} | ||||||
|   | |||||||
| @@ -9,59 +9,32 @@ | |||||||
| #ifndef Atari2600_cpp | #ifndef Atari2600_cpp | ||||||
| #define Atari2600_cpp | #define Atari2600_cpp | ||||||
|  |  | ||||||
| #include <stdint.h> |  | ||||||
|  |  | ||||||
| #include "../../Processors/6502/6502.hpp" |  | ||||||
| #include "../CRTMachine.hpp" |  | ||||||
| #include "Bus.hpp" |  | ||||||
| #include "PIA.hpp" |  | ||||||
| #include "Speaker.hpp" |  | ||||||
| #include "TIA.hpp" |  | ||||||
|  |  | ||||||
| #include "../ConfigurationTarget.hpp" | #include "../ConfigurationTarget.hpp" | ||||||
|  | #include "../CRTMachine.hpp" | ||||||
|  | #include "../JoystickMachine.hpp" | ||||||
|  |  | ||||||
| #include "Atari2600Inputs.h" | #include "Atari2600Inputs.h" | ||||||
|  |  | ||||||
| namespace Atari2600 { | namespace Atari2600 { | ||||||
|  |  | ||||||
|  | /*! | ||||||
|  | 	Models an Atari 2600. | ||||||
|  | */ | ||||||
| class Machine: | class Machine: | ||||||
| 	public CRTMachine::Machine, | 	public CRTMachine::Machine, | ||||||
| 	public ConfigurationTarget::Machine, | 	public ConfigurationTarget::Machine, | ||||||
| 	public Outputs::CRT::Delegate { | 	public JoystickMachine::Machine { | ||||||
|  |  | ||||||
| 	public: | 	public: | ||||||
| 		Machine(); | 		virtual ~Machine(); | ||||||
| 		~Machine(); |  | ||||||
|  |  | ||||||
| 		void configure_as_target(const StaticAnalyser::Target &target); | 		/// Creates and returns an Atari 2600 on the heap. | ||||||
| 		void switch_region(); | 		static Machine *Atari2600(); | ||||||
|  |  | ||||||
| 		void set_digital_input(Atari2600DigitalInput input, bool state); | 		/// Sets the switch @c input to @c state. | ||||||
| 		void set_switch_is_enabled(Atari2600Switch input, bool state); | 		virtual void set_switch_is_enabled(Atari2600Switch input, bool state) = 0; | ||||||
| 		void set_reset_line(bool state) { bus_->set_reset_line(state); } |  | ||||||
|  |  | ||||||
| 		// to satisfy CRTMachine::Machine | 		// Presses or releases the reset button. | ||||||
| 		virtual void setup_output(float aspect_ratio); | 		virtual void set_reset_switch(bool state) = 0; | ||||||
| 		virtual void close_output(); |  | ||||||
| 		virtual std::shared_ptr<Outputs::CRT::CRT> get_crt() { return bus_->tia_->get_crt(); } |  | ||||||
| 		virtual std::shared_ptr<Outputs::Speaker> get_speaker() { return bus_->speaker_; } |  | ||||||
| 		virtual void run_for(const Cycles cycles) { bus_->run_for(cycles); } |  | ||||||
|  |  | ||||||
| 		// to satisfy Outputs::CRT::Delegate |  | ||||||
| 		virtual void crt_did_end_batch_of_frames(Outputs::CRT::CRT *crt, unsigned int number_of_frames, unsigned int number_of_unexpected_vertical_syncs); |  | ||||||
|  |  | ||||||
| 	private: |  | ||||||
| 		// the bus |  | ||||||
| 		std::unique_ptr<Bus> bus_; |  | ||||||
|  |  | ||||||
| 		// output frame rate tracker |  | ||||||
| 		struct FrameRecord { |  | ||||||
| 			unsigned int number_of_frames; |  | ||||||
| 			unsigned int number_of_unexpected_vertical_syncs; |  | ||||||
|  |  | ||||||
| 			FrameRecord() : number_of_frames(0), number_of_unexpected_vertical_syncs(0) {} |  | ||||||
| 		} frame_records_[4]; |  | ||||||
| 		unsigned int frame_record_pointer_; |  | ||||||
| 		bool is_ntsc_; |  | ||||||
| }; | }; | ||||||
|  |  | ||||||
| } | } | ||||||
|   | |||||||
| @@ -11,16 +11,19 @@ | |||||||
|  |  | ||||||
| #include "Atari2600.hpp" | #include "Atari2600.hpp" | ||||||
| #include "PIA.hpp" | #include "PIA.hpp" | ||||||
| #include "Speaker.hpp" |  | ||||||
| #include "TIA.hpp" | #include "TIA.hpp" | ||||||
|  | #include "TIASound.hpp" | ||||||
|  |  | ||||||
| #include "../../ClockReceiver/ClockReceiver.hpp" | #include "../../ClockReceiver/ClockReceiver.hpp" | ||||||
|  | #include "../../Outputs/Speaker/Implementation/LowpassSpeaker.hpp" | ||||||
|  |  | ||||||
| namespace Atari2600 { | namespace Atari2600 { | ||||||
|  |  | ||||||
| class Bus { | class Bus { | ||||||
| 	public: | 	public: | ||||||
| 		Bus() : | 		Bus() : | ||||||
|  | 			tia_sound_(audio_queue_), | ||||||
|  | 			speaker_(tia_sound_), | ||||||
| 			tia_input_value_{0xff, 0xff}, | 			tia_input_value_{0xff, 0xff}, | ||||||
| 			cycles_since_speaker_update_(0) {} | 			cycles_since_speaker_update_(0) {} | ||||||
|  |  | ||||||
| @@ -30,7 +33,10 @@ class Bus { | |||||||
| 		// the RIOT, TIA and speaker | 		// the RIOT, TIA and speaker | ||||||
| 		PIA mos6532_; | 		PIA mos6532_; | ||||||
| 		std::shared_ptr<TIA> tia_; | 		std::shared_ptr<TIA> tia_; | ||||||
| 		std::shared_ptr<Speaker> speaker_; |  | ||||||
|  | 		Concurrency::DeferringAsyncTaskQueue audio_queue_; | ||||||
|  | 		TIASound tia_sound_; | ||||||
|  | 		Outputs::Speaker::LowpassSpeaker<TIASound> speaker_; | ||||||
|  |  | ||||||
| 		// joystick state | 		// joystick state | ||||||
| 		uint8_t tia_input_value_[2]; | 		uint8_t tia_input_value_[2]; | ||||||
| @@ -39,7 +45,7 @@ class Bus { | |||||||
| 		// speaker backlog accumlation counter | 		// speaker backlog accumlation counter | ||||||
| 		Cycles cycles_since_speaker_update_; | 		Cycles cycles_since_speaker_update_; | ||||||
| 		inline void update_audio() { | 		inline void update_audio() { | ||||||
| 			speaker_->run_for(cycles_since_speaker_update_.divide(Cycles(CPUTicksPerAudioTick * 3))); | 			speaker_.run_for(audio_queue_, cycles_since_speaker_update_.divide(Cycles(CPUTicksPerAudioTick * 3))); | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		// video backlog accumulation counter | 		// video backlog accumulation counter | ||||||
|   | |||||||
| @@ -6,18 +6,18 @@ | |||||||
| //  Copyright © 2017 Thomas Harte. All rights reserved.
 | //  Copyright © 2017 Thomas Harte. All rights reserved.
 | ||||||
| //
 | //
 | ||||||
| 
 | 
 | ||||||
| #ifndef Atari2600_CartridgeActivisionStack_hpp | #ifndef Atari2600_ActivisionStack_hpp | ||||||
| #define Atari2600_CartridgeActivisionStack_hpp | #define Atari2600_ActivisionStack_hpp | ||||||
| 
 | 
 | ||||||
| namespace Atari2600 { | namespace Atari2600 { | ||||||
|  | namespace Cartridge { | ||||||
| 
 | 
 | ||||||
| class CartridgeActivisionStack: public Cartridge<CartridgeActivisionStack> { | class ActivisionStack: public BusExtender { | ||||||
| 	public: | 	public: | ||||||
| 		CartridgeActivisionStack(const std::vector<uint8_t> &rom) : | 		ActivisionStack(uint8_t *rom_base, std::size_t rom_size) : | ||||||
| 			Cartridge(rom), | 			BusExtender(rom_base, rom_size), | ||||||
| 			last_opcode_(0x00) { | 			rom_ptr_(rom_base), | ||||||
| 			rom_ptr_ = rom_.data(); | 			last_opcode_(0x00) {} | ||||||
| 		} |  | ||||||
| 
 | 
 | ||||||
| 		void perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value) { | 		void perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value) { | ||||||
| 			if(!(address & 0x1000)) return; | 			if(!(address & 0x1000)) return; | ||||||
| @@ -27,9 +27,9 @@ class CartridgeActivisionStack: public Cartridge<CartridgeActivisionStack> { | |||||||
| 			// RST or JSR.
 | 			// RST or JSR.
 | ||||||
| 			if(operation == CPU::MOS6502::BusOperation::ReadOpcode && (last_opcode_ == 0x20 || last_opcode_ == 0x60)) { | 			if(operation == CPU::MOS6502::BusOperation::ReadOpcode && (last_opcode_ == 0x20 || last_opcode_ == 0x60)) { | ||||||
| 				if(address & 0x2000) { | 				if(address & 0x2000) { | ||||||
| 					rom_ptr_ = rom_.data(); | 					rom_ptr_ = rom_base_; | ||||||
| 				} else { | 				} else { | ||||||
| 					rom_ptr_ = rom_.data() + 4096; | 					rom_ptr_ = rom_base_ + 4096; | ||||||
| 				} | 				} | ||||||
| 			} | 			} | ||||||
| 
 | 
 | ||||||
| @@ -45,6 +45,7 @@ class CartridgeActivisionStack: public Cartridge<CartridgeActivisionStack> { | |||||||
| 		uint8_t last_opcode_; | 		uint8_t last_opcode_; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
|  | } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| #endif /* Atari2600_CartridgeActivisionStack_hpp */ | #endif /* Atari2600_CartridgeActivisionStack_hpp */ | ||||||
| @@ -12,19 +12,19 @@ | |||||||
| #include "Cartridge.hpp" | #include "Cartridge.hpp" | ||||||
| 
 | 
 | ||||||
| namespace Atari2600 { | namespace Atari2600 { | ||||||
|  | namespace Cartridge { | ||||||
| 
 | 
 | ||||||
| class CartridgeAtari16k: public Cartridge<CartridgeAtari16k> { | class Atari16k: public BusExtender { | ||||||
| 	public: | 	public: | ||||||
| 		CartridgeAtari16k(const std::vector<uint8_t> &rom) : | 		Atari16k(uint8_t *rom_base, std::size_t rom_size) : | ||||||
| 			Cartridge(rom) { | 			BusExtender(rom_base, rom_size), | ||||||
| 			rom_ptr_ = rom_.data(); | 			rom_ptr_(rom_base) {} | ||||||
| 		} |  | ||||||
| 
 | 
 | ||||||
| 		void perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value) { | 		void perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value) { | ||||||
| 			address &= 0x1fff; | 			address &= 0x1fff; | ||||||
| 			if(!(address & 0x1000)) return; | 			if(!(address & 0x1000)) return; | ||||||
| 
 | 
 | ||||||
| 			if(address >= 0x1ff6 && address <= 0x1ff9) rom_ptr_ = rom_.data() + (address - 0x1ff6) * 4096; | 			if(address >= 0x1ff6 && address <= 0x1ff9) rom_ptr_ = rom_base_ + (address - 0x1ff6) * 4096; | ||||||
| 
 | 
 | ||||||
| 			if(isReadOperation(operation)) { | 			if(isReadOperation(operation)) { | ||||||
| 				*value = rom_ptr_[address & 4095]; | 				*value = rom_ptr_[address & 4095]; | ||||||
| @@ -35,18 +35,17 @@ class CartridgeAtari16k: public Cartridge<CartridgeAtari16k> { | |||||||
| 		uint8_t *rom_ptr_; | 		uint8_t *rom_ptr_; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| class CartridgeAtari16kSuperChip: public Cartridge<CartridgeAtari16kSuperChip> { | class Atari16kSuperChip: public BusExtender { | ||||||
| 	public: | 	public: | ||||||
| 		CartridgeAtari16kSuperChip(const std::vector<uint8_t> &rom) : | 		Atari16kSuperChip(uint8_t *rom_base, std::size_t rom_size) : | ||||||
| 			Cartridge(rom) { | 			BusExtender(rom_base, rom_size), | ||||||
| 			rom_ptr_ = rom_.data(); | 			rom_ptr_(rom_base) {} | ||||||
| 		} |  | ||||||
| 
 | 
 | ||||||
| 		void perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value) { | 		void perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value) { | ||||||
| 			address &= 0x1fff; | 			address &= 0x1fff; | ||||||
| 			if(!(address & 0x1000)) return; | 			if(!(address & 0x1000)) return; | ||||||
| 
 | 
 | ||||||
| 			if(address >= 0x1ff6 && address <= 0x1ff9) rom_ptr_ = rom_.data() + (address - 0x1ff6) * 4096; | 			if(address >= 0x1ff6 && address <= 0x1ff9) rom_ptr_ = rom_base_ + (address - 0x1ff6) * 4096; | ||||||
| 
 | 
 | ||||||
| 			if(isReadOperation(operation)) { | 			if(isReadOperation(operation)) { | ||||||
| 				*value = rom_ptr_[address & 4095]; | 				*value = rom_ptr_[address & 4095]; | ||||||
| @@ -61,6 +60,7 @@ class CartridgeAtari16kSuperChip: public Cartridge<CartridgeAtari16kSuperChip> { | |||||||
| 		uint8_t ram_[128]; | 		uint8_t ram_[128]; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
|  | } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| #endif /* Atari2600_CartridgeAtari16k_hpp */ | #endif /* Atari2600_CartridgeAtari16k_hpp */ | ||||||
| @@ -12,19 +12,17 @@ | |||||||
| #include "Cartridge.hpp" | #include "Cartridge.hpp" | ||||||
| 
 | 
 | ||||||
| namespace Atari2600 { | namespace Atari2600 { | ||||||
|  | namespace Cartridge { | ||||||
| 
 | 
 | ||||||
| class CartridgeAtari32k: public Cartridge<CartridgeAtari32k> { | class Atari32k: public BusExtender { | ||||||
| 	public: | 	public: | ||||||
| 		CartridgeAtari32k(const std::vector<uint8_t> &rom) : | 		Atari32k(uint8_t *rom_base, std::size_t rom_size) : BusExtender(rom_base, rom_size), rom_ptr_(rom_base) {} | ||||||
| 			Cartridge(rom) { |  | ||||||
| 			rom_ptr_ = rom_.data(); |  | ||||||
| 		} |  | ||||||
| 
 | 
 | ||||||
| 		void perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value) { | 		void perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value) { | ||||||
| 			address &= 0x1fff; | 			address &= 0x1fff; | ||||||
| 			if(!(address & 0x1000)) return; | 			if(!(address & 0x1000)) return; | ||||||
| 
 | 
 | ||||||
| 			if(address >= 0x1ff4 && address <= 0x1ffb) rom_ptr_ = rom_.data() + (address - 0x1ff4) * 4096; | 			if(address >= 0x1ff4 && address <= 0x1ffb) rom_ptr_ = rom_base_ + (address - 0x1ff4) * 4096; | ||||||
| 
 | 
 | ||||||
| 			if(isReadOperation(operation)) { | 			if(isReadOperation(operation)) { | ||||||
| 				*value = rom_ptr_[address & 4095]; | 				*value = rom_ptr_[address & 4095]; | ||||||
| @@ -35,18 +33,15 @@ class CartridgeAtari32k: public Cartridge<CartridgeAtari32k> { | |||||||
| 		uint8_t *rom_ptr_; | 		uint8_t *rom_ptr_; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| class CartridgeAtari32kSuperChip: public Cartridge<CartridgeAtari32kSuperChip> { | class Atari32kSuperChip: public BusExtender { | ||||||
| 	public: | 	public: | ||||||
| 		CartridgeAtari32kSuperChip(const std::vector<uint8_t> &rom) : | 		Atari32kSuperChip(uint8_t *rom_base, std::size_t rom_size) : BusExtender(rom_base, rom_size), rom_ptr_(rom_base) {} | ||||||
| 			Cartridge(rom) { |  | ||||||
| 			rom_ptr_ = rom_.data(); |  | ||||||
| 		} |  | ||||||
| 
 | 
 | ||||||
| 		void perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value) { | 		void perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value) { | ||||||
| 			address &= 0x1fff; | 			address &= 0x1fff; | ||||||
| 			if(!(address & 0x1000)) return; | 			if(!(address & 0x1000)) return; | ||||||
| 
 | 
 | ||||||
| 			if(address >= 0x1ff4 && address <= 0x1ffb) rom_ptr_ = rom_.data() + (address - 0x1ff4) * 4096; | 			if(address >= 0x1ff4 && address <= 0x1ffb) rom_ptr_ = rom_base_ + (address - 0x1ff4) * 4096; | ||||||
| 
 | 
 | ||||||
| 			if(isReadOperation(operation)) { | 			if(isReadOperation(operation)) { | ||||||
| 				*value = rom_ptr_[address & 4095]; | 				*value = rom_ptr_[address & 4095]; | ||||||
| @@ -61,6 +56,7 @@ class CartridgeAtari32kSuperChip: public Cartridge<CartridgeAtari32kSuperChip> { | |||||||
| 		uint8_t ram_[128]; | 		uint8_t ram_[128]; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
|  | } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| #endif /* Atari2600_CartridgeAtari32k_hpp */ | #endif /* Atari2600_CartridgeAtari32k_hpp */ | ||||||
| @@ -12,20 +12,18 @@ | |||||||
| #include "Cartridge.hpp" | #include "Cartridge.hpp" | ||||||
| 
 | 
 | ||||||
| namespace Atari2600 { | namespace Atari2600 { | ||||||
|  | namespace Cartridge { | ||||||
| 
 | 
 | ||||||
| class CartridgeAtari8k: public Cartridge<CartridgeAtari8k> { | class Atari8k: public BusExtender { | ||||||
| 	public: | 	public: | ||||||
| 		CartridgeAtari8k(const std::vector<uint8_t> &rom) : | 		Atari8k(uint8_t *rom_base, std::size_t rom_size) : BusExtender(rom_base, rom_size), rom_ptr_(rom_base) {} | ||||||
| 			Cartridge(rom) { |  | ||||||
| 			rom_ptr_ = rom_.data(); |  | ||||||
| 		} |  | ||||||
| 
 | 
 | ||||||
| 		void perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value) { | 		void perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value) { | ||||||
| 			address &= 0x1fff; | 			address &= 0x1fff; | ||||||
| 			if(!(address & 0x1000)) return; | 			if(!(address & 0x1000)) return; | ||||||
| 
 | 
 | ||||||
| 			if(address == 0x1ff8) rom_ptr_ = rom_.data(); | 			if(address == 0x1ff8) rom_ptr_ = rom_base_; | ||||||
| 			else if(address == 0x1ff9) rom_ptr_ = rom_.data() + 4096; | 			else if(address == 0x1ff9) rom_ptr_ = rom_base_ + 4096; | ||||||
| 
 | 
 | ||||||
| 			if(isReadOperation(operation)) { | 			if(isReadOperation(operation)) { | ||||||
| 				*value = rom_ptr_[address & 4095]; | 				*value = rom_ptr_[address & 4095]; | ||||||
| @@ -36,19 +34,16 @@ class CartridgeAtari8k: public Cartridge<CartridgeAtari8k> { | |||||||
| 		uint8_t *rom_ptr_; | 		uint8_t *rom_ptr_; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| class CartridgeAtari8kSuperChip: public Cartridge<CartridgeAtari8kSuperChip> { | class Atari8kSuperChip: public BusExtender { | ||||||
| 	public: | 	public: | ||||||
| 		CartridgeAtari8kSuperChip(const std::vector<uint8_t> &rom) : | 		Atari8kSuperChip(uint8_t *rom_base, std::size_t rom_size) : BusExtender(rom_base, rom_size), rom_ptr_(rom_base) {} | ||||||
| 			Cartridge(rom) { |  | ||||||
| 			rom_ptr_ = rom_.data(); |  | ||||||
| 		} |  | ||||||
| 
 | 
 | ||||||
| 		void perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value) { | 		void perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value) { | ||||||
| 			address &= 0x1fff; | 			address &= 0x1fff; | ||||||
| 			if(!(address & 0x1000)) return; | 			if(!(address & 0x1000)) return; | ||||||
| 
 | 
 | ||||||
| 			if(address == 0x1ff8) rom_ptr_ = rom_.data(); | 			if(address == 0x1ff8) rom_ptr_ = rom_base_; | ||||||
| 			if(address == 0x1ff9) rom_ptr_ = rom_.data() + 4096; | 			if(address == 0x1ff9) rom_ptr_ = rom_base_ + 4096; | ||||||
| 
 | 
 | ||||||
| 			if(isReadOperation(operation)) { | 			if(isReadOperation(operation)) { | ||||||
| 				*value = rom_ptr_[address & 4095]; | 				*value = rom_ptr_[address & 4095]; | ||||||
| @@ -63,6 +58,7 @@ class CartridgeAtari8kSuperChip: public Cartridge<CartridgeAtari8kSuperChip> { | |||||||
| 		uint8_t ram_[128]; | 		uint8_t ram_[128]; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
|  | } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| #endif /* Atari2600_CartridgeAtari8k_hpp */ | #endif /* Atari2600_CartridgeAtari8k_hpp */ | ||||||
| @@ -12,19 +12,17 @@ | |||||||
| #include "Cartridge.hpp" | #include "Cartridge.hpp" | ||||||
| 
 | 
 | ||||||
| namespace Atari2600 { | namespace Atari2600 { | ||||||
|  | namespace Cartridge { | ||||||
| 
 | 
 | ||||||
| class CartridgeCBSRAMPlus: public Cartridge<CartridgeCBSRAMPlus> { | class CBSRAMPlus: public BusExtender { | ||||||
| 	public: | 	public: | ||||||
| 		CartridgeCBSRAMPlus(const std::vector<uint8_t> &rom) : | 		CBSRAMPlus(uint8_t *rom_base, std::size_t rom_size) : BusExtender(rom_base, rom_size), rom_ptr_(rom_base) {} | ||||||
| 			Cartridge(rom) { |  | ||||||
| 			rom_ptr_ = rom_.data(); |  | ||||||
| 		} |  | ||||||
| 
 | 
 | ||||||
| 		void perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value) { | 		void perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value) { | ||||||
| 			address &= 0x1fff; | 			address &= 0x1fff; | ||||||
| 			if(!(address & 0x1000)) return; | 			if(!(address & 0x1000)) return; | ||||||
| 
 | 
 | ||||||
| 			if(address >= 0x1ff8 && address <= 0x1ffa) rom_ptr_ = rom_.data() + (address - 0x1ff8) * 4096; | 			if(address >= 0x1ff8 && address <= 0x1ffa) rom_ptr_ = rom_base_ + (address - 0x1ff8) * 4096; | ||||||
| 
 | 
 | ||||||
| 			if(isReadOperation(operation)) { | 			if(isReadOperation(operation)) { | ||||||
| 				*value = rom_ptr_[address & 4095]; | 				*value = rom_ptr_[address & 4095]; | ||||||
| @@ -39,6 +37,7 @@ class CartridgeCBSRAMPlus: public Cartridge<CartridgeCBSRAMPlus> { | |||||||
| 		uint8_t ram_[256]; | 		uint8_t ram_[256]; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
|  | } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| #endif /* Atari2600_CartridgeCBSRAMPlus_hpp */ | #endif /* Atari2600_CartridgeCBSRAMPlus_hpp */ | ||||||
| @@ -13,18 +13,34 @@ | |||||||
| #include "../Bus.hpp" | #include "../Bus.hpp" | ||||||
|  |  | ||||||
| namespace Atari2600 { | namespace Atari2600 { | ||||||
|  | namespace Cartridge { | ||||||
|  |  | ||||||
|  | class BusExtender: public CPU::MOS6502::BusHandler { | ||||||
|  | 	public: | ||||||
|  | 		BusExtender(uint8_t *rom_base, std::size_t rom_size) : rom_base_(rom_base), rom_size_(rom_size) {} | ||||||
|  |  | ||||||
|  | 		void advance_cycles(int cycles) {} | ||||||
|  |  | ||||||
|  | 	protected: | ||||||
|  | 		uint8_t *rom_base_; | ||||||
|  | 		std::size_t rom_size_; | ||||||
|  | }; | ||||||
|  |  | ||||||
| template<class T> class Cartridge: | template<class T> class Cartridge: | ||||||
| 	public CPU::MOS6502::Processor<Cartridge<T>>, | 	public CPU::MOS6502::BusHandler, | ||||||
| 	public Bus { | 	public Bus { | ||||||
|  |  | ||||||
| 	public: | 	public: | ||||||
| 		Cartridge(const std::vector<uint8_t> &rom) : | 		Cartridge(const std::vector<uint8_t> &rom) : | ||||||
| 			rom_(rom) {} | 			m6502_(*this), | ||||||
|  | 			rom_(rom), | ||||||
|  | 			bus_extender_(rom_.data(), rom.size()) { | ||||||
|  | 			// The above works because bus_extender_ is declared after rom_ in the instance storage list; | ||||||
|  | 			// consider doing something less fragile. | ||||||
|  | 		} | ||||||
|  |  | ||||||
| 		void run_for(const Cycles cycles) { CPU::MOS6502::Processor<Cartridge<T>>::run_for(cycles); } | 		void run_for(const Cycles cycles)	{ m6502_.run_for(cycles);		} | ||||||
| 		void set_reset_line(bool state) { CPU::MOS6502::Processor<Cartridge<T>>::set_reset_line(state); } | 		void set_reset_line(bool state)		{ m6502_.set_reset_line(state);	} | ||||||
| 		void advance_cycles(int cycles) {} |  | ||||||
|  |  | ||||||
| 		// to satisfy CPU::MOS6502::Processor | 		// to satisfy CPU::MOS6502::Processor | ||||||
| 		Cycles perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value) { | 		Cycles perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value) { | ||||||
| @@ -41,11 +57,11 @@ template<class T> class Cartridge: | |||||||
| 			cycles_since_speaker_update_ += Cycles(cycles_run_for); | 			cycles_since_speaker_update_ += Cycles(cycles_run_for); | ||||||
| 			cycles_since_video_update_ += Cycles(cycles_run_for); | 			cycles_since_video_update_ += Cycles(cycles_run_for); | ||||||
| 			cycles_since_6532_update_ += Cycles(cycles_run_for / 3); | 			cycles_since_6532_update_ += Cycles(cycles_run_for / 3); | ||||||
| 			static_cast<T *>(this)->advance_cycles(cycles_run_for / 3); | 			bus_extender_.advance_cycles(cycles_run_for / 3); | ||||||
|  |  | ||||||
| 			if(operation != CPU::MOS6502::BusOperation::Ready) { | 			if(operation != CPU::MOS6502::BusOperation::Ready) { | ||||||
| 				// give the cartridge a chance to respond to the bus access | 				// give the cartridge a chance to respond to the bus access | ||||||
| 				static_cast<T *>(this)->perform_bus_operation(operation, address, value); | 				bus_extender_.perform_bus_operation(operation, address, value); | ||||||
|  |  | ||||||
| 				// check for a RIOT RAM access | 				// check for a RIOT RAM access | ||||||
| 				if((address&0x1280) == 0x80) { | 				if((address&0x1280) == 0x80) { | ||||||
| @@ -91,7 +107,7 @@ template<class T> class Cartridge: | |||||||
| 							case 0x00:	update_video(); tia_->set_sync(*value & 0x02);		break; | 							case 0x00:	update_video(); tia_->set_sync(*value & 0x02);		break; | ||||||
| 							case 0x01:	update_video();	tia_->set_blank(*value & 0x02);		break; | 							case 0x01:	update_video();	tia_->set_blank(*value & 0x02);		break; | ||||||
|  |  | ||||||
| 							case 0x02:	CPU::MOS6502::Processor<Cartridge<T>>::set_ready_line(true);								break; | 							case 0x02:	m6502_.set_ready_line(true);						break; | ||||||
| 							case 0x03:	update_video();	tia_->reset_horizontal_counter();	break; | 							case 0x03:	update_video();	tia_->reset_horizontal_counter();	break; | ||||||
| 								// TODO: audio will now be out of synchronisation — fix | 								// TODO: audio will now be out of synchronisation — fix | ||||||
|  |  | ||||||
| @@ -132,11 +148,11 @@ template<class T> class Cartridge: | |||||||
| 							case 0x2c:	update_video(); tia_->clear_collision_flags();										break; | 							case 0x2c:	update_video(); tia_->clear_collision_flags();										break; | ||||||
|  |  | ||||||
| 							case 0x15: | 							case 0x15: | ||||||
| 							case 0x16:	update_audio(); speaker_->set_control(decodedAddress - 0x15, *value);				break; | 							case 0x16:	update_audio(); tia_sound_.set_control(decodedAddress - 0x15, *value);				break; | ||||||
| 							case 0x17: | 							case 0x17: | ||||||
| 							case 0x18:	update_audio(); speaker_->set_divider(decodedAddress - 0x17, *value);				break; | 							case 0x18:	update_audio(); tia_sound_.set_divider(decodedAddress - 0x17, *value);				break; | ||||||
| 							case 0x19: | 							case 0x19: | ||||||
| 							case 0x1a:	update_audio(); speaker_->set_volume(decodedAddress - 0x19, *value);				break; | 							case 0x1a:	update_audio(); tia_sound_.set_volume(decodedAddress - 0x19, *value);				break; | ||||||
| 						} | 						} | ||||||
| 					} | 					} | ||||||
| 				} | 				} | ||||||
| @@ -156,7 +172,7 @@ template<class T> class Cartridge: | |||||||
| 				} | 				} | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
| 			if(!tia_->get_cycles_until_horizontal_blank(cycles_since_video_update_)) CPU::MOS6502::Processor<Cartridge<T>>::set_ready_line(false); | 			if(!tia_->get_cycles_until_horizontal_blank(cycles_since_video_update_)) m6502_.set_ready_line(false); | ||||||
|  |  | ||||||
| 			return Cycles(cycles_run_for / 3); | 			return Cycles(cycles_run_for / 3); | ||||||
| 		} | 		} | ||||||
| @@ -164,13 +180,18 @@ template<class T> class Cartridge: | |||||||
| 		void flush() { | 		void flush() { | ||||||
| 			update_audio(); | 			update_audio(); | ||||||
| 			update_video(); | 			update_video(); | ||||||
| 			speaker_->flush(); | 			audio_queue_.perform(); | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 	protected: | 	protected: | ||||||
|  | 		CPU::MOS6502::Processor<Cartridge<T>, true> m6502_; | ||||||
| 		std::vector<uint8_t> rom_; | 		std::vector<uint8_t> rom_; | ||||||
|  |  | ||||||
|  | 	private: | ||||||
|  | 		T bus_extender_; | ||||||
| }; | }; | ||||||
|  |  | ||||||
|  | } | ||||||
| } | } | ||||||
|  |  | ||||||
| #endif /* Atari2600_Cartridge_hpp */ | #endif /* Atari2600_Cartridge_hpp */ | ||||||
|   | |||||||
| @@ -9,12 +9,14 @@ | |||||||
| #ifndef Atari2600_CartridgeCommaVid_hpp | #ifndef Atari2600_CartridgeCommaVid_hpp | ||||||
| #define Atari2600_CartridgeCommaVid_hpp | #define Atari2600_CartridgeCommaVid_hpp | ||||||
| 
 | 
 | ||||||
| namespace Atari2600 { | #include "Cartridge.hpp" | ||||||
| 
 | 
 | ||||||
| class CartridgeCommaVid: public Cartridge<CartridgeCommaVid> { | namespace Atari2600 { | ||||||
|  | namespace Cartridge { | ||||||
|  | 
 | ||||||
|  | class CommaVid: public BusExtender { | ||||||
| 	public: | 	public: | ||||||
| 		CartridgeCommaVid(const std::vector<uint8_t> &rom) : | 		CommaVid(uint8_t *rom_base, std::size_t rom_size) : BusExtender(rom_base, rom_size) {} | ||||||
| 			Cartridge(rom) {} |  | ||||||
| 
 | 
 | ||||||
| 		void perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value) { | 		void perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value) { | ||||||
| 			if(!(address & 0x1000)) return; | 			if(!(address & 0x1000)) return; | ||||||
| @@ -30,13 +32,14 @@ class CartridgeCommaVid: public Cartridge<CartridgeCommaVid> { | |||||||
| 				return; | 				return; | ||||||
| 			} | 			} | ||||||
| 
 | 
 | ||||||
| 			if(isReadOperation(operation)) *value = rom_[address & 2047]; | 			if(isReadOperation(operation)) *value = rom_base_[address & 2047]; | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 	private: | 	private: | ||||||
| 		uint8_t ram_[1024]; | 		uint8_t ram_[1024]; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
|  | } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| #endif /* Atari2600_CartridgeCommaVid_hpp */ | #endif /* Atari2600_CartridgeCommaVid_hpp */ | ||||||
| @@ -12,12 +12,13 @@ | |||||||
| #include "Cartridge.hpp" | #include "Cartridge.hpp" | ||||||
| 
 | 
 | ||||||
| namespace Atari2600 { | namespace Atari2600 { | ||||||
|  | namespace Cartridge { | ||||||
| 
 | 
 | ||||||
| class CartridgeMNetwork: public Cartridge<CartridgeMNetwork> { | class MNetwork: public BusExtender { | ||||||
| 	public: | 	public: | ||||||
| 		CartridgeMNetwork(const std::vector<uint8_t> &rom) : | 		MNetwork(uint8_t *rom_base, std::size_t rom_size) : | ||||||
| 			Cartridge(rom) { | 			BusExtender(rom_base, rom_size) { | ||||||
| 			rom_ptr_[0] = rom_.data() + rom_.size() - 4096; | 			rom_ptr_[0] = rom_base + rom_size_ - 4096; | ||||||
| 			rom_ptr_[1] = rom_ptr_[0] + 2048; | 			rom_ptr_[1] = rom_ptr_[0] + 2048; | ||||||
| 			high_ram_ptr_ = high_ram_; | 			high_ram_ptr_ = high_ram_; | ||||||
| 		} | 		} | ||||||
| @@ -27,7 +28,7 @@ class CartridgeMNetwork: public Cartridge<CartridgeMNetwork> { | |||||||
| 			if(!(address & 0x1000)) return; | 			if(!(address & 0x1000)) return; | ||||||
| 
 | 
 | ||||||
| 			if(address >= 0x1fe0 && address <= 0x1fe6) { | 			if(address >= 0x1fe0 && address <= 0x1fe6) { | ||||||
| 				rom_ptr_[0] = rom_.data() + (address - 0x1fe0) * 2048; | 				rom_ptr_[0] = rom_base_ + (address - 0x1fe0) * 2048; | ||||||
| 			} else if(address == 0x1fe7) { | 			} else if(address == 0x1fe7) { | ||||||
| 				rom_ptr_[0] = nullptr; | 				rom_ptr_[0] = nullptr; | ||||||
| 			} else if(address >= 0x1ff8 && address <= 0x1ffb) { | 			} else if(address >= 0x1ff8 && address <= 0x1ffb) { | ||||||
| @@ -54,7 +55,6 @@ class CartridgeMNetwork: public Cartridge<CartridgeMNetwork> { | |||||||
| 					} | 					} | ||||||
| 				} | 				} | ||||||
| 			} | 			} | ||||||
| 
 |  | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 	private: | 	private: | ||||||
| @@ -63,6 +63,7 @@ class CartridgeMNetwork: public Cartridge<CartridgeMNetwork> { | |||||||
| 		uint8_t low_ram_[1024], high_ram_[1024]; | 		uint8_t low_ram_[1024], high_ram_[1024]; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
|  | } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| #endif /* Atari2600_CartridgeMNetwork_hpp */ | #endif /* Atari2600_CartridgeMNetwork_hpp */ | ||||||
| @@ -12,13 +12,14 @@ | |||||||
| #include "Cartridge.hpp" | #include "Cartridge.hpp" | ||||||
| 
 | 
 | ||||||
| namespace Atari2600 { | namespace Atari2600 { | ||||||
|  | namespace Cartridge { | ||||||
| 
 | 
 | ||||||
| class CartridgeMegaBoy: public Cartridge<CartridgeMegaBoy> { | class MegaBoy: public BusExtender { | ||||||
| 	public: | 	public: | ||||||
| 		CartridgeMegaBoy(const std::vector<uint8_t> &rom) : | 		MegaBoy(uint8_t *rom_base, std::size_t rom_size) : | ||||||
| 			Cartridge(rom), | 			BusExtender(rom_base, rom_size), | ||||||
|  | 			rom_ptr_(rom_base), | ||||||
| 			current_page_(0) { | 			current_page_(0) { | ||||||
| 			rom_ptr_ = rom_.data(); |  | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		void perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value) { | 		void perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value) { | ||||||
| @@ -27,7 +28,7 @@ class CartridgeMegaBoy: public Cartridge<CartridgeMegaBoy> { | |||||||
| 
 | 
 | ||||||
| 			if(address == 0x1ff0) { | 			if(address == 0x1ff0) { | ||||||
| 				current_page_ = (current_page_ + 1) & 15; | 				current_page_ = (current_page_ + 1) & 15; | ||||||
| 				rom_ptr_ = rom_.data() + current_page_ * 4096; | 				rom_ptr_ = rom_base_ + current_page_ * 4096; | ||||||
| 			} | 			} | ||||||
| 
 | 
 | ||||||
| 			if(isReadOperation(operation)) { | 			if(isReadOperation(operation)) { | ||||||
| @@ -40,6 +41,7 @@ class CartridgeMegaBoy: public Cartridge<CartridgeMegaBoy> { | |||||||
| 		uint8_t current_page_; | 		uint8_t current_page_; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
|  | } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| #endif /* CartridgeMegaBoy_h */ | #endif /* CartridgeMegaBoy_h */ | ||||||
| @@ -12,12 +12,13 @@ | |||||||
| #include "Cartridge.hpp" | #include "Cartridge.hpp" | ||||||
| 
 | 
 | ||||||
| namespace Atari2600 { | namespace Atari2600 { | ||||||
|  | namespace Cartridge { | ||||||
| 
 | 
 | ||||||
| class CartridgeParkerBros: public Cartridge<CartridgeParkerBros> { | class ParkerBros: public BusExtender { | ||||||
| 	public: | 	public: | ||||||
| 		CartridgeParkerBros(const std::vector<uint8_t> &rom) : | 		ParkerBros(uint8_t *rom_base, std::size_t rom_size) : | ||||||
| 			Cartridge(rom) { | 			BusExtender(rom_base, rom_size) { | ||||||
| 			rom_ptr_[0] = rom_.data() + 4096; | 			rom_ptr_[0] = rom_base + 4096; | ||||||
| 			rom_ptr_[1] = rom_ptr_[0] + 1024; | 			rom_ptr_[1] = rom_ptr_[0] + 1024; | ||||||
| 			rom_ptr_[2] = rom_ptr_[1] + 1024; | 			rom_ptr_[2] = rom_ptr_[1] + 1024; | ||||||
| 			rom_ptr_[3] = rom_ptr_[2] + 1024; | 			rom_ptr_[3] = rom_ptr_[2] + 1024; | ||||||
| @@ -29,7 +30,7 @@ class CartridgeParkerBros: public Cartridge<CartridgeParkerBros> { | |||||||
| 
 | 
 | ||||||
| 			if(address >= 0x1fe0 && address < 0x1ff8) { | 			if(address >= 0x1fe0 && address < 0x1ff8) { | ||||||
| 				int slot = (address >> 3)&3; | 				int slot = (address >> 3)&3; | ||||||
| 				rom_ptr_[slot] = rom_.data() + ((address & 7) * 1024); | 				rom_ptr_[slot] = rom_base_ + ((address & 7) * 1024); | ||||||
| 			} | 			} | ||||||
| 
 | 
 | ||||||
| 			if(isReadOperation(operation)) { | 			if(isReadOperation(operation)) { | ||||||
| @@ -41,6 +42,7 @@ class CartridgeParkerBros: public Cartridge<CartridgeParkerBros> { | |||||||
| 		uint8_t *rom_ptr_[4]; | 		uint8_t *rom_ptr_[4]; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
|  | } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| #endif /* Atari2600_CartridgeParkerBros_hpp */ | #endif /* Atari2600_CartridgeParkerBros_hpp */ | ||||||
| @@ -10,17 +10,13 @@ | |||||||
| #define Atari2600_CartridgePitfall2_hpp | #define Atari2600_CartridgePitfall2_hpp | ||||||
| 
 | 
 | ||||||
| namespace Atari2600 { | namespace Atari2600 { | ||||||
|  | namespace Cartridge { | ||||||
| 
 | 
 | ||||||
| class CartridgePitfall2: public Cartridge<CartridgePitfall2> { | class Pitfall2: public BusExtender { | ||||||
| 	public: | 	public: | ||||||
| 		CartridgePitfall2(const std::vector<uint8_t> &rom) : | 		Pitfall2(uint8_t *rom_base, std::size_t rom_size) : | ||||||
| 			Cartridge(rom), | 			BusExtender(rom_base, rom_size), | ||||||
| 			random_number_generator_(0), | 			rom_ptr_(rom_base) {} | ||||||
| 			featcher_address_{0, 0, 0, 0, 0, 0, 0, 0}, |  | ||||||
| 			mask_{0, 0, 0, 0, 0, 0, 0, 0}, |  | ||||||
| 			cycles_since_audio_update_(0) { |  | ||||||
| 			rom_ptr_ = rom_.data(); |  | ||||||
| 		} |  | ||||||
| 
 | 
 | ||||||
| 		void advance_cycles(int cycles) { | 		void advance_cycles(int cycles) { | ||||||
| 			cycles_since_audio_update_ += cycles; | 			cycles_since_audio_update_ += cycles; | ||||||
| @@ -32,14 +28,14 @@ class CartridgePitfall2: public Cartridge<CartridgePitfall2> { | |||||||
| 
 | 
 | ||||||
| 			switch(address) { | 			switch(address) { | ||||||
| 
 | 
 | ||||||
| #pragma mark - Reads | // MARK: - Reads
 | ||||||
| 
 | 
 | ||||||
| 				// The random number generator
 | 				// The random number generator
 | ||||||
| 				case 0x1000: case 0x1001: case 0x1002: case 0x1003: case 0x1004: | 				case 0x1000: case 0x1001: case 0x1002: case 0x1003: case 0x1004: | ||||||
| 					if(isReadOperation(operation)) { | 					if(isReadOperation(operation)) { | ||||||
| 						*value = random_number_generator_; | 						*value = random_number_generator_; | ||||||
| 					} | 					} | ||||||
| 					random_number_generator_ = (uint8_t)( | 					random_number_generator_ = static_cast<uint8_t>( | ||||||
| 						(random_number_generator_ << 1) | | 						(random_number_generator_ << 1) | | ||||||
| 						(~(	(random_number_generator_ >> 7) ^ | 						(~(	(random_number_generator_ >> 7) ^ | ||||||
| 							(random_number_generator_ >> 5) ^ | 							(random_number_generator_ >> 5) ^ | ||||||
| @@ -53,14 +49,14 @@ class CartridgePitfall2: public Cartridge<CartridgePitfall2> { | |||||||
| 				break; | 				break; | ||||||
| 
 | 
 | ||||||
| 				case 0x1008: case 0x1009: case 0x100a: case 0x100b: case 0x100c: case 0x100d: case 0x100e: case 0x100f: | 				case 0x1008: case 0x1009: case 0x100a: case 0x100b: case 0x100c: case 0x100d: case 0x100e: case 0x100f: | ||||||
| 					*value = rom_[8192 + address_for_counter(address & 7)]; | 					*value = rom_base_[8192 + address_for_counter(address & 7)]; | ||||||
| 				break; | 				break; | ||||||
| 
 | 
 | ||||||
| 				case 0x1010: case 0x1011: case 0x1012: case 0x1013: case 0x1014: case 0x1015: case 0x1016: case 0x1017: | 				case 0x1010: case 0x1011: case 0x1012: case 0x1013: case 0x1014: case 0x1015: case 0x1016: case 0x1017: | ||||||
| 					*value = rom_[8192 + address_for_counter(address & 7)] & mask_[address & 7]; | 					*value = rom_base_[8192 + address_for_counter(address & 7)] & mask_[address & 7]; | ||||||
| 				break; | 				break; | ||||||
| 
 | 
 | ||||||
| #pragma mark - Writes | // MARK: - Writes
 | ||||||
| 
 | 
 | ||||||
| 				case 0x1040: case 0x1041: case 0x1042: case 0x1043: case 0x1044: case 0x1045: case 0x1046: case 0x1047: | 				case 0x1040: case 0x1041: case 0x1042: case 0x1043: case 0x1044: case 0x1045: case 0x1046: case 0x1047: | ||||||
| 					top_[address & 7] = *value; | 					top_[address & 7] = *value; | ||||||
| @@ -73,18 +69,18 @@ class CartridgePitfall2: public Cartridge<CartridgePitfall2> { | |||||||
| 					mask_[address & 7] = 0x00; | 					mask_[address & 7] = 0x00; | ||||||
| 				break; | 				break; | ||||||
| 				case 0x1058: case 0x1059: case 0x105a: case 0x105b: case 0x105c: case 0x105d: case 0x105e: case 0x105f: | 				case 0x1058: case 0x1059: case 0x105a: case 0x105b: case 0x105c: case 0x105d: case 0x105e: case 0x105f: | ||||||
| 					featcher_address_[address & 7] = (featcher_address_[address & 7] & 0x00ff) | (uint16_t)(*value << 8); | 					featcher_address_[address & 7] = (featcher_address_[address & 7] & 0x00ff) | static_cast<uint16_t>(*value << 8); | ||||||
| 				break; | 				break; | ||||||
| 				case 0x1070: case 0x1071: case 0x1072: case 0x1073: case 0x1074: case 0x1075: case 0x1076: case 0x1077: | 				case 0x1070: case 0x1071: case 0x1072: case 0x1073: case 0x1074: case 0x1075: case 0x1076: case 0x1077: | ||||||
| 					random_number_generator_ = 0; | 					random_number_generator_ = 0; | ||||||
| 				break; | 				break; | ||||||
| 
 | 
 | ||||||
| #pragma mark - Paging | // MARK: - Paging
 | ||||||
| 
 | 
 | ||||||
| 				case 0x1ff8: rom_ptr_ = rom_.data();		break; | 				case 0x1ff8: rom_ptr_ = rom_base_;			break; | ||||||
| 				case 0x1ff9: rom_ptr_ = rom_.data() + 4096;	break; | 				case 0x1ff9: rom_ptr_ = rom_base_ + 4096;	break; | ||||||
| 
 | 
 | ||||||
| #pragma mark - Business as usual | // MARK: - Business as usual
 | ||||||
| 
 | 
 | ||||||
| 				default: | 				default: | ||||||
| 					if(isReadOperation(operation)) { | 					if(isReadOperation(operation)) { | ||||||
| @@ -119,15 +115,16 @@ class CartridgePitfall2: public Cartridge<CartridgePitfall2> { | |||||||
| 			return level_table[table_position]; | 			return level_table[table_position]; | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		uint16_t featcher_address_[8]; | 		uint16_t featcher_address_[8] = {0, 0, 0, 0, 0, 0, 0, 0}; | ||||||
| 		uint8_t top_[8], bottom_[8], mask_[8]; | 		uint8_t top_[8], bottom_[8], mask_[8] = {0, 0, 0, 0, 0, 0, 0, 0}; | ||||||
| 		uint8_t music_mode_[3]; | 		uint8_t music_mode_[3]; | ||||||
| 		uint8_t random_number_generator_; | 		uint8_t random_number_generator_ = 0; | ||||||
| 		uint8_t *rom_ptr_; | 		uint8_t *rom_ptr_; | ||||||
| 		uint8_t audio_channel_[3]; | 		uint8_t audio_channel_[3]; | ||||||
| 		Cycles cycles_since_audio_update_; | 		Cycles cycles_since_audio_update_ = 0; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
|  | } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| #endif /* Atari2600_CartridgePitfall2_hpp */ | #endif /* Atari2600_CartridgePitfall2_hpp */ | ||||||
| @@ -12,19 +12,20 @@ | |||||||
| #include "Cartridge.hpp" | #include "Cartridge.hpp" | ||||||
| 
 | 
 | ||||||
| namespace Atari2600 { | namespace Atari2600 { | ||||||
|  | namespace Cartridge { | ||||||
| 
 | 
 | ||||||
| class CartridgeTigervision: public Cartridge<CartridgeTigervision> { | class Tigervision: public BusExtender { | ||||||
| 	public: | 	public: | ||||||
| 		CartridgeTigervision(const std::vector<uint8_t> &rom) : | 		Tigervision(uint8_t *rom_base, std::size_t rom_size) : | ||||||
| 			Cartridge(rom) { | 			BusExtender(rom_base, rom_size) { | ||||||
| 			rom_ptr_[0] = rom_.data() + rom_.size() - 4096; | 			rom_ptr_[0] = rom_base + rom_size - 4096; | ||||||
| 			rom_ptr_[1] = rom_ptr_[0] + 2048; | 			rom_ptr_[1] = rom_ptr_[0] + 2048; | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		void perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value) { | 		void perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value) { | ||||||
| 			if((address&0x1fff) == 0x3f) { | 			if((address&0x1fff) == 0x3f) { | ||||||
| 				int offset = ((*value) * 2048) & (rom_.size() - 1); | 				int offset = ((*value) * 2048) & (rom_size_ - 1); | ||||||
| 				rom_ptr_[0] = rom_.data() + offset; | 				rom_ptr_[0] = rom_base_ + offset; | ||||||
| 				return; | 				return; | ||||||
| 			} else if((address&0x1000) && isReadOperation(operation)) { | 			} else if((address&0x1000) && isReadOperation(operation)) { | ||||||
| 				*value = rom_ptr_[(address >> 11)&1][address & 2047]; | 				*value = rom_ptr_[(address >> 11)&1][address & 2047]; | ||||||
| @@ -35,6 +36,7 @@ class CartridgeTigervision: public Cartridge<CartridgeTigervision> { | |||||||
| 		uint8_t *rom_ptr_[2]; | 		uint8_t *rom_ptr_[2]; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
|  | } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| #endif /* Atari2600_CartridgeTigervision_hpp */ | #endif /* Atari2600_CartridgeTigervision_hpp */ | ||||||
| @@ -12,19 +12,20 @@ | |||||||
| #include "Cartridge.hpp" | #include "Cartridge.hpp" | ||||||
| 
 | 
 | ||||||
| namespace Atari2600 { | namespace Atari2600 { | ||||||
|  | namespace Cartridge { | ||||||
| 
 | 
 | ||||||
| class CartridgeUnpaged: public Cartridge<CartridgeUnpaged> { | class Unpaged: public BusExtender { | ||||||
| 	public: | 	public: | ||||||
| 		CartridgeUnpaged(const std::vector<uint8_t> &rom) : | 		Unpaged(uint8_t *rom_base, std::size_t rom_size) : BusExtender(rom_base, rom_size) {} | ||||||
| 			Cartridge(rom) {} |  | ||||||
| 
 | 
 | ||||||
| 		void perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value) { | 		void perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value) { | ||||||
| 			if(isReadOperation(operation) && (address & 0x1000)) { | 			if(isReadOperation(operation) && (address & 0x1000)) { | ||||||
| 				*value = rom_[address & (rom_.size() - 1)]; | 				*value = rom_base_[address & (rom_size_ - 1)]; | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
|  | } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| #endif /* Atari2600_CartridgeUnpaged_hpp */ | #endif /* Atari2600_CartridgeUnpaged_hpp */ | ||||||
| @@ -9,6 +9,8 @@ | |||||||
| #ifndef Atari2600_PIA_h | #ifndef Atari2600_PIA_h | ||||||
| #define Atari2600_PIA_h | #define Atari2600_PIA_h | ||||||
|  |  | ||||||
|  | #include <cstdint> | ||||||
|  |  | ||||||
| #include "../../Components/6532/6532.hpp" | #include "../../Components/6532/6532.hpp" | ||||||
|  |  | ||||||
| namespace Atari2600 { | namespace Atari2600 { | ||||||
|   | |||||||
| @@ -7,7 +7,9 @@ | |||||||
| // | // | ||||||
|  |  | ||||||
| #include "TIA.hpp" | #include "TIA.hpp" | ||||||
|  |  | ||||||
| #include <cassert> | #include <cassert> | ||||||
|  | #include <cstring> | ||||||
|  |  | ||||||
| using namespace Atari2600; | using namespace Atari2600; | ||||||
| namespace { | namespace { | ||||||
| @@ -20,36 +22,27 @@ namespace { | |||||||
| 	uint8_t reverse_table[256]; | 	uint8_t reverse_table[256]; | ||||||
| } | } | ||||||
|  |  | ||||||
| TIA::TIA(bool create_crt) : | TIA::TIA(bool create_crt) { | ||||||
| 	horizontal_counter_(0), |  | ||||||
| 	pixels_start_location_(0), |  | ||||||
| 	output_mode_(0), |  | ||||||
| 	pixel_target_(nullptr), |  | ||||||
| 	background_{0, 0}, |  | ||||||
| 	background_half_mask_(0), |  | ||||||
| 	horizontal_blank_extend_(false), |  | ||||||
| 	collision_flags_(0) |  | ||||||
| { |  | ||||||
| 	if(create_crt) { | 	if(create_crt) { | ||||||
| 		crt_.reset(new Outputs::CRT::CRT(cycles_per_line * 2 - 1, 1, Outputs::CRT::DisplayType::NTSC60, 1)); | 		crt_.reset(new Outputs::CRT::CRT(cycles_per_line * 2 - 1, 1, Outputs::CRT::DisplayType::NTSC60, 1)); | ||||||
| 		crt_->set_output_device(Outputs::CRT::Television); | 		crt_->set_output_device(Outputs::CRT::OutputDevice::Television); | ||||||
| 		set_output_mode(OutputMode::NTSC); | 		set_output_mode(OutputMode::NTSC); | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	for(int c = 0; c < 256; c++) { | 	for(int c = 0; c < 256; c++) { | ||||||
| 		reverse_table[c] = (uint8_t)( | 		reverse_table[c] = static_cast<uint8_t>( | ||||||
| 			((c & 0x01) << 7) | ((c & 0x02) << 5) | ((c & 0x04) << 3) | ((c & 0x08) << 1) | | 			((c & 0x01) << 7) | ((c & 0x02) << 5) | ((c & 0x04) << 3) | ((c & 0x08) << 1) | | ||||||
| 			((c & 0x10) >> 1) | ((c & 0x20) >> 3) | ((c & 0x40) >> 5) | ((c & 0x80) >> 7) | 			((c & 0x10) >> 1) | ((c & 0x20) >> 3) | ((c & 0x40) >> 5) | ((c & 0x80) >> 7) | ||||||
| 		); | 		); | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	for(int c = 0; c < 64; c++) { | 	for(int c = 0; c < 64; c++) { | ||||||
| 		bool has_playfield = c & (int)(CollisionType::Playfield); | 		bool has_playfield = c & static_cast<int>(CollisionType::Playfield); | ||||||
| 		bool has_ball = c & (int)(CollisionType::Ball); | 		bool has_ball = c & static_cast<int>(CollisionType::Ball); | ||||||
| 		bool has_player0 = c & (int)(CollisionType::Player0); | 		bool has_player0 = c & static_cast<int>(CollisionType::Player0); | ||||||
| 		bool has_player1 = c & (int)(CollisionType::Player1); | 		bool has_player1 = c & static_cast<int>(CollisionType::Player1); | ||||||
| 		bool has_missile0 = c & (int)(CollisionType::Missile0); | 		bool has_missile0 = c & static_cast<int>(CollisionType::Missile0); | ||||||
| 		bool has_missile1 = c & (int)(CollisionType::Missile1); | 		bool has_missile1 = c & static_cast<int>(CollisionType::Missile1); | ||||||
|  |  | ||||||
| 		uint8_t collision_registers[8]; | 		uint8_t collision_registers[8]; | ||||||
| 		collision_registers[0] = ((has_missile0 && has_player1) ? 0x80 : 0x00)		|	((has_missile0 && has_player0) ? 0x40 : 0x00); | 		collision_registers[0] = ((has_missile0 && has_player1) ? 0x80 : 0x00)		|	((has_missile0 && has_player0) ? 0x40 : 0x00); | ||||||
| @@ -71,51 +64,51 @@ TIA::TIA(bool create_crt) : | |||||||
| 			(collision_registers[7] << 8); | 			(collision_registers[7] << 8); | ||||||
|  |  | ||||||
| 		// all priority modes show the background if nothing else is present | 		// all priority modes show the background if nothing else is present | ||||||
| 		colour_mask_by_mode_collision_flags_[(int)ColourMode::Standard][c] = | 		colour_mask_by_mode_collision_flags_[static_cast<int>(ColourMode::Standard)][c] = | ||||||
| 		colour_mask_by_mode_collision_flags_[(int)ColourMode::ScoreLeft][c] = | 		colour_mask_by_mode_collision_flags_[static_cast<int>(ColourMode::ScoreLeft)][c] = | ||||||
| 		colour_mask_by_mode_collision_flags_[(int)ColourMode::ScoreRight][c] = | 		colour_mask_by_mode_collision_flags_[static_cast<int>(ColourMode::ScoreRight)][c] = | ||||||
| 		colour_mask_by_mode_collision_flags_[(int)ColourMode::OnTop][c] = (uint8_t)ColourIndex::Background; | 		colour_mask_by_mode_collision_flags_[static_cast<int>(ColourMode::OnTop)][c] = static_cast<uint8_t>(ColourIndex::Background); | ||||||
|  |  | ||||||
| 		// test 1 for standard priority: if there is a playfield or ball pixel, plot that colour | 		// test 1 for standard priority: if there is a playfield or ball pixel, plot that colour | ||||||
| 		if(has_playfield || has_ball) { | 		if(has_playfield || has_ball) { | ||||||
| 			colour_mask_by_mode_collision_flags_[(int)ColourMode::Standard][c] = (uint8_t)ColourIndex::PlayfieldBall; | 			colour_mask_by_mode_collision_flags_[static_cast<int>(ColourMode::Standard)][c] = static_cast<uint8_t>(ColourIndex::PlayfieldBall); | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		// test 1 for score mode: if there is a ball pixel, plot that colour | 		// test 1 for score mode: if there is a ball pixel, plot that colour | ||||||
| 		if(has_ball) { | 		if(has_ball) { | ||||||
| 			colour_mask_by_mode_collision_flags_[(int)ColourMode::ScoreLeft][c] = | 			colour_mask_by_mode_collision_flags_[static_cast<int>(ColourMode::ScoreLeft)][c] = | ||||||
| 			colour_mask_by_mode_collision_flags_[(int)ColourMode::ScoreRight][c] = (uint8_t)ColourIndex::PlayfieldBall; | 			colour_mask_by_mode_collision_flags_[static_cast<int>(ColourMode::ScoreRight)][c] = static_cast<uint8_t>(ColourIndex::PlayfieldBall); | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		// test 1 for on-top mode, test 2 for everbody else: if there is a player 1 or missile 1 pixel, plot that colour | 		// test 1 for on-top mode, test 2 for everbody else: if there is a player 1 or missile 1 pixel, plot that colour | ||||||
| 		if(has_player1 || has_missile1) { | 		if(has_player1 || has_missile1) { | ||||||
| 			colour_mask_by_mode_collision_flags_[(int)ColourMode::Standard][c] = | 			colour_mask_by_mode_collision_flags_[static_cast<int>(ColourMode::Standard)][c] = | ||||||
| 			colour_mask_by_mode_collision_flags_[(int)ColourMode::ScoreLeft][c] = | 			colour_mask_by_mode_collision_flags_[static_cast<int>(ColourMode::ScoreLeft)][c] = | ||||||
| 			colour_mask_by_mode_collision_flags_[(int)ColourMode::ScoreRight][c] = | 			colour_mask_by_mode_collision_flags_[static_cast<int>(ColourMode::ScoreRight)][c] = | ||||||
| 			colour_mask_by_mode_collision_flags_[(int)ColourMode::OnTop][c] = (uint8_t)ColourIndex::PlayerMissile1; | 			colour_mask_by_mode_collision_flags_[static_cast<int>(ColourMode::OnTop)][c] = static_cast<uint8_t>(ColourIndex::PlayerMissile1); | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		// in the right-hand side of score mode, the playfield has the same priority as player 1 | 		// in the right-hand side of score mode, the playfield has the same priority as player 1 | ||||||
| 		if(has_playfield) { | 		if(has_playfield) { | ||||||
| 			colour_mask_by_mode_collision_flags_[(int)ColourMode::ScoreRight][c] = (uint8_t)ColourIndex::PlayerMissile1; | 			colour_mask_by_mode_collision_flags_[static_cast<int>(ColourMode::ScoreRight)][c] = static_cast<uint8_t>(ColourIndex::PlayerMissile1); | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		// next test for everybody: if there is a player 0 or missile 0 pixel, plot that colour instead | 		// next test for everybody: if there is a player 0 or missile 0 pixel, plot that colour instead | ||||||
| 		if(has_player0 || has_missile0) { | 		if(has_player0 || has_missile0) { | ||||||
| 			colour_mask_by_mode_collision_flags_[(int)ColourMode::Standard][c] = | 			colour_mask_by_mode_collision_flags_[static_cast<int>(ColourMode::Standard)][c] = | ||||||
| 			colour_mask_by_mode_collision_flags_[(int)ColourMode::ScoreLeft][c] = | 			colour_mask_by_mode_collision_flags_[static_cast<int>(ColourMode::ScoreLeft)][c] = | ||||||
| 			colour_mask_by_mode_collision_flags_[(int)ColourMode::ScoreRight][c] = | 			colour_mask_by_mode_collision_flags_[static_cast<int>(ColourMode::ScoreRight)][c] = | ||||||
| 			colour_mask_by_mode_collision_flags_[(int)ColourMode::OnTop][c] = (uint8_t)ColourIndex::PlayerMissile0; | 			colour_mask_by_mode_collision_flags_[static_cast<int>(ColourMode::OnTop)][c] = static_cast<uint8_t>(ColourIndex::PlayerMissile0); | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		// if this is the left-hand side of score mode, the playfield has the same priority as player 0 | 		// if this is the left-hand side of score mode, the playfield has the same priority as player 0 | ||||||
| 		if(has_playfield) { | 		if(has_playfield) { | ||||||
| 			colour_mask_by_mode_collision_flags_[(int)ColourMode::ScoreLeft][c] = (uint8_t)ColourIndex::PlayerMissile0; | 			colour_mask_by_mode_collision_flags_[static_cast<int>(ColourMode::ScoreLeft)][c] = static_cast<uint8_t>(ColourIndex::PlayerMissile0); | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		// a final test for 'on top' priority mode: if the playfield or ball are visible, prefer that colour to all others | 		// a final test for 'on top' priority mode: if the playfield or ball are visible, prefer that colour to all others | ||||||
| 		if(has_playfield || has_ball) { | 		if(has_playfield || has_ball) { | ||||||
| 			colour_mask_by_mode_collision_flags_[(int)ColourMode::OnTop][c] = (uint8_t)ColourIndex::PlayfieldBall; | 			colour_mask_by_mode_collision_flags_[static_cast<int>(ColourMode::OnTop)][c] = static_cast<uint8_t>(ColourIndex::PlayfieldBall); | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| @@ -162,7 +155,7 @@ void TIA::set_output_mode(Atari2600::TIA::OutputMode output_mode) { | |||||||
| 	// cycles_per_line * 2 cycles of information from one sync edge to the next | 	// cycles_per_line * 2 cycles of information from one sync edge to the next | ||||||
| 	crt_->set_new_display_type(cycles_per_line * 2 - 1, display_type); | 	crt_->set_new_display_type(cycles_per_line * 2 - 1, display_type); | ||||||
|  |  | ||||||
| /*	speaker_->set_input_rate((float)(get_clock_rate() / 38.0));*/ | /*	speaker_->set_input_rate(static_cast<float>(get_clock_rate() / 38.0));*/ | ||||||
| } | } | ||||||
|  |  | ||||||
| void TIA::run_for(const Cycles cycles) { | void TIA::run_for(const Cycles cycles) { | ||||||
| @@ -203,23 +196,23 @@ int TIA::get_cycles_until_horizontal_blank(const Cycles from_offset) { | |||||||
| } | } | ||||||
|  |  | ||||||
| void TIA::set_background_colour(uint8_t colour) { | void TIA::set_background_colour(uint8_t colour) { | ||||||
| 	colour_palette_[(int)ColourIndex::Background] = colour; | 	colour_palette_[static_cast<int>(ColourIndex::Background)] = colour; | ||||||
| } | } | ||||||
|  |  | ||||||
| void TIA::set_playfield(uint16_t offset, uint8_t value) { | void TIA::set_playfield(uint16_t offset, uint8_t value) { | ||||||
| 	assert(offset >= 0 && offset < 3); | 	assert(offset >= 0 && offset < 3); | ||||||
| 	switch(offset) { | 	switch(offset) { | ||||||
| 		case 0: | 		case 0: | ||||||
| 			background_[1] = (background_[1] & 0x0ffff) | ((uint32_t)reverse_table[value & 0xf0] << 16); | 			background_[1] = (background_[1] & 0x0ffff) | (static_cast<uint32_t>(reverse_table[value & 0xf0]) << 16); | ||||||
| 			background_[0] = (background_[0] & 0xffff0) | (uint32_t)(value >> 4); | 			background_[0] = (background_[0] & 0xffff0) | static_cast<uint32_t>(value >> 4); | ||||||
| 		break; | 		break; | ||||||
| 		case 1: | 		case 1: | ||||||
| 			background_[1] = (background_[1] & 0xf00ff) | ((uint32_t)value << 8); | 			background_[1] = (background_[1] & 0xf00ff) | (static_cast<uint32_t>(value) << 8); | ||||||
| 			background_[0] = (background_[0] & 0xff00f) | ((uint32_t)reverse_table[value] << 4); | 			background_[0] = (background_[0] & 0xff00f) | (static_cast<uint32_t>(reverse_table[value]) << 4); | ||||||
| 		break; | 		break; | ||||||
| 		case 2: | 		case 2: | ||||||
| 			background_[1] = (background_[1] & 0xfff00) | reverse_table[value]; | 			background_[1] = (background_[1] & 0xfff00) | reverse_table[value]; | ||||||
| 			background_[0] = (background_[0] & 0x00fff) | ((uint32_t)value << 12); | 			background_[0] = (background_[0] & 0x00fff) | (static_cast<uint32_t>(value) << 12); | ||||||
| 		break; | 		break; | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| @@ -243,7 +236,7 @@ void TIA::set_playfield_control_and_ball_size(uint8_t value) { | |||||||
| } | } | ||||||
|  |  | ||||||
| void TIA::set_playfield_ball_colour(uint8_t colour) { | void TIA::set_playfield_ball_colour(uint8_t colour) { | ||||||
| 	colour_palette_[(int)ColourIndex::PlayfieldBall] = colour; | 	colour_palette_[static_cast<int>(ColourIndex::PlayfieldBall)] = colour; | ||||||
| } | } | ||||||
|  |  | ||||||
| void TIA::set_player_number_and_size(int player, uint8_t value) { | void TIA::set_player_number_and_size(int player, uint8_t value) { | ||||||
| @@ -305,7 +298,7 @@ void TIA::set_player_motion(int player, uint8_t motion) { | |||||||
|  |  | ||||||
| void TIA::set_player_missile_colour(int player, uint8_t colour) { | void TIA::set_player_missile_colour(int player, uint8_t colour) { | ||||||
| 	assert(player >= 0 && player < 2); | 	assert(player >= 0 && player < 2); | ||||||
| 	colour_palette_[(int)ColourIndex::PlayerMissile0 + player] = colour; | 	colour_palette_[static_cast<int>(ColourIndex::PlayerMissile0) + player] = colour; | ||||||
| } | } | ||||||
|  |  | ||||||
| void TIA::set_missile_enable(int missile, bool enabled) { | void TIA::set_missile_enable(int missile, bool enabled) { | ||||||
| @@ -360,7 +353,7 @@ void TIA::clear_motion() { | |||||||
| } | } | ||||||
|  |  | ||||||
| uint8_t TIA::get_collision_flags(int offset) { | uint8_t TIA::get_collision_flags(int offset) { | ||||||
| 	return (uint8_t)((collision_flags_ >> (offset << 1)) << 6) & 0xc0; | 	return static_cast<uint8_t>((collision_flags_ >> (offset << 1)) << 6) & 0xc0; | ||||||
| } | } | ||||||
|  |  | ||||||
| void TIA::clear_collision_flags() { | void TIA::clear_collision_flags() { | ||||||
| @@ -388,7 +381,7 @@ void TIA::output_for_cycles(int number_of_cycles) { | |||||||
|  |  | ||||||
| 	if(!output_cursor) { | 	if(!output_cursor) { | ||||||
| 		if(line_end_function_) line_end_function_(collision_buffer_); | 		if(line_end_function_) line_end_function_(collision_buffer_); | ||||||
| 		memset(collision_buffer_, 0, sizeof(collision_buffer_)); | 		std::memset(collision_buffer_, 0, sizeof(collision_buffer_)); | ||||||
|  |  | ||||||
| 		ball_.motion_time %= 228; | 		ball_.motion_time %= 228; | ||||||
| 		player_[0].motion_time %= 228; | 		player_[0].motion_time %= 228; | ||||||
| @@ -401,22 +394,22 @@ void TIA::output_for_cycles(int number_of_cycles) { | |||||||
| 	int latent_start = output_cursor + 4; | 	int latent_start = output_cursor + 4; | ||||||
| 	int latent_end = horizontal_counter_ + 4; | 	int latent_end = horizontal_counter_ + 4; | ||||||
| 	draw_playfield(latent_start, latent_end); | 	draw_playfield(latent_start, latent_end); | ||||||
| 	draw_object<Player>(player_[0], (uint8_t)CollisionType::Player0, output_cursor, horizontal_counter_); | 	draw_object<Player>(player_[0], static_cast<uint8_t>(CollisionType::Player0), output_cursor, horizontal_counter_); | ||||||
| 	draw_object<Player>(player_[1], (uint8_t)CollisionType::Player1, output_cursor, horizontal_counter_); | 	draw_object<Player>(player_[1], static_cast<uint8_t>(CollisionType::Player1), output_cursor, horizontal_counter_); | ||||||
| 	draw_missile(missile_[0], player_[0], (uint8_t)CollisionType::Missile0, output_cursor, horizontal_counter_); | 	draw_missile(missile_[0], player_[0], static_cast<uint8_t>(CollisionType::Missile0), output_cursor, horizontal_counter_); | ||||||
| 	draw_missile(missile_[1], player_[1], (uint8_t)CollisionType::Missile1, output_cursor, horizontal_counter_); | 	draw_missile(missile_[1], player_[1], static_cast<uint8_t>(CollisionType::Missile1), output_cursor, horizontal_counter_); | ||||||
| 	draw_object<Ball>(ball_, (uint8_t)CollisionType::Ball, output_cursor, horizontal_counter_); | 	draw_object<Ball>(ball_, static_cast<uint8_t>(CollisionType::Ball), output_cursor, horizontal_counter_); | ||||||
|  |  | ||||||
| 	// convert to television signals | 	// convert to television signals | ||||||
|  |  | ||||||
| #define Period(function, target)	\ | #define Period(function, target)	\ | ||||||
| 	if(output_cursor < target) { \ | 	if(output_cursor < target) { \ | ||||||
| 		if(horizontal_counter_ <= target) { \ | 		if(horizontal_counter_ <= target) { \ | ||||||
| 			if(crt_) crt_->function((unsigned int)((horizontal_counter_ - output_cursor) * 2)); \ | 			if(crt_) crt_->function(static_cast<unsigned int>((horizontal_counter_ - output_cursor) * 2)); \ | ||||||
| 			horizontal_counter_ %= cycles_per_line; \ | 			horizontal_counter_ %= cycles_per_line; \ | ||||||
| 			return; \ | 			return; \ | ||||||
| 		} else { \ | 		} else { \ | ||||||
| 			if(crt_) crt_->function((unsigned int)((target - output_cursor) * 2)); \ | 			if(crt_) crt_->function(static_cast<unsigned int>((target - output_cursor) * 2)); \ | ||||||
| 			output_cursor = target; \ | 			output_cursor = target; \ | ||||||
| 		} \ | 		} \ | ||||||
| 	} | 	} | ||||||
| @@ -442,12 +435,12 @@ void TIA::output_for_cycles(int number_of_cycles) { | |||||||
| 	if(output_mode_ & blank_flag) { | 	if(output_mode_ & blank_flag) { | ||||||
| 		if(pixel_target_) { | 		if(pixel_target_) { | ||||||
| 			output_pixels(pixels_start_location_, output_cursor); | 			output_pixels(pixels_start_location_, output_cursor); | ||||||
| 			if(crt_) crt_->output_data((unsigned int)(output_cursor - pixels_start_location_) * 2, 2); | 			if(crt_) crt_->output_data(static_cast<unsigned int>(output_cursor - pixels_start_location_) * 2, 2); | ||||||
| 			pixel_target_ = nullptr; | 			pixel_target_ = nullptr; | ||||||
| 			pixels_start_location_ = 0; | 			pixels_start_location_ = 0; | ||||||
| 		} | 		} | ||||||
| 		int duration = std::min(228, horizontal_counter_) - output_cursor; | 		int duration = std::min(228, horizontal_counter_) - output_cursor; | ||||||
| 		if(crt_) crt_->output_blank((unsigned int)(duration * 2)); | 		if(crt_) crt_->output_blank(static_cast<unsigned int>(duration * 2)); | ||||||
| 	} else { | 	} else { | ||||||
| 		if(!pixels_start_location_ && crt_) { | 		if(!pixels_start_location_ && crt_) { | ||||||
| 			pixels_start_location_ = output_cursor; | 			pixels_start_location_ = output_cursor; | ||||||
| @@ -464,7 +457,7 @@ void TIA::output_for_cycles(int number_of_cycles) { | |||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		if(horizontal_counter_ == cycles_per_line && crt_) { | 		if(horizontal_counter_ == cycles_per_line && crt_) { | ||||||
| 			crt_->output_data((unsigned int)(output_cursor - pixels_start_location_) * 2, 2); | 			crt_->output_data(static_cast<unsigned int>(output_cursor - pixels_start_location_) * 2, 2); | ||||||
| 			pixel_target_ = nullptr; | 			pixel_target_ = nullptr; | ||||||
| 			pixels_start_location_ = 0; | 			pixels_start_location_ = 0; | ||||||
| 		} | 		} | ||||||
| @@ -490,18 +483,18 @@ void TIA::output_pixels(int start, int end) { | |||||||
| 	if(playfield_priority_ == PlayfieldPriority::Score) { | 	if(playfield_priority_ == PlayfieldPriority::Score) { | ||||||
| 		while(start < end && start < first_pixel_cycle + 80) { | 		while(start < end && start < first_pixel_cycle + 80) { | ||||||
| 			uint8_t buffer_value = collision_buffer_[start - first_pixel_cycle]; | 			uint8_t buffer_value = collision_buffer_[start - first_pixel_cycle]; | ||||||
| 			pixel_target_[target_position] = colour_palette_[colour_mask_by_mode_collision_flags_[(int)ColourMode::ScoreLeft][buffer_value]]; | 			pixel_target_[target_position] = colour_palette_[colour_mask_by_mode_collision_flags_[static_cast<int>(ColourMode::ScoreLeft)][buffer_value]]; | ||||||
| 			start++; | 			start++; | ||||||
| 			target_position++; | 			target_position++; | ||||||
| 		} | 		} | ||||||
| 		while(start < end) { | 		while(start < end) { | ||||||
| 			uint8_t buffer_value = collision_buffer_[start - first_pixel_cycle]; | 			uint8_t buffer_value = collision_buffer_[start - first_pixel_cycle]; | ||||||
| 			pixel_target_[target_position] = colour_palette_[colour_mask_by_mode_collision_flags_[(int)ColourMode::ScoreRight][buffer_value]]; | 			pixel_target_[target_position] = colour_palette_[colour_mask_by_mode_collision_flags_[static_cast<int>(ColourMode::ScoreRight)][buffer_value]]; | ||||||
| 			start++; | 			start++; | ||||||
| 			target_position++; | 			target_position++; | ||||||
| 		} | 		} | ||||||
| 	} else { | 	} else { | ||||||
| 		int table_index = (int)((playfield_priority_ == PlayfieldPriority::Standard) ? ColourMode::Standard : ColourMode::OnTop); | 		int table_index = static_cast<int>((playfield_priority_ == PlayfieldPriority::Standard) ? ColourMode::Standard : ColourMode::OnTop); | ||||||
| 		while(start < end) { | 		while(start < end) { | ||||||
| 			uint8_t buffer_value = collision_buffer_[start - first_pixel_cycle]; | 			uint8_t buffer_value = collision_buffer_[start - first_pixel_cycle]; | ||||||
| 			pixel_target_[target_position] = colour_palette_[colour_mask_by_mode_collision_flags_[table_index][buffer_value]]; | 			pixel_target_[target_position] = colour_palette_[colour_mask_by_mode_collision_flags_[table_index][buffer_value]]; | ||||||
| @@ -538,7 +531,7 @@ void TIA::output_line() { | |||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| #pragma mark - Playfield output | // MARK: - Playfield output | ||||||
|  |  | ||||||
| void TIA::draw_playfield(int start, int end) { | void TIA::draw_playfield(int start, int end) { | ||||||
| 	// don't do anything if this window ends too early | 	// don't do anything if this window ends too early | ||||||
| @@ -553,12 +546,12 @@ void TIA::draw_playfield(int start, int end) { | |||||||
| 	while(aligned_position < end) { | 	while(aligned_position < end) { | ||||||
| 		int offset = (aligned_position - first_pixel_cycle) >> 2; | 		int offset = (aligned_position - first_pixel_cycle) >> 2; | ||||||
| 		uint32_t value = ((background_[(offset/20)&background_half_mask_] >> (offset%20))&1) * 0x01010101; | 		uint32_t value = ((background_[(offset/20)&background_half_mask_] >> (offset%20))&1) * 0x01010101; | ||||||
| 		*(uint32_t *)&collision_buffer_[aligned_position - first_pixel_cycle] |= value; | 		*reinterpret_cast<uint32_t *>(&collision_buffer_[aligned_position - first_pixel_cycle]) |= value; | ||||||
| 		aligned_position += 4; | 		aligned_position += 4; | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| #pragma mark - Motion | // MARK: - Motion | ||||||
|  |  | ||||||
| template<class T> void TIA::perform_motion_step(T &object) { | template<class T> void TIA::perform_motion_step(T &object) { | ||||||
| 	if((object.motion_step ^ (object.motion ^ 8)) == 0xf) { | 	if((object.motion_step ^ (object.motion ^ 8)) == 0xf) { | ||||||
| @@ -666,7 +659,7 @@ template<class T> void TIA::draw_object_visible(T &object, const uint8_t collisi | |||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| #pragma mark - Missile drawing | // MARK: - Missile drawing | ||||||
|  |  | ||||||
| void TIA::draw_missile(Missile &missile, Player &player, const uint8_t collision_identity, int start, int end) { | void TIA::draw_missile(Missile &missile, Player &player, const uint8_t collision_identity, int start, int end) { | ||||||
| 	if(!missile.locked_to_player || player.latched_pixel4_time < 0) { | 	if(!missile.locked_to_player || player.latched_pixel4_time < 0) { | ||||||
|   | |||||||
| @@ -73,18 +73,18 @@ class TIA { | |||||||
| 		uint8_t get_collision_flags(int offset); | 		uint8_t get_collision_flags(int offset); | ||||||
| 		void clear_collision_flags(); | 		void clear_collision_flags(); | ||||||
|  |  | ||||||
| 		virtual std::shared_ptr<Outputs::CRT::CRT> get_crt() { return crt_; } | 		Outputs::CRT::CRT *get_crt() { return crt_.get(); } | ||||||
|  |  | ||||||
| 	private: | 	private: | ||||||
| 		TIA(bool create_crt); | 		TIA(bool create_crt); | ||||||
| 		std::shared_ptr<Outputs::CRT::CRT> crt_; | 		std::unique_ptr<Outputs::CRT::CRT> crt_; | ||||||
| 		std::function<void(uint8_t *output_buffer)> line_end_function_; | 		std::function<void(uint8_t *output_buffer)> line_end_function_; | ||||||
|  |  | ||||||
| 		// the master counter; counts from 0 to 228 with all visible pixels being in the final 160 | 		// the master counter; counts from 0 to 228 with all visible pixels being in the final 160 | ||||||
| 		int horizontal_counter_; | 		int horizontal_counter_ = 0; | ||||||
|  |  | ||||||
| 		// contains flags to indicate whether sync or blank are currently active | 		// contains flags to indicate whether sync or blank are currently active | ||||||
| 		int output_mode_; | 		int output_mode_ = 0; | ||||||
|  |  | ||||||
| 		// keeps track of the target pixel buffer for this line and when it was acquired, and a corresponding collision buffer | 		// keeps track of the target pixel buffer for this line and when it was acquired, and a corresponding collision buffer | ||||||
| 		alignas(alignof(uint32_t)) uint8_t collision_buffer_[160]; | 		alignas(alignof(uint32_t)) uint8_t collision_buffer_[160]; | ||||||
| @@ -97,7 +97,7 @@ class TIA { | |||||||
| 			Missile1	= (1 << 5) | 			Missile1	= (1 << 5) | ||||||
| 		}; | 		}; | ||||||
|  |  | ||||||
| 		int collision_flags_; | 		int collision_flags_ = 0; | ||||||
| 		int collision_flags_by_buffer_vaules_[64]; | 		int collision_flags_by_buffer_vaules_[64]; | ||||||
|  |  | ||||||
| 		// colour mapping tables | 		// colour mapping tables | ||||||
| @@ -118,13 +118,14 @@ class TIA { | |||||||
| 		uint8_t colour_palette_[4]; | 		uint8_t colour_palette_[4]; | ||||||
|  |  | ||||||
| 		// playfield state | 		// playfield state | ||||||
| 		int background_half_mask_; | 		int background_half_mask_ = 0; | ||||||
| 		enum class PlayfieldPriority { | 		enum class PlayfieldPriority { | ||||||
| 			Standard, | 			Standard, | ||||||
| 			Score, | 			Score, | ||||||
| 			OnTop | 			OnTop | ||||||
| 		} playfield_priority_; | 		} playfield_priority_; | ||||||
| 		uint32_t background_[2];	// contains two 20-bit bitfields representing the background state; | 		uint32_t background_[2] = {0, 0}; | ||||||
|  | 				// contains two 20-bit bitfields representing the background state; | ||||||
| 				// at index 0 is the left-hand side of the playfield with bit 0 being | 				// at index 0 is the left-hand side of the playfield with bit 0 being | ||||||
| 				// the first bit to display, bit 1 the second, etc. Index 1 contains | 				// the first bit to display, bit 1 the second, etc. Index 1 contains | ||||||
| 				// a mirror image of index 0. If the playfield is being displayed in | 				// a mirror image of index 0. If the playfield is being displayed in | ||||||
| @@ -135,42 +136,27 @@ class TIA { | |||||||
| 		// objects | 		// objects | ||||||
| 		template<class T> struct Object { | 		template<class T> struct Object { | ||||||
| 			// the two programmer-set values | 			// the two programmer-set values | ||||||
| 			int position; | 			int position = 0; | ||||||
| 			int motion; | 			int motion = 0; | ||||||
|  |  | ||||||
| 			// motion_step_ is the current motion counter value; motion_time_ is the next time it will fire | 			// motion_step_ is the current motion counter value; motion_time_ is the next time it will fire | ||||||
| 			int motion_step; | 			int motion_step = 0; | ||||||
| 			int motion_time; | 			int motion_time = 0; | ||||||
|  |  | ||||||
| 			// indicates whether this object is currently undergoing motion | 			// indicates whether this object is currently undergoing motion | ||||||
| 			bool is_moving; | 			bool is_moving = false; | ||||||
|  |  | ||||||
| 			Object() : position(0), motion(0), motion_step(0), motion_time(0), is_moving(false) {}; |  | ||||||
| 		}; | 		}; | ||||||
|  |  | ||||||
| 		// player state | 		// player state | ||||||
| 		struct Player: public Object<Player> { | 		struct Player: public Object<Player> { | ||||||
| 			Player() : | 			int adder = 4; | ||||||
| 				adder(4), | 			int copy_flags = 0;				// a bit field, corresponding to the first few values of NUSIZ | ||||||
| 				copy_flags(0), | 			uint8_t graphic[2] = {0, 0};	// the player graphic; 1 = new, 0 = current | ||||||
| 				graphic{0, 0}, | 			int reverse_mask = false;		// 7 for a reflected player, 0 for normal | ||||||
| 				reverse_mask(false), | 			int graphic_index = 0; | ||||||
| 				graphic_index(0), |  | ||||||
| 				pixel_position(32), |  | ||||||
| 				pixel_counter(0), |  | ||||||
| 				latched_pixel4_time(-1), |  | ||||||
| 				copy_index_(0), |  | ||||||
| 				queue_read_pointer_(0), |  | ||||||
| 				queue_write_pointer_(0) {} |  | ||||||
|  |  | ||||||
| 			int adder; | 			int pixel_position = 32, pixel_counter = 0; | ||||||
| 			int copy_flags;		// a bit field, corresponding to the first few values of NUSIZ | 			int latched_pixel4_time = -1; | ||||||
| 			uint8_t graphic[2];	// the player graphic; 1 = new, 0 = current |  | ||||||
| 			int reverse_mask;	// 7 for a reflected player, 0 for normal |  | ||||||
| 			int graphic_index; |  | ||||||
|  |  | ||||||
| 			int pixel_position, pixel_counter; |  | ||||||
| 			int latched_pixel4_time; |  | ||||||
| 			const bool enqueues = true; | 			const bool enqueues = true; | ||||||
|  |  | ||||||
| 			inline void skip_pixels(const int count, int from_horizontal_counter) { | 			inline void skip_pixels(const int count, int from_horizontal_counter) { | ||||||
| @@ -219,15 +205,14 @@ class TIA { | |||||||
| 			} | 			} | ||||||
|  |  | ||||||
| 			private: | 			private: | ||||||
| 				int copy_index_; | 				int copy_index_ = 0; | ||||||
| 				struct QueuedPixels { | 				struct QueuedPixels { | ||||||
| 					int start, end; | 					int start = 0, end = 0; | ||||||
| 					int pixel_position; | 					int pixel_position = 0; | ||||||
| 					int adder; | 					int adder = 0; | ||||||
| 					int reverse_mask; | 					int reverse_mask = false; | ||||||
| 					QueuedPixels() : start(0), end(0), pixel_position(0), adder(0), reverse_mask(false) {} |  | ||||||
| 				} queue_[4]; | 				} queue_[4]; | ||||||
| 				int queue_read_pointer_, queue_write_pointer_; | 				int queue_read_pointer_ = 0, queue_write_pointer_ = 0; | ||||||
|  |  | ||||||
| 				inline void output_pixels(uint8_t *const target, const int count, const uint8_t collision_identity, int output_pixel_position, int output_adder, int output_reverse_mask) { | 				inline void output_pixels(uint8_t *const target, const int count, const uint8_t collision_identity, int output_pixel_position, int output_adder, int output_reverse_mask) { | ||||||
| 					if(output_pixel_position == 32 || !graphic[graphic_index]) return; | 					if(output_pixel_position == 32 || !graphic[graphic_index]) return; | ||||||
| @@ -244,8 +229,8 @@ class TIA { | |||||||
|  |  | ||||||
| 		// common actor for things that appear as a horizontal run of pixels | 		// common actor for things that appear as a horizontal run of pixels | ||||||
| 		struct HorizontalRun: public Object<HorizontalRun> { | 		struct HorizontalRun: public Object<HorizontalRun> { | ||||||
| 			int pixel_position; | 			int pixel_position = 0; | ||||||
| 			int size; | 			int size = 1; | ||||||
| 			const bool enqueues = false; | 			const bool enqueues = false; | ||||||
|  |  | ||||||
| 			inline void skip_pixels(const int count, int from_horizontal_counter) { | 			inline void skip_pixels(const int count, int from_horizontal_counter) { | ||||||
| @@ -268,16 +253,13 @@ class TIA { | |||||||
|  |  | ||||||
| 			void dequeue_pixels(uint8_t *const target, const uint8_t collision_identity, const int time_now) {} | 			void dequeue_pixels(uint8_t *const target, const uint8_t collision_identity, const int time_now) {} | ||||||
| 			void enqueue_pixels(const int start, const int end, int from_horizontal_counter) {} | 			void enqueue_pixels(const int start, const int end, int from_horizontal_counter) {} | ||||||
|  |  | ||||||
| 			HorizontalRun() : pixel_position(0), size(1) {} |  | ||||||
| 		}; | 		}; | ||||||
|  |  | ||||||
|  |  | ||||||
| 		// missile state | 		// missile state | ||||||
| 		struct Missile: public HorizontalRun { | 		struct Missile: public HorizontalRun { | ||||||
| 			bool enabled; | 			bool enabled = false; | ||||||
| 			bool locked_to_player; | 			bool locked_to_player = false; | ||||||
| 			int copy_flags; | 			int copy_flags = 0; | ||||||
|  |  | ||||||
| 			inline void output_pixels(uint8_t *const target, const int count, const uint8_t collision_identity, int from_horizontal_counter) { | 			inline void output_pixels(uint8_t *const target, const int count, const uint8_t collision_identity, int from_horizontal_counter) { | ||||||
| 				if(!pixel_position) return; | 				if(!pixel_position) return; | ||||||
| @@ -287,14 +269,12 @@ class TIA { | |||||||
| 					skip_pixels(count, from_horizontal_counter); | 					skip_pixels(count, from_horizontal_counter); | ||||||
| 				} | 				} | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
| 			Missile() : enabled(false), copy_flags(0) {} |  | ||||||
| 		} missile_[2]; | 		} missile_[2]; | ||||||
|  |  | ||||||
| 		// ball state | 		// ball state | ||||||
| 		struct Ball: public HorizontalRun { | 		struct Ball: public HorizontalRun { | ||||||
| 			bool enabled[2]; | 			bool enabled[2] = {false, false}; | ||||||
| 			int enabled_index; | 			int enabled_index = 0; | ||||||
| 			const int copy_flags = 0; | 			const int copy_flags = 0; | ||||||
|  |  | ||||||
| 			inline void output_pixels(uint8_t *const target, const int count, const uint8_t collision_identity, int from_horizontal_counter) { | 			inline void output_pixels(uint8_t *const target, const int count, const uint8_t collision_identity, int from_horizontal_counter) { | ||||||
| @@ -305,12 +285,10 @@ class TIA { | |||||||
| 					skip_pixels(count, from_horizontal_counter); | 					skip_pixels(count, from_horizontal_counter); | ||||||
| 				} | 				} | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
| 			Ball() : enabled{false, false}, enabled_index(0) {} |  | ||||||
| 		} ball_; | 		} ball_; | ||||||
|  |  | ||||||
| 		// motion | 		// motion | ||||||
| 		bool horizontal_blank_extend_; | 		bool horizontal_blank_extend_ = false; | ||||||
| 		template<class T> void perform_border_motion(T &object, int start, int end); | 		template<class T> void perform_border_motion(T &object, int start, int end); | ||||||
| 		template<class T> void perform_motion_step(T &object); | 		template<class T> void perform_motion_step(T &object); | ||||||
|  |  | ||||||
| @@ -323,8 +301,8 @@ class TIA { | |||||||
| 		inline void output_for_cycles(int number_of_cycles); | 		inline void output_for_cycles(int number_of_cycles); | ||||||
| 		inline void output_line(); | 		inline void output_line(); | ||||||
|  |  | ||||||
| 		int pixels_start_location_; | 		int pixels_start_location_ = 0; | ||||||
| 		uint8_t *pixel_target_; | 		uint8_t *pixel_target_ = nullptr; | ||||||
| 		inline void output_pixels(int start, int end); | 		inline void output_pixels(int start, int end); | ||||||
| }; | }; | ||||||
|  |  | ||||||
|   | |||||||
| @@ -6,31 +6,32 @@ | |||||||
| //  Copyright © 2016 Thomas Harte. All rights reserved.
 | //  Copyright © 2016 Thomas Harte. All rights reserved.
 | ||||||
| //
 | //
 | ||||||
| 
 | 
 | ||||||
| #include "Speaker.hpp" | #include "TIASound.hpp" | ||||||
| 
 | 
 | ||||||
| using namespace Atari2600; | using namespace Atari2600; | ||||||
| 
 | 
 | ||||||
| Atari2600::Speaker::Speaker() : | Atari2600::TIASound::TIASound(Concurrency::DeferringAsyncTaskQueue &audio_queue) : | ||||||
|  | 	audio_queue_(audio_queue), | ||||||
| 	poly4_counter_{0x00f, 0x00f}, | 	poly4_counter_{0x00f, 0x00f}, | ||||||
| 	poly5_counter_{0x01f, 0x01f}, | 	poly5_counter_{0x01f, 0x01f}, | ||||||
| 	poly9_counter_{0x1ff, 0x1ff} | 	poly9_counter_{0x1ff, 0x1ff} | ||||||
| {} | {} | ||||||
| 
 | 
 | ||||||
| void Atari2600::Speaker::set_volume(int channel, uint8_t volume) { | void Atari2600::TIASound::set_volume(int channel, uint8_t volume) { | ||||||
| 	enqueue([=]() { | 	audio_queue_.defer([=]() { | ||||||
| 		volume_[channel] = volume & 0xf; | 		volume_[channel] = volume & 0xf; | ||||||
| 	}); | 	}); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void Atari2600::Speaker::set_divider(int channel, uint8_t divider) { | void Atari2600::TIASound::set_divider(int channel, uint8_t divider) { | ||||||
| 	enqueue([=]() { | 	audio_queue_.defer([=]() { | ||||||
| 		divider_[channel] = divider & 0x1f; | 		divider_[channel] = divider & 0x1f; | ||||||
| 		divider_counter_[channel] = 0; | 		divider_counter_[channel] = 0; | ||||||
| 	}); | 	}); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void Atari2600::Speaker::set_control(int channel, uint8_t control) { | void Atari2600::TIASound::set_control(int channel, uint8_t control) { | ||||||
| 	enqueue([=]() { | 	audio_queue_.defer([=]() { | ||||||
| 		control_[channel] = control & 0xf; | 		control_[channel] = control & 0xf; | ||||||
| 	}); | 	}); | ||||||
| } | } | ||||||
| @@ -39,7 +40,7 @@ void Atari2600::Speaker::set_control(int channel, uint8_t control) { | |||||||
| #define advance_poly5(c) poly5_counter_[channel] = (poly5_counter_[channel] >> 1) | (((poly5_counter_[channel] << 4) ^ (poly5_counter_[channel] << 2))&0x010) | #define advance_poly5(c) poly5_counter_[channel] = (poly5_counter_[channel] >> 1) | (((poly5_counter_[channel] << 4) ^ (poly5_counter_[channel] << 2))&0x010) | ||||||
| #define advance_poly9(c) poly9_counter_[channel] = (poly9_counter_[channel] >> 1) | (((poly9_counter_[channel] << 4) ^ (poly9_counter_[channel] << 8))&0x100) | #define advance_poly9(c) poly9_counter_[channel] = (poly9_counter_[channel] >> 1) | (((poly9_counter_[channel] << 4) ^ (poly9_counter_[channel] << 8))&0x100) | ||||||
| 
 | 
 | ||||||
| void Atari2600::Speaker::get_samples(unsigned int number_of_samples, int16_t *target) { | void Atari2600::TIASound::get_samples(std::size_t number_of_samples, int16_t *target) { | ||||||
| 	for(unsigned int c = 0; c < number_of_samples; c++) { | 	for(unsigned int c = 0; c < number_of_samples; c++) { | ||||||
| 		target[c] = 0; | 		target[c] = 0; | ||||||
| 		for(int channel = 0; channel < 2; channel++) { | 		for(int channel = 0; channel < 2; channel++) { | ||||||
| @@ -1,15 +1,16 @@ | |||||||
| //
 | //
 | ||||||
| //  Speaker.hpp
 | //  TIASound.hpp
 | ||||||
| //  Clock Signal
 | //  Clock Signal
 | ||||||
| //
 | //
 | ||||||
| //  Created by Thomas Harte on 03/12/2016.
 | //  Created by Thomas Harte on 03/12/2016.
 | ||||||
| //  Copyright © 2016 Thomas Harte. All rights reserved.
 | //  Copyright © 2016 Thomas Harte. All rights reserved.
 | ||||||
| //
 | //
 | ||||||
| 
 | 
 | ||||||
| #ifndef Atari2600_Speaker_hpp | #ifndef Atari2600_TIASound_hpp | ||||||
| #define Atari2600_Speaker_hpp | #define Atari2600_TIASound_hpp | ||||||
| 
 | 
 | ||||||
| #include "../../Outputs/Speaker.hpp" | #include "../../Outputs/Speaker/Implementation/SampleSource.hpp" | ||||||
|  | #include "../../Concurrency/AsyncTaskQueue.hpp" | ||||||
| 
 | 
 | ||||||
| namespace Atari2600 { | namespace Atari2600 { | ||||||
| 
 | 
 | ||||||
| @@ -17,17 +18,19 @@ namespace Atari2600 { | |||||||
| // will give greater resolution to changes in audio state. 1, 2 and 19 are the only divisors of 38.
 | // will give greater resolution to changes in audio state. 1, 2 and 19 are the only divisors of 38.
 | ||||||
| const int CPUTicksPerAudioTick = 2; | const int CPUTicksPerAudioTick = 2; | ||||||
| 
 | 
 | ||||||
| class Speaker: public ::Outputs::Filter<Speaker> { | class TIASound: public Outputs::Speaker::SampleSource { | ||||||
| 	public: | 	public: | ||||||
| 		Speaker(); | 		TIASound(Concurrency::DeferringAsyncTaskQueue &audio_queue); | ||||||
| 
 | 
 | ||||||
| 		void set_volume(int channel, uint8_t volume); | 		void set_volume(int channel, uint8_t volume); | ||||||
| 		void set_divider(int channel, uint8_t divider); | 		void set_divider(int channel, uint8_t divider); | ||||||
| 		void set_control(int channel, uint8_t control); | 		void set_control(int channel, uint8_t control); | ||||||
| 
 | 
 | ||||||
| 		void get_samples(unsigned int number_of_samples, int16_t *target); | 		void get_samples(std::size_t number_of_samples, int16_t *target); | ||||||
| 
 | 
 | ||||||
| 	private: | 	private: | ||||||
|  | 		Concurrency::DeferringAsyncTaskQueue &audio_queue_; | ||||||
|  | 
 | ||||||
| 		uint8_t volume_[2]; | 		uint8_t volume_[2]; | ||||||
| 		uint8_t divider_[2]; | 		uint8_t divider_[2]; | ||||||
| 		uint8_t control_[2]; | 		uint8_t control_[2]; | ||||||
| @@ -10,8 +10,9 @@ | |||||||
| #define CRTMachine_hpp | #define CRTMachine_hpp | ||||||
|  |  | ||||||
| #include "../Outputs/CRT/CRT.hpp" | #include "../Outputs/CRT/CRT.hpp" | ||||||
| #include "../Outputs/Speaker.hpp" | #include "../Outputs/Speaker/Speaker.hpp" | ||||||
| #include "../ClockReceiver/ClockReceiver.hpp" | #include "../ClockReceiver/ClockReceiver.hpp" | ||||||
|  | #include "ROMMachine.hpp" | ||||||
|  |  | ||||||
| namespace CRTMachine { | namespace CRTMachine { | ||||||
|  |  | ||||||
| @@ -20,10 +21,8 @@ namespace CRTMachine { | |||||||
| 	that optionally provide a speaker, and that nominate a clock rate and can announce to a delegate | 	that optionally provide a speaker, and that nominate a clock rate and can announce to a delegate | ||||||
| 	should that clock rate change. | 	should that clock rate change. | ||||||
| */ | */ | ||||||
| class Machine { | class Machine: public ROMMachine::Machine { | ||||||
| 	public: | 	public: | ||||||
| 		Machine() : clock_is_unlimited_(false), delegate_(nullptr) {} |  | ||||||
|  |  | ||||||
| 		/*! | 		/*! | ||||||
| 			Causes the machine to set up its CRT and, if it has one, speaker. The caller guarantees | 			Causes the machine to set up its CRT and, if it has one, speaker. The caller guarantees | ||||||
| 			that an OpenGL context is bound. | 			that an OpenGL context is bound. | ||||||
| @@ -37,10 +36,10 @@ class Machine { | |||||||
| 		virtual void close_output() = 0; | 		virtual void close_output() = 0; | ||||||
|  |  | ||||||
| 		/// @returns The CRT this machine is drawing to. Should not be @c nullptr. | 		/// @returns The CRT this machine is drawing to. Should not be @c nullptr. | ||||||
| 		virtual std::shared_ptr<Outputs::CRT::CRT> get_crt() = 0; | 		virtual Outputs::CRT::CRT *get_crt() = 0; | ||||||
|  |  | ||||||
| 		/// @returns The speaker that receives this machine's output, or @c nullptr if this machine is mute. | 		/// @returns The speaker that receives this machine's output, or @c nullptr if this machine is mute. | ||||||
| 		virtual std::shared_ptr<Outputs::Speaker> get_speaker() = 0; | 		virtual Outputs::Speaker::Speaker *get_speaker() = 0; | ||||||
|  |  | ||||||
| 		/// Runs the machine for @c cycles. | 		/// Runs the machine for @c cycles. | ||||||
| 		virtual void run_for(const Cycles cycles) = 0; | 		virtual void run_for(const Cycles cycles) = 0; | ||||||
| @@ -74,9 +73,9 @@ class Machine { | |||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 	private: | 	private: | ||||||
| 		Delegate *delegate_; | 		Delegate *delegate_ = nullptr; | ||||||
| 		double clock_rate_; | 		double clock_rate_ = 1.0; | ||||||
| 		bool clock_is_unlimited_; | 		bool clock_is_unlimited_ = false; | ||||||
| }; | }; | ||||||
|  |  | ||||||
| } | } | ||||||
|   | |||||||
| @@ -9,159 +9,43 @@ | |||||||
| #ifndef Commodore1540_hpp | #ifndef Commodore1540_hpp | ||||||
| #define Commodore1540_hpp | #define Commodore1540_hpp | ||||||
|  |  | ||||||
| #include "../../../Processors/6502/6502.hpp" |  | ||||||
| #include "../../../Components/6522/6522.hpp" |  | ||||||
|  |  | ||||||
| #include "../SerialBus.hpp" | #include "../SerialBus.hpp" | ||||||
|  | #include "../../ROMMachine.hpp" | ||||||
| #include "../../../Storage/Disk/Disk.hpp" | #include "../../../Storage/Disk/Disk.hpp" | ||||||
| #include "../../../Storage/Disk/DiskController.hpp" | #include "Implementation/C1540Base.hpp" | ||||||
|  |  | ||||||
| namespace Commodore { | namespace Commodore { | ||||||
| namespace C1540 { | namespace C1540 { | ||||||
|  |  | ||||||
| /*! |  | ||||||
| 	An implementation of the serial-port VIA in a Commodore 1540 — the VIA that facilitates all |  | ||||||
| 	IEC bus communications. |  | ||||||
|  |  | ||||||
| 	It is wired up such that Port B contains: |  | ||||||
| 		Bit 0:		data input; 1 if the line is low, 0 if it is high; |  | ||||||
| 		Bit 1:		data output; 1 if the line should be low, 0 if it should be high; |  | ||||||
| 		Bit 2:		clock input; 1 if the line is low, 0 if it is high; |  | ||||||
| 		Bit 3:		clock output; 1 if the line is low, 0 if it is high; |  | ||||||
| 		Bit 4:		attention acknowledge output; exclusive ORd with the attention input and ORd onto the data output; |  | ||||||
| 		Bits 5/6:	device select input; the 1540 will act as device 8 + [value of bits] |  | ||||||
| 		Bit 7:		attention input; 1 if the line is low, 0 if it is high |  | ||||||
|  |  | ||||||
| 	The attention input is also connected to CA1, similarly inverted — the CA1 wire will be high when the bus is low and vice versa. |  | ||||||
| */ |  | ||||||
| class SerialPortVIA: public MOS::MOS6522<SerialPortVIA>, public MOS::MOS6522IRQDelegate { |  | ||||||
| 	public: |  | ||||||
| 		using MOS6522IRQDelegate::set_interrupt_status; |  | ||||||
|  |  | ||||||
| 		SerialPortVIA(); |  | ||||||
|  |  | ||||||
| 		uint8_t get_port_input(Port); |  | ||||||
|  |  | ||||||
| 		void set_port_output(Port, uint8_t value, uint8_t mask); |  | ||||||
| 		void set_serial_line_state(::Commodore::Serial::Line, bool); |  | ||||||
|  |  | ||||||
| 		void set_serial_port(const std::shared_ptr<::Commodore::Serial::Port> &); |  | ||||||
|  |  | ||||||
| 	private: |  | ||||||
| 		uint8_t port_b_; |  | ||||||
| 		std::weak_ptr<::Commodore::Serial::Port> serial_port_; |  | ||||||
| 		bool attention_acknowledge_level_, attention_level_input_, data_level_output_; |  | ||||||
|  |  | ||||||
| 		void update_data_line(); |  | ||||||
| }; |  | ||||||
|  |  | ||||||
| /*! |  | ||||||
| 	An implementation of the drive VIA in a Commodore 1540 — the VIA that is used to interface with the disk. |  | ||||||
|  |  | ||||||
| 	It is wired up such that Port B contains: |  | ||||||
| 		Bits 0/1:	head step direction |  | ||||||
| 		Bit 2:		motor control |  | ||||||
| 		Bit 3:		LED control (TODO) |  | ||||||
| 		Bit 4:		write protect photocell status (TODO) |  | ||||||
| 		Bits 5/6:	read/write density |  | ||||||
| 		Bit 7:		0 if sync marks are currently being detected, 1 otherwise. |  | ||||||
|  |  | ||||||
| 	... and Port A contains the byte most recently read from the disk or the byte next to write to the disk, depending on data direction. |  | ||||||
|  |  | ||||||
| 	It is implied that CA2 might be used to set processor overflow, CA1 a strobe for data input, and one of the CBs being definitive on |  | ||||||
| 	whether the disk head is being told to read or write, but it's unclear and I've yet to investigate. So, TODO. |  | ||||||
| */ |  | ||||||
| class DriveVIA: public MOS::MOS6522<DriveVIA>, public MOS::MOS6522IRQDelegate { |  | ||||||
| 	public: |  | ||||||
| 		class Delegate { |  | ||||||
| 			public: |  | ||||||
| 				virtual void drive_via_did_step_head(void *driveVIA, int direction) = 0; |  | ||||||
| 				virtual void drive_via_did_set_data_density(void *driveVIA, int density) = 0; |  | ||||||
| 		}; |  | ||||||
| 		void set_delegate(Delegate *); |  | ||||||
|  |  | ||||||
| 		using MOS6522IRQDelegate::set_interrupt_status; |  | ||||||
|  |  | ||||||
| 		DriveVIA(); |  | ||||||
|  |  | ||||||
| 		uint8_t get_port_input(Port port); |  | ||||||
|  |  | ||||||
| 		void set_sync_detected(bool); |  | ||||||
| 		void set_data_input(uint8_t); |  | ||||||
| 		bool get_should_set_overflow(); |  | ||||||
| 		bool get_motor_enabled(); |  | ||||||
|  |  | ||||||
| 		void set_control_line_output(Port, Line, bool value); |  | ||||||
|  |  | ||||||
| 		void set_port_output(Port, uint8_t value, uint8_t direction_mask); |  | ||||||
|  |  | ||||||
| 	private: |  | ||||||
| 		uint8_t port_b_, port_a_; |  | ||||||
| 		bool should_set_overflow_; |  | ||||||
| 		bool drive_motor_; |  | ||||||
| 		uint8_t previous_port_b_output_; |  | ||||||
| 		Delegate *delegate_; |  | ||||||
| }; |  | ||||||
|  |  | ||||||
| /*! |  | ||||||
| 	An implementation of the C1540's serial port; this connects incoming line levels to the serial-port VIA. |  | ||||||
| */ |  | ||||||
| class SerialPort : public ::Commodore::Serial::Port { |  | ||||||
| 	public: |  | ||||||
| 		void set_input(::Commodore::Serial::Line, ::Commodore::Serial::LineLevel); |  | ||||||
| 		void set_serial_port_via(const std::shared_ptr<SerialPortVIA> &); |  | ||||||
|  |  | ||||||
| 	private: |  | ||||||
| 		std::weak_ptr<SerialPortVIA> serial_port_VIA_; |  | ||||||
| }; |  | ||||||
|  |  | ||||||
| /*! | /*! | ||||||
| 	Provides an emulation of the C1540. | 	Provides an emulation of the C1540. | ||||||
| */ | */ | ||||||
| class Machine: | class Machine: public MachineBase, public ROMMachine::Machine { | ||||||
| 	public CPU::MOS6502::Processor<Machine>, |  | ||||||
| 	public MOS::MOS6522IRQDelegate::Delegate, |  | ||||||
| 	public DriveVIA::Delegate, |  | ||||||
| 	public Storage::Disk::Controller { |  | ||||||
|  |  | ||||||
| 	public: | 	public: | ||||||
| 		Machine(); | 		enum Personality { | ||||||
|  | 			C1540, | ||||||
|  | 			C1541 | ||||||
|  | 		}; | ||||||
|  | 		Machine(Personality p); | ||||||
|  |  | ||||||
| 		/*! | 		/*! | ||||||
| 			Sets the ROM image to use for this drive; it is assumed that the buffer provided will be at least 16 kb in size. | 			Sets the source for this drive's ROM image. | ||||||
| 		*/ | 		*/ | ||||||
| 		void set_rom(const std::vector<uint8_t> &rom); | 		bool set_rom_fetcher(const std::function<std::vector<std::unique_ptr<std::vector<uint8_t>>>(const std::string &machine, const std::vector<std::string> &names)> &roms_with_names); | ||||||
|  |  | ||||||
| 		/*! | 		/*! | ||||||
| 			Sets the serial bus to which this drive should attach itself. | 			Sets the serial bus to which this drive should attach itself. | ||||||
| 		*/ | 		*/ | ||||||
| 		void set_serial_bus(std::shared_ptr<::Commodore::Serial::Bus> serial_bus); | 		void set_serial_bus(std::shared_ptr<::Commodore::Serial::Bus> serial_bus); | ||||||
|  |  | ||||||
|  | 		/// Advances time. | ||||||
| 		void run_for(const Cycles cycles); | 		void run_for(const Cycles cycles); | ||||||
|  |  | ||||||
|  | 		/// Inserts @c disk into the drive. | ||||||
| 		void set_disk(std::shared_ptr<Storage::Disk::Disk> disk); | 		void set_disk(std::shared_ptr<Storage::Disk::Disk> disk); | ||||||
|  |  | ||||||
| 		// to satisfy CPU::MOS6502::Processor |  | ||||||
| 		Cycles perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value); |  | ||||||
|  |  | ||||||
| 		// to satisfy MOS::MOS6522::Delegate |  | ||||||
| 		virtual void mos6522_did_change_interrupt_status(void *mos6522); |  | ||||||
|  |  | ||||||
| 		// to satisfy DriveVIA::Delegate |  | ||||||
| 		void drive_via_did_step_head(void *driveVIA, int direction); |  | ||||||
| 		void drive_via_did_set_data_density(void *driveVIA, int density); |  | ||||||
|  |  | ||||||
| 	private: | 	private: | ||||||
| 		uint8_t ram_[0x800]; | 		Personality personality_; | ||||||
| 		uint8_t rom_[0x4000]; |  | ||||||
|  |  | ||||||
| 		std::shared_ptr<SerialPortVIA> serial_port_VIA_; |  | ||||||
| 		std::shared_ptr<SerialPort> serial_port_; |  | ||||||
| 		DriveVIA drive_VIA_; |  | ||||||
|  |  | ||||||
| 		int shift_register_, bit_window_offset_; |  | ||||||
| 		virtual void process_input_bit(int value, unsigned int cycles_since_index_hole); |  | ||||||
| 		virtual void process_index_hole(); |  | ||||||
| }; | }; | ||||||
|  |  | ||||||
| } | } | ||||||
|   | |||||||
| @@ -6,35 +6,47 @@ | |||||||
| //  Copyright © 2016 Thomas Harte. All rights reserved.
 | //  Copyright © 2016 Thomas Harte. All rights reserved.
 | ||||||
| //
 | //
 | ||||||
| 
 | 
 | ||||||
| #include "C1540.hpp" | #include "../C1540.hpp" | ||||||
|  | 
 | ||||||
|  | #include <cassert> | ||||||
|  | #include <cstring> | ||||||
| #include <string> | #include <string> | ||||||
| #include "../../../Storage/Disk/Encodings/CommodoreGCR.hpp" | 
 | ||||||
|  | #include "../../../../Storage/Disk/Encodings/CommodoreGCR.hpp" | ||||||
| 
 | 
 | ||||||
| using namespace Commodore::C1540; | using namespace Commodore::C1540; | ||||||
| 
 | 
 | ||||||
| Machine::Machine() : | MachineBase::MachineBase() : | ||||||
| 		shift_register_(0), | 		Storage::Disk::Controller(1000000), | ||||||
| 		Storage::Disk::Controller(1000000, 4, 300), | 		m6502_(*this), | ||||||
|  | 		drive_(new Storage::Disk::Drive(1000000, 300, 2)), | ||||||
|  | 		serial_port_VIA_port_handler_(new SerialPortVIA(serial_port_VIA_)), | ||||||
| 		serial_port_(new SerialPort), | 		serial_port_(new SerialPort), | ||||||
| 		serial_port_VIA_(new SerialPortVIA) { | 		drive_VIA_(drive_VIA_port_handler_), | ||||||
|  | 		serial_port_VIA_(*serial_port_VIA_port_handler_) { | ||||||
| 	// attach the serial port to its VIA and vice versa
 | 	// attach the serial port to its VIA and vice versa
 | ||||||
| 	serial_port_->set_serial_port_via(serial_port_VIA_); | 	serial_port_->set_serial_port_via(serial_port_VIA_port_handler_); | ||||||
| 	serial_port_VIA_->set_serial_port(serial_port_); | 	serial_port_VIA_port_handler_->set_serial_port(serial_port_); | ||||||
| 
 | 
 | ||||||
| 	// set this instance as the delegate to receive interrupt requests from both VIAs
 | 	// set this instance as the delegate to receive interrupt requests from both VIAs
 | ||||||
| 	serial_port_VIA_->set_interrupt_delegate(this); | 	serial_port_VIA_port_handler_->set_interrupt_delegate(this); | ||||||
| 	drive_VIA_.set_interrupt_delegate(this); | 	drive_VIA_port_handler_.set_interrupt_delegate(this); | ||||||
| 	drive_VIA_.set_delegate(this); | 	drive_VIA_port_handler_.set_delegate(this); | ||||||
| 
 | 
 | ||||||
| 	// set a bit rate
 | 	// set a bit rate
 | ||||||
| 	set_expected_bit_length(Storage::Encodings::CommodoreGCR::length_of_a_bit_in_time_zone(3)); | 	set_expected_bit_length(Storage::Encodings::CommodoreGCR::length_of_a_bit_in_time_zone(3)); | ||||||
|  | 
 | ||||||
|  | 	// attach the only drive there is
 | ||||||
|  | 	set_drive(drive_); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | Machine::Machine(Commodore::C1540::Machine::Personality personality) : personality_(personality) {} | ||||||
|  | 
 | ||||||
| void Machine::set_serial_bus(std::shared_ptr<::Commodore::Serial::Bus> serial_bus) { | void Machine::set_serial_bus(std::shared_ptr<::Commodore::Serial::Bus> serial_bus) { | ||||||
| 	Commodore::Serial::AttachPortAndBus(serial_port_, serial_bus); | 	Commodore::Serial::AttachPortAndBus(serial_port_, serial_bus); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| Cycles Machine::perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value) { | Cycles MachineBase::perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value) { | ||||||
| 	/*
 | 	/*
 | ||||||
| 		Memory map (given that I'm unsure yet on any potential mirroring): | 		Memory map (given that I'm unsure yet on any potential mirroring): | ||||||
| 
 | 
 | ||||||
| @@ -49,13 +61,14 @@ Cycles Machine::perform_bus_operation(CPU::MOS6502::BusOperation operation, uint | |||||||
| 		else | 		else | ||||||
| 			ram_[address] = *value; | 			ram_[address] = *value; | ||||||
| 	} else if(address >= 0xc000) { | 	} else if(address >= 0xc000) { | ||||||
| 		if(isReadOperation(operation)) | 		if(isReadOperation(operation)) { | ||||||
| 			*value = rom_[address & 0x3fff]; | 			*value = rom_[address & 0x3fff]; | ||||||
|  | 		} | ||||||
| 	} else if(address >= 0x1800 && address <= 0x180f) { | 	} else if(address >= 0x1800 && address <= 0x180f) { | ||||||
| 		if(isReadOperation(operation)) | 		if(isReadOperation(operation)) | ||||||
| 			*value = serial_port_VIA_->get_register(address); | 			*value = serial_port_VIA_.get_register(address); | ||||||
| 		else | 		else | ||||||
| 			serial_port_VIA_->set_register(address, *value); | 			serial_port_VIA_.set_register(address, *value); | ||||||
| 	} else if(address >= 0x1c00 && address <= 0x1c0f) { | 	} else if(address >= 0x1c00 && address <= 0x1c0f) { | ||||||
| 		if(isReadOperation(operation)) | 		if(isReadOperation(operation)) | ||||||
| 			*value = drive_VIA_.get_register(address); | 			*value = drive_VIA_.get_register(address); | ||||||
| @@ -63,81 +76,89 @@ Cycles Machine::perform_bus_operation(CPU::MOS6502::BusOperation operation, uint | |||||||
| 			drive_VIA_.set_register(address, *value); | 			drive_VIA_.set_register(address, *value); | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	serial_port_VIA_->run_for(Cycles(1)); | 	serial_port_VIA_.run_for(Cycles(1)); | ||||||
| 	drive_VIA_.run_for(Cycles(1)); | 	drive_VIA_.run_for(Cycles(1)); | ||||||
| 
 | 
 | ||||||
| 	return Cycles(1); | 	return Cycles(1); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void Machine::set_rom(const std::vector<uint8_t> &rom) { | bool Machine::set_rom_fetcher(const std::function<std::vector<std::unique_ptr<std::vector<uint8_t>>>(const std::string &machine, const std::vector<std::string> &names)> &roms_with_names) { | ||||||
| 	memcpy(rom_, rom.data(), std::min(sizeof(rom_), rom.size())); | 	std::string rom_name; | ||||||
|  | 	switch(personality_) { | ||||||
|  | 		case Personality::C1540:	rom_name = "1540.bin";	break; | ||||||
|  | 		case Personality::C1541:	rom_name = "1541.bin";	break; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	auto roms = roms_with_names("Commodore1540", {rom_name}); | ||||||
|  | 	if(!roms[0]) return false; | ||||||
|  | 	std::memcpy(rom_, roms[0]->data(), std::min(sizeof(rom_), roms[0]->size())); | ||||||
|  | 	return true; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void Machine::set_disk(std::shared_ptr<Storage::Disk::Disk> disk) { | void Machine::set_disk(std::shared_ptr<Storage::Disk::Disk> disk) { | ||||||
| 	std::shared_ptr<Storage::Disk::Drive> drive(new Storage::Disk::Drive); | 	drive_->set_disk(disk); | ||||||
| 	drive->set_disk(disk); |  | ||||||
| 	set_drive(drive); |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void Machine::run_for(const Cycles cycles) { | void Machine::run_for(const Cycles cycles) { | ||||||
| 	CPU::MOS6502::Processor<Machine>::run_for(cycles); | 	m6502_.run_for(cycles); | ||||||
| 	set_motor_on(drive_VIA_.get_motor_enabled()); | 
 | ||||||
| 	if(drive_VIA_.get_motor_enabled()) // TODO: motor speed up/down
 | 	bool drive_motor = drive_VIA_port_handler_.get_motor_enabled(); | ||||||
|  | 	drive_->set_motor_on(drive_motor); | ||||||
|  | 	if(drive_motor) | ||||||
| 		Storage::Disk::Controller::run_for(cycles); | 		Storage::Disk::Controller::run_for(cycles); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| #pragma mark - 6522 delegate | // MARK: - 6522 delegate
 | ||||||
| 
 | 
 | ||||||
| void Machine::mos6522_did_change_interrupt_status(void *mos6522) { | void MachineBase::mos6522_did_change_interrupt_status(void *mos6522) { | ||||||
| 	// both VIAs are connected to the IRQ line
 | 	// both VIAs are connected to the IRQ line
 | ||||||
| 	set_irq_line(serial_port_VIA_->get_interrupt_line() || drive_VIA_.get_interrupt_line()); | 	m6502_.set_irq_line(serial_port_VIA_.get_interrupt_line() || drive_VIA_.get_interrupt_line()); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| #pragma mark - Disk drive | // MARK: - Disk drive
 | ||||||
| 
 | 
 | ||||||
| void Machine::process_input_bit(int value, unsigned int cycles_since_index_hole) { | void MachineBase::process_input_bit(int value) { | ||||||
| 	shift_register_ = (shift_register_ << 1) | value; | 	shift_register_ = (shift_register_ << 1) | value; | ||||||
| 	if((shift_register_ & 0x3ff) == 0x3ff) { | 	if((shift_register_ & 0x3ff) == 0x3ff) { | ||||||
| 		drive_VIA_.set_sync_detected(true); | 		drive_VIA_port_handler_.set_sync_detected(true); | ||||||
| 		bit_window_offset_ = -1; // i.e. this bit isn't the first within a data window, but the next might be
 | 		bit_window_offset_ = -1; // i.e. this bit isn't the first within a data window, but the next might be
 | ||||||
| 	} else { | 	} else { | ||||||
| 		drive_VIA_.set_sync_detected(false); | 		drive_VIA_port_handler_.set_sync_detected(false); | ||||||
| 	} | 	} | ||||||
| 	bit_window_offset_++; | 	bit_window_offset_++; | ||||||
| 	if(bit_window_offset_ == 8) { | 	if(bit_window_offset_ == 8) { | ||||||
| 		drive_VIA_.set_data_input((uint8_t)shift_register_); | 		drive_VIA_port_handler_.set_data_input(static_cast<uint8_t>(shift_register_)); | ||||||
| 		bit_window_offset_ = 0; | 		bit_window_offset_ = 0; | ||||||
| 		if(drive_VIA_.get_should_set_overflow()) { | 		if(drive_VIA_port_handler_.get_should_set_overflow()) { | ||||||
| 			set_overflow_line(true); | 			m6502_.set_overflow_line(true); | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 	else set_overflow_line(false); | 	else m6502_.set_overflow_line(false); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // the 1540 does not recognise index holes
 | // the 1540 does not recognise index holes
 | ||||||
| void Machine::process_index_hole()	{} | void MachineBase::process_index_hole()	{} | ||||||
| 
 | 
 | ||||||
| #pragma mak - Drive VIA delegate | // MARK: - Drive VIA delegate
 | ||||||
| 
 | 
 | ||||||
| void Machine::drive_via_did_step_head(void *driveVIA, int direction) { | void MachineBase::drive_via_did_step_head(void *driveVIA, int direction) { | ||||||
| 	step(direction); | 	drive_->step(direction); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void Machine::drive_via_did_set_data_density(void *driveVIA, int density) { | void MachineBase::drive_via_did_set_data_density(void *driveVIA, int density) { | ||||||
| 	set_expected_bit_length(Storage::Encodings::CommodoreGCR::length_of_a_bit_in_time_zone((unsigned int)density)); | 	set_expected_bit_length(Storage::Encodings::CommodoreGCR::length_of_a_bit_in_time_zone(static_cast<unsigned int>(density))); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| #pragma mark - SerialPortVIA | // MARK: - SerialPortVIA
 | ||||||
| 
 | 
 | ||||||
| SerialPortVIA::SerialPortVIA() : | SerialPortVIA::SerialPortVIA(MOS::MOS6522::MOS6522<SerialPortVIA> &via) : via_(via) {} | ||||||
| 	port_b_(0x00), attention_acknowledge_level_(false), attention_level_input_(true), data_level_output_(false) {} |  | ||||||
| 
 | 
 | ||||||
| uint8_t SerialPortVIA::get_port_input(Port port) { | uint8_t SerialPortVIA::get_port_input(MOS::MOS6522::Port port) { | ||||||
| 	if(port) return port_b_; | 	if(port) return port_b_; | ||||||
| 	return 0xff; | 	return 0xff; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void SerialPortVIA::set_port_output(Port port, uint8_t value, uint8_t mask) { | void SerialPortVIA::set_port_output(MOS::MOS6522::Port port, uint8_t value, uint8_t mask) { | ||||||
| 	if(port) { | 	if(port) { | ||||||
| 		std::shared_ptr<::Commodore::Serial::Port> serialPort = serial_port_.lock(); | 		std::shared_ptr<::Commodore::Serial::Port> serialPort = serial_port_.lock(); | ||||||
| 		if(serialPort) { | 		if(serialPort) { | ||||||
| @@ -151,6 +172,8 @@ void SerialPortVIA::set_port_output(Port port, uint8_t value, uint8_t mask) { | |||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void SerialPortVIA::set_serial_line_state(::Commodore::Serial::Line line, bool value) { | void SerialPortVIA::set_serial_line_state(::Commodore::Serial::Line line, bool value) { | ||||||
|  | //	printf("[C1540] %s is %s\n", StringForLine(line), value ? "high" : "low");
 | ||||||
|  | 
 | ||||||
| 	switch(line) { | 	switch(line) { | ||||||
| 		default: break; | 		default: break; | ||||||
| 		case ::Commodore::Serial::Line::Data:		port_b_ = (port_b_ & ~0x01) | (value ? 0x00 : 0x01);		break; | 		case ::Commodore::Serial::Line::Data:		port_b_ = (port_b_ & ~0x01) | (value ? 0x00 : 0x01);		break; | ||||||
| @@ -158,7 +181,7 @@ void SerialPortVIA::set_serial_line_state(::Commodore::Serial::Line line, bool v | |||||||
| 		case ::Commodore::Serial::Line::Attention: | 		case ::Commodore::Serial::Line::Attention: | ||||||
| 			attention_level_input_ = !value; | 			attention_level_input_ = !value; | ||||||
| 			port_b_ = (port_b_ & ~0x80) | (value ? 0x00 : 0x80); | 			port_b_ = (port_b_ & ~0x80) | (value ? 0x00 : 0x80); | ||||||
| 			set_control_line_input(Port::A, Line::One, !value); | 			via_.set_control_line_input(MOS::MOS6522::Port::A, MOS::MOS6522::Line::One, !value); | ||||||
| 			update_data_line(); | 			update_data_line(); | ||||||
| 		break; | 		break; | ||||||
| 	} | 	} | ||||||
| @@ -177,7 +200,7 @@ void SerialPortVIA::update_data_line() { | |||||||
| 	} | 	} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| #pragma mark - DriveVIA | // MARK: - DriveVIA
 | ||||||
| 
 | 
 | ||||||
| void DriveVIA::set_delegate(Delegate *delegate) { | void DriveVIA::set_delegate(Delegate *delegate) { | ||||||
| 	delegate_ = delegate; | 	delegate_ = delegate; | ||||||
| @@ -186,7 +209,7 @@ void DriveVIA::set_delegate(Delegate *delegate) { | |||||||
| // write protect tab uncovered
 | // write protect tab uncovered
 | ||||||
| DriveVIA::DriveVIA() : port_b_(0xff), port_a_(0xff), delegate_(nullptr) {} | DriveVIA::DriveVIA() : port_b_(0xff), port_a_(0xff), delegate_(nullptr) {} | ||||||
| 
 | 
 | ||||||
| uint8_t DriveVIA::get_port_input(Port port) { | uint8_t DriveVIA::get_port_input(MOS::MOS6522::Port port) { | ||||||
| 	return port ? port_b_ : port_a_; | 	return port ? port_b_ : port_a_; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| @@ -206,14 +229,15 @@ bool DriveVIA::get_motor_enabled() { | |||||||
| 	return drive_motor_; | 	return drive_motor_; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void DriveVIA::set_control_line_output(Port port, Line line, bool value) { | void DriveVIA::set_control_line_output(MOS::MOS6522::Port port, MOS::MOS6522::Line line, bool value) { | ||||||
| 	if(port == Port::A && line == Line::Two) { | 	if(port == MOS::MOS6522::Port::A && line == MOS::MOS6522::Line::Two) { | ||||||
| 		should_set_overflow_ = value; | 		should_set_overflow_ = value; | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void DriveVIA::set_port_output(Port port, uint8_t value, uint8_t direction_mask) { | void DriveVIA::set_port_output(MOS::MOS6522::Port port, uint8_t value, uint8_t direction_mask) { | ||||||
| 	if(port) { | 	if(port) { | ||||||
|  | 		if(previous_port_b_output_ != value) { | ||||||
| 			// record drive motor state
 | 			// record drive motor state
 | ||||||
| 			drive_motor_ = !!(value&4); | 			drive_motor_ = !!(value&4); | ||||||
| 
 | 
 | ||||||
| @@ -234,9 +258,10 @@ void DriveVIA::set_port_output(Port port, uint8_t value, uint8_t direction_mask) | |||||||
| 
 | 
 | ||||||
| 			previous_port_b_output_ = value; | 			previous_port_b_output_ = value; | ||||||
| 		} | 		} | ||||||
|  | 	} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| #pragma mark - SerialPort | // MARK: - SerialPort
 | ||||||
| 
 | 
 | ||||||
| void SerialPort::set_input(::Commodore::Serial::Line line, ::Commodore::Serial::LineLevel level) { | void SerialPort::set_input(::Commodore::Serial::Line line, ::Commodore::Serial::LineLevel level) { | ||||||
| 	std::shared_ptr<SerialPortVIA> serialPortVIA = serial_port_VIA_.lock(); | 	std::shared_ptr<SerialPortVIA> serialPortVIA = serial_port_VIA_.lock(); | ||||||
							
								
								
									
										160
									
								
								Machines/Commodore/1540/Implementation/C1540Base.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										160
									
								
								Machines/Commodore/1540/Implementation/C1540Base.hpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,160 @@ | |||||||
|  | // | ||||||
|  | //  C1540Base.hpp | ||||||
|  | //  Clock Signal | ||||||
|  | // | ||||||
|  | //  Created by Thomas Harte on 04/09/2017. | ||||||
|  | //  Copyright © 2017 Thomas Harte. All rights reserved. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | #ifndef C1540Base_hpp | ||||||
|  | #define C1540Base_hpp | ||||||
|  |  | ||||||
|  | #include "../../../../Processors/6502/6502.hpp" | ||||||
|  | #include "../../../../Components/6522/6522.hpp" | ||||||
|  |  | ||||||
|  | #include "../../SerialBus.hpp" | ||||||
|  |  | ||||||
|  | #include "../../../../Storage/Disk/Disk.hpp" | ||||||
|  |  | ||||||
|  | #include "../../../../Storage/Disk/Controller/DiskController.hpp" | ||||||
|  |  | ||||||
|  | namespace Commodore { | ||||||
|  | namespace C1540 { | ||||||
|  |  | ||||||
|  | /*! | ||||||
|  | 	An implementation of the serial-port VIA in a Commodore 1540 — the VIA that facilitates all | ||||||
|  | 	IEC bus communications. | ||||||
|  |  | ||||||
|  | 	It is wired up such that Port B contains: | ||||||
|  | 		Bit 0:		data input; 1 if the line is low, 0 if it is high; | ||||||
|  | 		Bit 1:		data output; 1 if the line should be low, 0 if it should be high; | ||||||
|  | 		Bit 2:		clock input; 1 if the line is low, 0 if it is high; | ||||||
|  | 		Bit 3:		clock output; 1 if the line is low, 0 if it is high; | ||||||
|  | 		Bit 4:		attention acknowledge output; exclusive ORd with the attention input and ORd onto the data output; | ||||||
|  | 		Bits 5/6:	device select input; the 1540 will act as device 8 + [value of bits] | ||||||
|  | 		Bit 7:		attention input; 1 if the line is low, 0 if it is high | ||||||
|  |  | ||||||
|  | 	The attention input is also connected to CA1, similarly inverted — the CA1 wire will be high when the bus is low and vice versa. | ||||||
|  | */ | ||||||
|  | class SerialPortVIA: public MOS::MOS6522::IRQDelegatePortHandler { | ||||||
|  | 	public: | ||||||
|  | 		SerialPortVIA(MOS::MOS6522::MOS6522<SerialPortVIA> &via); | ||||||
|  |  | ||||||
|  | 		uint8_t get_port_input(MOS::MOS6522::Port); | ||||||
|  |  | ||||||
|  | 		void set_port_output(MOS::MOS6522::Port, uint8_t value, uint8_t mask); | ||||||
|  | 		void set_serial_line_state(::Commodore::Serial::Line, bool); | ||||||
|  |  | ||||||
|  | 		void set_serial_port(const std::shared_ptr<::Commodore::Serial::Port> &); | ||||||
|  |  | ||||||
|  | 	private: | ||||||
|  | 		MOS::MOS6522::MOS6522<SerialPortVIA> &via_; | ||||||
|  | 		uint8_t port_b_ = 0x0; | ||||||
|  | 		std::weak_ptr<::Commodore::Serial::Port> serial_port_; | ||||||
|  | 		bool attention_acknowledge_level_ = false; | ||||||
|  | 		bool attention_level_input_ = true; | ||||||
|  | 		bool data_level_output_ = false; | ||||||
|  |  | ||||||
|  | 		void update_data_line(); | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | /*! | ||||||
|  | 	An implementation of the drive VIA in a Commodore 1540 — the VIA that is used to interface with the disk. | ||||||
|  |  | ||||||
|  | 	It is wired up such that Port B contains: | ||||||
|  | 		Bits 0/1:	head step direction | ||||||
|  | 		Bit 2:		motor control | ||||||
|  | 		Bit 3:		LED control (TODO) | ||||||
|  | 		Bit 4:		write protect photocell status (TODO) | ||||||
|  | 		Bits 5/6:	read/write density | ||||||
|  | 		Bit 7:		0 if sync marks are currently being detected, 1 otherwise. | ||||||
|  |  | ||||||
|  | 	... and Port A contains the byte most recently read from the disk or the byte next to write to the disk, depending on data direction. | ||||||
|  |  | ||||||
|  | 	It is implied that CA2 might be used to set processor overflow, CA1 a strobe for data input, and one of the CBs being definitive on | ||||||
|  | 	whether the disk head is being told to read or write, but it's unclear and I've yet to investigate. So, TODO. | ||||||
|  | */ | ||||||
|  | class DriveVIA: public MOS::MOS6522::IRQDelegatePortHandler { | ||||||
|  | 	public: | ||||||
|  | 		class Delegate { | ||||||
|  | 			public: | ||||||
|  | 				virtual void drive_via_did_step_head(void *driveVIA, int direction) = 0; | ||||||
|  | 				virtual void drive_via_did_set_data_density(void *driveVIA, int density) = 0; | ||||||
|  | 		}; | ||||||
|  | 		void set_delegate(Delegate *); | ||||||
|  |  | ||||||
|  | 		DriveVIA(); | ||||||
|  |  | ||||||
|  | 		uint8_t get_port_input(MOS::MOS6522::Port port); | ||||||
|  |  | ||||||
|  | 		void set_sync_detected(bool); | ||||||
|  | 		void set_data_input(uint8_t); | ||||||
|  | 		bool get_should_set_overflow(); | ||||||
|  | 		bool get_motor_enabled(); | ||||||
|  |  | ||||||
|  | 		void set_control_line_output(MOS::MOS6522::Port, MOS::MOS6522::Line, bool value); | ||||||
|  |  | ||||||
|  | 		void set_port_output(MOS::MOS6522::Port, uint8_t value, uint8_t direction_mask); | ||||||
|  |  | ||||||
|  | 	private: | ||||||
|  | 		uint8_t port_b_, port_a_; | ||||||
|  | 		bool should_set_overflow_; | ||||||
|  | 		bool drive_motor_; | ||||||
|  | 		uint8_t previous_port_b_output_; | ||||||
|  | 		Delegate *delegate_; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | /*! | ||||||
|  | 	An implementation of the C1540's serial port; this connects incoming line levels to the serial-port VIA. | ||||||
|  | */ | ||||||
|  | class SerialPort : public ::Commodore::Serial::Port { | ||||||
|  | 	public: | ||||||
|  | 		void set_input(::Commodore::Serial::Line, ::Commodore::Serial::LineLevel); | ||||||
|  | 		void set_serial_port_via(const std::shared_ptr<SerialPortVIA> &); | ||||||
|  |  | ||||||
|  | 	private: | ||||||
|  | 		std::weak_ptr<SerialPortVIA> serial_port_VIA_; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | class MachineBase: | ||||||
|  | 	public CPU::MOS6502::BusHandler, | ||||||
|  | 	public MOS::MOS6522::IRQDelegatePortHandler::Delegate, | ||||||
|  | 	public DriveVIA::Delegate, | ||||||
|  | 	public Storage::Disk::Controller { | ||||||
|  |  | ||||||
|  | 	public: | ||||||
|  | 		MachineBase(); | ||||||
|  |  | ||||||
|  | 		// to satisfy CPU::MOS6502::Processor | ||||||
|  | 		Cycles perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value); | ||||||
|  |  | ||||||
|  | 		// to satisfy MOS::MOS6522::Delegate | ||||||
|  | 		virtual void mos6522_did_change_interrupt_status(void *mos6522); | ||||||
|  |  | ||||||
|  | 		// to satisfy DriveVIA::Delegate | ||||||
|  | 		void drive_via_did_step_head(void *driveVIA, int direction); | ||||||
|  | 		void drive_via_did_set_data_density(void *driveVIA, int density); | ||||||
|  |  | ||||||
|  | 	protected: | ||||||
|  | 		CPU::MOS6502::Processor<MachineBase, false> m6502_; | ||||||
|  | 		std::shared_ptr<Storage::Disk::Drive> drive_; | ||||||
|  |  | ||||||
|  | 		uint8_t ram_[0x800]; | ||||||
|  | 		uint8_t rom_[0x4000]; | ||||||
|  |  | ||||||
|  | 		std::shared_ptr<SerialPortVIA> serial_port_VIA_port_handler_; | ||||||
|  | 		std::shared_ptr<SerialPort> serial_port_; | ||||||
|  | 		DriveVIA drive_VIA_port_handler_; | ||||||
|  |  | ||||||
|  | 		MOS::MOS6522::MOS6522<DriveVIA> drive_VIA_; | ||||||
|  | 		MOS::MOS6522::MOS6522<SerialPortVIA> serial_port_VIA_; | ||||||
|  |  | ||||||
|  | 		int shift_register_ = 0, bit_window_offset_; | ||||||
|  | 		virtual void process_input_bit(int value); | ||||||
|  | 		virtual void process_index_hole(); | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #endif /* C1540Base_hpp */ | ||||||
| @@ -8,6 +8,8 @@ | |||||||
|  |  | ||||||
| #include "SerialBus.hpp" | #include "SerialBus.hpp" | ||||||
|  |  | ||||||
|  | #include <cstdio> | ||||||
|  |  | ||||||
| using namespace Commodore::Serial; | using namespace Commodore::Serial; | ||||||
|  |  | ||||||
| const char *::Commodore::Serial::StringForLine(Line line) { | const char *::Commodore::Serial::StringForLine(Line line) { | ||||||
| @@ -17,6 +19,7 @@ const char *::Commodore::Serial::StringForLine(Line line) { | |||||||
| 		case Clock: return "Clock"; | 		case Clock: return "Clock"; | ||||||
| 		case Data: return "Data"; | 		case Data: return "Data"; | ||||||
| 		case Reset: return "Reset"; | 		case Reset: return "Reset"; | ||||||
|  | 		default: return nullptr; | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -27,12 +30,12 @@ void ::Commodore::Serial::AttachPortAndBus(std::shared_ptr<Port> port, std::shar | |||||||
|  |  | ||||||
| void Bus::add_port(std::shared_ptr<Port> port) { | void Bus::add_port(std::shared_ptr<Port> port) { | ||||||
| 	ports_.push_back(port); | 	ports_.push_back(port); | ||||||
| 	for(int line = (int)ServiceRequest; line <= (int)Reset; line++) { | 	for(int line = static_cast<int>(ServiceRequest); line <= static_cast<int>(Reset); line++) { | ||||||
| 		// the addition of a new device may change the line output... | 		// the addition of a new device may change the line output... | ||||||
| 		set_line_output_did_change((Line)line); | 		set_line_output_did_change(static_cast<Line>(line)); | ||||||
|  |  | ||||||
| 		// ... but the new device will need to be told the current state regardless | 		// ... but the new device will need to be told the current state regardless | ||||||
| 		port->set_input((Line)line, line_levels_[line]); | 		port->set_input(static_cast<Line>(line), line_levels_[line]); | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -46,6 +49,8 @@ void Bus::set_line_output_did_change(Line line) { | |||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | //	printf("[Bus] %s is %s\n", StringForLine(line), new_line_level ? "high" : "low"); | ||||||
|  |  | ||||||
| 	// post an update only if one occurred | 	// post an update only if one occurred | ||||||
| 	if(new_line_level != line_levels_[line]) { | 	if(new_line_level != line_levels_[line]) { | ||||||
| 		line_levels_[line] = new_line_level; | 		line_levels_[line] = new_line_level; | ||||||
| @@ -59,7 +64,7 @@ void Bus::set_line_output_did_change(Line line) { | |||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| #pragma mark - The debug port | // MARK: - The debug port | ||||||
|  |  | ||||||
| void DebugPort::set_input(Line line, LineLevel value) { | void DebugPort::set_input(Line line, LineLevel value) { | ||||||
| 	input_levels_[line] = value; | 	input_levels_[line] = value; | ||||||
|   | |||||||
| @@ -9,6 +9,8 @@ | |||||||
| #ifndef SerialBus_hpp | #ifndef SerialBus_hpp | ||||||
| #define SerialBus_hpp | #define SerialBus_hpp | ||||||
|  |  | ||||||
|  | #include <cstdint> | ||||||
|  | #include <memory> | ||||||
| #include <vector> | #include <vector> | ||||||
|  |  | ||||||
| namespace Commodore { | namespace Commodore { | ||||||
|   | |||||||
| @@ -1,25 +0,0 @@ | |||||||
| // |  | ||||||
| //  CharacterMapper.hpp |  | ||||||
| //  Clock Signal |  | ||||||
| // |  | ||||||
| //  Created by Thomas Harte on 03/08/2017. |  | ||||||
| //  Copyright © 2017 Thomas Harte. All rights reserved. |  | ||||||
| // |  | ||||||
|  |  | ||||||
| #ifndef Machines_Commodore_Vic20_CharacterMapper_hpp |  | ||||||
| #define Machines_Commodore_Vic20_CharacterMapper_hpp |  | ||||||
|  |  | ||||||
| #include "../../Typer.hpp" |  | ||||||
|  |  | ||||||
| namespace Commodore { |  | ||||||
| namespace Vic20 { |  | ||||||
|  |  | ||||||
| class CharacterMapper: public ::Utility::CharacterMapper { |  | ||||||
| 	public: |  | ||||||
| 		uint16_t *sequence_for_character(char character); |  | ||||||
| }; |  | ||||||
|  |  | ||||||
| } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| #endif /* CharacterMapper_hpp */ |  | ||||||
| @@ -1,20 +1,80 @@ | |||||||
| //
 | //
 | ||||||
| //  CharacterMapper.cpp
 | //  Keyboard.cpp
 | ||||||
| //  Clock Signal
 | //  Clock Signal
 | ||||||
| //
 | //
 | ||||||
| //  Created by Thomas Harte on 03/08/2017.
 | //  Created by Thomas Harte on 10/10/2017.
 | ||||||
| //  Copyright © 2017 Thomas Harte. All rights reserved.
 | //  Copyright © 2017 Thomas Harte. All rights reserved.
 | ||||||
| //
 | //
 | ||||||
| 
 | 
 | ||||||
| #include "CharacterMapper.hpp" | #include "Keyboard.hpp" | ||||||
| #include "Vic20.hpp" |  | ||||||
| 
 | 
 | ||||||
| using namespace Commodore::Vic20; | using namespace Commodore::Vic20; | ||||||
| 
 | 
 | ||||||
|  | uint16_t KeyboardMapper::mapped_key_for_key(Inputs::Keyboard::Key key) { | ||||||
|  | #define BIND(source, dest)	case Inputs::Keyboard::Key::source:	return Commodore::Vic20::dest | ||||||
|  | 	switch(key) { | ||||||
|  | 		default: break; | ||||||
|  | 
 | ||||||
|  | 		BIND(k0, Key0);		BIND(k1, Key1);		BIND(k2, Key2);		BIND(k3, Key3);		BIND(k4, Key4); | ||||||
|  | 		BIND(k5, Key5);		BIND(k6, Key6);		BIND(k7, Key7);		BIND(k8, Key8);		BIND(k9, Key9); | ||||||
|  | 		BIND(Q, KeyQ);		BIND(W, KeyW);		BIND(E, KeyE);		BIND(R, KeyR);		BIND(T, KeyT); | ||||||
|  | 		BIND(Y, KeyY);		BIND(U, KeyU);		BIND(I, KeyI);		BIND(O, KeyO);		BIND(P, KeyP); | ||||||
|  | 		BIND(A, KeyA);		BIND(S, KeyS);		BIND(D, KeyD);		BIND(F, KeyF);		BIND(G, KeyG); | ||||||
|  | 		BIND(H, KeyH);		BIND(J, KeyJ);		BIND(K, KeyK);		BIND(L, KeyL); | ||||||
|  | 		BIND(Z, KeyZ);		BIND(X, KeyX);		BIND(C, KeyC);		BIND(V, KeyV); | ||||||
|  | 		BIND(B, KeyB);		BIND(N, KeyN);		BIND(M, KeyM); | ||||||
|  | 
 | ||||||
|  | 		BIND(BackTick, KeyLeft); | ||||||
|  | 		BIND(Hyphen, KeyPlus); | ||||||
|  | 		BIND(Equals, KeyDash); | ||||||
|  | 		BIND(F11, KeyGBP); | ||||||
|  | 		BIND(F12, KeyHome); | ||||||
|  | 
 | ||||||
|  | 		BIND(Tab, KeyControl); | ||||||
|  | 		BIND(OpenSquareBracket, KeyAt); | ||||||
|  | 		BIND(CloseSquareBracket, KeyAsterisk); | ||||||
|  | 
 | ||||||
|  | 		BIND(BackSlash, KeyRestore); | ||||||
|  | 		BIND(Hash, KeyUp); | ||||||
|  | 		BIND(F10, KeyUp); | ||||||
|  | 
 | ||||||
|  | 		BIND(Semicolon, KeyColon); | ||||||
|  | 		BIND(Quote, KeySemicolon); | ||||||
|  | 		BIND(F9, KeyEquals); | ||||||
|  | 
 | ||||||
|  | 		BIND(LeftMeta, KeyCBM); | ||||||
|  | 		BIND(LeftOption, KeyCBM); | ||||||
|  | 		BIND(RightOption, KeyCBM); | ||||||
|  | 		BIND(RightMeta, KeyCBM); | ||||||
|  | 
 | ||||||
|  | 		BIND(LeftShift, KeyLShift); | ||||||
|  | 		BIND(RightShift, KeyRShift); | ||||||
|  | 
 | ||||||
|  | 		BIND(Comma, KeyComma); | ||||||
|  | 		BIND(FullStop, KeyFullStop); | ||||||
|  | 		BIND(ForwardSlash, KeySlash); | ||||||
|  | 
 | ||||||
|  | 		BIND(Right, KeyRight); | ||||||
|  | 		BIND(Down, KeyDown); | ||||||
|  | 
 | ||||||
|  | 		BIND(Enter, KeyReturn); | ||||||
|  | 		BIND(Space, KeySpace); | ||||||
|  | 		BIND(BackSpace, KeyDelete); | ||||||
|  | 
 | ||||||
|  | 		BIND(Escape, KeyRunStop); | ||||||
|  | 		BIND(F1, KeyF1); | ||||||
|  | 		BIND(F3, KeyF3); | ||||||
|  | 		BIND(F5, KeyF5); | ||||||
|  | 		BIND(F7, KeyF7); | ||||||
|  | 	} | ||||||
|  | #undef BIND | ||||||
|  | 	return KeyboardMachine::Machine::KeyNotMapped; | ||||||
|  | } | ||||||
|  | 
 | ||||||
| uint16_t *CharacterMapper::sequence_for_character(char character) { | uint16_t *CharacterMapper::sequence_for_character(char character) { | ||||||
| #define KEYS(...)	{__VA_ARGS__, EndSequence} | #define KEYS(...)	{__VA_ARGS__, KeyboardMachine::Machine::KeyEndSequence} | ||||||
| #define SHIFT(...)	{KeyLShift, __VA_ARGS__, EndSequence} | #define SHIFT(...)	{KeyLShift, __VA_ARGS__, KeyboardMachine::Machine::KeyEndSequence} | ||||||
| #define X			{NotMapped} | #define X			{KeyboardMachine::Machine::KeyNotMapped} | ||||||
| 	static KeySequence key_sequences[] = { | 	static KeySequence key_sequences[] = { | ||||||
| 		/* NUL */	X,							/* SOH */	X, | 		/* NUL */	X,							/* SOH */	X, | ||||||
| 		/* STX */	X,							/* ETX */	X, | 		/* STX */	X,							/* ETX */	X, | ||||||
							
								
								
									
										52
									
								
								Machines/Commodore/Vic-20/Keyboard.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										52
									
								
								Machines/Commodore/Vic-20/Keyboard.hpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,52 @@ | |||||||
|  | // | ||||||
|  | //  Keyboard.hpp | ||||||
|  | //  Clock Signal | ||||||
|  | // | ||||||
|  | //  Created by Thomas Harte on 10/10/2017. | ||||||
|  | //  Copyright © 2017 Thomas Harte. All rights reserved. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | #ifndef Machines_Commodore_Vic20_Keyboard_hpp | ||||||
|  | #define Machines_Commodore_Vic20_Keyboard_hpp | ||||||
|  |  | ||||||
|  | #include "../../KeyboardMachine.hpp" | ||||||
|  | #include "../../Utility/Typer.hpp" | ||||||
|  |  | ||||||
|  | namespace Commodore { | ||||||
|  | namespace Vic20 { | ||||||
|  |  | ||||||
|  | enum Key: uint16_t { | ||||||
|  | #define key(line, mask) (((mask) << 3) | (line)) | ||||||
|  | 	Key2		= key(7, 0x01),		Key4		= key(7, 0x02),		Key6			= key(7, 0x04),		Key8		= key(7, 0x08), | ||||||
|  | 	Key0		= key(7, 0x10),		KeyDash		= key(7, 0x20),		KeyHome			= key(7, 0x40),		KeyF7		= key(7, 0x80), | ||||||
|  | 	KeyQ		= key(6, 0x01),		KeyE		= key(6, 0x02),		KeyT			= key(6, 0x04),		KeyU		= key(6, 0x08), | ||||||
|  | 	KeyO		= key(6, 0x10),		KeyAt		= key(6, 0x20),		KeyUp			= key(6, 0x40),		KeyF5		= key(6, 0x80), | ||||||
|  | 	KeyCBM		= key(5, 0x01),		KeyS		= key(5, 0x02),		KeyF			= key(5, 0x04),		KeyH		= key(5, 0x08), | ||||||
|  | 	KeyK		= key(5, 0x10),		KeyColon	= key(5, 0x20),		KeyEquals		= key(5, 0x40),		KeyF3		= key(5, 0x80), | ||||||
|  | 	KeySpace	= key(4, 0x01),		KeyZ		= key(4, 0x02),		KeyC			= key(4, 0x04),		KeyB		= key(4, 0x08), | ||||||
|  | 	KeyM		= key(4, 0x10),		KeyFullStop	= key(4, 0x20),		KeyRShift		= key(4, 0x40),		KeyF1		= key(4, 0x80), | ||||||
|  | 	KeyRunStop	= key(3, 0x01),		KeyLShift	= key(3, 0x02),		KeyX			= key(3, 0x04),		KeyV		= key(3, 0x08), | ||||||
|  | 	KeyN		= key(3, 0x10),		KeyComma	= key(3, 0x20),		KeySlash		= key(3, 0x40),		KeyDown		= key(3, 0x80), | ||||||
|  | 	KeyControl	= key(2, 0x01),		KeyA		= key(2, 0x02),		KeyD			= key(2, 0x04),		KeyG		= key(2, 0x08), | ||||||
|  | 	KeyJ		= key(2, 0x10),		KeyL		= key(2, 0x20),		KeySemicolon	= key(2, 0x40),		KeyRight	= key(2, 0x80), | ||||||
|  | 	KeyLeft		= key(1, 0x01),		KeyW		= key(1, 0x02),		KeyR			= key(1, 0x04),		KeyY		= key(1, 0x08), | ||||||
|  | 	KeyI		= key(1, 0x10),		KeyP		= key(1, 0x20),		KeyAsterisk		= key(1, 0x40),		KeyReturn	= key(1, 0x80), | ||||||
|  | 	Key1		= key(0, 0x01),		Key3		= key(0, 0x02),		Key5			= key(0, 0x04),		Key7		= key(0, 0x08), | ||||||
|  | 	Key9		= key(0, 0x10),		KeyPlus		= key(0, 0x20),		KeyGBP			= key(0, 0x40),		KeyDelete	= key(0, 0x80), | ||||||
|  |  | ||||||
|  | 	KeyRestore 	= 0xfffd | ||||||
|  | #undef key | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | struct KeyboardMapper: public KeyboardMachine::Machine::KeyboardMapper { | ||||||
|  | 	uint16_t mapped_key_for_key(Inputs::Keyboard::Key key); | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | struct CharacterMapper: public ::Utility::CharacterMapper { | ||||||
|  | 	uint16_t *sequence_for_character(char character); | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #endif /* Keyboard_hpp */ | ||||||
| @@ -8,53 +8,453 @@ | |||||||
|  |  | ||||||
| #include "Vic20.hpp" | #include "Vic20.hpp" | ||||||
|  |  | ||||||
| #include <algorithm> | #include "Keyboard.hpp" | ||||||
| #include "../../../Storage/Tape/Formats/TapePRG.hpp" |  | ||||||
|  | #include "../../../Processors/6502/6502.hpp" | ||||||
|  | #include "../../../Components/6560/6560.hpp" | ||||||
|  | #include "../../../Components/6522/6522.hpp" | ||||||
|  |  | ||||||
|  | #include "../../../ClockReceiver/ForceInline.hpp" | ||||||
|  |  | ||||||
| #include "../../../Storage/Tape/Parsers/Commodore.hpp" | #include "../../../Storage/Tape/Parsers/Commodore.hpp" | ||||||
| #include "../../../StaticAnalyser/StaticAnalyser.hpp" |  | ||||||
| #include "CharacterMapper.hpp" |  | ||||||
|  |  | ||||||
| using namespace Commodore::Vic20; | #include "../SerialBus.hpp" | ||||||
|  | #include "../1540/C1540.hpp" | ||||||
|  |  | ||||||
| Machine::Machine() : | #include "../../../Storage/Tape/Tape.hpp" | ||||||
| 		rom_(nullptr), | #include "../../../Storage/Disk/Disk.hpp" | ||||||
| 		is_running_at_zero_cost_(false), |  | ||||||
| 		tape_(new Storage::Tape::BinaryTapePlayer(1022727)), | #include "../../../Configurable/StandardOptions.hpp" | ||||||
| 		user_port_via_(new UserPortVIA), |  | ||||||
| 		keyboard_via_(new KeyboardVIA), | #include <algorithm> | ||||||
|  | #include <cstdint> | ||||||
|  |  | ||||||
|  | namespace Commodore { | ||||||
|  | namespace Vic20 { | ||||||
|  |  | ||||||
|  | enum ROMSlot { | ||||||
|  | 	Kernel = 0, | ||||||
|  | 	BASIC, | ||||||
|  | 	Characters, | ||||||
|  | 	Drive | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | std::vector<std::unique_ptr<Configurable::Option>> get_options() { | ||||||
|  | 	return Configurable::standard_options(Configurable::QuickLoadTape); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | enum JoystickInput { | ||||||
|  | 	Up = 0x04, | ||||||
|  | 	Down = 0x08, | ||||||
|  | 	Left = 0x10, | ||||||
|  | 	Right = 0x80, | ||||||
|  | 	Fire = 0x20 | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | enum ROM { | ||||||
|  | 	CharactersDanish = 0, | ||||||
|  | 	CharactersEnglish, | ||||||
|  | 	CharactersJapanese, | ||||||
|  | 	CharactersSwedish, | ||||||
|  | 	KernelDanish, | ||||||
|  | 	KernelJapanese, | ||||||
|  | 	KernelNTSC, | ||||||
|  | 	KernelPAL, | ||||||
|  | 	KernelSwedish | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | /*! | ||||||
|  | 	Models the user-port VIA, which is the Vic's connection point for controlling its tape recorder — | ||||||
|  | 	sensing the presence or absence of a tape and controlling the tape motor — and reading the current | ||||||
|  | 	state from its serial port. Most of the joystick input is also exposed here. | ||||||
|  | */ | ||||||
|  | class UserPortVIA: public MOS::MOS6522::IRQDelegatePortHandler { | ||||||
|  | 	public: | ||||||
|  | 		UserPortVIA() : port_a_(0xbf) {} | ||||||
|  |  | ||||||
|  | 		/// Reports the current input to the 6522 port @c port. | ||||||
|  | 		uint8_t get_port_input(MOS::MOS6522::Port port) { | ||||||
|  | 			// Port A provides information about the presence or absence of a tape, and parts of | ||||||
|  | 			// the joystick and serial port state, both of which have been statefully collected | ||||||
|  | 			// into port_a_. | ||||||
|  | 			if(!port) { | ||||||
|  | 				return port_a_ | (tape_->has_tape() ? 0x00 : 0x40); | ||||||
|  | 			} | ||||||
|  | 			return 0xff; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		/// Receives announcements of control line output change from the 6522. | ||||||
|  | 		void set_control_line_output(MOS::MOS6522::Port port, MOS::MOS6522::Line line, bool value) { | ||||||
|  | 			// The CA2 output is used to control the tape motor. | ||||||
|  | 			if(port == MOS::MOS6522::Port::A && line == MOS::MOS6522::Line::Two) { | ||||||
|  | 				tape_->set_motor_control(!value); | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		/// Receives announcements of changes in the serial bus connected to the serial port and propagates them into Port A. | ||||||
|  | 		void set_serial_line_state(::Commodore::Serial::Line line, bool value) { | ||||||
|  | 			switch(line) { | ||||||
|  | 				default: break; | ||||||
|  | 				case ::Commodore::Serial::Line::Data: port_a_ = (port_a_ & ~0x02) | (value ? 0x02 : 0x00);	break; | ||||||
|  | 				case ::Commodore::Serial::Line::Clock: port_a_ = (port_a_ & ~0x01) | (value ? 0x01 : 0x00);	break; | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		/// Allows the current joystick input to be set. | ||||||
|  | 		void set_joystick_state(JoystickInput input, bool value) { | ||||||
|  | 			if(input != JoystickInput::Right) { | ||||||
|  | 				port_a_ = (port_a_ & ~input) | (value ? 0 : input); | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		/// Receives announcements from the 6522 of user-port output, which might affect what's currently being presented onto the serial bus. | ||||||
|  | 		void set_port_output(MOS::MOS6522::Port port, uint8_t value, uint8_t mask) { | ||||||
|  | 			// Line 7 of port A is inverted and output as serial ATN. | ||||||
|  | 			if(!port) { | ||||||
|  | 				std::shared_ptr<::Commodore::Serial::Port> serialPort = serial_port_.lock(); | ||||||
|  | 				if(serialPort) serialPort->set_output(::Commodore::Serial::Line::Attention, (::Commodore::Serial::LineLevel)!(value&0x80)); | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		/// Sets @serial_port as this VIA's connection to the serial bus. | ||||||
|  | 		void set_serial_port(std::shared_ptr<::Commodore::Serial::Port> serial_port) { | ||||||
|  | 			serial_port_ = serial_port; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		/// Sets @tape as the tape player connected to this VIA. | ||||||
|  | 		void set_tape(std::shared_ptr<Storage::Tape::BinaryTapePlayer> tape) { | ||||||
|  | 			tape_ = tape; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 	private: | ||||||
|  | 		uint8_t port_a_; | ||||||
|  | 		std::weak_ptr<::Commodore::Serial::Port> serial_port_; | ||||||
|  | 		std::shared_ptr<Storage::Tape::BinaryTapePlayer> tape_; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | /*! | ||||||
|  | 	Models the keyboard VIA, which is used by the Vic for reading its keyboard, to output to its serial port, | ||||||
|  | 	and for the small portion of joystick input not connected to the user-port VIA. | ||||||
|  | */ | ||||||
|  | class KeyboardVIA: public MOS::MOS6522::IRQDelegatePortHandler { | ||||||
|  | 	public: | ||||||
|  | 		KeyboardVIA() : port_b_(0xff) { | ||||||
|  | 			clear_all_keys(); | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		/// Sets whether @c key @c is_pressed. | ||||||
|  | 		void set_key_state(uint16_t key, bool is_pressed) { | ||||||
|  | 			if(is_pressed) | ||||||
|  | 				columns_[key & 7] &= ~(key >> 3); | ||||||
|  | 			else | ||||||
|  | 				columns_[key & 7] |= (key >> 3); | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		/// Sets all keys as unpressed. | ||||||
|  | 		void clear_all_keys() { | ||||||
|  | 			memset(columns_, 0xff, sizeof(columns_)); | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		/// Called by the 6522 to get input. Reads the keyboard on Port A, returns a small amount of joystick state on Port B. | ||||||
|  | 		uint8_t get_port_input(MOS::MOS6522::Port port) { | ||||||
|  | 			if(!port) { | ||||||
|  | 				uint8_t result = 0xff; | ||||||
|  | 				for(int c = 0; c < 8; c++) { | ||||||
|  | 					if(!(activation_mask_&(1 << c))) | ||||||
|  | 						result &= columns_[c]; | ||||||
|  | 				} | ||||||
|  | 				return result; | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			return port_b_; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		/// Called by the 6522 to set output. The value of Port B selects which part of the keyboard to read. | ||||||
|  | 		void set_port_output(MOS::MOS6522::Port port, uint8_t value, uint8_t mask) { | ||||||
|  | 			if(port) activation_mask_ = (value & mask) | (~mask); | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		/// Called by the 6522 to set control line output. Which affects the serial port. | ||||||
|  | 		void set_control_line_output(MOS::MOS6522::Port port, MOS::MOS6522::Line line, bool value) { | ||||||
|  | 			if(line == MOS::MOS6522::Line::Two) { | ||||||
|  | 				std::shared_ptr<::Commodore::Serial::Port> serialPort = serial_port_.lock(); | ||||||
|  | 				if(serialPort) { | ||||||
|  | 					// CB2 is inverted to become serial data; CA2 is inverted to become serial clock | ||||||
|  | 					if(port == MOS::MOS6522::Port::A) | ||||||
|  | 						serialPort->set_output(::Commodore::Serial::Line::Clock, (::Commodore::Serial::LineLevel)!value); | ||||||
|  | 					else | ||||||
|  | 						serialPort->set_output(::Commodore::Serial::Line::Data, (::Commodore::Serial::LineLevel)!value); | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		/// Sets whether the joystick input @c input is pressed. | ||||||
|  | 		void set_joystick_state(JoystickInput input, bool value) { | ||||||
|  | 			if(input == JoystickInput::Right) { | ||||||
|  | 				port_b_ = (port_b_ & ~input) | (value ? 0 : input); | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		/// Sets the serial port to which this VIA is connected. | ||||||
|  | 		void set_serial_port(std::shared_ptr<::Commodore::Serial::Port> serialPort) { | ||||||
|  | 			serial_port_ = serialPort; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 	private: | ||||||
|  | 		uint8_t port_b_; | ||||||
|  | 		uint8_t columns_[8]; | ||||||
|  | 		uint8_t activation_mask_; | ||||||
|  | 		std::weak_ptr<::Commodore::Serial::Port> serial_port_; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | /*! | ||||||
|  | 	Models the Vic's serial port, providing the receipticle for input. | ||||||
|  | */ | ||||||
|  | class SerialPort : public ::Commodore::Serial::Port { | ||||||
|  | 	public: | ||||||
|  | 		/// Receives an input change from the base serial port class, and communicates it to the user-port VIA. | ||||||
|  | 		void set_input(::Commodore::Serial::Line line, ::Commodore::Serial::LineLevel level) { | ||||||
|  | 			std::shared_ptr<UserPortVIA> userPortVIA = user_port_via_.lock(); | ||||||
|  | 			if(userPortVIA) userPortVIA->set_serial_line_state(line, (bool)level); | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		/// Sets the user-port VIA with which this serial port communicates. | ||||||
|  | 		void set_user_port_via(std::shared_ptr<UserPortVIA> userPortVIA) { | ||||||
|  | 			user_port_via_ = userPortVIA; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 	private: | ||||||
|  | 		std::weak_ptr<UserPortVIA> user_port_via_; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | /*! | ||||||
|  | 	Provides the bus over which the Vic 6560 fetches memory in a Vic-20. | ||||||
|  | */ | ||||||
|  | class Vic6560: public MOS::MOS6560<Vic6560> { | ||||||
|  | 	public: | ||||||
|  | 		/// Performs a read on behalf of the 6560; in practice uses @c video_memory_map and @c colour_memory to find data. | ||||||
|  | 		inline void perform_read(uint16_t address, uint8_t *pixel_data, uint8_t *colour_data) { | ||||||
|  | 			*pixel_data = video_memory_map[address >> 10] ? video_memory_map[address >> 10][address & 0x3ff] : 0xff; // TODO | ||||||
|  | 			*colour_data = colour_memory[address & 0x03ff]; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		// It is assumed that these pointers have been filled in by the machine. | ||||||
|  | 		uint8_t *video_memory_map[16];	// Segments video memory into 1kb portions. | ||||||
|  | 		uint8_t *colour_memory;			// Colour memory must be contiguous. | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | /*! | ||||||
|  | 	Interfaces a joystick to the two VIAs. | ||||||
|  | */ | ||||||
|  | class Joystick: public Inputs::Joystick { | ||||||
|  | 	public: | ||||||
|  | 		Joystick(UserPortVIA &user_port_via_port_handler, KeyboardVIA &keyboard_via_port_handler) : | ||||||
|  | 			user_port_via_port_handler_(user_port_via_port_handler), | ||||||
|  | 			keyboard_via_port_handler_(keyboard_via_port_handler) {} | ||||||
|  |  | ||||||
|  | 		void set_digital_input(DigitalInput digital_input, bool is_active) override { | ||||||
|  | 			JoystickInput mapped_input; | ||||||
|  | 			switch (digital_input) { | ||||||
|  | 				default: return; | ||||||
|  | 				case DigitalInput::Up: mapped_input = Up;		break; | ||||||
|  | 				case DigitalInput::Down: mapped_input = Down;	break; | ||||||
|  | 				case DigitalInput::Left: mapped_input = Left;	break; | ||||||
|  | 				case DigitalInput::Right: mapped_input = Right;	break; | ||||||
|  | 				case DigitalInput::Fire: mapped_input = Fire;	break; | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			user_port_via_port_handler_.set_joystick_state(mapped_input, is_active); | ||||||
|  | 			keyboard_via_port_handler_.set_joystick_state(mapped_input, is_active); | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 	private: | ||||||
|  | 		UserPortVIA &user_port_via_port_handler_; | ||||||
|  | 		KeyboardVIA &keyboard_via_port_handler_; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | class ConcreteMachine: | ||||||
|  | 	public CPU::MOS6502::BusHandler, | ||||||
|  | 	public MOS::MOS6522::IRQDelegatePortHandler::Delegate, | ||||||
|  | 	public Utility::TypeRecipient, | ||||||
|  | 	public Storage::Tape::BinaryTapePlayer::Delegate, | ||||||
|  | 	public Machine { | ||||||
|  | 	public: | ||||||
|  | 		ConcreteMachine() : | ||||||
|  | 				m6502_(*this), | ||||||
|  | 				user_port_via_port_handler_(new UserPortVIA), | ||||||
|  | 				keyboard_via_port_handler_(new KeyboardVIA), | ||||||
| 				serial_port_(new SerialPort), | 				serial_port_(new SerialPort), | ||||||
| 		serial_bus_(new ::Commodore::Serial::Bus) { | 				serial_bus_(new ::Commodore::Serial::Bus), | ||||||
|  | 				user_port_via_(*user_port_via_port_handler_), | ||||||
|  | 				keyboard_via_(*keyboard_via_port_handler_), | ||||||
|  | 				tape_(new Storage::Tape::BinaryTapePlayer(1022727)) { | ||||||
| 			// communicate the tape to the user-port VIA | 			// communicate the tape to the user-port VIA | ||||||
| 	user_port_via_->set_tape(tape_); | 			user_port_via_port_handler_->set_tape(tape_); | ||||||
|  |  | ||||||
| 			// wire up the serial bus and serial port | 			// wire up the serial bus and serial port | ||||||
| 			Commodore::Serial::AttachPortAndBus(serial_port_, serial_bus_); | 			Commodore::Serial::AttachPortAndBus(serial_port_, serial_bus_); | ||||||
|  |  | ||||||
| 			// wire up 6522s and serial port | 			// wire up 6522s and serial port | ||||||
| 	user_port_via_->set_serial_port(serial_port_); | 			user_port_via_port_handler_->set_serial_port(serial_port_); | ||||||
| 	keyboard_via_->set_serial_port(serial_port_); | 			keyboard_via_port_handler_->set_serial_port(serial_port_); | ||||||
| 	serial_port_->set_user_port_via(user_port_via_); | 			serial_port_->set_user_port_via(user_port_via_port_handler_); | ||||||
|  |  | ||||||
| 			// wire up the 6522s, tape and machine | 			// wire up the 6522s, tape and machine | ||||||
| 	user_port_via_->set_interrupt_delegate(this); | 			user_port_via_port_handler_->set_interrupt_delegate(this); | ||||||
| 	keyboard_via_->set_interrupt_delegate(this); | 			keyboard_via_port_handler_->set_interrupt_delegate(this); | ||||||
| 			tape_->set_delegate(this); | 			tape_->set_delegate(this); | ||||||
|  |  | ||||||
| 	// establish the memory maps | 			// install a joystick | ||||||
| 	set_memory_size(MemorySize::Default); | 			joysticks_.emplace_back(new Joystick(*user_port_via_port_handler_, *keyboard_via_port_handler_)); | ||||||
|  | 		} | ||||||
|  |  | ||||||
| 	// set the NTSC clock rate | 		~ConcreteMachine() { | ||||||
| 	set_region(NTSC); | 			delete[] rom_; | ||||||
| //	_debugPort.reset(new ::Commodore::Serial::DebugPort); | 		} | ||||||
| //	_debugPort->set_serial_bus(serial_bus_); |  | ||||||
| //	serial_bus_->add_port(_debugPort); | 		void set_rom(ROMSlot slot, const std::vector<uint8_t> &data) { | ||||||
| } | 		} | ||||||
|  |  | ||||||
|  | 		// Obtains the system ROMs. | ||||||
|  | 		bool set_rom_fetcher(const std::function<std::vector<std::unique_ptr<std::vector<uint8_t>>>(const std::string &machine, const std::vector<std::string> &names)> &roms_with_names) override { | ||||||
|  | 			auto roms = roms_with_names( | ||||||
|  | 				"Vic20", | ||||||
|  | 				{ | ||||||
|  | 					"characters-danish.bin", | ||||||
|  | 					"characters-english.bin", | ||||||
|  | 					"characters-japanese.bin", | ||||||
|  | 					"characters-swedish.bin", | ||||||
|  | 					"kernel-danish.bin", | ||||||
|  | 					"kernel-japanese.bin", | ||||||
|  | 					"kernel-ntsc.bin", | ||||||
|  | 					"kernel-pal.bin", | ||||||
|  | 					"kernel-swedish.bin", | ||||||
|  | 					"basic.bin" | ||||||
|  | 				}); | ||||||
|  |  | ||||||
|  | 			for(std::size_t index = 0; index < roms.size(); ++index) { | ||||||
|  | 				auto &data = roms[index]; | ||||||
|  | 				if(!data) return false; | ||||||
|  | 				if(index < 9) roms_[index] = std::move(*data); else basic_rom_ = std::move(*data); | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			// Characters ROMs should be 4kb. | ||||||
|  | 			for(std::size_t index = 0; index < 4; ++index) roms_[index].resize(4096); | ||||||
|  | 			// Kernel ROMs and the BASIC ROM should be 8kb. | ||||||
|  | 			for(std::size_t index = 4; index < roms.size(); ++index) roms_[index].resize(8192); | ||||||
|  |  | ||||||
|  | 			return true; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		void configure_as_target(const StaticAnalyser::Target &target) override final { | ||||||
|  | 			if(target.loading_command.length()) { | ||||||
|  | 				type_string(target.loading_command); | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			switch(target.vic20.memory_model) { | ||||||
|  | 				case StaticAnalyser::Vic20MemoryModel::Unexpanded: | ||||||
|  | 					set_memory_size(Default); | ||||||
|  | 				break; | ||||||
|  | 				case StaticAnalyser::Vic20MemoryModel::EightKB: | ||||||
|  | 					set_memory_size(ThreeKB); | ||||||
|  | 				break; | ||||||
|  | 				case StaticAnalyser::Vic20MemoryModel::ThirtyTwoKB: | ||||||
|  | 					set_memory_size(ThirtyTwoKB); | ||||||
|  | 				break; | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			if(target.media.disks.size()) { | ||||||
|  | 				// construct the 1540 | ||||||
|  | 				c1540_.reset(new ::Commodore::C1540::Machine(Commodore::C1540::Machine::C1540)); | ||||||
|  |  | ||||||
|  | 				// attach it to the serial bus | ||||||
|  | 				c1540_->set_serial_bus(serial_bus_); | ||||||
|  |  | ||||||
|  | 				// give it a means to obtain its ROM | ||||||
|  | 				c1540_->set_rom_fetcher(rom_fetcher_); | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			insert_media(target.media); | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		bool insert_media(const StaticAnalyser::Media &media) override final { | ||||||
|  | 			if(!media.tapes.empty()) { | ||||||
|  | 				tape_->set_tape(media.tapes.front()); | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			if(!media.disks.empty() && c1540_) { | ||||||
|  | 				c1540_->set_disk(media.disks.front()); | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			if(!media.cartridges.empty()) { | ||||||
|  | 				rom_address_ = 0xa000; | ||||||
|  | 				std::vector<uint8_t> rom_image = media.cartridges.front()->get_segments().front().data; | ||||||
|  | 				rom_length_ = static_cast<uint16_t>(rom_image.size()); | ||||||
|  |  | ||||||
|  | 				rom_ = new uint8_t[0x2000]; | ||||||
|  | 				std::memcpy(rom_, rom_image.data(), rom_image.size()); | ||||||
|  | 				write_to_map(processor_read_memory_map_, rom_, rom_address_, 0x2000); | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			return !media.tapes.empty() || (!media.disks.empty() && c1540_ != nullptr) || !media.cartridges.empty(); | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		void set_key_state(uint16_t key, bool is_pressed) override final { | ||||||
|  | 			if(key != KeyRestore) | ||||||
|  | 				keyboard_via_port_handler_->set_key_state(key, is_pressed); | ||||||
|  | 			else | ||||||
|  | 				user_port_via_.set_control_line_input(MOS::MOS6522::Port::A, MOS::MOS6522::Line::One, !is_pressed); | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		void clear_all_keys() override final { | ||||||
|  | 			keyboard_via_port_handler_->clear_all_keys(); | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		std::vector<std::unique_ptr<Inputs::Joystick>> &get_joysticks() override { | ||||||
|  | 			return joysticks_; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		void set_memory_size(MemorySize size) override final { | ||||||
|  | 			memory_size_ = size; | ||||||
|  | 			needs_configuration_ = true; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		void set_region(Region region) override final { | ||||||
|  | 			region_ = region; | ||||||
|  | 			needs_configuration_ = true; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		void set_ntsc_6560() { | ||||||
|  | 			set_clock_rate(1022727); | ||||||
|  | 			if(mos6560_) { | ||||||
|  | 				mos6560_->set_output_mode(MOS::MOS6560<Commodore::Vic20::Vic6560>::OutputMode::NTSC); | ||||||
|  | 				mos6560_->set_clock_rate(1022727); | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		void set_pal_6560() { | ||||||
|  | 			set_clock_rate(1108404); | ||||||
|  | 			if(mos6560_) { | ||||||
|  | 				mos6560_->set_output_mode(MOS::MOS6560<Commodore::Vic20::Vic6560>::OutputMode::PAL); | ||||||
|  | 				mos6560_->set_clock_rate(1108404); | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		void configure_memory() { | ||||||
|  | 			// Determine PAL/NTSC | ||||||
|  | 			if(region_ == American || region_ == Japanese) { | ||||||
|  | 				// NTSC | ||||||
|  | 				set_ntsc_6560(); | ||||||
|  | 			} else { | ||||||
|  | 				// PAL | ||||||
|  | 				set_pal_6560(); | ||||||
|  | 			} | ||||||
|  |  | ||||||
| void Machine::set_memory_size(MemorySize size) { |  | ||||||
| 			memset(processor_read_memory_map_, 0, sizeof(processor_read_memory_map_)); | 			memset(processor_read_memory_map_, 0, sizeof(processor_read_memory_map_)); | ||||||
| 			memset(processor_write_memory_map_, 0, sizeof(processor_write_memory_map_)); | 			memset(processor_write_memory_map_, 0, sizeof(processor_write_memory_map_)); | ||||||
|  | 			memset(mos6560_->video_memory_map, 0, sizeof(mos6560_->video_memory_map)); | ||||||
|  |  | ||||||
| 	switch(size) { | 			switch(memory_size_) { | ||||||
| 				default: break; | 				default: break; | ||||||
| 				case ThreeKB: | 				case ThreeKB: | ||||||
| 					write_to_map(processor_read_memory_map_, expansion_ram_, 0x0000, 0x1000); | 					write_to_map(processor_read_memory_map_, expansion_ram_, 0x0000, 0x1000); | ||||||
| @@ -70,35 +470,58 @@ void Machine::set_memory_size(MemorySize size) { | |||||||
| 			write_to_map(processor_read_memory_map_, user_basic_memory_, 0x0000, sizeof(user_basic_memory_)); | 			write_to_map(processor_read_memory_map_, user_basic_memory_, 0x0000, sizeof(user_basic_memory_)); | ||||||
| 			write_to_map(processor_read_memory_map_, screen_memory_, 0x1000, sizeof(screen_memory_)); | 			write_to_map(processor_read_memory_map_, screen_memory_, 0x1000, sizeof(screen_memory_)); | ||||||
| 			write_to_map(processor_read_memory_map_, colour_memory_, 0x9400, sizeof(colour_memory_)); | 			write_to_map(processor_read_memory_map_, colour_memory_, 0x9400, sizeof(colour_memory_)); | ||||||
| 	write_to_map(processor_read_memory_map_, character_rom_, 0x8000, sizeof(character_rom_)); |  | ||||||
| 	write_to_map(processor_read_memory_map_, basic_rom_, 0xc000, sizeof(basic_rom_)); |  | ||||||
| 	write_to_map(processor_read_memory_map_, kernel_rom_, 0xe000, sizeof(kernel_rom_)); |  | ||||||
|  |  | ||||||
| 			write_to_map(processor_write_memory_map_, user_basic_memory_, 0x0000, sizeof(user_basic_memory_)); | 			write_to_map(processor_write_memory_map_, user_basic_memory_, 0x0000, sizeof(user_basic_memory_)); | ||||||
| 			write_to_map(processor_write_memory_map_, screen_memory_, 0x1000, sizeof(screen_memory_)); | 			write_to_map(processor_write_memory_map_, screen_memory_, 0x1000, sizeof(screen_memory_)); | ||||||
| 			write_to_map(processor_write_memory_map_, colour_memory_, 0x9400, sizeof(colour_memory_)); | 			write_to_map(processor_write_memory_map_, colour_memory_, 0x9400, sizeof(colour_memory_)); | ||||||
|  |  | ||||||
|  | 			write_to_map(mos6560_->video_memory_map, user_basic_memory_, 0x2000, sizeof(user_basic_memory_)); | ||||||
|  | 			write_to_map(mos6560_->video_memory_map, screen_memory_, 0x3000, sizeof(screen_memory_)); | ||||||
|  | 			mos6560_->colour_memory = colour_memory_; | ||||||
|  |  | ||||||
|  | 			write_to_map(processor_read_memory_map_, basic_rom_.data(), 0xc000, static_cast<uint16_t>(basic_rom_.size())); | ||||||
|  |  | ||||||
|  | 			ROM character_rom; | ||||||
|  | 			ROM kernel_rom; | ||||||
|  | 			switch(region_) { | ||||||
|  | 				default: | ||||||
|  | 					character_rom = CharactersEnglish; | ||||||
|  | 					kernel_rom = KernelPAL; | ||||||
|  | 				break; | ||||||
|  | 				case American: | ||||||
|  | 					character_rom = CharactersEnglish; | ||||||
|  | 					kernel_rom = KernelNTSC; | ||||||
|  | 				break; | ||||||
|  | 				case Danish: | ||||||
|  | 					character_rom = CharactersDanish; | ||||||
|  | 					kernel_rom = KernelDanish; | ||||||
|  | 				break; | ||||||
|  | 				case Japanese: | ||||||
|  | 					character_rom = CharactersJapanese; | ||||||
|  | 					kernel_rom = KernelJapanese; | ||||||
|  | 				break; | ||||||
|  | 				case Swedish: | ||||||
|  | 					character_rom = CharactersSwedish; | ||||||
|  | 					kernel_rom = KernelSwedish; | ||||||
|  | 				break; | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			write_to_map(processor_read_memory_map_, roms_[character_rom].data(), 0x8000, static_cast<uint16_t>(roms_[character_rom].size())); | ||||||
|  | 			write_to_map(mos6560_->video_memory_map, roms_[character_rom].data(), 0x0000, static_cast<uint16_t>(roms_[character_rom].size())); | ||||||
|  | 			write_to_map(processor_read_memory_map_, roms_[kernel_rom].data(), 0xe000, static_cast<uint16_t>(roms_[kernel_rom].size())); | ||||||
|  |  | ||||||
| 			// install the inserted ROM if there is one | 			// install the inserted ROM if there is one | ||||||
| 			if(rom_) { | 			if(rom_) { | ||||||
| 				write_to_map(processor_read_memory_map_, rom_, rom_address_, rom_length_); | 				write_to_map(processor_read_memory_map_, rom_, rom_address_, rom_length_); | ||||||
| 			} | 			} | ||||||
| } |  | ||||||
|  |  | ||||||
| void Machine::write_to_map(uint8_t **map, uint8_t *area, uint16_t address, uint16_t length) { |  | ||||||
| 	address >>= 10; |  | ||||||
| 	length >>= 10; |  | ||||||
| 	while(length--) { |  | ||||||
| 		map[address] = area; |  | ||||||
| 		area += 0x400; |  | ||||||
| 		address++; |  | ||||||
| 		} | 		} | ||||||
| } |  | ||||||
|  |  | ||||||
| Machine::~Machine() { | 		void set_use_fast_tape_hack(bool activate) { | ||||||
| 	delete[] rom_; | 			use_fast_tape_hack_ = activate; | ||||||
| } | 		} | ||||||
|  |  | ||||||
| Cycles Machine::perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value) { | 		// to satisfy CPU::MOS6502::Processor | ||||||
|  | 		forceinline Cycles perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value) { | ||||||
| 			// run the phase-1 part of this cycle, in which the VIC accesses memory | 			// run the phase-1 part of this cycle, in which the VIC accesses memory | ||||||
| 			if(!is_running_at_zero_cost_) mos6560_->run_for(Cycles(1)); | 			if(!is_running_at_zero_cost_) mos6560_->run_for(Cycles(1)); | ||||||
|  |  | ||||||
| @@ -107,8 +530,8 @@ Cycles Machine::perform_bus_operation(CPU::MOS6502::BusOperation operation, uint | |||||||
| 				uint8_t result = processor_read_memory_map_[address >> 10] ? processor_read_memory_map_[address >> 10][address & 0x3ff] : 0xff; | 				uint8_t result = processor_read_memory_map_[address >> 10] ? processor_read_memory_map_[address >> 10][address & 0x3ff] : 0xff; | ||||||
| 				if((address&0xfc00) == 0x9000) { | 				if((address&0xfc00) == 0x9000) { | ||||||
| 					if((address&0xff00) == 0x9000)	result &= mos6560_->get_register(address); | 					if((address&0xff00) == 0x9000)	result &= mos6560_->get_register(address); | ||||||
| 			if((address&0xfc10) == 0x9010)	result &= user_port_via_->get_register(address); | 					if((address&0xfc10) == 0x9010)	result &= user_port_via_.get_register(address); | ||||||
| 			if((address&0xfc20) == 0x9020)	result &= keyboard_via_->get_register(address); | 					if((address&0xfc20) == 0x9020)	result &= keyboard_via_.get_register(address); | ||||||
| 				} | 				} | ||||||
| 				*value = result; | 				*value = result; | ||||||
|  |  | ||||||
| @@ -124,7 +547,7 @@ Cycles Machine::perform_bus_operation(CPU::MOS6502::BusOperation operation, uint | |||||||
| 						std::unique_ptr<Storage::Tape::Commodore::Header> header = parser.get_next_header(tape_->get_tape()); | 						std::unique_ptr<Storage::Tape::Commodore::Header> header = parser.get_next_header(tape_->get_tape()); | ||||||
|  |  | ||||||
| 						// serialise to wherever b2:b3 points | 						// serialise to wherever b2:b3 points | ||||||
| 				uint16_t tape_buffer_pointer = (uint16_t)user_basic_memory_[0xb2] | (uint16_t)(user_basic_memory_[0xb3] << 8); | 						uint16_t tape_buffer_pointer = static_cast<uint16_t>(user_basic_memory_[0xb2]) | static_cast<uint16_t>(user_basic_memory_[0xb3] << 8); | ||||||
| 						if(header) { | 						if(header) { | ||||||
| 							header->serialise(&user_basic_memory_[tape_buffer_pointer], 0x8000 - tape_buffer_pointer); | 							header->serialise(&user_basic_memory_[tape_buffer_pointer], 0x8000 - tape_buffer_pointer); | ||||||
| 						} else { | 						} else { | ||||||
| @@ -138,17 +561,17 @@ Cycles Machine::perform_bus_operation(CPU::MOS6502::BusOperation operation, uint | |||||||
|  |  | ||||||
| 						*value = 0x0c;	// i.e. NOP abs | 						*value = 0x0c;	// i.e. NOP abs | ||||||
| 					} else if(address == 0xf90b) { | 					} else if(address == 0xf90b) { | ||||||
| 				uint8_t x = (uint8_t)get_value_of_register(CPU::MOS6502::Register::X); | 						uint8_t x = static_cast<uint8_t>(m6502_.get_value_of_register(CPU::MOS6502::Register::X)); | ||||||
| 						if(x == 0xe) { | 						if(x == 0xe) { | ||||||
| 							Storage::Tape::Commodore::Parser parser; | 							Storage::Tape::Commodore::Parser parser; | ||||||
| 							std::unique_ptr<Storage::Tape::Commodore::Data> data = parser.get_next_data(tape_->get_tape()); | 							std::unique_ptr<Storage::Tape::Commodore::Data> data = parser.get_next_data(tape_->get_tape()); | ||||||
| 							uint16_t start_address, end_address; | 							uint16_t start_address, end_address; | ||||||
| 					start_address = (uint16_t)(user_basic_memory_[0xc1] | (user_basic_memory_[0xc2] << 8)); | 							start_address = static_cast<uint16_t>(user_basic_memory_[0xc1] | (user_basic_memory_[0xc2] << 8)); | ||||||
| 					end_address = (uint16_t)(user_basic_memory_[0xae] | (user_basic_memory_[0xaf] << 8)); | 							end_address = static_cast<uint16_t>(user_basic_memory_[0xae] | (user_basic_memory_[0xaf] << 8)); | ||||||
|  |  | ||||||
| 							// perform a via-processor_write_memory_map_ memcpy | 							// perform a via-processor_write_memory_map_ memcpy | ||||||
| 							uint8_t *data_ptr = data->data.data(); | 							uint8_t *data_ptr = data->data.data(); | ||||||
| 					size_t data_left = data->data.size(); | 							std::size_t data_left = data->data.size(); | ||||||
| 							while(data_left && start_address != end_address) { | 							while(data_left && start_address != end_address) { | ||||||
| 								uint8_t *page = processor_write_memory_map_[start_address >> 10]; | 								uint8_t *page = processor_write_memory_map_[start_address >> 10]; | ||||||
| 								if(page) page[start_address & 0x3ff] = *data_ptr; | 								if(page) page[start_address & 0x3ff] = *data_ptr; | ||||||
| @@ -159,13 +582,13 @@ Cycles Machine::perform_bus_operation(CPU::MOS6502::BusOperation operation, uint | |||||||
|  |  | ||||||
| 							// set tape status, carry and flag | 							// set tape status, carry and flag | ||||||
| 							user_basic_memory_[0x90] |= 0x40; | 							user_basic_memory_[0x90] |= 0x40; | ||||||
| 					uint8_t	flags = (uint8_t)get_value_of_register(CPU::MOS6502::Register::Flags); | 							uint8_t	flags = static_cast<uint8_t>(m6502_.get_value_of_register(CPU::MOS6502::Register::Flags)); | ||||||
| 					flags &= ~(uint8_t)(CPU::MOS6502::Flag::Carry | CPU::MOS6502::Flag::Interrupt); | 							flags &= ~static_cast<uint8_t>((CPU::MOS6502::Flag::Carry | CPU::MOS6502::Flag::Interrupt)); | ||||||
| 					set_value_of_register(CPU::MOS6502::Register::Flags, flags); | 							m6502_.set_value_of_register(CPU::MOS6502::Register::Flags, flags); | ||||||
|  |  | ||||||
| 							// to ensure that execution proceeds to 0xfccf, pretend a NOP was here and | 							// to ensure that execution proceeds to 0xfccf, pretend a NOP was here and | ||||||
| 							// ensure that the PC leaps to 0xfccf | 							// ensure that the PC leaps to 0xfccf | ||||||
| 					set_value_of_register(CPU::MOS6502::Register::ProgramCounter, 0xfccf); | 							m6502_.set_value_of_register(CPU::MOS6502::Register::ProgramCounter, 0xfccf); | ||||||
| 							*value = 0xea;	// i.e. NOP implied | 							*value = 0xea;	// i.e. NOP implied | ||||||
| 						} | 						} | ||||||
| 					} | 					} | ||||||
| @@ -175,13 +598,13 @@ Cycles Machine::perform_bus_operation(CPU::MOS6502::BusOperation operation, uint | |||||||
| 				if(ram) ram[address & 0x3ff] = *value; | 				if(ram) ram[address & 0x3ff] = *value; | ||||||
| 				if((address&0xfc00) == 0x9000) { | 				if((address&0xfc00) == 0x9000) { | ||||||
| 					if((address&0xff00) == 0x9000)	mos6560_->set_register(address, *value); | 					if((address&0xff00) == 0x9000)	mos6560_->set_register(address, *value); | ||||||
| 			if((address&0xfc10) == 0x9010)	user_port_via_->set_register(address, *value); | 					if((address&0xfc10) == 0x9010)	user_port_via_.set_register(address, *value); | ||||||
| 			if((address&0xfc20) == 0x9020)	keyboard_via_->set_register(address, *value); | 					if((address&0xfc20) == 0x9020)	keyboard_via_.set_register(address, *value); | ||||||
| 				} | 				} | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
| 	user_port_via_->run_for(Cycles(1)); | 			user_port_via_.run_for(Cycles(1)); | ||||||
| 	keyboard_via_->run_for(Cycles(1)); | 			keyboard_via_.run_for(Cycles(1)); | ||||||
| 			if(typer_ && operation == CPU::MOS6502::BusOperation::ReadOpcode && address == 0xEB1E) { | 			if(typer_ && operation == CPU::MOS6502::BusOperation::ReadOpcode && address == 0xEB1E) { | ||||||
| 				if(!typer_->type_next_character()) { | 				if(!typer_->type_next_character()) { | ||||||
| 					clear_all_keys(); | 					clear_all_keys(); | ||||||
| @@ -192,253 +615,144 @@ Cycles Machine::perform_bus_operation(CPU::MOS6502::BusOperation operation, uint | |||||||
| 			if(c1540_) c1540_->run_for(Cycles(1)); | 			if(c1540_) c1540_->run_for(Cycles(1)); | ||||||
|  |  | ||||||
| 			return Cycles(1); | 			return Cycles(1); | ||||||
| } |  | ||||||
|  |  | ||||||
| #pragma mark - 6522 delegate |  | ||||||
|  |  | ||||||
| void Machine::mos6522_did_change_interrupt_status(void *mos6522) { |  | ||||||
| 	set_nmi_line(user_port_via_->get_interrupt_line()); |  | ||||||
| 	set_irq_line(keyboard_via_->get_interrupt_line()); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| #pragma mark - Setup |  | ||||||
|  |  | ||||||
| void Machine::set_region(Commodore::Vic20::Region region) { |  | ||||||
| 	region_ = region; |  | ||||||
| 	switch(region) { |  | ||||||
| 		case PAL: |  | ||||||
| 			set_clock_rate(1108404); |  | ||||||
| 			if(mos6560_) { |  | ||||||
| 				mos6560_->set_output_mode(MOS::MOS6560<Commodore::Vic20::Vic6560>::OutputMode::PAL); |  | ||||||
| 				mos6560_->set_clock_rate(1108404); |  | ||||||
| 		} | 		} | ||||||
| 		break; |  | ||||||
| 		case NTSC: |  | ||||||
| 			set_clock_rate(1022727); |  | ||||||
| 			if(mos6560_) { |  | ||||||
| 				mos6560_->set_output_mode(MOS::MOS6560<Commodore::Vic20::Vic6560>::OutputMode::NTSC); |  | ||||||
| 				mos6560_->set_clock_rate(1022727); |  | ||||||
| 			} |  | ||||||
| 		break; |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void Machine::setup_output(float aspect_ratio) { | 		forceinline void flush() { | ||||||
|  | 			mos6560_->flush(); | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		void run_for(const Cycles cycles) override final { | ||||||
|  | 			if(needs_configuration_) { | ||||||
|  | 				needs_configuration_ = false; | ||||||
|  | 				configure_memory(); | ||||||
|  | 			} | ||||||
|  | 			m6502_.run_for(cycles); | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		void setup_output(float aspect_ratio) override final { | ||||||
| 			mos6560_.reset(new Vic6560()); | 			mos6560_.reset(new Vic6560()); | ||||||
| 	mos6560_->get_speaker()->set_high_frequency_cut_off(1600);	// There is a 1.6Khz low-pass filter in the Vic-20. | 			mos6560_->set_high_frequency_cutoff(1600);	// There is a 1.6Khz low-pass filter in the Vic-20. | ||||||
| 	set_region(region_); | 			// Make a guess: PAL. Without setting a clock rate the 6560 isn't fully set up so contractually something must be set. | ||||||
|  | 			set_pal_6560(); | ||||||
|  | 		} | ||||||
|  |  | ||||||
| 	memset(mos6560_->video_memory_map, 0, sizeof(mos6560_->video_memory_map)); | 		void close_output() override final { | ||||||
| 	write_to_map(mos6560_->video_memory_map, character_rom_, 0x0000, sizeof(character_rom_)); |  | ||||||
| 	write_to_map(mos6560_->video_memory_map, user_basic_memory_, 0x2000, sizeof(user_basic_memory_)); |  | ||||||
| 	write_to_map(mos6560_->video_memory_map, screen_memory_, 0x3000, sizeof(screen_memory_)); |  | ||||||
| 	mos6560_->colour_memory = colour_memory_; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void Machine::close_output() { |  | ||||||
| 			mos6560_ = nullptr; | 			mos6560_ = nullptr; | ||||||
| } |  | ||||||
|  |  | ||||||
| void Machine::set_rom(ROMSlot slot, size_t length, const uint8_t *data) { |  | ||||||
| 	uint8_t *target = nullptr; |  | ||||||
| 	size_t max_length = 0x2000; |  | ||||||
| 	switch(slot) { |  | ||||||
| 		case Kernel:		target = kernel_rom_;								break; |  | ||||||
| 		case Characters:	target = character_rom_;	max_length = 0x1000;	break; |  | ||||||
| 		case BASIC:			target = basic_rom_;								break; |  | ||||||
| 		case Drive: |  | ||||||
| 			drive_rom_.resize(length); |  | ||||||
| 			memcpy(drive_rom_.data(), data, length); |  | ||||||
| 			install_disk_rom(); |  | ||||||
| 		return; |  | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 	if(target) { | 		Outputs::CRT::CRT *get_crt() override final { | ||||||
| 		size_t length_to_copy = std::min(max_length, length); | 			return mos6560_->get_crt(); | ||||||
| 		memcpy(target, data, length_to_copy); |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
|  |  | ||||||
| #pragma mar - Tape |  | ||||||
|  |  | ||||||
| void Machine::configure_as_target(const StaticAnalyser::Target &target) { |  | ||||||
| 	if(target.tapes.size()) { |  | ||||||
| 		tape_->set_tape(target.tapes.front()); |  | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 	if(target.disks.size()) { | 		Outputs::Speaker::Speaker *get_speaker() override final { | ||||||
| 		// construct the 1540 | 			return mos6560_->get_speaker(); | ||||||
| 		c1540_.reset(new ::Commodore::C1540::Machine); |  | ||||||
|  |  | ||||||
| 		// attach it to the serial bus |  | ||||||
| 		c1540_->set_serial_bus(serial_bus_); |  | ||||||
|  |  | ||||||
| 		// hand it the disk |  | ||||||
| 		c1540_->set_disk(target.disks.front()); |  | ||||||
|  |  | ||||||
| 		// install the ROM if it was previously set |  | ||||||
| 		install_disk_rom(); |  | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 	if(target.cartridges.size()) { | 		void mos6522_did_change_interrupt_status(void *mos6522) override final { | ||||||
| 		rom_address_ = 0xa000; | 			m6502_.set_nmi_line(user_port_via_.get_interrupt_line()); | ||||||
| 		std::vector<uint8_t> rom_image = target.cartridges.front()->get_segments().front().data; | 			m6502_.set_irq_line(keyboard_via_.get_interrupt_line()); | ||||||
| 		rom_length_ = (uint16_t)(rom_image.size()); |  | ||||||
|  |  | ||||||
| 		rom_ = new uint8_t[0x2000]; |  | ||||||
| 		memcpy(rom_, rom_image.data(), rom_image.size()); |  | ||||||
| 		write_to_map(processor_read_memory_map_, rom_, rom_address_, 0x2000); |  | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 	if(target.loadingCommand.length()) { | 		void type_string(const std::string &string) override final { | ||||||
| 		set_typer_for_string(target.loadingCommand.c_str()); |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	switch(target.vic20.memory_model) { |  | ||||||
| 		case StaticAnalyser::Vic20MemoryModel::Unexpanded: |  | ||||||
| 			set_memory_size(Default); |  | ||||||
| 		break; |  | ||||||
| 		case StaticAnalyser::Vic20MemoryModel::EightKB: |  | ||||||
| 			set_memory_size(ThreeKB); |  | ||||||
| 		break; |  | ||||||
| 		case StaticAnalyser::Vic20MemoryModel::ThirtyTwoKB: |  | ||||||
| 			set_memory_size(ThirtyTwoKB); |  | ||||||
| 		break; |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void Machine::set_typer_for_string(const char *string) { |  | ||||||
| 			std::unique_ptr<CharacterMapper> mapper(new CharacterMapper()); | 			std::unique_ptr<CharacterMapper> mapper(new CharacterMapper()); | ||||||
| 	Utility::TypeRecipient::set_typer_for_string(string, std::move(mapper)); | 			Utility::TypeRecipient::add_typer(string, std::move(mapper)); | ||||||
| } |  | ||||||
|  |  | ||||||
| void Machine::tape_did_change_input(Storage::Tape::BinaryTapePlayer *tape) { |  | ||||||
| 	keyboard_via_->set_control_line_input(KeyboardVIA::Port::A, KeyboardVIA::Line::One, !tape->get_input()); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| #pragma mark - Disc |  | ||||||
|  |  | ||||||
| void Machine::install_disk_rom() { |  | ||||||
| 	if(!drive_rom_.empty() && c1540_) { |  | ||||||
| 		c1540_->set_rom(drive_rom_); |  | ||||||
| 		c1540_->run_for(Cycles(2000000)); |  | ||||||
| 		drive_rom_.clear(); |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
|  |  | ||||||
| #pragma mark - UserPortVIA |  | ||||||
|  |  | ||||||
| uint8_t UserPortVIA::get_port_input(Port port) { |  | ||||||
| 	if(!port) { |  | ||||||
| 		return port_a_ | (tape_->has_tape() ? 0x00 : 0x40); |  | ||||||
| 	} |  | ||||||
| 	return 0xff; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void UserPortVIA::set_control_line_output(Port port, Line line, bool value) { |  | ||||||
| 	if(port == Port::A && line == Line::Two) { |  | ||||||
| 		tape_->set_motor_control(!value); |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void UserPortVIA::set_serial_line_state(::Commodore::Serial::Line line, bool value) { |  | ||||||
| 	switch(line) { |  | ||||||
| 		default: break; |  | ||||||
| 		case ::Commodore::Serial::Line::Data: port_a_ = (port_a_ & ~0x02) | (value ? 0x02 : 0x00);	break; |  | ||||||
| 		case ::Commodore::Serial::Line::Clock: port_a_ = (port_a_ & ~0x01) | (value ? 0x01 : 0x00);	break; |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void UserPortVIA::set_joystick_state(JoystickInput input, bool value) { |  | ||||||
| 	if(input != JoystickInput::Right) { |  | ||||||
| 		port_a_ = (port_a_ & ~input) | (value ? 0 : input); |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void UserPortVIA::set_port_output(Port port, uint8_t value, uint8_t mask) { |  | ||||||
| 	// Line 7 of port A is inverted and output as serial ATN |  | ||||||
| 	if(!port) { |  | ||||||
| 		std::shared_ptr<::Commodore::Serial::Port> serialPort = serial_port_.lock(); |  | ||||||
| 		if(serialPort) |  | ||||||
| 			serialPort->set_output(::Commodore::Serial::Line::Attention, (::Commodore::Serial::LineLevel)!(value&0x80)); |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
|  |  | ||||||
| UserPortVIA::UserPortVIA() : port_a_(0xbf) {} |  | ||||||
|  |  | ||||||
| void UserPortVIA::set_serial_port(std::shared_ptr<::Commodore::Serial::Port> serialPort) { |  | ||||||
| 	serial_port_ = serialPort; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void UserPortVIA::set_tape(std::shared_ptr<Storage::Tape::BinaryTapePlayer> tape) { |  | ||||||
| 	tape_ = tape; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| #pragma mark - KeyboardVIA |  | ||||||
|  |  | ||||||
| KeyboardVIA::KeyboardVIA() : port_b_(0xff) { |  | ||||||
| 	clear_all_keys(); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void KeyboardVIA::set_key_state(uint16_t key, bool isPressed) { |  | ||||||
| 	if(isPressed) |  | ||||||
| 		columns_[key & 7] &= ~(key >> 3); |  | ||||||
| 	else |  | ||||||
| 		columns_[key & 7] |= (key >> 3); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void KeyboardVIA::clear_all_keys() { |  | ||||||
| 	memset(columns_, 0xff, sizeof(columns_)); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| uint8_t KeyboardVIA::get_port_input(Port port) { |  | ||||||
| 	if(!port) { |  | ||||||
| 		uint8_t result = 0xff; |  | ||||||
| 		for(int c = 0; c < 8; c++) { |  | ||||||
| 			if(!(activation_mask_&(1 << c))) |  | ||||||
| 				result &= columns_[c]; |  | ||||||
| 		} |  | ||||||
| 		return result; |  | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 	return port_b_; | 		void tape_did_change_input(Storage::Tape::BinaryTapePlayer *tape) override final { | ||||||
| } | 			keyboard_via_.set_control_line_input(MOS::MOS6522::Port::A, MOS::MOS6522::Line::One, !tape->get_input()); | ||||||
|  | 		} | ||||||
|  |  | ||||||
| void KeyboardVIA::set_port_output(Port port, uint8_t value, uint8_t mask) { | 		KeyboardMapper &get_keyboard_mapper() override { | ||||||
| 	if(port) | 			return keyboard_mapper_; | ||||||
| 		activation_mask_ = (value & mask) | (~mask); | 		} | ||||||
| } |  | ||||||
|  |  | ||||||
| void KeyboardVIA::set_control_line_output(Port port, Line line, bool value) { | 		// MARK: - Configuration options. | ||||||
| 	if(line == Line::Two) { | 		std::vector<std::unique_ptr<Configurable::Option>> get_options() override { | ||||||
| 		std::shared_ptr<::Commodore::Serial::Port> serialPort = serial_port_.lock(); | 			return Commodore::Vic20::get_options(); | ||||||
| 		if(serialPort) { | 		} | ||||||
| 			// CB2 is inverted to become serial data; CA2 is inverted to become serial clock |  | ||||||
| 			if(port == Port::A) | 		void set_selections(const Configurable::SelectionSet &selections_by_option) override { | ||||||
| 				serialPort->set_output(::Commodore::Serial::Line::Clock, (::Commodore::Serial::LineLevel)!value); | 			bool quickload; | ||||||
| 			else | 			if(Configurable::get_quick_load_tape(selections_by_option, quickload)) { | ||||||
| 				serialPort->set_output(::Commodore::Serial::Line::Data, (::Commodore::Serial::LineLevel)!value); | 				set_use_fast_tape_hack(quickload); | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
| } |  | ||||||
|  |  | ||||||
| void KeyboardVIA::set_joystick_state(JoystickInput input, bool value) { | 		Configurable::SelectionSet get_accurate_selections() override { | ||||||
| 	if(input == JoystickInput::Right) { | 			Configurable::SelectionSet selection_set; | ||||||
| 		port_b_ = (port_b_ & ~input) | (value ? 0 : input); | 			Configurable::append_quick_load_tape_selection(selection_set, false); | ||||||
|  | 			return selection_set; | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
|  | 		Configurable::SelectionSet get_user_friendly_selections() override { | ||||||
|  | 			Configurable::SelectionSet selection_set; | ||||||
|  | 			Configurable::append_quick_load_tape_selection(selection_set, true); | ||||||
|  | 			return selection_set; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 	private: | ||||||
|  | 		CPU::MOS6502::Processor<ConcreteMachine, false> m6502_; | ||||||
|  |  | ||||||
|  | 		std::vector<uint8_t>  roms_[9]; | ||||||
|  |  | ||||||
|  | 		std::vector<uint8_t>  character_rom_; | ||||||
|  | 		std::vector<uint8_t>  basic_rom_; | ||||||
|  | 		std::vector<uint8_t>  kernel_rom_; | ||||||
|  | 		uint8_t expansion_ram_[0x8000]; | ||||||
|  |  | ||||||
|  | 		uint8_t *rom_ = nullptr; | ||||||
|  | 		uint16_t rom_address_, rom_length_; | ||||||
|  |  | ||||||
|  | 		uint8_t user_basic_memory_[0x0400]; | ||||||
|  | 		uint8_t screen_memory_[0x1000]; | ||||||
|  | 		uint8_t colour_memory_[0x0400]; | ||||||
|  |  | ||||||
|  | 		std::function<std::vector<std::unique_ptr<std::vector<uint8_t>>>(const std::string &machine, const std::vector<std::string> &names)> rom_fetcher_; | ||||||
|  |  | ||||||
|  | 		uint8_t *processor_read_memory_map_[64]; | ||||||
|  | 		uint8_t *processor_write_memory_map_[64]; | ||||||
|  | 		void write_to_map(uint8_t **map, uint8_t *area, uint16_t address, uint16_t length) { | ||||||
|  | 			address >>= 10; | ||||||
|  | 			length >>= 10; | ||||||
|  | 			while(length--) { | ||||||
|  | 				map[address] = area; | ||||||
|  | 				area += 0x400; | ||||||
|  | 				address++; | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		Region region_ = European; | ||||||
|  | 		MemorySize memory_size_ = MemorySize::Default; | ||||||
|  | 		bool needs_configuration_ = true; | ||||||
|  |  | ||||||
|  | 		Commodore::Vic20::KeyboardMapper keyboard_mapper_; | ||||||
|  | 		std::vector<std::unique_ptr<Inputs::Joystick>> joysticks_; | ||||||
|  |  | ||||||
|  | 		std::unique_ptr<Vic6560> mos6560_; | ||||||
|  | 		std::shared_ptr<UserPortVIA> user_port_via_port_handler_; | ||||||
|  | 		std::shared_ptr<KeyboardVIA> keyboard_via_port_handler_; | ||||||
|  | 		std::shared_ptr<SerialPort> serial_port_; | ||||||
|  | 		std::shared_ptr<::Commodore::Serial::Bus> serial_bus_; | ||||||
|  |  | ||||||
|  | 		MOS::MOS6522::MOS6522<UserPortVIA> user_port_via_; | ||||||
|  | 		MOS::MOS6522::MOS6522<KeyboardVIA> keyboard_via_; | ||||||
|  |  | ||||||
|  | 		// Tape | ||||||
|  | 		std::shared_ptr<Storage::Tape::BinaryTapePlayer> tape_; | ||||||
|  | 		bool use_fast_tape_hack_; | ||||||
|  | 		bool is_running_at_zero_cost_ = false; | ||||||
|  |  | ||||||
|  | 		// Disk | ||||||
|  | 		std::shared_ptr<::Commodore::C1540::Machine> c1540_; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | } | ||||||
| } | } | ||||||
|  |  | ||||||
| void KeyboardVIA::set_serial_port(std::shared_ptr<::Commodore::Serial::Port> serialPort) { | using namespace Commodore::Vic20; | ||||||
| 	serial_port_ = serialPort; |  | ||||||
|  | Machine *Machine::Vic20() { | ||||||
|  | 	return new Vic20::ConcreteMachine; | ||||||
| } | } | ||||||
|  |  | ||||||
| #pragma mark - SerialPort | Machine::~Machine() {} | ||||||
|  |  | ||||||
| void SerialPort::set_input(::Commodore::Serial::Line line, ::Commodore::Serial::LineLevel level) { |  | ||||||
| 	std::shared_ptr<UserPortVIA> userPortVIA = user_port_via_.lock(); |  | ||||||
| 	if(userPortVIA) userPortVIA->set_serial_line_state(line, (bool)level); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void SerialPort::set_user_port_via(std::shared_ptr<UserPortVIA> userPortVIA) { |  | ||||||
| 	user_port_via_ = userPortVIA; |  | ||||||
| } |  | ||||||
|   | |||||||
| @@ -9,30 +9,15 @@ | |||||||
| #ifndef Vic20_hpp | #ifndef Vic20_hpp | ||||||
| #define Vic20_hpp | #define Vic20_hpp | ||||||
|  |  | ||||||
|  | #include "../../../Configurable/Configurable.hpp" | ||||||
| #include "../../ConfigurationTarget.hpp" | #include "../../ConfigurationTarget.hpp" | ||||||
| #include "../../CRTMachine.hpp" | #include "../../CRTMachine.hpp" | ||||||
| #include "../../Typer.hpp" | #include "../../KeyboardMachine.hpp" | ||||||
|  | #include "../../JoystickMachine.hpp" | ||||||
| #include "../../../Processors/6502/6502.hpp" |  | ||||||
| #include "../../../Components/6560/6560.hpp" |  | ||||||
| #include "../../../Components/6522/6522.hpp" |  | ||||||
|  |  | ||||||
| #include "../SerialBus.hpp" |  | ||||||
| #include "../1540/C1540.hpp" |  | ||||||
|  |  | ||||||
| #include "../../../Storage/Tape/Tape.hpp" |  | ||||||
| #include "../../../Storage/Disk/Disk.hpp" |  | ||||||
|  |  | ||||||
| namespace Commodore { | namespace Commodore { | ||||||
| namespace Vic20 { | namespace Vic20 { | ||||||
|  |  | ||||||
| enum ROMSlot { |  | ||||||
| 	Kernel, |  | ||||||
| 	BASIC, |  | ||||||
| 	Characters, |  | ||||||
| 	Drive |  | ||||||
| }; |  | ||||||
|  |  | ||||||
| enum MemorySize { | enum MemorySize { | ||||||
| 	Default, | 	Default, | ||||||
| 	ThreeKB, | 	ThreeKB, | ||||||
| @@ -40,186 +25,33 @@ enum MemorySize { | |||||||
| }; | }; | ||||||
|  |  | ||||||
| enum Region { | enum Region { | ||||||
| 	NTSC, | 	American, | ||||||
| 	PAL | 	Danish, | ||||||
|  | 	Japanese, | ||||||
|  | 	European, | ||||||
|  | 	Swedish | ||||||
| }; | }; | ||||||
|  |  | ||||||
| #define key(line, mask) (((mask) << 3) | (line)) | /// @returns The options available for a Vic-20. | ||||||
|  | std::vector<std::unique_ptr<Configurable::Option>> get_options(); | ||||||
| enum Key: uint16_t { |  | ||||||
| 	Key2		= key(7, 0x01),		Key4		= key(7, 0x02),		Key6			= key(7, 0x04),		Key8		= key(7, 0x08), |  | ||||||
| 	Key0		= key(7, 0x10),		KeyDash		= key(7, 0x20),		KeyHome			= key(7, 0x40),		KeyF7		= key(7, 0x80), |  | ||||||
| 	KeyQ		= key(6, 0x01),		KeyE		= key(6, 0x02),		KeyT			= key(6, 0x04),		KeyU		= key(6, 0x08), |  | ||||||
| 	KeyO		= key(6, 0x10),		KeyAt		= key(6, 0x20),		KeyUp			= key(6, 0x40),		KeyF5		= key(6, 0x80), |  | ||||||
| 	KeyCBM		= key(5, 0x01),		KeyS		= key(5, 0x02),		KeyF			= key(5, 0x04),		KeyH		= key(5, 0x08), |  | ||||||
| 	KeyK		= key(5, 0x10),		KeyColon	= key(5, 0x20),		KeyEquals		= key(5, 0x40),		KeyF3		= key(5, 0x80), |  | ||||||
| 	KeySpace	= key(4, 0x01),		KeyZ		= key(4, 0x02),		KeyC			= key(4, 0x04),		KeyB		= key(4, 0x08), |  | ||||||
| 	KeyM		= key(4, 0x10),		KeyFullStop	= key(4, 0x20),		KeyRShift		= key(4, 0x40),		KeyF1		= key(4, 0x80), |  | ||||||
| 	KeyRunStop	= key(3, 0x01),		KeyLShift	= key(3, 0x02),		KeyX			= key(3, 0x04),		KeyV		= key(3, 0x08), |  | ||||||
| 	KeyN		= key(3, 0x10),		KeyComma	= key(3, 0x20),		KeySlash		= key(3, 0x40),		KeyDown		= key(3, 0x80), |  | ||||||
| 	KeyControl	= key(2, 0x01),		KeyA		= key(2, 0x02),		KeyD			= key(2, 0x04),		KeyG		= key(2, 0x08), |  | ||||||
| 	KeyJ		= key(2, 0x10),		KeyL		= key(2, 0x20),		KeySemicolon	= key(2, 0x40),		KeyRight	= key(2, 0x80), |  | ||||||
| 	KeyLeft		= key(1, 0x01),		KeyW		= key(1, 0x02),		KeyR			= key(1, 0x04),		KeyY		= key(1, 0x08), |  | ||||||
| 	KeyI		= key(1, 0x10),		KeyP		= key(1, 0x20),		KeyAsterisk		= key(1, 0x40),		KeyReturn	= key(1, 0x80), |  | ||||||
| 	Key1		= key(0, 0x01),		Key3		= key(0, 0x02),		Key5			= key(0, 0x04),		Key7		= key(0, 0x08), |  | ||||||
| 	Key9		= key(0, 0x10),		KeyPlus		= key(0, 0x20),		KeyGBP			= key(0, 0x40),		KeyDelete	= key(0, 0x80), |  | ||||||
| }; |  | ||||||
|  |  | ||||||
| enum JoystickInput { |  | ||||||
| 	Up = 0x04, |  | ||||||
| 	Down = 0x08, |  | ||||||
| 	Left = 0x10, |  | ||||||
| 	Right = 0x80, |  | ||||||
| 	Fire = 0x20 |  | ||||||
| }; |  | ||||||
|  |  | ||||||
| class UserPortVIA: public MOS::MOS6522<UserPortVIA>, public MOS::MOS6522IRQDelegate { |  | ||||||
| 	public: |  | ||||||
| 		UserPortVIA(); |  | ||||||
| 		using MOS6522IRQDelegate::set_interrupt_status; |  | ||||||
|  |  | ||||||
| 		uint8_t get_port_input(Port port); |  | ||||||
| 		void set_control_line_output(Port port, Line line, bool value); |  | ||||||
| 		void set_serial_line_state(::Commodore::Serial::Line line, bool value); |  | ||||||
| 		void set_joystick_state(JoystickInput input, bool value); |  | ||||||
| 		void set_port_output(Port port, uint8_t value, uint8_t mask); |  | ||||||
|  |  | ||||||
| 		void set_serial_port(std::shared_ptr<::Commodore::Serial::Port> serialPort); |  | ||||||
| 		void set_tape(std::shared_ptr<Storage::Tape::BinaryTapePlayer> tape); |  | ||||||
|  |  | ||||||
| 	private: |  | ||||||
| 		uint8_t port_a_; |  | ||||||
| 		std::weak_ptr<::Commodore::Serial::Port> serial_port_; |  | ||||||
| 		std::shared_ptr<Storage::Tape::BinaryTapePlayer> tape_; |  | ||||||
| }; |  | ||||||
|  |  | ||||||
| class KeyboardVIA: public MOS::MOS6522<KeyboardVIA>, public MOS::MOS6522IRQDelegate { |  | ||||||
| 	public: |  | ||||||
| 		KeyboardVIA(); |  | ||||||
| 		using MOS6522IRQDelegate::set_interrupt_status; |  | ||||||
|  |  | ||||||
| 		void set_key_state(uint16_t key, bool isPressed); |  | ||||||
| 		void clear_all_keys(); |  | ||||||
|  |  | ||||||
| 		// to satisfy MOS::MOS6522 |  | ||||||
| 		uint8_t get_port_input(Port port); |  | ||||||
|  |  | ||||||
| 		void set_port_output(Port port, uint8_t value, uint8_t mask); |  | ||||||
| 		void set_control_line_output(Port port, Line line, bool value); |  | ||||||
|  |  | ||||||
| 		void set_joystick_state(JoystickInput input, bool value); |  | ||||||
|  |  | ||||||
| 		void set_serial_port(std::shared_ptr<::Commodore::Serial::Port> serialPort); |  | ||||||
|  |  | ||||||
| 	private: |  | ||||||
| 		uint8_t port_b_; |  | ||||||
| 		uint8_t columns_[8]; |  | ||||||
| 		uint8_t activation_mask_; |  | ||||||
| 		std::weak_ptr<::Commodore::Serial::Port> serial_port_; |  | ||||||
| }; |  | ||||||
|  |  | ||||||
| class SerialPort : public ::Commodore::Serial::Port { |  | ||||||
| 	public: |  | ||||||
| 		void set_input(::Commodore::Serial::Line line, ::Commodore::Serial::LineLevel level); |  | ||||||
| 		void set_user_port_via(std::shared_ptr<UserPortVIA> userPortVIA); |  | ||||||
|  |  | ||||||
| 	private: |  | ||||||
| 		std::weak_ptr<UserPortVIA> user_port_via_; |  | ||||||
| }; |  | ||||||
|  |  | ||||||
| class Vic6560: public MOS::MOS6560<Vic6560> { |  | ||||||
| 	public: |  | ||||||
| 		inline void perform_read(uint16_t address, uint8_t *pixel_data, uint8_t *colour_data) { |  | ||||||
| 			*pixel_data = video_memory_map[address >> 10] ? video_memory_map[address >> 10][address & 0x3ff] : 0xff; // TODO |  | ||||||
| 			*colour_data = colour_memory[address & 0x03ff]; |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		uint8_t *video_memory_map[16]; |  | ||||||
| 		uint8_t *colour_memory; |  | ||||||
| }; |  | ||||||
|  |  | ||||||
| class Machine: | class Machine: | ||||||
| 	public CPU::MOS6502::Processor<Machine>, |  | ||||||
| 	public CRTMachine::Machine, | 	public CRTMachine::Machine, | ||||||
| 	public MOS::MOS6522IRQDelegate::Delegate, | 	public ConfigurationTarget::Machine, | ||||||
| 	public Utility::TypeRecipient, | 	public KeyboardMachine::Machine, | ||||||
| 	public Storage::Tape::BinaryTapePlayer::Delegate, | 	public JoystickMachine::Machine, | ||||||
| 	public ConfigurationTarget::Machine { | 	public Configurable::Device { | ||||||
|  |  | ||||||
| 	public: | 	public: | ||||||
| 		Machine(); | 		virtual ~Machine(); | ||||||
| 		~Machine(); |  | ||||||
|  |  | ||||||
| 		void set_rom(ROMSlot slot, size_t length, const uint8_t *data); | 		/// Creates and returns a Vic-20. | ||||||
| 		void configure_as_target(const StaticAnalyser::Target &target); | 		static Machine *Vic20(); | ||||||
|  |  | ||||||
| 		void set_key_state(uint16_t key, bool isPressed) { keyboard_via_->set_key_state(key, isPressed); } | 		/// Sets the memory size of this Vic-20. | ||||||
| 		void clear_all_keys() { keyboard_via_->clear_all_keys(); } | 		virtual void set_memory_size(MemorySize size) = 0; | ||||||
| 		void set_joystick_state(JoystickInput input, bool isPressed) { |  | ||||||
| 			user_port_via_->set_joystick_state(input, isPressed); |  | ||||||
| 			keyboard_via_->set_joystick_state(input, isPressed); |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		void set_memory_size(MemorySize size); | 		/// Sets the region of this Vic-20. | ||||||
| 		void set_region(Region region); | 		virtual void set_region(Region region) = 0; | ||||||
|  |  | ||||||
| 		inline void set_use_fast_tape_hack(bool activate) { use_fast_tape_hack_ = activate; } |  | ||||||
|  |  | ||||||
| 		// to satisfy CPU::MOS6502::Processor |  | ||||||
| 		Cycles perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value); |  | ||||||
| 		void flush() { mos6560_->flush(); } |  | ||||||
|  |  | ||||||
| 		// to satisfy CRTMachine::Machine |  | ||||||
| 		virtual void setup_output(float aspect_ratio); |  | ||||||
| 		virtual void close_output(); |  | ||||||
| 		virtual std::shared_ptr<Outputs::CRT::CRT> get_crt() { return mos6560_->get_crt(); } |  | ||||||
| 		virtual std::shared_ptr<Outputs::Speaker> get_speaker() { return mos6560_->get_speaker(); } |  | ||||||
| 		virtual void run_for(const Cycles cycles) { CPU::MOS6502::Processor<Machine>::run_for(cycles); } |  | ||||||
|  |  | ||||||
| 		// to satisfy MOS::MOS6522::Delegate |  | ||||||
| 		virtual void mos6522_did_change_interrupt_status(void *mos6522); |  | ||||||
|  |  | ||||||
| 		// for Utility::TypeRecipient |  | ||||||
| 		uint16_t *sequence_for_character(Utility::Typer *typer, char character); |  | ||||||
| 		void set_typer_for_string(const char *string); |  | ||||||
|  |  | ||||||
| 		// for Tape::Delegate |  | ||||||
| 		virtual void tape_did_change_input(Storage::Tape::BinaryTapePlayer *tape); |  | ||||||
|  |  | ||||||
| 	private: |  | ||||||
| 		uint8_t character_rom_[0x1000]; |  | ||||||
| 		uint8_t basic_rom_[0x2000]; |  | ||||||
| 		uint8_t kernel_rom_[0x2000]; |  | ||||||
| 		uint8_t expansion_ram_[0x8000]; |  | ||||||
|  |  | ||||||
| 		uint8_t *rom_; |  | ||||||
| 		uint16_t rom_address_, rom_length_; |  | ||||||
|  |  | ||||||
| 		uint8_t user_basic_memory_[0x0400]; |  | ||||||
| 		uint8_t screen_memory_[0x1000]; |  | ||||||
| 		uint8_t colour_memory_[0x0400]; |  | ||||||
| 		std::vector<uint8_t> drive_rom_; |  | ||||||
|  |  | ||||||
| 		uint8_t *processor_read_memory_map_[64]; |  | ||||||
| 		uint8_t *processor_write_memory_map_[64]; |  | ||||||
| 		void write_to_map(uint8_t **map, uint8_t *area, uint16_t address, uint16_t length); |  | ||||||
|  |  | ||||||
| 		Region region_; |  | ||||||
|  |  | ||||||
| 		std::unique_ptr<Vic6560> mos6560_; |  | ||||||
| 		std::shared_ptr<UserPortVIA> user_port_via_; |  | ||||||
| 		std::shared_ptr<KeyboardVIA> keyboard_via_; |  | ||||||
| 		std::shared_ptr<SerialPort> serial_port_; |  | ||||||
| 		std::shared_ptr<::Commodore::Serial::Bus> serial_bus_; |  | ||||||
|  |  | ||||||
| 		// Tape |  | ||||||
| 		std::shared_ptr<Storage::Tape::BinaryTapePlayer> tape_; |  | ||||||
| 		bool use_fast_tape_hack_; |  | ||||||
| 		bool is_running_at_zero_cost_; |  | ||||||
|  |  | ||||||
| 		// Disk |  | ||||||
| 		std::shared_ptr<::Commodore::C1540::Machine> c1540_; |  | ||||||
| 		void install_disk_rom(); |  | ||||||
| }; | }; | ||||||
|  |  | ||||||
| } | } | ||||||
|   | |||||||
| @@ -10,16 +10,27 @@ | |||||||
| #define ConfigurationTarget_hpp | #define ConfigurationTarget_hpp | ||||||
|  |  | ||||||
| #include "../StaticAnalyser/StaticAnalyser.hpp" | #include "../StaticAnalyser/StaticAnalyser.hpp" | ||||||
|  | #include "../Configurable/Configurable.hpp" | ||||||
|  |  | ||||||
|  | #include <string> | ||||||
|  |  | ||||||
| namespace ConfigurationTarget { | namespace ConfigurationTarget { | ||||||
|  |  | ||||||
| /*! | /*! | ||||||
| 	A ConfigurationTarget::Machine is anything that can accept a StaticAnalyser::Target | 	A ConfigurationTarget::Machine is anything that can accept a StaticAnalyser::Target | ||||||
| 	and configure itself appropriately. | 	and configure itself appropriately, or accept a list of media subsequently to insert. | ||||||
| */ | */ | ||||||
| class Machine { | class Machine { | ||||||
| 	public: | 	public: | ||||||
|  | 		/// Instructs the machine to configure itself as described by @c target and insert the included media. | ||||||
| 		virtual void configure_as_target(const StaticAnalyser::Target &target) = 0; | 		virtual void configure_as_target(const StaticAnalyser::Target &target) = 0; | ||||||
|  |  | ||||||
|  | 		/*! | ||||||
|  | 			Requests that the machine insert @c media as a modification to current state | ||||||
|  |  | ||||||
|  | 			@returns @c true if any media was inserted; @c false otherwise. | ||||||
|  | 		*/ | ||||||
|  | 		virtual bool insert_media(const StaticAnalyser::Media &media) = 0; | ||||||
| }; | }; | ||||||
|  |  | ||||||
| } | } | ||||||
|   | |||||||
| @@ -1,23 +0,0 @@ | |||||||
| // |  | ||||||
| //  CharacterMapper.hpp |  | ||||||
| //  Clock Signal |  | ||||||
| // |  | ||||||
| //  Created by Thomas Harte on 03/08/2017. |  | ||||||
| //  Copyright © 2017 Thomas Harte. All rights reserved. |  | ||||||
| // |  | ||||||
|  |  | ||||||
| #ifndef Machines_Electron_CharacterMapper_hpp |  | ||||||
| #define Machines_Electron_CharacterMapper_hpp |  | ||||||
|  |  | ||||||
| #include "../Typer.hpp" |  | ||||||
|  |  | ||||||
| namespace Electron { |  | ||||||
|  |  | ||||||
| class CharacterMapper: public ::Utility::CharacterMapper { |  | ||||||
| 	public: |  | ||||||
| 		uint16_t *sequence_for_character(char character); |  | ||||||
| }; |  | ||||||
|  |  | ||||||
| } |  | ||||||
|  |  | ||||||
| #endif /* Machines_Electron_CharacterMapper_hpp */ |  | ||||||
| @@ -8,114 +8,56 @@ | |||||||
|  |  | ||||||
| #include "Electron.hpp" | #include "Electron.hpp" | ||||||
|  |  | ||||||
| #include "CharacterMapper.hpp" | #include "../../ClockReceiver/ClockReceiver.hpp" | ||||||
|  | #include "../../ClockReceiver/ForceInline.hpp" | ||||||
|  | #include "../../Configurable/StandardOptions.hpp" | ||||||
|  | #include "../../Outputs/Speaker/Implementation/LowpassSpeaker.hpp" | ||||||
|  | #include "../../Processors/6502/6502.hpp" | ||||||
|  | #include "../../Storage/Tape/Tape.hpp" | ||||||
|  |  | ||||||
| using namespace Electron; | #include "../Utility/Typer.hpp" | ||||||
|  |  | ||||||
| #pragma mark - Lifecycle | #include "Interrupts.hpp" | ||||||
|  | #include "Keyboard.hpp" | ||||||
|  | #include "Plus3.hpp" | ||||||
|  | #include "SoundGenerator.hpp" | ||||||
|  | #include "Tape.hpp" | ||||||
|  | #include "Video.hpp" | ||||||
|  |  | ||||||
| Machine::Machine() : | namespace Electron { | ||||||
| 		interrupt_control_(0), |  | ||||||
| 		interrupt_status_(Interrupt::PowerOnReset | Interrupt::TransmitDataEmpty | 0x80), | std::vector<std::unique_ptr<Configurable::Option>> get_options() { | ||||||
| 		cycles_since_audio_update_(0), | 	return Configurable::standard_options( | ||||||
| 		use_fast_tape_hack_(false), | 		static_cast<Configurable::StandardOptions>(Configurable::DisplayRGBComposite | Configurable::QuickLoadTape) | ||||||
| 		cycles_until_display_interrupt_(0) { | 	); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | class ConcreteMachine: | ||||||
|  | 	public Machine, | ||||||
|  | 	public CPU::MOS6502::BusHandler, | ||||||
|  | 	public Tape::Delegate, | ||||||
|  | 	public Utility::TypeRecipient { | ||||||
|  | 	public: | ||||||
|  | 		ConcreteMachine() : | ||||||
|  | 			m6502_(*this), | ||||||
|  | 			sound_generator_(audio_queue_), | ||||||
|  | 			speaker_(sound_generator_) { | ||||||
| 			memset(key_states_, 0, sizeof(key_states_)); | 			memset(key_states_, 0, sizeof(key_states_)); | ||||||
| 			for(int c = 0; c < 16; c++) | 			for(int c = 0; c < 16; c++) | ||||||
| 				memset(roms_[c], 0xff, 16384); | 				memset(roms_[c], 0xff, 16384); | ||||||
|  |  | ||||||
| 			tape_.set_delegate(this); | 			tape_.set_delegate(this); | ||||||
| 			set_clock_rate(2000000); | 			set_clock_rate(2000000); | ||||||
| } |  | ||||||
|  |  | ||||||
| #pragma mark - Output | 			speaker_.set_input_rate(2000000 / SoundGenerator::clock_rate_divider); | ||||||
|  |  | ||||||
| void Machine::setup_output(float aspect_ratio) { |  | ||||||
| 	video_output_.reset(new VideoOutput(ram_)); |  | ||||||
|  |  | ||||||
| 	// The maximum output frequency is 62500Hz and all other permitted output frequencies are integral divisions of that; |  | ||||||
| 	// however setting the speaker on or off can happen on any 2Mhz cycle, and probably (?) takes effect immediately. So |  | ||||||
| 	// run the speaker at a 2000000Hz input rate, at least for the time being. |  | ||||||
| 	speaker_.reset(new Speaker); |  | ||||||
| 	speaker_->set_input_rate(2000000 / Speaker::clock_rate_divider); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void Machine::close_output() { |  | ||||||
| 	video_output_.reset(); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| std::shared_ptr<Outputs::CRT::CRT> Machine::get_crt() { |  | ||||||
| 	return video_output_->get_crt(); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| std::shared_ptr<Outputs::Speaker> Machine::get_speaker() { |  | ||||||
| 	return speaker_; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| #pragma mark - The keyboard |  | ||||||
|  |  | ||||||
| void Machine::clear_all_keys() { |  | ||||||
| 	memset(key_states_, 0, sizeof(key_states_)); |  | ||||||
| 	if(is_holding_shift_) set_key_state(KeyShift, true); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void Machine::set_key_state(uint16_t key, bool isPressed) { |  | ||||||
| 	if(key == KeyBreak) { |  | ||||||
| 		set_reset_line(isPressed); |  | ||||||
| 	} else { |  | ||||||
| 		if(isPressed) |  | ||||||
| 			key_states_[key >> 4] |= key&0xf; |  | ||||||
| 		else |  | ||||||
| 			key_states_[key >> 4] &= ~(key&0xf); |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
|  |  | ||||||
| #pragma mark - Machine configuration |  | ||||||
|  |  | ||||||
| void Machine::configure_as_target(const StaticAnalyser::Target &target) { |  | ||||||
| 	if(target.tapes.size()) { |  | ||||||
| 		tape_.set_tape(target.tapes.front()); |  | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 	if(target.disks.size()) { | 		void set_rom(ROMSlot slot, const std::vector<uint8_t> &data, bool is_writeable) override final { | ||||||
| 		plus3_.reset(new Plus3); |  | ||||||
|  |  | ||||||
| 		if(target.acorn.has_dfs) { |  | ||||||
| 			set_rom(ROMSlot0, dfs_, true); |  | ||||||
| 		} |  | ||||||
| 		if(target.acorn.has_adfs) { |  | ||||||
| 			set_rom(ROMSlot4, adfs_, true); |  | ||||||
| 			set_rom(ROMSlot5, std::vector<uint8_t>(adfs_.begin() + 16384, adfs_.end()), true); |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		plus3_->set_disk(target.disks.front(), 0); |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	ROMSlot slot = ROMSlot12; |  | ||||||
| 	for(std::shared_ptr<Storage::Cartridge::Cartridge> cartridge : target.cartridges) { |  | ||||||
| 		set_rom(slot, cartridge->get_segments().front().data, false); |  | ||||||
| 		slot = (ROMSlot)(((int)slot + 1)&15); |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	if(target.loadingCommand.length()) { |  | ||||||
| 		set_typer_for_string(target.loadingCommand.c_str()); |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	if(target.acorn.should_shift_restart) { |  | ||||||
| 		shift_restart_counter_ = 1000000; |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void Machine::set_typer_for_string(const char *string) { |  | ||||||
| 	std::unique_ptr<CharacterMapper> mapper(new CharacterMapper()); |  | ||||||
| 	Utility::TypeRecipient::set_typer_for_string(string, std::move(mapper)); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void Machine::set_rom(ROMSlot slot, std::vector<uint8_t> data, bool is_writeable) { |  | ||||||
| 			uint8_t *target = nullptr; | 			uint8_t *target = nullptr; | ||||||
| 			switch(slot) { | 			switch(slot) { | ||||||
| 				case ROMSlotDFS:	dfs_ = data;			return; | 				case ROMSlotDFS:	dfs_ = data;			return; | ||||||
| 		case ROMSlotADFS:	adfs_ = data;			return; | 				case ROMSlotADFS1:	adfs1_ = data;			return; | ||||||
|  | 				case ROMSlotADFS2:	adfs2_ = data;			return; | ||||||
|  |  | ||||||
| 				case ROMSlotOS:		target = os_;			break; | 				case ROMSlotOS:		target = os_;			break; | ||||||
| 				default: | 				default: | ||||||
| @@ -124,12 +66,96 @@ void Machine::set_rom(ROMSlot slot, std::vector<uint8_t> data, bool is_writeable | |||||||
| 				break; | 				break; | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
| 	memcpy(target, &data[0], std::min((size_t)16384, data.size())); | 			std::memcpy(target, &data[0], std::min(static_cast<std::size_t>(16384), data.size())); | ||||||
| } | 		} | ||||||
|  |  | ||||||
| #pragma mark - The bus | 		// Obtains the system ROMs. | ||||||
|  | 		bool set_rom_fetcher(const std::function<std::vector<std::unique_ptr<std::vector<uint8_t>>>(const std::string &machine, const std::vector<std::string> &names)> &roms_with_names) override { | ||||||
|  | 			auto roms = roms_with_names( | ||||||
|  | 				"Electron", | ||||||
|  | 				{ | ||||||
|  | 					"DFS-1770-2.20.rom", | ||||||
|  | 					"ADFS-E00_1.rom",	"ADFS-E00_2.rom", | ||||||
|  | 					"basic.rom",		"os.rom" | ||||||
|  | 				}); | ||||||
|  | 			ROMSlot slots[] = { | ||||||
|  | 				ROMSlotDFS, | ||||||
|  | 				ROMSlotADFS1, ROMSlotADFS2, | ||||||
|  | 				ROMSlotBASIC, ROMSlotOS | ||||||
|  | 			}; | ||||||
|  |  | ||||||
| Cycles Machine::perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value) { | 			for(std::size_t index = 0; index < roms.size(); ++index) { | ||||||
|  | 				auto &data = roms[index]; | ||||||
|  | 				if(!data) return false; | ||||||
|  | 				set_rom(slots[index], *data, false); | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			return true; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		void set_key_state(uint16_t key, bool isPressed) override final { | ||||||
|  | 			if(key == KeyBreak) { | ||||||
|  | 				m6502_.set_reset_line(isPressed); | ||||||
|  | 			} else { | ||||||
|  | 				if(isPressed) | ||||||
|  | 					key_states_[key >> 4] |= key&0xf; | ||||||
|  | 				else | ||||||
|  | 					key_states_[key >> 4] &= ~(key&0xf); | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		void clear_all_keys() override final { | ||||||
|  | 			memset(key_states_, 0, sizeof(key_states_)); | ||||||
|  | 			if(is_holding_shift_) set_key_state(KeyShift, true); | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		void set_use_fast_tape_hack(bool activate) { | ||||||
|  | 			use_fast_tape_hack_ = activate; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		void configure_as_target(const StaticAnalyser::Target &target) override final { | ||||||
|  | 			if(target.loading_command.length()) { | ||||||
|  | 				type_string(target.loading_command); | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			if(target.acorn.should_shift_restart) { | ||||||
|  | 				shift_restart_counter_ = 1000000; | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			if(target.acorn.has_dfs || target.acorn.has_adfs) { | ||||||
|  | 				plus3_.reset(new Plus3); | ||||||
|  |  | ||||||
|  | 				if(target.acorn.has_dfs) { | ||||||
|  | 					set_rom(ROMSlot0, dfs_, true); | ||||||
|  | 				} | ||||||
|  | 				if(target.acorn.has_adfs) { | ||||||
|  | 					set_rom(ROMSlot4, adfs1_, true); | ||||||
|  | 					set_rom(ROMSlot5, adfs2_, true); | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			insert_media(target.media); | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		bool insert_media(const StaticAnalyser::Media &media) override final { | ||||||
|  | 			if(!media.tapes.empty()) { | ||||||
|  | 				tape_.set_tape(media.tapes.front()); | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			if(!media.disks.empty() && plus3_) { | ||||||
|  | 				plus3_->set_disk(media.disks.front(), 0); | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			ROMSlot slot = ROMSlot12; | ||||||
|  | 			for(std::shared_ptr<Storage::Cartridge::Cartridge> cartridge : media.cartridges) { | ||||||
|  | 				set_rom(slot, cartridge->get_segments().front().data, false); | ||||||
|  | 				slot = static_cast<ROMSlot>((static_cast<int>(slot) + 1)&15); | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			return !media.tapes.empty() || !media.disks.empty() || !media.cartridges.empty(); | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		forceinline Cycles perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value) { | ||||||
| 			unsigned int cycles = 1; | 			unsigned int cycles = 1; | ||||||
|  |  | ||||||
| 			if(address < 0x8000) { | 			if(address < 0x8000) { | ||||||
| @@ -160,7 +186,7 @@ Cycles Machine::perform_bus_operation(CPU::MOS6502::BusOperation operation, uint | |||||||
| 							bool new_speaker_is_enabled = (*value & 6) == 2; | 							bool new_speaker_is_enabled = (*value & 6) == 2; | ||||||
| 							if(new_speaker_is_enabled != speaker_is_enabled_) { | 							if(new_speaker_is_enabled != speaker_is_enabled_) { | ||||||
| 								update_audio(); | 								update_audio(); | ||||||
| 						speaker_->set_is_enabled(new_speaker_is_enabled); | 								sound_generator_.set_is_enabled(new_speaker_is_enabled); | ||||||
| 								speaker_is_enabled_ = new_speaker_is_enabled; | 								speaker_is_enabled_ = new_speaker_is_enabled; | ||||||
| 							} | 							} | ||||||
|  |  | ||||||
| @@ -221,7 +247,7 @@ Cycles Machine::perform_bus_operation(CPU::MOS6502::BusOperation operation, uint | |||||||
| 					case 0xfe06: | 					case 0xfe06: | ||||||
| 						if(!isReadOperation(operation)) { | 						if(!isReadOperation(operation)) { | ||||||
| 							update_audio(); | 							update_audio(); | ||||||
| 					speaker_->set_divider(*value); | 							sound_generator_.set_divider(*value); | ||||||
| 							tape_.set_counter(*value); | 							tape_.set_counter(*value); | ||||||
| 						} | 						} | ||||||
| 					break; | 					break; | ||||||
| @@ -269,7 +295,7 @@ Cycles Machine::perform_bus_operation(CPU::MOS6502::BusOperation operation, uint | |||||||
| 																						// allow the PC read to return an RTS. | 																						// allow the PC read to return an RTS. | ||||||
| 									) | 									) | ||||||
| 								) { | 								) { | ||||||
| 							uint8_t service_call = (uint8_t)get_value_of_register(CPU::MOS6502::Register::X); | 									uint8_t service_call = static_cast<uint8_t>(m6502_.get_value_of_register(CPU::MOS6502::Register::X)); | ||||||
| 									if(address == 0xf0a8) { | 									if(address == 0xf0a8) { | ||||||
| 										if(!ram_[0x247] && service_call == 14) { | 										if(!ram_[0x247] && service_call == 14) { | ||||||
| 											tape_.set_delegate(nullptr); | 											tape_.set_delegate(nullptr); | ||||||
| @@ -291,8 +317,8 @@ Cycles Machine::perform_bus_operation(CPU::MOS6502::BusOperation operation, uint | |||||||
| 											interrupt_status_ |= tape_.get_interrupt_status(); | 											interrupt_status_ |= tape_.get_interrupt_status(); | ||||||
|  |  | ||||||
| 											fast_load_is_in_data_ = true; | 											fast_load_is_in_data_ = true; | ||||||
| 									set_value_of_register(CPU::MOS6502::Register::A, 0); | 											m6502_.set_value_of_register(CPU::MOS6502::Register::A, 0); | ||||||
| 									set_value_of_register(CPU::MOS6502::Register::Y, tape_.get_data_register()); | 											m6502_.set_value_of_register(CPU::MOS6502::Register::Y, tape_.get_data_register()); | ||||||
| 											*value = 0x60; // 0x60 is RTS | 											*value = 0x60; // 0x60 is RTS | ||||||
| 										} | 										} | ||||||
| 										else *value = os_[address & 16383]; | 										else *value = os_[address & 16383]; | ||||||
| @@ -322,10 +348,10 @@ Cycles Machine::perform_bus_operation(CPU::MOS6502::BusOperation operation, uint | |||||||
| 				} | 				} | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
| 	cycles_since_display_update_ += Cycles((int)cycles); | 			cycles_since_display_update_ += Cycles(static_cast<int>(cycles)); | ||||||
| 	cycles_since_audio_update_ += Cycles((int)cycles); | 			cycles_since_audio_update_ += Cycles(static_cast<int>(cycles)); | ||||||
| 			if(cycles_since_audio_update_ > Cycles(16384)) update_audio(); | 			if(cycles_since_audio_update_ > Cycles(16384)) update_audio(); | ||||||
| 	tape_.run_for(Cycles((int)cycles)); | 			tape_.run_for(Cycles(static_cast<int>(cycles))); | ||||||
|  |  | ||||||
| 			cycles_until_display_interrupt_ -= cycles; | 			cycles_until_display_interrupt_ -= cycles; | ||||||
| 			if(cycles_until_display_interrupt_ < 0) { | 			if(cycles_until_display_interrupt_ < 0) { | ||||||
| @@ -334,81 +360,189 @@ Cycles Machine::perform_bus_operation(CPU::MOS6502::BusOperation operation, uint | |||||||
| 				queue_next_display_interrupt(); | 				queue_next_display_interrupt(); | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
| 	if(typer_) typer_->run_for(Cycles((int)cycles)); | 			if(typer_) typer_->run_for(Cycles(static_cast<int>(cycles))); | ||||||
| 	if(plus3_) plus3_->run_for(Cycles(4*(int)cycles)); | 			if(plus3_) plus3_->run_for(Cycles(4*static_cast<int>(cycles))); | ||||||
| 			if(shift_restart_counter_) { | 			if(shift_restart_counter_) { | ||||||
| 				shift_restart_counter_ -= cycles; | 				shift_restart_counter_ -= cycles; | ||||||
| 				if(shift_restart_counter_ <= 0) { | 				if(shift_restart_counter_ <= 0) { | ||||||
| 					shift_restart_counter_ = 0; | 					shift_restart_counter_ = 0; | ||||||
| 			set_power_on(true); | 					m6502_.set_power_on(true); | ||||||
| 					set_key_state(KeyShift, true); | 					set_key_state(KeyShift, true); | ||||||
| 					is_holding_shift_ = true; | 					is_holding_shift_ = true; | ||||||
| 				} | 				} | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
| 	return Cycles((int)cycles); | 			return Cycles(static_cast<int>(cycles)); | ||||||
| } | 		} | ||||||
|  |  | ||||||
| void Machine::flush() { | 		forceinline void flush() { | ||||||
| 			update_display(); | 			update_display(); | ||||||
| 			update_audio(); | 			update_audio(); | ||||||
| 	speaker_->flush(); | 			audio_queue_.perform(); | ||||||
| } | 		} | ||||||
|  |  | ||||||
| #pragma mark - Deferred scheduling | 		void setup_output(float aspect_ratio) override final { | ||||||
|  | 			video_output_.reset(new VideoOutput(ram_)); | ||||||
|  | 		} | ||||||
|  |  | ||||||
| inline void Machine::update_display() { | 		void close_output() override final { | ||||||
|  | 			video_output_.reset(); | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		Outputs::CRT::CRT *get_crt() override final { | ||||||
|  | 			return video_output_->get_crt(); | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		Outputs::Speaker::Speaker *get_speaker() override final { | ||||||
|  | 			return &speaker_; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		void run_for(const Cycles cycles) override final { | ||||||
|  | 			m6502_.run_for(cycles); | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		void tape_did_change_interrupt_status(Tape *tape) override final { | ||||||
|  | 			interrupt_status_ = (interrupt_status_ & ~(Interrupt::TransmitDataEmpty | Interrupt::ReceiveDataFull | Interrupt::HighToneDetect)) | tape_.get_interrupt_status(); | ||||||
|  | 			evaluate_interrupts(); | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		HalfCycles get_typer_delay() override final { | ||||||
|  | 			return m6502_.get_is_resetting() ? Cycles(625*25*128) : Cycles(0);	// wait one second if resetting | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		HalfCycles get_typer_frequency() override final { | ||||||
|  | 			return Cycles(625*128*2);	// accept a new character every two frames | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		void type_string(const std::string &string) override final { | ||||||
|  | 			std::unique_ptr<CharacterMapper> mapper(new CharacterMapper()); | ||||||
|  | 			Utility::TypeRecipient::add_typer(string, std::move(mapper)); | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		KeyboardMapper &get_keyboard_mapper() override { | ||||||
|  | 			return keyboard_mapper_; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		// MARK: - Configuration options. | ||||||
|  | 		std::vector<std::unique_ptr<Configurable::Option>> get_options() override { | ||||||
|  | 			return Electron::get_options(); | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		void set_selections(const Configurable::SelectionSet &selections_by_option) override { | ||||||
|  | 			bool quickload; | ||||||
|  | 			if(Configurable::get_quick_load_tape(selections_by_option, quickload)) { | ||||||
|  | 				set_use_fast_tape_hack(quickload); | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			Configurable::Display display; | ||||||
|  | 			if(Configurable::get_display(selections_by_option, display)) { | ||||||
|  | 				get_crt()->set_output_device((display == Configurable::Display::RGB) ? Outputs::CRT::OutputDevice::Monitor : Outputs::CRT::OutputDevice::Television); | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		Configurable::SelectionSet get_accurate_selections() override { | ||||||
|  | 			Configurable::SelectionSet selection_set; | ||||||
|  | 			Configurable::append_quick_load_tape_selection(selection_set, false); | ||||||
|  | 			Configurable::append_display_selection(selection_set, Configurable::Display::Composite); | ||||||
|  | 			return selection_set; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		Configurable::SelectionSet get_user_friendly_selections() override { | ||||||
|  | 			Configurable::SelectionSet selection_set; | ||||||
|  | 			Configurable::append_quick_load_tape_selection(selection_set, true); | ||||||
|  | 			Configurable::append_display_selection(selection_set, Configurable::Display::RGB); | ||||||
|  | 			return selection_set; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 	private: | ||||||
|  | 		// MARK: - Work deferral updates. | ||||||
|  | 		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()); | ||||||
| 			} | 			} | ||||||
| } | 		} | ||||||
|  |  | ||||||
| inline void Machine::queue_next_display_interrupt() { | 		inline void queue_next_display_interrupt() { | ||||||
| 			VideoOutput::Interrupt next_interrupt = video_output_->get_next_interrupt(); | 			VideoOutput::Interrupt next_interrupt = video_output_->get_next_interrupt(); | ||||||
| 			cycles_until_display_interrupt_ = next_interrupt.cycles; | 			cycles_until_display_interrupt_ = next_interrupt.cycles; | ||||||
| 			next_display_interrupt_ = next_interrupt.interrupt; | 			next_display_interrupt_ = next_interrupt.interrupt; | ||||||
| } |  | ||||||
|  |  | ||||||
| inline void Machine::update_audio() { |  | ||||||
| 	if(cycles_since_audio_update_ > 0) { |  | ||||||
| 		speaker_->run_for(cycles_since_audio_update_.divide(Cycles(Speaker::clock_rate_divider))); |  | ||||||
| 		} | 		} | ||||||
| } |  | ||||||
|  |  | ||||||
| #pragma mark - Interrupts | 		inline void update_audio() { | ||||||
|  | 			speaker_.run_for(audio_queue_, cycles_since_audio_update_.divide(Cycles(SoundGenerator::clock_rate_divider))); | ||||||
|  | 		} | ||||||
|  |  | ||||||
| inline void Machine::signal_interrupt(Electron::Interrupt interrupt) { | 		inline void signal_interrupt(Interrupt interrupt) { | ||||||
| 			interrupt_status_ |= interrupt; | 			interrupt_status_ |= interrupt; | ||||||
| 			evaluate_interrupts(); | 			evaluate_interrupts(); | ||||||
| } | 		} | ||||||
|  |  | ||||||
| inline void Machine::clear_interrupt(Electron::Interrupt interrupt) { | 		inline void clear_interrupt(Interrupt interrupt) { | ||||||
| 			interrupt_status_ &= ~interrupt; | 			interrupt_status_ &= ~interrupt; | ||||||
| 			evaluate_interrupts(); | 			evaluate_interrupts(); | ||||||
| } | 		} | ||||||
|  |  | ||||||
| inline void Machine::evaluate_interrupts() { | 		inline void evaluate_interrupts() { | ||||||
| 			if(interrupt_status_ & interrupt_control_) { | 			if(interrupt_status_ & interrupt_control_) { | ||||||
| 				interrupt_status_ |= 1; | 				interrupt_status_ |= 1; | ||||||
| 			} else { | 			} else { | ||||||
| 				interrupt_status_ &= ~1; | 				interrupt_status_ &= ~1; | ||||||
| 			} | 			} | ||||||
| 	set_irq_line(interrupt_status_ & 1); | 			m6502_.set_irq_line(interrupt_status_ & 1); | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		CPU::MOS6502::Processor<ConcreteMachine, false> m6502_; | ||||||
|  |  | ||||||
|  | 		// Things that directly constitute the memory map. | ||||||
|  | 		uint8_t roms_[16][16384]; | ||||||
|  | 		bool rom_write_masks_[16] = {false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false}; | ||||||
|  | 		uint8_t os_[16384], ram_[32768]; | ||||||
|  | 		std::vector<uint8_t> dfs_, adfs1_, adfs2_; | ||||||
|  |  | ||||||
|  | 		// Paging | ||||||
|  | 		ROMSlot active_rom_ = ROMSlot::ROMSlot0; | ||||||
|  | 		bool keyboard_is_active_ = false; | ||||||
|  | 		bool basic_is_active_ = false; | ||||||
|  |  | ||||||
|  | 		// Interrupt and keyboard state | ||||||
|  | 		uint8_t interrupt_status_ = Interrupt::PowerOnReset | Interrupt::TransmitDataEmpty | 0x80; | ||||||
|  | 		uint8_t interrupt_control_ = 0; | ||||||
|  | 		uint8_t key_states_[14]; | ||||||
|  | 		Electron::KeyboardMapper keyboard_mapper_; | ||||||
|  |  | ||||||
|  | 		// Counters related to simultaneous subsystems | ||||||
|  | 		Cycles cycles_since_display_update_ = 0; | ||||||
|  | 		Cycles cycles_since_audio_update_ = 0; | ||||||
|  | 		int cycles_until_display_interrupt_ = 0; | ||||||
|  | 		Interrupt next_display_interrupt_ = Interrupt::RealTimeClock; | ||||||
|  | 		VideoOutput::Range video_access_range_ = {0, 0xffff}; | ||||||
|  |  | ||||||
|  | 		// Tape | ||||||
|  | 		Tape tape_; | ||||||
|  | 		bool use_fast_tape_hack_ = false; | ||||||
|  | 		bool fast_load_is_in_data_ = false; | ||||||
|  |  | ||||||
|  | 		// Disk | ||||||
|  | 		std::unique_ptr<Plus3> plus3_; | ||||||
|  | 		bool is_holding_shift_ = false; | ||||||
|  | 		int shift_restart_counter_ = 0; | ||||||
|  |  | ||||||
|  | 		// Outputs | ||||||
|  | 		std::unique_ptr<VideoOutput> video_output_; | ||||||
|  |  | ||||||
|  | 		Concurrency::DeferringAsyncTaskQueue audio_queue_; | ||||||
|  | 		SoundGenerator sound_generator_; | ||||||
|  | 		Outputs::Speaker::LowpassSpeaker<SoundGenerator> speaker_; | ||||||
|  |  | ||||||
|  | 		bool speaker_is_enabled_ = false; | ||||||
|  | }; | ||||||
|  |  | ||||||
| } | } | ||||||
|  |  | ||||||
| #pragma mark - Tape::Delegate | using namespace Electron; | ||||||
|  |  | ||||||
| void Machine::tape_did_change_interrupt_status(Tape *tape) { | Machine *Machine::Electron() { | ||||||
| 	interrupt_status_ = (interrupt_status_ & ~(Interrupt::TransmitDataEmpty | Interrupt::ReceiveDataFull | Interrupt::HighToneDetect)) | tape_.get_interrupt_status(); | 	return new Electron::ConcreteMachine; | ||||||
| 	evaluate_interrupts(); |  | ||||||
| } | } | ||||||
|  |  | ||||||
| #pragma mark - Typer timing | Machine::~Machine() {} | ||||||
|  |  | ||||||
| HalfCycles Electron::Machine::get_typer_delay() { |  | ||||||
| 	return get_is_resetting() ? Cycles(625*25*128) : Cycles(0);	// wait one second if resetting |  | ||||||
| } |  | ||||||
|  |  | ||||||
| HalfCycles Electron::Machine::get_typer_frequency() { |  | ||||||
| 	return Cycles(625*128*2);	// accept a new character every two frames |  | ||||||
| } |  | ||||||
|   | |||||||
| @@ -9,19 +9,10 @@ | |||||||
| #ifndef Electron_hpp | #ifndef Electron_hpp | ||||||
| #define Electron_hpp | #define Electron_hpp | ||||||
|  |  | ||||||
| #include "../../Processors/6502/6502.hpp" | #include "../../Configurable/Configurable.hpp" | ||||||
| #include "../../Storage/Tape/Tape.hpp" |  | ||||||
| #include "../../ClockReceiver/ClockReceiver.hpp" |  | ||||||
|  |  | ||||||
| #include "../ConfigurationTarget.hpp" | #include "../ConfigurationTarget.hpp" | ||||||
| #include "../CRTMachine.hpp" | #include "../CRTMachine.hpp" | ||||||
| #include "../Typer.hpp" | #include "../KeyboardMachine.hpp" | ||||||
|  |  | ||||||
| #include "Interrupts.hpp" |  | ||||||
| #include "Plus3.hpp" |  | ||||||
| #include "Speaker.hpp" |  | ||||||
| #include "Tape.hpp" |  | ||||||
| #include "Video.hpp" |  | ||||||
|  |  | ||||||
| #include <cstdint> | #include <cstdint> | ||||||
| #include <vector> | #include <vector> | ||||||
| @@ -38,27 +29,12 @@ enum ROMSlot: uint8_t { | |||||||
|  |  | ||||||
| 	ROMSlot12,	ROMSlot13,	ROMSlot14,	ROMSlot15, | 	ROMSlot12,	ROMSlot13,	ROMSlot14,	ROMSlot15, | ||||||
|  |  | ||||||
| 	ROMSlotOS,	ROMSlotDFS,	ROMSlotADFS | 	ROMSlotOS,		ROMSlotDFS, | ||||||
|  | 	ROMSlotADFS1,	ROMSlotADFS2 | ||||||
| }; | }; | ||||||
|  |  | ||||||
| enum Key: uint16_t { | /// @returns The options available for an Electron. | ||||||
| 	KeySpace		= 0x0000 | 0x08,										KeyCopy			= 0x0000 | 0x02,	KeyRight		= 0x0000 | 0x01, | std::vector<std::unique_ptr<Configurable::Option>> get_options(); | ||||||
| 	KeyDelete		= 0x0010 | 0x08,	KeyReturn		= 0x0010 | 0x04,	KeyDown			= 0x0010 | 0x02,	KeyLeft			= 0x0010 | 0x01, |  | ||||||
| 										KeyColon		= 0x0020 | 0x04,	KeyUp			= 0x0020 | 0x02,	KeyMinus		= 0x0020 | 0x01, |  | ||||||
| 	KeySlash		= 0x0030 | 0x08,	KeySemiColon	= 0x0030 | 0x04,	KeyP			= 0x0030 | 0x02,	Key0			= 0x0030 | 0x01, |  | ||||||
| 	KeyFullStop		= 0x0040 | 0x08,	KeyL			= 0x0040 | 0x04,	KeyO			= 0x0040 | 0x02,	Key9			= 0x0040 | 0x01, |  | ||||||
| 	KeyComma		= 0x0050 | 0x08,	KeyK			= 0x0050 | 0x04,	KeyI			= 0x0050 | 0x02,	Key8			= 0x0050 | 0x01, |  | ||||||
| 	KeyM			= 0x0060 | 0x08,	KeyJ			= 0x0060 | 0x04,	KeyU			= 0x0060 | 0x02,	Key7			= 0x0060 | 0x01, |  | ||||||
| 	KeyN			= 0x0070 | 0x08,	KeyH			= 0x0070 | 0x04,	KeyY			= 0x0070 | 0x02,	Key6			= 0x0070 | 0x01, |  | ||||||
| 	KeyB			= 0x0080 | 0x08,	KeyG			= 0x0080 | 0x04,	KeyT			= 0x0080 | 0x02,	Key5			= 0x0080 | 0x01, |  | ||||||
| 	KeyV			= 0x0090 | 0x08,	KeyF			= 0x0090 | 0x04,	KeyR			= 0x0090 | 0x02,	Key4			= 0x0090 | 0x01, |  | ||||||
| 	KeyC			= 0x00a0 | 0x08,	KeyD			= 0x00a0 | 0x04,	KeyE			= 0x00a0 | 0x02,	Key3			= 0x00a0 | 0x01, |  | ||||||
| 	KeyX			= 0x00b0 | 0x08,	KeyS			= 0x00b0 | 0x04,	KeyW			= 0x00b0 | 0x02,	Key2			= 0x00b0 | 0x01, |  | ||||||
| 	KeyZ			= 0x00c0 | 0x08,	KeyA			= 0x00c0 | 0x04,	KeyQ			= 0x00c0 | 0x02,	Key1			= 0x00c0 | 0x01, |  | ||||||
| 	KeyShift		= 0x00d0 | 0x08,	KeyControl		= 0x00d0 | 0x04,	KeyFunc			= 0x00d0 | 0x02,	KeyEscape		= 0x00d0 | 0x01, |  | ||||||
|  |  | ||||||
| 	KeyBreak		= 0xfffd, |  | ||||||
| }; |  | ||||||
|  |  | ||||||
| /*! | /*! | ||||||
| 	@abstract Represents an Acorn Electron. | 	@abstract Represents an Acorn Electron. | ||||||
| @@ -67,88 +43,21 @@ enum Key: uint16_t { | |||||||
| 	Acorn Electron. | 	Acorn Electron. | ||||||
| */ | */ | ||||||
| class Machine: | class Machine: | ||||||
| 	public CPU::MOS6502::Processor<Machine>, |  | ||||||
| 	public CRTMachine::Machine, | 	public CRTMachine::Machine, | ||||||
| 	public Tape::Delegate, | 	public ConfigurationTarget::Machine, | ||||||
| 	public Utility::TypeRecipient, | 	public KeyboardMachine::Machine, | ||||||
| 	public ConfigurationTarget::Machine { | 	public Configurable::Device { | ||||||
|  |  | ||||||
| 	public: | 	public: | ||||||
| 		Machine(); | 		virtual ~Machine(); | ||||||
|  |  | ||||||
| 		void set_rom(ROMSlot slot, std::vector<uint8_t> data, bool is_writeable); | 		/// Creates and returns an Electron. | ||||||
|  | 		static Machine *Electron(); | ||||||
|  |  | ||||||
| 		void set_key_state(uint16_t key, bool isPressed); | 		/*! | ||||||
| 		void clear_all_keys(); | 			Sets the contents of @c slot to @c data. If @c is_writeable is @c true then writing to the slot | ||||||
|  | 			is enabled — it acts as if it were sideways RAM. Otherwise the slot is modelled as containing ROM. | ||||||
| 		inline void set_use_fast_tape_hack(bool activate) { use_fast_tape_hack_ = activate; } | 		*/ | ||||||
|  | 		virtual void set_rom(ROMSlot slot, const std::vector<uint8_t> &data, bool is_writeable) = 0; | ||||||
| 		// to satisfy ConfigurationTarget::Machine |  | ||||||
| 		void configure_as_target(const StaticAnalyser::Target &target); |  | ||||||
|  |  | ||||||
| 		// to satisfy CPU::MOS6502::Processor |  | ||||||
| 		Cycles perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value); |  | ||||||
| 		void flush(); |  | ||||||
|  |  | ||||||
| 		// to satisfy CRTMachine::Machine |  | ||||||
| 		virtual void setup_output(float aspect_ratio); |  | ||||||
| 		virtual void close_output(); |  | ||||||
| 		virtual std::shared_ptr<Outputs::CRT::CRT> get_crt(); |  | ||||||
| 		virtual std::shared_ptr<Outputs::Speaker> get_speaker(); |  | ||||||
| 		virtual void run_for(const Cycles cycles) { CPU::MOS6502::Processor<Machine>::run_for(cycles); } |  | ||||||
|  |  | ||||||
| 		// to satisfy Tape::Delegate |  | ||||||
| 		virtual void tape_did_change_interrupt_status(Tape *tape); |  | ||||||
|  |  | ||||||
| 		// for Utility::TypeRecipient |  | ||||||
| 		virtual HalfCycles get_typer_delay(); |  | ||||||
| 		virtual HalfCycles get_typer_frequency(); |  | ||||||
| 		virtual void set_typer_for_string(const char *string); |  | ||||||
|  |  | ||||||
| 	private: |  | ||||||
| 		inline void update_display(); |  | ||||||
| 		inline void queue_next_display_interrupt(); |  | ||||||
| 		inline void update_audio(); |  | ||||||
|  |  | ||||||
| 		inline void signal_interrupt(Interrupt interrupt); |  | ||||||
| 		inline void clear_interrupt(Interrupt interrupt); |  | ||||||
| 		inline void evaluate_interrupts(); |  | ||||||
|  |  | ||||||
| 		// Things that directly constitute the memory map. |  | ||||||
| 		uint8_t roms_[16][16384]; |  | ||||||
| 		bool rom_write_masks_[16]; |  | ||||||
| 		uint8_t os_[16384], ram_[32768]; |  | ||||||
| 		std::vector<uint8_t> dfs_, adfs_; |  | ||||||
|  |  | ||||||
| 		// Paging |  | ||||||
| 		ROMSlot active_rom_; |  | ||||||
| 		bool keyboard_is_active_, basic_is_active_; |  | ||||||
|  |  | ||||||
| 		// Interrupt and keyboard state |  | ||||||
| 		uint8_t interrupt_status_, interrupt_control_; |  | ||||||
| 		uint8_t key_states_[14]; |  | ||||||
|  |  | ||||||
| 		// Counters related to simultaneous subsystems |  | ||||||
| 		Cycles cycles_since_display_update_; |  | ||||||
| 		Cycles cycles_since_audio_update_; |  | ||||||
| 		int cycles_until_display_interrupt_; |  | ||||||
| 		Interrupt next_display_interrupt_; |  | ||||||
| 		VideoOutput::Range video_access_range_; |  | ||||||
|  |  | ||||||
| 		// Tape |  | ||||||
| 		Tape tape_; |  | ||||||
| 		bool use_fast_tape_hack_; |  | ||||||
| 		bool fast_load_is_in_data_; |  | ||||||
|  |  | ||||||
| 		// Disk |  | ||||||
| 		std::unique_ptr<Plus3> plus3_; |  | ||||||
| 		bool is_holding_shift_; |  | ||||||
| 		int shift_restart_counter_; |  | ||||||
|  |  | ||||||
| 		// Outputs |  | ||||||
| 		std::unique_ptr<VideoOutput> video_output_; |  | ||||||
| 		std::shared_ptr<Speaker> speaker_; |  | ||||||
| 		bool speaker_is_enabled_; |  | ||||||
| }; | }; | ||||||
|  |  | ||||||
| } | } | ||||||
|   | |||||||
| @@ -1,21 +1,65 @@ | |||||||
| //
 | //
 | ||||||
| //  CharacterMapper.cpp
 | //  Keyboard.cpp
 | ||||||
| //  Clock Signal
 | //  Clock Signal
 | ||||||
| //
 | //
 | ||||||
| //  Created by Thomas Harte on 03/08/2017.
 | //  Created by Thomas Harte on 10/10/2017.
 | ||||||
| //  Copyright © 2017 Thomas Harte. All rights reserved.
 | //  Copyright © 2017 Thomas Harte. All rights reserved.
 | ||||||
| //
 | //
 | ||||||
| 
 | 
 | ||||||
| #include "CharacterMapper.hpp" | #include "Keyboard.hpp" | ||||||
| #include "Electron.hpp" |  | ||||||
| 
 | 
 | ||||||
| using namespace Electron; | using namespace Electron; | ||||||
| 
 | 
 | ||||||
|  | uint16_t KeyboardMapper::mapped_key_for_key(Inputs::Keyboard::Key key) { | ||||||
|  | #define BIND(source, dest)	case Inputs::Keyboard::Key::source:	return Electron::Key::dest | ||||||
|  | 	switch(key) { | ||||||
|  | 		default: return KeyCopy; | ||||||
|  | 
 | ||||||
|  | 		BIND(k0, Key0);		BIND(k1, Key1);		BIND(k2, Key2);		BIND(k3, Key3);		BIND(k4, Key4); | ||||||
|  | 		BIND(k5, Key5);		BIND(k6, Key6);		BIND(k7, Key7);		BIND(k8, Key8);		BIND(k9, Key9); | ||||||
|  | 		BIND(Q, KeyQ);		BIND(W, KeyW);		BIND(E, KeyE);		BIND(R, KeyR);		BIND(T, KeyT); | ||||||
|  | 		BIND(Y, KeyY);		BIND(U, KeyU);		BIND(I, KeyI);		BIND(O, KeyO);		BIND(P, KeyP); | ||||||
|  | 		BIND(A, KeyA);		BIND(S, KeyS);		BIND(D, KeyD);		BIND(F, KeyF);		BIND(G, KeyG); | ||||||
|  | 		BIND(H, KeyH);		BIND(J, KeyJ);		BIND(K, KeyK);		BIND(L, KeyL); | ||||||
|  | 		BIND(Z, KeyZ);		BIND(X, KeyX);		BIND(C, KeyC);		BIND(V, KeyV); | ||||||
|  | 		BIND(B, KeyB);		BIND(N, KeyN);		BIND(M, KeyM); | ||||||
|  | 
 | ||||||
|  | 		BIND(Comma, KeyComma); | ||||||
|  | 		BIND(FullStop, KeyFullStop); | ||||||
|  | 		BIND(ForwardSlash, KeySlash); | ||||||
|  | 		BIND(Semicolon, KeySemiColon); | ||||||
|  | 		BIND(Quote, KeyColon); | ||||||
|  | 
 | ||||||
|  | 		BIND(Escape, KeyEscape); | ||||||
|  | 		BIND(Equals, KeyBreak); | ||||||
|  | 		BIND(F12, KeyBreak); | ||||||
|  | 
 | ||||||
|  | 		BIND(Left, KeyLeft);	BIND(Right, KeyRight);		BIND(Up, KeyUp);		BIND(Down, KeyDown); | ||||||
|  | 
 | ||||||
|  | 		BIND(Tab, KeyFunc);				BIND(LeftOption, KeyFunc);		BIND(RightOption, KeyFunc); | ||||||
|  | 		BIND(LeftMeta, KeyFunc);		BIND(RightMeta, KeyFunc); | ||||||
|  | 		BIND(CapsLock, KeyControl);		BIND(LeftControl, KeyControl);	BIND(RightControl, KeyControl); | ||||||
|  | 		BIND(LeftShift, KeyShift);		BIND(RightShift, KeyShift); | ||||||
|  | 
 | ||||||
|  | 		BIND(Hyphen, KeyMinus); | ||||||
|  | 		BIND(Delete, KeyDelete);		BIND(BackSpace, KeyDelete); | ||||||
|  | 		BIND(Enter, KeyReturn);			BIND(KeyPadEnter, KeyReturn); | ||||||
|  | 
 | ||||||
|  | 		BIND(KeyPad0, Key0);		BIND(KeyPad1, Key1);		BIND(KeyPad2, Key2);		BIND(KeyPad3, Key3);		BIND(KeyPad4, Key4); | ||||||
|  | 		BIND(KeyPad5, Key5);		BIND(KeyPad6, Key6);		BIND(KeyPad7, Key7);		BIND(KeyPad8, Key8);		BIND(KeyPad9, Key9); | ||||||
|  | 
 | ||||||
|  | 		BIND(KeyPadMinus, KeyMinus);			BIND(KeyPadPlus, KeyColon); | ||||||
|  | 
 | ||||||
|  | 		BIND(Space, KeySpace); | ||||||
|  | 	} | ||||||
|  | #undef BIND | ||||||
|  | } | ||||||
|  | 
 | ||||||
| uint16_t *CharacterMapper::sequence_for_character(char character) { | uint16_t *CharacterMapper::sequence_for_character(char character) { | ||||||
| #define KEYS(...)	{__VA_ARGS__, EndSequence} | #define KEYS(...)	{__VA_ARGS__, KeyboardMachine::Machine::KeyEndSequence} | ||||||
| #define SHIFT(...)	{KeyShift, __VA_ARGS__, EndSequence} | #define SHIFT(...)	{KeyShift, __VA_ARGS__, KeyboardMachine::Machine::KeyEndSequence} | ||||||
| #define CTRL(...)	{KeyControl, __VA_ARGS__, EndSequence} | #define CTRL(...)	{KeyControl, __VA_ARGS__, KeyboardMachine::Machine::KeyEndSequence} | ||||||
| #define X			{NotMapped} | #define X			{KeyboardMachine::Machine::KeyNotMapped} | ||||||
| 	static KeySequence key_sequences[] = { | 	static KeySequence key_sequences[] = { | ||||||
| 		/* NUL */	X,							/* SOH */	X, | 		/* NUL */	X,							/* SOH */	X, | ||||||
| 		/* STX */	X,							/* ETX */	X, | 		/* STX */	X,							/* ETX */	X, | ||||||
							
								
								
									
										46
									
								
								Machines/Electron/Keyboard.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										46
									
								
								Machines/Electron/Keyboard.hpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,46 @@ | |||||||
|  | // | ||||||
|  | //  Keyboard.hpp | ||||||
|  | //  Clock Signal | ||||||
|  | // | ||||||
|  | //  Created by Thomas Harte on 10/10/2017. | ||||||
|  | //  Copyright © 2017 Thomas Harte. All rights reserved. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | #ifndef Machines_Electron_Keyboard_hpp | ||||||
|  | #define Machines_Electron_Keyboard_hpp | ||||||
|  |  | ||||||
|  | #include "../KeyboardMachine.hpp" | ||||||
|  | #include "../Utility/Typer.hpp" | ||||||
|  |  | ||||||
|  | namespace Electron { | ||||||
|  |  | ||||||
|  | enum Key: uint16_t { | ||||||
|  | 	KeySpace		= 0x0000 | 0x08,										KeyCopy			= 0x0000 | 0x02,	KeyRight		= 0x0000 | 0x01, | ||||||
|  | 	KeyDelete		= 0x0010 | 0x08,	KeyReturn		= 0x0010 | 0x04,	KeyDown			= 0x0010 | 0x02,	KeyLeft			= 0x0010 | 0x01, | ||||||
|  | 										KeyColon		= 0x0020 | 0x04,	KeyUp			= 0x0020 | 0x02,	KeyMinus		= 0x0020 | 0x01, | ||||||
|  | 	KeySlash		= 0x0030 | 0x08,	KeySemiColon	= 0x0030 | 0x04,	KeyP			= 0x0030 | 0x02,	Key0			= 0x0030 | 0x01, | ||||||
|  | 	KeyFullStop		= 0x0040 | 0x08,	KeyL			= 0x0040 | 0x04,	KeyO			= 0x0040 | 0x02,	Key9			= 0x0040 | 0x01, | ||||||
|  | 	KeyComma		= 0x0050 | 0x08,	KeyK			= 0x0050 | 0x04,	KeyI			= 0x0050 | 0x02,	Key8			= 0x0050 | 0x01, | ||||||
|  | 	KeyM			= 0x0060 | 0x08,	KeyJ			= 0x0060 | 0x04,	KeyU			= 0x0060 | 0x02,	Key7			= 0x0060 | 0x01, | ||||||
|  | 	KeyN			= 0x0070 | 0x08,	KeyH			= 0x0070 | 0x04,	KeyY			= 0x0070 | 0x02,	Key6			= 0x0070 | 0x01, | ||||||
|  | 	KeyB			= 0x0080 | 0x08,	KeyG			= 0x0080 | 0x04,	KeyT			= 0x0080 | 0x02,	Key5			= 0x0080 | 0x01, | ||||||
|  | 	KeyV			= 0x0090 | 0x08,	KeyF			= 0x0090 | 0x04,	KeyR			= 0x0090 | 0x02,	Key4			= 0x0090 | 0x01, | ||||||
|  | 	KeyC			= 0x00a0 | 0x08,	KeyD			= 0x00a0 | 0x04,	KeyE			= 0x00a0 | 0x02,	Key3			= 0x00a0 | 0x01, | ||||||
|  | 	KeyX			= 0x00b0 | 0x08,	KeyS			= 0x00b0 | 0x04,	KeyW			= 0x00b0 | 0x02,	Key2			= 0x00b0 | 0x01, | ||||||
|  | 	KeyZ			= 0x00c0 | 0x08,	KeyA			= 0x00c0 | 0x04,	KeyQ			= 0x00c0 | 0x02,	Key1			= 0x00c0 | 0x01, | ||||||
|  | 	KeyShift		= 0x00d0 | 0x08,	KeyControl		= 0x00d0 | 0x04,	KeyFunc			= 0x00d0 | 0x02,	KeyEscape		= 0x00d0 | 0x01, | ||||||
|  |  | ||||||
|  | 	KeyBreak		= 0xfffd, | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | struct KeyboardMapper: public KeyboardMachine::Machine::KeyboardMapper { | ||||||
|  | 	uint16_t mapped_key_for_key(Inputs::Keyboard::Key key); | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | struct CharacterMapper: public ::Utility::CharacterMapper { | ||||||
|  | 	uint16_t *sequence_for_character(char character); | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | #endif /* KeyboardMapper_hpp */ | ||||||
| @@ -10,13 +10,13 @@ | |||||||
|  |  | ||||||
| using namespace Electron; | using namespace Electron; | ||||||
|  |  | ||||||
| Plus3::Plus3() : WD1770(P1770), last_control_(0) { | Plus3::Plus3() : WD1770(P1770) { | ||||||
| 	set_control_register(last_control_, 0xff); | 	set_control_register(last_control_, 0xff); | ||||||
| } | } | ||||||
|  |  | ||||||
| void Plus3::set_disk(std::shared_ptr<Storage::Disk::Disk> disk, int drive) { | void Plus3::set_disk(std::shared_ptr<Storage::Disk::Disk> disk, int drive) { | ||||||
| 	if(!drives_[drive]) { | 	if(!drives_[drive]) { | ||||||
| 		drives_[drive].reset(new Storage::Disk::Drive); | 		drives_[drive].reset(new Storage::Disk::Drive(8000000, 300, 2)); | ||||||
| 		if(drive == selected_drive_) set_drive(drives_[drive]); | 		if(drive == selected_drive_) set_drive(drives_[drive]); | ||||||
| 	} | 	} | ||||||
| 	drives_[drive]->set_disk(disk); | 	drives_[drive]->set_disk(disk); | ||||||
| @@ -42,9 +42,14 @@ void Plus3::set_control_register(uint8_t control, uint8_t changes) { | |||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 	if(changes & 0x04) { | 	if(changes & 0x04) { | ||||||
| 		invalidate_track(); |  | ||||||
| 		if(drives_[0]) drives_[0]->set_head((control & 0x04) ? 1 : 0); | 		if(drives_[0]) drives_[0]->set_head((control & 0x04) ? 1 : 0); | ||||||
| 		if(drives_[1]) drives_[1]->set_head((control & 0x04) ? 1 : 0); | 		if(drives_[1]) drives_[1]->set_head((control & 0x04) ? 1 : 0); | ||||||
| 	} | 	} | ||||||
| 	if(changes & 0x08) set_is_double_density(!(control & 0x08)); | 	if(changes & 0x08) set_is_double_density(!(control & 0x08)); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | void Plus3::set_motor_on(bool on) { | ||||||
|  | 	// TODO: this status should transfer if the selected drive changes. But the same goes for | ||||||
|  | 	// writing state, so plenty of work to do in general here. | ||||||
|  | 	get_drive().set_motor_on(on); | ||||||
|  | } | ||||||
|   | |||||||
| @@ -23,8 +23,10 @@ class Plus3 : public WD::WD1770 { | |||||||
| 	private: | 	private: | ||||||
| 		void set_control_register(uint8_t control, uint8_t changes); | 		void set_control_register(uint8_t control, uint8_t changes); | ||||||
| 		std::shared_ptr<Storage::Disk::Drive> drives_[2]; | 		std::shared_ptr<Storage::Disk::Drive> drives_[2]; | ||||||
| 		int selected_drive_; | 		int selected_drive_ = 0; | ||||||
| 		uint8_t last_control_; | 		uint8_t last_control_ = 0; | ||||||
|  |  | ||||||
|  | 		void set_motor_on(bool on); | ||||||
| }; | }; | ||||||
|  |  | ||||||
| } | } | ||||||
|   | |||||||
							
								
								
									
										45
									
								
								Machines/Electron/SoundGenerator.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										45
									
								
								Machines/Electron/SoundGenerator.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,45 @@ | |||||||
|  | // | ||||||
|  | //  Speaker.cpp | ||||||
|  | //  Clock Signal | ||||||
|  | // | ||||||
|  | //  Created by Thomas Harte on 03/12/2016. | ||||||
|  | //  Copyright © 2016 Thomas Harte. All rights reserved. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | #include "SoundGenerator.hpp" | ||||||
|  |  | ||||||
|  | #include <cstring> | ||||||
|  |  | ||||||
|  | using namespace Electron; | ||||||
|  |  | ||||||
|  | SoundGenerator::SoundGenerator(Concurrency::DeferringAsyncTaskQueue &audio_queue) : | ||||||
|  | 	audio_queue_(audio_queue) {} | ||||||
|  |  | ||||||
|  | void SoundGenerator::get_samples(std::size_t number_of_samples, int16_t *target) { | ||||||
|  | 	if(is_enabled_) { | ||||||
|  | 		while(number_of_samples--) { | ||||||
|  | 			*target = static_cast<int16_t>((counter_ / (divider_+1)) * 8192); | ||||||
|  | 			target++; | ||||||
|  | 			counter_ = (counter_ + 1) % ((divider_+1) * 2); | ||||||
|  | 		} | ||||||
|  | 	} else { | ||||||
|  | 		std::memset(target, 0, sizeof(int16_t) * number_of_samples); | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void SoundGenerator::skip_samples(std::size_t number_of_samples) { | ||||||
|  | 	counter_ = (counter_ + number_of_samples) % ((divider_+1) * 2); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void SoundGenerator::set_divider(uint8_t divider) { | ||||||
|  | 	audio_queue_.defer([=]() { | ||||||
|  | 		divider_ = divider * 32 / clock_rate_divider; | ||||||
|  | 	}); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void SoundGenerator::set_is_enabled(bool is_enabled) { | ||||||
|  | 	audio_queue_.defer([=]() { | ||||||
|  | 		is_enabled_ = is_enabled; | ||||||
|  | 		counter_ = 0; | ||||||
|  | 	}); | ||||||
|  | } | ||||||
							
								
								
									
										39
									
								
								Machines/Electron/SoundGenerator.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										39
									
								
								Machines/Electron/SoundGenerator.hpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,39 @@ | |||||||
|  | // | ||||||
|  | //  SoundGenerator.hpp | ||||||
|  | //  Clock Signal | ||||||
|  | // | ||||||
|  | //  Created by Thomas Harte on 03/12/2016. | ||||||
|  | //  Copyright © 2016 Thomas Harte. All rights reserved. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | #ifndef Electron_SoundGenerator_hpp | ||||||
|  | #define Electron_SoundGenerator_hpp | ||||||
|  |  | ||||||
|  | #include "../../Outputs/Speaker/Implementation/SampleSource.hpp" | ||||||
|  | #include "../../Concurrency/AsyncTaskQueue.hpp" | ||||||
|  |  | ||||||
|  | namespace Electron { | ||||||
|  |  | ||||||
|  | class SoundGenerator: public ::Outputs::Speaker::SampleSource { | ||||||
|  | 	public: | ||||||
|  | 		SoundGenerator(Concurrency::DeferringAsyncTaskQueue &audio_queue); | ||||||
|  |  | ||||||
|  | 		void set_divider(uint8_t divider); | ||||||
|  |  | ||||||
|  | 		void set_is_enabled(bool is_enabled); | ||||||
|  |  | ||||||
|  | 		void get_samples(std::size_t number_of_samples, int16_t *target); | ||||||
|  | 		void skip_samples(std::size_t number_of_samples); | ||||||
|  |  | ||||||
|  | 		static const unsigned int clock_rate_divider = 8; | ||||||
|  |  | ||||||
|  | 	private: | ||||||
|  | 		Concurrency::DeferringAsyncTaskQueue &audio_queue_; | ||||||
|  | 		unsigned int counter_ = 0; | ||||||
|  | 		unsigned int divider_ = 0; | ||||||
|  | 		bool is_enabled_ = false; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #endif /* Speaker_hpp */ | ||||||
| @@ -1,40 +0,0 @@ | |||||||
| // |  | ||||||
| //  Speaker.cpp |  | ||||||
| //  Clock Signal |  | ||||||
| // |  | ||||||
| //  Created by Thomas Harte on 03/12/2016. |  | ||||||
| //  Copyright © 2016 Thomas Harte. All rights reserved. |  | ||||||
| // |  | ||||||
|  |  | ||||||
| #include "Speaker.hpp" |  | ||||||
|  |  | ||||||
| using namespace Electron; |  | ||||||
|  |  | ||||||
| void Speaker::get_samples(unsigned int number_of_samples, int16_t *target) { |  | ||||||
| 	if(is_enabled_) { |  | ||||||
| 		while(number_of_samples--) { |  | ||||||
| 			*target = (int16_t)((counter_ / (divider_+1)) * 8192); |  | ||||||
| 			target++; |  | ||||||
| 			counter_ = (counter_ + 1) % ((divider_+1) * 2); |  | ||||||
| 		} |  | ||||||
| 	} else { |  | ||||||
| 		memset(target, 0, sizeof(int16_t) * number_of_samples); |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void Speaker::skip_samples(unsigned int number_of_samples) { |  | ||||||
| 	counter_ = (counter_ + number_of_samples) % ((divider_+1) * 2); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void Speaker::set_divider(uint8_t divider) { |  | ||||||
| 	enqueue([=]() { |  | ||||||
| 		divider_ = divider * 32 / clock_rate_divider; |  | ||||||
| 	}); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void Speaker::set_is_enabled(bool is_enabled) { |  | ||||||
| 	enqueue([=]() { |  | ||||||
| 		is_enabled_ = is_enabled; |  | ||||||
| 		counter_ = 0; |  | ||||||
| 	}); |  | ||||||
| } |  | ||||||
| @@ -1,35 +0,0 @@ | |||||||
| // |  | ||||||
| //  Speaker.hpp |  | ||||||
| //  Clock Signal |  | ||||||
| // |  | ||||||
| //  Created by Thomas Harte on 03/12/2016. |  | ||||||
| //  Copyright © 2016 Thomas Harte. All rights reserved. |  | ||||||
| // |  | ||||||
|  |  | ||||||
| #ifndef Electron_Speaker_hpp |  | ||||||
| #define Electron_Speaker_hpp |  | ||||||
|  |  | ||||||
| #include "../../Outputs/Speaker.hpp" |  | ||||||
|  |  | ||||||
| namespace Electron { |  | ||||||
|  |  | ||||||
| class Speaker: public ::Outputs::Filter<Speaker> { |  | ||||||
| 	public: |  | ||||||
| 		void set_divider(uint8_t divider); |  | ||||||
|  |  | ||||||
| 		void set_is_enabled(bool is_enabled); |  | ||||||
|  |  | ||||||
| 		void get_samples(unsigned int number_of_samples, int16_t *target); |  | ||||||
| 		void skip_samples(unsigned int number_of_samples); |  | ||||||
|  |  | ||||||
| 		static const unsigned int clock_rate_divider = 8; |  | ||||||
|  |  | ||||||
| 	private: |  | ||||||
| 		unsigned int counter_; |  | ||||||
| 		unsigned int divider_; |  | ||||||
| 		bool is_enabled_; |  | ||||||
| }; |  | ||||||
|  |  | ||||||
| } |  | ||||||
|  |  | ||||||
| #endif /* Speaker_hpp */ |  | ||||||
| @@ -10,19 +10,12 @@ | |||||||
|  |  | ||||||
| using namespace Electron; | using namespace Electron; | ||||||
|  |  | ||||||
| Tape::Tape() : | Tape::Tape() : TapePlayer(2000000) { | ||||||
| 		TapePlayer(2000000), |  | ||||||
| 		is_running_(false), |  | ||||||
| 		data_register_(0), |  | ||||||
| 		delegate_(nullptr), |  | ||||||
| 		output_({.bits_remaining_until_empty = 0, .cycles_into_pulse = 0}), |  | ||||||
| 		last_posted_interrupt_status_(0), |  | ||||||
| 		interrupt_status_(0) { |  | ||||||
| 	shifter_.set_delegate(this); | 	shifter_.set_delegate(this); | ||||||
| } | } | ||||||
|  |  | ||||||
| void Tape::push_tape_bit(uint16_t bit) { | void Tape::push_tape_bit(uint16_t bit) { | ||||||
| 	data_register_ = (uint16_t)((data_register_ >> 1) | (bit << 10)); | 	data_register_ = static_cast<uint16_t>((data_register_ >> 1) | (bit << 10)); | ||||||
|  |  | ||||||
| 	if(input_.minimum_bits_until_full) input_.minimum_bits_until_full--; | 	if(input_.minimum_bits_until_full) input_.minimum_bits_until_full--; | ||||||
| 	if(input_.minimum_bits_until_full == 8) interrupt_status_ &= ~Interrupt::ReceiveDataFull; | 	if(input_.minimum_bits_until_full == 8) interrupt_status_ &= ~Interrupt::ReceiveDataFull; | ||||||
| @@ -64,12 +57,12 @@ void Tape::set_counter(uint8_t value) { | |||||||
| } | } | ||||||
|  |  | ||||||
| void Tape::set_data_register(uint8_t value) { | void Tape::set_data_register(uint8_t value) { | ||||||
| 	data_register_ = (uint16_t)((value << 2) | 1); | 	data_register_ = static_cast<uint16_t>((value << 2) | 1); | ||||||
| 	output_.bits_remaining_until_empty = 9; | 	output_.bits_remaining_until_empty = 9; | ||||||
| } | } | ||||||
|  |  | ||||||
| uint8_t Tape::get_data_register() { | uint8_t Tape::get_data_register() { | ||||||
| 	return (uint8_t)(data_register_ >> 2); | 	return static_cast<uint8_t>(data_register_ >> 2); | ||||||
| } | } | ||||||
|  |  | ||||||
| void Tape::process_input_pulse(const Storage::Tape::Tape::Pulse &pulse) { | void Tape::process_input_pulse(const Storage::Tape::Tape::Pulse &pulse) { | ||||||
| @@ -77,7 +70,7 @@ void Tape::process_input_pulse(const Storage::Tape::Tape::Pulse &pulse) { | |||||||
| } | } | ||||||
|  |  | ||||||
| void Tape::acorn_shifter_output_bit(int value) { | void Tape::acorn_shifter_output_bit(int value) { | ||||||
| 	push_tape_bit((uint16_t)value); | 	push_tape_bit(static_cast<uint16_t>(value)); | ||||||
| } | } | ||||||
|  |  | ||||||
| void Tape::run_for(const Cycles cycles) { | void Tape::run_for(const Cycles cycles) { | ||||||
| @@ -87,7 +80,7 @@ void Tape::run_for(const Cycles cycles) { | |||||||
| 				TapePlayer::run_for(cycles); | 				TapePlayer::run_for(cycles); | ||||||
| 			} | 			} | ||||||
| 		} else { | 		} else { | ||||||
| 			output_.cycles_into_pulse += (unsigned int)cycles.as_int(); | 			output_.cycles_into_pulse += static_cast<unsigned int>(cycles.as_int()); | ||||||
| 			while(output_.cycles_into_pulse > 1664) {	// 1664 = the closest you can get to 1200 baud if you're looking for something | 			while(output_.cycles_into_pulse > 1664) {	// 1664 = the closest you can get to 1200 baud if you're looking for something | ||||||
| 				output_.cycles_into_pulse -= 1664;		// that divides the 125,000Hz clock that the sound divider runs off. | 				output_.cycles_into_pulse -= 1664;		// that divides the 125,000Hz clock that the sound divider runs off. | ||||||
| 				push_tape_bit(1); | 				push_tape_bit(1); | ||||||
|   | |||||||
| @@ -52,22 +52,23 @@ class Tape: | |||||||
| 		inline void get_next_tape_pulse(); | 		inline void get_next_tape_pulse(); | ||||||
|  |  | ||||||
| 		struct { | 		struct { | ||||||
| 			int minimum_bits_until_full; | 			int minimum_bits_until_full = 0; | ||||||
| 		} input_; | 		} input_; | ||||||
| 		struct { | 		struct { | ||||||
| 			unsigned int cycles_into_pulse; | 			unsigned int cycles_into_pulse = 0; | ||||||
| 			unsigned int bits_remaining_until_empty; | 			unsigned int bits_remaining_until_empty = 0; | ||||||
| 		} output_; | 		} output_; | ||||||
|  |  | ||||||
| 		bool is_running_; | 		bool is_running_ = false; | ||||||
| 		bool is_enabled_; | 		bool is_enabled_ = false; | ||||||
| 		bool is_in_input_mode_; | 		bool is_in_input_mode_ = false; | ||||||
|  |  | ||||||
| 		inline void evaluate_interrupts(); | 		inline void evaluate_interrupts(); | ||||||
| 		uint16_t data_register_; | 		uint16_t data_register_ = 0; | ||||||
|  |  | ||||||
| 		uint8_t interrupt_status_, last_posted_interrupt_status_; | 		uint8_t interrupt_status_ = 0; | ||||||
| 		Delegate *delegate_; | 		uint8_t last_posted_interrupt_status_ = 0; | ||||||
|  | 		Delegate *delegate_ = nullptr; | ||||||
|  |  | ||||||
| 		::Storage::Tape::Acorn::Shifter shifter_; | 		::Storage::Tape::Acorn::Shifter shifter_; | ||||||
| }; | }; | ||||||
|   | |||||||
| @@ -8,6 +8,8 @@ | |||||||
|  |  | ||||||
| #include "Video.hpp" | #include "Video.hpp" | ||||||
|  |  | ||||||
|  | #include <cstring> | ||||||
|  |  | ||||||
| using namespace Electron; | using namespace Electron; | ||||||
|  |  | ||||||
| #define graphics_line(v)	((((v) >> 7) - first_graphics_line + field_divider_line) % field_divider_line) | #define graphics_line(v)	((((v) >> 7) - first_graphics_line + field_divider_line) % field_divider_line) | ||||||
| @@ -32,17 +34,18 @@ namespace { | |||||||
| 	static const int real_time_clock_interrupt_2 = 56704; | 	static const int real_time_clock_interrupt_2 = 56704; | ||||||
| 	static const int display_end_interrupt_1 = (first_graphics_line + display_end_interrupt_line)*cycles_per_line; | 	static const int display_end_interrupt_1 = (first_graphics_line + display_end_interrupt_line)*cycles_per_line; | ||||||
| 	static const int display_end_interrupt_2 = (first_graphics_line + field_divider_line + display_end_interrupt_line)*cycles_per_line; | 	static const int display_end_interrupt_2 = (first_graphics_line + field_divider_line + display_end_interrupt_line)*cycles_per_line; | ||||||
|  |  | ||||||
|  | 	struct FourBPPBookender: public Outputs::CRT::TextureBuilder::Bookender { | ||||||
|  | 		void add_bookends(uint8_t *const left_value, uint8_t *const right_value, uint8_t *left_bookend, uint8_t *right_bookend) { | ||||||
|  | 			*left_bookend = static_cast<uint8_t>(((*left_value) & 0x0f) | (((*left_value) & 0x0f) << 4)); | ||||||
|  | 			*right_bookend = static_cast<uint8_t>(((*right_value) & 0xf0) | (((*right_value) & 0xf0) >> 4)); | ||||||
|  | 		} | ||||||
|  | 	}; | ||||||
| } | } | ||||||
|  |  | ||||||
| #pragma mark - Lifecycle | // MARK: - Lifecycle | ||||||
|  |  | ||||||
| VideoOutput::VideoOutput(uint8_t *memory) : | VideoOutput::VideoOutput(uint8_t *memory) : ram_(memory) { | ||||||
| 		ram_(memory), |  | ||||||
| 		current_pixel_line_(-1), |  | ||||||
| 		output_position_(0), |  | ||||||
| 		screen_mode_(6), |  | ||||||
| 		screen_map_pointer_(0), |  | ||||||
| 		cycles_into_draw_action_(0) { |  | ||||||
| 	memset(palette_, 0xf, sizeof(palette_)); | 	memset(palette_, 0xf, sizeof(palette_)); | ||||||
| 	setup_screen_map(); | 	setup_screen_map(); | ||||||
| 	setup_base_address(); | 	setup_base_address(); | ||||||
| @@ -55,17 +58,19 @@ VideoOutput::VideoOutput(uint8_t *memory) : | |||||||
| 			"texValue >>= 4 - (int(icoordinate.x * 8) & 4);" | 			"texValue >>= 4 - (int(icoordinate.x * 8) & 4);" | ||||||
| 			"return vec3( uvec3(texValue) & uvec3(4u, 2u, 1u));" | 			"return vec3( uvec3(texValue) & uvec3(4u, 2u, 1u));" | ||||||
| 		"}"); | 		"}"); | ||||||
|  | 	std::unique_ptr<Outputs::CRT::TextureBuilder::Bookender> bookender(new FourBPPBookender); | ||||||
|  | 	crt_->set_bookender(std::move(bookender)); | ||||||
| 	// TODO: as implied below, I've introduced a clock's latency into the graphics pipeline somehow. Investigate. | 	// TODO: as implied below, I've introduced a clock's latency into the graphics pipeline somehow. Investigate. | ||||||
| 	crt_->set_visible_area(crt_->get_rect_for_area(first_graphics_line - 3, 256, (first_graphics_cycle+1) * crt_cycles_multiplier, 80 * crt_cycles_multiplier, 4.0f / 3.0f)); | 	crt_->set_visible_area(crt_->get_rect_for_area(first_graphics_line - 3, 256, (first_graphics_cycle+1) * crt_cycles_multiplier, 80 * crt_cycles_multiplier, 4.0f / 3.0f)); | ||||||
| } | } | ||||||
|  |  | ||||||
| #pragma mark - CRT getter | // MARK: - CRT getter | ||||||
|  |  | ||||||
| std::shared_ptr<Outputs::CRT::CRT> VideoOutput::get_crt() { | Outputs::CRT::CRT *VideoOutput::get_crt() { | ||||||
| 	return crt_; | 	return crt_.get(); | ||||||
| } | } | ||||||
|  |  | ||||||
| #pragma mark - Display update methods | // MARK: - Display update methods | ||||||
|  |  | ||||||
| void VideoOutput::start_pixel_line() { | void VideoOutput::start_pixel_line() { | ||||||
| 	current_pixel_line_ = (current_pixel_line_+1)&255; | 	current_pixel_line_ = (current_pixel_line_+1)&255; | ||||||
| @@ -92,7 +97,7 @@ void VideoOutput::start_pixel_line() { | |||||||
| } | } | ||||||
|  |  | ||||||
| void VideoOutput::end_pixel_line() { | void VideoOutput::end_pixel_line() { | ||||||
| 	if(current_output_target_) crt_->output_data((unsigned int)((current_output_target_ - initial_output_target_) * current_output_divider_), current_output_divider_); | 	if(current_output_target_) crt_->output_data(static_cast<unsigned int>((current_output_target_ - initial_output_target_) * current_output_divider_), current_output_divider_); | ||||||
| 	current_character_row_++; | 	current_character_row_++; | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -110,9 +115,9 @@ void VideoOutput::output_pixels(unsigned int number_of_cycles) { | |||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		if(!initial_output_target_ || divider != current_output_divider_) { | 		if(!initial_output_target_ || divider != current_output_divider_) { | ||||||
| 			if(current_output_target_) crt_->output_data((unsigned int)((current_output_target_ - initial_output_target_) * current_output_divider_), current_output_divider_); | 			if(current_output_target_) crt_->output_data(static_cast<unsigned int>((current_output_target_ - initial_output_target_) * current_output_divider_), current_output_divider_); | ||||||
| 			current_output_divider_ = divider; | 			current_output_divider_ = divider; | ||||||
| 			initial_output_target_ = current_output_target_ = crt_->allocate_write_area(640 / current_output_divider_); | 			initial_output_target_ = current_output_target_ = crt_->allocate_write_area(640 / current_output_divider_, 4); | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| #define get_pixel()	\ | #define get_pixel()	\ | ||||||
| @@ -127,7 +132,7 @@ void VideoOutput::output_pixels(unsigned int number_of_cycles) { | |||||||
| 				if(initial_output_target_) { | 				if(initial_output_target_) { | ||||||
| 					while(number_of_cycles--) { | 					while(number_of_cycles--) { | ||||||
| 						get_pixel(); | 						get_pixel(); | ||||||
| 						*(uint32_t *)current_output_target_ = palette_tables_.eighty1bpp[last_pixel_byte_]; | 						*reinterpret_cast<uint32_t *>(current_output_target_) = palette_tables_.eighty1bpp[last_pixel_byte_]; | ||||||
| 						current_output_target_ += 4; | 						current_output_target_ += 4; | ||||||
| 						current_pixel_column_++; | 						current_pixel_column_++; | ||||||
| 					} | 					} | ||||||
| @@ -138,7 +143,7 @@ void VideoOutput::output_pixels(unsigned int number_of_cycles) { | |||||||
| 				if(initial_output_target_) { | 				if(initial_output_target_) { | ||||||
| 					while(number_of_cycles--) { | 					while(number_of_cycles--) { | ||||||
| 						get_pixel(); | 						get_pixel(); | ||||||
| 						*(uint16_t *)current_output_target_ = palette_tables_.eighty2bpp[last_pixel_byte_]; | 						*reinterpret_cast<uint16_t *>(current_output_target_) = palette_tables_.eighty2bpp[last_pixel_byte_]; | ||||||
| 						current_output_target_ += 2; | 						current_output_target_ += 2; | ||||||
| 						current_pixel_column_++; | 						current_pixel_column_++; | ||||||
| 					} | 					} | ||||||
| @@ -160,7 +165,7 @@ void VideoOutput::output_pixels(unsigned int number_of_cycles) { | |||||||
| 				if(initial_output_target_) { | 				if(initial_output_target_) { | ||||||
| 					if(current_pixel_column_&1) { | 					if(current_pixel_column_&1) { | ||||||
| 						last_pixel_byte_ <<= 4; | 						last_pixel_byte_ <<= 4; | ||||||
| 						*(uint16_t *)current_output_target_ = palette_tables_.forty1bpp[last_pixel_byte_]; | 						*reinterpret_cast<uint16_t *>(current_output_target_) = palette_tables_.forty1bpp[last_pixel_byte_]; | ||||||
| 						current_output_target_ += 2; | 						current_output_target_ += 2; | ||||||
|  |  | ||||||
| 						number_of_cycles--; | 						number_of_cycles--; | ||||||
| @@ -168,11 +173,11 @@ void VideoOutput::output_pixels(unsigned int number_of_cycles) { | |||||||
| 					} | 					} | ||||||
| 					while(number_of_cycles > 1) { | 					while(number_of_cycles > 1) { | ||||||
| 						get_pixel(); | 						get_pixel(); | ||||||
| 						*(uint16_t *)current_output_target_ = palette_tables_.forty1bpp[last_pixel_byte_]; | 						*reinterpret_cast<uint16_t *>(current_output_target_) = palette_tables_.forty1bpp[last_pixel_byte_]; | ||||||
| 						current_output_target_ += 2; | 						current_output_target_ += 2; | ||||||
|  |  | ||||||
| 						last_pixel_byte_ <<= 4; | 						last_pixel_byte_ <<= 4; | ||||||
| 						*(uint16_t *)current_output_target_ = palette_tables_.forty1bpp[last_pixel_byte_]; | 						*reinterpret_cast<uint16_t *>(current_output_target_) = palette_tables_.forty1bpp[last_pixel_byte_]; | ||||||
| 						current_output_target_ += 2; | 						current_output_target_ += 2; | ||||||
|  |  | ||||||
| 						number_of_cycles -= 2; | 						number_of_cycles -= 2; | ||||||
| @@ -180,7 +185,7 @@ void VideoOutput::output_pixels(unsigned int number_of_cycles) { | |||||||
| 					} | 					} | ||||||
| 					if(number_of_cycles) { | 					if(number_of_cycles) { | ||||||
| 						get_pixel(); | 						get_pixel(); | ||||||
| 						*(uint16_t *)current_output_target_ = palette_tables_.forty1bpp[last_pixel_byte_]; | 						*reinterpret_cast<uint16_t *>(current_output_target_) = palette_tables_.forty1bpp[last_pixel_byte_]; | ||||||
| 						current_output_target_ += 2; | 						current_output_target_ += 2; | ||||||
| 						current_pixel_column_++; | 						current_pixel_column_++; | ||||||
| 					} | 					} | ||||||
| @@ -229,15 +234,15 @@ void VideoOutput::run_for(const Cycles cycles) { | |||||||
| 	while(number_of_cycles) { | 	while(number_of_cycles) { | ||||||
| 		int draw_action_length = screen_map_[screen_map_pointer_].length; | 		int draw_action_length = screen_map_[screen_map_pointer_].length; | ||||||
| 		int time_left_in_action = std::min(number_of_cycles, draw_action_length - cycles_into_draw_action_); | 		int time_left_in_action = std::min(number_of_cycles, draw_action_length - cycles_into_draw_action_); | ||||||
| 		if(screen_map_[screen_map_pointer_].type == DrawAction::Pixels) output_pixels((unsigned int)time_left_in_action); | 		if(screen_map_[screen_map_pointer_].type == DrawAction::Pixels) output_pixels(static_cast<unsigned int>(time_left_in_action)); | ||||||
|  |  | ||||||
| 		number_of_cycles -= time_left_in_action; | 		number_of_cycles -= time_left_in_action; | ||||||
| 		cycles_into_draw_action_ += time_left_in_action; | 		cycles_into_draw_action_ += time_left_in_action; | ||||||
| 		if(cycles_into_draw_action_ == draw_action_length) { | 		if(cycles_into_draw_action_ == draw_action_length) { | ||||||
| 			switch(screen_map_[screen_map_pointer_].type) { | 			switch(screen_map_[screen_map_pointer_].type) { | ||||||
| 				case DrawAction::Sync:			crt_->output_sync((unsigned int)(draw_action_length * crt_cycles_multiplier));					break; | 				case DrawAction::Sync:			crt_->output_sync(static_cast<unsigned int>(draw_action_length * crt_cycles_multiplier));					break; | ||||||
| 				case DrawAction::ColourBurst:	crt_->output_default_colour_burst((unsigned int)(draw_action_length * crt_cycles_multiplier));	break; | 				case DrawAction::ColourBurst:	crt_->output_default_colour_burst(static_cast<unsigned int>(draw_action_length * crt_cycles_multiplier));	break; | ||||||
| 				case DrawAction::Blank:			crt_->output_blank((unsigned int)(draw_action_length * crt_cycles_multiplier));					break; | 				case DrawAction::Blank:			crt_->output_blank(static_cast<unsigned int>(draw_action_length * crt_cycles_multiplier));					break; | ||||||
| 				case DrawAction::Pixels:		end_pixel_line();																							break; | 				case DrawAction::Pixels:		end_pixel_line();																							break; | ||||||
| 			} | 			} | ||||||
| 			screen_map_pointer_ = (screen_map_pointer_ + 1) % screen_map_.size(); | 			screen_map_pointer_ = (screen_map_pointer_ + 1) % screen_map_.size(); | ||||||
| @@ -247,16 +252,16 @@ void VideoOutput::run_for(const Cycles cycles) { | |||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| #pragma mark - Register hub | // MARK: - Register hub | ||||||
|  |  | ||||||
| void VideoOutput::set_register(int address, uint8_t value) { | void VideoOutput::set_register(int address, uint8_t value) { | ||||||
| 	switch(address & 0xf) { | 	switch(address & 0xf) { | ||||||
| 		case 0x02: | 		case 0x02: | ||||||
| 			start_screen_address_ = (start_screen_address_ & 0xfe00) | (uint16_t)((value & 0xe0) << 1); | 			start_screen_address_ = (start_screen_address_ & 0xfe00) | static_cast<uint16_t>((value & 0xe0) << 1); | ||||||
| 			if(!start_screen_address_) start_screen_address_ |= 0x8000; | 			if(!start_screen_address_) start_screen_address_ |= 0x8000; | ||||||
| 		break; | 		break; | ||||||
| 		case 0x03: | 		case 0x03: | ||||||
| 			start_screen_address_ = (start_screen_address_ & 0x01ff) | (uint16_t)((value & 0x3f) << 9); | 			start_screen_address_ = (start_screen_address_ & 0x01ff) | static_cast<uint16_t>((value & 0x3f) << 9); | ||||||
| 			if(!start_screen_address_) start_screen_address_ |= 0x8000; | 			if(!start_screen_address_) start_screen_address_ |= 0x8000; | ||||||
| 		break; | 		break; | ||||||
| 		case 0x07: { | 		case 0x07: { | ||||||
| @@ -298,17 +303,17 @@ void VideoOutput::set_register(int address, uint8_t value) { | |||||||
| 			} | 			} | ||||||
|  |  | ||||||
| 			// regenerate all palette tables for now | 			// regenerate all palette tables for now | ||||||
| #define pack(a, b) (uint8_t)((a << 4) | (b)) | #define pack(a, b) static_cast<uint8_t>((a << 4) | (b)) | ||||||
| 			for(int byte = 0; byte < 256; byte++) { | 			for(int byte = 0; byte < 256; byte++) { | ||||||
| 				uint8_t *target = (uint8_t *)&palette_tables_.forty1bpp[byte]; | 				uint8_t *target = reinterpret_cast<uint8_t *>(&palette_tables_.forty1bpp[byte]); | ||||||
| 				target[0] = pack(palette_[(byte&0x80) >> 4], palette_[(byte&0x40) >> 3]); | 				target[0] = pack(palette_[(byte&0x80) >> 4], palette_[(byte&0x40) >> 3]); | ||||||
| 				target[1] = pack(palette_[(byte&0x20) >> 2], palette_[(byte&0x10) >> 1]); | 				target[1] = pack(palette_[(byte&0x20) >> 2], palette_[(byte&0x10) >> 1]); | ||||||
|  |  | ||||||
| 				target = (uint8_t *)&palette_tables_.eighty2bpp[byte]; | 				target = reinterpret_cast<uint8_t *>(&palette_tables_.eighty2bpp[byte]); | ||||||
| 				target[0] = pack(palette_[((byte&0x80) >> 4) | ((byte&0x08) >> 2)], palette_[((byte&0x40) >> 3) | ((byte&0x04) >> 1)]); | 				target[0] = pack(palette_[((byte&0x80) >> 4) | ((byte&0x08) >> 2)], palette_[((byte&0x40) >> 3) | ((byte&0x04) >> 1)]); | ||||||
| 				target[1] = pack(palette_[((byte&0x20) >> 2) | ((byte&0x02) >> 0)], palette_[((byte&0x10) >> 1) | ((byte&0x01) << 1)]); | 				target[1] = pack(palette_[((byte&0x20) >> 2) | ((byte&0x02) >> 0)], palette_[((byte&0x10) >> 1) | ((byte&0x01) << 1)]); | ||||||
|  |  | ||||||
| 				target = (uint8_t *)&palette_tables_.eighty1bpp[byte]; | 				target = reinterpret_cast<uint8_t *>(&palette_tables_.eighty1bpp[byte]); | ||||||
| 				target[0] = pack(palette_[(byte&0x80) >> 4], palette_[(byte&0x40) >> 3]); | 				target[0] = pack(palette_[(byte&0x80) >> 4], palette_[(byte&0x40) >> 3]); | ||||||
| 				target[1] = pack(palette_[(byte&0x20) >> 2], palette_[(byte&0x10) >> 1]); | 				target[1] = pack(palette_[(byte&0x20) >> 2], palette_[(byte&0x10) >> 1]); | ||||||
| 				target[2] = pack(palette_[(byte&0x08) >> 0], palette_[(byte&0x04) << 1]); | 				target[2] = pack(palette_[(byte&0x08) >> 0], palette_[(byte&0x04) << 1]); | ||||||
| @@ -333,7 +338,7 @@ void VideoOutput::setup_base_address() { | |||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| #pragma mark - Interrupts | // MARK: - Interrupts | ||||||
|  |  | ||||||
| VideoOutput::Interrupt VideoOutput::get_next_interrupt() { | VideoOutput::Interrupt VideoOutput::get_next_interrupt() { | ||||||
| 	VideoOutput::Interrupt interrupt; | 	VideoOutput::Interrupt interrupt; | ||||||
| @@ -367,24 +372,43 @@ VideoOutput::Interrupt VideoOutput::get_next_interrupt() { | |||||||
| 	return interrupt; | 	return interrupt; | ||||||
| } | } | ||||||
|  |  | ||||||
| #pragma mark - RAM timing and access information | // MARK: - RAM timing and access information | ||||||
|  |  | ||||||
| unsigned int VideoOutput::get_cycles_until_next_ram_availability(int from_time) { | unsigned int VideoOutput::get_cycles_until_next_ram_availability(int from_time) { | ||||||
| 	unsigned int result = 0; | 	unsigned int result = 0; | ||||||
| 	int position = output_position_ + from_time; | 	int position = (output_position_ + from_time) % cycles_per_frame; | ||||||
|  |  | ||||||
|  | 	// Apply the standard cost of aligning to the available 1Mhz of RAM bandwidth. | ||||||
| 	result += 1 + (position&1); | 	result += 1 + (position&1); | ||||||
|  |  | ||||||
|  | 	// In Modes 0–3 there is also a complete block on any access while pixels are being fetched. | ||||||
| 	if(screen_mode_ < 4) { | 	if(screen_mode_ < 4) { | ||||||
| 		const int current_column = graphics_column(position + (position&1)); | 		const int current_column = graphics_column(position + (position&1)); | ||||||
| 		int current_line = graphics_line(position); | 		int current_line = graphics_line(position); | ||||||
| 		if(current_column < 80 && current_line < 256) { | 		if(current_column < 80 && current_line < 256) { | ||||||
|  | 			// Mode 3 is a further special case: in 'every ten line block', the final two aren't painted, | ||||||
|  | 			// so the CPU is allowed access. But the offset of the ten-line blocks depends on when the | ||||||
|  | 			// user switched into Mode 3, so that needs to be calculated relative to current output. | ||||||
| 			if(screen_mode_ == 3) { | 			if(screen_mode_ == 3) { | ||||||
|  | 				// Get the line the display was on. | ||||||
| 				int output_position_line = graphics_line(output_position_); | 				int output_position_line = graphics_line(output_position_); | ||||||
| 				int implied_row = current_character_row_ + (current_line - output_position_line) % 10; |  | ||||||
| 				if(implied_row < 8) | 				int implied_row; | ||||||
| 					result += (unsigned int)(80 - current_column); | 				if(current_line >= output_position_line) { | ||||||
|  | 					// Get the number of lines since then if still in the same frame. | ||||||
|  | 					int lines_since_output_position = current_line - output_position_line; | ||||||
|  | 				 | ||||||
|  | 					// Therefore get the character row at the proposed time, modulo 10. | ||||||
|  | 					implied_row = (current_character_row_ + lines_since_output_position) % 10; | ||||||
|  | 				} else { | ||||||
|  | 					// If the frame has rolled over, the implied row is just related to the current line. | ||||||
|  | 					implied_row = current_line % 10; | ||||||
| 				} | 				} | ||||||
| 			else result += (unsigned int)(80 - current_column); |  | ||||||
|  | 				// Mode 3 ends after 250 lines, not the usual 256. | ||||||
|  | 				if(implied_row < 8 && current_line < 250) result += static_cast<unsigned int>(80 - current_column); | ||||||
|  | 			} | ||||||
|  | 			else result += static_cast<unsigned int>(80 - current_column); | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 	return result; | 	return result; | ||||||
| @@ -402,7 +426,7 @@ VideoOutput::Range VideoOutput::get_memory_access_range() { | |||||||
| 	return range; | 	return range; | ||||||
| } | } | ||||||
|  |  | ||||||
| #pragma mark - The screen map | // MARK: - The screen map | ||||||
|  |  | ||||||
| void VideoOutput::setup_screen_map() { | void VideoOutput::setup_screen_map() { | ||||||
| 	/* | 	/* | ||||||
|   | |||||||
| @@ -31,7 +31,7 @@ class VideoOutput { | |||||||
| 		VideoOutput(uint8_t *memory); | 		VideoOutput(uint8_t *memory); | ||||||
|  |  | ||||||
| 		/// @returns the CRT to which output is being painted. | 		/// @returns the CRT to which output is being painted. | ||||||
| 		std::shared_ptr<Outputs::CRT::CRT> get_crt(); | 		Outputs::CRT::CRT *get_crt(); | ||||||
|  |  | ||||||
| 		/// Produces the next @c cycles of video output. | 		/// Produces the next @c cycles of video output. | ||||||
| 		void run_for(const Cycles cycles); | 		void run_for(const Cycles cycles); | ||||||
| @@ -80,12 +80,13 @@ class VideoOutput { | |||||||
| 		inline void output_pixels(unsigned int number_of_cycles); | 		inline void output_pixels(unsigned int number_of_cycles); | ||||||
| 		inline void setup_base_address(); | 		inline void setup_base_address(); | ||||||
|  |  | ||||||
| 		int output_position_, unused_cycles_; | 		int output_position_ = 0; | ||||||
|  | 		int unused_cycles_ = 0; | ||||||
|  |  | ||||||
| 		uint8_t palette_[16]; | 		uint8_t palette_[16]; | ||||||
| 		uint8_t screen_mode_; | 		uint8_t screen_mode_ = 6; | ||||||
| 		uint16_t screen_mode_base_address_; | 		uint16_t screen_mode_base_address_ = 0; | ||||||
| 		uint16_t start_screen_address_; | 		uint16_t start_screen_address_ = 0; | ||||||
|  |  | ||||||
| 		uint8_t *ram_; | 		uint8_t *ram_; | ||||||
| 		struct { | 		struct { | ||||||
| @@ -97,16 +98,20 @@ class VideoOutput { | |||||||
| 		} palette_tables_; | 		} palette_tables_; | ||||||
|  |  | ||||||
| 		// Display generation. | 		// Display generation. | ||||||
| 		uint16_t start_line_address_, current_screen_address_; | 		uint16_t start_line_address_ = 0; | ||||||
| 		int current_pixel_line_, current_pixel_column_, current_character_row_; | 		uint16_t current_screen_address_ = 0; | ||||||
| 		uint8_t last_pixel_byte_; | 		int current_pixel_line_ = -1; | ||||||
| 		bool is_blank_line_; | 		int current_pixel_column_ = 0; | ||||||
|  | 		int current_character_row_ = 0; | ||||||
|  | 		uint8_t last_pixel_byte_ = 0; | ||||||
|  | 		bool is_blank_line_ = false; | ||||||
|  |  | ||||||
| 		// CRT output | 		// CRT output | ||||||
| 		uint8_t *current_output_target_, *initial_output_target_; | 		uint8_t *current_output_target_ = nullptr; | ||||||
| 		unsigned int current_output_divider_; | 		uint8_t *initial_output_target_ = nullptr; | ||||||
|  | 		unsigned int current_output_divider_ = 1; | ||||||
|  |  | ||||||
| 		std::shared_ptr<Outputs::CRT::CRT> crt_; | 		std::unique_ptr<Outputs::CRT::CRT> crt_; | ||||||
|  |  | ||||||
| 		struct DrawAction { | 		struct DrawAction { | ||||||
| 			enum Type { | 			enum Type { | ||||||
| @@ -119,8 +124,8 @@ class VideoOutput { | |||||||
| 		void setup_screen_map(); | 		void setup_screen_map(); | ||||||
| 		void emplace_blank_line(); | 		void emplace_blank_line(); | ||||||
| 		void emplace_pixel_line(); | 		void emplace_pixel_line(); | ||||||
| 		size_t screen_map_pointer_; | 		std::size_t screen_map_pointer_ = 0; | ||||||
| 		int cycles_into_draw_action_; | 		int cycles_into_draw_action_ = 0; | ||||||
| }; | }; | ||||||
|  |  | ||||||
| } | } | ||||||
|   | |||||||
							
								
								
									
										24
									
								
								Machines/JoystickMachine.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								Machines/JoystickMachine.hpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,24 @@ | |||||||
|  | // | ||||||
|  | //  JoystickMachine.hpp | ||||||
|  | //  Clock Signal | ||||||
|  | // | ||||||
|  | //  Created by Thomas Harte on 14/10/2017. | ||||||
|  | //  Copyright © 2017 Thomas Harte. All rights reserved. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | #ifndef JoystickMachine_hpp | ||||||
|  | #define JoystickMachine_hpp | ||||||
|  |  | ||||||
|  | #include "../Inputs/Joystick.hpp" | ||||||
|  | #include <vector> | ||||||
|  |  | ||||||
|  | namespace JoystickMachine { | ||||||
|  |  | ||||||
|  | class Machine { | ||||||
|  | 	public: | ||||||
|  | 		virtual std::vector<std::unique_ptr<Inputs::Joystick>> &get_joysticks() = 0; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #endif /* JoystickMachine_hpp */ | ||||||
							
								
								
									
										32
									
								
								Machines/KeyboardMachine.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								Machines/KeyboardMachine.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,32 @@ | |||||||
|  | // | ||||||
|  | //  KeyboardMachine.cpp | ||||||
|  | //  Clock Signal | ||||||
|  | // | ||||||
|  | //  Created by Thomas Harte on 10/10/2017. | ||||||
|  | //  Copyright © 2017 Thomas Harte. All rights reserved. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | #include "KeyboardMachine.hpp" | ||||||
|  |  | ||||||
|  | using namespace KeyboardMachine; | ||||||
|  |  | ||||||
|  | Machine::Machine() { | ||||||
|  | 	keyboard_.set_delegate(this); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void Machine::keyboard_did_change_key(Inputs::Keyboard *keyboard, Inputs::Keyboard::Key key, bool is_pressed) { | ||||||
|  | 	uint16_t mapped_key = get_keyboard_mapper().mapped_key_for_key(key); | ||||||
|  | 	if(mapped_key != KeyNotMapped) set_key_state(mapped_key, is_pressed); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void Machine::reset_all_keys(Inputs::Keyboard *keyboard) { | ||||||
|  | 	// TODO: unify naming. | ||||||
|  | 	clear_all_keys(); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | Inputs::Keyboard &Machine::get_keyboard() { | ||||||
|  | 	return keyboard_; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void Machine::type_string(const std::string &) { | ||||||
|  | } | ||||||
| @@ -9,20 +9,69 @@ | |||||||
| #ifndef KeyboardMachine_h | #ifndef KeyboardMachine_h | ||||||
| #define KeyboardMachine_h | #define KeyboardMachine_h | ||||||
|  |  | ||||||
|  | #include <cstdint> | ||||||
|  | #include <string> | ||||||
|  |  | ||||||
|  | #include "../Inputs/Keyboard.hpp" | ||||||
|  |  | ||||||
| namespace KeyboardMachine { | namespace KeyboardMachine { | ||||||
|  |  | ||||||
| class Machine { | class Machine: public Inputs::Keyboard::Delegate { | ||||||
| 	public: | 	public: | ||||||
|  | 		Machine(); | ||||||
|  |  | ||||||
| 		/*! | 		/*! | ||||||
| 			Indicates that the key @c key has been either pressed or released, according to | 			Indicates that the key @c key has been either pressed or released, according to | ||||||
| 			the state of @c isPressed. | 			the state of @c isPressed. | ||||||
| 		*/ | 		*/ | ||||||
| 		virtual void set_key_state(uint16_t key, bool isPressed) = 0; | 		virtual void set_key_state(uint16_t key, bool is_pressed) = 0; | ||||||
|  |  | ||||||
| 		/*! | 		/*! | ||||||
| 			Instructs that all keys should now be treated as released. | 			Instructs that all keys should now be treated as released. | ||||||
| 		*/ | 		*/ | ||||||
| 		virtual void clear_all_keys() = 0; | 		virtual void clear_all_keys() = 0; | ||||||
|  |  | ||||||
|  | 		/*! | ||||||
|  | 			Causes the machine to attempt to type the supplied string. | ||||||
|  |  | ||||||
|  | 			This is best effort. Success or failure is permitted to be a function of machine and current state. | ||||||
|  | 		*/ | ||||||
|  | 		virtual void type_string(const std::string &); | ||||||
|  |  | ||||||
|  | 		/*! | ||||||
|  | 			Provides a destination for keyboard input. | ||||||
|  | 		*/ | ||||||
|  | 		virtual Inputs::Keyboard &get_keyboard(); | ||||||
|  |  | ||||||
|  | 		/*! | ||||||
|  | 			A keyboard mapper attempts to provide a physical mapping between host keys and emulated keys. | ||||||
|  | 			See the character mapper for logical mapping. | ||||||
|  | 		*/ | ||||||
|  | 		class KeyboardMapper { | ||||||
|  | 			public: | ||||||
|  | 				virtual uint16_t mapped_key_for_key(Inputs::Keyboard::Key key) = 0; | ||||||
|  | 		}; | ||||||
|  |  | ||||||
|  | 		/// Terminates a key sequence from the character mapper. | ||||||
|  | 		static const uint16_t KeyEndSequence = 0xffff; | ||||||
|  |  | ||||||
|  | 		/*! | ||||||
|  | 			Indicates that a key is not mapped (for the keyboard mapper) or that a | ||||||
|  | 			character cannot be typed (for the character mapper). | ||||||
|  | 		*/ | ||||||
|  | 		static const uint16_t KeyNotMapped = 0xfffe; | ||||||
|  |  | ||||||
|  | 	protected: | ||||||
|  | 		/*! | ||||||
|  | 			Allows individual machines to provide the mapping between host keys | ||||||
|  | 			as per Inputs::Keyboard and their native scheme. | ||||||
|  | 		*/ | ||||||
|  | 		virtual KeyboardMapper &get_keyboard_mapper() = 0; | ||||||
|  |  | ||||||
|  | 	private: | ||||||
|  | 		void keyboard_did_change_key(Inputs::Keyboard *keyboard, Inputs::Keyboard::Key key, bool is_pressed) override; | ||||||
|  | 		void reset_all_keys(Inputs::Keyboard *keyboard) override; | ||||||
|  | 		Inputs::Keyboard keyboard_; | ||||||
| }; | }; | ||||||
|  |  | ||||||
| } | } | ||||||
|   | |||||||
							
								
								
									
										42
									
								
								Machines/MSX/Cartridges/ASCII16kb.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										42
									
								
								Machines/MSX/Cartridges/ASCII16kb.hpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,42 @@ | |||||||
|  | // | ||||||
|  | //  ASCII16kb.hpp | ||||||
|  | //  Clock Signal | ||||||
|  | // | ||||||
|  | //  Created by Thomas Harte on 04/01/2018. | ||||||
|  | //  Copyright © 2018 Thomas Harte. All rights reserved. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | #ifndef ASCII16kb_hpp | ||||||
|  | #define ASCII16kb_hpp | ||||||
|  |  | ||||||
|  | #include "../ROMSlotHandler.hpp" | ||||||
|  |  | ||||||
|  | namespace MSX { | ||||||
|  | namespace Cartridge { | ||||||
|  |  | ||||||
|  | class ASCII16kbROMSlotHandler: public ROMSlotHandler { | ||||||
|  | 	public: | ||||||
|  | 		ASCII16kbROMSlotHandler(MSX::MemoryMap &map, int slot) : | ||||||
|  | 			map_(map), slot_(slot) {} | ||||||
|  |  | ||||||
|  | 		void write(uint16_t address, uint8_t value) { | ||||||
|  | 			switch(address >> 11) { | ||||||
|  | 				default: break; | ||||||
|  | 				case 0xc: | ||||||
|  | 					map_.map(slot_, value * 8192, 0x4000, 0x4000); | ||||||
|  | 				break; | ||||||
|  | 				case 0xe: | ||||||
|  | 					map_.map(slot_, value * 8192, 0x8000, 0x4000); | ||||||
|  | 				break; | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 	private: | ||||||
|  | 		MSX::MemoryMap &map_; | ||||||
|  | 		int slot_; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #endif /* ASCII16kb_hpp */ | ||||||
							
								
								
									
										48
									
								
								Machines/MSX/Cartridges/ASCII8kb.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										48
									
								
								Machines/MSX/Cartridges/ASCII8kb.hpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,48 @@ | |||||||
|  | // | ||||||
|  | //  ASCII8kb.hpp | ||||||
|  | //  Clock Signal | ||||||
|  | // | ||||||
|  | //  Created by Thomas Harte on 04/01/2018. | ||||||
|  | //  Copyright © 2018 Thomas Harte. All rights reserved. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | #ifndef ASCII8kb_hpp | ||||||
|  | #define ASCII8kb_hpp | ||||||
|  |  | ||||||
|  | #include "../ROMSlotHandler.hpp" | ||||||
|  |  | ||||||
|  | namespace MSX { | ||||||
|  | namespace Cartridge { | ||||||
|  |  | ||||||
|  | class ASCII8kbROMSlotHandler: public ROMSlotHandler { | ||||||
|  | 	public: | ||||||
|  | 		ASCII8kbROMSlotHandler(MSX::MemoryMap &map, int slot) : | ||||||
|  | 			map_(map), slot_(slot) {} | ||||||
|  |  | ||||||
|  | 		void write(uint16_t address, uint8_t value) { | ||||||
|  | 			switch(address >> 11) { | ||||||
|  | 				default: break; | ||||||
|  | 				case 0xc: | ||||||
|  | 					map_.map(slot_, value * 8192, 0x4000, 0x2000); | ||||||
|  | 				break; | ||||||
|  | 				case 0xd: | ||||||
|  | 					map_.map(slot_, value * 8192, 0x6000, 0x2000); | ||||||
|  | 				break; | ||||||
|  | 				case 0xe: | ||||||
|  | 					map_.map(slot_, value * 8192, 0x8000, 0x2000); | ||||||
|  | 				break; | ||||||
|  | 				case 0xf: | ||||||
|  | 					map_.map(slot_, value * 8192, 0xa000, 0x2000); | ||||||
|  | 				break; | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 	private: | ||||||
|  | 		MSX::MemoryMap &map_; | ||||||
|  | 		int slot_; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #endif /* ASCII8kb_hpp */ | ||||||
							
								
								
									
										45
									
								
								Machines/MSX/Cartridges/Konami.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										45
									
								
								Machines/MSX/Cartridges/Konami.hpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,45 @@ | |||||||
|  | // | ||||||
|  | //  Konami.hpp | ||||||
|  | //  Clock Signal | ||||||
|  | // | ||||||
|  | //  Created by Thomas Harte on 04/01/2018. | ||||||
|  | //  Copyright © 2018 Thomas Harte. All rights reserved. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | #ifndef Konami_hpp | ||||||
|  | #define Konami_hpp | ||||||
|  |  | ||||||
|  | #include "../ROMSlotHandler.hpp" | ||||||
|  |  | ||||||
|  | namespace MSX { | ||||||
|  | namespace Cartridge { | ||||||
|  |  | ||||||
|  | class KonamiROMSlotHandler: public ROMSlotHandler { | ||||||
|  | 	public: | ||||||
|  | 		KonamiROMSlotHandler(MSX::MemoryMap &map, int slot) : | ||||||
|  | 			map_(map), slot_(slot) {} | ||||||
|  |  | ||||||
|  | 		void write(uint16_t address, uint8_t value) { | ||||||
|  | 			switch(address >> 13) { | ||||||
|  | 				default: break; | ||||||
|  | 				case 3: | ||||||
|  | 					map_.map(slot_, value * 8192, 0x6000, 0x2000); | ||||||
|  | 				break; | ||||||
|  | 				case 4: | ||||||
|  | 					map_.map(slot_, value * 8192, 0x8000, 0x2000); | ||||||
|  | 				break; | ||||||
|  | 				case 5: | ||||||
|  | 					map_.map(slot_, value * 8192, 0xa000, 0x2000); | ||||||
|  | 				break; | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 	private: | ||||||
|  | 		MSX::MemoryMap &map_; | ||||||
|  | 		int slot_; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #endif /* Konami_hpp */ | ||||||
							
								
								
									
										67
									
								
								Machines/MSX/Cartridges/KonamiWithSCC.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										67
									
								
								Machines/MSX/Cartridges/KonamiWithSCC.hpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,67 @@ | |||||||
|  | // | ||||||
|  | //  KonamiWithSCC.hpp | ||||||
|  | //  Clock Signal | ||||||
|  | // | ||||||
|  | //  Created by Thomas Harte on 04/01/2018. | ||||||
|  | //  Copyright © 2018 Thomas Harte. All rights reserved. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | #ifndef KonamiWithSCC_hpp | ||||||
|  | #define KonamiWithSCC_hpp | ||||||
|  |  | ||||||
|  | #include "../ROMSlotHandler.hpp" | ||||||
|  | #include "../../../Components/KonamiSCC/KonamiSCC.hpp" | ||||||
|  |  | ||||||
|  | namespace MSX { | ||||||
|  | namespace Cartridge { | ||||||
|  |  | ||||||
|  | class KonamiWithSCCROMSlotHandler: public ROMSlotHandler { | ||||||
|  | 	public: | ||||||
|  | 		KonamiWithSCCROMSlotHandler(MSX::MemoryMap &map, int slot, Konami::SCC &scc) : | ||||||
|  | 			map_(map), slot_(slot), scc_(scc) {} | ||||||
|  |  | ||||||
|  | 		void write(uint16_t address, uint8_t value) override { | ||||||
|  | 			switch(address >> 11) { | ||||||
|  | 				default: break; | ||||||
|  | 				case 0x0a: | ||||||
|  | 					map_.map(slot_, value * 8192, 0x4000, 0x2000); | ||||||
|  | 				break; | ||||||
|  | 				case 0x0e: | ||||||
|  | 					map_.map(slot_, value * 8192, 0x6000, 0x2000); | ||||||
|  | 				break; | ||||||
|  | 				case 0x12: | ||||||
|  | 					if((value&0x3f) == 0x3f) { | ||||||
|  | 						scc_is_visible_ = true; | ||||||
|  | 						map_.unmap(slot_, 0x8000, 0x2000); | ||||||
|  | 					} else { | ||||||
|  | 						scc_is_visible_ = false; | ||||||
|  | 						map_.map(slot_, value * 8192, 0x8000, 0x2000); | ||||||
|  | 					} | ||||||
|  | 				break; | ||||||
|  | 				case 0x13: | ||||||
|  | 					if(scc_is_visible_) scc_.write(address, value); | ||||||
|  | 				break; | ||||||
|  | 				case 0x16: | ||||||
|  | 					map_.map(slot_, value * 8192, 0xa000, 0x2000); | ||||||
|  | 				break; | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		uint8_t read(uint16_t address) override { | ||||||
|  | 			if(scc_is_visible_ && address >= 0x9800 && address < 0xa000) { | ||||||
|  | 				return scc_.read(address); | ||||||
|  | 			} | ||||||
|  | 			return 0xff; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 	private: | ||||||
|  | 		MSX::MemoryMap &map_; | ||||||
|  | 		int slot_; | ||||||
|  | 		Konami::SCC &scc_; | ||||||
|  | 		bool scc_is_visible_ = false; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #endif /* KonamiWithSCC_hpp */ | ||||||
							
								
								
									
										71
									
								
								Machines/MSX/DiskROM.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										71
									
								
								Machines/MSX/DiskROM.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,71 @@ | |||||||
|  | // | ||||||
|  | //  DiskROM.cpp | ||||||
|  | //  Clock Signal | ||||||
|  | // | ||||||
|  | //  Created by Thomas Harte on 07/01/2018. | ||||||
|  | //  Copyright © 2018 Thomas Harte. All rights reserved. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | #include "DiskROM.hpp" | ||||||
|  |  | ||||||
|  | using namespace MSX; | ||||||
|  |  | ||||||
|  | DiskROM::DiskROM(const std::vector<uint8_t> &rom) : | ||||||
|  | 	WD1770(P1793), | ||||||
|  | 	rom_(rom) { | ||||||
|  | 	set_is_double_density(true); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void DiskROM::write(uint16_t address, uint8_t value) { | ||||||
|  | 	switch(address) { | ||||||
|  | 		case 0x7ff8: case 0x7ff9: case 0x7ffa: case 0x7ffb: | ||||||
|  | 			set_register(address, value); | ||||||
|  | 		break; | ||||||
|  | 		case 0x7ffc: | ||||||
|  | 			selected_head_ = value & 1; | ||||||
|  | 			if(drives_[0]) drives_[0]->set_head(selected_head_); | ||||||
|  | 			if(drives_[1]) drives_[1]->set_head(selected_head_); | ||||||
|  | 		break; | ||||||
|  | 		case 0x7ffd: { | ||||||
|  | 			selected_drive_ = value & 1; | ||||||
|  | 			set_drive(drives_[selected_drive_]); | ||||||
|  |  | ||||||
|  | 			bool drive_motor = !!(value & 0x80); | ||||||
|  | 			if(drives_[0]) drives_[0]->set_motor_on(drive_motor); | ||||||
|  | 			if(drives_[1]) drives_[1]->set_motor_on(drive_motor); | ||||||
|  | 		} break; | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | uint8_t DiskROM::read(uint16_t address) { | ||||||
|  | 	if(address >= 0x7ff8 && address < 0x7ffc) { | ||||||
|  | 		return get_register(address); | ||||||
|  | 	} | ||||||
|  | 	if(address == 0x7fff) { | ||||||
|  | 		return (get_data_request_line() ? 0x00 : 0x80) | (get_interrupt_request_line() ? 0x00 : 0x40); | ||||||
|  | 	} | ||||||
|  | 	return rom_[address & 0x3fff]; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void DiskROM::run_for(HalfCycles half_cycles) { | ||||||
|  | 	// Input clock is going to be 7159090/2 Mhz, but the drive controller | ||||||
|  | 	// needs an 8Mhz clock, so scale up. 8000000/7159090 simplifies to | ||||||
|  | 	// 800000/715909. | ||||||
|  | 	controller_cycles_ += 800000 * half_cycles.as_int(); | ||||||
|  | 	WD::WD1770::run_for(Cycles(static_cast<int>(controller_cycles_ / 715909))); | ||||||
|  | 	controller_cycles_ %= 715909; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void DiskROM::set_disk(std::shared_ptr<Storage::Disk::Disk> disk, int drive) { | ||||||
|  | 	if(!drives_[drive]) { | ||||||
|  | 		drives_[drive].reset(new Storage::Disk::Drive(8000000, 300, 2)); | ||||||
|  | 		drives_[drive]->set_head(selected_head_); | ||||||
|  | 		if(drive == selected_drive_) set_drive(drives_[drive]); | ||||||
|  | 	} | ||||||
|  | 	drives_[drive]->set_disk(disk); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void DiskROM::set_head_load_request(bool head_load) { | ||||||
|  | 	// Magic! | ||||||
|  | 	set_head_loaded(head_load); | ||||||
|  | } | ||||||
							
								
								
									
										44
									
								
								Machines/MSX/DiskROM.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										44
									
								
								Machines/MSX/DiskROM.hpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,44 @@ | |||||||
|  | // | ||||||
|  | //  DiskROM.hpp | ||||||
|  | //  Clock Signal | ||||||
|  | // | ||||||
|  | //  Created by Thomas Harte on 07/01/2018. | ||||||
|  | //  Copyright © 2018 Thomas Harte. All rights reserved. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | #ifndef DiskROM_hpp | ||||||
|  | #define DiskROM_hpp | ||||||
|  |  | ||||||
|  | #include "ROMSlotHandler.hpp" | ||||||
|  |  | ||||||
|  | #include "../../Components/1770/1770.hpp" | ||||||
|  |  | ||||||
|  | #include <cstdint> | ||||||
|  | #include <vector> | ||||||
|  |  | ||||||
|  | namespace MSX { | ||||||
|  |  | ||||||
|  | class DiskROM: public ROMSlotHandler, public WD::WD1770 { | ||||||
|  | 	public: | ||||||
|  | 		DiskROM(const std::vector<uint8_t> &rom); | ||||||
|  |  | ||||||
|  | 		void write(uint16_t address, uint8_t value) override; | ||||||
|  | 		uint8_t read(uint16_t address) override; | ||||||
|  | 		void run_for(HalfCycles half_cycles) override; | ||||||
|  |  | ||||||
|  | 		void set_disk(std::shared_ptr<Storage::Disk::Disk> disk, int drive); | ||||||
|  |  | ||||||
|  | 	private: | ||||||
|  | 		const std::vector<uint8_t> &rom_; | ||||||
|  |  | ||||||
|  | 		long int controller_cycles_ = 0; | ||||||
|  | 		int selected_drive_ = 0; | ||||||
|  | 		int selected_head_ = 0; | ||||||
|  | 		std::shared_ptr<Storage::Disk::Drive> drives_[4]; | ||||||
|  |  | ||||||
|  | 		void set_head_load_request(bool head_load) override; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #endif /* DiskROM_hpp */ | ||||||
							
								
								
									
										61
									
								
								Machines/MSX/Keyboard.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										61
									
								
								Machines/MSX/Keyboard.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,61 @@ | |||||||
|  | // | ||||||
|  | //  Keyboard.cpp | ||||||
|  | //  Clock Signal | ||||||
|  | // | ||||||
|  | //  Created by Thomas Harte on 29/11/2017. | ||||||
|  | //  Copyright © 2017 Thomas Harte. All rights reserved. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | #include "Keyboard.hpp" | ||||||
|  |  | ||||||
|  | uint16_t MSX::KeyboardMapper::mapped_key_for_key(Inputs::Keyboard::Key key) { | ||||||
|  | #define BIND(source, dest)	case Inputs::Keyboard::Key::source:	return MSX::Key::dest | ||||||
|  | 	switch(key) { | ||||||
|  | 		BIND(k0, Key0);		BIND(k1, Key1);		BIND(k2, Key2);		BIND(k3, Key3);		BIND(k4, Key4); | ||||||
|  | 		BIND(k5, Key5);		BIND(k6, Key6);		BIND(k7, Key7);		BIND(k8, Key8);		BIND(k9, Key9); | ||||||
|  | 		BIND(Q, KeyQ);		BIND(W, KeyW);		BIND(E, KeyE);		BIND(R, KeyR);		BIND(T, KeyT); | ||||||
|  | 		BIND(Y, KeyY);		BIND(U, KeyU);		BIND(I, KeyI);		BIND(O, KeyO);		BIND(P, KeyP); | ||||||
|  | 		BIND(A, KeyA);		BIND(S, KeyS);		BIND(D, KeyD);		BIND(F, KeyF);		BIND(G, KeyG); | ||||||
|  | 		BIND(H, KeyH);		BIND(J, KeyJ);		BIND(K, KeyK);		BIND(L, KeyL); | ||||||
|  | 		BIND(Z, KeyZ);		BIND(X, KeyX);		BIND(C, KeyC);		BIND(V, KeyV); | ||||||
|  | 		BIND(B, KeyB);		BIND(N, KeyN);		BIND(M, KeyM); | ||||||
|  |  | ||||||
|  | 		BIND(F1, KeyF1);	BIND(F2, KeyF2);	BIND(F3, KeyF3);	BIND(F4, KeyF4);	BIND(F5, KeyF5); | ||||||
|  |  | ||||||
|  | 		BIND(F12, KeyStop); | ||||||
|  | 		BIND(F10, KeyDelete);		BIND(F9, KeyInsert);		BIND(F8, KeyHome); | ||||||
|  | 		BIND(Delete, KeyDelete);	BIND(Insert, KeyInsert);	BIND(Home, KeyHome); | ||||||
|  |  | ||||||
|  | 		BIND(Escape, KeyEscape); | ||||||
|  | 		BIND(Tab, KeyTab);			BIND(CapsLock, KeyCaps); | ||||||
|  |  | ||||||
|  | 		BIND(LeftControl, KeyControl);	BIND(RightControl, KeyControl); | ||||||
|  | 		BIND(LeftShift, KeyShift);		BIND(RightShift, KeyShift); | ||||||
|  | 		BIND(LeftMeta, KeyCode);		BIND(RightMeta, KeyGraph); | ||||||
|  | 		BIND(LeftOption, KeyCode);		BIND(RightOption, KeySelect); | ||||||
|  |  | ||||||
|  | 		BIND(Semicolon, KeySemicolon); | ||||||
|  | 		BIND(Quote, KeyQuote); | ||||||
|  | 		BIND(OpenSquareBracket, KeyLeftSquareBracket); | ||||||
|  | 		BIND(CloseSquareBracket, KeyRightSquareBracket); | ||||||
|  | 		BIND(Hyphen, KeyMinus); | ||||||
|  | 		BIND(Equals, KeyEquals); | ||||||
|  | 		BIND(Left, KeyLeft); | ||||||
|  | 		BIND(Right, KeyRight); | ||||||
|  | 		BIND(Up, KeyUp); | ||||||
|  | 		BIND(Down, KeyDown); | ||||||
|  | 		BIND(FullStop, KeyFullStop); | ||||||
|  | 		BIND(Comma, KeyComma); | ||||||
|  | 		BIND(ForwardSlash, KeyForwardSlash); | ||||||
|  | 		BIND(BackSlash, KeyBackSlash); | ||||||
|  | 		BIND(BackTick, KeyGrave); | ||||||
|  |  | ||||||
|  | 		BIND(Enter, KeyEnter); | ||||||
|  | 		BIND(Space, KeySpace); | ||||||
|  | 		BIND(BackSpace, KeyBackspace); | ||||||
|  |  | ||||||
|  | 		default: break; | ||||||
|  | 	} | ||||||
|  | #undef BIND | ||||||
|  | 	return KeyboardMachine::Machine::KeyNotMapped; | ||||||
|  | } | ||||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user