Compare commits
	
		
			524 Commits
		
	
	
		
			main
			...
			f96e26eb76
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| f96e26eb76 | |||
| f3e5939c51 | |||
| 5351c14e12 | |||
| d6304a7382 | |||
| 79af17a457 | |||
| 0f18389eb8 | |||
| da825ad144 | |||
| 9436888b28 | |||
| 583ed54ee3 | |||
| 7944cd7abc | |||
| ffd94c96ce | |||
| 4fe612da61 | |||
| 4262688fa8 | |||
| 35d685f032 | |||
| 5b9d3d0960 | |||
| 44b2e9be88 | |||
| 4b7ef52a7a | |||
| 5103fdf646 | |||
| acddaf76aa | |||
| 3da3b69e13 | |||
| 40062d4dfe | |||
| 1c91b497bc | |||
| e3286a9872 | |||
| ddb0e2f928 | |||
| d927ee6c90 | |||
| 011f105bee | |||
| 2c88f9bbce | |||
| 30c5fafec1 | |||
| 3b144e581a | |||
| 0117a52f4d | |||
| 3cf7c83269 | |||
| 85d4690c56 | |||
| 130d40bc7f | |||
| 2c928eafec | |||
| dcbeeee7cc | |||
| 84ccb7224b | |||
| dd4bb4ff03 | |||
| ecbfe67b27 | |||
| e29c20019d | |||
| d8c397248f | |||
| 64aed01869 | |||
| 29f2a0288f | |||
| 27ada16377 | |||
| ad1cec58e7 | |||
| 97d9edab93 | |||
| 39f56e6d8a | |||
| f0014e7179 | |||
| 8a804ea05e | |||
| efce1a6dce | |||
| 8b9ab6f4b5 | |||
| d21c860eb5 | |||
| 85e670a1d7 | |||
| 4f823217bd | |||
| 207a99edbe | |||
| 3c5b30dece | |||
| 739d38533a | |||
| ae6b8e2f03 | |||
| 208f4b522d | |||
| bd54700103 | |||
| 08d27520e0 | |||
| bfe97c671e | |||
| 9baadadba2 | |||
| 68a87e0a54 | |||
| 1d7dfe71ca | |||
| 1acc5e35e9 | |||
| df73cdbecc | |||
| 6738dfac85 | |||
| 11985f4019 | |||
| 7cad3aca13 | |||
| 2bbc12cd1a | |||
| 270db2b5ff | |||
| bce46418cd | |||
| 2d9b03a725 | |||
| c34752ac65 | |||
| 60680a36e2 | |||
| 4111bb5e4f | |||
| 612f5fe30e | |||
| d9776e99d3 | |||
| 960efcd428 | |||
| b07dc8484d | |||
| ebcae80a9d | |||
| ff8ea79620 | |||
| fe19b19fc7 | |||
| e709c2030c | |||
| 5725bbbe35 | |||
| 8553ab6e6d | |||
| 372bfdc5f6 | |||
| fad5c9e632 | |||
| 5fb5247d0e | |||
| 75be6aa82a | |||
| 0b8cc30d4d | |||
| d2a50cf9d2 | |||
| b2386a6a2b | |||
| 22fbe380eb | |||
| f2b27f31f4 | |||
| 867025b1ec | |||
| 9b0f54b111 | |||
| 4cf58f1faa | |||
| 1a56f957c1 | |||
| 1a4a2a56a3 | |||
| c701156ce6 | |||
| 4b6897aedf | |||
| 1471288969 | |||
| 7488fd7fd5 | |||
| 20056eff2c | |||
| 3f760fccaf | |||
| f833de765c | |||
| aa19ef5f71 | |||
| f0284107f9 | |||
| 91c94fe528 | |||
| 45fc49b216 | |||
| 0939d6d7bc | |||
| e7b5410509 | |||
| 2758e511ea | |||
| 46ee21f464 | |||
| 0287c9a260 | |||
| 665767c250 | |||
| 0fd8a13a93 | |||
| 125b931d0c | |||
| 27259c97db | |||
| 9479838614 | |||
| 8a203ff05f | |||
| 39f71730e0 | |||
| 33f993c19d | |||
| 18ec16eb6d | |||
| bf558922f9 | |||
| e87bda7584 | |||
| 87dc70436c | |||
| 5dd78177f4 | |||
| 203af4c471 | |||
| 98223d9e5a | |||
| e8cc0dfabb | |||
| c8585a6f9a | |||
| 7d79361aca | |||
| d1d32e465c | |||
| 42c6b21124 | |||
| 44e1dacb7b | |||
| eeea8a6327 | |||
| 0a5df26c31 | |||
| d3bc58d71c | |||
| 8d32d9788e | |||
| b18f488b01 | |||
| 4b4242df2a | |||
| e2533dedeb | |||
| 89619596ad | |||
| 02fee16561 | |||
| bf8521eb5e | |||
| 299244a37a | |||
| 9990fa3513 | |||
| db064d2321 | |||
| e0523aea63 | |||
| 5982fdea98 | |||
| c18be62b11 | |||
| 9f69b122d0 | |||
| 295aa139f6 | |||
| 2502cc5bf0 | |||
| fc7f2a2959 | |||
| 9d839a0328 | |||
| 55dada243e | |||
| 78e7c0bc3f | |||
| 9134456229 | |||
| 0ef71ecb49 | |||
| 3d18685d36 | |||
| f194bee4eb | |||
| f373d8e17c | |||
| 1b7d15e7d2 | |||
| 89d8c08cd1 | |||
| 3cd02a44cf | |||
| 87eb0cc808 | |||
| 8313210ddc | |||
| 2664f5cf20 | |||
| 47518268a6 | |||
| 29ee225c1f | |||
| ff6d2517be | |||
| 242bf08cf2 | |||
| 996de65688 | |||
| b97b66927f | |||
| 628d4cfb68 | |||
| ff002f18c6 | |||
| 5d5d3827fb | |||
| 8826242bf3 | |||
| 4bb1aa06f7 | |||
| 570ff8536c | |||
| 2c0d2b64a2 | |||
| 1750c0a26e | |||
| a102d68e99 | |||
| 61483b93e8 | |||
| 192d7645eb | |||
| f1c68fb0de | |||
| cb74bfd280 | |||
| 0d1717538b | |||
| 896ae0935a | |||
| 1a23073424 | |||
| 36f3b0d381 | |||
| 74335cacbf | |||
| 1ee2dd9c5e | |||
| c2f3790dc3 | |||
| 746c28004f | |||
| 084d4b28dd | |||
| ed6e83b121 | |||
| b827ba3a1c | |||
| 0f343f0b74 | |||
| 7a670c6ed9 | |||
| e690f88cda | |||
| c7f537959b | |||
| f074b703b3 | |||
| f8159645e0 | |||
| cccb83a926 | |||
| af8fe66c43 | |||
| 1f23aff22c | |||
| e580b78020 | |||
| a87a5ce273 | |||
| af1887e0a6 | |||
| 14bb2f6fbe | |||
| d144973acf | |||
| eecd78a008 | |||
| c03c142b14 | |||
| 40f3600de2 | |||
| c08e331f77 | |||
| a92989ed24 | |||
| 6c61eb3537 | |||
| 05c1274ec1 | |||
| f5bc78ae22 | |||
| 27ad8772ea | |||
| 135987745c | |||
| f039c891c7 | |||
| e69f4cfafe | |||
| a4020400da | |||
| d4aac22e34 | |||
| 601e717850 | |||
| 886b9abf3d | |||
| 9c87b9820e | |||
| 1fe332a44f | |||
| b6d2084c96 | |||
| ed896d976d | |||
| 677eecad41 | |||
| 74b6fa2ecc | |||
| effb6315e9 | |||
| 3c8390a248 | |||
| 8337a6dcd9 | |||
| ee194b03d0 | |||
| 2d8fa9c2f7 | |||
| b76481b34c | |||
| 147f6ac9ec | |||
| 84273cbdad | |||
| 885f92beeb | |||
| f12800f2d0 | |||
| a93b335dea | |||
| ad9463dcb9 | |||
| 242627199b | |||
| 22a8f67d81 | |||
| 4ee0eed2e5 | |||
| 20f39176c6 | |||
| ed792d71d3 | |||
| c3ae727ed1 | |||
| 0116dcdbe1 | |||
| 5ecbcc9f33 | |||
| 2d16e4a4e6 | |||
| bb9dc45e0c | |||
| 9dcecc0d58 | |||
| 92e63f5dd6 | |||
| 74cda6a1d0 | |||
| d7354cca33 | |||
| 7684cf0f4a | |||
| 3994c2c7c8 | |||
| a768d28e7c | |||
| efd99f16e0 | |||
| 61d6288fec | |||
| e3e45cd129 | |||
| 15191aedca | |||
| 63486f13f2 | |||
| 17455e40d1 | |||
| d54e593276 | |||
| c6a544a824 | |||
| e3ae3635bc | |||
| 6723bfb364 | |||
| 12c628e82c | |||
| 5f9abf69d3 | |||
| 351a687a2d | |||
| 68b0601a42 | |||
| 9455ffe837 | |||
| 02d2ff3e0c | |||
| 02572dd15c | |||
| 6d253cc74e | |||
| 4885a86833 | |||
| fd9ffb20b4 | |||
| 56ef2b077a | |||
| d4c7cfdf8b | |||
| 4d75d156e5 | |||
| d30a4d7ee5 | |||
| d1fce8ba75 | |||
| 0f74e4fcf9 | |||
| 4afcbd0957 | |||
| 50dc31447d | |||
| acf1a10f91 | |||
| 606f9b959a | |||
| f19b42baf3 | |||
| 2a3d0c8b0d | |||
| 4405fa6bbf | |||
| e2b2bf882a | |||
| 08e4eb1bf1 | |||
| f4b176a813 | |||
| efc7d817db | |||
| 1b17b1eb0c | |||
| fab6d4c2a2 | |||
| 223a3403c0 | |||
| dfd0d064de | |||
| 0c4882e658 | |||
| bfdad9fa32 | |||
| 37fd8dab84 | |||
| c143aefb01 | |||
| 05bf245b5a | |||
| f9e7128061 | |||
| 603e4b6fdf | |||
| 9ed37340cc | |||
| 599a1f2973 | |||
| 22424ca69c | |||
| 67a785cc22 | |||
| 4a4663607e | |||
| 4eb3842606 | |||
| 1ee8b51b2b | |||
| 130310e5cc | |||
| e933d7e5c7 | |||
| 44e8b5b882 | |||
| 28361e8b7d | |||
| 75921d6413 | |||
| 17226d8f75 | |||
| 0166999446 | |||
| 2cb1bf834a | |||
| 8006ca31e6 | |||
| 82b92b4733 | |||
| bf42d3ae2f | |||
| f63ae76931 | |||
| feded4ac25 | |||
| f046787523 | |||
| bbd73550e8 | |||
| fce560dd89 | |||
| 4776dc0788 | |||
| d8bd6da563 | |||
| b569a32170 | |||
| 06e20666bd | |||
| 36687c5c67 | |||
| dd9b20030a | |||
| c0db2a987b | |||
| c3ff2ed6c1 | |||
| 271f42cf0e | |||
| 01e15584da | |||
| 17b91db2ef | |||
| 3e786d02ac | |||
| c8f2db69df | |||
| b4e0682801 | |||
| af10c1b076 | |||
| e6a0eab667 | |||
| 523b9d2736 | |||
| 011d2f2f2a | |||
| c37546d273 | |||
| fbedebb938 | |||
| 1773a3acc8 | |||
| 058c02150c | |||
| 8d841ead50 | |||
| 152dafbdf7 | |||
| 7dbd2fc556 | |||
| 85e0924669 | |||
| 97919f646d | |||
| 696cc1b359 | |||
| 151de2eab4 | |||
| e7f6464564 | |||
| da681c946e | |||
| e0e43eece5 | |||
| 7013389288 | |||
| 443520ecae | |||
| 96d7285111 | |||
| 7e6fc44191 | |||
| 9cb4ebaa7f | |||
| 3e4d7e7ed8 | |||
| 3a6951d93d | |||
| 391096872e | |||
| eebf6fcae4 | |||
| 8b7223cf35 | |||
| e1fec48a0e | |||
| 0778ee8dd7 | |||
| 14d5160674 | |||
| eabf787305 | |||
| 980e4ff5dd | |||
| 1ac193c506 | |||
| d6ed071bc6 | |||
| a3d53d40fb | |||
| a17dfbe41f | |||
| b9a81baa47 | |||
| 97b236225e | |||
| 8113146b86 | |||
| e6625113db | |||
| 2643504eb5 | |||
| f7518d1bab | |||
| 800ca798cd | |||
| d714ffb4f9 | |||
| 7bc186a03c | |||
| b94b87d186 | |||
| 0289be60ef | |||
| 93922b65e3 | |||
| c6608748c6 | |||
| 8e383d55d7 | |||
| 8926026b5b | |||
| 464479b986 | |||
| de1c84914c | |||
| 8e7766e694 | |||
| 4858dbc5dc | |||
| c4e131b92d | |||
| 90e0d9139c | |||
| 2e54be76d6 | |||
| 7914268702 | |||
| 4bdb85834c | |||
| f89a37936f | |||
| 8bb7ea6be6 | |||
| 60a1f7fa99 | |||
| b3b8182f85 | |||
| 19094b492e | |||
| 56e660714c | |||
| eb632056a2 | |||
| fbc9de0335 | |||
| 5c7539cd26 | |||
| 7c20e5fdb5 | |||
| f79e7126ee | |||
| 15e92bc6af | |||
| 47fc96fe00 | |||
| 4ac5ad42c6 | |||
| c93153672f | |||
| a46dd448f4 | |||
| 01f75112ce | |||
| 051b98bc02 | |||
| 5d0bc1b335 | |||
| 43d011538e | |||
| f3ad5e90ff | |||
| 9b867c02e0 | |||
| 2ba09868ba | |||
| 9394754593 | |||
| 41bab3d6ba | |||
| 99b686b2d7 | |||
| daad98bbfe | |||
| 2fb01577af | |||
| 96d21f27a5 | |||
| 793110f315 | |||
| 5ed5c5d52d | |||
| 01385ee46b | |||
| 0eba3aca1f | |||
| 83a5370196 | |||
| b0b6247f06 | |||
| 2a33716166 | |||
| 9b26454c72 | |||
| 97b933d9ea | |||
| ff70aadfdb | |||
| ae53f92d40 | |||
| f51e1d3154 | |||
| a21f94569f | |||
| b9255bffe7 | |||
| e1f8400343 | |||
| 52493831cc | |||
| 00ba7afac4 | |||
| c703352ac3 | |||
| 97e663febf | |||
| 3891b54f42 | |||
| 9a5959e46c | |||
| 780c717409 | |||
| 34c6df344d | |||
| 670347d4a0 | |||
| e0c4b3b407 | |||
| cffffab8ea | |||
| 527bd2889e | |||
| 15dc4ce03a | |||
| 4f629227ab | |||
| caa799853e | |||
| 3590215d33 | |||
| d6aafc61bd | |||
| 357211a4cc | |||
| c4c4332485 | |||
| 2cec8d9f70 | |||
| 8348d7c4bc | |||
| c33068d831 | |||
| f28b963f9d | |||
| 7fc77f993f | |||
| 0e0b21d8c3 | |||
| f3ad0eb3aa | |||
| 036b861b05 | |||
| 880546468c | |||
| 1a9c9ba4cb | |||
| a9e7140a88 | |||
| 808633deb7 | |||
| ee4fcd926b | |||
| 9d1229fe0c | |||
| d54c8df7b3 | |||
| d495f5b4c5 | |||
| 788bef188d | |||
| bff9be03cc | |||
| 44424e0687 | |||
| 4b43dcd256 | |||
| 47805fb60c | |||
| abe2fc431e | |||
| 46c694d95a | |||
| faced77161 | |||
| 32f0b9d71c | |||
| bbdcc0a8c2 | |||
| 67bf975034 | |||
| da7300a78c | |||
| 5c5179a553 | |||
| dcf78d0f76 | |||
| 4836ea3bcf | |||
| 182392bf1c | |||
| c6de540c8a | |||
| 82fb5e7a93 | |||
| cbcc6282df | |||
| 614ac4a262 | |||
| 80d49e03d6 | |||
| 7016fcdb79 | |||
| f030889d6c | |||
| d50aff30c9 | |||
| 5cbfae677a | |||
| d660babecd | |||
| cb06c20864 | |||
| c98e8d384a | |||
| 446eeba094 | |||
| e841bf44ca | |||
| 1991bd8525 | |||
| 6c6d7d463d | |||
| acdf13cb7b | 
							
								
								
									
										14
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										14
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							@@ -1,4 +1,14 @@
 | 
				
			|||||||
/.vscode
 | 
					/.vscode
 | 
				
			||||||
/bin
 | 
					/bin
 | 
				
			||||||
/zig-cache
 | 
					**/zig-cache
 | 
				
			||||||
/zig-out
 | 
					**/zig-out
 | 
				
			||||||
 | 
					/docs
 | 
				
			||||||
 | 
					**/*.log
 | 
				
			||||||
 | 
					**/*.bin
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Build on Windows
 | 
				
			||||||
 | 
					/.build_config
 | 
				
			||||||
 | 
					/lib/SDL2
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Any Custom Scripts for Debugging purposes
 | 
				
			||||||
 | 
					*.sh
 | 
				
			||||||
							
								
								
									
										15
									
								
								.gitmodules
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								.gitmodules
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,15 @@
 | 
				
			|||||||
 | 
					[submodule "lib/SDL.zig"]
 | 
				
			||||||
 | 
						path = lib/SDL.zig
 | 
				
			||||||
 | 
						url = https://github.com/MasterQ32/SDL.zig
 | 
				
			||||||
 | 
					[submodule "lib/zig-clap"]
 | 
				
			||||||
 | 
						path = lib/zig-clap
 | 
				
			||||||
 | 
						url = https://github.com/Hejsil/zig-clap
 | 
				
			||||||
 | 
					[submodule "lib/known-folders"]
 | 
				
			||||||
 | 
						path = lib/known-folders
 | 
				
			||||||
 | 
						url = https://github.com/ziglibs/known-folders
 | 
				
			||||||
 | 
					[submodule "lib/zig-datetime"]
 | 
				
			||||||
 | 
						path = lib/zig-datetime
 | 
				
			||||||
 | 
						url = https://github.com/frmdstryr/zig-datetime
 | 
				
			||||||
 | 
					[submodule "lib/zig-toml"]
 | 
				
			||||||
 | 
						path = lib/zig-toml
 | 
				
			||||||
 | 
						url = https://github.com/aeronavery/zig-toml
 | 
				
			||||||
							
								
								
									
										8
									
								
								.vscode/extensions.json
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								.vscode/extensions.json
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,8 @@
 | 
				
			|||||||
 | 
					{
 | 
				
			||||||
 | 
					    "recommendations": [
 | 
				
			||||||
 | 
					        "augusterame.zls-vscode",
 | 
				
			||||||
 | 
					        "usernamehw.errorlens",
 | 
				
			||||||
 | 
					        "vadimcn.vscode-lldb",
 | 
				
			||||||
 | 
					        "dan-c-underwood.arm"
 | 
				
			||||||
 | 
					    ]
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										86
									
								
								README.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										86
									
								
								README.md
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,86 @@
 | 
				
			|||||||
 | 
					# ZBA (working title)
 | 
				
			||||||
 | 
					A Game Boy Advance Emulator written in Zig ⚡!
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## Scope
 | 
				
			||||||
 | 
					I'm hardly the first to write a Game Boy Advance Emulator nor will I be the last. This project isn't going to compete with the GOATs like 
 | 
				
			||||||
 | 
					[mGBA](https://github.com/mgba-emu) or [NanoBoyAdvance](https://github.com/nba-emu/NanoBoyAdvance). There aren't any interesting
 | 
				
			||||||
 | 
					ideas either like in [DSHBA](https://github.com/DenSinH/DSHBA). 
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					This is a simple (read: incomplete) for-fun long-term project. I hope to get "mostly there", which to me means that I'm not missing any major hardware
 | 
				
			||||||
 | 
					features and the set of possible improvements would be in memory timing or in UI/UX. With respect to that goal, here's what's outstanding: 
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### TODO 
 | 
				
			||||||
 | 
					- [ ] Affine Sprites
 | 
				
			||||||
 | 
					- [ ] Windowing (see [this branch](https://git.musuka.dev/paoda/zba/src/branch/window))
 | 
				
			||||||
 | 
					- [ ] Audio Resampler (Having issues with SDL2's)
 | 
				
			||||||
 | 
					- [ ] Immediate Mode GUI
 | 
				
			||||||
 | 
					- [ ] Refactoring for easy-ish perf boosts
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## Tests 
 | 
				
			||||||
 | 
					- [x] [jsmolka's GBA Test Collection](https://github.com/jsmolka/gba-tests)
 | 
				
			||||||
 | 
					    - [x] `arm.gba` and `thumb.gba`
 | 
				
			||||||
 | 
					    - [x] `flash64.gba`, `flash128.gba`, `none.gba`, and `sram.gba`
 | 
				
			||||||
 | 
					    - [x] `hello.gba`, `shades.gba`, and `stripes.gba`
 | 
				
			||||||
 | 
					    - [x] `memory.gba`
 | 
				
			||||||
 | 
					    - [x] `bios.gba`
 | 
				
			||||||
 | 
					    - [x] `nes.gba`
 | 
				
			||||||
 | 
					- [ ] [DenSinH's GBA ROMs](https://github.com/DenSinH/GBARoms)
 | 
				
			||||||
 | 
					    - [x] `eeprom-test` and `flash-test`
 | 
				
			||||||
 | 
					    - [x] `midikey2freq`
 | 
				
			||||||
 | 
					    - [ ] `swi-tests-random`
 | 
				
			||||||
 | 
					- [ ] [destoer's GBA Tests](https://github.com/destoer/gba_tests)
 | 
				
			||||||
 | 
					    - [x] `cond_invalid.gba`
 | 
				
			||||||
 | 
					    - [x] `dma_priority.gba`
 | 
				
			||||||
 | 
					    - [x] `hello_world.gba`
 | 
				
			||||||
 | 
					    - [x] `if_ack.gba`
 | 
				
			||||||
 | 
					    - [ ] `line_timing.gba`
 | 
				
			||||||
 | 
					    - [ ] `lyc_midline.gba`
 | 
				
			||||||
 | 
					    - [ ] `window_midframe.gba`
 | 
				
			||||||
 | 
					- [x] [ladystarbreeze's GBA Test Collection](https://github.com/ladystarbreeze/GBA-Test-Collection)
 | 
				
			||||||
 | 
					    - [x] `retAddr.gba`
 | 
				
			||||||
 | 
					    - [x] `helloWorld.gba`
 | 
				
			||||||
 | 
					    - [x] `helloAudio.gba`
 | 
				
			||||||
 | 
					- [x] [`armwrestler-gba-fixed.gba`](https://github.com/destoer/armwrestler-gba-fixed)
 | 
				
			||||||
 | 
					- [x] [FuzzARM](https://github.com/DenSinH/FuzzARM)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## Resources
 | 
				
			||||||
 | 
					* [GBATEK](https://problemkaputt.de/gbatek.htm)
 | 
				
			||||||
 | 
					* [TONC](https://coranac.com/tonc/text/toc.htm)
 | 
				
			||||||
 | 
					* [ARM Architecture Reference Manual](https://www.intel.com/content/dam/www/programmable/us/en/pdfs/literature/third-party/ddi0100e_arm_arm.pdf)
 | 
				
			||||||
 | 
					* [ARM7TDMI Data Sheet](https://www.dca.fee.unicamp.br/cursos/EA871/references/ARM/ARM7TDMIDataSheet.pdf)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## Compiling
 | 
				
			||||||
 | 
					Most recently built on Zig [0.10.0-dev.4474+b41b35f57](https://github.com/ziglang/zig/tree/b41b35f57)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### Dependencies
 | 
				
			||||||
 | 
					* [SDL.zig](https://github.com/MasterQ32/SDL.zig)
 | 
				
			||||||
 | 
					    * [SDL2](https://www.libsdl.org/download-2.0.php)
 | 
				
			||||||
 | 
					* [zig-clap](https://github.com/Hejsil/zig-clap)
 | 
				
			||||||
 | 
					* [known-folders](https://github.com/ziglibs/known-folders)
 | 
				
			||||||
 | 
					* [zig-toml](https://github.com/aeronavery/zig-toml)
 | 
				
			||||||
 | 
					* [zig-datetime](https://github.com/frmdstryr/zig-datetime)
 | 
				
			||||||
 | 
					* [`bitfields.zig`](https://github.com/FlorenceOS/Florence/blob/aaa5a9e568/lib/util/bitfields.zig)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					`bitfields.zig` from [FlorenceOS](https://github.com/FlorenceOS) is included under `lib/util/bitfield.zig`.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Use `git submodule update --init` from the project root to pull the git submodules `SDL.zig`, `zig-clap`, `known-folders`, `zig-toml` and `zig-datetime`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Be sure to provide SDL2 using: 
 | 
				
			||||||
 | 
					* Linux: Your distro's package manager
 | 
				
			||||||
 | 
					* MacOS: ¯\\\_(ツ)_/¯
 | 
				
			||||||
 | 
					* Windows: [`vcpkg`](https://github.com/Microsoft/vcpkg) (install `sdl2:x64-windows`)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					`SDL.zig` will provide a helpful compile error if the zig compiler is unable to find SDL2. 
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Once you've got all the dependencies, execute `zig build -Drelease-fast`. The executable is located at `zig-out/bin/`. 
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## Controls
 | 
				
			||||||
 | 
					Key | Button
 | 
				
			||||||
 | 
					--- | ---
 | 
				
			||||||
 | 
					<kbd>X</kbd> | A
 | 
				
			||||||
 | 
					<kbd>Z</kbd> | B
 | 
				
			||||||
 | 
					<kbd>A</kbd> | L
 | 
				
			||||||
 | 
					<kbd>S</kbd> | R
 | 
				
			||||||
 | 
					<kbd>Return</kbd> | Start
 | 
				
			||||||
 | 
					<kbd>RShift</kbd> | Select
 | 
				
			||||||
 | 
					Arrow Keys | D-Pad
 | 
				
			||||||
							
								
								
									
										27
									
								
								build.zig
									
									
									
									
									
								
							
							
						
						
									
										27
									
								
								build.zig
									
									
									
									
									
								
							@@ -1,4 +1,5 @@
 | 
				
			|||||||
const std = @import("std");
 | 
					const std = @import("std");
 | 
				
			||||||
 | 
					const Sdk = @import("lib/SDL.zig/Sdk.zig");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
pub fn build(b: *std.build.Builder) void {
 | 
					pub fn build(b: *std.build.Builder) void {
 | 
				
			||||||
    // Standard target options allows the person running `zig build` to choose
 | 
					    // Standard target options allows the person running `zig build` to choose
 | 
				
			||||||
@@ -12,7 +13,33 @@ pub fn build(b: *std.build.Builder) void {
 | 
				
			|||||||
    const mode = b.standardReleaseOptions();
 | 
					    const mode = b.standardReleaseOptions();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const exe = b.addExecutable("zba", "src/main.zig");
 | 
					    const exe = b.addExecutable("zba", "src/main.zig");
 | 
				
			||||||
 | 
					    exe.setMainPkgPath("."); // Necessary so that src/main.zig can embed example.toml
 | 
				
			||||||
    exe.setTarget(target);
 | 
					    exe.setTarget(target);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Known Folders (%APPDATA%, XDG, etc.)
 | 
				
			||||||
 | 
					    exe.addPackagePath("known_folders", "lib/known-folders/known-folders.zig");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // DateTime Library
 | 
				
			||||||
 | 
					    exe.addPackagePath("datetime", "lib/zig-datetime/src/main.zig");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Bitfield type from FlorenceOS: https://github.com/FlorenceOS/
 | 
				
			||||||
 | 
					    // exe.addPackage(.{ .name = "bitfield", .path = .{ .path = "lib/util/bitfield.zig" } });
 | 
				
			||||||
 | 
					    exe.addPackagePath("bitfield", "lib/util/bitfield.zig");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Argument Parsing Library
 | 
				
			||||||
 | 
					    exe.addPackagePath("clap", "lib/zig-clap/clap.zig");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // TOML Library
 | 
				
			||||||
 | 
					    exe.addPackagePath("toml", "lib/zig-toml/src/toml.zig");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // OpenGL 3.3 Bindings
 | 
				
			||||||
 | 
					    exe.addPackagePath("gl", "lib/gl.zig");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Zig SDL Bindings: https://github.com/MasterQ32/SDL.zig
 | 
				
			||||||
 | 
					    const sdk = Sdk.init(b);
 | 
				
			||||||
 | 
					    sdk.link(exe, .dynamic);
 | 
				
			||||||
 | 
					    exe.addPackage(sdk.getNativePackage("sdl2"));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    exe.setBuildMode(mode);
 | 
					    exe.setBuildMode(mode);
 | 
				
			||||||
    exe.install();
 | 
					    exe.install();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										25
									
								
								example.toml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								example.toml
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,25 @@
 | 
				
			|||||||
 | 
					[Host]
 | 
				
			||||||
 | 
					# Using nearest-neighbour scaling, how many times the native resolution 
 | 
				
			||||||
 | 
					# of the game bow should the screen be?
 | 
				
			||||||
 | 
					win_scale = 4
 | 
				
			||||||
 | 
					# Enable VSYNC on the UI thread
 | 
				
			||||||
 | 
					vsync = true
 | 
				
			||||||
 | 
					# Mute ZBA
 | 
				
			||||||
 | 
					mute = false
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[Guest]
 | 
				
			||||||
 | 
					# Sync Emulation to Audio 
 | 
				
			||||||
 | 
					audio_sync = false
 | 
				
			||||||
 | 
					# Sync Emulation to Video
 | 
				
			||||||
 | 
					video_sync = false
 | 
				
			||||||
 | 
					# Force RTC support
 | 
				
			||||||
 | 
					force_rtc = false
 | 
				
			||||||
 | 
					# Skip BIOS
 | 
				
			||||||
 | 
					skip_bios = false
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[Debug]
 | 
				
			||||||
 | 
					# Enable detailed CPU logs
 | 
				
			||||||
 | 
					cpu_trace = false
 | 
				
			||||||
 | 
					# When false and builtin.mode == .Debug, ZBA will panic
 | 
				
			||||||
 | 
					# on unknown I/O reads
 | 
				
			||||||
 | 
					unhandled_io = true
 | 
				
			||||||
							
								
								
									
										1
									
								
								lib/SDL.zig
									
									
									
									
									
										Submodule
									
								
							
							
								
								
								
								
								
							
						
						
									
										1
									
								
								lib/SDL.zig
									
									
									
									
									
										Submodule
									
								
							 Submodule lib/SDL.zig added at 6a9e37687a
									
								
							
							
								
								
									
										5028
									
								
								lib/gl.zig
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5028
									
								
								lib/gl.zig
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										1
									
								
								lib/known-folders
									
									
									
									
									
										Submodule
									
								
							
							
								
								
								
								
								
							
						
						
									
										1
									
								
								lib/known-folders
									
									
									
									
									
										Submodule
									
								
							 Submodule lib/known-folders added at 24845b0103
									
								
							
							
								
								
									
										146
									
								
								lib/util/bitfield.zig
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										146
									
								
								lib/util/bitfield.zig
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,146 @@
 | 
				
			|||||||
 | 
					const std = @import("std");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fn PtrCastPreserveCV(comptime T: type, comptime PtrToT: type, comptime NewT: type) type {
 | 
				
			||||||
 | 
					    return switch (PtrToT) {
 | 
				
			||||||
 | 
					        *T => *NewT,
 | 
				
			||||||
 | 
					        *const T => *const NewT,
 | 
				
			||||||
 | 
					        *volatile T => *volatile NewT,
 | 
				
			||||||
 | 
					        *const volatile T => *const volatile NewT,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        else => @compileError("wtf you doing"),
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fn BitType(comptime FieldType: type, comptime ValueType: type, comptime shamt: usize) type {
 | 
				
			||||||
 | 
					    const self_bit: FieldType = (1 << shamt);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return extern struct {
 | 
				
			||||||
 | 
					        bits: Bitfield(FieldType, shamt, 1),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        pub fn set(self: anytype) void {
 | 
				
			||||||
 | 
					            self.bits.field().* |= self_bit;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        pub fn unset(self: anytype) void {
 | 
				
			||||||
 | 
					            self.bits.field().* &= ~self_bit;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        pub fn read(self: anytype) ValueType {
 | 
				
			||||||
 | 
					            return @bitCast(ValueType, @truncate(u1, self.bits.field().* >> shamt));
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Since these are mostly used with MMIO, I want to avoid
 | 
				
			||||||
 | 
					        // reading the memory just to write it again, also races
 | 
				
			||||||
 | 
					        pub fn write(self: anytype, val: ValueType) void {
 | 
				
			||||||
 | 
					            if (@bitCast(bool, val)) {
 | 
				
			||||||
 | 
					                self.set();
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					                self.unset();
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Original Bit Constructor
 | 
				
			||||||
 | 
					// pub fn Bit(comptime FieldType: type, comptime shamt: usize) type {
 | 
				
			||||||
 | 
					//     return BitType(FieldType, u1, shamt);
 | 
				
			||||||
 | 
					// }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn Bit(comptime FieldType: type, comptime shamt: usize) type {
 | 
				
			||||||
 | 
					    return BitType(FieldType, bool, shamt);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fn Boolean(comptime FieldType: type, comptime shamt: usize) type {
 | 
				
			||||||
 | 
					    return BitType(FieldType, bool, shamt);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn Bitfield(comptime FieldType: type, comptime shamt: usize, comptime num_bits: usize) type {
 | 
				
			||||||
 | 
					    if (shamt + num_bits > @bitSizeOf(FieldType)) {
 | 
				
			||||||
 | 
					        @compileError("bitfield doesn't fit");
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const self_mask: FieldType = ((1 << num_bits) - 1) << shamt;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const ValueType = std.meta.Int(.unsigned, num_bits);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return extern struct {
 | 
				
			||||||
 | 
					        dummy: FieldType,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        fn field(self: anytype) PtrCastPreserveCV(@This(), @TypeOf(self), FieldType) {
 | 
				
			||||||
 | 
					            return @ptrCast(PtrCastPreserveCV(@This(), @TypeOf(self), FieldType), self);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        pub fn write(self: anytype, val: ValueType) void {
 | 
				
			||||||
 | 
					            self.field().* &= ~self_mask;
 | 
				
			||||||
 | 
					            self.field().* |= @intCast(FieldType, val) << shamt;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        pub fn read(self: anytype) ValueType {
 | 
				
			||||||
 | 
					            const val: FieldType = self.field().*;
 | 
				
			||||||
 | 
					            return @intCast(ValueType, (val & self_mask) >> shamt);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					test "bit" {
 | 
				
			||||||
 | 
					    const S = extern union {
 | 
				
			||||||
 | 
					        low: Bit(u32, 0),
 | 
				
			||||||
 | 
					        high: Bit(u32, 1),
 | 
				
			||||||
 | 
					        val: u32,
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    std.testing.expect(@sizeOf(S) == 4);
 | 
				
			||||||
 | 
					    std.testing.expect(@bitSizeOf(S) == 32);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    var s: S = .{ .val = 1 };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    std.testing.expect(s.low.read() == 1);
 | 
				
			||||||
 | 
					    std.testing.expect(s.high.read() == 0);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    s.low.write(0);
 | 
				
			||||||
 | 
					    s.high.write(1);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    std.testing.expect(s.val == 2);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					test "boolean" {
 | 
				
			||||||
 | 
					    const S = extern union {
 | 
				
			||||||
 | 
					        low: Boolean(u32, 0),
 | 
				
			||||||
 | 
					        high: Boolean(u32, 1),
 | 
				
			||||||
 | 
					        val: u32,
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    std.testing.expect(@sizeOf(S) == 4);
 | 
				
			||||||
 | 
					    std.testing.expect(@bitSizeOf(S) == 32);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    var s: S = .{ .val = 2 };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    std.testing.expect(s.low.read() == false);
 | 
				
			||||||
 | 
					    std.testing.expect(s.high.read() == true);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    s.low.write(true);
 | 
				
			||||||
 | 
					    s.high.write(false);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    std.testing.expect(s.val == 1);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					test "bitfield" {
 | 
				
			||||||
 | 
					    const S = extern union {
 | 
				
			||||||
 | 
					        low: Bitfield(u32, 0, 16),
 | 
				
			||||||
 | 
					        high: Bitfield(u32, 16, 16),
 | 
				
			||||||
 | 
					        val: u32,
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    std.testing.expect(@sizeOf(S) == 4);
 | 
				
			||||||
 | 
					    std.testing.expect(@bitSizeOf(S) == 32);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    var s: S = .{ .val = 0x13376969 };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    std.testing.expect(s.low.read() == 0x6969);
 | 
				
			||||||
 | 
					    std.testing.expect(s.high.read() == 0x1337);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    s.low.write(0x1337);
 | 
				
			||||||
 | 
					    s.high.write(0x6969);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    std.testing.expect(s.val == 0x69691337);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										1
									
								
								lib/zig-clap
									
									
									
									
									
										Submodule
									
								
							
							
								
								
								
								
								
							
						
						
									
										1
									
								
								lib/zig-clap
									
									
									
									
									
										Submodule
									
								
							 Submodule lib/zig-clap added at e5d09c4b2d
									
								
							
							
								
								
									
										1
									
								
								lib/zig-datetime
									
									
									
									
									
										Submodule
									
								
							
							
								
								
								
								
								
							
						
						
									
										1
									
								
								lib/zig-datetime
									
									
									
									
									
										Submodule
									
								
							 Submodule lib/zig-datetime added at 5ec1c36cf3
									
								
							
							
								
								
									
										1
									
								
								lib/zig-toml
									
									
									
									
									
										Submodule
									
								
							
							
								
								
								
								
								
							
						
						
									
										1
									
								
								lib/zig-toml
									
									
									
									
									
										Submodule
									
								
							 Submodule lib/zig-toml added at 5dfa919e03
									
								
							
							
								
								
									
										44
									
								
								src/bus.zig
									
									
									
									
									
								
							
							
						
						
									
										44
									
								
								src/bus.zig
									
									
									
									
									
								
							@@ -1,44 +0,0 @@
 | 
				
			|||||||
const std = @import("std");
 | 
					 | 
				
			||||||
const GamePak = @import("pak.zig").GamePak;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const Allocator = std.mem.Allocator;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
pub const Bus = struct {
 | 
					 | 
				
			||||||
    pak: GamePak,
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    pub fn withPak(alloc: Allocator, path: []const u8) !@This() {
 | 
					 | 
				
			||||||
        return @This(){
 | 
					 | 
				
			||||||
            .pak = try GamePak.fromPath(alloc, path),
 | 
					 | 
				
			||||||
        };
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    pub fn readWord(self: *const @This(), addr: u32) u32 {
 | 
					 | 
				
			||||||
        return self.pak.readWord(addr);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    pub fn writeWord(_: *@This(), _: u32, _: u32) void {
 | 
					 | 
				
			||||||
        std.debug.panic("TODO: Implement Bus#writeWord", .{});
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    pub fn readHalfWord(self: *const @This(), addr: u32) u16 {
 | 
					 | 
				
			||||||
        return self.pak.readHalfWord(addr);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    pub fn writeHalfWord(self: *@This(), addr: u32, halfword: u16) void {
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        // TODO: Actually implement the memory mmap
 | 
					 | 
				
			||||||
        if (addr >= self.pak.buf.len) {
 | 
					 | 
				
			||||||
            return;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        self.pak.writeHalfWord(addr, halfword);
 | 
					 | 
				
			||||||
    } 
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    pub fn readByte(self: *const @This(), addr: u32) u8 {
 | 
					 | 
				
			||||||
        return self.pak.readByte(addr);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    pub fn writeByte(_: *@This(), _: u32, _: u8) void {
 | 
					 | 
				
			||||||
        std.debug.panic("TODO: Implement Bus#writeByte", .{});
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
							
								
								
									
										83
									
								
								src/config.zig
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										83
									
								
								src/config.zig
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,83 @@
 | 
				
			|||||||
 | 
					const std = @import("std");
 | 
				
			||||||
 | 
					const toml = @import("toml");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const Allocator = std.mem.Allocator;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const log = std.log.scoped(.Config);
 | 
				
			||||||
 | 
					var state: Config = .{};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const Config = struct {
 | 
				
			||||||
 | 
					    host: Host = .{},
 | 
				
			||||||
 | 
					    guest: Guest = .{},
 | 
				
			||||||
 | 
					    debug: Debug = .{},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /// Settings related to the Computer the Emulator is being run on
 | 
				
			||||||
 | 
					    const Host = struct {
 | 
				
			||||||
 | 
					        /// Using Nearest-Neighbor, multiply the resolution of the GBA Window
 | 
				
			||||||
 | 
					        win_scale: i64 = 3,
 | 
				
			||||||
 | 
					        /// Enable Vsync
 | 
				
			||||||
 | 
					        ///
 | 
				
			||||||
 | 
					        /// Note: This does not affect whether Emulation is synced to 59Hz
 | 
				
			||||||
 | 
					        vsync: bool = true,
 | 
				
			||||||
 | 
					        /// Mute ZBA
 | 
				
			||||||
 | 
					        mute: bool = false,
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Settings realted to the emulation itself
 | 
				
			||||||
 | 
					    const Guest = struct {
 | 
				
			||||||
 | 
					        /// Whether Emulation thread to sync to Audio Callbacks
 | 
				
			||||||
 | 
					        audio_sync: bool = true,
 | 
				
			||||||
 | 
					        /// Whether Emulation thread should sync to 59Hz
 | 
				
			||||||
 | 
					        video_sync: bool = true,
 | 
				
			||||||
 | 
					        /// Whether RTC I/O should always be enabled
 | 
				
			||||||
 | 
					        force_rtc: bool = false,
 | 
				
			||||||
 | 
					        /// Skip BIOS
 | 
				
			||||||
 | 
					        skip_bios: bool = false,
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /// Settings related to debugging ZBA
 | 
				
			||||||
 | 
					    const Debug = struct {
 | 
				
			||||||
 | 
					        /// Enable CPU Trace logs
 | 
				
			||||||
 | 
					        cpu_trace: bool = false,
 | 
				
			||||||
 | 
					        /// If false and ZBA is built in debug mode, ZBA will panic on unhandled I/O
 | 
				
			||||||
 | 
					        unhandled_io: bool = true,
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn config() *const Config {
 | 
				
			||||||
 | 
					    return &state;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// Reads a config file and then loads it into the global state
 | 
				
			||||||
 | 
					pub fn load(allocator: Allocator, config_path: []const u8) !void {
 | 
				
			||||||
 | 
					    var config_file = try std.fs.cwd().openFile(config_path, .{});
 | 
				
			||||||
 | 
					    defer config_file.close();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    log.info("loaded from {s}", .{config_path});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const contents = try config_file.readToEndAlloc(allocator, try config_file.getEndPos());
 | 
				
			||||||
 | 
					    defer allocator.free(contents);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const table = try toml.parseContents(allocator, contents, null);
 | 
				
			||||||
 | 
					    defer table.deinit();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // TODO: Report unknown config options
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (table.keys.get("Host")) |host| {
 | 
				
			||||||
 | 
					        if (host.Table.keys.get("win_scale")) |scale| state.host.win_scale = scale.Integer;
 | 
				
			||||||
 | 
					        if (host.Table.keys.get("vsync")) |vsync| state.host.vsync = vsync.Boolean;
 | 
				
			||||||
 | 
					        if (host.Table.keys.get("mute")) |mute| state.host.mute = mute.Boolean;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (table.keys.get("Guest")) |guest| {
 | 
				
			||||||
 | 
					        if (guest.Table.keys.get("audio_sync")) |sync| state.guest.audio_sync = sync.Boolean;
 | 
				
			||||||
 | 
					        if (guest.Table.keys.get("video_sync")) |sync| state.guest.video_sync = sync.Boolean;
 | 
				
			||||||
 | 
					        if (guest.Table.keys.get("force_rtc")) |forced| state.guest.force_rtc = forced.Boolean;
 | 
				
			||||||
 | 
					        if (guest.Table.keys.get("skip_bios")) |skip| state.guest.skip_bios = skip.Boolean;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (table.keys.get("Debug")) |debug| {
 | 
				
			||||||
 | 
					        if (debug.Table.keys.get("cpu_trace")) |trace| state.debug.cpu_trace = trace.Boolean;
 | 
				
			||||||
 | 
					        if (debug.Table.keys.get("unhandled_io")) |unhandled| state.debug.unhandled_io = unhandled.Boolean;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										257
									
								
								src/core/Bus.zig
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										257
									
								
								src/core/Bus.zig
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,257 @@
 | 
				
			|||||||
 | 
					const std = @import("std");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const AudioDeviceId = @import("sdl2").SDL_AudioDeviceID;
 | 
				
			||||||
 | 
					const Arm7tdmi = @import("cpu.zig").Arm7tdmi;
 | 
				
			||||||
 | 
					const Bios = @import("bus/Bios.zig");
 | 
				
			||||||
 | 
					const Ewram = @import("bus/Ewram.zig");
 | 
				
			||||||
 | 
					const GamePak = @import("bus/GamePak.zig");
 | 
				
			||||||
 | 
					const Io = @import("bus/io.zig").Io;
 | 
				
			||||||
 | 
					const Iwram = @import("bus/Iwram.zig");
 | 
				
			||||||
 | 
					const Ppu = @import("ppu.zig").Ppu;
 | 
				
			||||||
 | 
					const Apu = @import("apu.zig").Apu;
 | 
				
			||||||
 | 
					const DmaTuple = @import("bus/dma.zig").DmaTuple;
 | 
				
			||||||
 | 
					const TimerTuple = @import("bus/timer.zig").TimerTuple;
 | 
				
			||||||
 | 
					const Scheduler = @import("scheduler.zig").Scheduler;
 | 
				
			||||||
 | 
					const FilePaths = @import("../util.zig").FilePaths;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const io = @import("bus/io.zig");
 | 
				
			||||||
 | 
					const Allocator = std.mem.Allocator;
 | 
				
			||||||
 | 
					const log = std.log.scoped(.Bus);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const createDmaTuple = @import("bus/dma.zig").create;
 | 
				
			||||||
 | 
					const createTimerTuple = @import("bus/timer.zig").create;
 | 
				
			||||||
 | 
					const rotr = @import("../util.zig").rotr;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const timings: [2][0x10]u8 = [_][0x10]u8{
 | 
				
			||||||
 | 
					    // BIOS, Unused, EWRAM, IWRAM, I/0, PALRAM, VRAM, OAM, ROM0, ROM0, ROM1, ROM1, ROM2, ROM2, SRAM, Unused
 | 
				
			||||||
 | 
					    [_]u8{ 1, 1, 3, 1, 1, 1, 1, 1, 5, 5, 5, 5, 5, 5, 5, 5 }, // 8-bit & 16-bit
 | 
				
			||||||
 | 
					    [_]u8{ 1, 1, 6, 1, 1, 2, 2, 1, 8, 8, 8, 8, 8, 8, 8, 8 }, // 32-bit
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub const fetch_timings: [2][0x10]u8 = [_][0x10]u8{
 | 
				
			||||||
 | 
					    // BIOS, Unused, EWRAM, IWRAM, I/0, PALRAM, VRAM, OAM, ROM0, ROM0, ROM1, ROM1, ROM2, ROM2, SRAM, Unused
 | 
				
			||||||
 | 
					    [_]u8{ 1, 1, 3, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 5, 5 }, // 8-bit & 16-bit
 | 
				
			||||||
 | 
					    [_]u8{ 1, 1, 6, 1, 1, 2, 2, 1, 4, 4, 4, 4, 4, 4, 8, 8 }, // 32-bit
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const Self = @This();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pak: GamePak,
 | 
				
			||||||
 | 
					bios: Bios,
 | 
				
			||||||
 | 
					ppu: Ppu,
 | 
				
			||||||
 | 
					apu: Apu,
 | 
				
			||||||
 | 
					dma: DmaTuple,
 | 
				
			||||||
 | 
					tim: TimerTuple,
 | 
				
			||||||
 | 
					iwram: Iwram,
 | 
				
			||||||
 | 
					ewram: Ewram,
 | 
				
			||||||
 | 
					io: Io,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					cpu: *Arm7tdmi,
 | 
				
			||||||
 | 
					sched: *Scheduler,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn init(self: *Self, allocator: Allocator, sched: *Scheduler, cpu: *Arm7tdmi, paths: FilePaths) !void {
 | 
				
			||||||
 | 
					    self.* = .{
 | 
				
			||||||
 | 
					        .pak = try GamePak.init(allocator, cpu, paths.rom, paths.save),
 | 
				
			||||||
 | 
					        .bios = try Bios.init(allocator, paths.bios),
 | 
				
			||||||
 | 
					        .ppu = try Ppu.init(allocator, sched),
 | 
				
			||||||
 | 
					        .apu = Apu.init(sched),
 | 
				
			||||||
 | 
					        .iwram = try Iwram.init(allocator),
 | 
				
			||||||
 | 
					        .ewram = try Ewram.init(allocator),
 | 
				
			||||||
 | 
					        .dma = createDmaTuple(),
 | 
				
			||||||
 | 
					        .tim = createTimerTuple(sched),
 | 
				
			||||||
 | 
					        .io = Io.init(),
 | 
				
			||||||
 | 
					        .cpu = cpu,
 | 
				
			||||||
 | 
					        .sched = sched,
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn deinit(self: *Self) void {
 | 
				
			||||||
 | 
					    self.iwram.deinit();
 | 
				
			||||||
 | 
					    self.ewram.deinit();
 | 
				
			||||||
 | 
					    self.pak.deinit();
 | 
				
			||||||
 | 
					    self.bios.deinit();
 | 
				
			||||||
 | 
					    self.ppu.deinit();
 | 
				
			||||||
 | 
					    self.* = undefined;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn dbgRead(self: *const Self, comptime T: type, address: u32) T {
 | 
				
			||||||
 | 
					    const page = @truncate(u8, address >> 24);
 | 
				
			||||||
 | 
					    const aligned_addr = forceAlign(T, address);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return switch (page) {
 | 
				
			||||||
 | 
					        // General Internal Memory
 | 
				
			||||||
 | 
					        0x00 => blk: {
 | 
				
			||||||
 | 
					            if (address < Bios.size)
 | 
				
			||||||
 | 
					                break :blk self.bios.dbgRead(T, self.cpu.r[15], aligned_addr);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            break :blk self.openBus(T, address);
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        0x02 => self.ewram.read(T, aligned_addr),
 | 
				
			||||||
 | 
					        0x03 => self.iwram.read(T, aligned_addr),
 | 
				
			||||||
 | 
					        0x04 => self.readIo(T, address),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Internal Display Memory
 | 
				
			||||||
 | 
					        0x05 => self.ppu.palette.read(T, aligned_addr),
 | 
				
			||||||
 | 
					        0x06 => self.ppu.vram.read(T, aligned_addr),
 | 
				
			||||||
 | 
					        0x07 => self.ppu.oam.read(T, aligned_addr),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // External Memory (Game Pak)
 | 
				
			||||||
 | 
					        0x08...0x0D => self.pak.dbgRead(T, aligned_addr),
 | 
				
			||||||
 | 
					        0x0E...0x0F => blk: {
 | 
				
			||||||
 | 
					            const value = self.pak.backup.read(address);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            const multiplier = switch (T) {
 | 
				
			||||||
 | 
					                u32 => 0x01010101,
 | 
				
			||||||
 | 
					                u16 => 0x0101,
 | 
				
			||||||
 | 
					                u8 => 1,
 | 
				
			||||||
 | 
					                else => @compileError("Backup: Unsupported read width"),
 | 
				
			||||||
 | 
					            };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            break :blk @as(T, value) * multiplier;
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        else => self.openBus(T, address),
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fn readIo(self: *const Self, comptime T: type, unaligned_address: u32) T {
 | 
				
			||||||
 | 
					    const maybe_value = io.read(self, T, forceAlign(T, unaligned_address));
 | 
				
			||||||
 | 
					    return if (maybe_value) |value| value else self.openBus(T, unaligned_address);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fn openBus(self: *const Self, comptime T: type, address: u32) T {
 | 
				
			||||||
 | 
					    const r15 = self.cpu.r[15];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const word = blk: {
 | 
				
			||||||
 | 
					        // If Arm, get the most recently fetched instruction (PC + 8)
 | 
				
			||||||
 | 
					        if (!self.cpu.cpsr.t.read()) break :blk self.cpu.pipe.stage[1].?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const page = @truncate(u8, r15 >> 24);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // PC + 2 = stage[0]
 | 
				
			||||||
 | 
					        // PC + 4 = stage[1]
 | 
				
			||||||
 | 
					        // PC + 6 = Need a Debug Read for this?
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        switch (page) {
 | 
				
			||||||
 | 
					            // EWRAM, PALRAM, VRAM, and Game ROM (16-bit)
 | 
				
			||||||
 | 
					            0x02, 0x05, 0x06, 0x08...0x0D => {
 | 
				
			||||||
 | 
					                const halfword: u32 = @truncate(u16, self.cpu.pipe.stage[1].?);
 | 
				
			||||||
 | 
					                break :blk halfword << 16 | halfword;
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // BIOS or OAM (32-bit)
 | 
				
			||||||
 | 
					            0x00, 0x07 => {
 | 
				
			||||||
 | 
					                // Aligned: (PC + 6) | (PC + 4)
 | 
				
			||||||
 | 
					                // Unaligned: (PC + 4) | (PC + 2)
 | 
				
			||||||
 | 
					                const aligned = address & 3 == 0b00;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                // TODO: What to do on PC + 6?
 | 
				
			||||||
 | 
					                const high: u32 = if (aligned) self.dbgRead(u16, r15 + 4) else @truncate(u16, self.cpu.pipe.stage[1].?);
 | 
				
			||||||
 | 
					                const low: u32 = @truncate(u16, self.cpu.pipe.stage[@boolToInt(aligned)].?);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                break :blk high << 16 | low;
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // IWRAM (16-bit but special)
 | 
				
			||||||
 | 
					            0x03 => {
 | 
				
			||||||
 | 
					                // Aligned: (PC + 2) | (PC + 4)
 | 
				
			||||||
 | 
					                // Unaligned: (PC + 4) | (PC + 2)
 | 
				
			||||||
 | 
					                const aligned = address & 3 == 0b00;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                const high: u32 = @truncate(u16, self.cpu.pipe.stage[1 - @boolToInt(aligned)].?);
 | 
				
			||||||
 | 
					                const low: u32 = @truncate(u16, self.cpu.pipe.stage[@boolToInt(aligned)].?);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                break :blk high << 16 | low;
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            else => {
 | 
				
			||||||
 | 
					                log.err("THUMB open bus read from 0x{X:0>2} page @0x{X:0>8}", .{ page, address });
 | 
				
			||||||
 | 
					                @panic("invariant most-likely broken");
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return @truncate(T, rotr(u32, word, 8 * (address & 3)));
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn read(self: *Self, comptime T: type, address: u32) T {
 | 
				
			||||||
 | 
					    const page = @truncate(u8, address >> 24);
 | 
				
			||||||
 | 
					    const aligned_addr = forceAlign(T, address);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    self.sched.tick += timings[@boolToInt(T == u32)][@truncate(u4, page)];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return switch (page) {
 | 
				
			||||||
 | 
					        // General Internal Memory
 | 
				
			||||||
 | 
					        0x00 => blk: {
 | 
				
			||||||
 | 
					            if (address < Bios.size)
 | 
				
			||||||
 | 
					                break :blk self.bios.read(T, self.cpu.r[15], aligned_addr);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            break :blk self.openBus(T, address);
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        0x02 => self.ewram.read(T, aligned_addr),
 | 
				
			||||||
 | 
					        0x03 => self.iwram.read(T, aligned_addr),
 | 
				
			||||||
 | 
					        0x04 => self.readIo(T, address),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Internal Display Memory
 | 
				
			||||||
 | 
					        0x05 => self.ppu.palette.read(T, aligned_addr),
 | 
				
			||||||
 | 
					        0x06 => self.ppu.vram.read(T, aligned_addr),
 | 
				
			||||||
 | 
					        0x07 => self.ppu.oam.read(T, aligned_addr),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // External Memory (Game Pak)
 | 
				
			||||||
 | 
					        0x08...0x0D => self.pak.read(T, aligned_addr),
 | 
				
			||||||
 | 
					        0x0E...0x0F => blk: {
 | 
				
			||||||
 | 
					            const value = self.pak.backup.read(address);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            const multiplier = switch (T) {
 | 
				
			||||||
 | 
					                u32 => 0x01010101,
 | 
				
			||||||
 | 
					                u16 => 0x0101,
 | 
				
			||||||
 | 
					                u8 => 1,
 | 
				
			||||||
 | 
					                else => @compileError("Backup: Unsupported read width"),
 | 
				
			||||||
 | 
					            };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            break :blk @as(T, value) * multiplier;
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        else => self.openBus(T, address),
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn write(self: *Self, comptime T: type, address: u32, value: T) void {
 | 
				
			||||||
 | 
					    const page = @truncate(u8, address >> 24);
 | 
				
			||||||
 | 
					    const aligned_addr = forceAlign(T, address);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    self.sched.tick += timings[@boolToInt(T == u32)][@truncate(u4, page)];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    switch (page) {
 | 
				
			||||||
 | 
					        // General Internal Memory
 | 
				
			||||||
 | 
					        0x00 => self.bios.write(T, aligned_addr, value),
 | 
				
			||||||
 | 
					        0x02 => self.ewram.write(T, aligned_addr, value),
 | 
				
			||||||
 | 
					        0x03 => self.iwram.write(T, aligned_addr, value),
 | 
				
			||||||
 | 
					        0x04 => io.write(self, T, aligned_addr, value),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Internal Display Memory
 | 
				
			||||||
 | 
					        0x05 => self.ppu.palette.write(T, aligned_addr, value),
 | 
				
			||||||
 | 
					        0x06 => self.ppu.vram.write(T, self.ppu.dispcnt, aligned_addr, value),
 | 
				
			||||||
 | 
					        0x07 => self.ppu.oam.write(T, aligned_addr, value),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // External Memory (Game Pak)
 | 
				
			||||||
 | 
					        0x08...0x0D => self.pak.write(T, self.dma[3].word_count, aligned_addr, value),
 | 
				
			||||||
 | 
					        0x0E...0x0F => {
 | 
				
			||||||
 | 
					            const rotate_by = switch (T) {
 | 
				
			||||||
 | 
					                u32 => address & 3,
 | 
				
			||||||
 | 
					                u16 => address & 1,
 | 
				
			||||||
 | 
					                u8 => 0,
 | 
				
			||||||
 | 
					                else => @compileError("Backup: Unsupported write width"),
 | 
				
			||||||
 | 
					            };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            self.pak.backup.write(address, @truncate(u8, rotr(T, value, 8 * rotate_by)));
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        else => {},
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fn forceAlign(comptime T: type, address: u32) u32 {
 | 
				
			||||||
 | 
					    return switch (T) {
 | 
				
			||||||
 | 
					        u32 => address & 0xFFFF_FFFC,
 | 
				
			||||||
 | 
					        u16 => address & 0xFFFF_FFFE,
 | 
				
			||||||
 | 
					        u8 => address,
 | 
				
			||||||
 | 
					        else => @compileError("Bus: Invalid read/write type"),
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										479
									
								
								src/core/apu.zig
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										479
									
								
								src/core/apu.zig
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,479 @@
 | 
				
			|||||||
 | 
					const std = @import("std");
 | 
				
			||||||
 | 
					const SDL = @import("sdl2");
 | 
				
			||||||
 | 
					const io = @import("bus/io.zig");
 | 
				
			||||||
 | 
					const util = @import("../util.zig");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const AudioDeviceId = SDL.SDL_AudioDeviceID;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const Arm7tdmi = @import("cpu.zig").Arm7tdmi;
 | 
				
			||||||
 | 
					const Scheduler = @import("scheduler.zig").Scheduler;
 | 
				
			||||||
 | 
					const ToneSweep = @import("apu/ToneSweep.zig");
 | 
				
			||||||
 | 
					const Tone = @import("apu/Tone.zig");
 | 
				
			||||||
 | 
					const Wave = @import("apu/Wave.zig");
 | 
				
			||||||
 | 
					const Noise = @import("apu/Noise.zig");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const SoundFifo = std.fifo.LinearFifo(u8, .{ .Static = 0x20 });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const intToBytes = @import("../util.zig").intToBytes;
 | 
				
			||||||
 | 
					const setHi = @import("../util.zig").setHi;
 | 
				
			||||||
 | 
					const setLo = @import("../util.zig").setLo;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const log = std.log.scoped(.APU);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub const host_sample_rate = 1 << 15;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn read(comptime T: type, apu: *const Apu, addr: u32) ?T {
 | 
				
			||||||
 | 
					    const byte = @truncate(u8, addr);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return switch (T) {
 | 
				
			||||||
 | 
					        u16 => switch (byte) {
 | 
				
			||||||
 | 
					            0x60 => apu.ch1.sound1CntL(),
 | 
				
			||||||
 | 
					            0x62 => apu.ch1.sound1CntH(),
 | 
				
			||||||
 | 
					            0x64 => apu.ch1.sound1CntX(),
 | 
				
			||||||
 | 
					            0x68 => apu.ch2.sound2CntL(),
 | 
				
			||||||
 | 
					            0x6C => apu.ch2.sound2CntH(),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            0x70 => apu.ch3.select.raw & 0xE0, // SOUND3CNT_L
 | 
				
			||||||
 | 
					            0x72 => apu.ch3.sound3CntH(),
 | 
				
			||||||
 | 
					            0x74 => apu.ch3.freq.raw & 0x4000, // SOUND3CNT_X
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            0x78 => apu.ch4.sound4CntL(),
 | 
				
			||||||
 | 
					            0x7C => apu.ch4.sound4CntH(),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            0x80 => apu.psg_cnt.raw & 0xFF77, // SOUNDCNT_L
 | 
				
			||||||
 | 
					            0x82 => apu.dma_cnt.raw & 0x770F, // SOUNDCNT_H
 | 
				
			||||||
 | 
					            0x84 => apu.soundCntX(),
 | 
				
			||||||
 | 
					            0x88 => apu.bias.raw, // SOUNDBIAS
 | 
				
			||||||
 | 
					            0x90...0x9F => apu.ch3.wave_dev.read(T, apu.ch3.select, addr),
 | 
				
			||||||
 | 
					            else => util.io.read.undef(T, log, "Tried to perform a {} read to 0x{X:0>8}", .{ T, addr }),
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        u8 => switch (byte) {
 | 
				
			||||||
 | 
					            0x60 => apu.ch1.sound1CntL(), // NR10
 | 
				
			||||||
 | 
					            0x62 => apu.ch1.duty.raw, // NR11
 | 
				
			||||||
 | 
					            0x63 => apu.ch1.envelope.raw, // NR12
 | 
				
			||||||
 | 
					            0x68 => apu.ch2.duty.raw, // NR21
 | 
				
			||||||
 | 
					            0x69 => apu.ch2.envelope.raw, // NR22
 | 
				
			||||||
 | 
					            0x73 => apu.ch3.vol.raw, // NR32
 | 
				
			||||||
 | 
					            0x79 => apu.ch4.envelope.raw, // NR42
 | 
				
			||||||
 | 
					            0x7C => apu.ch4.poly.raw, // NR43
 | 
				
			||||||
 | 
					            0x81 => @truncate(u8, apu.psg_cnt.raw >> 8), // NR51
 | 
				
			||||||
 | 
					            0x84 => apu.soundCntX(),
 | 
				
			||||||
 | 
					            0x89 => @truncate(u8, apu.bias.raw >> 8), // SOUNDBIAS_H
 | 
				
			||||||
 | 
					            else => util.io.read.undef(T, log, "Tried to perform a {} read to 0x{X:0>8}", .{ T, addr }),
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        u32 => util.io.read.undef(T, log, "Tried to perform a {} read to 0x{X:0>8}", .{ T, addr }),
 | 
				
			||||||
 | 
					        else => @compileError("APU: Unsupported read width"),
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn write(comptime T: type, apu: *Apu, addr: u32, value: T) void {
 | 
				
			||||||
 | 
					    const byte = @truncate(u8, addr);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    switch (T) {
 | 
				
			||||||
 | 
					        u32 => switch (byte) {
 | 
				
			||||||
 | 
					            0x60 => apu.ch1.setSound1Cnt(value),
 | 
				
			||||||
 | 
					            0x64 => apu.ch1.setSound1CntX(&apu.fs, @truncate(u16, value)),
 | 
				
			||||||
 | 
					            0x68 => apu.ch2.setSound2CntL(@truncate(u16, value)),
 | 
				
			||||||
 | 
					            0x6C => apu.ch2.setSound2CntH(&apu.fs, @truncate(u16, value)),
 | 
				
			||||||
 | 
					            0x70 => apu.ch3.setSound3Cnt(value),
 | 
				
			||||||
 | 
					            0x74 => apu.ch3.setSound3CntX(&apu.fs, @truncate(u16, value)),
 | 
				
			||||||
 | 
					            0x78 => apu.ch4.setSound4CntL(@truncate(u16, value)),
 | 
				
			||||||
 | 
					            0x7C => apu.ch4.setSound4CntH(&apu.fs, @truncate(u16, value)),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            0x80 => apu.setSoundCnt(value),
 | 
				
			||||||
 | 
					            // WAVE_RAM
 | 
				
			||||||
 | 
					            0x90...0x9F => apu.ch3.wave_dev.write(T, apu.ch3.select, addr, value),
 | 
				
			||||||
 | 
					            0xA0 => apu.chA.push(value), // FIFO_A
 | 
				
			||||||
 | 
					            0xA4 => apu.chB.push(value), // FIFO_B
 | 
				
			||||||
 | 
					            else => util.io.write.undef(log, "Tried to write 0x{X:0>8}{} to 0x{X:0>8}", .{ value, T, addr }),
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        u16 => switch (byte) {
 | 
				
			||||||
 | 
					            0x60 => apu.ch1.setSound1CntL(@truncate(u8, value)), // SOUND1CNT_L
 | 
				
			||||||
 | 
					            0x62 => apu.ch1.setSound1CntH(value),
 | 
				
			||||||
 | 
					            0x64 => apu.ch1.setSound1CntX(&apu.fs, value),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            0x68 => apu.ch2.setSound2CntL(value),
 | 
				
			||||||
 | 
					            0x6C => apu.ch2.setSound2CntH(&apu.fs, value),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            0x70 => apu.ch3.setSound3CntL(@truncate(u8, value)),
 | 
				
			||||||
 | 
					            0x72 => apu.ch3.setSound3CntH(value),
 | 
				
			||||||
 | 
					            0x74 => apu.ch3.setSound3CntX(&apu.fs, value),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            0x78 => apu.ch4.setSound4CntL(value),
 | 
				
			||||||
 | 
					            0x7C => apu.ch4.setSound4CntH(&apu.fs, value),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            0x80 => apu.psg_cnt.raw = value, // SOUNDCNT_L
 | 
				
			||||||
 | 
					            0x82 => apu.setSoundCntH(value),
 | 
				
			||||||
 | 
					            0x84 => apu.setSoundCntX(value >> 7 & 1 == 1),
 | 
				
			||||||
 | 
					            0x88 => apu.bias.raw = value, // SOUNDBIAS
 | 
				
			||||||
 | 
					            // WAVE_RAM
 | 
				
			||||||
 | 
					            0x90...0x9F => apu.ch3.wave_dev.write(T, apu.ch3.select, addr, value),
 | 
				
			||||||
 | 
					            else => util.io.write.undef(log, "Tried to write 0x{X:0>4}{} to 0x{X:0>8}", .{ value, T, addr }),
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        u8 => switch (byte) {
 | 
				
			||||||
 | 
					            0x60 => apu.ch1.setSound1CntL(value),
 | 
				
			||||||
 | 
					            0x62 => apu.ch1.setNr11(value),
 | 
				
			||||||
 | 
					            0x63 => apu.ch1.setNr12(value),
 | 
				
			||||||
 | 
					            0x64 => apu.ch1.setNr13(value),
 | 
				
			||||||
 | 
					            0x65 => apu.ch1.setNr14(&apu.fs, value),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            0x68 => apu.ch2.setNr21(value),
 | 
				
			||||||
 | 
					            0x69 => apu.ch2.setNr22(value),
 | 
				
			||||||
 | 
					            0x6C => apu.ch2.setNr23(value),
 | 
				
			||||||
 | 
					            0x6D => apu.ch2.setNr24(&apu.fs, value),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            0x70 => apu.ch3.setSound3CntL(value), // NR30
 | 
				
			||||||
 | 
					            0x72 => apu.ch3.setNr31(value),
 | 
				
			||||||
 | 
					            0x73 => apu.ch3.vol.raw = value, // NR32
 | 
				
			||||||
 | 
					            0x74 => apu.ch3.setNr33(value),
 | 
				
			||||||
 | 
					            0x75 => apu.ch3.setNr34(&apu.fs, value),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            0x78 => apu.ch4.setNr41(value),
 | 
				
			||||||
 | 
					            0x79 => apu.ch4.setNr42(value),
 | 
				
			||||||
 | 
					            0x7C => apu.ch4.poly.raw = value, // NR 43
 | 
				
			||||||
 | 
					            0x7D => apu.ch4.setNr44(&apu.fs, value),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            0x80 => apu.setNr50(value),
 | 
				
			||||||
 | 
					            0x81 => apu.setNr51(value),
 | 
				
			||||||
 | 
					            0x82 => apu.setSoundCntH(setLo(u16, apu.dma_cnt.raw, value)),
 | 
				
			||||||
 | 
					            0x83 => apu.setSoundCntH(setHi(u16, apu.dma_cnt.raw, value)),
 | 
				
			||||||
 | 
					            0x84 => apu.setSoundCntX(value >> 7 & 1 == 1), // NR52
 | 
				
			||||||
 | 
					            0x89 => apu.setSoundBiasH(value),
 | 
				
			||||||
 | 
					            0x90...0x9F => apu.ch3.wave_dev.write(T, apu.ch3.select, addr, value),
 | 
				
			||||||
 | 
					            else => util.io.write.undef(log, "Tried to write 0x{X:0>2}{} to 0x{X:0>8}", .{ value, T, addr }),
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        else => @compileError("APU: Unsupported write width"),
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub const Apu = struct {
 | 
				
			||||||
 | 
					    const Self = @This();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    ch1: ToneSweep,
 | 
				
			||||||
 | 
					    ch2: Tone,
 | 
				
			||||||
 | 
					    ch3: Wave,
 | 
				
			||||||
 | 
					    ch4: Noise,
 | 
				
			||||||
 | 
					    chA: DmaSound(.A),
 | 
				
			||||||
 | 
					    chB: DmaSound(.B),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    bias: io.SoundBias,
 | 
				
			||||||
 | 
					    /// NR50, NR51
 | 
				
			||||||
 | 
					    psg_cnt: io.ChannelVolumeControl,
 | 
				
			||||||
 | 
					    dma_cnt: io.DmaSoundControl,
 | 
				
			||||||
 | 
					    cnt: io.SoundControl,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    sampling_cycle: u2,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    stream: *SDL.SDL_AudioStream,
 | 
				
			||||||
 | 
					    sched: *Scheduler,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fs: FrameSequencer,
 | 
				
			||||||
 | 
					    capacitor: f32,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    is_buffer_full: bool,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub const Tick = enum { Length, Envelope, Sweep };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn init(sched: *Scheduler) Self {
 | 
				
			||||||
 | 
					        const apu: Self = .{
 | 
				
			||||||
 | 
					            .ch1 = ToneSweep.init(sched),
 | 
				
			||||||
 | 
					            .ch2 = Tone.init(sched),
 | 
				
			||||||
 | 
					            .ch3 = Wave.init(sched),
 | 
				
			||||||
 | 
					            .ch4 = Noise.init(sched),
 | 
				
			||||||
 | 
					            .chA = DmaSound(.A).init(),
 | 
				
			||||||
 | 
					            .chB = DmaSound(.B).init(),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            .psg_cnt = .{ .raw = 0 },
 | 
				
			||||||
 | 
					            .dma_cnt = .{ .raw = 0 },
 | 
				
			||||||
 | 
					            .cnt = .{ .raw = 0 },
 | 
				
			||||||
 | 
					            .bias = .{ .raw = 0x0200 },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            .sampling_cycle = 0b00,
 | 
				
			||||||
 | 
					            .stream = SDL.SDL_NewAudioStream(SDL.AUDIO_U16, 2, 1 << 15, SDL.AUDIO_U16, 2, host_sample_rate).?,
 | 
				
			||||||
 | 
					            .sched = sched,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            .capacitor = 0,
 | 
				
			||||||
 | 
					            .fs = FrameSequencer.init(),
 | 
				
			||||||
 | 
					            .is_buffer_full = false,
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        sched.push(.SampleAudio, apu.interval());
 | 
				
			||||||
 | 
					        sched.push(.{ .ApuChannel = 0 }, @import("apu/signal/Square.zig").interval);
 | 
				
			||||||
 | 
					        sched.push(.{ .ApuChannel = 1 }, @import("apu/signal/Square.zig").interval);
 | 
				
			||||||
 | 
					        sched.push(.{ .ApuChannel = 2 }, @import("apu/signal/Wave.zig").interval);
 | 
				
			||||||
 | 
					        sched.push(.{ .ApuChannel = 3 }, @import("apu/signal/Lfsr.zig").interval);
 | 
				
			||||||
 | 
					        sched.push(.FrameSequencer, FrameSequencer.interval);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return apu;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fn reset(self: *Self) void {
 | 
				
			||||||
 | 
					        self.ch1.reset();
 | 
				
			||||||
 | 
					        self.ch2.reset();
 | 
				
			||||||
 | 
					        self.ch3.reset();
 | 
				
			||||||
 | 
					        self.ch4.reset();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /// SOUNDCNT
 | 
				
			||||||
 | 
					    fn setSoundCnt(self: *Self, value: u32) void {
 | 
				
			||||||
 | 
					        self.psg_cnt.raw = @truncate(u16, value);
 | 
				
			||||||
 | 
					        self.setSoundCntH(@truncate(u16, value >> 16));
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /// SOUNDCNT_H
 | 
				
			||||||
 | 
					    pub fn setSoundCntH(self: *Self, value: u16) void {
 | 
				
			||||||
 | 
					        const new: io.DmaSoundControl = .{ .raw = value };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Reinitializing instead of resetting is fine because
 | 
				
			||||||
 | 
					        // the FIFOs I'm using are stack allocated and 0x20 bytes big
 | 
				
			||||||
 | 
					        if (new.chA_reset.read()) self.chA.fifo = SoundFifo.init();
 | 
				
			||||||
 | 
					        if (new.chB_reset.read()) self.chB.fifo = SoundFifo.init();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.dma_cnt = new;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /// NR52
 | 
				
			||||||
 | 
					    pub fn setSoundCntX(self: *Self, value: bool) void {
 | 
				
			||||||
 | 
					        self.cnt.apu_enable.write(value);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (value) {
 | 
				
			||||||
 | 
					            self.fs.step = 0; // Reset Frame Sequencer
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // Reset Square Wave Offsets
 | 
				
			||||||
 | 
					            self.ch1.square.pos = 0;
 | 
				
			||||||
 | 
					            self.ch2.square.pos = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // Reset Wave Device Offsets
 | 
				
			||||||
 | 
					            self.ch3.wave_dev.offset = 0;
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            self.reset();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /// NR52
 | 
				
			||||||
 | 
					    pub fn soundCntX(self: *const Self) u8 {
 | 
				
			||||||
 | 
					        const apu_enable: u8 = @boolToInt(self.cnt.apu_enable.read());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const ch1_enable: u8 = @boolToInt(self.ch1.enabled);
 | 
				
			||||||
 | 
					        const ch2_enable: u8 = @boolToInt(self.ch2.enabled);
 | 
				
			||||||
 | 
					        const ch3_enable: u8 = @boolToInt(self.ch3.enabled);
 | 
				
			||||||
 | 
					        const ch4_enable: u8 = @boolToInt(self.ch4.enabled);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return apu_enable << 7 | ch4_enable << 3 | ch3_enable << 2 | ch2_enable << 1 | ch1_enable;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /// NR50
 | 
				
			||||||
 | 
					    pub fn setNr50(self: *Self, byte: u8) void {
 | 
				
			||||||
 | 
					        self.psg_cnt.raw = (self.psg_cnt.raw & 0xFF00) | byte;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /// NR51
 | 
				
			||||||
 | 
					    pub fn setNr51(self: *Self, byte: u8) void {
 | 
				
			||||||
 | 
					        self.psg_cnt.raw = @as(u16, byte) << 8 | (self.psg_cnt.raw & 0xFF);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn setSoundBiasH(self: *Self, byte: u8) void {
 | 
				
			||||||
 | 
					        self.bias.raw = (@as(u16, byte) << 8) | (self.bias.raw & 0xFF);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn sampleAudio(self: *Self, late: u64) void {
 | 
				
			||||||
 | 
					        self.sched.push(.SampleAudio, self.interval() -| late);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Whether the APU is busy or not is determined  by the main loop in emu.zig
 | 
				
			||||||
 | 
					        // This should only ever be true (because this side of the emu is single threaded)
 | 
				
			||||||
 | 
					        // When audio sync is disaabled
 | 
				
			||||||
 | 
					        if (self.is_buffer_full) return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        var left: i16 = 0;
 | 
				
			||||||
 | 
					        var right: i16 = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // SOUNDCNT_L Channel Enable flags
 | 
				
			||||||
 | 
					        const ch_left: u4 = self.psg_cnt.ch_left.read();
 | 
				
			||||||
 | 
					        const ch_right: u4 = self.psg_cnt.ch_right.read();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Determine SOUNDCNT_H volume modifications
 | 
				
			||||||
 | 
					        const gba_vol: u4 = switch (self.dma_cnt.ch_vol.read()) {
 | 
				
			||||||
 | 
					            0b00 => 2,
 | 
				
			||||||
 | 
					            0b01 => 1,
 | 
				
			||||||
 | 
					            else => 0,
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Add all PSG channels together
 | 
				
			||||||
 | 
					        left += if (ch_left & 1 == 1) @as(i16, self.ch1.sample) else 0;
 | 
				
			||||||
 | 
					        left += if (ch_left >> 1 & 1 == 1) @as(i16, self.ch2.sample) else 0;
 | 
				
			||||||
 | 
					        left += if (ch_left >> 2 & 1 == 1) @as(i16, self.ch3.sample) else 0;
 | 
				
			||||||
 | 
					        left += if (ch_left >> 3 == 1) @as(i16, self.ch4.sample) else 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        right += if (ch_right & 1 == 1) @as(i16, self.ch1.sample) else 0;
 | 
				
			||||||
 | 
					        right += if (ch_right >> 1 & 1 == 1) @as(i16, self.ch2.sample) else 0;
 | 
				
			||||||
 | 
					        right += if (ch_right >> 2 & 1 == 1) @as(i16, self.ch3.sample) else 0;
 | 
				
			||||||
 | 
					        right += if (ch_right >> 3 == 1) @as(i16, self.ch4.sample) else 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Multiply by master channel volume
 | 
				
			||||||
 | 
					        left *= 1 + @as(i16, self.psg_cnt.left_vol.read());
 | 
				
			||||||
 | 
					        right *= 1 + @as(i16, self.psg_cnt.right_vol.read());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Apply GBA volume modifications to PSG Channels
 | 
				
			||||||
 | 
					        left >>= gba_vol;
 | 
				
			||||||
 | 
					        right >>= gba_vol;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const chA_sample = self.chA.amplitude() << if (self.dma_cnt.chA_vol.read()) @as(u4, 2) else 1;
 | 
				
			||||||
 | 
					        const chB_sample = self.chB.amplitude() << if (self.dma_cnt.chB_vol.read()) @as(u4, 2) else 1;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        left += if (self.dma_cnt.chA_left.read()) chA_sample else 0;
 | 
				
			||||||
 | 
					        left += if (self.dma_cnt.chB_left.read()) chB_sample else 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        right += if (self.dma_cnt.chA_right.read()) chA_sample else 0;
 | 
				
			||||||
 | 
					        right += if (self.dma_cnt.chB_right.read()) chB_sample else 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Add SOUNDBIAS
 | 
				
			||||||
 | 
					        // FIXME: Is SOUNDBIAS 9-bit or 10-bit?
 | 
				
			||||||
 | 
					        const bias = @as(i16, self.bias.level.read()) << 1;
 | 
				
			||||||
 | 
					        left += bias;
 | 
				
			||||||
 | 
					        right += bias;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const clamped_left = std.math.clamp(@bitCast(u16, left), std.math.minInt(u11), std.math.maxInt(u11));
 | 
				
			||||||
 | 
					        const clamped_right = std.math.clamp(@bitCast(u16, right), std.math.minInt(u11), std.math.maxInt(u11));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Extend to 16-bit signed audio samples
 | 
				
			||||||
 | 
					        const ext_left = (clamped_left << 5) | (clamped_left >> 6);
 | 
				
			||||||
 | 
					        const ext_right = (clamped_right << 5) | (clamped_right >> 6);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // FIXME: This rarely happens
 | 
				
			||||||
 | 
					        if (self.sampling_cycle != self.bias.sampling_cycle.read()) self.replaceSDLResampler();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        _ = SDL.SDL_AudioStreamPut(self.stream, &[2]u16{ ext_left, ext_right }, 2 * @sizeOf(u16));
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fn replaceSDLResampler(self: *Self) void {
 | 
				
			||||||
 | 
					        @setCold(true);
 | 
				
			||||||
 | 
					        const sample_rate = Self.sampleRate(self.bias.sampling_cycle.read());
 | 
				
			||||||
 | 
					        log.info("Sample Rate changed from {}Hz to {}Hz", .{ Self.sampleRate(self.sampling_cycle), sample_rate });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Sampling Cycle (Sample Rate) changed, Craete a new SDL Audio Resampler
 | 
				
			||||||
 | 
					        // FIXME: Replace SDL's Audio Resampler with either a custom or more reliable one
 | 
				
			||||||
 | 
					        const old_stream = self.stream;
 | 
				
			||||||
 | 
					        defer SDL.SDL_FreeAudioStream(old_stream);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.sampling_cycle = self.bias.sampling_cycle.read();
 | 
				
			||||||
 | 
					        self.stream = SDL.SDL_NewAudioStream(SDL.AUDIO_U16, 2, @intCast(c_int, sample_rate), SDL.AUDIO_U16, 2, host_sample_rate).?;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fn interval(self: *const Self) u64 {
 | 
				
			||||||
 | 
					        return (1 << 24) / Self.sampleRate(self.bias.sampling_cycle.read());
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fn sampleRate(cycle: u2) u64 {
 | 
				
			||||||
 | 
					        return @as(u64, 1) << (15 + @as(u6, cycle));
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn onSequencerTick(self: *Self, late: u64) void {
 | 
				
			||||||
 | 
					        self.fs.tick();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        switch (self.fs.step) {
 | 
				
			||||||
 | 
					            7 => self.tick(.Envelope), // Clock Envelope
 | 
				
			||||||
 | 
					            0, 4 => self.tick(.Length), // Clock Length
 | 
				
			||||||
 | 
					            2, 6 => {
 | 
				
			||||||
 | 
					                // Clock Length and Sweep
 | 
				
			||||||
 | 
					                self.tick(.Length);
 | 
				
			||||||
 | 
					                self.tick(.Sweep);
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            1, 3, 5 => {},
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.sched.push(.FrameSequencer, ((1 << 24) / 512) -| late);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fn tick(self: *Self, comptime kind: Tick) void {
 | 
				
			||||||
 | 
					        self.ch1.tick(kind);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        switch (kind) {
 | 
				
			||||||
 | 
					            .Length => {
 | 
				
			||||||
 | 
					                self.ch2.tick(kind);
 | 
				
			||||||
 | 
					                self.ch3.tick(kind);
 | 
				
			||||||
 | 
					                self.ch4.tick(kind);
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            .Envelope => {
 | 
				
			||||||
 | 
					                self.ch2.tick(kind);
 | 
				
			||||||
 | 
					                self.ch4.tick(kind);
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            .Sweep => {}, // Already handled above (only for Ch1)
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn onDmaAudioSampleRequest(self: *Self, cpu: *Arm7tdmi, tim_id: u3) void {
 | 
				
			||||||
 | 
					        if (!self.cnt.apu_enable.read()) return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (@boolToInt(self.dma_cnt.chA_timer.read()) == tim_id) {
 | 
				
			||||||
 | 
					            self.chA.updateSample();
 | 
				
			||||||
 | 
					            if (self.chA.len() <= 15) cpu.bus.dma[1].requestAudio(0x0400_00A0);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (@boolToInt(self.dma_cnt.chB_timer.read()) == tim_id) {
 | 
				
			||||||
 | 
					            self.chB.updateSample();
 | 
				
			||||||
 | 
					            if (self.chB.len() <= 15) cpu.bus.dma[2].requestAudio(0x0400_00A4);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn DmaSound(comptime kind: DmaSoundKind) type {
 | 
				
			||||||
 | 
					    return struct {
 | 
				
			||||||
 | 
					        const Self = @This();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        fifo: SoundFifo,
 | 
				
			||||||
 | 
					        kind: DmaSoundKind,
 | 
				
			||||||
 | 
					        sample: i8,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        fn init() Self {
 | 
				
			||||||
 | 
					            return .{
 | 
				
			||||||
 | 
					                .fifo = SoundFifo.init(),
 | 
				
			||||||
 | 
					                .kind = kind,
 | 
				
			||||||
 | 
					                .sample = 0,
 | 
				
			||||||
 | 
					            };
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        pub fn push(self: *Self, value: u32) void {
 | 
				
			||||||
 | 
					            self.fifo.write(&intToBytes(u32, value)) catch |e| log.err("{} Error: {}", .{ kind, e });
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        pub fn len(self: *const Self) usize {
 | 
				
			||||||
 | 
					            return self.fifo.readableLength();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        pub fn updateSample(self: *Self) void {
 | 
				
			||||||
 | 
					            if (self.fifo.readItem()) |sample| self.sample = @bitCast(i8, sample);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        pub fn amplitude(self: *const Self) i16 {
 | 
				
			||||||
 | 
					            return @as(i16, self.sample);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const DmaSoundKind = enum {
 | 
				
			||||||
 | 
					    A,
 | 
				
			||||||
 | 
					    B,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub const FrameSequencer = struct {
 | 
				
			||||||
 | 
					    const interval = (1 << 24) / 512;
 | 
				
			||||||
 | 
					    const Self = @This();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    step: u3,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn init() Self {
 | 
				
			||||||
 | 
					        return .{ .step = 0 };
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn tick(self: *Self) void {
 | 
				
			||||||
 | 
					        self.step +%= 1;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn isLengthNext(self: *const Self) bool {
 | 
				
			||||||
 | 
					        return (self.step +% 1) & 1 == 0; // Steps, 0, 2, 4, and 6 clock length
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn isEnvelopeNext(self: *const Self) bool {
 | 
				
			||||||
 | 
					        return (self.step +% 1) == 7;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
							
								
								
									
										142
									
								
								src/core/apu/Noise.zig
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										142
									
								
								src/core/apu/Noise.zig
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,142 @@
 | 
				
			|||||||
 | 
					const io = @import("../bus/io.zig");
 | 
				
			||||||
 | 
					const util = @import("../../util.zig");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const Scheduler = @import("../scheduler.zig").Scheduler;
 | 
				
			||||||
 | 
					const FrameSequencer = @import("../apu.zig").FrameSequencer;
 | 
				
			||||||
 | 
					const Tick = @import("../apu.zig").Apu.Tick;
 | 
				
			||||||
 | 
					const Envelope = @import("device/Envelope.zig");
 | 
				
			||||||
 | 
					const Length = @import("device/Length.zig");
 | 
				
			||||||
 | 
					const Lfsr = @import("signal/Lfsr.zig");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const Self = @This();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// Write-only
 | 
				
			||||||
 | 
					/// NR41
 | 
				
			||||||
 | 
					len: u6,
 | 
				
			||||||
 | 
					/// NR42
 | 
				
			||||||
 | 
					envelope: io.Envelope,
 | 
				
			||||||
 | 
					/// NR43
 | 
				
			||||||
 | 
					poly: io.PolyCounter,
 | 
				
			||||||
 | 
					/// NR44
 | 
				
			||||||
 | 
					cnt: io.NoiseControl,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// Length Functionarlity
 | 
				
			||||||
 | 
					len_dev: Length,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// Envelope Functionality
 | 
				
			||||||
 | 
					env_dev: Envelope,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Linear Feedback Shift Register
 | 
				
			||||||
 | 
					lfsr: Lfsr,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					enabled: bool,
 | 
				
			||||||
 | 
					sample: i8,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn init(sched: *Scheduler) Self {
 | 
				
			||||||
 | 
					    return .{
 | 
				
			||||||
 | 
					        .len = 0,
 | 
				
			||||||
 | 
					        .envelope = .{ .raw = 0 },
 | 
				
			||||||
 | 
					        .poly = .{ .raw = 0 },
 | 
				
			||||||
 | 
					        .cnt = .{ .raw = 0 },
 | 
				
			||||||
 | 
					        .enabled = false,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        .len_dev = Length.create(),
 | 
				
			||||||
 | 
					        .env_dev = Envelope.create(),
 | 
				
			||||||
 | 
					        .lfsr = Lfsr.create(sched),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        .sample = 0,
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn reset(self: *Self) void {
 | 
				
			||||||
 | 
					    self.len = 0;
 | 
				
			||||||
 | 
					    self.envelope.raw = 0;
 | 
				
			||||||
 | 
					    self.poly.raw = 0;
 | 
				
			||||||
 | 
					    self.cnt.raw = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    self.sample = 0;
 | 
				
			||||||
 | 
					    self.enabled = false;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn tick(self: *Self, comptime kind: Tick) void {
 | 
				
			||||||
 | 
					    switch (kind) {
 | 
				
			||||||
 | 
					        .Length => self.len_dev.tick(self.cnt.length_enable.read(), &self.enabled),
 | 
				
			||||||
 | 
					        .Envelope => self.env_dev.tick(self.envelope),
 | 
				
			||||||
 | 
					        .Sweep => @compileError("Channel 4 does not implement Sweep"),
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// NR41, NR42
 | 
				
			||||||
 | 
					pub fn sound4CntL(self: *const Self) u16 {
 | 
				
			||||||
 | 
					    return @as(u16, self.envelope.raw) << 8;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// NR41, NR42
 | 
				
			||||||
 | 
					pub fn setSound4CntL(self: *Self, value: u16) void {
 | 
				
			||||||
 | 
					    self.setNr41(@truncate(u8, value));
 | 
				
			||||||
 | 
					    self.setNr42(@truncate(u8, value >> 8));
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// NR41
 | 
				
			||||||
 | 
					pub fn setNr41(self: *Self, len: u8) void {
 | 
				
			||||||
 | 
					    self.len = @truncate(u6, len);
 | 
				
			||||||
 | 
					    self.len_dev.timer = @as(u7, 64) - @truncate(u6, len);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// NR42
 | 
				
			||||||
 | 
					pub fn setNr42(self: *Self, value: u8) void {
 | 
				
			||||||
 | 
					    self.envelope.raw = value;
 | 
				
			||||||
 | 
					    if (!self.isDacEnabled()) self.enabled = false;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// NR43, NR44
 | 
				
			||||||
 | 
					pub fn sound4CntH(self: *const Self) u16 {
 | 
				
			||||||
 | 
					    return @as(u16, self.poly.raw & 0x40) << 8 | self.cnt.raw;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// NR43, NR44
 | 
				
			||||||
 | 
					pub fn setSound4CntH(self: *Self, fs: *const FrameSequencer, value: u16) void {
 | 
				
			||||||
 | 
					    self.poly.raw = @truncate(u8, value);
 | 
				
			||||||
 | 
					    self.setNr44(fs, @truncate(u8, value >> 8));
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// NR44
 | 
				
			||||||
 | 
					pub fn setNr44(self: *Self, fs: *const FrameSequencer, byte: u8) void {
 | 
				
			||||||
 | 
					    var new: io.NoiseControl = .{ .raw = byte };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (new.trigger.read()) {
 | 
				
			||||||
 | 
					        self.enabled = true;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (self.len_dev.timer == 0) {
 | 
				
			||||||
 | 
					            self.len_dev.timer =
 | 
				
			||||||
 | 
					                if (!fs.isLengthNext() and new.length_enable.read()) 63 else 64;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Update The Frequency Timer
 | 
				
			||||||
 | 
					        self.lfsr.reload(self.poly);
 | 
				
			||||||
 | 
					        self.lfsr.shift = 0x7FFF;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Update Envelope and Volume
 | 
				
			||||||
 | 
					        self.env_dev.timer = self.envelope.period.read();
 | 
				
			||||||
 | 
					        if (fs.isEnvelopeNext() and self.env_dev.timer != 0b111) self.env_dev.timer += 1;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.env_dev.vol = self.envelope.init_vol.read();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.enabled = self.isDacEnabled();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    util.audio.length.ch4.update(self, fs, new);
 | 
				
			||||||
 | 
					    self.cnt = new;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn onNoiseEvent(self: *Self, late: u64) void {
 | 
				
			||||||
 | 
					    self.lfsr.onLfsrTimerExpire(self.poly, late);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    self.sample = 0;
 | 
				
			||||||
 | 
					    if (!self.isDacEnabled()) return;
 | 
				
			||||||
 | 
					    self.sample = if (self.enabled) self.lfsr.sample() * @as(i8, self.env_dev.vol) else 0;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fn isDacEnabled(self: *const Self) bool {
 | 
				
			||||||
 | 
					    return self.envelope.raw & 0xF8 != 0x00;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										138
									
								
								src/core/apu/Tone.zig
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										138
									
								
								src/core/apu/Tone.zig
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,138 @@
 | 
				
			|||||||
 | 
					const io = @import("../bus/io.zig");
 | 
				
			||||||
 | 
					const util = @import("../../util.zig");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const Scheduler = @import("../scheduler.zig").Scheduler;
 | 
				
			||||||
 | 
					const FrameSequencer = @import("../apu.zig").FrameSequencer;
 | 
				
			||||||
 | 
					const Tick = @import("../apu.zig").Apu.Tick;
 | 
				
			||||||
 | 
					const Length = @import("device/Length.zig");
 | 
				
			||||||
 | 
					const Envelope = @import("device/Envelope.zig");
 | 
				
			||||||
 | 
					const Square = @import("signal/Square.zig");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const Self = @This();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// NR21
 | 
				
			||||||
 | 
					duty: io.Duty,
 | 
				
			||||||
 | 
					/// NR22
 | 
				
			||||||
 | 
					envelope: io.Envelope,
 | 
				
			||||||
 | 
					/// NR23, NR24
 | 
				
			||||||
 | 
					freq: io.Frequency,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// Length Functionarlity
 | 
				
			||||||
 | 
					len_dev: Length,
 | 
				
			||||||
 | 
					/// Envelope Functionality
 | 
				
			||||||
 | 
					env_dev: Envelope,
 | 
				
			||||||
 | 
					/// FrequencyTimer Functionality
 | 
				
			||||||
 | 
					square: Square,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					enabled: bool,
 | 
				
			||||||
 | 
					sample: i8,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn init(sched: *Scheduler) Self {
 | 
				
			||||||
 | 
					    return .{
 | 
				
			||||||
 | 
					        .duty = .{ .raw = 0 },
 | 
				
			||||||
 | 
					        .envelope = .{ .raw = 0 },
 | 
				
			||||||
 | 
					        .freq = .{ .raw = 0 },
 | 
				
			||||||
 | 
					        .enabled = false,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        .square = Square.init(sched),
 | 
				
			||||||
 | 
					        .len_dev = Length.create(),
 | 
				
			||||||
 | 
					        .env_dev = Envelope.create(),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        .sample = 0,
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn reset(self: *Self) void {
 | 
				
			||||||
 | 
					    self.duty.raw = 0;
 | 
				
			||||||
 | 
					    self.envelope.raw = 0;
 | 
				
			||||||
 | 
					    self.freq.raw = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    self.sample = 0;
 | 
				
			||||||
 | 
					    self.enabled = false;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn tick(self: *Self, comptime kind: Tick) void {
 | 
				
			||||||
 | 
					    switch (kind) {
 | 
				
			||||||
 | 
					        .Length => self.len_dev.tick(self.freq.length_enable.read(), &self.enabled),
 | 
				
			||||||
 | 
					        .Envelope => self.env_dev.tick(self.envelope),
 | 
				
			||||||
 | 
					        .Sweep => @compileError("Channel 2 does not implement Sweep"),
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn onToneEvent(self: *Self, late: u64) void {
 | 
				
			||||||
 | 
					    self.square.onSquareTimerExpire(Self, self.freq, late);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    self.sample = 0;
 | 
				
			||||||
 | 
					    if (!self.isDacEnabled()) return;
 | 
				
			||||||
 | 
					    self.sample = if (self.enabled) self.square.sample(self.duty) * @as(i8, self.env_dev.vol) else 0;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// NR21, NR22
 | 
				
			||||||
 | 
					pub fn sound2CntL(self: *const Self) u16 {
 | 
				
			||||||
 | 
					    return @as(u16, self.envelope.raw) << 8 | (self.duty.raw & 0xC0);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// NR21, NR22
 | 
				
			||||||
 | 
					pub fn setSound2CntL(self: *Self, value: u16) void {
 | 
				
			||||||
 | 
					    self.setNr21(@truncate(u8, value));
 | 
				
			||||||
 | 
					    self.setNr22(@truncate(u8, value >> 8));
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// NR21
 | 
				
			||||||
 | 
					pub fn setNr21(self: *Self, value: u8) void {
 | 
				
			||||||
 | 
					    self.duty.raw = value;
 | 
				
			||||||
 | 
					    self.len_dev.timer = @as(u7, 64) - @truncate(u6, value);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// NR22
 | 
				
			||||||
 | 
					pub fn setNr22(self: *Self, value: u8) void {
 | 
				
			||||||
 | 
					    self.envelope.raw = value;
 | 
				
			||||||
 | 
					    if (!self.isDacEnabled()) self.enabled = false;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// NR23, NR24
 | 
				
			||||||
 | 
					pub fn sound2CntH(self: *const Self) u16 {
 | 
				
			||||||
 | 
					    return self.freq.raw & 0x4000;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// NR23, NR24
 | 
				
			||||||
 | 
					pub fn setSound2CntH(self: *Self, fs: *const FrameSequencer, value: u16) void {
 | 
				
			||||||
 | 
					    self.setNr23(@truncate(u8, value));
 | 
				
			||||||
 | 
					    self.setNr24(fs, @truncate(u8, value >> 8));
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// NR23
 | 
				
			||||||
 | 
					pub fn setNr23(self: *Self, byte: u8) void {
 | 
				
			||||||
 | 
					    self.freq.raw = (self.freq.raw & 0xFF00) | byte;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// NR24
 | 
				
			||||||
 | 
					pub fn setNr24(self: *Self, fs: *const FrameSequencer, byte: u8) void {
 | 
				
			||||||
 | 
					    var new: io.Frequency = .{ .raw = (@as(u16, byte) << 8) | (self.freq.raw & 0xFF) };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (new.trigger.read()) {
 | 
				
			||||||
 | 
					        self.enabled = true;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (self.len_dev.timer == 0) {
 | 
				
			||||||
 | 
					            self.len_dev.timer =
 | 
				
			||||||
 | 
					                if (!fs.isLengthNext() and new.length_enable.read()) 63 else 64;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.square.reload(Self, self.freq.frequency.read());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Reload Envelope period and timer
 | 
				
			||||||
 | 
					        self.env_dev.timer = self.envelope.period.read();
 | 
				
			||||||
 | 
					        if (fs.isEnvelopeNext() and self.env_dev.timer != 0b111) self.env_dev.timer += 1;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.env_dev.vol = self.envelope.init_vol.read();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.enabled = self.isDacEnabled();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    util.audio.length.update(Self, self, fs, new);
 | 
				
			||||||
 | 
					    self.freq = new;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fn isDacEnabled(self: *const Self) bool {
 | 
				
			||||||
 | 
					    return self.envelope.raw & 0xF8 != 0;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										184
									
								
								src/core/apu/ToneSweep.zig
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										184
									
								
								src/core/apu/ToneSweep.zig
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,184 @@
 | 
				
			|||||||
 | 
					const io = @import("../bus/io.zig");
 | 
				
			||||||
 | 
					const util = @import("../../util.zig");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const Scheduler = @import("../scheduler.zig").Scheduler;
 | 
				
			||||||
 | 
					const FrameSequencer = @import("../apu.zig").FrameSequencer;
 | 
				
			||||||
 | 
					const Length = @import("device/Length.zig");
 | 
				
			||||||
 | 
					const Envelope = @import("device/Envelope.zig");
 | 
				
			||||||
 | 
					const Sweep = @import("device/Sweep.zig");
 | 
				
			||||||
 | 
					const Square = @import("signal/Square.zig");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const Tick = @import("../apu.zig").Apu.Tick;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const Self = @This();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// NR10
 | 
				
			||||||
 | 
					sweep: io.Sweep,
 | 
				
			||||||
 | 
					/// NR11
 | 
				
			||||||
 | 
					duty: io.Duty,
 | 
				
			||||||
 | 
					/// NR12
 | 
				
			||||||
 | 
					envelope: io.Envelope,
 | 
				
			||||||
 | 
					/// NR13, NR14
 | 
				
			||||||
 | 
					freq: io.Frequency,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// Length Functionality
 | 
				
			||||||
 | 
					len_dev: Length,
 | 
				
			||||||
 | 
					/// Sweep Functionality
 | 
				
			||||||
 | 
					sweep_dev: Sweep,
 | 
				
			||||||
 | 
					/// Envelope Functionality
 | 
				
			||||||
 | 
					env_dev: Envelope,
 | 
				
			||||||
 | 
					/// Frequency Timer Functionality
 | 
				
			||||||
 | 
					square: Square,
 | 
				
			||||||
 | 
					enabled: bool,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					sample: i8,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn init(sched: *Scheduler) Self {
 | 
				
			||||||
 | 
					    return .{
 | 
				
			||||||
 | 
					        .sweep = .{ .raw = 0 },
 | 
				
			||||||
 | 
					        .duty = .{ .raw = 0 },
 | 
				
			||||||
 | 
					        .envelope = .{ .raw = 0 },
 | 
				
			||||||
 | 
					        .freq = .{ .raw = 0 },
 | 
				
			||||||
 | 
					        .sample = 0,
 | 
				
			||||||
 | 
					        .enabled = false,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        .square = Square.init(sched),
 | 
				
			||||||
 | 
					        .len_dev = Length.create(),
 | 
				
			||||||
 | 
					        .sweep_dev = Sweep.create(),
 | 
				
			||||||
 | 
					        .env_dev = Envelope.create(),
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn reset(self: *Self) void {
 | 
				
			||||||
 | 
					    self.sweep.raw = 0;
 | 
				
			||||||
 | 
					    self.sweep_dev.calc_performed = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    self.duty.raw = 0;
 | 
				
			||||||
 | 
					    self.envelope.raw = 0;
 | 
				
			||||||
 | 
					    self.freq.raw = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    self.sample = 0;
 | 
				
			||||||
 | 
					    self.enabled = false;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn tick(self: *Self, comptime kind: Tick) void {
 | 
				
			||||||
 | 
					    switch (kind) {
 | 
				
			||||||
 | 
					        .Length => self.len_dev.tick(self.freq.length_enable.read(), &self.enabled),
 | 
				
			||||||
 | 
					        .Envelope => self.env_dev.tick(self.envelope),
 | 
				
			||||||
 | 
					        .Sweep => self.sweep_dev.tick(self),
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn onToneSweepEvent(self: *Self, late: u64) void {
 | 
				
			||||||
 | 
					    self.square.onSquareTimerExpire(Self, self.freq, late);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    self.sample = 0;
 | 
				
			||||||
 | 
					    if (!self.isDacEnabled()) return;
 | 
				
			||||||
 | 
					    self.sample = if (self.enabled) self.square.sample(self.duty) * @as(i8, self.env_dev.vol) else 0;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// NR10, NR11, NR12
 | 
				
			||||||
 | 
					pub fn setSound1Cnt(self: *Self, value: u32) void {
 | 
				
			||||||
 | 
					    self.setSound1CntL(@truncate(u8, value));
 | 
				
			||||||
 | 
					    self.setSound1CntH(@truncate(u16, value >> 16));
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// NR10
 | 
				
			||||||
 | 
					pub fn sound1CntL(self: *const Self) u8 {
 | 
				
			||||||
 | 
					    return self.sweep.raw & 0x7F;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// NR10
 | 
				
			||||||
 | 
					pub fn setSound1CntL(self: *Self, value: u8) void {
 | 
				
			||||||
 | 
					    const new = io.Sweep{ .raw = value };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (self.sweep.direction.read() and !new.direction.read()) {
 | 
				
			||||||
 | 
					        // Sweep Negate bit has been cleared
 | 
				
			||||||
 | 
					        // If At least 1 Sweep Calculation has been made since
 | 
				
			||||||
 | 
					        // the last trigger, the channel is immediately disabled
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (self.sweep_dev.calc_performed) self.enabled = false;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    self.sweep.raw = value;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// NR11, NR12
 | 
				
			||||||
 | 
					pub fn sound1CntH(self: *const Self) u16 {
 | 
				
			||||||
 | 
					    return @as(u16, self.envelope.raw) << 8 | (self.duty.raw & 0xC0);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// NR11, NR12
 | 
				
			||||||
 | 
					pub fn setSound1CntH(self: *Self, value: u16) void {
 | 
				
			||||||
 | 
					    self.setNr11(@truncate(u8, value));
 | 
				
			||||||
 | 
					    self.setNr12(@truncate(u8, value >> 8));
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// NR11
 | 
				
			||||||
 | 
					pub fn setNr11(self: *Self, value: u8) void {
 | 
				
			||||||
 | 
					    self.duty.raw = value;
 | 
				
			||||||
 | 
					    self.len_dev.timer = @as(u7, 64) - @truncate(u6, value);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// NR12
 | 
				
			||||||
 | 
					pub fn setNr12(self: *Self, value: u8) void {
 | 
				
			||||||
 | 
					    self.envelope.raw = value;
 | 
				
			||||||
 | 
					    if (!self.isDacEnabled()) self.enabled = false;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// NR13, NR14
 | 
				
			||||||
 | 
					pub fn sound1CntX(self: *const Self) u16 {
 | 
				
			||||||
 | 
					    return self.freq.raw & 0x4000;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// NR13, NR14
 | 
				
			||||||
 | 
					pub fn setSound1CntX(self: *Self, fs: *const FrameSequencer, value: u16) void {
 | 
				
			||||||
 | 
					    self.setNr13(@truncate(u8, value));
 | 
				
			||||||
 | 
					    self.setNr14(fs, @truncate(u8, value >> 8));
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// NR13
 | 
				
			||||||
 | 
					pub fn setNr13(self: *Self, byte: u8) void {
 | 
				
			||||||
 | 
					    self.freq.raw = (self.freq.raw & 0xFF00) | byte;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// NR14
 | 
				
			||||||
 | 
					pub fn setNr14(self: *Self, fs: *const FrameSequencer, byte: u8) void {
 | 
				
			||||||
 | 
					    var new: io.Frequency = .{ .raw = (@as(u16, byte) << 8) | (self.freq.raw & 0xFF) };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (new.trigger.read()) {
 | 
				
			||||||
 | 
					        self.enabled = true;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (self.len_dev.timer == 0) {
 | 
				
			||||||
 | 
					            self.len_dev.timer =
 | 
				
			||||||
 | 
					                if (!fs.isLengthNext() and new.length_enable.read()) 63 else 64;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.square.reload(Self, self.freq.frequency.read());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Reload Envelope period and timer
 | 
				
			||||||
 | 
					        self.env_dev.timer = self.envelope.period.read();
 | 
				
			||||||
 | 
					        if (fs.isEnvelopeNext() and self.env_dev.timer != 0b111) self.env_dev.timer += 1;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.env_dev.vol = self.envelope.init_vol.read();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Sweep Trigger Behaviour
 | 
				
			||||||
 | 
					        const sw_period = self.sweep.period.read();
 | 
				
			||||||
 | 
					        const sw_shift = self.sweep.shift.read();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.sweep_dev.calc_performed = false;
 | 
				
			||||||
 | 
					        self.sweep_dev.shadow = self.freq.frequency.read();
 | 
				
			||||||
 | 
					        self.sweep_dev.timer = if (sw_period == 0) 8 else sw_period;
 | 
				
			||||||
 | 
					        self.sweep_dev.enabled = sw_period != 0 or sw_shift != 0;
 | 
				
			||||||
 | 
					        if (sw_shift != 0) _ = self.sweep_dev.calculate(self.sweep, &self.enabled);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.enabled = self.isDacEnabled();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    util.audio.length.update(Self, self, fs, new);
 | 
				
			||||||
 | 
					    self.freq = new;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fn isDacEnabled(self: *const Self) bool {
 | 
				
			||||||
 | 
					    return self.envelope.raw & 0xF8 != 0;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										132
									
								
								src/core/apu/Wave.zig
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										132
									
								
								src/core/apu/Wave.zig
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,132 @@
 | 
				
			|||||||
 | 
					const io = @import("../bus/io.zig");
 | 
				
			||||||
 | 
					const util = @import("../../util.zig");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const Scheduler = @import("../scheduler.zig").Scheduler;
 | 
				
			||||||
 | 
					const FrameSequencer = @import("../apu.zig").FrameSequencer;
 | 
				
			||||||
 | 
					const Tick = @import("../apu.zig").Apu.Tick;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const Length = @import("device/Length.zig");
 | 
				
			||||||
 | 
					const Wave = @import("signal/Wave.zig");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const Self = @This();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// Write-only
 | 
				
			||||||
 | 
					/// NR30
 | 
				
			||||||
 | 
					select: io.WaveSelect,
 | 
				
			||||||
 | 
					/// NR31
 | 
				
			||||||
 | 
					length: u8,
 | 
				
			||||||
 | 
					/// NR32
 | 
				
			||||||
 | 
					vol: io.WaveVolume,
 | 
				
			||||||
 | 
					/// NR33, NR34
 | 
				
			||||||
 | 
					freq: io.Frequency,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// Length Functionarlity
 | 
				
			||||||
 | 
					len_dev: Length,
 | 
				
			||||||
 | 
					wave_dev: Wave,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					enabled: bool,
 | 
				
			||||||
 | 
					sample: i8,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn init(sched: *Scheduler) Self {
 | 
				
			||||||
 | 
					    return .{
 | 
				
			||||||
 | 
					        .select = .{ .raw = 0 },
 | 
				
			||||||
 | 
					        .vol = .{ .raw = 0 },
 | 
				
			||||||
 | 
					        .freq = .{ .raw = 0 },
 | 
				
			||||||
 | 
					        .length = 0,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        .len_dev = Length.create(),
 | 
				
			||||||
 | 
					        .wave_dev = Wave.init(sched),
 | 
				
			||||||
 | 
					        .enabled = false,
 | 
				
			||||||
 | 
					        .sample = 0,
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn reset(self: *Self) void {
 | 
				
			||||||
 | 
					    self.select.raw = 0;
 | 
				
			||||||
 | 
					    self.length = 0;
 | 
				
			||||||
 | 
					    self.vol.raw = 0;
 | 
				
			||||||
 | 
					    self.freq.raw = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    self.sample = 0;
 | 
				
			||||||
 | 
					    self.enabled = false;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn tick(self: *Self, comptime kind: Tick) void {
 | 
				
			||||||
 | 
					    switch (kind) {
 | 
				
			||||||
 | 
					        .Length => self.len_dev.tick(self.freq.length_enable.read(), &self.enabled),
 | 
				
			||||||
 | 
					        .Envelope => @compileError("Channel 3 does not implement Envelope"),
 | 
				
			||||||
 | 
					        .Sweep => @compileError("Channel 3 does not implement Sweep"),
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// NR30, NR31, NR32
 | 
				
			||||||
 | 
					pub fn setSound3Cnt(self: *Self, value: u32) void {
 | 
				
			||||||
 | 
					    self.setSound3CntL(@truncate(u8, value));
 | 
				
			||||||
 | 
					    self.setSound3CntH(@truncate(u16, value >> 16));
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// NR30
 | 
				
			||||||
 | 
					pub fn setSound3CntL(self: *Self, value: u8) void {
 | 
				
			||||||
 | 
					    self.select.raw = value;
 | 
				
			||||||
 | 
					    if (!self.select.enabled.read()) self.enabled = false;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// NR31, NR32
 | 
				
			||||||
 | 
					pub fn sound3CntH(self: *const Self) u16 {
 | 
				
			||||||
 | 
					    return @as(u16, self.length & 0xE0) << 8;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// NR31, NR32
 | 
				
			||||||
 | 
					pub fn setSound3CntH(self: *Self, value: u16) void {
 | 
				
			||||||
 | 
					    self.setNr31(@truncate(u8, value));
 | 
				
			||||||
 | 
					    self.vol.raw = (@truncate(u8, value >> 8));
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// NR31
 | 
				
			||||||
 | 
					pub fn setNr31(self: *Self, len: u8) void {
 | 
				
			||||||
 | 
					    self.length = len;
 | 
				
			||||||
 | 
					    self.len_dev.timer = 256 - @as(u9, len);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// NR33, NR34
 | 
				
			||||||
 | 
					pub fn setSound3CntX(self: *Self, fs: *const FrameSequencer, value: u16) void {
 | 
				
			||||||
 | 
					    self.setNr33(@truncate(u8, value));
 | 
				
			||||||
 | 
					    self.setNr34(fs, @truncate(u8, value >> 8));
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// NR33
 | 
				
			||||||
 | 
					pub fn setNr33(self: *Self, byte: u8) void {
 | 
				
			||||||
 | 
					    self.freq.raw = (self.freq.raw & 0xFF00) | byte;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// NR34
 | 
				
			||||||
 | 
					pub fn setNr34(self: *Self, fs: *const FrameSequencer, byte: u8) void {
 | 
				
			||||||
 | 
					    var new: io.Frequency = .{ .raw = (@as(u16, byte) << 8) | (self.freq.raw & 0xFF) };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (new.trigger.read()) {
 | 
				
			||||||
 | 
					        self.enabled = true;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (self.len_dev.timer == 0) {
 | 
				
			||||||
 | 
					            self.len_dev.timer =
 | 
				
			||||||
 | 
					                if (!fs.isLengthNext() and new.length_enable.read()) 255 else 256;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Update The Frequency Timer
 | 
				
			||||||
 | 
					        self.wave_dev.reload(self.freq.frequency.read());
 | 
				
			||||||
 | 
					        self.wave_dev.offset = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.enabled = self.select.enabled.read();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    util.audio.length.update(Self, self, fs, new);
 | 
				
			||||||
 | 
					    self.freq = new;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn onWaveEvent(self: *Self, late: u64) void {
 | 
				
			||||||
 | 
					    self.wave_dev.onWaveTimerExpire(self.freq, self.select, late);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    self.sample = 0;
 | 
				
			||||||
 | 
					    if (!self.select.enabled.read()) return;
 | 
				
			||||||
 | 
					    // Convert unsigned 4-bit wave sample to signed 8-bit sample
 | 
				
			||||||
 | 
					    self.sample = (2 * @as(i8, self.wave_dev.sample(self.select)) - 15) >> self.wave_dev.shift(self.vol);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										28
									
								
								src/core/apu/device/Envelope.zig
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								src/core/apu/device/Envelope.zig
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,28 @@
 | 
				
			|||||||
 | 
					const io = @import("../../bus/io.zig");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const Self = @This();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// Period Timer
 | 
				
			||||||
 | 
					timer: u3,
 | 
				
			||||||
 | 
					/// Current Volume
 | 
				
			||||||
 | 
					vol: u4,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn create() Self {
 | 
				
			||||||
 | 
					    return .{ .timer = 0, .vol = 0 };
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn tick(self: *Self, nrx2: io.Envelope) void {
 | 
				
			||||||
 | 
					    if (nrx2.period.read() != 0) {
 | 
				
			||||||
 | 
					        if (self.timer != 0) self.timer -= 1;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (self.timer == 0) {
 | 
				
			||||||
 | 
					            self.timer = nrx2.period.read();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (nrx2.direction.read()) {
 | 
				
			||||||
 | 
					                if (self.vol < 0xF) self.vol += 1;
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					                if (self.vol > 0x0) self.vol -= 1;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										18
									
								
								src/core/apu/device/Length.zig
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								src/core/apu/device/Length.zig
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,18 @@
 | 
				
			|||||||
 | 
					const Self = @This();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					timer: u9,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn create() Self {
 | 
				
			||||||
 | 
					    return .{ .timer = 0 };
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn tick(self: *Self, enabled: bool, ch_enable: *bool) void {
 | 
				
			||||||
 | 
					    if (enabled) {
 | 
				
			||||||
 | 
					        if (self.timer == 0) return;
 | 
				
			||||||
 | 
					        self.timer -= 1;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // By returning early if timer == 0, this is only
 | 
				
			||||||
 | 
					        // true if timer == 0 because of the decrement we just did
 | 
				
			||||||
 | 
					        if (self.timer == 0) ch_enable.* = false;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										52
									
								
								src/core/apu/device/Sweep.zig
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										52
									
								
								src/core/apu/device/Sweep.zig
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,52 @@
 | 
				
			|||||||
 | 
					const io = @import("../../bus/io.zig");
 | 
				
			||||||
 | 
					const ToneSweep = @import("../ToneSweep.zig");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const Self = @This();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					timer: u8,
 | 
				
			||||||
 | 
					enabled: bool,
 | 
				
			||||||
 | 
					shadow: u11,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					calc_performed: bool,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn create() Self {
 | 
				
			||||||
 | 
					    return .{
 | 
				
			||||||
 | 
					        .timer = 0,
 | 
				
			||||||
 | 
					        .enabled = false,
 | 
				
			||||||
 | 
					        .shadow = 0,
 | 
				
			||||||
 | 
					        .calc_performed = false,
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn tick(self: *Self, ch1: *ToneSweep) void {
 | 
				
			||||||
 | 
					    if (self.timer != 0) self.timer -= 1;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (self.timer == 0) {
 | 
				
			||||||
 | 
					        const period = ch1.sweep.period.read();
 | 
				
			||||||
 | 
					        self.timer = if (period == 0) 8 else period;
 | 
				
			||||||
 | 
					        if (!self.calc_performed) self.calc_performed = true;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (self.enabled and period != 0) {
 | 
				
			||||||
 | 
					            const new_freq = self.calculate(ch1.sweep, &ch1.enabled);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (new_freq <= 0x7FF and ch1.sweep.shift.read() != 0) {
 | 
				
			||||||
 | 
					                ch1.freq.frequency.write(@truncate(u11, new_freq));
 | 
				
			||||||
 | 
					                self.shadow = @truncate(u11, new_freq);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                _ = self.calculate(ch1.sweep, &ch1.enabled);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// Calculates the Sweep Frequency
 | 
				
			||||||
 | 
					pub fn calculate(self: *Self, sweep: io.Sweep, ch_enable: *bool) u12 {
 | 
				
			||||||
 | 
					    const shadow = @as(u12, self.shadow);
 | 
				
			||||||
 | 
					    const shadow_shifted = shadow >> sweep.shift.read();
 | 
				
			||||||
 | 
					    const decrease = sweep.direction.read();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const freq = if (decrease) shadow - shadow_shifted else shadow + shadow_shifted;
 | 
				
			||||||
 | 
					    if (freq > 0x7FF) ch_enable.* = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return freq;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										59
									
								
								src/core/apu/signal/Lfsr.zig
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										59
									
								
								src/core/apu/signal/Lfsr.zig
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,59 @@
 | 
				
			|||||||
 | 
					const io = @import("../../bus/io.zig");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// Linear Feedback Shift Register
 | 
				
			||||||
 | 
					const Scheduler = @import("../../scheduler.zig").Scheduler;
 | 
				
			||||||
 | 
					const FrameSequencer = @import("../../apu.zig").FrameSequencer;
 | 
				
			||||||
 | 
					const Noise = @import("../Noise.zig");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const Self = @This();
 | 
				
			||||||
 | 
					pub const interval: u64 = (1 << 24) / (1 << 22);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					shift: u15,
 | 
				
			||||||
 | 
					timer: u16,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					sched: *Scheduler,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn create(sched: *Scheduler) Self {
 | 
				
			||||||
 | 
					    return .{
 | 
				
			||||||
 | 
					        .shift = 0,
 | 
				
			||||||
 | 
					        .timer = 0,
 | 
				
			||||||
 | 
					        .sched = sched,
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn sample(self: *const Self) i8 {
 | 
				
			||||||
 | 
					    return if ((~self.shift & 1) == 1) 1 else -1;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// Reload LFSR Timer
 | 
				
			||||||
 | 
					pub fn reload(self: *Self, poly: io.PolyCounter) void {
 | 
				
			||||||
 | 
					    self.sched.removeScheduledEvent(.{ .ApuChannel = 3 });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const div = Self.divisor(poly.div_ratio.read());
 | 
				
			||||||
 | 
					    const timer = div << poly.shift.read();
 | 
				
			||||||
 | 
					    self.sched.push(.{ .ApuChannel = 3 }, @as(u64, timer) * interval);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// Scheduler Event Handler for LFSR Timer Expire
 | 
				
			||||||
 | 
					/// FIXME: This gets called a lot, clogging up the Scheduler
 | 
				
			||||||
 | 
					pub fn onLfsrTimerExpire(self: *Self, poly: io.PolyCounter, late: u64) void {
 | 
				
			||||||
 | 
					    // Obscure: "Using a noise channel clock shift of 14 or 15
 | 
				
			||||||
 | 
					    // results in the LFSR receiving no clocks."
 | 
				
			||||||
 | 
					    if (poly.shift.read() >= 14) return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const div = Self.divisor(poly.div_ratio.read());
 | 
				
			||||||
 | 
					    const timer = div << poly.shift.read();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const tmp = (self.shift & 1) ^ ((self.shift & 2) >> 1);
 | 
				
			||||||
 | 
					    self.shift = (self.shift >> 1) | (tmp << 14);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (poly.width.read())
 | 
				
			||||||
 | 
					        self.shift = (self.shift & ~@as(u15, 0x40)) | tmp << 6;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    self.sched.push(.{ .ApuChannel = 3 }, @as(u64, timer) * interval -| late);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fn divisor(code: u3) u16 {
 | 
				
			||||||
 | 
					    if (code == 0) return 8;
 | 
				
			||||||
 | 
					    return @as(u16, code) << 4;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										58
									
								
								src/core/apu/signal/Square.zig
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										58
									
								
								src/core/apu/signal/Square.zig
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,58 @@
 | 
				
			|||||||
 | 
					const std = @import("std");
 | 
				
			||||||
 | 
					const io = @import("../../bus/io.zig");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const Scheduler = @import("../../scheduler.zig").Scheduler;
 | 
				
			||||||
 | 
					const FrameSequencer = @import("../../apu.zig").FrameSequencer;
 | 
				
			||||||
 | 
					const ToneSweep = @import("../ToneSweep.zig");
 | 
				
			||||||
 | 
					const Tone = @import("../Tone.zig");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const Self = @This();
 | 
				
			||||||
 | 
					pub const interval: u64 = (1 << 24) / (1 << 22);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pos: u3,
 | 
				
			||||||
 | 
					sched: *Scheduler,
 | 
				
			||||||
 | 
					timer: u16,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn init(sched: *Scheduler) Self {
 | 
				
			||||||
 | 
					    return .{
 | 
				
			||||||
 | 
					        .timer = 0,
 | 
				
			||||||
 | 
					        .pos = 0,
 | 
				
			||||||
 | 
					        .sched = sched,
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// Scheduler Event Handler for Square Synth Timer Expire
 | 
				
			||||||
 | 
					pub fn onSquareTimerExpire(self: *Self, comptime T: type, nrx34: io.Frequency, late: u64) void {
 | 
				
			||||||
 | 
					    comptime std.debug.assert(T == ToneSweep or T == Tone);
 | 
				
			||||||
 | 
					    self.pos +%= 1;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    self.timer = (@as(u16, 2048) - nrx34.frequency.read()) * 4;
 | 
				
			||||||
 | 
					    self.sched.push(.{ .ApuChannel = if (T == ToneSweep) 0 else 1 }, @as(u64, self.timer) * interval -| late);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// Reload Square Wave Timer
 | 
				
			||||||
 | 
					pub fn reload(self: *Self, comptime T: type, value: u11) void {
 | 
				
			||||||
 | 
					    comptime std.debug.assert(T == ToneSweep or T == Tone);
 | 
				
			||||||
 | 
					    const channel = if (T == ToneSweep) 0 else 1;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    self.sched.removeScheduledEvent(.{ .ApuChannel = channel });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const tmp = (@as(u16, 2048) - value) * 4; // What Freq Timer should be assuming no weird behaviour
 | 
				
			||||||
 | 
					    self.timer = (tmp & ~@as(u16, 0x3)) | self.timer & 0x3; // Keep the last two bits from the old timer;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    self.sched.push(.{ .ApuChannel = channel }, @as(u64, self.timer) * interval);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn sample(self: *const Self, nrx1: io.Duty) i8 {
 | 
				
			||||||
 | 
					    const pattern = nrx1.pattern.read();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const i = self.pos ^ 7; // index of 0 should get highest bit
 | 
				
			||||||
 | 
					    const result = switch (pattern) {
 | 
				
			||||||
 | 
					        0b00 => @as(u8, 0b00000001) >> i, // 12.5%
 | 
				
			||||||
 | 
					        0b01 => @as(u8, 0b00000011) >> i, // 25%
 | 
				
			||||||
 | 
					        0b10 => @as(u8, 0b00001111) >> i, // 50%
 | 
				
			||||||
 | 
					        0b11 => @as(u8, 0b11111100) >> i, // 75%
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return if (result & 1 == 1) 1 else -1;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										79
									
								
								src/core/apu/signal/Wave.zig
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										79
									
								
								src/core/apu/signal/Wave.zig
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,79 @@
 | 
				
			|||||||
 | 
					const std = @import("std");
 | 
				
			||||||
 | 
					const io = @import("../../bus/io.zig");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const Scheduler = @import("../../scheduler.zig").Scheduler;
 | 
				
			||||||
 | 
					const FrameSequencer = @import("../../apu.zig").FrameSequencer;
 | 
				
			||||||
 | 
					const Wave = @import("../Wave.zig");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const buf_len = 0x20;
 | 
				
			||||||
 | 
					pub const interval: u64 = (1 << 24) / (1 << 22);
 | 
				
			||||||
 | 
					const Self = @This();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					buf: [buf_len]u8,
 | 
				
			||||||
 | 
					timer: u16,
 | 
				
			||||||
 | 
					offset: u12,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					sched: *Scheduler,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn read(self: *const Self, comptime T: type, nr30: io.WaveSelect, addr: u32) T {
 | 
				
			||||||
 | 
					    // TODO: Handle reads when Channel 3 is disabled
 | 
				
			||||||
 | 
					    const base = if (!nr30.bank.read()) @as(u32, 0x10) else 0; // Read from the Opposite Bank in Use
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const i = base + addr - 0x0400_0090;
 | 
				
			||||||
 | 
					    return std.mem.readIntSliceLittle(T, self.buf[i..][0..@sizeOf(T)]);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn write(self: *Self, comptime T: type, nr30: io.WaveSelect, addr: u32, value: T) void {
 | 
				
			||||||
 | 
					    // TODO: Handle writes when Channel 3 is disabled
 | 
				
			||||||
 | 
					    const base = if (!nr30.bank.read()) @as(u32, 0x10) else 0; // Write to the Opposite Bank in Use
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const i = base + addr - 0x0400_0090;
 | 
				
			||||||
 | 
					    std.mem.writeIntSliceLittle(T, self.buf[i..][0..@sizeOf(T)], value);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn init(sched: *Scheduler) Self {
 | 
				
			||||||
 | 
					    return .{
 | 
				
			||||||
 | 
					        .buf = [_]u8{0x00} ** buf_len,
 | 
				
			||||||
 | 
					        .timer = 0,
 | 
				
			||||||
 | 
					        .offset = 0,
 | 
				
			||||||
 | 
					        .sched = sched,
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// Reload internal Wave Timer
 | 
				
			||||||
 | 
					pub fn reload(self: *Self, value: u11) void {
 | 
				
			||||||
 | 
					    self.sched.removeScheduledEvent(.{ .ApuChannel = 2 });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    self.timer = (@as(u16, 2048) - value) * 2;
 | 
				
			||||||
 | 
					    self.sched.push(.{ .ApuChannel = 2 }, @as(u64, self.timer) * interval);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// Scheduler Event Handler
 | 
				
			||||||
 | 
					pub fn onWaveTimerExpire(self: *Self, nrx34: io.Frequency, nr30: io.WaveSelect, late: u64) void {
 | 
				
			||||||
 | 
					    if (nr30.dimension.read()) {
 | 
				
			||||||
 | 
					        self.offset = (self.offset + 1) % 0x40; // 0x20 bytes (both banks), which contain 2 samples each
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					        self.offset = (self.offset + 1) % 0x20; // 0x10 bytes, which contain 2 samples each
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    self.timer = (@as(u16, 2048) - nrx34.frequency.read()) * 2;
 | 
				
			||||||
 | 
					    self.sched.push(.{ .ApuChannel = 2 }, @as(u64, self.timer) * interval -| late);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// Generate Sample from Wave Synth
 | 
				
			||||||
 | 
					pub fn sample(self: *const Self, nr30: io.WaveSelect) u4 {
 | 
				
			||||||
 | 
					    const base = if (nr30.bank.read()) @as(u32, 0x10) else 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const value = self.buf[base + self.offset / 2];
 | 
				
			||||||
 | 
					    return if (self.offset & 1 == 0) @truncate(u4, value >> 4) else @truncate(u4, value);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// TODO: Write comment
 | 
				
			||||||
 | 
					pub fn shift(_: *const Self, nr32: io.WaveVolume) u2 {
 | 
				
			||||||
 | 
					    return switch (nr32.kind.read()) {
 | 
				
			||||||
 | 
					        0b00 => 3, // Mute / Zero
 | 
				
			||||||
 | 
					        0b01 => 0, // 100% Volume
 | 
				
			||||||
 | 
					        0b10 => 1, // 50% Volume
 | 
				
			||||||
 | 
					        0b11 => 2, // 25% Volume
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										63
									
								
								src/core/bus/Bios.zig
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										63
									
								
								src/core/bus/Bios.zig
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,63 @@
 | 
				
			|||||||
 | 
					const std = @import("std");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const Allocator = std.mem.Allocator;
 | 
				
			||||||
 | 
					const log = std.log.scoped(.Bios);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// Size of the BIOS in bytes
 | 
				
			||||||
 | 
					pub const size = 0x4000;
 | 
				
			||||||
 | 
					const Self = @This();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					buf: ?[]u8,
 | 
				
			||||||
 | 
					allocator: Allocator,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					addr_latch: u32,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn read(self: *Self, comptime T: type, r15: u32, addr: u32) T {
 | 
				
			||||||
 | 
					    if (r15 < Self.size) {
 | 
				
			||||||
 | 
					        self.addr_latch = addr;
 | 
				
			||||||
 | 
					        return self._read(T, addr);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    log.debug("Rejected read since r15=0x{X:0>8}", .{r15});
 | 
				
			||||||
 | 
					    return @truncate(T, self._read(T, self.addr_latch + 8));
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn dbgRead(self: *const Self, comptime T: type, r15: u32, addr: u32) T {
 | 
				
			||||||
 | 
					    if (r15 < Self.size) return self._read(T, addr);
 | 
				
			||||||
 | 
					    return @truncate(T, self._read(T, self.addr_latch + 8));
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// Read without the GBA safety checks
 | 
				
			||||||
 | 
					fn _read(self: *const Self, comptime T: type, addr: u32) T {
 | 
				
			||||||
 | 
					    const buf = self.buf orelse std.debug.panic("[BIOS] ZBA tried to read {} from 0x{X:0>8} but not BIOS was present", .{ T, addr });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return switch (T) {
 | 
				
			||||||
 | 
					        u32, u16, u8 => std.mem.readIntSliceLittle(T, buf[addr..][0..@sizeOf(T)]),
 | 
				
			||||||
 | 
					        else => @compileError("BIOS: Unsupported read width"),
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn write(_: *Self, comptime T: type, addr: u32, value: T) void {
 | 
				
			||||||
 | 
					    @setCold(true);
 | 
				
			||||||
 | 
					    log.debug("Tried to write {} 0x{X:} to 0x{X:0>8} ", .{ T, value, addr });
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn init(allocator: Allocator, maybe_path: ?[]const u8) !Self {
 | 
				
			||||||
 | 
					    const buf: ?[]u8 = if (maybe_path) |path| blk: {
 | 
				
			||||||
 | 
					        const file = try std.fs.cwd().openFile(path, .{});
 | 
				
			||||||
 | 
					        defer file.close();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        break :blk try file.readToEndAlloc(allocator, try file.getEndPos());
 | 
				
			||||||
 | 
					    } else null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return Self{
 | 
				
			||||||
 | 
					        .buf = buf,
 | 
				
			||||||
 | 
					        .allocator = allocator,
 | 
				
			||||||
 | 
					        .addr_latch = 0,
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn deinit(self: *Self) void {
 | 
				
			||||||
 | 
					    if (self.buf) |buf| self.allocator.free(buf);
 | 
				
			||||||
 | 
					    self.* = undefined;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										41
									
								
								src/core/bus/Ewram.zig
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										41
									
								
								src/core/bus/Ewram.zig
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,41 @@
 | 
				
			|||||||
 | 
					const std = @import("std");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const Allocator = std.mem.Allocator;
 | 
				
			||||||
 | 
					const ewram_size = 0x40000;
 | 
				
			||||||
 | 
					const Self = @This();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					buf: []u8,
 | 
				
			||||||
 | 
					allocator: Allocator,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn read(self: *const Self, comptime T: type, address: usize) T {
 | 
				
			||||||
 | 
					    const addr = address & 0x3FFFF;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return switch (T) {
 | 
				
			||||||
 | 
					        u32, u16, u8 => std.mem.readIntSliceLittle(T, self.buf[addr..][0..@sizeOf(T)]),
 | 
				
			||||||
 | 
					        else => @compileError("EWRAM: Unsupported read width"),
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn write(self: *const Self, comptime T: type, address: usize, value: T) void {
 | 
				
			||||||
 | 
					    const addr = address & 0x3FFFF;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return switch (T) {
 | 
				
			||||||
 | 
					        u32, u16, u8 => std.mem.writeIntSliceLittle(T, self.buf[addr..][0..@sizeOf(T)], value),
 | 
				
			||||||
 | 
					        else => @compileError("EWRAM: Unsupported write width"),
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn init(allocator: Allocator) !Self {
 | 
				
			||||||
 | 
					    const buf = try allocator.alloc(u8, ewram_size);
 | 
				
			||||||
 | 
					    std.mem.set(u8, buf, 0);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return Self{
 | 
				
			||||||
 | 
					        .buf = buf,
 | 
				
			||||||
 | 
					        .allocator = allocator,
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn deinit(self: *Self) void {
 | 
				
			||||||
 | 
					    self.allocator.free(self.buf);
 | 
				
			||||||
 | 
					    self.* = undefined;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										260
									
								
								src/core/bus/GamePak.zig
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										260
									
								
								src/core/bus/GamePak.zig
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,260 @@
 | 
				
			|||||||
 | 
					const std = @import("std");
 | 
				
			||||||
 | 
					const config = @import("../../config.zig");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const Bit = @import("bitfield").Bit;
 | 
				
			||||||
 | 
					const Bitfield = @import("bitfield").Bitfield;
 | 
				
			||||||
 | 
					const DateTime = @import("datetime").datetime.Datetime;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const Arm7tdmi = @import("../cpu.zig").Arm7tdmi;
 | 
				
			||||||
 | 
					const Backup = @import("backup.zig").Backup;
 | 
				
			||||||
 | 
					const Gpio = @import("gpio.zig").Gpio;
 | 
				
			||||||
 | 
					const Allocator = std.mem.Allocator;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const log = std.log.scoped(.GamePak);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const Self = @This();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					title: [12]u8,
 | 
				
			||||||
 | 
					buf: []u8,
 | 
				
			||||||
 | 
					allocator: Allocator,
 | 
				
			||||||
 | 
					backup: Backup,
 | 
				
			||||||
 | 
					gpio: *Gpio,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn read(self: *Self, comptime T: type, address: u32) T {
 | 
				
			||||||
 | 
					    const addr = address & 0x1FF_FFFF;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (self.backup.kind == .Eeprom) {
 | 
				
			||||||
 | 
					        if (self.buf.len > 0x100_0000) { // Large
 | 
				
			||||||
 | 
					            // Addresses 0x1FF_FF00 to 0x1FF_FFFF are reserved from EEPROM accesses if
 | 
				
			||||||
 | 
					            // * Backup type is EEPROM
 | 
				
			||||||
 | 
					            // * Large ROM (Size is greater than 16MB)
 | 
				
			||||||
 | 
					            if (addr > 0x1FF_FEFF)
 | 
				
			||||||
 | 
					                return self.backup.eeprom.read();
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            // Addresses 0x0D00_0000 to 0x0DFF_FFFF are reserved for EEPROM accesses if
 | 
				
			||||||
 | 
					            // * Backup type is EEPROM
 | 
				
			||||||
 | 
					            // * Small ROM (less than 16MB)
 | 
				
			||||||
 | 
					            if (@truncate(u8, address >> 24) == 0x0D)
 | 
				
			||||||
 | 
					                return self.backup.eeprom.read();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (self.gpio.cnt == 1) {
 | 
				
			||||||
 | 
					        // GPIO Can be read from
 | 
				
			||||||
 | 
					        // We assume that this will only be true when a ROM actually does want something from GPIO
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        switch (T) {
 | 
				
			||||||
 | 
					            u32 => switch (address) {
 | 
				
			||||||
 | 
					                // TODO: Do I even need to implement these?
 | 
				
			||||||
 | 
					                0x0800_00C4 => std.debug.panic("Handle 32-bit GPIO Data/Direction Reads", .{}),
 | 
				
			||||||
 | 
					                0x0800_00C6 => std.debug.panic("Handle 32-bit GPIO Direction/Control Reads", .{}),
 | 
				
			||||||
 | 
					                0x0800_00C8 => std.debug.panic("Handle 32-bit GPIO Control Reads", .{}),
 | 
				
			||||||
 | 
					                else => {},
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            u16 => switch (address) {
 | 
				
			||||||
 | 
					                // FIXME: What do 16-bit GPIO Reads look like?
 | 
				
			||||||
 | 
					                0x0800_00C4 => return self.gpio.read(.Data),
 | 
				
			||||||
 | 
					                0x0800_00C6 => return self.gpio.read(.Direction),
 | 
				
			||||||
 | 
					                0x0800_00C8 => return self.gpio.read(.Control),
 | 
				
			||||||
 | 
					                else => {},
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            u8 => switch (address) {
 | 
				
			||||||
 | 
					                0x0800_00C4 => return self.gpio.read(.Data),
 | 
				
			||||||
 | 
					                0x0800_00C6 => return self.gpio.read(.Direction),
 | 
				
			||||||
 | 
					                0x0800_00C8 => return self.gpio.read(.Control),
 | 
				
			||||||
 | 
					                else => {},
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            else => @compileError("GamePak[GPIO]: Unsupported read width"),
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return switch (T) {
 | 
				
			||||||
 | 
					        u32 => (@as(T, self.get(addr + 3)) << 24) | (@as(T, self.get(addr + 2)) << 16) | (@as(T, self.get(addr + 1)) << 8) | (@as(T, self.get(addr))),
 | 
				
			||||||
 | 
					        u16 => (@as(T, self.get(addr + 1)) << 8) | @as(T, self.get(addr)),
 | 
				
			||||||
 | 
					        u8 => self.get(addr),
 | 
				
			||||||
 | 
					        else => @compileError("GamePak: Unsupported read width"),
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					inline fn get(self: *const Self, i: u32) u8 {
 | 
				
			||||||
 | 
					    @setRuntimeSafety(false);
 | 
				
			||||||
 | 
					    if (i < self.buf.len) return self.buf[i];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const lhs = i >> 1 & 0xFFFF;
 | 
				
			||||||
 | 
					    return @truncate(u8, lhs >> 8 * @truncate(u5, i & 1));
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn dbgRead(self: *const Self, comptime T: type, address: u32) T {
 | 
				
			||||||
 | 
					    const addr = address & 0x1FF_FFFF;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (self.backup.kind == .Eeprom) {
 | 
				
			||||||
 | 
					        if (self.buf.len > 0x100_0000) { // Large
 | 
				
			||||||
 | 
					            // Addresses 0x1FF_FF00 to 0x1FF_FFFF are reserved from EEPROM accesses if
 | 
				
			||||||
 | 
					            // * Backup type is EEPROM
 | 
				
			||||||
 | 
					            // * Large ROM (Size is greater than 16MB)
 | 
				
			||||||
 | 
					            if (addr > 0x1FF_FEFF)
 | 
				
			||||||
 | 
					                return self.backup.eeprom.dbgRead();
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            // Addresses 0x0D00_0000 to 0x0DFF_FFFF are reserved for EEPROM accesses if
 | 
				
			||||||
 | 
					            // * Backup type is EEPROM
 | 
				
			||||||
 | 
					            // * Small ROM (less than 16MB)
 | 
				
			||||||
 | 
					            if (@truncate(u8, address >> 24) == 0x0D)
 | 
				
			||||||
 | 
					                return self.backup.eeprom.dbgRead();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (self.gpio.cnt == 1) {
 | 
				
			||||||
 | 
					        // GPIO Can be read from
 | 
				
			||||||
 | 
					        // We assume that this will only be true when a ROM actually does want something from GPIO
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        switch (T) {
 | 
				
			||||||
 | 
					            u32 => switch (address) {
 | 
				
			||||||
 | 
					                // TODO: Do I even need to implement these?
 | 
				
			||||||
 | 
					                0x0800_00C4 => std.debug.panic("Handle 32-bit GPIO Data/Direction Reads", .{}),
 | 
				
			||||||
 | 
					                0x0800_00C6 => std.debug.panic("Handle 32-bit GPIO Direction/Control Reads", .{}),
 | 
				
			||||||
 | 
					                0x0800_00C8 => std.debug.panic("Handle 32-bit GPIO Control Reads", .{}),
 | 
				
			||||||
 | 
					                else => {},
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            u16 => switch (address) {
 | 
				
			||||||
 | 
					                // FIXME: What do 16-bit GPIO Reads look like?
 | 
				
			||||||
 | 
					                0x0800_00C4 => return self.gpio.read(.Data),
 | 
				
			||||||
 | 
					                0x0800_00C6 => return self.gpio.read(.Direction),
 | 
				
			||||||
 | 
					                0x0800_00C8 => return self.gpio.read(.Control),
 | 
				
			||||||
 | 
					                else => {},
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            u8 => switch (address) {
 | 
				
			||||||
 | 
					                0x0800_00C4 => return self.gpio.read(.Data),
 | 
				
			||||||
 | 
					                0x0800_00C6 => return self.gpio.read(.Direction),
 | 
				
			||||||
 | 
					                0x0800_00C8 => return self.gpio.read(.Control),
 | 
				
			||||||
 | 
					                else => {},
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            else => @compileError("GamePak[GPIO]: Unsupported read width"),
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return switch (T) {
 | 
				
			||||||
 | 
					        u32 => (@as(T, self.get(addr + 3)) << 24) | (@as(T, self.get(addr + 2)) << 16) | (@as(T, self.get(addr + 1)) << 8) | (@as(T, self.get(addr))),
 | 
				
			||||||
 | 
					        u16 => (@as(T, self.get(addr + 1)) << 8) | @as(T, self.get(addr)),
 | 
				
			||||||
 | 
					        u8 => self.get(addr),
 | 
				
			||||||
 | 
					        else => @compileError("GamePak: Unsupported read width"),
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn write(self: *Self, comptime T: type, word_count: u16, address: u32, value: T) void {
 | 
				
			||||||
 | 
					    const addr = address & 0x1FF_FFFF;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (self.backup.kind == .Eeprom) {
 | 
				
			||||||
 | 
					        const bit = @truncate(u1, value);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (self.buf.len > 0x100_0000) { // Large
 | 
				
			||||||
 | 
					            // Addresses 0x1FF_FF00 to 0x1FF_FFFF are reserved from EEPROM accesses if
 | 
				
			||||||
 | 
					            // * Backup type is EEPROM
 | 
				
			||||||
 | 
					            // * Large ROM (Size is greater than 16MB)
 | 
				
			||||||
 | 
					            if (addr > 0x1FF_FEFF)
 | 
				
			||||||
 | 
					                return self.backup.eeprom.write(word_count, &self.backup.buf, bit);
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            // Addresses 0x0D00_0000 to 0x0DFF_FFFF are reserved for EEPROM accesses if
 | 
				
			||||||
 | 
					            // * Backup type is EEPROM
 | 
				
			||||||
 | 
					            // * Small ROM (less than 16MB)
 | 
				
			||||||
 | 
					            if (@truncate(u8, address >> 24) == 0x0D)
 | 
				
			||||||
 | 
					                return self.backup.eeprom.write(word_count, &self.backup.buf, bit);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    switch (T) {
 | 
				
			||||||
 | 
					        u32 => switch (address) {
 | 
				
			||||||
 | 
					            0x0800_00C4 => {
 | 
				
			||||||
 | 
					                self.gpio.write(.Data, @truncate(u4, value));
 | 
				
			||||||
 | 
					                self.gpio.write(.Direction, @truncate(u4, value >> 16));
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            0x0800_00C6 => {
 | 
				
			||||||
 | 
					                self.gpio.write(.Direction, @truncate(u4, value));
 | 
				
			||||||
 | 
					                self.gpio.write(.Control, @truncate(u1, value >> 16));
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            else => log.err("Wrote {} 0x{X:0>8} to 0x{X:0>8}, Unhandled", .{ T, value, address }),
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        u16 => switch (address) {
 | 
				
			||||||
 | 
					            0x0800_00C4 => self.gpio.write(.Data, @truncate(u4, value)),
 | 
				
			||||||
 | 
					            0x0800_00C6 => self.gpio.write(.Direction, @truncate(u4, value)),
 | 
				
			||||||
 | 
					            0x0800_00C8 => self.gpio.write(.Control, @truncate(u1, value)),
 | 
				
			||||||
 | 
					            else => log.err("Wrote {} 0x{X:0>4} to 0x{X:0>8}, Unhandled", .{ T, value, address }),
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        u8 => log.debug("Wrote {} 0x{X:0>2} to 0x{X:0>8}, Ignored.", .{ T, value, address }),
 | 
				
			||||||
 | 
					        else => @compileError("GamePak: Unsupported write width"),
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn init(allocator: Allocator, cpu: *Arm7tdmi, rom_path: []const u8, save_path: ?[]const u8) !Self {
 | 
				
			||||||
 | 
					    const file = try std.fs.cwd().openFile(rom_path, .{});
 | 
				
			||||||
 | 
					    defer file.close();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const file_buf = try file.readToEndAlloc(allocator, try file.getEndPos());
 | 
				
			||||||
 | 
					    const title = file_buf[0xA0..0xAC].*;
 | 
				
			||||||
 | 
					    const kind = Backup.guess(file_buf);
 | 
				
			||||||
 | 
					    const device = if (config.config().guest.force_rtc) .Rtc else guessDevice(file_buf);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    logHeader(file_buf, &title);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return .{
 | 
				
			||||||
 | 
					        .buf = file_buf,
 | 
				
			||||||
 | 
					        .allocator = allocator,
 | 
				
			||||||
 | 
					        .title = title,
 | 
				
			||||||
 | 
					        .backup = try Backup.init(allocator, kind, title, save_path),
 | 
				
			||||||
 | 
					        .gpio = try Gpio.init(allocator, cpu, device),
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn deinit(self: *Self) void {
 | 
				
			||||||
 | 
					    self.backup.deinit();
 | 
				
			||||||
 | 
					    self.gpio.deinit(self.allocator);
 | 
				
			||||||
 | 
					    self.allocator.destroy(self.gpio);
 | 
				
			||||||
 | 
					    self.allocator.free(self.buf);
 | 
				
			||||||
 | 
					    self.* = undefined;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// Searches the ROM to see if it can determine whether the ROM it's searching uses
 | 
				
			||||||
 | 
					/// any GPIO device, like a RTC for example.
 | 
				
			||||||
 | 
					fn guessDevice(buf: []const u8) Gpio.Device.Kind {
 | 
				
			||||||
 | 
					    // Try to Guess if ROM uses RTC
 | 
				
			||||||
 | 
					    const needle = "RTC_V"; // I was told SIIRTC_V, though Pokemen Firered (USA) is a false negative
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    var i: usize = 0;
 | 
				
			||||||
 | 
					    while ((i + needle.len) < buf.len) : (i += 1) {
 | 
				
			||||||
 | 
					        if (std.mem.eql(u8, needle, buf[i..(i + needle.len)])) return .Rtc;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // TODO: Detect other GPIO devices
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return .None;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fn logHeader(buf: []const u8, title: *const [12]u8) void {
 | 
				
			||||||
 | 
					    const code = buf[0xAC..0xB0];
 | 
				
			||||||
 | 
					    const maker = buf[0xB0..0xB2];
 | 
				
			||||||
 | 
					    const version = buf[0xBC];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    log.info("Title: {s}", .{title});
 | 
				
			||||||
 | 
					    if (version != 0) log.info("Version: {}", .{version});
 | 
				
			||||||
 | 
					    log.info("Game Code: {s}", .{code});
 | 
				
			||||||
 | 
					    log.info("Maker Code: {s}", .{maker});
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					test "OOB Access" {
 | 
				
			||||||
 | 
					    const title = .{ 'H', 'E', 'L', 'L', 'O', ' ', 'W', 'O', 'R', 'L', 'D', '!' };
 | 
				
			||||||
 | 
					    const alloc = std.testing.allocator;
 | 
				
			||||||
 | 
					    const pak = Self{
 | 
				
			||||||
 | 
					        .buf = &.{},
 | 
				
			||||||
 | 
					        .alloc = alloc,
 | 
				
			||||||
 | 
					        .title = title,
 | 
				
			||||||
 | 
					        .backup = try Backup.init(alloc, .None, title, null),
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    std.debug.assert(pak.get(0) == 0x00); // 0x0000
 | 
				
			||||||
 | 
					    std.debug.assert(pak.get(1) == 0x00);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    std.debug.assert(pak.get(2) == 0x01); // 0x0001
 | 
				
			||||||
 | 
					    std.debug.assert(pak.get(3) == 0x00);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    std.debug.assert(pak.get(4) == 0x02); // 0x0002
 | 
				
			||||||
 | 
					    std.debug.assert(pak.get(5) == 0x00);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										41
									
								
								src/core/bus/Iwram.zig
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										41
									
								
								src/core/bus/Iwram.zig
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,41 @@
 | 
				
			|||||||
 | 
					const std = @import("std");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const Allocator = std.mem.Allocator;
 | 
				
			||||||
 | 
					const iwram_size = 0x8000;
 | 
				
			||||||
 | 
					const Self = @This();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					buf: []u8,
 | 
				
			||||||
 | 
					allocator: Allocator,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn read(self: *const Self, comptime T: type, address: usize) T {
 | 
				
			||||||
 | 
					    const addr = address & 0x7FFF;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return switch (T) {
 | 
				
			||||||
 | 
					        u32, u16, u8 => std.mem.readIntSliceLittle(T, self.buf[addr..][0..@sizeOf(T)]),
 | 
				
			||||||
 | 
					        else => @compileError("IWRAM: Unsupported read width"),
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn write(self: *const Self, comptime T: type, address: usize, value: T) void {
 | 
				
			||||||
 | 
					    const addr = address & 0x7FFF;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return switch (T) {
 | 
				
			||||||
 | 
					        u32, u16, u8 => std.mem.writeIntSliceLittle(T, self.buf[addr..][0..@sizeOf(T)], value),
 | 
				
			||||||
 | 
					        else => @compileError("IWRAM: Unsupported write width"),
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn init(allocator: Allocator) !Self {
 | 
				
			||||||
 | 
					    const buf = try allocator.alloc(u8, iwram_size);
 | 
				
			||||||
 | 
					    std.mem.set(u8, buf, 0);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return Self{
 | 
				
			||||||
 | 
					        .buf = buf,
 | 
				
			||||||
 | 
					        .allocator = allocator,
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn deinit(self: *Self) void {
 | 
				
			||||||
 | 
					    self.allocator.free(self.buf);
 | 
				
			||||||
 | 
					    self.* = undefined;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										219
									
								
								src/core/bus/backup.zig
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										219
									
								
								src/core/bus/backup.zig
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,219 @@
 | 
				
			|||||||
 | 
					const std = @import("std");
 | 
				
			||||||
 | 
					const Allocator = std.mem.Allocator;
 | 
				
			||||||
 | 
					const log = std.log.scoped(.Backup);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const Eeprom = @import("backup/eeprom.zig").Eeprom;
 | 
				
			||||||
 | 
					const Flash = @import("backup/Flash.zig");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const escape = @import("../../util.zig").escape;
 | 
				
			||||||
 | 
					const span = @import("../../util.zig").span;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const Needle = struct { str: []const u8, kind: Backup.Kind };
 | 
				
			||||||
 | 
					const backup_kinds = [6]Needle{
 | 
				
			||||||
 | 
					    .{ .str = "EEPROM_V", .kind = .Eeprom },
 | 
				
			||||||
 | 
					    .{ .str = "SRAM_V", .kind = .Sram },
 | 
				
			||||||
 | 
					    .{ .str = "SRAM_F_V", .kind = .Sram },
 | 
				
			||||||
 | 
					    .{ .str = "FLASH_V", .kind = .Flash },
 | 
				
			||||||
 | 
					    .{ .str = "FLASH512_V", .kind = .Flash },
 | 
				
			||||||
 | 
					    .{ .str = "FLASH1M_V", .kind = .Flash1M },
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const SaveError = error{Unsupported};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub const Backup = struct {
 | 
				
			||||||
 | 
					    const Self = @This();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    buf: []u8,
 | 
				
			||||||
 | 
					    allocator: Allocator,
 | 
				
			||||||
 | 
					    kind: Kind,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    title: [12]u8,
 | 
				
			||||||
 | 
					    save_path: ?[]const u8,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    flash: Flash,
 | 
				
			||||||
 | 
					    eeprom: Eeprom,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const Kind = enum {
 | 
				
			||||||
 | 
					        Eeprom,
 | 
				
			||||||
 | 
					        Sram,
 | 
				
			||||||
 | 
					        Flash,
 | 
				
			||||||
 | 
					        Flash1M,
 | 
				
			||||||
 | 
					        None,
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn read(self: *const Self, address: usize) u8 {
 | 
				
			||||||
 | 
					        const addr = address & 0xFFFF;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        switch (self.kind) {
 | 
				
			||||||
 | 
					            .Flash => {
 | 
				
			||||||
 | 
					                switch (addr) {
 | 
				
			||||||
 | 
					                    0x0000 => if (self.flash.id_mode) return 0x32, // Panasonic manufacturer ID
 | 
				
			||||||
 | 
					                    0x0001 => if (self.flash.id_mode) return 0x1B, // Panasonic device ID
 | 
				
			||||||
 | 
					                    else => {},
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                return self.flash.read(self.buf, addr);
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            .Flash1M => {
 | 
				
			||||||
 | 
					                switch (addr) {
 | 
				
			||||||
 | 
					                    0x0000 => if (self.flash.id_mode) return 0x62, // Sanyo manufacturer ID
 | 
				
			||||||
 | 
					                    0x0001 => if (self.flash.id_mode) return 0x13, // Sanyo device ID
 | 
				
			||||||
 | 
					                    else => {},
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                return self.flash.read(self.buf, addr);
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            .Sram => return self.buf[addr & 0x7FFF], // 32K SRAM chip is mirrored
 | 
				
			||||||
 | 
					            .None, .Eeprom => return 0xFF,
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn write(self: *Self, address: usize, byte: u8) void {
 | 
				
			||||||
 | 
					        const addr = address & 0xFFFF;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        switch (self.kind) {
 | 
				
			||||||
 | 
					            .Flash, .Flash1M => {
 | 
				
			||||||
 | 
					                if (self.flash.prep_write) return self.flash.write(self.buf, addr, byte);
 | 
				
			||||||
 | 
					                if (self.flash.shouldEraseSector(addr, byte)) return self.flash.erase(self.buf, addr);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                switch (addr) {
 | 
				
			||||||
 | 
					                    0x0000 => if (self.kind == .Flash1M and self.flash.set_bank) {
 | 
				
			||||||
 | 
					                        self.flash.bank = @truncate(u1, byte);
 | 
				
			||||||
 | 
					                    },
 | 
				
			||||||
 | 
					                    0x5555 => {
 | 
				
			||||||
 | 
					                        if (self.flash.state == .Command) {
 | 
				
			||||||
 | 
					                            self.flash.handleCommand(self.buf, byte);
 | 
				
			||||||
 | 
					                        } else if (byte == 0xAA and self.flash.state == .Ready) {
 | 
				
			||||||
 | 
					                            self.flash.state = .Set;
 | 
				
			||||||
 | 
					                        } else if (byte == 0xF0) {
 | 
				
			||||||
 | 
					                            self.flash.state = .Ready;
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					                    },
 | 
				
			||||||
 | 
					                    0x2AAA => if (byte == 0x55 and self.flash.state == .Set) {
 | 
				
			||||||
 | 
					                        self.flash.state = .Command;
 | 
				
			||||||
 | 
					                    },
 | 
				
			||||||
 | 
					                    else => {},
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            .Sram => self.buf[addr & 0x7FFF] = byte,
 | 
				
			||||||
 | 
					            .None, .Eeprom => {},
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn init(allocator: Allocator, kind: Kind, title: [12]u8, path: ?[]const u8) !Self {
 | 
				
			||||||
 | 
					        log.info("Kind: {}", .{kind});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const buf_size: usize = switch (kind) {
 | 
				
			||||||
 | 
					            .Sram => 0x8000, // 32K
 | 
				
			||||||
 | 
					            .Flash => 0x10000, // 64K
 | 
				
			||||||
 | 
					            .Flash1M => 0x20000, // 128K
 | 
				
			||||||
 | 
					            .None, .Eeprom => 0, // EEPROM is handled upon first Read Request to it
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const buf = try allocator.alloc(u8, buf_size);
 | 
				
			||||||
 | 
					        std.mem.set(u8, buf, 0xFF);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        var backup = Self{
 | 
				
			||||||
 | 
					            .buf = buf,
 | 
				
			||||||
 | 
					            .allocator = allocator,
 | 
				
			||||||
 | 
					            .kind = kind,
 | 
				
			||||||
 | 
					            .title = title,
 | 
				
			||||||
 | 
					            .save_path = path,
 | 
				
			||||||
 | 
					            .flash = Flash.create(),
 | 
				
			||||||
 | 
					            .eeprom = Eeprom.create(allocator),
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (backup.save_path) |p| backup.readSave(allocator, p) catch |e| log.err("Failed to load save: {}", .{e});
 | 
				
			||||||
 | 
					        return backup;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn deinit(self: *Self) void {
 | 
				
			||||||
 | 
					        if (self.save_path) |path| self.writeSave(self.allocator, path) catch |e| log.err("Failed to write save: {}", .{e});
 | 
				
			||||||
 | 
					        self.allocator.free(self.buf);
 | 
				
			||||||
 | 
					        self.* = undefined;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /// Guesses the Backup Kind of a GBA ROM
 | 
				
			||||||
 | 
					    pub fn guess(rom: []const u8) Kind {
 | 
				
			||||||
 | 
					        for (backup_kinds) |needle| {
 | 
				
			||||||
 | 
					            const needle_len = needle.str.len;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            var i: usize = 0;
 | 
				
			||||||
 | 
					            while ((i + needle_len) < rom.len) : (i += 1) {
 | 
				
			||||||
 | 
					                if (std.mem.eql(u8, needle.str, rom[i..][0..needle_len])) return needle.kind;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return .None;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fn readSave(self: *Self, allocator: Allocator, path: []const u8) !void {
 | 
				
			||||||
 | 
					        const file_path = try self.savePath(allocator, path);
 | 
				
			||||||
 | 
					        defer allocator.free(file_path);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // FIXME: Don't rely on this lol
 | 
				
			||||||
 | 
					        if (std.mem.eql(u8, file_path[file_path.len - 12 .. file_path.len], "untitled.sav")) {
 | 
				
			||||||
 | 
					            return log.err("ROM header lacks title, no save loaded", .{});
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const file: std.fs.File = try std.fs.openFileAbsolute(file_path, .{});
 | 
				
			||||||
 | 
					        const file_buf = try file.readToEndAlloc(allocator, try file.getEndPos());
 | 
				
			||||||
 | 
					        defer allocator.free(file_buf);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        switch (self.kind) {
 | 
				
			||||||
 | 
					            .Sram, .Flash, .Flash1M => {
 | 
				
			||||||
 | 
					                if (self.buf.len == file_buf.len) {
 | 
				
			||||||
 | 
					                    std.mem.copy(u8, self.buf, file_buf);
 | 
				
			||||||
 | 
					                    return log.info("Loaded Save from {s}", .{file_path});
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                log.err("{s} is {} bytes, but we expected {} bytes", .{ file_path, file_buf.len, self.buf.len });
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            .Eeprom => {
 | 
				
			||||||
 | 
					                if (file_buf.len == 0x200 or file_buf.len == 0x2000) {
 | 
				
			||||||
 | 
					                    self.eeprom.kind = if (file_buf.len == 0x200) .Small else .Large;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    self.buf = try allocator.alloc(u8, file_buf.len);
 | 
				
			||||||
 | 
					                    std.mem.copy(u8, self.buf, file_buf);
 | 
				
			||||||
 | 
					                    return log.info("Loaded Save from {s}", .{file_path});
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                log.err("EEPROM can either be 0x200 bytes or 0x2000 byes, but {s} was {X:} bytes", .{
 | 
				
			||||||
 | 
					                    file_path,
 | 
				
			||||||
 | 
					                    file_buf.len,
 | 
				
			||||||
 | 
					                });
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            .None => return SaveError.Unsupported,
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fn savePath(self: *const Self, allocator: Allocator, path: []const u8) ![]const u8 {
 | 
				
			||||||
 | 
					        const filename = try self.saveName(allocator);
 | 
				
			||||||
 | 
					        defer allocator.free(filename);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return try std.fs.path.join(allocator, &[_][]const u8{ path, filename });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fn saveName(self: *const Self, allocator: Allocator) ![]const u8 {
 | 
				
			||||||
 | 
					        const title_str = span(&escape(self.title));
 | 
				
			||||||
 | 
					        const name = if (title_str.len != 0) title_str else "untitled";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return try std.mem.concat(allocator, u8, &[_][]const u8{ name, ".sav" });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fn writeSave(self: Self, allocator: Allocator, path: []const u8) !void {
 | 
				
			||||||
 | 
					        const file_path = try self.savePath(allocator, path);
 | 
				
			||||||
 | 
					        defer allocator.free(file_path);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        switch (self.kind) {
 | 
				
			||||||
 | 
					            .Sram, .Flash, .Flash1M, .Eeprom => {
 | 
				
			||||||
 | 
					                const file = try std.fs.createFileAbsolute(file_path, .{});
 | 
				
			||||||
 | 
					                defer file.close();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                try file.writeAll(self.buf);
 | 
				
			||||||
 | 
					                log.info("Wrote Save to {s}", .{file_path});
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            else => return SaveError.Unsupported,
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
							
								
								
									
										72
									
								
								src/core/bus/backup/Flash.zig
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										72
									
								
								src/core/bus/backup/Flash.zig
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,72 @@
 | 
				
			|||||||
 | 
					const std = @import("std");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const Self = @This();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					state: State,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					id_mode: bool,
 | 
				
			||||||
 | 
					set_bank: bool,
 | 
				
			||||||
 | 
					prep_erase: bool,
 | 
				
			||||||
 | 
					prep_write: bool,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					bank: u1,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const State = enum {
 | 
				
			||||||
 | 
					    Ready,
 | 
				
			||||||
 | 
					    Set,
 | 
				
			||||||
 | 
					    Command,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn read(self: *const Self, buf: []u8, idx: usize) u8 {
 | 
				
			||||||
 | 
					    return buf[self.address() + idx];
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn write(self: *Self, buf: []u8, idx: usize, byte: u8) void {
 | 
				
			||||||
 | 
					    buf[self.address() + idx] = byte;
 | 
				
			||||||
 | 
					    self.prep_write = false;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn create() Self {
 | 
				
			||||||
 | 
					    return .{
 | 
				
			||||||
 | 
					        .state = .Ready,
 | 
				
			||||||
 | 
					        .id_mode = false,
 | 
				
			||||||
 | 
					        .set_bank = false,
 | 
				
			||||||
 | 
					        .prep_erase = false,
 | 
				
			||||||
 | 
					        .prep_write = false,
 | 
				
			||||||
 | 
					        .bank = 0,
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn handleCommand(self: *Self, buf: []u8, byte: u8) void {
 | 
				
			||||||
 | 
					    switch (byte) {
 | 
				
			||||||
 | 
					        0x90 => self.id_mode = true,
 | 
				
			||||||
 | 
					        0xF0 => self.id_mode = false,
 | 
				
			||||||
 | 
					        0xB0 => self.set_bank = true,
 | 
				
			||||||
 | 
					        0x80 => self.prep_erase = true,
 | 
				
			||||||
 | 
					        0x10 => {
 | 
				
			||||||
 | 
					            std.mem.set(u8, buf, 0xFF);
 | 
				
			||||||
 | 
					            self.prep_erase = false;
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        0xA0 => self.prep_write = true,
 | 
				
			||||||
 | 
					        else => std.debug.panic("Unhandled Flash Command: 0x{X:0>2}", .{byte}),
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    self.state = .Ready;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn shouldEraseSector(self: *const Self, addr: usize, byte: u8) bool {
 | 
				
			||||||
 | 
					    return self.state == .Command and self.prep_erase and byte == 0x30 and addr & 0xFFF == 0x000;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn erase(self: *Self, buf: []u8, sector: usize) void {
 | 
				
			||||||
 | 
					    const start = self.address() + (sector & 0xF000);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    std.mem.set(u8, buf[start..][0..0x1000], 0xFF);
 | 
				
			||||||
 | 
					    self.prep_erase = false;
 | 
				
			||||||
 | 
					    self.state = .Ready;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// Base Address
 | 
				
			||||||
 | 
					inline fn address(self: *const Self) usize {
 | 
				
			||||||
 | 
					    return if (self.bank == 1) 0x10000 else @as(usize, 0);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										269
									
								
								src/core/bus/backup/eeprom.zig
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										269
									
								
								src/core/bus/backup/eeprom.zig
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,269 @@
 | 
				
			|||||||
 | 
					const std = @import("std");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const Allocator = std.mem.Allocator;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const log = std.log.scoped(.Eeprom);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub const Eeprom = struct {
 | 
				
			||||||
 | 
					    const Self = @This();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    addr: u14,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    kind: Kind,
 | 
				
			||||||
 | 
					    state: State,
 | 
				
			||||||
 | 
					    writer: Writer,
 | 
				
			||||||
 | 
					    reader: Reader,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    allocator: Allocator,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const Kind = enum {
 | 
				
			||||||
 | 
					        Unknown,
 | 
				
			||||||
 | 
					        Small, // 512B
 | 
				
			||||||
 | 
					        Large, // 8KB
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const State = enum {
 | 
				
			||||||
 | 
					        Ready,
 | 
				
			||||||
 | 
					        Read,
 | 
				
			||||||
 | 
					        Write,
 | 
				
			||||||
 | 
					        WriteTransfer,
 | 
				
			||||||
 | 
					        RequestEnd,
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn read(self: *Self) u1 {
 | 
				
			||||||
 | 
					        return self.reader.read();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn dbgRead(self: *const Self) u1 {
 | 
				
			||||||
 | 
					        return self.reader.dbgRead();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn write(self: *Self, word_count: u16, buf: *[]u8, bit: u1) void {
 | 
				
			||||||
 | 
					        if (self.guessKind(word_count)) |found| {
 | 
				
			||||||
 | 
					            log.info("EEPROM Kind: {}", .{found});
 | 
				
			||||||
 | 
					            self.kind = found;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // buf.len will not equal zero when a save file was found and loaded.
 | 
				
			||||||
 | 
					            // Right now, we assume that the save file is of the correct size which
 | 
				
			||||||
 | 
					            // isn't necessarily true, since we can't trust anything a user can influence
 | 
				
			||||||
 | 
					            // TODO: use ?[]u8 instead of a 0-sized slice?
 | 
				
			||||||
 | 
					            if (buf.len == 0) {
 | 
				
			||||||
 | 
					                const len: usize = switch (found) {
 | 
				
			||||||
 | 
					                    .Small => 0x200,
 | 
				
			||||||
 | 
					                    .Large => 0x2000,
 | 
				
			||||||
 | 
					                    else => unreachable,
 | 
				
			||||||
 | 
					                };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                buf.* = self.allocator.alloc(u8, len) catch |e| {
 | 
				
			||||||
 | 
					                    log.err("Failed to resize EEPROM buf to {} bytes", .{len});
 | 
				
			||||||
 | 
					                    std.debug.panic("EEPROM entered irrecoverable state {}", .{e});
 | 
				
			||||||
 | 
					                };
 | 
				
			||||||
 | 
					                std.mem.set(u8, buf.*, 0xFF);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (self.state == .RequestEnd) {
 | 
				
			||||||
 | 
					            if (bit != 0) log.debug("EEPROM Request did not end in 0u1. TODO: is this ok?", .{});
 | 
				
			||||||
 | 
					            self.state = .Ready;
 | 
				
			||||||
 | 
					            return;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        switch (self.state) {
 | 
				
			||||||
 | 
					            .Ready => self.writer.requestWrite(bit),
 | 
				
			||||||
 | 
					            .Read, .Write => self.writer.addressWrite(self.kind, bit),
 | 
				
			||||||
 | 
					            .WriteTransfer => self.writer.dataWrite(bit),
 | 
				
			||||||
 | 
					            .RequestEnd => unreachable, // We return early just above this block
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.tick(buf.*);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn create(allocator: Allocator) Self {
 | 
				
			||||||
 | 
					        return .{
 | 
				
			||||||
 | 
					            .kind = .Unknown,
 | 
				
			||||||
 | 
					            .state = .Ready,
 | 
				
			||||||
 | 
					            .writer = Writer.create(),
 | 
				
			||||||
 | 
					            .reader = Reader.create(),
 | 
				
			||||||
 | 
					            .addr = 0,
 | 
				
			||||||
 | 
					            .allocator = allocator,
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fn guessKind(self: *const Self, word_count: u16) ?Kind {
 | 
				
			||||||
 | 
					        if (self.kind != .Unknown or self.state != .Read) return null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return switch (word_count) {
 | 
				
			||||||
 | 
					            17 => .Large,
 | 
				
			||||||
 | 
					            9 => .Small,
 | 
				
			||||||
 | 
					            else => blk: {
 | 
				
			||||||
 | 
					                log.err("Unexpected length of DMA3 Transfer upon initial EEPROM read: {}", .{word_count});
 | 
				
			||||||
 | 
					                break :blk null;
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fn tick(self: *Self, buf: []u8) void {
 | 
				
			||||||
 | 
					        switch (self.state) {
 | 
				
			||||||
 | 
					            .Ready => {
 | 
				
			||||||
 | 
					                if (self.writer.len() == 2) {
 | 
				
			||||||
 | 
					                    const req = @intCast(u2, self.writer.finish());
 | 
				
			||||||
 | 
					                    switch (req) {
 | 
				
			||||||
 | 
					                        0b11 => self.state = .Read,
 | 
				
			||||||
 | 
					                        0b10 => self.state = .Write,
 | 
				
			||||||
 | 
					                        else => log.err("Unknown EEPROM Request 0b{b:0>2}", .{req}),
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            .Read => {
 | 
				
			||||||
 | 
					                switch (self.kind) {
 | 
				
			||||||
 | 
					                    .Large => {
 | 
				
			||||||
 | 
					                        if (self.writer.len() == 14) {
 | 
				
			||||||
 | 
					                            const addr = @intCast(u10, self.writer.finish());
 | 
				
			||||||
 | 
					                            const value = std.mem.readIntSliceLittle(u64, buf[@as(u13, addr) * 8 ..][0..8]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                            self.reader.configure(value);
 | 
				
			||||||
 | 
					                            self.state = .RequestEnd;
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					                    },
 | 
				
			||||||
 | 
					                    .Small => {
 | 
				
			||||||
 | 
					                        if (self.writer.len() == 6) {
 | 
				
			||||||
 | 
					                            // FIXME: Duplicated code from above
 | 
				
			||||||
 | 
					                            const addr = @intCast(u6, self.writer.finish());
 | 
				
			||||||
 | 
					                            const value = std.mem.readIntSliceLittle(u64, buf[@as(u13, addr) * 8 ..][0..8]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                            self.reader.configure(value);
 | 
				
			||||||
 | 
					                            self.state = .RequestEnd;
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					                    },
 | 
				
			||||||
 | 
					                    else => log.err("Unable to calculate EEPROM read address. EEPROM size UNKNOWN", .{}),
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            .Write => {
 | 
				
			||||||
 | 
					                switch (self.kind) {
 | 
				
			||||||
 | 
					                    .Large => {
 | 
				
			||||||
 | 
					                        if (self.writer.len() == 14) {
 | 
				
			||||||
 | 
					                            self.addr = @intCast(u10, self.writer.finish());
 | 
				
			||||||
 | 
					                            self.state = .WriteTransfer;
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					                    },
 | 
				
			||||||
 | 
					                    .Small => {
 | 
				
			||||||
 | 
					                        if (self.writer.len() == 6) {
 | 
				
			||||||
 | 
					                            self.addr = @intCast(u6, self.writer.finish());
 | 
				
			||||||
 | 
					                            self.state = .WriteTransfer;
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					                    },
 | 
				
			||||||
 | 
					                    else => log.err("Unable to calculate EEPROM write address. EEPROM size UNKNOWN", .{}),
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            .WriteTransfer => {
 | 
				
			||||||
 | 
					                if (self.writer.len() == 64) {
 | 
				
			||||||
 | 
					                    std.mem.writeIntSliceLittle(u64, buf[self.addr * 8 ..][0..8], self.writer.finish());
 | 
				
			||||||
 | 
					                    self.state = .RequestEnd;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            .RequestEnd => unreachable, // We return early in write() if state is .RequestEnd
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const Reader = struct {
 | 
				
			||||||
 | 
					    const Self = @This();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    data: u64,
 | 
				
			||||||
 | 
					    i: u8,
 | 
				
			||||||
 | 
					    enabled: bool,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fn create() Self {
 | 
				
			||||||
 | 
					        return .{
 | 
				
			||||||
 | 
					            .data = 0,
 | 
				
			||||||
 | 
					            .i = 0,
 | 
				
			||||||
 | 
					            .enabled = false,
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fn read(self: *Self) u1 {
 | 
				
			||||||
 | 
					        if (!self.enabled) return 1;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const bit = if (self.i < 4) blk: {
 | 
				
			||||||
 | 
					            break :blk 0;
 | 
				
			||||||
 | 
					        } else blk: {
 | 
				
			||||||
 | 
					            const idx = @intCast(u6, 63 - (self.i - 4));
 | 
				
			||||||
 | 
					            break :blk @truncate(u1, self.data >> idx);
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.i = (self.i + 1) % (64 + 4);
 | 
				
			||||||
 | 
					        if (self.i == 0) self.enabled = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return bit;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fn dbgRead(self: *const Self) u1 {
 | 
				
			||||||
 | 
					        if (!self.enabled) return 1;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const bit = if (self.i < 4) blk: {
 | 
				
			||||||
 | 
					            break :blk 0;
 | 
				
			||||||
 | 
					        } else blk: {
 | 
				
			||||||
 | 
					            const idx = @intCast(u6, 63 - (self.i - 4));
 | 
				
			||||||
 | 
					            break :blk @truncate(u1, self.data >> idx);
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return bit;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fn configure(self: *Self, value: u64) void {
 | 
				
			||||||
 | 
					        self.data = value;
 | 
				
			||||||
 | 
					        self.i = 0;
 | 
				
			||||||
 | 
					        self.enabled = true;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const Writer = struct {
 | 
				
			||||||
 | 
					    const Self = @This();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    data: u64,
 | 
				
			||||||
 | 
					    i: u8,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fn create() Self {
 | 
				
			||||||
 | 
					        return .{ .data = 0, .i = 0 };
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fn requestWrite(self: *Self, bit: u1) void {
 | 
				
			||||||
 | 
					        const idx = @intCast(u1, 1 - self.i);
 | 
				
			||||||
 | 
					        self.data = (self.data & ~(@as(u64, 1) << idx)) | (@as(u64, bit) << idx);
 | 
				
			||||||
 | 
					        self.i += 1;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fn addressWrite(self: *Self, kind: Eeprom.Kind, bit: u1) void {
 | 
				
			||||||
 | 
					        if (kind == .Unknown) return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const size: u4 = switch (kind) {
 | 
				
			||||||
 | 
					            .Large => 13,
 | 
				
			||||||
 | 
					            .Small => 5,
 | 
				
			||||||
 | 
					            .Unknown => unreachable,
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const idx = @intCast(u4, size - self.i);
 | 
				
			||||||
 | 
					        self.data = (self.data & ~(@as(u64, 1) << idx)) | (@as(u64, bit) << idx);
 | 
				
			||||||
 | 
					        self.i += 1;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fn dataWrite(self: *Self, bit: u1) void {
 | 
				
			||||||
 | 
					        const idx = @intCast(u6, 63 - self.i);
 | 
				
			||||||
 | 
					        self.data = (self.data & ~(@as(u64, 1) << idx)) | (@as(u64, bit) << idx);
 | 
				
			||||||
 | 
					        self.i += 1;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fn len(self: *const Self) u8 {
 | 
				
			||||||
 | 
					        return self.i;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fn finish(self: *Self) u64 {
 | 
				
			||||||
 | 
					        defer self.reset();
 | 
				
			||||||
 | 
					        return self.data;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fn reset(self: *Self) void {
 | 
				
			||||||
 | 
					        self.i = 0;
 | 
				
			||||||
 | 
					        self.data = 0;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
							
								
								
									
										288
									
								
								src/core/bus/dma.zig
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										288
									
								
								src/core/bus/dma.zig
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,288 @@
 | 
				
			|||||||
 | 
					const std = @import("std");
 | 
				
			||||||
 | 
					const util = @import("../../util.zig");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const DmaControl = @import("io.zig").DmaControl;
 | 
				
			||||||
 | 
					const Bus = @import("../Bus.zig");
 | 
				
			||||||
 | 
					const Arm7tdmi = @import("../cpu.zig").Arm7tdmi;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub const DmaTuple = std.meta.Tuple(&[_]type{ DmaController(0), DmaController(1), DmaController(2), DmaController(3) });
 | 
				
			||||||
 | 
					const log = std.log.scoped(.DmaTransfer);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const setHi = util.setHi;
 | 
				
			||||||
 | 
					const setLo = util.setLo;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn create() DmaTuple {
 | 
				
			||||||
 | 
					    return .{ DmaController(0).init(), DmaController(1).init(), DmaController(2).init(), DmaController(3).init() };
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn read(comptime T: type, dma: *const DmaTuple, addr: u32) ?T {
 | 
				
			||||||
 | 
					    const byte = @truncate(u8, addr);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return switch (T) {
 | 
				
			||||||
 | 
					        u32 => switch (byte) {
 | 
				
			||||||
 | 
					            0xB8 => @as(T, dma.*[0].cnt.raw) << 16,
 | 
				
			||||||
 | 
					            0xC4 => @as(T, dma.*[1].cnt.raw) << 16,
 | 
				
			||||||
 | 
					            0xD0 => @as(T, dma.*[2].cnt.raw) << 16,
 | 
				
			||||||
 | 
					            0xDC => @as(T, dma.*[3].cnt.raw) << 16,
 | 
				
			||||||
 | 
					            else => util.io.read.undef(T, log, "Tried to perform a {} read to 0x{X:0>8}", .{ T, addr }),
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        u16 => switch (byte) {
 | 
				
			||||||
 | 
					            0xBA => dma.*[0].cnt.raw,
 | 
				
			||||||
 | 
					            0xC6 => dma.*[1].cnt.raw,
 | 
				
			||||||
 | 
					            0xD2 => dma.*[2].cnt.raw,
 | 
				
			||||||
 | 
					            0xDE => dma.*[3].cnt.raw,
 | 
				
			||||||
 | 
					            else => util.io.read.undef(T, log, "Tried to perform a {} read to 0x{X:0>8}", .{ T, addr }),
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        u8 => util.io.read.undef(T, log, "Tried to perform a {} read to 0x{X:0>8}", .{ T, addr }),
 | 
				
			||||||
 | 
					        else => @compileError("DMA: Unsupported read width"),
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn write(comptime T: type, dma: *DmaTuple, addr: u32, value: T) void {
 | 
				
			||||||
 | 
					    const byte = @truncate(u8, addr);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    switch (T) {
 | 
				
			||||||
 | 
					        u32 => switch (byte) {
 | 
				
			||||||
 | 
					            0xB0 => dma.*[0].setDmasad(value),
 | 
				
			||||||
 | 
					            0xB4 => dma.*[0].setDmadad(value),
 | 
				
			||||||
 | 
					            0xB8 => dma.*[0].setDmacnt(value),
 | 
				
			||||||
 | 
					            0xBC => dma.*[1].setDmasad(value),
 | 
				
			||||||
 | 
					            0xC0 => dma.*[1].setDmadad(value),
 | 
				
			||||||
 | 
					            0xC4 => dma.*[1].setDmacnt(value),
 | 
				
			||||||
 | 
					            0xC8 => dma.*[2].setDmasad(value),
 | 
				
			||||||
 | 
					            0xCC => dma.*[2].setDmadad(value),
 | 
				
			||||||
 | 
					            0xD0 => dma.*[2].setDmacnt(value),
 | 
				
			||||||
 | 
					            0xD4 => dma.*[3].setDmasad(value),
 | 
				
			||||||
 | 
					            0xD8 => dma.*[3].setDmadad(value),
 | 
				
			||||||
 | 
					            0xDC => dma.*[3].setDmacnt(value),
 | 
				
			||||||
 | 
					            else => util.io.write.undef(log, "Tried to write 0x{X:0>8}{} to 0x{X:0>8}", .{ value, T, addr }),
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        u16 => switch (byte) {
 | 
				
			||||||
 | 
					            0xB0 => dma.*[0].setDmasad(setLo(u32, dma.*[0].sad, value)),
 | 
				
			||||||
 | 
					            0xB2 => dma.*[0].setDmasad(setHi(u32, dma.*[0].sad, value)),
 | 
				
			||||||
 | 
					            0xB4 => dma.*[0].setDmadad(setLo(u32, dma.*[0].dad, value)),
 | 
				
			||||||
 | 
					            0xB6 => dma.*[0].setDmadad(setHi(u32, dma.*[0].dad, value)),
 | 
				
			||||||
 | 
					            0xB8 => dma.*[0].setDmacntL(value),
 | 
				
			||||||
 | 
					            0xBA => dma.*[0].setDmacntH(value),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            0xBC => dma.*[1].setDmasad(setLo(u32, dma.*[1].sad, value)),
 | 
				
			||||||
 | 
					            0xBE => dma.*[1].setDmasad(setHi(u32, dma.*[1].sad, value)),
 | 
				
			||||||
 | 
					            0xC0 => dma.*[1].setDmadad(setLo(u32, dma.*[1].dad, value)),
 | 
				
			||||||
 | 
					            0xC2 => dma.*[1].setDmadad(setHi(u32, dma.*[1].dad, value)),
 | 
				
			||||||
 | 
					            0xC4 => dma.*[1].setDmacntL(value),
 | 
				
			||||||
 | 
					            0xC6 => dma.*[1].setDmacntH(value),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            0xC8 => dma.*[2].setDmasad(setLo(u32, dma.*[2].sad, value)),
 | 
				
			||||||
 | 
					            0xCA => dma.*[2].setDmasad(setHi(u32, dma.*[2].sad, value)),
 | 
				
			||||||
 | 
					            0xCC => dma.*[2].setDmadad(setLo(u32, dma.*[2].dad, value)),
 | 
				
			||||||
 | 
					            0xCE => dma.*[2].setDmadad(setHi(u32, dma.*[2].dad, value)),
 | 
				
			||||||
 | 
					            0xD0 => dma.*[2].setDmacntL(value),
 | 
				
			||||||
 | 
					            0xD2 => dma.*[2].setDmacntH(value),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            0xD4 => dma.*[3].setDmasad(setLo(u32, dma.*[3].sad, value)),
 | 
				
			||||||
 | 
					            0xD6 => dma.*[3].setDmasad(setHi(u32, dma.*[3].sad, value)),
 | 
				
			||||||
 | 
					            0xD8 => dma.*[3].setDmadad(setLo(u32, dma.*[3].dad, value)),
 | 
				
			||||||
 | 
					            0xDA => dma.*[3].setDmadad(setHi(u32, dma.*[3].dad, value)),
 | 
				
			||||||
 | 
					            0xDC => dma.*[3].setDmacntL(value),
 | 
				
			||||||
 | 
					            0xDE => dma.*[3].setDmacntH(value),
 | 
				
			||||||
 | 
					            else => util.io.write.undef(log, "Tried to write 0x{X:0>4}{} to 0x{X:0>8}", .{ value, T, addr }),
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        u8 => util.io.write.undef(log, "Tried to write 0x{X:0>2}{} to 0x{X:0>8}", .{ value, T, addr }),
 | 
				
			||||||
 | 
					        else => @compileError("DMA: Unsupported write width"),
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// Function that creates a DMAController. Determines unique DMA Controller behaiour at compile-time
 | 
				
			||||||
 | 
					fn DmaController(comptime id: u2) type {
 | 
				
			||||||
 | 
					    return struct {
 | 
				
			||||||
 | 
					        const Self = @This();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const sad_mask: u32 = if (id == 0) 0x07FF_FFFF else 0x0FFF_FFFF;
 | 
				
			||||||
 | 
					        const dad_mask: u32 = if (id != 3) 0x07FF_FFFF else 0x0FFF_FFFF;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /// Write-only. The first address in a DMA transfer. (DMASAD)
 | 
				
			||||||
 | 
					        /// Note: use writeSrc instead of manipulating src_addr directly
 | 
				
			||||||
 | 
					        sad: u32,
 | 
				
			||||||
 | 
					        /// Write-only. The final address in a DMA transffer. (DMADAD)
 | 
				
			||||||
 | 
					        /// Note: Use writeDst instead of manipulatig dst_addr directly
 | 
				
			||||||
 | 
					        dad: u32,
 | 
				
			||||||
 | 
					        /// Write-only. The Word Count for the DMA Transfer (DMACNT_L)
 | 
				
			||||||
 | 
					        word_count: if (id == 3) u16 else u14,
 | 
				
			||||||
 | 
					        /// Read / Write. DMACNT_H
 | 
				
			||||||
 | 
					        /// Note: Use writeControl instead of manipulating cnt directly.
 | 
				
			||||||
 | 
					        cnt: DmaControl,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /// Internal. Currrent Source Address
 | 
				
			||||||
 | 
					        sad_latch: u32,
 | 
				
			||||||
 | 
					        /// Internal. Current Destination Address
 | 
				
			||||||
 | 
					        dad_latch: u32,
 | 
				
			||||||
 | 
					        /// Internal. Word Count
 | 
				
			||||||
 | 
					        _word_count: if (id == 3) u16 else u14,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /// Some DMA Transfers are enabled during Hblank / VBlank and / or
 | 
				
			||||||
 | 
					        /// have delays. Thefore bit 15 of DMACNT isn't actually something
 | 
				
			||||||
 | 
					        /// we can use to control when we do or do not execute a step in a DMA Transfer
 | 
				
			||||||
 | 
					        in_progress: bool,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        pub fn init() Self {
 | 
				
			||||||
 | 
					            return .{
 | 
				
			||||||
 | 
					                .sad = 0,
 | 
				
			||||||
 | 
					                .dad = 0,
 | 
				
			||||||
 | 
					                .word_count = 0,
 | 
				
			||||||
 | 
					                .cnt = .{ .raw = 0x000 },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                // Internals
 | 
				
			||||||
 | 
					                .sad_latch = 0,
 | 
				
			||||||
 | 
					                .dad_latch = 0,
 | 
				
			||||||
 | 
					                ._word_count = 0,
 | 
				
			||||||
 | 
					                .in_progress = false,
 | 
				
			||||||
 | 
					            };
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        pub fn setDmasad(self: *Self, addr: u32) void {
 | 
				
			||||||
 | 
					            self.sad = addr & sad_mask;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        pub fn setDmadad(self: *Self, addr: u32) void {
 | 
				
			||||||
 | 
					            self.dad = addr & dad_mask;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        pub fn setDmacntL(self: *Self, halfword: u16) void {
 | 
				
			||||||
 | 
					            self.word_count = @truncate(@TypeOf(self.word_count), halfword);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        pub fn setDmacntH(self: *Self, halfword: u16) void {
 | 
				
			||||||
 | 
					            const new = DmaControl{ .raw = halfword };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (!self.cnt.enabled.read() and new.enabled.read()) {
 | 
				
			||||||
 | 
					                // Reload Internals on Rising Edge.
 | 
				
			||||||
 | 
					                self.sad_latch = self.sad;
 | 
				
			||||||
 | 
					                self.dad_latch = self.dad;
 | 
				
			||||||
 | 
					                self._word_count = if (self.word_count == 0) std.math.maxInt(@TypeOf(self._word_count)) else self.word_count;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                // Only a Start Timing of 00 has a DMA Transfer immediately begin
 | 
				
			||||||
 | 
					                self.in_progress = new.start_timing.read() == 0b00;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            self.cnt.raw = halfword;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        pub fn setDmacnt(self: *Self, word: u32) void {
 | 
				
			||||||
 | 
					            self.setDmacntL(@truncate(u16, word));
 | 
				
			||||||
 | 
					            self.setDmacntH(@truncate(u16, word >> 16));
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        pub fn step(self: *Self, cpu: *Arm7tdmi) void {
 | 
				
			||||||
 | 
					            const is_fifo = (id == 1 or id == 2) and self.cnt.start_timing.read() == 0b11;
 | 
				
			||||||
 | 
					            const sad_adj = @intToEnum(Adjustment, self.cnt.sad_adj.read());
 | 
				
			||||||
 | 
					            const dad_adj = if (is_fifo) .Fixed else @intToEnum(Adjustment, self.cnt.dad_adj.read());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            const transfer_type = is_fifo or self.cnt.transfer_type.read();
 | 
				
			||||||
 | 
					            const offset: u32 = if (transfer_type) @sizeOf(u32) else @sizeOf(u16);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            const mask = if (transfer_type) ~@as(u32, 3) else ~@as(u32, 1);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (transfer_type) {
 | 
				
			||||||
 | 
					                cpu.bus.write(u32, self.dad_latch & mask, cpu.bus.read(u32, self.sad_latch & mask));
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					                cpu.bus.write(u16, self.dad_latch & mask, cpu.bus.read(u16, self.sad_latch & mask));
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            switch (sad_adj) {
 | 
				
			||||||
 | 
					                .Increment => self.sad_latch +%= offset,
 | 
				
			||||||
 | 
					                .Decrement => self.sad_latch -%= offset,
 | 
				
			||||||
 | 
					                // FIXME: Is just ignoring this ok?
 | 
				
			||||||
 | 
					                .IncrementReload => log.err("{} is a prohibited adjustment on SAD", .{sad_adj}),
 | 
				
			||||||
 | 
					                .Fixed => {},
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            switch (dad_adj) {
 | 
				
			||||||
 | 
					                .Increment, .IncrementReload => self.dad_latch +%= offset,
 | 
				
			||||||
 | 
					                .Decrement => self.dad_latch -%= offset,
 | 
				
			||||||
 | 
					                .Fixed => {},
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            self._word_count -= 1;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (self._word_count == 0) {
 | 
				
			||||||
 | 
					                if (self.cnt.irq.read()) {
 | 
				
			||||||
 | 
					                    switch (id) {
 | 
				
			||||||
 | 
					                        0 => cpu.bus.io.irq.dma0.set(),
 | 
				
			||||||
 | 
					                        1 => cpu.bus.io.irq.dma1.set(),
 | 
				
			||||||
 | 
					                        2 => cpu.bus.io.irq.dma2.set(),
 | 
				
			||||||
 | 
					                        3 => cpu.bus.io.irq.dma3.set(),
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    cpu.handleInterrupt();
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                // If we're not repeating, Fire the IRQs and disable the DMA
 | 
				
			||||||
 | 
					                if (!self.cnt.repeat.read()) self.cnt.enabled.unset();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                // We want to disable our internal enabled flag regardless of repeat
 | 
				
			||||||
 | 
					                // because we only want to step A DMA that repeats during it's specific
 | 
				
			||||||
 | 
					                // timing window
 | 
				
			||||||
 | 
					                self.in_progress = false;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        fn poll(self: *Self, comptime kind: DmaKind) void {
 | 
				
			||||||
 | 
					            if (self.in_progress) return; // If there's an ongoing DMA Transfer, exit early
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // No ongoing DMA Transfer, We want to check if we should repeat an existing one
 | 
				
			||||||
 | 
					            // Determined by the repeat bit and whether the DMA is in the right start_timing
 | 
				
			||||||
 | 
					            switch (kind) {
 | 
				
			||||||
 | 
					                .VBlank => self.in_progress = self.cnt.enabled.read() and self.cnt.start_timing.read() == 0b01,
 | 
				
			||||||
 | 
					                .HBlank => self.in_progress = self.cnt.enabled.read() and self.cnt.start_timing.read() == 0b10,
 | 
				
			||||||
 | 
					                .Immediate, .Special => {},
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // If we determined that the repeat bit is set (and now the Hblank / Vblank DMA is now in progress)
 | 
				
			||||||
 | 
					            // Reload internal word count latch
 | 
				
			||||||
 | 
					            // Reload internal DAD latch if we are in IncrementRelaod
 | 
				
			||||||
 | 
					            if (self.in_progress) {
 | 
				
			||||||
 | 
					                self._word_count = if (self.word_count == 0) std.math.maxInt(@TypeOf(self._word_count)) else self.word_count;
 | 
				
			||||||
 | 
					                if (@intToEnum(Adjustment, self.cnt.dad_adj.read()) == .IncrementReload) self.dad_latch = self.dad;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        pub fn requestAudio(self: *Self, _: u32) void {
 | 
				
			||||||
 | 
					            comptime std.debug.assert(id == 1 or id == 2);
 | 
				
			||||||
 | 
					            if (self.in_progress) return; // APU must wait their turn
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // DMA May not be configured for handling DMAs
 | 
				
			||||||
 | 
					            if (self.cnt.start_timing.read() != 0b11) return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // We Assume the Repeat Bit is Set
 | 
				
			||||||
 | 
					            // We Assume that DAD is set to 0x0400_00A0 or 0x0400_00A4 (fifo_addr)
 | 
				
			||||||
 | 
					            // We Assume DMACNT_L is set to 4
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // FIXME: Safe to just assume whatever DAD is set to is the FIFO Address?
 | 
				
			||||||
 | 
					            // self.dad_latch = fifo_addr;
 | 
				
			||||||
 | 
					            self.cnt.repeat.set();
 | 
				
			||||||
 | 
					            self._word_count = 4;
 | 
				
			||||||
 | 
					            self.in_progress = true;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn onBlanking(bus: *Bus, comptime kind: DmaKind) void {
 | 
				
			||||||
 | 
					    bus.dma[0].poll(kind);
 | 
				
			||||||
 | 
					    bus.dma[1].poll(kind);
 | 
				
			||||||
 | 
					    bus.dma[2].poll(kind);
 | 
				
			||||||
 | 
					    bus.dma[3].poll(kind);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const Adjustment = enum(u2) {
 | 
				
			||||||
 | 
					    Increment = 0,
 | 
				
			||||||
 | 
					    Decrement = 1,
 | 
				
			||||||
 | 
					    Fixed = 2,
 | 
				
			||||||
 | 
					    IncrementReload = 3,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const DmaKind = enum(u2) {
 | 
				
			||||||
 | 
					    Immediate = 0,
 | 
				
			||||||
 | 
					    HBlank,
 | 
				
			||||||
 | 
					    VBlank,
 | 
				
			||||||
 | 
					    Special,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
							
								
								
									
										465
									
								
								src/core/bus/gpio.zig
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										465
									
								
								src/core/bus/gpio.zig
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,465 @@
 | 
				
			|||||||
 | 
					const std = @import("std");
 | 
				
			||||||
 | 
					const Bit = @import("bitfield").Bit;
 | 
				
			||||||
 | 
					const Bitfield = @import("bitfield").Bitfield;
 | 
				
			||||||
 | 
					const DateTime = @import("datetime").datetime.Datetime;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const Arm7tdmi = @import("../cpu.zig").Arm7tdmi;
 | 
				
			||||||
 | 
					const Allocator = std.mem.Allocator;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// GPIO Register Implementation
 | 
				
			||||||
 | 
					pub const Gpio = struct {
 | 
				
			||||||
 | 
					    const Self = @This();
 | 
				
			||||||
 | 
					    const log = std.log.scoped(.Gpio);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    data: u4,
 | 
				
			||||||
 | 
					    direction: u4,
 | 
				
			||||||
 | 
					    cnt: u1,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    device: Device,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const Register = enum { Data, Direction, Control };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub const Device = struct {
 | 
				
			||||||
 | 
					        ptr: ?*anyopaque,
 | 
				
			||||||
 | 
					        kind: Kind, // TODO: Make comptime known?
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        pub const Kind = enum { Rtc, None };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        fn step(self: *Device, value: u4) u4 {
 | 
				
			||||||
 | 
					            return switch (self.kind) {
 | 
				
			||||||
 | 
					                .Rtc => blk: {
 | 
				
			||||||
 | 
					                    const clock = @ptrCast(*Clock, @alignCast(@alignOf(*Clock), self.ptr.?));
 | 
				
			||||||
 | 
					                    break :blk clock.step(Clock.Data{ .raw = value });
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					                .None => value,
 | 
				
			||||||
 | 
					            };
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        fn init(kind: Kind, ptr: ?*anyopaque) Device {
 | 
				
			||||||
 | 
					            return .{ .kind = kind, .ptr = ptr };
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn write(self: *Self, comptime reg: Register, value: if (reg == .Control) u1 else u4) void {
 | 
				
			||||||
 | 
					        switch (reg) {
 | 
				
			||||||
 | 
					            .Data => {
 | 
				
			||||||
 | 
					                const masked_value = value & self.direction;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                // The value which is actually stored in the GPIO register
 | 
				
			||||||
 | 
					                // might be modified by the device implementing the GPIO interface e.g. RTC reads
 | 
				
			||||||
 | 
					                self.data = self.device.step(masked_value);
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            .Direction => self.direction = value,
 | 
				
			||||||
 | 
					            .Control => self.cnt = value,
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn read(self: *const Self, comptime reg: Register) if (reg == .Control) u1 else u4 {
 | 
				
			||||||
 | 
					        if (self.cnt == 0) return 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return switch (reg) {
 | 
				
			||||||
 | 
					            .Data => self.data & ~self.direction,
 | 
				
			||||||
 | 
					            .Direction => self.direction,
 | 
				
			||||||
 | 
					            .Control => self.cnt,
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn init(allocator: Allocator, cpu: *Arm7tdmi, kind: Device.Kind) !*Self {
 | 
				
			||||||
 | 
					        log.info("Device: {}", .{kind});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const self = try allocator.create(Self);
 | 
				
			||||||
 | 
					        errdefer allocator.destroy(self);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.* = .{
 | 
				
			||||||
 | 
					            .data = 0b0000,
 | 
				
			||||||
 | 
					            .direction = 0b1111, // TODO: What is GPIO DIrection set to by default?
 | 
				
			||||||
 | 
					            .cnt = 0b0,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            .device = switch (kind) {
 | 
				
			||||||
 | 
					                .Rtc => blk: {
 | 
				
			||||||
 | 
					                    const clock = try allocator.create(Clock);
 | 
				
			||||||
 | 
					                    clock.init(cpu, self);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    break :blk Device{ .kind = kind, .ptr = clock };
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					                .None => Device{ .kind = kind, .ptr = null },
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return self;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn deinit(self: *Self, allocator: Allocator) void {
 | 
				
			||||||
 | 
					        switch (self.device.kind) {
 | 
				
			||||||
 | 
					            .Rtc => allocator.destroy(@ptrCast(*Clock, @alignCast(@alignOf(*Clock), self.device.ptr.?))),
 | 
				
			||||||
 | 
					            .None => {},
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.* = undefined;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// GBA Real Time Clock
 | 
				
			||||||
 | 
					pub const Clock = struct {
 | 
				
			||||||
 | 
					    const Self = @This();
 | 
				
			||||||
 | 
					    const log = std.log.scoped(.Rtc);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    writer: Writer,
 | 
				
			||||||
 | 
					    reader: Reader,
 | 
				
			||||||
 | 
					    state: State,
 | 
				
			||||||
 | 
					    cnt: Control,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    year: u8,
 | 
				
			||||||
 | 
					    month: u5,
 | 
				
			||||||
 | 
					    day: u6,
 | 
				
			||||||
 | 
					    weekday: u3,
 | 
				
			||||||
 | 
					    hour: u6,
 | 
				
			||||||
 | 
					    minute: u7,
 | 
				
			||||||
 | 
					    second: u7,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    cpu: *Arm7tdmi,
 | 
				
			||||||
 | 
					    gpio: *const Gpio,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const Register = enum {
 | 
				
			||||||
 | 
					        Control,
 | 
				
			||||||
 | 
					        DateTime,
 | 
				
			||||||
 | 
					        Time,
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const State = union(enum) {
 | 
				
			||||||
 | 
					        Idle,
 | 
				
			||||||
 | 
					        Command,
 | 
				
			||||||
 | 
					        Write: Register,
 | 
				
			||||||
 | 
					        Read: Register,
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const Reader = struct {
 | 
				
			||||||
 | 
					        i: u4,
 | 
				
			||||||
 | 
					        count: u8,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /// Reads a bit from RTC registers. Which bit it reads is dependent on
 | 
				
			||||||
 | 
					        ///
 | 
				
			||||||
 | 
					        /// 1. The RTC State Machine, whitch tells us which register we're accessing
 | 
				
			||||||
 | 
					        /// 2. A `count`, which keeps track of which byte is currently being read
 | 
				
			||||||
 | 
					        /// 3. An index, which keeps track of which bit of the byte determined by `count` is being read
 | 
				
			||||||
 | 
					        fn read(self: *Reader, clock: *const Clock, register: Register) u1 {
 | 
				
			||||||
 | 
					            const idx = @intCast(u3, self.i);
 | 
				
			||||||
 | 
					            defer self.i += 1;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // FIXME: What do I do about the unused bits?
 | 
				
			||||||
 | 
					            return switch (register) {
 | 
				
			||||||
 | 
					                .Control => @truncate(u1, switch (self.count) {
 | 
				
			||||||
 | 
					                    0 => clock.cnt.raw >> idx,
 | 
				
			||||||
 | 
					                    else => std.debug.panic("Tried to read from byte #{} of {} (hint: there's only 1 byte)", .{ self.count, register }),
 | 
				
			||||||
 | 
					                }),
 | 
				
			||||||
 | 
					                .DateTime => @truncate(u1, switch (self.count) {
 | 
				
			||||||
 | 
					                    // Date
 | 
				
			||||||
 | 
					                    0 => clock.year >> idx,
 | 
				
			||||||
 | 
					                    1 => @as(u8, clock.month) >> idx,
 | 
				
			||||||
 | 
					                    2 => @as(u8, clock.day) >> idx,
 | 
				
			||||||
 | 
					                    3 => @as(u8, clock.weekday) >> idx,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    // Time
 | 
				
			||||||
 | 
					                    4 => @as(u8, clock.hour) >> idx,
 | 
				
			||||||
 | 
					                    5 => @as(u8, clock.minute) >> idx,
 | 
				
			||||||
 | 
					                    6 => @as(u8, clock.second) >> idx,
 | 
				
			||||||
 | 
					                    else => std.debug.panic("Tried to read from byte #{} of {} (hint: there's only 7 bytes)", .{ self.count, register }),
 | 
				
			||||||
 | 
					                }),
 | 
				
			||||||
 | 
					                .Time => @truncate(u1, switch (self.count) {
 | 
				
			||||||
 | 
					                    0 => @as(u8, clock.hour) >> idx,
 | 
				
			||||||
 | 
					                    1 => @as(u8, clock.minute) >> idx,
 | 
				
			||||||
 | 
					                    2 => @as(u8, clock.second) >> idx,
 | 
				
			||||||
 | 
					                    else => std.debug.panic("Tried to read from byte #{} of {} (hint: there's only 3 bytes)", .{ self.count, register }),
 | 
				
			||||||
 | 
					                }),
 | 
				
			||||||
 | 
					            };
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /// Is true when a Reader has read a u8's worth of bits
 | 
				
			||||||
 | 
					        fn finished(self: *const Reader) bool {
 | 
				
			||||||
 | 
					            return self.i >= 8;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /// Resets the index used to shift bits out of RTC registers
 | 
				
			||||||
 | 
					        /// and `count`, which is used to keep track of which byte we're reading
 | 
				
			||||||
 | 
					        /// is incremeneted
 | 
				
			||||||
 | 
					        fn lap(self: *Reader) void {
 | 
				
			||||||
 | 
					            self.i = 0;
 | 
				
			||||||
 | 
					            self.count += 1;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /// Resets the state of a `Reader` in preparation for a future
 | 
				
			||||||
 | 
					        /// read command
 | 
				
			||||||
 | 
					        fn reset(self: *Reader) void {
 | 
				
			||||||
 | 
					            self.i = 0;
 | 
				
			||||||
 | 
					            self.count = 0;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const Writer = struct {
 | 
				
			||||||
 | 
					        buf: u8,
 | 
				
			||||||
 | 
					        i: u4,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /// The Number of bytes written since last reset
 | 
				
			||||||
 | 
					        count: u8,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /// Append a bit to the internal bit buffer (aka an integer)
 | 
				
			||||||
 | 
					        fn push(self: *Writer, value: u1) void {
 | 
				
			||||||
 | 
					            const idx = @intCast(u3, self.i);
 | 
				
			||||||
 | 
					            self.buf = (self.buf & ~(@as(u8, 1) << idx)) | @as(u8, value) << idx;
 | 
				
			||||||
 | 
					            self.i += 1;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /// Takes the contents of the internal buffer and writes it to an RTC register
 | 
				
			||||||
 | 
					        /// Where it writes to is dependent on:
 | 
				
			||||||
 | 
					        ///
 | 
				
			||||||
 | 
					        /// 1. The RTC State Machine, whitch tells us which register we're accessing
 | 
				
			||||||
 | 
					        /// 2. A `count`, which keeps track of which byte is currently being read
 | 
				
			||||||
 | 
					        fn write(self: *const Writer, clock: *Clock, register: Register) void {
 | 
				
			||||||
 | 
					            // FIXME: What do do about unused bits?
 | 
				
			||||||
 | 
					            switch (register) {
 | 
				
			||||||
 | 
					                .Control => switch (self.count) {
 | 
				
			||||||
 | 
					                    0 => clock.cnt.raw = (clock.cnt.raw & 0x80) | (self.buf & 0x7F), // Bit 7 read-only
 | 
				
			||||||
 | 
					                    else => std.debug.panic("Tried to write to byte #{} of {} (hint: there's only 1 byte)", .{ self.count, register }),
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					                .DateTime, .Time => log.debug("Ignoring {} write", .{register}),
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /// Is true when 8 bits have been shifted into the internal buffer
 | 
				
			||||||
 | 
					        fn finished(self: *const Writer) bool {
 | 
				
			||||||
 | 
					            return self.i >= 8;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /// Resets the internal buffer
 | 
				
			||||||
 | 
					        /// resets the index used to shift bits into the internal buffer
 | 
				
			||||||
 | 
					        /// increments `count` (which keeps track of byte offsets) by one
 | 
				
			||||||
 | 
					        fn lap(self: *Writer) void {
 | 
				
			||||||
 | 
					            self.buf = 0;
 | 
				
			||||||
 | 
					            self.i = 0;
 | 
				
			||||||
 | 
					            self.count += 1;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /// Resets `Writer` to a clean state in preparation for a future write command
 | 
				
			||||||
 | 
					        fn reset(self: *Writer) void {
 | 
				
			||||||
 | 
					            self.buf = 0;
 | 
				
			||||||
 | 
					            self.i = 0;
 | 
				
			||||||
 | 
					            self.count = 0;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const Data = extern union {
 | 
				
			||||||
 | 
					        sck: Bit(u8, 0),
 | 
				
			||||||
 | 
					        sio: Bit(u8, 1),
 | 
				
			||||||
 | 
					        cs: Bit(u8, 2),
 | 
				
			||||||
 | 
					        raw: u8,
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const Control = extern union {
 | 
				
			||||||
 | 
					        /// Unknown, value should be preserved though
 | 
				
			||||||
 | 
					        unk: Bit(u8, 1),
 | 
				
			||||||
 | 
					        /// Per-minute IRQ
 | 
				
			||||||
 | 
					        /// If set, fire a Gamepak IRQ every 30s,
 | 
				
			||||||
 | 
					        irq: Bit(u8, 3),
 | 
				
			||||||
 | 
					        /// 12/24 Hour Bit
 | 
				
			||||||
 | 
					        /// If set, 12h mode
 | 
				
			||||||
 | 
					        /// If cleared, 24h mode
 | 
				
			||||||
 | 
					        mode: Bit(u8, 6),
 | 
				
			||||||
 | 
					        /// Read-Only, bit cleared on read
 | 
				
			||||||
 | 
					        /// If is set, means that there has been a failure / time has been lost
 | 
				
			||||||
 | 
					        off: Bit(u8, 7),
 | 
				
			||||||
 | 
					        raw: u8,
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fn init(ptr: *Self, cpu: *Arm7tdmi, gpio: *const Gpio) void {
 | 
				
			||||||
 | 
					        ptr.* = .{
 | 
				
			||||||
 | 
					            .writer = .{ .buf = 0, .i = 0, .count = 0 },
 | 
				
			||||||
 | 
					            .reader = .{ .i = 0, .count = 0 },
 | 
				
			||||||
 | 
					            .state = .Idle,
 | 
				
			||||||
 | 
					            .cnt = .{ .raw = 0 },
 | 
				
			||||||
 | 
					            .year = 0x01,
 | 
				
			||||||
 | 
					            .month = 0x6,
 | 
				
			||||||
 | 
					            .day = 0x13,
 | 
				
			||||||
 | 
					            .weekday = 0x3,
 | 
				
			||||||
 | 
					            .hour = 0x23,
 | 
				
			||||||
 | 
					            .minute = 0x59,
 | 
				
			||||||
 | 
					            .second = 0x59,
 | 
				
			||||||
 | 
					            .cpu = cpu,
 | 
				
			||||||
 | 
					            .gpio = gpio, // Can't use Arm7tdmi ptr b/c not initialized yet
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        cpu.sched.push(.RealTimeClock, 1 << 24); // Every Second
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn onClockUpdate(self: *Self, late: u64) void {
 | 
				
			||||||
 | 
					        self.cpu.sched.push(.RealTimeClock, (1 << 24) -| late); // Reschedule
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const now = DateTime.now();
 | 
				
			||||||
 | 
					        self.year = bcd(u8, @intCast(u8, now.date.year - 2000));
 | 
				
			||||||
 | 
					        self.month = bcd(u5, now.date.month);
 | 
				
			||||||
 | 
					        self.day = bcd(u6, now.date.day);
 | 
				
			||||||
 | 
					        self.weekday = bcd(u3, (now.date.weekday() + 1) % 7); // API is Monday = 0, Sunday = 6. We want Sunday = 0, Saturday = 6
 | 
				
			||||||
 | 
					        self.hour = bcd(u6, now.time.hour);
 | 
				
			||||||
 | 
					        self.minute = bcd(u7, now.time.minute);
 | 
				
			||||||
 | 
					        self.second = bcd(u7, now.time.second);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fn step(self: *Self, value: Data) u4 {
 | 
				
			||||||
 | 
					        const cache: Data = .{ .raw = self.gpio.data };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return switch (self.state) {
 | 
				
			||||||
 | 
					            .Idle => blk: {
 | 
				
			||||||
 | 
					                // FIXME: Maybe check incoming value to see if SCK is also high?
 | 
				
			||||||
 | 
					                if (cache.sck.read()) {
 | 
				
			||||||
 | 
					                    if (!cache.cs.read() and value.cs.read()) {
 | 
				
			||||||
 | 
					                        log.debug("Entering Command Mode", .{});
 | 
				
			||||||
 | 
					                        self.state = .Command;
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                break :blk @truncate(u4, value.raw);
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            .Command => blk: {
 | 
				
			||||||
 | 
					                if (!value.cs.read()) log.err("Expected CS to be set during {}, however CS was cleared", .{self.state});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                // If SCK rises, sample SIO
 | 
				
			||||||
 | 
					                if (!cache.sck.read() and value.sck.read()) {
 | 
				
			||||||
 | 
					                    self.writer.push(@boolToInt(value.sio.read()));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    if (self.writer.finished()) {
 | 
				
			||||||
 | 
					                        self.state = self.processCommand(self.writer.buf);
 | 
				
			||||||
 | 
					                        self.writer.reset();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                        log.debug("Switching to {}", .{self.state});
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                break :blk @truncate(u4, value.raw);
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            .Write => |register| blk: {
 | 
				
			||||||
 | 
					                if (!value.cs.read()) log.err("Expected CS to be set during {}, however CS was cleared", .{self.state});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                // If SCK rises, sample SIO
 | 
				
			||||||
 | 
					                if (!cache.sck.read() and value.sck.read()) {
 | 
				
			||||||
 | 
					                    self.writer.push(@boolToInt(value.sio.read()));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    const register_width: u32 = switch (register) {
 | 
				
			||||||
 | 
					                        .Control => 1,
 | 
				
			||||||
 | 
					                        .DateTime => 7,
 | 
				
			||||||
 | 
					                        .Time => 3,
 | 
				
			||||||
 | 
					                    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    if (self.writer.finished()) {
 | 
				
			||||||
 | 
					                        self.writer.write(self, register); // write inner buffer to RTC register
 | 
				
			||||||
 | 
					                        self.writer.lap();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                        if (self.writer.count == register_width) {
 | 
				
			||||||
 | 
					                            self.writer.reset();
 | 
				
			||||||
 | 
					                            self.state = .Idle;
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                break :blk @truncate(u4, value.raw);
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            .Read => |register| blk: {
 | 
				
			||||||
 | 
					                if (!value.cs.read()) log.err("Expected CS to be set during {}, however CS was cleared", .{self.state});
 | 
				
			||||||
 | 
					                var ret = value;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                // if SCK rises, sample SIO
 | 
				
			||||||
 | 
					                if (!cache.sck.read() and value.sck.read()) {
 | 
				
			||||||
 | 
					                    ret.sio.write(self.reader.read(self, register) == 0b1);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    const register_width: u32 = switch (register) {
 | 
				
			||||||
 | 
					                        .Control => 1,
 | 
				
			||||||
 | 
					                        .DateTime => 7,
 | 
				
			||||||
 | 
					                        .Time => 3,
 | 
				
			||||||
 | 
					                    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    if (self.reader.finished()) {
 | 
				
			||||||
 | 
					                        self.reader.lap();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                        if (self.reader.count == register_width) {
 | 
				
			||||||
 | 
					                            self.reader.reset();
 | 
				
			||||||
 | 
					                            self.state = .Idle;
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                break :blk @truncate(u4, ret.raw);
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fn reset(self: *Self) void {
 | 
				
			||||||
 | 
					        // mGBA and NBA only zero the control register. We will do the same
 | 
				
			||||||
 | 
					        log.debug("Reset (control register was zeroed)", .{});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.cnt.raw = 0;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fn irq(self: *Self) void {
 | 
				
			||||||
 | 
					        // TODO: Confirm that this is the right behaviour
 | 
				
			||||||
 | 
					        log.debug("Force GamePak IRQ", .{});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.cpu.bus.io.irq.game_pak.set();
 | 
				
			||||||
 | 
					        self.cpu.handleInterrupt();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fn processCommand(self: *Self, raw_command: u8) State {
 | 
				
			||||||
 | 
					        const command = blk: {
 | 
				
			||||||
 | 
					            // If High Nybble is 0x6, no need to switch the endianness
 | 
				
			||||||
 | 
					            if (raw_command >> 4 & 0xF == 0x6) break :blk raw_command;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // Turns out reversing the order of bits isn't trivial at all
 | 
				
			||||||
 | 
					            // https://stackoverflow.com/questions/2602823/in-c-c-whats-the-simplest-way-to-reverse-the-order-of-bits-in-a-byte
 | 
				
			||||||
 | 
					            var ret = raw_command;
 | 
				
			||||||
 | 
					            ret = (ret & 0xF0) >> 4 | (ret & 0x0F) << 4;
 | 
				
			||||||
 | 
					            ret = (ret & 0xCC) >> 2 | (ret & 0x33) << 2;
 | 
				
			||||||
 | 
					            ret = (ret & 0xAA) >> 1 | (ret & 0x55) << 1;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            break :blk ret;
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					        log.debug("Handling Command 0x{X:0>2} [0b{b:0>8}]", .{ command, command });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const is_write = command & 1 == 0;
 | 
				
			||||||
 | 
					        const rtc_register = @truncate(u3, command >> 1 & 0x7);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (is_write) {
 | 
				
			||||||
 | 
					            return switch (rtc_register) {
 | 
				
			||||||
 | 
					                0 => blk: {
 | 
				
			||||||
 | 
					                    self.reset();
 | 
				
			||||||
 | 
					                    break :blk .Idle;
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					                1 => .{ .Write = .Control },
 | 
				
			||||||
 | 
					                2 => .{ .Write = .DateTime },
 | 
				
			||||||
 | 
					                3 => .{ .Write = .Time },
 | 
				
			||||||
 | 
					                6 => blk: {
 | 
				
			||||||
 | 
					                    self.irq();
 | 
				
			||||||
 | 
					                    break :blk .Idle;
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					                4, 5, 7 => .Idle,
 | 
				
			||||||
 | 
					            };
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            return switch (rtc_register) {
 | 
				
			||||||
 | 
					                1 => .{ .Read = .Control },
 | 
				
			||||||
 | 
					                2 => .{ .Read = .DateTime },
 | 
				
			||||||
 | 
					                3 => .{ .Read = .Time },
 | 
				
			||||||
 | 
					                0, 4, 5, 6, 7 => .Idle, // Do Nothing
 | 
				
			||||||
 | 
					            };
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fn bcd(comptime T: type, value: u8) T {
 | 
				
			||||||
 | 
					    var input = value;
 | 
				
			||||||
 | 
					    var ret: u8 = 0;
 | 
				
			||||||
 | 
					    var shift: u3 = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    while (input > 0) {
 | 
				
			||||||
 | 
					        ret |= (input % 10) << (shift << 2);
 | 
				
			||||||
 | 
					        shift += 1;
 | 
				
			||||||
 | 
					        input /= 10;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return @truncate(T, ret);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										686
									
								
								src/core/bus/io.zig
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										686
									
								
								src/core/bus/io.zig
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,686 @@
 | 
				
			|||||||
 | 
					const std = @import("std");
 | 
				
			||||||
 | 
					const builtin = @import("builtin");
 | 
				
			||||||
 | 
					const timer = @import("timer.zig");
 | 
				
			||||||
 | 
					const dma = @import("dma.zig");
 | 
				
			||||||
 | 
					const apu = @import("../apu.zig");
 | 
				
			||||||
 | 
					const util = @import("../../util.zig");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const Bit = @import("bitfield").Bit;
 | 
				
			||||||
 | 
					const Bitfield = @import("bitfield").Bitfield;
 | 
				
			||||||
 | 
					const Bus = @import("../Bus.zig");
 | 
				
			||||||
 | 
					const DmaController = @import("dma.zig").DmaController;
 | 
				
			||||||
 | 
					const Scheduler = @import("../scheduler.zig").Scheduler;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const setHi = util.setLo;
 | 
				
			||||||
 | 
					const setLo = util.setHi;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const log = std.log.scoped(.@"I/O");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub const Io = struct {
 | 
				
			||||||
 | 
					    const Self = @This();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /// Read / Write
 | 
				
			||||||
 | 
					    ime: bool,
 | 
				
			||||||
 | 
					    ie: InterruptEnable,
 | 
				
			||||||
 | 
					    irq: InterruptRequest,
 | 
				
			||||||
 | 
					    postflg: PostFlag,
 | 
				
			||||||
 | 
					    haltcnt: HaltControl,
 | 
				
			||||||
 | 
					    keyinput: KeyInput,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn init() Self {
 | 
				
			||||||
 | 
					        return .{
 | 
				
			||||||
 | 
					            .ime = false,
 | 
				
			||||||
 | 
					            .ie = .{ .raw = 0x0000 },
 | 
				
			||||||
 | 
					            .irq = .{ .raw = 0x0000 },
 | 
				
			||||||
 | 
					            .keyinput = .{ .raw = 0x03FF },
 | 
				
			||||||
 | 
					            .postflg = .FirstBoot,
 | 
				
			||||||
 | 
					            .haltcnt = .Execute,
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fn setIrqs(self: *Io, word: u32) void {
 | 
				
			||||||
 | 
					        self.ie.raw = @truncate(u16, word);
 | 
				
			||||||
 | 
					        self.irq.raw &= ~@truncate(u16, word >> 16);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn read(bus: *const Bus, comptime T: type, address: u32) ?T {
 | 
				
			||||||
 | 
					    return switch (T) {
 | 
				
			||||||
 | 
					        u32 => switch (address) {
 | 
				
			||||||
 | 
					            // Display
 | 
				
			||||||
 | 
					            0x0400_0000 => bus.ppu.dispcnt.raw,
 | 
				
			||||||
 | 
					            0x0400_0004 => @as(T, bus.ppu.vcount.raw) << 16 | bus.ppu.dispstat.raw,
 | 
				
			||||||
 | 
					            0x0400_0006 => @as(T, bus.ppu.bg[0].cnt.raw) << 16 | bus.ppu.vcount.raw,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // DMA Transfers
 | 
				
			||||||
 | 
					            0x0400_00B0...0x0400_00DC => dma.read(T, &bus.dma, address),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // Timers
 | 
				
			||||||
 | 
					            0x0400_0100...0x0400_010C => timer.read(T, &bus.tim, address),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // Serial Communication 1
 | 
				
			||||||
 | 
					            0x0400_0128 => util.io.read.todo(log, "Read {} from SIOCNT and SIOMLT_SEND", .{T}),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // Keypad Input
 | 
				
			||||||
 | 
					            0x0400_0130 => util.io.read.todo(log, "Read {} from KEYINPUT", .{T}),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // Serial Communication 2
 | 
				
			||||||
 | 
					            0x0400_0150 => util.io.read.todo(log, "Read {} from JOY_RECV", .{T}),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // Interrupts
 | 
				
			||||||
 | 
					            0x0400_0200 => @as(T, bus.io.irq.raw) << 16 | bus.io.ie.raw,
 | 
				
			||||||
 | 
					            0x0400_0208 => @boolToInt(bus.io.ime),
 | 
				
			||||||
 | 
					            else => util.io.read.undef(T, log, "Tried to perform a {} read to 0x{X:0>8}", .{ T, address }),
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        u16 => switch (address) {
 | 
				
			||||||
 | 
					            // Display
 | 
				
			||||||
 | 
					            0x0400_0000 => bus.ppu.dispcnt.raw,
 | 
				
			||||||
 | 
					            0x0400_0004 => bus.ppu.dispstat.raw,
 | 
				
			||||||
 | 
					            0x0400_0006 => bus.ppu.vcount.raw,
 | 
				
			||||||
 | 
					            0x0400_0008 => bus.ppu.bg[0].cnt.raw,
 | 
				
			||||||
 | 
					            0x0400_000A => bus.ppu.bg[1].cnt.raw,
 | 
				
			||||||
 | 
					            0x0400_000C => bus.ppu.bg[2].cnt.raw,
 | 
				
			||||||
 | 
					            0x0400_000E => bus.ppu.bg[3].cnt.raw,
 | 
				
			||||||
 | 
					            0x0400_004C => util.io.read.todo(log, "Read {} from MOSAIC", .{T}),
 | 
				
			||||||
 | 
					            0x0400_0050 => bus.ppu.bldcnt.raw,
 | 
				
			||||||
 | 
					            0x0400_0052 => bus.ppu.bldalpha.raw,
 | 
				
			||||||
 | 
					            0x0400_0054 => bus.ppu.bldy.raw,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // Sound
 | 
				
			||||||
 | 
					            0x0400_0060...0x0400_009E => apu.read(T, &bus.apu, address),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // DMA Transfers
 | 
				
			||||||
 | 
					            0x0400_00B0...0x0400_00DE => dma.read(T, &bus.dma, address),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // Timers
 | 
				
			||||||
 | 
					            0x0400_0100...0x0400_010E => timer.read(T, &bus.tim, address),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // Serial Communication 1
 | 
				
			||||||
 | 
					            0x0400_0128 => util.io.read.todo(log, "Read {} from SIOCNT", .{T}),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // Keypad Input
 | 
				
			||||||
 | 
					            0x0400_0130 => bus.io.keyinput.raw,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // Serial Communication 2
 | 
				
			||||||
 | 
					            0x0400_0134 => util.io.read.todo(log, "Read {} from RCNT", .{T}),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // Interrupts
 | 
				
			||||||
 | 
					            0x0400_0200 => bus.io.ie.raw,
 | 
				
			||||||
 | 
					            0x0400_0202 => bus.io.irq.raw,
 | 
				
			||||||
 | 
					            0x0400_0204 => util.io.read.todo(log, "Read {} from WAITCNT", .{T}),
 | 
				
			||||||
 | 
					            0x0400_0208 => @boolToInt(bus.io.ime),
 | 
				
			||||||
 | 
					            else => util.io.read.undef(T, log, "Tried to perform a {} read to 0x{X:0>8}", .{ T, address }),
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        u8 => return switch (address) {
 | 
				
			||||||
 | 
					            // Display
 | 
				
			||||||
 | 
					            0x0400_0000 => @truncate(T, bus.ppu.dispcnt.raw),
 | 
				
			||||||
 | 
					            0x0400_0004 => @truncate(T, bus.ppu.dispstat.raw),
 | 
				
			||||||
 | 
					            0x0400_0005 => @truncate(T, bus.ppu.dispcnt.raw >> 8),
 | 
				
			||||||
 | 
					            0x0400_0006 => @truncate(T, bus.ppu.vcount.raw),
 | 
				
			||||||
 | 
					            0x0400_0008 => @truncate(T, bus.ppu.bg[0].cnt.raw),
 | 
				
			||||||
 | 
					            0x0400_0009 => @truncate(T, bus.ppu.bg[0].cnt.raw >> 8),
 | 
				
			||||||
 | 
					            0x0400_000A => @truncate(T, bus.ppu.bg[1].cnt.raw),
 | 
				
			||||||
 | 
					            0x0400_000B => @truncate(T, bus.ppu.bg[1].cnt.raw >> 8),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // Sound
 | 
				
			||||||
 | 
					            0x0400_0060...0x0400_00A7 => apu.read(T, &bus.apu, address),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // Serial Communication 1
 | 
				
			||||||
 | 
					            0x0400_0128 => util.io.read.todo(log, "Read {} from SIOCNT_L", .{T}),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // Keypad Input
 | 
				
			||||||
 | 
					            0x0400_0130 => util.io.read.todo(log, "read {} from KEYINPUT_L", .{T}),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // Serial Communication 2
 | 
				
			||||||
 | 
					            0x0400_0135 => util.io.read.todo(log, "Read {} from RCNT_H", .{T}),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // Interrupts
 | 
				
			||||||
 | 
					            0x0400_0200 => @truncate(T, bus.io.ie.raw),
 | 
				
			||||||
 | 
					            0x0400_0300 => @enumToInt(bus.io.postflg),
 | 
				
			||||||
 | 
					            else => util.io.read.undef(T, log, "Tried to perform a {} read to 0x{X:0>8}", .{ T, address }),
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        else => @compileError("I/O: Unsupported read width"),
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn write(bus: *Bus, comptime T: type, address: u32, value: T) void {
 | 
				
			||||||
 | 
					    return switch (T) {
 | 
				
			||||||
 | 
					        u32 => switch (address) {
 | 
				
			||||||
 | 
					            // Display
 | 
				
			||||||
 | 
					            0x0400_0000 => bus.ppu.dispcnt.raw = @truncate(u16, value),
 | 
				
			||||||
 | 
					            0x0400_0004 => {
 | 
				
			||||||
 | 
					                bus.ppu.dispstat.raw = @truncate(u16, value);
 | 
				
			||||||
 | 
					                bus.ppu.vcount.raw = @truncate(u16, value >> 16);
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            0x0400_0008 => bus.ppu.setAdjCnts(0, value),
 | 
				
			||||||
 | 
					            0x0400_000C => bus.ppu.setAdjCnts(2, value),
 | 
				
			||||||
 | 
					            0x0400_0010 => bus.ppu.setBgOffsets(0, value),
 | 
				
			||||||
 | 
					            0x0400_0014 => bus.ppu.setBgOffsets(1, value),
 | 
				
			||||||
 | 
					            0x0400_0018 => bus.ppu.setBgOffsets(2, value),
 | 
				
			||||||
 | 
					            0x0400_001C => bus.ppu.setBgOffsets(3, value),
 | 
				
			||||||
 | 
					            0x0400_0020 => bus.ppu.aff_bg[0].writePaPb(value),
 | 
				
			||||||
 | 
					            0x0400_0024 => bus.ppu.aff_bg[0].writePcPd(value),
 | 
				
			||||||
 | 
					            0x0400_0028 => bus.ppu.aff_bg[0].setX(bus.ppu.dispstat.vblank.read(), value),
 | 
				
			||||||
 | 
					            0x0400_002C => bus.ppu.aff_bg[0].setY(bus.ppu.dispstat.vblank.read(), value),
 | 
				
			||||||
 | 
					            0x0400_0030 => bus.ppu.aff_bg[1].writePaPb(value),
 | 
				
			||||||
 | 
					            0x0400_0034 => bus.ppu.aff_bg[1].writePcPd(value),
 | 
				
			||||||
 | 
					            0x0400_0038 => bus.ppu.aff_bg[1].setX(bus.ppu.dispstat.vblank.read(), value),
 | 
				
			||||||
 | 
					            0x0400_003C => bus.ppu.aff_bg[1].setY(bus.ppu.dispstat.vblank.read(), value),
 | 
				
			||||||
 | 
					            0x0400_0040 => bus.ppu.win.setH(value),
 | 
				
			||||||
 | 
					            0x0400_0044 => bus.ppu.win.setV(value),
 | 
				
			||||||
 | 
					            0x0400_0048 => bus.ppu.win.setIo(value),
 | 
				
			||||||
 | 
					            0x0400_004C => log.debug("Wrote 0x{X:0>8} to MOSAIC", .{value}),
 | 
				
			||||||
 | 
					            0x0400_0050 => {
 | 
				
			||||||
 | 
					                bus.ppu.bldcnt.raw = @truncate(u16, value);
 | 
				
			||||||
 | 
					                bus.ppu.bldalpha.raw = @truncate(u16, value >> 16);
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            0x0400_0054 => bus.ppu.bldy.raw = @truncate(u16, value),
 | 
				
			||||||
 | 
					            0x0400_0058...0x0400_005C => {}, // Unused
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // Sound
 | 
				
			||||||
 | 
					            0x0400_0060...0x0400_00A4 => apu.write(T, &bus.apu, address, value),
 | 
				
			||||||
 | 
					            0x0400_00A8, 0x0400_00AC => {}, // Unused
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // DMA Transfers
 | 
				
			||||||
 | 
					            0x0400_00B0...0x0400_00DC => dma.write(T, &bus.dma, address, value),
 | 
				
			||||||
 | 
					            0x0400_00E0...0x0400_00FC => {}, // Unused
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // Timers
 | 
				
			||||||
 | 
					            0x0400_0100...0x0400_010C => timer.write(T, &bus.tim, address, value),
 | 
				
			||||||
 | 
					            0x0400_0110...0x0400_011C => {}, // Unused
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // Serial Communication 1
 | 
				
			||||||
 | 
					            0x0400_0120 => log.debug("Wrote 0x{X:0>8} to SIODATA32/(SIOMULTI0 and SIOMULTI1)", .{value}),
 | 
				
			||||||
 | 
					            0x0400_0124 => log.debug("Wrote 0x{X:0>8} to SIOMULTI2 and SIOMULTI3", .{value}),
 | 
				
			||||||
 | 
					            0x0400_0128 => log.debug("Wrote 0x{X:0>8} to SIOCNT and SIOMLT_SEND/SIODATA8", .{value}),
 | 
				
			||||||
 | 
					            0x0400_012C => {}, // Unused
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // Keypad Input
 | 
				
			||||||
 | 
					            0x0400_0130 => log.debug("Wrote 0x{X:0>8} to KEYINPUT and KEYCNT", .{value}),
 | 
				
			||||||
 | 
					            0x0400_0134 => log.debug("Wrote 0x{X:0>8} to RCNT and IR", .{value}),
 | 
				
			||||||
 | 
					            0x0400_0138, 0x0400_013C => {}, // Unused
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // Serial Communication 2
 | 
				
			||||||
 | 
					            0x0400_0140 => log.debug("Wrote 0x{X:0>8} to JOYCNT", .{value}),
 | 
				
			||||||
 | 
					            0x0400_0150 => log.debug("Wrote 0x{X:0>8} to JOY_RECV", .{value}),
 | 
				
			||||||
 | 
					            0x0400_0154 => log.debug("Wrote 0x{X:0>8} to JOY_TRANS", .{value}),
 | 
				
			||||||
 | 
					            0x0400_0158 => log.debug("Wrote 0x{X:0>8} to JOYSTAT (?)", .{value}),
 | 
				
			||||||
 | 
					            0x0400_0144...0x0400_014C, 0x0400_015C => {}, // Unused
 | 
				
			||||||
 | 
					            0x0400_0160...0x0400_01FC => {},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // Interrupts
 | 
				
			||||||
 | 
					            0x0400_0200 => bus.io.setIrqs(value),
 | 
				
			||||||
 | 
					            0x0400_0204 => log.debug("Wrote 0x{X:0>8} to WAITCNT", .{value}),
 | 
				
			||||||
 | 
					            0x0400_0208 => bus.io.ime = value & 1 == 1,
 | 
				
			||||||
 | 
					            0x0400_020C...0x0400_021C => {}, // Unused
 | 
				
			||||||
 | 
					            else => util.io.write.undef(log, "Tried to write 0x{X:0>8}{} to 0x{X:0>8}", .{ value, T, address }),
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        u16 => switch (address) {
 | 
				
			||||||
 | 
					            // Display
 | 
				
			||||||
 | 
					            0x0400_0000 => bus.ppu.dispcnt.raw = value,
 | 
				
			||||||
 | 
					            0x0400_0004 => bus.ppu.dispstat.raw = value,
 | 
				
			||||||
 | 
					            0x0400_0006 => {}, // vcount is read-only
 | 
				
			||||||
 | 
					            0x0400_0008 => bus.ppu.bg[0].cnt.raw = value,
 | 
				
			||||||
 | 
					            0x0400_000A => bus.ppu.bg[1].cnt.raw = value,
 | 
				
			||||||
 | 
					            0x0400_000C => bus.ppu.bg[2].cnt.raw = value,
 | 
				
			||||||
 | 
					            0x0400_000E => bus.ppu.bg[3].cnt.raw = value,
 | 
				
			||||||
 | 
					            0x0400_0010 => bus.ppu.bg[0].hofs.raw = value, // TODO: Don't write out every HOFS / VOFS?
 | 
				
			||||||
 | 
					            0x0400_0012 => bus.ppu.bg[0].vofs.raw = value,
 | 
				
			||||||
 | 
					            0x0400_0014 => bus.ppu.bg[1].hofs.raw = value,
 | 
				
			||||||
 | 
					            0x0400_0016 => bus.ppu.bg[1].vofs.raw = value,
 | 
				
			||||||
 | 
					            0x0400_0018 => bus.ppu.bg[2].hofs.raw = value,
 | 
				
			||||||
 | 
					            0x0400_001A => bus.ppu.bg[2].vofs.raw = value,
 | 
				
			||||||
 | 
					            0x0400_001C => bus.ppu.bg[3].hofs.raw = value,
 | 
				
			||||||
 | 
					            0x0400_001E => bus.ppu.bg[3].vofs.raw = value,
 | 
				
			||||||
 | 
					            0x0400_0020 => bus.ppu.aff_bg[0].pa = @bitCast(i16, value),
 | 
				
			||||||
 | 
					            0x0400_0022 => bus.ppu.aff_bg[0].pb = @bitCast(i16, value),
 | 
				
			||||||
 | 
					            0x0400_0024 => bus.ppu.aff_bg[0].pc = @bitCast(i16, value),
 | 
				
			||||||
 | 
					            0x0400_0026 => bus.ppu.aff_bg[0].pd = @bitCast(i16, value),
 | 
				
			||||||
 | 
					            0x0400_0028 => bus.ppu.aff_bg[0].x = @bitCast(i32, setLo(u32, @bitCast(u32, bus.ppu.aff_bg[0].x), value)),
 | 
				
			||||||
 | 
					            0x0400_002A => bus.ppu.aff_bg[0].x = @bitCast(i32, setHi(u32, @bitCast(u32, bus.ppu.aff_bg[0].x), value)),
 | 
				
			||||||
 | 
					            0x0400_002C => bus.ppu.aff_bg[0].y = @bitCast(i32, setLo(u32, @bitCast(u32, bus.ppu.aff_bg[0].y), value)),
 | 
				
			||||||
 | 
					            0x0400_002E => bus.ppu.aff_bg[0].y = @bitCast(i32, setHi(u32, @bitCast(u32, bus.ppu.aff_bg[0].y), value)),
 | 
				
			||||||
 | 
					            0x0400_0030 => bus.ppu.aff_bg[1].pa = @bitCast(i16, value),
 | 
				
			||||||
 | 
					            0x0400_0032 => bus.ppu.aff_bg[1].pb = @bitCast(i16, value),
 | 
				
			||||||
 | 
					            0x0400_0034 => bus.ppu.aff_bg[1].pc = @bitCast(i16, value),
 | 
				
			||||||
 | 
					            0x0400_0036 => bus.ppu.aff_bg[1].pd = @bitCast(i16, value),
 | 
				
			||||||
 | 
					            0x0400_0038 => bus.ppu.aff_bg[1].x = @bitCast(i32, setLo(u32, @bitCast(u32, bus.ppu.aff_bg[1].x), value)),
 | 
				
			||||||
 | 
					            0x0400_003A => bus.ppu.aff_bg[1].x = @bitCast(i32, setHi(u32, @bitCast(u32, bus.ppu.aff_bg[1].x), value)),
 | 
				
			||||||
 | 
					            0x0400_003C => bus.ppu.aff_bg[1].y = @bitCast(i32, setLo(u32, @bitCast(u32, bus.ppu.aff_bg[1].y), value)),
 | 
				
			||||||
 | 
					            0x0400_003E => bus.ppu.aff_bg[1].y = @bitCast(i32, setHi(u32, @bitCast(u32, bus.ppu.aff_bg[1].y), value)),
 | 
				
			||||||
 | 
					            0x0400_0040 => bus.ppu.win.h[0].raw = value,
 | 
				
			||||||
 | 
					            0x0400_0042 => bus.ppu.win.h[1].raw = value,
 | 
				
			||||||
 | 
					            0x0400_0044 => bus.ppu.win.v[0].raw = value,
 | 
				
			||||||
 | 
					            0x0400_0046 => bus.ppu.win.v[1].raw = value,
 | 
				
			||||||
 | 
					            0x0400_0048 => bus.ppu.win.in.raw = value,
 | 
				
			||||||
 | 
					            0x0400_004A => bus.ppu.win.out.raw = value,
 | 
				
			||||||
 | 
					            0x0400_004C => log.debug("Wrote 0x{X:0>4} to MOSAIC", .{value}),
 | 
				
			||||||
 | 
					            0x0400_0050 => bus.ppu.bldcnt.raw = value,
 | 
				
			||||||
 | 
					            0x0400_0052 => bus.ppu.bldalpha.raw = value,
 | 
				
			||||||
 | 
					            0x0400_0054 => bus.ppu.bldy.raw = value,
 | 
				
			||||||
 | 
					            0x0400_004E, 0x0400_0056 => {}, // Not used
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // Sound
 | 
				
			||||||
 | 
					            0x0400_0060...0x0400_009E => apu.write(T, &bus.apu, address, value),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // Dma Transfers
 | 
				
			||||||
 | 
					            0x0400_00B0...0x0400_00DE => dma.write(T, &bus.dma, address, value),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // Timers
 | 
				
			||||||
 | 
					            0x0400_0100...0x0400_010E => timer.write(T, &bus.tim, address, value),
 | 
				
			||||||
 | 
					            0x0400_0114 => {}, // TODO: Gyakuten Saiban writes 0x8000 to 0x0400_0114
 | 
				
			||||||
 | 
					            0x0400_0110 => {}, // Not Used,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // Serial Communication 1
 | 
				
			||||||
 | 
					            0x0400_0120 => log.debug("Wrote 0x{X:0>4} to SIOMULTI0", .{value}),
 | 
				
			||||||
 | 
					            0x0400_0122 => log.debug("Wrote 0x{X:0>4} to SIOMULTI1", .{value}),
 | 
				
			||||||
 | 
					            0x0400_0124 => log.debug("Wrote 0x{X:0>4} to SIOMULTI2", .{value}),
 | 
				
			||||||
 | 
					            0x0400_0126 => log.debug("Wrote 0x{X:0>4} to SIOMULTI3", .{value}),
 | 
				
			||||||
 | 
					            0x0400_0128 => log.debug("Wrote 0x{X:0>4} to SIOCNT", .{value}),
 | 
				
			||||||
 | 
					            0x0400_012A => log.debug("Wrote 0x{X:0>4} to SIOMLT_SEND", .{value}),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // Keypad Input
 | 
				
			||||||
 | 
					            0x0400_0130 => log.debug("Wrote 0x{X:0>4} to KEYINPUT. Ignored", .{value}),
 | 
				
			||||||
 | 
					            0x0400_0132 => log.debug("Wrote 0x{X:0>4} to KEYCNT", .{value}),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // Serial Communication 2
 | 
				
			||||||
 | 
					            0x0400_0134 => log.debug("Wrote 0x{X:0>4} to RCNT", .{value}),
 | 
				
			||||||
 | 
					            0x0400_0140 => log.debug("Wrote 0x{X:0>4} to JOYCNT", .{value}),
 | 
				
			||||||
 | 
					            0x0400_0158 => log.debug("Wrote 0x{X:0>4} to JOYSTAT", .{value}),
 | 
				
			||||||
 | 
					            0x0400_0142, 0x0400_015A => {}, // Not Used
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // Interrupts
 | 
				
			||||||
 | 
					            0x0400_0200 => bus.io.ie.raw = value,
 | 
				
			||||||
 | 
					            0x0400_0202 => bus.io.irq.raw &= ~value,
 | 
				
			||||||
 | 
					            0x0400_0204 => log.debug("Wrote 0x{X:0>4} to WAITCNT", .{value}),
 | 
				
			||||||
 | 
					            0x0400_0208 => bus.io.ime = value & 1 == 1,
 | 
				
			||||||
 | 
					            0x0400_0206, 0x0400_020A => {}, // Not Used
 | 
				
			||||||
 | 
					            else => util.io.write.undef(log, "Tried to write 0x{X:0>4}{} to 0x{X:0>8}", .{ value, T, address }),
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        u8 => switch (address) {
 | 
				
			||||||
 | 
					            // Display
 | 
				
			||||||
 | 
					            0x0400_0004 => bus.ppu.dispstat.raw = setLo(u16, bus.ppu.dispstat.raw, value),
 | 
				
			||||||
 | 
					            0x0400_0005 => bus.ppu.dispstat.raw = setHi(u16, bus.ppu.dispstat.raw, value),
 | 
				
			||||||
 | 
					            0x0400_0008 => bus.ppu.bg[0].cnt.raw = setLo(u16, bus.ppu.bg[0].cnt.raw, value),
 | 
				
			||||||
 | 
					            0x0400_0009 => bus.ppu.bg[0].cnt.raw = setHi(u16, bus.ppu.bg[0].cnt.raw, value),
 | 
				
			||||||
 | 
					            0x0400_000A => bus.ppu.bg[1].cnt.raw = setLo(u16, bus.ppu.bg[1].cnt.raw, value),
 | 
				
			||||||
 | 
					            0x0400_000B => bus.ppu.bg[1].cnt.raw = setHi(u16, bus.ppu.bg[1].cnt.raw, value),
 | 
				
			||||||
 | 
					            0x0400_0040 => bus.ppu.win.h[0].raw = setLo(u16, bus.ppu.win.h[0].raw, value),
 | 
				
			||||||
 | 
					            0x0400_0041 => bus.ppu.win.h[0].raw = setHi(u16, bus.ppu.win.h[0].raw, value),
 | 
				
			||||||
 | 
					            0x0400_0042 => bus.ppu.win.h[1].raw = setLo(u16, bus.ppu.win.h[1].raw, value),
 | 
				
			||||||
 | 
					            0x0400_0043 => bus.ppu.win.h[1].raw = setHi(u16, bus.ppu.win.h[1].raw, value),
 | 
				
			||||||
 | 
					            0x0400_0044 => bus.ppu.win.v[0].raw = setLo(u16, bus.ppu.win.v[0].raw, value),
 | 
				
			||||||
 | 
					            0x0400_0045 => bus.ppu.win.v[0].raw = setHi(u16, bus.ppu.win.v[0].raw, value),
 | 
				
			||||||
 | 
					            0x0400_0046 => bus.ppu.win.v[1].raw = setLo(u16, bus.ppu.win.v[1].raw, value),
 | 
				
			||||||
 | 
					            0x0400_0047 => bus.ppu.win.v[1].raw = setHi(u16, bus.ppu.win.v[1].raw, value),
 | 
				
			||||||
 | 
					            0x0400_0048 => bus.ppu.win.in.raw = setLo(u16, bus.ppu.win.in.raw, value),
 | 
				
			||||||
 | 
					            0x0400_0049 => bus.ppu.win.in.raw = setHi(u16, bus.ppu.win.in.raw, value),
 | 
				
			||||||
 | 
					            0x0400_004A => bus.ppu.win.out.raw = setLo(u16, bus.ppu.win.out.raw, value),
 | 
				
			||||||
 | 
					            0x0400_0054 => bus.ppu.bldy.raw = setLo(u16, bus.ppu.bldy.raw, value),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // Sound
 | 
				
			||||||
 | 
					            0x0400_0060...0x0400_00A7 => apu.write(T, &bus.apu, address, value),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // Serial Communication 1
 | 
				
			||||||
 | 
					            0x0400_0120 => log.debug("Wrote 0x{X:0>2} to SIODATA32_L_L", .{value}),
 | 
				
			||||||
 | 
					            0x0400_0128 => log.debug("Wrote 0x{X:0>2} to SIOCNT_L", .{value}),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // Serial Communication 2
 | 
				
			||||||
 | 
					            0x0400_0135 => log.debug("Wrote 0x{X:0>2} to RCNT_H", .{value}),
 | 
				
			||||||
 | 
					            0x0400_0140 => log.debug("Wrote 0x{X:0>2} to JOYCNT_L", .{value}),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // Interrupts
 | 
				
			||||||
 | 
					            0x0400_0202 => bus.io.irq.raw &= ~@as(u16, value),
 | 
				
			||||||
 | 
					            0x0400_0208 => bus.io.ime = value & 1 == 1,
 | 
				
			||||||
 | 
					            0x0400_0300 => bus.io.postflg = std.meta.intToEnum(PostFlag, value & 1) catch unreachable,
 | 
				
			||||||
 | 
					            0x0400_0301 => bus.io.haltcnt = if (value >> 7 & 1 == 0) .Halt else std.debug.panic("TODO: Implement STOP", .{}),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            0x0400_0410 => log.debug("Wrote 0x{X:0>2} to the common yet undocumented 0x{X:0>8}", .{ value, address }),
 | 
				
			||||||
 | 
					            else => util.io.write.undef(log, "Tried to write 0x{X:0>2}{} to 0x{X:0>8}", .{ value, T, address }),
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        else => @compileError("I/O: Unsupported write width"),
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// Read / Write
 | 
				
			||||||
 | 
					pub const PostFlag = enum(u1) {
 | 
				
			||||||
 | 
					    FirstBoot = 0,
 | 
				
			||||||
 | 
					    FurtherBoots = 1,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// Write Only
 | 
				
			||||||
 | 
					pub const HaltControl = enum {
 | 
				
			||||||
 | 
					    Halt,
 | 
				
			||||||
 | 
					    Stop,
 | 
				
			||||||
 | 
					    Execute,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// Read / Write
 | 
				
			||||||
 | 
					pub const DisplayControl = extern union {
 | 
				
			||||||
 | 
					    bg_mode: Bitfield(u16, 0, 3),
 | 
				
			||||||
 | 
					    frame_select: Bit(u16, 4),
 | 
				
			||||||
 | 
					    hblank_interval_free: Bit(u16, 5),
 | 
				
			||||||
 | 
					    obj_mapping: Bit(u16, 6),
 | 
				
			||||||
 | 
					    forced_blank: Bit(u16, 7),
 | 
				
			||||||
 | 
					    bg_enable: Bitfield(u16, 8, 4),
 | 
				
			||||||
 | 
					    obj_enable: Bit(u16, 12),
 | 
				
			||||||
 | 
					    win_enable: Bitfield(u16, 13, 2),
 | 
				
			||||||
 | 
					    obj_win_enable: Bit(u16, 15),
 | 
				
			||||||
 | 
					    raw: u16,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// Read / Write
 | 
				
			||||||
 | 
					pub const DisplayStatus = extern union {
 | 
				
			||||||
 | 
					    vblank: Bit(u16, 0),
 | 
				
			||||||
 | 
					    hblank: Bit(u16, 1),
 | 
				
			||||||
 | 
					    coincidence: Bit(u16, 2),
 | 
				
			||||||
 | 
					    vblank_irq: Bit(u16, 3),
 | 
				
			||||||
 | 
					    hblank_irq: Bit(u16, 4),
 | 
				
			||||||
 | 
					    vcount_irq: Bit(u16, 5),
 | 
				
			||||||
 | 
					    vcount_trigger: Bitfield(u16, 8, 8),
 | 
				
			||||||
 | 
					    raw: u16,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// Read Only
 | 
				
			||||||
 | 
					pub const VCount = extern union {
 | 
				
			||||||
 | 
					    scanline: Bitfield(u16, 0, 8),
 | 
				
			||||||
 | 
					    raw: u16,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// Read / Write
 | 
				
			||||||
 | 
					const InterruptEnable = extern union {
 | 
				
			||||||
 | 
					    vblank: Bit(u16, 0),
 | 
				
			||||||
 | 
					    hblank: Bit(u16, 1),
 | 
				
			||||||
 | 
					    coincidence: Bit(u16, 2),
 | 
				
			||||||
 | 
					    tm0_overflow: Bit(u16, 3),
 | 
				
			||||||
 | 
					    tm1_overflow: Bit(u16, 4),
 | 
				
			||||||
 | 
					    tm2_overflow: Bit(u16, 5),
 | 
				
			||||||
 | 
					    tm3_overflow: Bit(u16, 6),
 | 
				
			||||||
 | 
					    serial: Bit(u16, 7),
 | 
				
			||||||
 | 
					    dma0: Bit(u16, 8),
 | 
				
			||||||
 | 
					    dma1: Bit(u16, 9),
 | 
				
			||||||
 | 
					    dma2: Bit(u16, 10),
 | 
				
			||||||
 | 
					    dma3: Bit(u16, 11),
 | 
				
			||||||
 | 
					    keypad: Bit(u16, 12),
 | 
				
			||||||
 | 
					    game_pak: Bit(u16, 13),
 | 
				
			||||||
 | 
					    raw: u16,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// Read Only
 | 
				
			||||||
 | 
					/// 0 = Pressed, 1 = Released
 | 
				
			||||||
 | 
					const KeyInput = extern union {
 | 
				
			||||||
 | 
					    a: Bit(u16, 0),
 | 
				
			||||||
 | 
					    b: Bit(u16, 1),
 | 
				
			||||||
 | 
					    select: Bit(u16, 2),
 | 
				
			||||||
 | 
					    start: Bit(u16, 3),
 | 
				
			||||||
 | 
					    right: Bit(u16, 4),
 | 
				
			||||||
 | 
					    left: Bit(u16, 5),
 | 
				
			||||||
 | 
					    up: Bit(u16, 6),
 | 
				
			||||||
 | 
					    down: Bit(u16, 7),
 | 
				
			||||||
 | 
					    shoulder_r: Bit(u16, 8),
 | 
				
			||||||
 | 
					    shoulder_l: Bit(u16, 9),
 | 
				
			||||||
 | 
					    raw: u16,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Read / Write
 | 
				
			||||||
 | 
					pub const BackgroundControl = extern union {
 | 
				
			||||||
 | 
					    priority: Bitfield(u16, 0, 2),
 | 
				
			||||||
 | 
					    char_base: Bitfield(u16, 2, 2),
 | 
				
			||||||
 | 
					    mosaic_enable: Bit(u16, 6),
 | 
				
			||||||
 | 
					    colour_mode: Bit(u16, 7),
 | 
				
			||||||
 | 
					    screen_base: Bitfield(u16, 8, 5),
 | 
				
			||||||
 | 
					    display_overflow: Bit(u16, 13),
 | 
				
			||||||
 | 
					    size: Bitfield(u16, 14, 2),
 | 
				
			||||||
 | 
					    raw: u16,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// Write Only
 | 
				
			||||||
 | 
					pub const BackgroundOffset = extern union {
 | 
				
			||||||
 | 
					    offset: Bitfield(u16, 0, 9),
 | 
				
			||||||
 | 
					    raw: u16,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// Read / Write
 | 
				
			||||||
 | 
					pub const BldCnt = extern union {
 | 
				
			||||||
 | 
					    /// BLDCNT{0} is BG0 A
 | 
				
			||||||
 | 
					    /// BLDCNT{4} is OBJ A
 | 
				
			||||||
 | 
					    /// BLDCNT{5} is BD  A
 | 
				
			||||||
 | 
					    layer_a: Bitfield(u16, 0, 6),
 | 
				
			||||||
 | 
					    mode: Bitfield(u16, 6, 2),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /// BLDCNT{8} is BG0 B
 | 
				
			||||||
 | 
					    /// BLDCNT{12} is OBJ B
 | 
				
			||||||
 | 
					    /// BLDCNT{13} is BD  B
 | 
				
			||||||
 | 
					    layer_b: Bitfield(u16, 8, 6),
 | 
				
			||||||
 | 
					    raw: u16,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// Read-only?
 | 
				
			||||||
 | 
					/// Alpha Blending Coefficients
 | 
				
			||||||
 | 
					pub const BldAlpha = extern union {
 | 
				
			||||||
 | 
					    eva: Bitfield(u16, 0, 5),
 | 
				
			||||||
 | 
					    evb: Bitfield(u16, 8, 5),
 | 
				
			||||||
 | 
					    raw: u16,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// Write-only?
 | 
				
			||||||
 | 
					/// Brightness COefficients
 | 
				
			||||||
 | 
					pub const BldY = extern union {
 | 
				
			||||||
 | 
					    evy: Bitfield(u16, 0, 5),
 | 
				
			||||||
 | 
					    raw: u16,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const u8WriteKind = enum { Hi, Lo };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// Write-only
 | 
				
			||||||
 | 
					pub const WinH = extern union {
 | 
				
			||||||
 | 
					    const Self = @This();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    x2: Bitfield(u16, 0, 8),
 | 
				
			||||||
 | 
					    x1: Bitfield(u16, 8, 8),
 | 
				
			||||||
 | 
					    raw: u16,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// Write-only
 | 
				
			||||||
 | 
					pub const WinV = extern union {
 | 
				
			||||||
 | 
					    const Self = @This();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    y2: Bitfield(u16, 0, 8),
 | 
				
			||||||
 | 
					    y1: Bitfield(u16, 8, 8),
 | 
				
			||||||
 | 
					    raw: u16,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn set(self: *Self, comptime K: u8WriteKind, value: u8) void {
 | 
				
			||||||
 | 
					        self.raw = switch (K) {
 | 
				
			||||||
 | 
					            .Hi => (@as(u16, value) << 8) | self.raw & 0xFF,
 | 
				
			||||||
 | 
					            .Lo => (self.raw & 0xFF00) | value,
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub const WinIn = extern union {
 | 
				
			||||||
 | 
					    w0_bg: Bitfield(u16, 0, 4),
 | 
				
			||||||
 | 
					    w0_obj: Bit(u16, 4),
 | 
				
			||||||
 | 
					    w0_bld: Bit(u16, 5),
 | 
				
			||||||
 | 
					    w1_bg: Bitfield(u16, 8, 4),
 | 
				
			||||||
 | 
					    w1_obj: Bit(u16, 12),
 | 
				
			||||||
 | 
					    w1_bld: Bit(u16, 13),
 | 
				
			||||||
 | 
					    raw: u16,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub const WinOut = extern union {
 | 
				
			||||||
 | 
					    out_bg: Bitfield(u16, 0, 4),
 | 
				
			||||||
 | 
					    out_obj: Bit(u16, 4),
 | 
				
			||||||
 | 
					    out_bld: Bit(u16, 5),
 | 
				
			||||||
 | 
					    obj_bg: Bitfield(u16, 8, 4),
 | 
				
			||||||
 | 
					    obj_obj: Bit(u16, 12),
 | 
				
			||||||
 | 
					    obj_bld: Bit(u16, 13),
 | 
				
			||||||
 | 
					    raw: u16,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// Read / Write
 | 
				
			||||||
 | 
					const InterruptRequest = extern union {
 | 
				
			||||||
 | 
					    vblank: Bit(u16, 0),
 | 
				
			||||||
 | 
					    hblank: Bit(u16, 1),
 | 
				
			||||||
 | 
					    coincidence: Bit(u16, 2),
 | 
				
			||||||
 | 
					    tim0: Bit(u16, 3),
 | 
				
			||||||
 | 
					    tim1: Bit(u16, 4),
 | 
				
			||||||
 | 
					    tim2: Bit(u16, 5),
 | 
				
			||||||
 | 
					    tim3: Bit(u16, 6),
 | 
				
			||||||
 | 
					    serial: Bit(u16, 7),
 | 
				
			||||||
 | 
					    dma0: Bit(u16, 8),
 | 
				
			||||||
 | 
					    dma1: Bit(u16, 9),
 | 
				
			||||||
 | 
					    dma2: Bit(u16, 10),
 | 
				
			||||||
 | 
					    dma3: Bit(u16, 11),
 | 
				
			||||||
 | 
					    keypad: Bit(u16, 12),
 | 
				
			||||||
 | 
					    game_pak: Bit(u16, 13),
 | 
				
			||||||
 | 
					    raw: u16,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// Read / Write
 | 
				
			||||||
 | 
					pub const DmaControl = extern union {
 | 
				
			||||||
 | 
					    dad_adj: Bitfield(u16, 5, 2),
 | 
				
			||||||
 | 
					    sad_adj: Bitfield(u16, 7, 2),
 | 
				
			||||||
 | 
					    repeat: Bit(u16, 9),
 | 
				
			||||||
 | 
					    transfer_type: Bit(u16, 10),
 | 
				
			||||||
 | 
					    pak_drq: Bit(u16, 11),
 | 
				
			||||||
 | 
					    start_timing: Bitfield(u16, 12, 2),
 | 
				
			||||||
 | 
					    irq: Bit(u16, 14),
 | 
				
			||||||
 | 
					    enabled: Bit(u16, 15),
 | 
				
			||||||
 | 
					    raw: u16,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// Read / Write
 | 
				
			||||||
 | 
					pub const TimerControl = extern union {
 | 
				
			||||||
 | 
					    frequency: Bitfield(u16, 0, 2),
 | 
				
			||||||
 | 
					    cascade: Bit(u16, 2),
 | 
				
			||||||
 | 
					    irq: Bit(u16, 6),
 | 
				
			||||||
 | 
					    enabled: Bit(u16, 7),
 | 
				
			||||||
 | 
					    raw: u16,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// Read / Write
 | 
				
			||||||
 | 
					/// NR10
 | 
				
			||||||
 | 
					pub const Sweep = extern union {
 | 
				
			||||||
 | 
					    shift: Bitfield(u8, 0, 3),
 | 
				
			||||||
 | 
					    direction: Bit(u8, 3),
 | 
				
			||||||
 | 
					    period: Bitfield(u8, 4, 3),
 | 
				
			||||||
 | 
					    raw: u8,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// Read / Write
 | 
				
			||||||
 | 
					/// This represents the Duty / Len
 | 
				
			||||||
 | 
					/// NRx1
 | 
				
			||||||
 | 
					pub const Duty = extern union {
 | 
				
			||||||
 | 
					    /// Write-only
 | 
				
			||||||
 | 
					    /// Only used when bit 6 is set
 | 
				
			||||||
 | 
					    length: Bitfield(u16, 0, 6),
 | 
				
			||||||
 | 
					    pattern: Bitfield(u16, 6, 2),
 | 
				
			||||||
 | 
					    raw: u8,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// Read / Write
 | 
				
			||||||
 | 
					/// NRx2
 | 
				
			||||||
 | 
					pub const Envelope = extern union {
 | 
				
			||||||
 | 
					    period: Bitfield(u8, 0, 3),
 | 
				
			||||||
 | 
					    direction: Bit(u8, 3),
 | 
				
			||||||
 | 
					    init_vol: Bitfield(u8, 4, 4),
 | 
				
			||||||
 | 
					    raw: u8,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// Read / Write
 | 
				
			||||||
 | 
					/// NRx3, NRx4
 | 
				
			||||||
 | 
					pub const Frequency = extern union {
 | 
				
			||||||
 | 
					    /// Write-only
 | 
				
			||||||
 | 
					    frequency: Bitfield(u16, 0, 11),
 | 
				
			||||||
 | 
					    length_enable: Bit(u16, 14),
 | 
				
			||||||
 | 
					    /// Write-only
 | 
				
			||||||
 | 
					    trigger: Bit(u16, 15),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    raw: u16,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// Read / Write
 | 
				
			||||||
 | 
					/// NR30
 | 
				
			||||||
 | 
					pub const WaveSelect = extern union {
 | 
				
			||||||
 | 
					    dimension: Bit(u8, 5),
 | 
				
			||||||
 | 
					    bank: Bit(u8, 6),
 | 
				
			||||||
 | 
					    enabled: Bit(u8, 7),
 | 
				
			||||||
 | 
					    raw: u8,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// Read / Write
 | 
				
			||||||
 | 
					/// NR32
 | 
				
			||||||
 | 
					pub const WaveVolume = extern union {
 | 
				
			||||||
 | 
					    kind: Bitfield(u8, 5, 2),
 | 
				
			||||||
 | 
					    force: Bit(u8, 7),
 | 
				
			||||||
 | 
					    raw: u8,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// Read / Write
 | 
				
			||||||
 | 
					/// NR43
 | 
				
			||||||
 | 
					pub const PolyCounter = extern union {
 | 
				
			||||||
 | 
					    div_ratio: Bitfield(u8, 0, 3),
 | 
				
			||||||
 | 
					    width: Bit(u8, 3),
 | 
				
			||||||
 | 
					    shift: Bitfield(u8, 4, 4),
 | 
				
			||||||
 | 
					    raw: u8,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// Read / Write
 | 
				
			||||||
 | 
					/// NR44
 | 
				
			||||||
 | 
					pub const NoiseControl = extern union {
 | 
				
			||||||
 | 
					    length_enable: Bit(u8, 6),
 | 
				
			||||||
 | 
					    trigger: Bit(u8, 7),
 | 
				
			||||||
 | 
					    raw: u8,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// Read / Write
 | 
				
			||||||
 | 
					pub const ChannelVolumeControl = extern union {
 | 
				
			||||||
 | 
					    right_vol: Bitfield(u16, 0, 3),
 | 
				
			||||||
 | 
					    left_vol: Bitfield(u16, 4, 3),
 | 
				
			||||||
 | 
					    ch_right: Bitfield(u16, 8, 4),
 | 
				
			||||||
 | 
					    ch_left: Bitfield(u16, 12, 4),
 | 
				
			||||||
 | 
					    raw: u16,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// Read / Write
 | 
				
			||||||
 | 
					pub const DmaSoundControl = extern union {
 | 
				
			||||||
 | 
					    ch_vol: Bitfield(u16, 0, 2),
 | 
				
			||||||
 | 
					    chA_vol: Bit(u16, 2),
 | 
				
			||||||
 | 
					    chB_vol: Bit(u16, 3),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    chA_right: Bit(u16, 8),
 | 
				
			||||||
 | 
					    chA_left: Bit(u16, 9),
 | 
				
			||||||
 | 
					    chA_timer: Bit(u16, 10),
 | 
				
			||||||
 | 
					    /// Write only?
 | 
				
			||||||
 | 
					    chA_reset: Bit(u16, 11),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    chB_right: Bit(u16, 12),
 | 
				
			||||||
 | 
					    chB_left: Bit(u16, 13),
 | 
				
			||||||
 | 
					    chB_timer: Bit(u16, 14),
 | 
				
			||||||
 | 
					    /// Write only?
 | 
				
			||||||
 | 
					    chB_reset: Bit(u16, 15),
 | 
				
			||||||
 | 
					    raw: u16,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// Read / Write
 | 
				
			||||||
 | 
					pub const SoundControl = extern union {
 | 
				
			||||||
 | 
					    /// Read-only
 | 
				
			||||||
 | 
					    ch1_enable: Bit(u8, 0),
 | 
				
			||||||
 | 
					    /// Read-only
 | 
				
			||||||
 | 
					    ch2_enable: Bit(u8, 1),
 | 
				
			||||||
 | 
					    /// Read-only
 | 
				
			||||||
 | 
					    ch3_enable: Bit(u8, 2),
 | 
				
			||||||
 | 
					    /// Read-only
 | 
				
			||||||
 | 
					    ch4_enable: Bit(u8, 3),
 | 
				
			||||||
 | 
					    apu_enable: Bit(u8, 7),
 | 
				
			||||||
 | 
					    raw: u8,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// Read / Write
 | 
				
			||||||
 | 
					pub const SoundBias = extern union {
 | 
				
			||||||
 | 
					    level: Bitfield(u16, 1, 9),
 | 
				
			||||||
 | 
					    sampling_cycle: Bitfield(u16, 14, 2),
 | 
				
			||||||
 | 
					    raw: u16,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
							
								
								
									
										200
									
								
								src/core/bus/timer.zig
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										200
									
								
								src/core/bus/timer.zig
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,200 @@
 | 
				
			|||||||
 | 
					const std = @import("std");
 | 
				
			||||||
 | 
					const util = @import("../../util.zig");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const TimerControl = @import("io.zig").TimerControl;
 | 
				
			||||||
 | 
					const Io = @import("io.zig").Io;
 | 
				
			||||||
 | 
					const Scheduler = @import("../scheduler.zig").Scheduler;
 | 
				
			||||||
 | 
					const Event = @import("../scheduler.zig").Event;
 | 
				
			||||||
 | 
					const Arm7tdmi = @import("../cpu.zig").Arm7tdmi;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub const TimerTuple = std.meta.Tuple(&[_]type{ Timer(0), Timer(1), Timer(2), Timer(3) });
 | 
				
			||||||
 | 
					const log = std.log.scoped(.Timer);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn create(sched: *Scheduler) TimerTuple {
 | 
				
			||||||
 | 
					    return .{ Timer(0).init(sched), Timer(1).init(sched), Timer(2).init(sched), Timer(3).init(sched) };
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn read(comptime T: type, tim: *const TimerTuple, addr: u32) ?T {
 | 
				
			||||||
 | 
					    const nybble = @truncate(u4, addr);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return switch (T) {
 | 
				
			||||||
 | 
					        u32 => switch (nybble) {
 | 
				
			||||||
 | 
					            0x0 => @as(T, tim.*[0].cnt.raw) << 16 | tim.*[0].timcntL(),
 | 
				
			||||||
 | 
					            0x4 => @as(T, tim.*[1].cnt.raw) << 16 | tim.*[1].timcntL(),
 | 
				
			||||||
 | 
					            0x8 => @as(T, tim.*[2].cnt.raw) << 16 | tim.*[2].timcntL(),
 | 
				
			||||||
 | 
					            0xC => @as(T, tim.*[3].cnt.raw) << 16 | tim.*[3].timcntL(),
 | 
				
			||||||
 | 
					            else => util.io.read.undef(T, log, "Tried to perform a {} read to 0x{X:0>8}", .{ T, addr }),
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        u16 => switch (nybble) {
 | 
				
			||||||
 | 
					            0x0 => tim.*[0].timcntL(),
 | 
				
			||||||
 | 
					            0x2 => tim.*[0].cnt.raw,
 | 
				
			||||||
 | 
					            0x4 => tim.*[1].timcntL(),
 | 
				
			||||||
 | 
					            0x6 => tim.*[1].cnt.raw,
 | 
				
			||||||
 | 
					            0x8 => tim.*[2].timcntL(),
 | 
				
			||||||
 | 
					            0xA => tim.*[2].cnt.raw,
 | 
				
			||||||
 | 
					            0xC => tim.*[3].timcntL(),
 | 
				
			||||||
 | 
					            0xE => tim.*[3].cnt.raw,
 | 
				
			||||||
 | 
					            else => util.io.read.undef(T, log, "Tried to perform a {} read to 0x{X:0>8}", .{ T, addr }),
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        u8 => util.io.read.undef(T, log, "Tried to perform a {} read to 0x{X:0>8}", .{ T, addr }),
 | 
				
			||||||
 | 
					        else => @compileError("TIM: Unsupported read width"),
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn write(comptime T: type, tim: *TimerTuple, addr: u32, value: T) void {
 | 
				
			||||||
 | 
					    const nybble = @truncate(u4, addr);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return switch (T) {
 | 
				
			||||||
 | 
					        u32 => switch (nybble) {
 | 
				
			||||||
 | 
					            0x0 => tim.*[0].setTimcnt(value),
 | 
				
			||||||
 | 
					            0x4 => tim.*[1].setTimcnt(value),
 | 
				
			||||||
 | 
					            0x8 => tim.*[2].setTimcnt(value),
 | 
				
			||||||
 | 
					            0xC => tim.*[3].setTimcnt(value),
 | 
				
			||||||
 | 
					            else => util.io.write.undef(log, "Tried to write 0x{X:0>8}{} to 0x{X:0>8}", .{ value, T, addr }),
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        u16 => switch (nybble) {
 | 
				
			||||||
 | 
					            0x0 => tim.*[0].setTimcntL(value),
 | 
				
			||||||
 | 
					            0x2 => tim.*[0].setTimcntH(value),
 | 
				
			||||||
 | 
					            0x4 => tim.*[1].setTimcntL(value),
 | 
				
			||||||
 | 
					            0x6 => tim.*[1].setTimcntH(value),
 | 
				
			||||||
 | 
					            0x8 => tim.*[2].setTimcntL(value),
 | 
				
			||||||
 | 
					            0xA => tim.*[2].setTimcntH(value),
 | 
				
			||||||
 | 
					            0xC => tim.*[3].setTimcntL(value),
 | 
				
			||||||
 | 
					            0xE => tim.*[3].setTimcntH(value),
 | 
				
			||||||
 | 
					            else => util.io.write.undef(log, "Tried to write 0x{X:0>4}{} to 0x{X:0>8}", .{ value, T, addr }),
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        u8 => util.io.write.undef(log, "Tried to write 0x{X:0>2}{} to 0x{X:0>8}", .{ value, T, addr }),
 | 
				
			||||||
 | 
					        else => @compileError("TIM: Unsupported write width"),
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fn Timer(comptime id: u2) type {
 | 
				
			||||||
 | 
					    return struct {
 | 
				
			||||||
 | 
					        const Self = @This();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /// Read Only, Internal. Please use self.timcntL()
 | 
				
			||||||
 | 
					        _counter: u16,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /// Write Only, Internal. Please use self.setTimcntL()
 | 
				
			||||||
 | 
					        _reload: u16,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /// Write Only, Internal. Please use self.setTimcntH()
 | 
				
			||||||
 | 
					        cnt: TimerControl,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /// Internal.
 | 
				
			||||||
 | 
					        sched: *Scheduler,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /// Internal
 | 
				
			||||||
 | 
					        _start_timestamp: u64,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        pub fn init(sched: *Scheduler) Self {
 | 
				
			||||||
 | 
					            return .{
 | 
				
			||||||
 | 
					                ._reload = 0,
 | 
				
			||||||
 | 
					                ._counter = 0,
 | 
				
			||||||
 | 
					                .cnt = .{ .raw = 0x0000 },
 | 
				
			||||||
 | 
					                .sched = sched,
 | 
				
			||||||
 | 
					                ._start_timestamp = 0,
 | 
				
			||||||
 | 
					            };
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /// TIMCNT_L Getter
 | 
				
			||||||
 | 
					        pub fn timcntL(self: *const Self) u16 {
 | 
				
			||||||
 | 
					            if (self.cnt.cascade.read() or !self.cnt.enabled.read()) return self._counter;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            return self._counter +% @truncate(u16, (self.sched.now() - self._start_timestamp) / self.frequency());
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /// TIMCNT_L Setter
 | 
				
			||||||
 | 
					        pub fn setTimcntL(self: *Self, halfword: u16) void {
 | 
				
			||||||
 | 
					            self._reload = halfword;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /// TIMCNT_L & TIMCNT_H
 | 
				
			||||||
 | 
					        pub fn setTimcnt(self: *Self, word: u32) void {
 | 
				
			||||||
 | 
					            self.setTimcntL(@truncate(u16, word));
 | 
				
			||||||
 | 
					            self.setTimcntH(@truncate(u16, word >> 16));
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /// TIMCNT_H
 | 
				
			||||||
 | 
					        pub fn setTimcntH(self: *Self, halfword: u16) void {
 | 
				
			||||||
 | 
					            const new = TimerControl{ .raw = halfword };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // If Timer happens to be enabled, It will either be resheduled or disabled
 | 
				
			||||||
 | 
					            self.sched.removeScheduledEvent(.{ .TimerOverflow = id });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (self.cnt.enabled.read() and (new.cascade.read() or !new.enabled.read())) {
 | 
				
			||||||
 | 
					                // Either through the cascade bit or the enable bit, the timer has effectively been disabled
 | 
				
			||||||
 | 
					                // The Counter should hold whatever value it should have been at when it was disabled
 | 
				
			||||||
 | 
					                self._counter +%= @truncate(u16, (self.sched.now() - self._start_timestamp) / self.frequency());
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // The counter is only reloaded on the rising edge of the enable bit
 | 
				
			||||||
 | 
					            if (!self.cnt.enabled.read() and new.enabled.read()) self._counter = self._reload;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // If Timer is enabled and we're not cascading, we need to schedule an overflow event
 | 
				
			||||||
 | 
					            if (new.enabled.read() and !new.cascade.read()) self.rescheduleTimerExpire(0);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            self.cnt.raw = halfword;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        pub fn onTimerExpire(self: *Self, cpu: *Arm7tdmi, late: u64) void {
 | 
				
			||||||
 | 
					            // Fire IRQ if enabled
 | 
				
			||||||
 | 
					            const io = &cpu.bus.io;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (self.cnt.irq.read()) {
 | 
				
			||||||
 | 
					                switch (id) {
 | 
				
			||||||
 | 
					                    0 => io.irq.tim0.set(),
 | 
				
			||||||
 | 
					                    1 => io.irq.tim1.set(),
 | 
				
			||||||
 | 
					                    2 => io.irq.tim2.set(),
 | 
				
			||||||
 | 
					                    3 => io.irq.tim3.set(),
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                cpu.handleInterrupt();
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // DMA Sound Things
 | 
				
			||||||
 | 
					            if (id == 0 or id == 1) {
 | 
				
			||||||
 | 
					                cpu.bus.apu.onDmaAudioSampleRequest(cpu, id);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // Perform Cascade Behaviour
 | 
				
			||||||
 | 
					            switch (id) {
 | 
				
			||||||
 | 
					                0 => if (cpu.bus.tim[1].cnt.cascade.read()) {
 | 
				
			||||||
 | 
					                    cpu.bus.tim[1]._counter +%= 1;
 | 
				
			||||||
 | 
					                    if (cpu.bus.tim[1]._counter == 0) cpu.bus.tim[1].onTimerExpire(cpu, late);
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					                1 => if (cpu.bus.tim[2].cnt.cascade.read()) {
 | 
				
			||||||
 | 
					                    cpu.bus.tim[2]._counter +%= 1;
 | 
				
			||||||
 | 
					                    if (cpu.bus.tim[2]._counter == 0) cpu.bus.tim[2].onTimerExpire(cpu, late);
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					                2 => if (cpu.bus.tim[3].cnt.cascade.read()) {
 | 
				
			||||||
 | 
					                    cpu.bus.tim[3]._counter +%= 1;
 | 
				
			||||||
 | 
					                    if (cpu.bus.tim[3]._counter == 0) cpu.bus.tim[3].onTimerExpire(cpu, late);
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					                3 => {}, // There is no Timer for TIM3 to "cascade" to,
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // Reschedule Timer if we're not cascading
 | 
				
			||||||
 | 
					            if (!self.cnt.cascade.read()) {
 | 
				
			||||||
 | 
					                self._counter = self._reload;
 | 
				
			||||||
 | 
					                self.rescheduleTimerExpire(late);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        fn rescheduleTimerExpire(self: *Self, late: u64) void {
 | 
				
			||||||
 | 
					            const when = (@as(u64, 0x10000) - self._counter) * self.frequency();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            self._start_timestamp = self.sched.now();
 | 
				
			||||||
 | 
					            self.sched.push(.{ .TimerOverflow = id }, when -| late);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        fn frequency(self: *const Self) u16 {
 | 
				
			||||||
 | 
					            return switch (self.cnt.frequency.read()) {
 | 
				
			||||||
 | 
					                0 => 1,
 | 
				
			||||||
 | 
					                1 => 64,
 | 
				
			||||||
 | 
					                2 => 256,
 | 
				
			||||||
 | 
					                3 => 1024,
 | 
				
			||||||
 | 
					            };
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										727
									
								
								src/core/cpu.zig
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										727
									
								
								src/core/cpu.zig
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,727 @@
 | 
				
			|||||||
 | 
					const std = @import("std");
 | 
				
			||||||
 | 
					const util = @import("../util.zig");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const Bus = @import("Bus.zig");
 | 
				
			||||||
 | 
					const Bit = @import("bitfield").Bit;
 | 
				
			||||||
 | 
					const Bitfield = @import("bitfield").Bitfield;
 | 
				
			||||||
 | 
					const Scheduler = @import("scheduler.zig").Scheduler;
 | 
				
			||||||
 | 
					const FilePaths = @import("../util.zig").FilePaths;
 | 
				
			||||||
 | 
					const Logger = @import("../util.zig").Logger;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const File = std.fs.File;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// ARM Instructions
 | 
				
			||||||
 | 
					pub const arm = struct {
 | 
				
			||||||
 | 
					    pub const InstrFn = *const fn (*Arm7tdmi, *Bus, u32) void;
 | 
				
			||||||
 | 
					    const lut: [0x1000]InstrFn = populate();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const processing = @import("cpu/arm/data_processing.zig").dataProcessing;
 | 
				
			||||||
 | 
					    const psrTransfer = @import("cpu/arm/psr_transfer.zig").psrTransfer;
 | 
				
			||||||
 | 
					    const transfer = @import("cpu/arm/single_data_transfer.zig").singleDataTransfer;
 | 
				
			||||||
 | 
					    const halfSignedTransfer = @import("cpu/arm/half_signed_data_transfer.zig").halfAndSignedDataTransfer;
 | 
				
			||||||
 | 
					    const blockTransfer = @import("cpu/arm/block_data_transfer.zig").blockDataTransfer;
 | 
				
			||||||
 | 
					    const branch = @import("cpu/arm/branch.zig").branch;
 | 
				
			||||||
 | 
					    const branchExchange = @import("cpu/arm/branch.zig").branchAndExchange;
 | 
				
			||||||
 | 
					    const swi = @import("cpu/arm/software_interrupt.zig").armSoftwareInterrupt;
 | 
				
			||||||
 | 
					    const swap = @import("cpu/arm/single_data_swap.zig").singleDataSwap;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const multiply = @import("cpu/arm/multiply.zig").multiply;
 | 
				
			||||||
 | 
					    const multiplyLong = @import("cpu/arm/multiply.zig").multiplyLong;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /// Determine index into ARM InstrFn LUT
 | 
				
			||||||
 | 
					    fn idx(opcode: u32) u12 {
 | 
				
			||||||
 | 
					        return @truncate(u12, opcode >> 20 & 0xFF) << 4 | @truncate(u12, opcode >> 4 & 0xF);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Undefined ARM Instruction handler
 | 
				
			||||||
 | 
					    fn und(cpu: *Arm7tdmi, _: *Bus, opcode: u32) void {
 | 
				
			||||||
 | 
					        const id = idx(opcode);
 | 
				
			||||||
 | 
					        cpu.panic("[CPU/Decode] ID: 0x{X:0>3} 0x{X:0>8} is an illegal opcode", .{ id, opcode });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fn populate() [0x1000]InstrFn {
 | 
				
			||||||
 | 
					        return comptime {
 | 
				
			||||||
 | 
					            @setEvalBranchQuota(0xE000);
 | 
				
			||||||
 | 
					            var ret = [_]InstrFn{und} ** 0x1000;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            var i: usize = 0;
 | 
				
			||||||
 | 
					            while (i < ret.len) : (i += 1) {
 | 
				
			||||||
 | 
					                ret[i] = switch (@as(u2, i >> 10)) {
 | 
				
			||||||
 | 
					                    0b00 => if (i == 0x121) blk: {
 | 
				
			||||||
 | 
					                        break :blk branchExchange;
 | 
				
			||||||
 | 
					                    } else if (i & 0xFCF == 0x009) blk: {
 | 
				
			||||||
 | 
					                        const A = i >> 5 & 1 == 1;
 | 
				
			||||||
 | 
					                        const S = i >> 4 & 1 == 1;
 | 
				
			||||||
 | 
					                        break :blk multiply(A, S);
 | 
				
			||||||
 | 
					                    } else if (i & 0xFBF == 0x109) blk: {
 | 
				
			||||||
 | 
					                        const B = i >> 6 & 1 == 1;
 | 
				
			||||||
 | 
					                        break :blk swap(B);
 | 
				
			||||||
 | 
					                    } else if (i & 0xF8F == 0x089) blk: {
 | 
				
			||||||
 | 
					                        const U = i >> 6 & 1 == 1;
 | 
				
			||||||
 | 
					                        const A = i >> 5 & 1 == 1;
 | 
				
			||||||
 | 
					                        const S = i >> 4 & 1 == 1;
 | 
				
			||||||
 | 
					                        break :blk multiplyLong(U, A, S);
 | 
				
			||||||
 | 
					                    } else if (i & 0xE49 == 0x009 or i & 0xE49 == 0x049) blk: {
 | 
				
			||||||
 | 
					                        const P = i >> 8 & 1 == 1;
 | 
				
			||||||
 | 
					                        const U = i >> 7 & 1 == 1;
 | 
				
			||||||
 | 
					                        const I = i >> 6 & 1 == 1;
 | 
				
			||||||
 | 
					                        const W = i >> 5 & 1 == 1;
 | 
				
			||||||
 | 
					                        const L = i >> 4 & 1 == 1;
 | 
				
			||||||
 | 
					                        break :blk halfSignedTransfer(P, U, I, W, L);
 | 
				
			||||||
 | 
					                    } else if (i & 0xD90 == 0x100) blk: {
 | 
				
			||||||
 | 
					                        const I = i >> 9 & 1 == 1;
 | 
				
			||||||
 | 
					                        const R = i >> 6 & 1 == 1;
 | 
				
			||||||
 | 
					                        const kind = i >> 4 & 0x3;
 | 
				
			||||||
 | 
					                        break :blk psrTransfer(I, R, kind);
 | 
				
			||||||
 | 
					                    } else blk: {
 | 
				
			||||||
 | 
					                        const I = i >> 9 & 1 == 1;
 | 
				
			||||||
 | 
					                        const S = i >> 4 & 1 == 1;
 | 
				
			||||||
 | 
					                        const instrKind = i >> 5 & 0xF;
 | 
				
			||||||
 | 
					                        break :blk processing(I, S, instrKind);
 | 
				
			||||||
 | 
					                    },
 | 
				
			||||||
 | 
					                    0b01 => if (i >> 9 & 1 == 1 and i & 1 == 1) und else blk: {
 | 
				
			||||||
 | 
					                        const I = i >> 9 & 1 == 1;
 | 
				
			||||||
 | 
					                        const P = i >> 8 & 1 == 1;
 | 
				
			||||||
 | 
					                        const U = i >> 7 & 1 == 1;
 | 
				
			||||||
 | 
					                        const B = i >> 6 & 1 == 1;
 | 
				
			||||||
 | 
					                        const W = i >> 5 & 1 == 1;
 | 
				
			||||||
 | 
					                        const L = i >> 4 & 1 == 1;
 | 
				
			||||||
 | 
					                        break :blk transfer(I, P, U, B, W, L);
 | 
				
			||||||
 | 
					                    },
 | 
				
			||||||
 | 
					                    else => switch (@as(u2, i >> 9 & 0x3)) {
 | 
				
			||||||
 | 
					                        // MSB is guaranteed to be 1
 | 
				
			||||||
 | 
					                        0b00 => blk: {
 | 
				
			||||||
 | 
					                            const P = i >> 8 & 1 == 1;
 | 
				
			||||||
 | 
					                            const U = i >> 7 & 1 == 1;
 | 
				
			||||||
 | 
					                            const S = i >> 6 & 1 == 1;
 | 
				
			||||||
 | 
					                            const W = i >> 5 & 1 == 1;
 | 
				
			||||||
 | 
					                            const L = i >> 4 & 1 == 1;
 | 
				
			||||||
 | 
					                            break :blk blockTransfer(P, U, S, W, L);
 | 
				
			||||||
 | 
					                        },
 | 
				
			||||||
 | 
					                        0b01 => blk: {
 | 
				
			||||||
 | 
					                            const L = i >> 8 & 1 == 1;
 | 
				
			||||||
 | 
					                            break :blk branch(L);
 | 
				
			||||||
 | 
					                        },
 | 
				
			||||||
 | 
					                        0b10 => und, // COP Data Transfer
 | 
				
			||||||
 | 
					                        0b11 => if (i >> 8 & 1 == 1) swi() else und, // COP Data Operation + Register Transfer
 | 
				
			||||||
 | 
					                    },
 | 
				
			||||||
 | 
					                };
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            return ret;
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// THUMB Instructions
 | 
				
			||||||
 | 
					pub const thumb = struct {
 | 
				
			||||||
 | 
					    pub const InstrFn = *const fn (*Arm7tdmi, *Bus, u16) void;
 | 
				
			||||||
 | 
					    const lut: [0x400]InstrFn = populate();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const processing = @import("cpu/thumb/data_processing.zig");
 | 
				
			||||||
 | 
					    const alu = @import("cpu/thumb/alu.zig").fmt4;
 | 
				
			||||||
 | 
					    const transfer = @import("cpu/thumb/data_transfer.zig");
 | 
				
			||||||
 | 
					    const block_transfer = @import("cpu/thumb/block_data_transfer.zig");
 | 
				
			||||||
 | 
					    const swi = @import("cpu/thumb/software_interrupt.zig").fmt17;
 | 
				
			||||||
 | 
					    const branch = @import("cpu/thumb/branch.zig");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /// Determine index into THUMB InstrFn LUT
 | 
				
			||||||
 | 
					    fn idx(opcode: u16) u10 {
 | 
				
			||||||
 | 
					        return @truncate(u10, opcode >> 6);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /// Undefined THUMB Instruction Handler
 | 
				
			||||||
 | 
					    fn und(cpu: *Arm7tdmi, _: *Bus, opcode: u16) void {
 | 
				
			||||||
 | 
					        const id = idx(opcode);
 | 
				
			||||||
 | 
					        cpu.panic("[CPU/Decode] ID: 0b{b:0>10} 0x{X:0>2} is an illegal opcode", .{ id, opcode });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fn populate() [0x400]InstrFn {
 | 
				
			||||||
 | 
					        return comptime {
 | 
				
			||||||
 | 
					            @setEvalBranchQuota(5025); // This is exact
 | 
				
			||||||
 | 
					            var ret = [_]InstrFn{und} ** 0x400;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            var i: usize = 0;
 | 
				
			||||||
 | 
					            while (i < ret.len) : (i += 1) {
 | 
				
			||||||
 | 
					                ret[i] = switch (@as(u3, i >> 7 & 0x7)) {
 | 
				
			||||||
 | 
					                    0b000 => if (i >> 5 & 0x3 == 0b11) blk: {
 | 
				
			||||||
 | 
					                        const I = i >> 4 & 1 == 1;
 | 
				
			||||||
 | 
					                        const is_sub = i >> 3 & 1 == 1;
 | 
				
			||||||
 | 
					                        const rn = i & 0x7;
 | 
				
			||||||
 | 
					                        break :blk processing.fmt2(I, is_sub, rn);
 | 
				
			||||||
 | 
					                    } else blk: {
 | 
				
			||||||
 | 
					                        const op = i >> 5 & 0x3;
 | 
				
			||||||
 | 
					                        const offset = i & 0x1F;
 | 
				
			||||||
 | 
					                        break :blk processing.fmt1(op, offset);
 | 
				
			||||||
 | 
					                    },
 | 
				
			||||||
 | 
					                    0b001 => blk: {
 | 
				
			||||||
 | 
					                        const op = i >> 5 & 0x3;
 | 
				
			||||||
 | 
					                        const rd = i >> 2 & 0x7;
 | 
				
			||||||
 | 
					                        break :blk processing.fmt3(op, rd);
 | 
				
			||||||
 | 
					                    },
 | 
				
			||||||
 | 
					                    0b010 => switch (@as(u2, i >> 5 & 0x3)) {
 | 
				
			||||||
 | 
					                        0b00 => if (i >> 4 & 1 == 1) blk: {
 | 
				
			||||||
 | 
					                            const op = i >> 2 & 0x3;
 | 
				
			||||||
 | 
					                            const h1 = i >> 1 & 1;
 | 
				
			||||||
 | 
					                            const h2 = i & 1;
 | 
				
			||||||
 | 
					                            break :blk processing.fmt5(op, h1, h2);
 | 
				
			||||||
 | 
					                        } else blk: {
 | 
				
			||||||
 | 
					                            const op = i & 0xF;
 | 
				
			||||||
 | 
					                            break :blk alu(op);
 | 
				
			||||||
 | 
					                        },
 | 
				
			||||||
 | 
					                        0b01 => blk: {
 | 
				
			||||||
 | 
					                            const rd = i >> 2 & 0x7;
 | 
				
			||||||
 | 
					                            break :blk transfer.fmt6(rd);
 | 
				
			||||||
 | 
					                        },
 | 
				
			||||||
 | 
					                        else => blk: {
 | 
				
			||||||
 | 
					                            const op = i >> 4 & 0x3;
 | 
				
			||||||
 | 
					                            const T = i >> 3 & 1 == 1;
 | 
				
			||||||
 | 
					                            break :blk transfer.fmt78(op, T);
 | 
				
			||||||
 | 
					                        },
 | 
				
			||||||
 | 
					                    },
 | 
				
			||||||
 | 
					                    0b011 => blk: {
 | 
				
			||||||
 | 
					                        const B = i >> 6 & 1 == 1;
 | 
				
			||||||
 | 
					                        const L = i >> 5 & 1 == 1;
 | 
				
			||||||
 | 
					                        const offset = i & 0x1F;
 | 
				
			||||||
 | 
					                        break :blk transfer.fmt9(B, L, offset);
 | 
				
			||||||
 | 
					                    },
 | 
				
			||||||
 | 
					                    else => switch (@as(u3, i >> 6 & 0x7)) {
 | 
				
			||||||
 | 
					                        // MSB is guaranteed to be 1
 | 
				
			||||||
 | 
					                        0b000 => blk: {
 | 
				
			||||||
 | 
					                            const L = i >> 5 & 1 == 1;
 | 
				
			||||||
 | 
					                            const offset = i & 0x1F;
 | 
				
			||||||
 | 
					                            break :blk transfer.fmt10(L, offset);
 | 
				
			||||||
 | 
					                        },
 | 
				
			||||||
 | 
					                        0b001 => blk: {
 | 
				
			||||||
 | 
					                            const L = i >> 5 & 1 == 1;
 | 
				
			||||||
 | 
					                            const rd = i >> 2 & 0x7;
 | 
				
			||||||
 | 
					                            break :blk transfer.fmt11(L, rd);
 | 
				
			||||||
 | 
					                        },
 | 
				
			||||||
 | 
					                        0b010 => blk: {
 | 
				
			||||||
 | 
					                            const isSP = i >> 5 & 1 == 1;
 | 
				
			||||||
 | 
					                            const rd = i >> 2 & 0x7;
 | 
				
			||||||
 | 
					                            break :blk processing.fmt12(isSP, rd);
 | 
				
			||||||
 | 
					                        },
 | 
				
			||||||
 | 
					                        0b011 => if (i >> 4 & 1 == 1) blk: {
 | 
				
			||||||
 | 
					                            const L = i >> 5 & 1 == 1;
 | 
				
			||||||
 | 
					                            const R = i >> 2 & 1 == 1;
 | 
				
			||||||
 | 
					                            break :blk block_transfer.fmt14(L, R);
 | 
				
			||||||
 | 
					                        } else blk: {
 | 
				
			||||||
 | 
					                            const S = i >> 1 & 1 == 1;
 | 
				
			||||||
 | 
					                            break :blk processing.fmt13(S);
 | 
				
			||||||
 | 
					                        },
 | 
				
			||||||
 | 
					                        0b100 => blk: {
 | 
				
			||||||
 | 
					                            const L = i >> 5 & 1 == 1;
 | 
				
			||||||
 | 
					                            const rb = i >> 2 & 0x7;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                            break :blk block_transfer.fmt15(L, rb);
 | 
				
			||||||
 | 
					                        },
 | 
				
			||||||
 | 
					                        0b101 => if (i >> 2 & 0xF == 0b1111) blk: {
 | 
				
			||||||
 | 
					                            break :blk thumb.swi();
 | 
				
			||||||
 | 
					                        } else blk: {
 | 
				
			||||||
 | 
					                            const cond = i >> 2 & 0xF;
 | 
				
			||||||
 | 
					                            break :blk branch.fmt16(cond);
 | 
				
			||||||
 | 
					                        },
 | 
				
			||||||
 | 
					                        0b110 => branch.fmt18(),
 | 
				
			||||||
 | 
					                        0b111 => blk: {
 | 
				
			||||||
 | 
					                            const is_low = i >> 5 & 1 == 1;
 | 
				
			||||||
 | 
					                            break :blk branch.fmt19(is_low);
 | 
				
			||||||
 | 
					                        },
 | 
				
			||||||
 | 
					                    },
 | 
				
			||||||
 | 
					                };
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            return ret;
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const log = std.log.scoped(.Arm7Tdmi);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub const Arm7tdmi = struct {
 | 
				
			||||||
 | 
					    const Self = @This();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    r: [16]u32,
 | 
				
			||||||
 | 
					    pipe: Pipeline,
 | 
				
			||||||
 | 
					    sched: *Scheduler,
 | 
				
			||||||
 | 
					    bus: *Bus,
 | 
				
			||||||
 | 
					    cpsr: PSR,
 | 
				
			||||||
 | 
					    spsr: PSR,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /// Storage  for R8_fiq -> R12_fiq and their normal counterparts
 | 
				
			||||||
 | 
					    /// e.g [r[0 + 8], fiq_r[0 + 8], r[1 + 8], fiq_r[1 + 8]...]
 | 
				
			||||||
 | 
					    banked_fiq: [2 * 5]u32,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /// Storage for r13_<mode>, r14_<mode>
 | 
				
			||||||
 | 
					    /// e.g. [r13, r14, r13_svc, r14_svc]
 | 
				
			||||||
 | 
					    banked_r: [2 * 6]u32,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    banked_spsr: [5]PSR,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    logger: ?Logger,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn init(sched: *Scheduler, bus: *Bus, log_file: ?std.fs.File) Self {
 | 
				
			||||||
 | 
					        return Self{
 | 
				
			||||||
 | 
					            .r = [_]u32{0x00} ** 16,
 | 
				
			||||||
 | 
					            .pipe = Pipeline.init(),
 | 
				
			||||||
 | 
					            .sched = sched,
 | 
				
			||||||
 | 
					            .bus = bus,
 | 
				
			||||||
 | 
					            .cpsr = .{ .raw = 0x0000_001F },
 | 
				
			||||||
 | 
					            .spsr = .{ .raw = 0x0000_0000 },
 | 
				
			||||||
 | 
					            .banked_fiq = [_]u32{0x00} ** 10,
 | 
				
			||||||
 | 
					            .banked_r = [_]u32{0x00} ** 12,
 | 
				
			||||||
 | 
					            .banked_spsr = [_]PSR{.{ .raw = 0x0000_0000 }} ** 5,
 | 
				
			||||||
 | 
					            .logger = if (log_file) |file| Logger.init(file) else null,
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    inline fn bankedIdx(mode: Mode, kind: BankedKind) usize {
 | 
				
			||||||
 | 
					        const idx: usize = switch (mode) {
 | 
				
			||||||
 | 
					            .User, .System => 0,
 | 
				
			||||||
 | 
					            .Supervisor => 1,
 | 
				
			||||||
 | 
					            .Abort => 2,
 | 
				
			||||||
 | 
					            .Undefined => 3,
 | 
				
			||||||
 | 
					            .Irq => 4,
 | 
				
			||||||
 | 
					            .Fiq => 5,
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return (idx * 2) + if (kind == .R14) @as(usize, 1) else 0;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    inline fn bankedSpsrIndex(mode: Mode) usize {
 | 
				
			||||||
 | 
					        return switch (mode) {
 | 
				
			||||||
 | 
					            .Supervisor => 0,
 | 
				
			||||||
 | 
					            .Abort => 1,
 | 
				
			||||||
 | 
					            .Undefined => 2,
 | 
				
			||||||
 | 
					            .Irq => 3,
 | 
				
			||||||
 | 
					            .Fiq => 4,
 | 
				
			||||||
 | 
					            else => std.debug.panic("[CPU/Mode] {} does not have a SPSR Register", .{mode}),
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    inline fn bankedFiqIdx(i: usize, mode: Mode) usize {
 | 
				
			||||||
 | 
					        return (i * 2) + if (mode == .Fiq) @as(usize, 1) else 0;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub inline fn hasSPSR(self: *const Self) bool {
 | 
				
			||||||
 | 
					        const mode = getModeChecked(self, self.cpsr.mode.read());
 | 
				
			||||||
 | 
					        return switch (mode) {
 | 
				
			||||||
 | 
					            .System, .User => false,
 | 
				
			||||||
 | 
					            else => true,
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub inline fn isPrivileged(self: *const Self) bool {
 | 
				
			||||||
 | 
					        const mode = getModeChecked(self, self.cpsr.mode.read());
 | 
				
			||||||
 | 
					        return switch (mode) {
 | 
				
			||||||
 | 
					            .User => false,
 | 
				
			||||||
 | 
					            else => true,
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub inline fn isHalted(self: *const Self) bool {
 | 
				
			||||||
 | 
					        return self.bus.io.haltcnt == .Halt;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn setCpsr(self: *Self, value: u32) void {
 | 
				
			||||||
 | 
					        if (value & 0x1F != self.cpsr.raw & 0x1F) self.changeModeFromIdx(@truncate(u5, value & 0x1F));
 | 
				
			||||||
 | 
					        self.cpsr.raw = value;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fn changeModeFromIdx(self: *Self, next: u5) void {
 | 
				
			||||||
 | 
					        self.changeMode(getModeChecked(self, next));
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn setUserModeRegister(self: *Self, idx: usize, value: u32) void {
 | 
				
			||||||
 | 
					        const current = getModeChecked(self, self.cpsr.mode.read());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        switch (idx) {
 | 
				
			||||||
 | 
					            8...12 => {
 | 
				
			||||||
 | 
					                if (current == .Fiq) {
 | 
				
			||||||
 | 
					                    self.banked_fiq[bankedFiqIdx(idx - 8, .User)] = value;
 | 
				
			||||||
 | 
					                } else self.r[idx] = value;
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            13, 14 => switch (current) {
 | 
				
			||||||
 | 
					                .User, .System => self.r[idx] = value,
 | 
				
			||||||
 | 
					                else => {
 | 
				
			||||||
 | 
					                    const kind = std.meta.intToEnum(BankedKind, idx - 13) catch unreachable;
 | 
				
			||||||
 | 
					                    self.banked_r[bankedIdx(.User, kind)] = value;
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            else => self.r[idx] = value, // R0 -> R7  and R15
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn getUserModeRegister(self: *Self, idx: usize) u32 {
 | 
				
			||||||
 | 
					        const current = getModeChecked(self, self.cpsr.mode.read());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return switch (idx) {
 | 
				
			||||||
 | 
					            8...12 => if (current == .Fiq) self.banked_fiq[bankedFiqIdx(idx - 8, .User)] else self.r[idx],
 | 
				
			||||||
 | 
					            13, 14 => switch (current) {
 | 
				
			||||||
 | 
					                .User, .System => self.r[idx],
 | 
				
			||||||
 | 
					                else => blk: {
 | 
				
			||||||
 | 
					                    const kind = std.meta.intToEnum(BankedKind, idx - 13) catch unreachable;
 | 
				
			||||||
 | 
					                    break :blk self.banked_r[bankedIdx(.User, kind)];
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            else => self.r[idx], // R0 -> R7  and R15
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn changeMode(self: *Self, next: Mode) void {
 | 
				
			||||||
 | 
					        const now = getModeChecked(self, self.cpsr.mode.read());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Bank R8 -> r12
 | 
				
			||||||
 | 
					        var i: usize = 0;
 | 
				
			||||||
 | 
					        while (i < 5) : (i += 1) {
 | 
				
			||||||
 | 
					            self.banked_fiq[bankedFiqIdx(i, now)] = self.r[8 + i];
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Bank r13, r14, SPSR
 | 
				
			||||||
 | 
					        switch (now) {
 | 
				
			||||||
 | 
					            .User, .System => {
 | 
				
			||||||
 | 
					                self.banked_r[bankedIdx(now, .R13)] = self.r[13];
 | 
				
			||||||
 | 
					                self.banked_r[bankedIdx(now, .R14)] = self.r[14];
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            else => {
 | 
				
			||||||
 | 
					                self.banked_r[bankedIdx(now, .R13)] = self.r[13];
 | 
				
			||||||
 | 
					                self.banked_r[bankedIdx(now, .R14)] = self.r[14];
 | 
				
			||||||
 | 
					                self.banked_spsr[bankedSpsrIndex(now)] = self.spsr;
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Grab R8 -> R12
 | 
				
			||||||
 | 
					        i = 0;
 | 
				
			||||||
 | 
					        while (i < 5) : (i += 1) {
 | 
				
			||||||
 | 
					            self.r[8 + i] = self.banked_fiq[bankedFiqIdx(i, next)];
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Grab r13, r14, SPSR
 | 
				
			||||||
 | 
					        switch (next) {
 | 
				
			||||||
 | 
					            .User, .System => {
 | 
				
			||||||
 | 
					                self.r[13] = self.banked_r[bankedIdx(next, .R13)];
 | 
				
			||||||
 | 
					                self.r[14] = self.banked_r[bankedIdx(next, .R14)];
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            else => {
 | 
				
			||||||
 | 
					                self.r[13] = self.banked_r[bankedIdx(next, .R13)];
 | 
				
			||||||
 | 
					                self.r[14] = self.banked_r[bankedIdx(next, .R14)];
 | 
				
			||||||
 | 
					                self.spsr = self.banked_spsr[bankedSpsrIndex(next)];
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.cpsr.mode.write(@enumToInt(next));
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /// Advances state so that the BIOS is skipped
 | 
				
			||||||
 | 
					    ///
 | 
				
			||||||
 | 
					    /// Note: This accesses the CPU's bus ptr so it only may be called
 | 
				
			||||||
 | 
					    /// once the Bus has been properly initialized
 | 
				
			||||||
 | 
					    ///
 | 
				
			||||||
 | 
					    /// TODO: Make above notice impossible to do in code
 | 
				
			||||||
 | 
					    pub fn fastBoot(self: *Self) void {
 | 
				
			||||||
 | 
					        self.r = std.mem.zeroes([16]u32);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // self.r[0] = 0x08000000;
 | 
				
			||||||
 | 
					        // self.r[1] = 0x000000EA;
 | 
				
			||||||
 | 
					        self.r[13] = 0x0300_7F00;
 | 
				
			||||||
 | 
					        self.r[15] = 0x0800_0000;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.banked_r[bankedIdx(.Irq, .R13)] = 0x0300_7FA0;
 | 
				
			||||||
 | 
					        self.banked_r[bankedIdx(.Supervisor, .R13)] = 0x0300_7FE0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // self.cpsr.raw = 0x6000001F;
 | 
				
			||||||
 | 
					        self.cpsr.raw = 0x0000_001F;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.bus.bios.addr_latch = 0x0000_00DC + 8;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn step(self: *Self) void {
 | 
				
			||||||
 | 
					        defer {
 | 
				
			||||||
 | 
					            if (!self.pipe.flushed) self.r[15] += if (self.cpsr.t.read()) 2 else @as(u32, 4);
 | 
				
			||||||
 | 
					            self.pipe.flushed = false;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (self.cpsr.t.read()) {
 | 
				
			||||||
 | 
					            const opcode = @truncate(u16, self.pipe.step(self, u16) orelse return);
 | 
				
			||||||
 | 
					            if (self.logger) |*trace| trace.mgbaLog(self, opcode);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            thumb.lut[thumb.idx(opcode)](self, self.bus, opcode);
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            const opcode = self.pipe.step(self, u32) orelse return;
 | 
				
			||||||
 | 
					            if (self.logger) |*trace| trace.mgbaLog(self, opcode);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (checkCond(self.cpsr, @truncate(u4, opcode >> 28))) {
 | 
				
			||||||
 | 
					                arm.lut[arm.idx(opcode)](self, self.bus, opcode);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn stepDmaTransfer(self: *Self) bool {
 | 
				
			||||||
 | 
					        const dma0 = &self.bus.dma[0];
 | 
				
			||||||
 | 
					        const dma1 = &self.bus.dma[1];
 | 
				
			||||||
 | 
					        const dma2 = &self.bus.dma[2];
 | 
				
			||||||
 | 
					        const dma3 = &self.bus.dma[3];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (dma0.in_progress) {
 | 
				
			||||||
 | 
					            dma0.step(self);
 | 
				
			||||||
 | 
					            return true;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (dma1.in_progress) {
 | 
				
			||||||
 | 
					            dma1.step(self);
 | 
				
			||||||
 | 
					            return true;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (dma2.in_progress) {
 | 
				
			||||||
 | 
					            dma2.step(self);
 | 
				
			||||||
 | 
					            return true;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (dma3.in_progress) {
 | 
				
			||||||
 | 
					            dma3.step(self);
 | 
				
			||||||
 | 
					            return true;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return false;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn handleInterrupt(self: *Self) void {
 | 
				
			||||||
 | 
					        const should_handle = self.bus.io.ie.raw & self.bus.io.irq.raw;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Return if IME is disabled, CPSR I is set or there is nothing to handle
 | 
				
			||||||
 | 
					        if (!self.bus.io.ime or self.cpsr.i.read() or should_handle == 0) return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // If Pipeline isn't full, we have a bug
 | 
				
			||||||
 | 
					        std.debug.assert(self.pipe.isFull());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // log.debug("Handling Interrupt!", .{});
 | 
				
			||||||
 | 
					        self.bus.io.haltcnt = .Execute;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // FIXME: This seems weird, but retAddr.gba suggests I need to make these changes
 | 
				
			||||||
 | 
					        const ret_addr = self.r[15] - if (self.cpsr.t.read()) 0 else @as(u32, 4);
 | 
				
			||||||
 | 
					        const new_spsr = self.cpsr.raw;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.changeMode(.Irq);
 | 
				
			||||||
 | 
					        self.cpsr.t.write(false);
 | 
				
			||||||
 | 
					        self.cpsr.i.write(true);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.r[14] = ret_addr;
 | 
				
			||||||
 | 
					        self.spsr.raw = new_spsr;
 | 
				
			||||||
 | 
					        self.r[15] = 0x0000_0018;
 | 
				
			||||||
 | 
					        self.pipe.reload(self);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    inline fn fetch(self: *Self, comptime T: type, address: u32) T {
 | 
				
			||||||
 | 
					        comptime std.debug.assert(T == u32 or T == u16); // Opcode may be 32-bit (ARM) or 16-bit (THUMB)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Bus.read will advance the scheduler. There are different timings for CPU fetches,
 | 
				
			||||||
 | 
					        // so we want to undo what Bus.read will apply. We can do this by caching the current tick
 | 
				
			||||||
 | 
					        // This is very dumb.
 | 
				
			||||||
 | 
					        //
 | 
				
			||||||
 | 
					        // FIXME: Please rework this
 | 
				
			||||||
 | 
					        const tick_cache = self.sched.tick;
 | 
				
			||||||
 | 
					        defer self.sched.tick = tick_cache + Bus.fetch_timings[@boolToInt(T == u32)][@truncate(u4, address >> 24)];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return self.bus.read(T, address);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn panic(self: *const Self, comptime format: []const u8, args: anytype) noreturn {
 | 
				
			||||||
 | 
					        var i: usize = 0;
 | 
				
			||||||
 | 
					        while (i < 16) : (i += 4) {
 | 
				
			||||||
 | 
					            const i_1 = i + 1;
 | 
				
			||||||
 | 
					            const i_2 = i + 2;
 | 
				
			||||||
 | 
					            const i_3 = i + 3;
 | 
				
			||||||
 | 
					            std.debug.print("R{}: 0x{X:0>8}\tR{}: 0x{X:0>8}\tR{}: 0x{X:0>8}\tR{}: 0x{X:0>8}\n", .{ i, self.r[i], i_1, self.r[i_1], i_2, self.r[i_2], i_3, self.r[i_3] });
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        std.debug.print("cpsr: 0x{X:0>8} ", .{self.cpsr.raw});
 | 
				
			||||||
 | 
					        prettyPrintPsr(&self.cpsr);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        std.debug.print("spsr: 0x{X:0>8} ", .{self.spsr.raw});
 | 
				
			||||||
 | 
					        prettyPrintPsr(&self.spsr);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        std.debug.print("pipeline: {??X:0>8}\n", .{self.pipe.stage});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (self.cpsr.t.read()) {
 | 
				
			||||||
 | 
					            const opcode = self.bus.dbgRead(u16, self.r[15] - 4);
 | 
				
			||||||
 | 
					            const id = thumb.idx(opcode);
 | 
				
			||||||
 | 
					            std.debug.print("opcode: ID: 0x{b:0>10} 0x{X:0>4}\n", .{ id, opcode });
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            const opcode = self.bus.dbgRead(u32, self.r[15] - 4);
 | 
				
			||||||
 | 
					            const id = arm.idx(opcode);
 | 
				
			||||||
 | 
					            std.debug.print("opcode: ID: 0x{X:0>3} 0x{X:0>8}\n", .{ id, opcode });
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        std.debug.print("tick: {}\n\n", .{self.sched.tick});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        std.debug.panic(format, args);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fn prettyPrintPsr(psr: *const PSR) void {
 | 
				
			||||||
 | 
					        std.debug.print("[", .{});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (psr.n.read()) std.debug.print("N", .{}) else std.debug.print("-", .{});
 | 
				
			||||||
 | 
					        if (psr.z.read()) std.debug.print("Z", .{}) else std.debug.print("-", .{});
 | 
				
			||||||
 | 
					        if (psr.c.read()) std.debug.print("C", .{}) else std.debug.print("-", .{});
 | 
				
			||||||
 | 
					        if (psr.v.read()) std.debug.print("V", .{}) else std.debug.print("-", .{});
 | 
				
			||||||
 | 
					        if (psr.i.read()) std.debug.print("I", .{}) else std.debug.print("-", .{});
 | 
				
			||||||
 | 
					        if (psr.f.read()) std.debug.print("F", .{}) else std.debug.print("-", .{});
 | 
				
			||||||
 | 
					        if (psr.t.read()) std.debug.print("T", .{}) else std.debug.print("-", .{});
 | 
				
			||||||
 | 
					        std.debug.print("|", .{});
 | 
				
			||||||
 | 
					        if (getMode(psr.mode.read())) |mode| std.debug.print("{s}", .{modeString(mode)}) else std.debug.print("---", .{});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        std.debug.print("]\n", .{});
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fn modeString(mode: Mode) []const u8 {
 | 
				
			||||||
 | 
					        return switch (mode) {
 | 
				
			||||||
 | 
					            .User => "usr",
 | 
				
			||||||
 | 
					            .Fiq => "fiq",
 | 
				
			||||||
 | 
					            .Irq => "irq",
 | 
				
			||||||
 | 
					            .Supervisor => "svc",
 | 
				
			||||||
 | 
					            .Abort => "abt",
 | 
				
			||||||
 | 
					            .Undefined => "und",
 | 
				
			||||||
 | 
					            .System => "sys",
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fn mgbaLog(self: *const Self, file: *const File, opcode: u32) !void {
 | 
				
			||||||
 | 
					        const thumb_fmt = "{X:0>8} {X:0>8} {X:0>8} {X:0>8} {X:0>8} {X:0>8} {X:0>8} {X:0>8} {X:0>8} {X:0>8} {X:0>8} {X:0>8} {X:0>8} {X:0>8} {X:0>8} {X:0>8} cpsr: {X:0>8} | {X:0>4}:\n";
 | 
				
			||||||
 | 
					        const arm_fmt = "{X:0>8} {X:0>8} {X:0>8} {X:0>8} {X:0>8} {X:0>8} {X:0>8} {X:0>8} {X:0>8} {X:0>8} {X:0>8} {X:0>8} {X:0>8} {X:0>8} {X:0>8} {X:0>8} cpsr: {X:0>8} | {X:0>8}:\n";
 | 
				
			||||||
 | 
					        var buf: [0x100]u8 = [_]u8{0x00} ** 0x100; // this is larger than it needs to be
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const r0 = self.r[0];
 | 
				
			||||||
 | 
					        const r1 = self.r[1];
 | 
				
			||||||
 | 
					        const r2 = self.r[2];
 | 
				
			||||||
 | 
					        const r3 = self.r[3];
 | 
				
			||||||
 | 
					        const r4 = self.r[4];
 | 
				
			||||||
 | 
					        const r5 = self.r[5];
 | 
				
			||||||
 | 
					        const r6 = self.r[6];
 | 
				
			||||||
 | 
					        const r7 = self.r[7];
 | 
				
			||||||
 | 
					        const r8 = self.r[8];
 | 
				
			||||||
 | 
					        const r9 = self.r[9];
 | 
				
			||||||
 | 
					        const r10 = self.r[10];
 | 
				
			||||||
 | 
					        const r11 = self.r[11];
 | 
				
			||||||
 | 
					        const r12 = self.r[12];
 | 
				
			||||||
 | 
					        const r13 = self.r[13];
 | 
				
			||||||
 | 
					        const r14 = self.r[14];
 | 
				
			||||||
 | 
					        const r15 = self.r[15] -| if (self.cpsr.t.read()) 2 else @as(u32, 4);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const c_psr = self.cpsr.raw;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        var log_str: []u8 = undefined;
 | 
				
			||||||
 | 
					        if (self.cpsr.t.read()) {
 | 
				
			||||||
 | 
					            if (opcode >> 11 == 0x1E) {
 | 
				
			||||||
 | 
					                // Instruction 1 of a BL Opcode, print in ARM mode
 | 
				
			||||||
 | 
					                const other_half = self.bus.debugRead(u16, self.r[15] - 2);
 | 
				
			||||||
 | 
					                const bl_opcode = @as(u32, opcode) << 16 | other_half;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                log_str = try std.fmt.bufPrint(&buf, arm_fmt, .{ r0, r1, r2, r3, r4, r5, r6, r7, r8, r9, r10, r11, r12, r13, r14, r15, c_psr, bl_opcode });
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					                log_str = try std.fmt.bufPrint(&buf, thumb_fmt, .{ r0, r1, r2, r3, r4, r5, r6, r7, r8, r9, r10, r11, r12, r13, r14, r15, c_psr, opcode });
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            log_str = try std.fmt.bufPrint(&buf, arm_fmt, .{ r0, r1, r2, r3, r4, r5, r6, r7, r8, r9, r10, r11, r12, r13, r14, r15, c_psr, opcode });
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        _ = try file.writeAll(log_str);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn checkCond(cpsr: PSR, cond: u4) bool {
 | 
				
			||||||
 | 
					    return switch (cond) {
 | 
				
			||||||
 | 
					        0x0 => cpsr.z.read(), // EQ - Equal
 | 
				
			||||||
 | 
					        0x1 => !cpsr.z.read(), // NE - Not equal
 | 
				
			||||||
 | 
					        0x2 => cpsr.c.read(), // CS - Unsigned higher or same
 | 
				
			||||||
 | 
					        0x3 => !cpsr.c.read(), // CC - Unsigned lower
 | 
				
			||||||
 | 
					        0x4 => cpsr.n.read(), // MI - Negative
 | 
				
			||||||
 | 
					        0x5 => !cpsr.n.read(), // PL - Positive or zero
 | 
				
			||||||
 | 
					        0x6 => cpsr.v.read(), // VS - Overflow
 | 
				
			||||||
 | 
					        0x7 => !cpsr.v.read(), // VC - No overflow
 | 
				
			||||||
 | 
					        0x8 => cpsr.c.read() and !cpsr.z.read(), // HI - unsigned higher
 | 
				
			||||||
 | 
					        0x9 => !cpsr.c.read() or cpsr.z.read(), // LS - unsigned lower or same
 | 
				
			||||||
 | 
					        0xA => cpsr.n.read() == cpsr.v.read(), // GE - Greater or equal
 | 
				
			||||||
 | 
					        0xB => cpsr.n.read() != cpsr.v.read(), // LT - Less than
 | 
				
			||||||
 | 
					        0xC => !cpsr.z.read() and (cpsr.n.read() == cpsr.v.read()), // GT - Greater than
 | 
				
			||||||
 | 
					        0xD => cpsr.z.read() or (cpsr.n.read() != cpsr.v.read()), // LE - Less than or equal
 | 
				
			||||||
 | 
					        0xE => true, // AL - Always
 | 
				
			||||||
 | 
					        0xF => false, // NV - Never (reserved in ARMv3 and up, but seems to have not changed?)
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const Pipeline = struct {
 | 
				
			||||||
 | 
					    const Self = @This();
 | 
				
			||||||
 | 
					    stage: [2]?u32,
 | 
				
			||||||
 | 
					    flushed: bool,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fn init() Self {
 | 
				
			||||||
 | 
					        return .{
 | 
				
			||||||
 | 
					            .stage = [_]?u32{null} ** 2,
 | 
				
			||||||
 | 
					            .flushed = false,
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn isFull(self: *const Self) bool {
 | 
				
			||||||
 | 
					        return self.stage[0] != null and self.stage[1] != null;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn step(self: *Self, cpu: *Arm7tdmi, comptime T: type) ?u32 {
 | 
				
			||||||
 | 
					        comptime std.debug.assert(T == u32 or T == u16);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // FIXME: https://github.com/ziglang/zig/issues/12642
 | 
				
			||||||
 | 
					        var opcode = self.stage[0];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.stage[0] = self.stage[1];
 | 
				
			||||||
 | 
					        self.stage[1] = cpu.fetch(T, cpu.r[15]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return opcode;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn reload(self: *Self, cpu: *Arm7tdmi) void {
 | 
				
			||||||
 | 
					        if (cpu.cpsr.t.read()) {
 | 
				
			||||||
 | 
					            self.stage[0] = cpu.fetch(u16, cpu.r[15]);
 | 
				
			||||||
 | 
					            self.stage[1] = cpu.fetch(u16, cpu.r[15] + 2);
 | 
				
			||||||
 | 
					            cpu.r[15] += 4;
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            self.stage[0] = cpu.fetch(u32, cpu.r[15]);
 | 
				
			||||||
 | 
					            self.stage[1] = cpu.fetch(u32, cpu.r[15] + 4);
 | 
				
			||||||
 | 
					            cpu.r[15] += 8;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.flushed = true;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub const PSR = extern union {
 | 
				
			||||||
 | 
					    mode: Bitfield(u32, 0, 5),
 | 
				
			||||||
 | 
					    t: Bit(u32, 5),
 | 
				
			||||||
 | 
					    f: Bit(u32, 6),
 | 
				
			||||||
 | 
					    i: Bit(u32, 7),
 | 
				
			||||||
 | 
					    v: Bit(u32, 28),
 | 
				
			||||||
 | 
					    c: Bit(u32, 29),
 | 
				
			||||||
 | 
					    z: Bit(u32, 30),
 | 
				
			||||||
 | 
					    n: Bit(u32, 31),
 | 
				
			||||||
 | 
					    raw: u32,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const Mode = enum(u5) {
 | 
				
			||||||
 | 
					    User = 0b10000,
 | 
				
			||||||
 | 
					    Fiq = 0b10001,
 | 
				
			||||||
 | 
					    Irq = 0b10010,
 | 
				
			||||||
 | 
					    Supervisor = 0b10011,
 | 
				
			||||||
 | 
					    Abort = 0b10111,
 | 
				
			||||||
 | 
					    Undefined = 0b11011,
 | 
				
			||||||
 | 
					    System = 0b11111,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const BankedKind = enum(u1) {
 | 
				
			||||||
 | 
					    R13 = 0,
 | 
				
			||||||
 | 
					    R14,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fn getMode(bits: u5) ?Mode {
 | 
				
			||||||
 | 
					    return std.meta.intToEnum(Mode, bits) catch null;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fn getModeChecked(cpu: *const Arm7tdmi, bits: u5) Mode {
 | 
				
			||||||
 | 
					    return getMode(bits) orelse cpu.panic("[CPU/CPSR] 0b{b:0>5} is an invalid CPU mode", .{bits});
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										112
									
								
								src/core/cpu/arm/block_data_transfer.zig
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										112
									
								
								src/core/cpu/arm/block_data_transfer.zig
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,112 @@
 | 
				
			|||||||
 | 
					const Bus = @import("../../Bus.zig");
 | 
				
			||||||
 | 
					const Arm7tdmi = @import("../../cpu.zig").Arm7tdmi;
 | 
				
			||||||
 | 
					const InstrFn = @import("../../cpu.zig").arm.InstrFn;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn blockDataTransfer(comptime P: bool, comptime U: bool, comptime S: bool, comptime W: bool, comptime L: bool) InstrFn {
 | 
				
			||||||
 | 
					    return struct {
 | 
				
			||||||
 | 
					        fn inner(cpu: *Arm7tdmi, bus: *Bus, opcode: u32) void {
 | 
				
			||||||
 | 
					            const rn = @truncate(u4, opcode >> 16 & 0xF);
 | 
				
			||||||
 | 
					            const rlist = opcode & 0xFFFF;
 | 
				
			||||||
 | 
					            const r15 = rlist >> 15 & 1 == 1;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            var count: u32 = 0;
 | 
				
			||||||
 | 
					            var i: u5 = 0;
 | 
				
			||||||
 | 
					            var first: u4 = 0;
 | 
				
			||||||
 | 
					            var write_to_base = true;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            while (i < 16) : (i += 1) {
 | 
				
			||||||
 | 
					                const r = @truncate(u4, 15 - i);
 | 
				
			||||||
 | 
					                if (rlist >> r & 1 == 1) {
 | 
				
			||||||
 | 
					                    first = r;
 | 
				
			||||||
 | 
					                    count += 1;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            var start = cpu.r[rn];
 | 
				
			||||||
 | 
					            if (U) {
 | 
				
			||||||
 | 
					                start += if (P) 4 else 0;
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					                start = start - (4 * count) + if (!P) 4 else 0;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            var end = cpu.r[rn];
 | 
				
			||||||
 | 
					            if (U) {
 | 
				
			||||||
 | 
					                end = end + (4 * count) - if (!P) 4 else 0;
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					                end -= if (P) 4 else 0;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            var new_base = cpu.r[rn];
 | 
				
			||||||
 | 
					            if (U) {
 | 
				
			||||||
 | 
					                new_base += 4 * count;
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					                new_base -= 4 * count;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            var address = start;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (rlist == 0) {
 | 
				
			||||||
 | 
					                var und_addr = cpu.r[rn];
 | 
				
			||||||
 | 
					                if (U) {
 | 
				
			||||||
 | 
					                    und_addr += if (P) 4 else 0;
 | 
				
			||||||
 | 
					                } else {
 | 
				
			||||||
 | 
					                    und_addr -= 0x40 - if (!P) 4 else 0;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                if (L) {
 | 
				
			||||||
 | 
					                    cpu.r[15] = bus.read(u32, und_addr);
 | 
				
			||||||
 | 
					                    cpu.pipe.reload(cpu);
 | 
				
			||||||
 | 
					                } else {
 | 
				
			||||||
 | 
					                    // FIXME: Should r15 on write be +12 ahead?
 | 
				
			||||||
 | 
					                    bus.write(u32, und_addr, cpu.r[15] + 4);
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                cpu.r[rn] = if (U) cpu.r[rn] + 0x40 else cpu.r[rn] - 0x40;
 | 
				
			||||||
 | 
					                return;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            i = first;
 | 
				
			||||||
 | 
					            while (i < 16) : (i += 1) {
 | 
				
			||||||
 | 
					                if (rlist >> i & 1 == 1) {
 | 
				
			||||||
 | 
					                    transfer(cpu, bus, r15, i, address);
 | 
				
			||||||
 | 
					                    address += 4;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    if (W and !L and write_to_base) {
 | 
				
			||||||
 | 
					                        cpu.r[rn] = new_base;
 | 
				
			||||||
 | 
					                        write_to_base = false;
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (W and L and rlist >> rn & 1 == 0) cpu.r[rn] = new_base;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        fn transfer(cpu: *Arm7tdmi, bus: *Bus, r15_present: bool, i: u5, address: u32) void {
 | 
				
			||||||
 | 
					            if (L) {
 | 
				
			||||||
 | 
					                if (S and !r15_present) {
 | 
				
			||||||
 | 
					                    // Always Transfer User mode Registers
 | 
				
			||||||
 | 
					                    cpu.setUserModeRegister(i, bus.read(u32, address));
 | 
				
			||||||
 | 
					                } else {
 | 
				
			||||||
 | 
					                    const value = bus.read(u32, address);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    cpu.r[i] = value;
 | 
				
			||||||
 | 
					                    if (i == 0xF) {
 | 
				
			||||||
 | 
					                        cpu.r[i] &= ~@as(u32, 3); // Align r15
 | 
				
			||||||
 | 
					                        cpu.pipe.reload(cpu);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                        if (S) cpu.setCpsr(cpu.spsr.raw);
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					                if (S) {
 | 
				
			||||||
 | 
					                    // Always Transfer User mode Registers
 | 
				
			||||||
 | 
					                    // This happens regardless if r15 is in the list
 | 
				
			||||||
 | 
					                    const value = cpu.getUserModeRegister(i);
 | 
				
			||||||
 | 
					                    bus.write(u32, address, value + if (i == 0xF) 4 else @as(u32, 0)); // PC is already 8 ahead to make 12
 | 
				
			||||||
 | 
					                } else {
 | 
				
			||||||
 | 
					                    bus.write(u32, address, cpu.r[i] + if (i == 0xF) 4 else @as(u32, 0));
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }.inner;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										28
									
								
								src/core/cpu/arm/branch.zig
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								src/core/cpu/arm/branch.zig
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,28 @@
 | 
				
			|||||||
 | 
					const std = @import("std");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const Bus = @import("../../Bus.zig");
 | 
				
			||||||
 | 
					const Arm7tdmi = @import("../../cpu.zig").Arm7tdmi;
 | 
				
			||||||
 | 
					const InstrFn = @import("../../cpu.zig").arm.InstrFn;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const sext = @import("../../../util.zig").sext;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn branch(comptime L: bool) InstrFn {
 | 
				
			||||||
 | 
					    return struct {
 | 
				
			||||||
 | 
					        fn inner(cpu: *Arm7tdmi, _: *Bus, opcode: u32) void {
 | 
				
			||||||
 | 
					            if (L) cpu.r[14] = cpu.r[15] - 4;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            cpu.r[15] +%= sext(u32, u24, opcode) << 2;
 | 
				
			||||||
 | 
					            cpu.pipe.reload(cpu);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }.inner;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn branchAndExchange(cpu: *Arm7tdmi, _: *Bus, opcode: u32) void {
 | 
				
			||||||
 | 
					    const rn = opcode & 0xF;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const thumb = cpu.r[rn] & 1 == 1;
 | 
				
			||||||
 | 
					    cpu.r[15] = cpu.r[rn] & if (thumb) ~@as(u32, 1) else ~@as(u32, 3);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    cpu.cpsr.t.write(thumb);
 | 
				
			||||||
 | 
					    cpu.pipe.reload(cpu);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										183
									
								
								src/core/cpu/arm/data_processing.zig
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										183
									
								
								src/core/cpu/arm/data_processing.zig
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,183 @@
 | 
				
			|||||||
 | 
					const Bus = @import("../../Bus.zig");
 | 
				
			||||||
 | 
					const Arm7tdmi = @import("../../cpu.zig").Arm7tdmi;
 | 
				
			||||||
 | 
					const InstrFn = @import("../../cpu.zig").arm.InstrFn;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const exec = @import("../barrel_shifter.zig").exec;
 | 
				
			||||||
 | 
					const ror = @import("../barrel_shifter.zig").ror;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn dataProcessing(comptime I: bool, comptime S: bool, comptime kind: u4) InstrFn {
 | 
				
			||||||
 | 
					    return struct {
 | 
				
			||||||
 | 
					        fn inner(cpu: *Arm7tdmi, _: *Bus, opcode: u32) void {
 | 
				
			||||||
 | 
					            const rd = @truncate(u4, opcode >> 12 & 0xF);
 | 
				
			||||||
 | 
					            const rn = opcode >> 16 & 0xF;
 | 
				
			||||||
 | 
					            const old_carry = @boolToInt(cpu.cpsr.c.read());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // If certain conditions are met, PC is 12 ahead instead of 8
 | 
				
			||||||
 | 
					            // TODO: Why these conditions?
 | 
				
			||||||
 | 
					            if (!I and opcode >> 4 & 1 == 1) cpu.r[15] += 4;
 | 
				
			||||||
 | 
					            const op1 = cpu.r[rn];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            const amount = @truncate(u8, (opcode >> 8 & 0xF) << 1);
 | 
				
			||||||
 | 
					            const op2 = if (I) ror(S, &cpu.cpsr, opcode & 0xFF, amount) else exec(S, cpu, opcode);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // Undo special condition from above
 | 
				
			||||||
 | 
					            if (!I and opcode >> 4 & 1 == 1) cpu.r[15] -= 4;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            var result: u32 = undefined;
 | 
				
			||||||
 | 
					            var overflow: bool = undefined;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // Perform Data Processing Logic
 | 
				
			||||||
 | 
					            switch (kind) {
 | 
				
			||||||
 | 
					                0x0 => result = op1 & op2, // AND
 | 
				
			||||||
 | 
					                0x1 => result = op1 ^ op2, // EOR
 | 
				
			||||||
 | 
					                0x2 => result = op1 -% op2, // SUB
 | 
				
			||||||
 | 
					                0x3 => result = op2 -% op1, // RSB
 | 
				
			||||||
 | 
					                0x4 => result = add(&overflow, op1, op2), // ADD
 | 
				
			||||||
 | 
					                0x5 => result = adc(&overflow, op1, op2, old_carry), // ADC
 | 
				
			||||||
 | 
					                0x6 => result = sbc(op1, op2, old_carry), // SBC
 | 
				
			||||||
 | 
					                0x7 => result = sbc(op2, op1, old_carry), // RSC
 | 
				
			||||||
 | 
					                0x8 => {
 | 
				
			||||||
 | 
					                    // TST
 | 
				
			||||||
 | 
					                    if (rd == 0xF)
 | 
				
			||||||
 | 
					                        return undefinedTestBehaviour(cpu);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    result = op1 & op2;
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					                0x9 => {
 | 
				
			||||||
 | 
					                    // TEQ
 | 
				
			||||||
 | 
					                    if (rd == 0xF)
 | 
				
			||||||
 | 
					                        return undefinedTestBehaviour(cpu);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    result = op1 ^ op2;
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					                0xA => {
 | 
				
			||||||
 | 
					                    // CMP
 | 
				
			||||||
 | 
					                    if (rd == 0xF)
 | 
				
			||||||
 | 
					                        return undefinedTestBehaviour(cpu);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    result = op1 -% op2;
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					                0xB => {
 | 
				
			||||||
 | 
					                    // CMN
 | 
				
			||||||
 | 
					                    if (rd == 0xF)
 | 
				
			||||||
 | 
					                        return undefinedTestBehaviour(cpu);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    overflow = @addWithOverflow(u32, op1, op2, &result);
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					                0xC => result = op1 | op2, // ORR
 | 
				
			||||||
 | 
					                0xD => result = op2, // MOV
 | 
				
			||||||
 | 
					                0xE => result = op1 & ~op2, // BIC
 | 
				
			||||||
 | 
					                0xF => result = ~op2, // MVN
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // Write to Destination Register
 | 
				
			||||||
 | 
					            switch (kind) {
 | 
				
			||||||
 | 
					                0x8, 0x9, 0xA, 0xB => {}, // Test Operations
 | 
				
			||||||
 | 
					                else => {
 | 
				
			||||||
 | 
					                    cpu.r[rd] = result;
 | 
				
			||||||
 | 
					                    if (rd == 0xF) {
 | 
				
			||||||
 | 
					                        if (S) cpu.setCpsr(cpu.spsr.raw);
 | 
				
			||||||
 | 
					                        cpu.pipe.reload(cpu);
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // Write Flags
 | 
				
			||||||
 | 
					            switch (kind) {
 | 
				
			||||||
 | 
					                0x0, 0x1, 0xC, 0xD, 0xE, 0xF => if (S and rd != 0xF) {
 | 
				
			||||||
 | 
					                    // Logic Operation Flags
 | 
				
			||||||
 | 
					                    cpu.cpsr.n.write(result >> 31 & 1 == 1);
 | 
				
			||||||
 | 
					                    cpu.cpsr.z.write(result == 0);
 | 
				
			||||||
 | 
					                    // C set by Barrel Shifter, V is unaffected
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					                0x2, 0x3 => if (S and rd != 0xF) {
 | 
				
			||||||
 | 
					                    // SUB, RSB Flags
 | 
				
			||||||
 | 
					                    cpu.cpsr.n.write(result >> 31 & 1 == 1);
 | 
				
			||||||
 | 
					                    cpu.cpsr.z.write(result == 0);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    if (kind == 0x2) {
 | 
				
			||||||
 | 
					                        // SUB specific
 | 
				
			||||||
 | 
					                        cpu.cpsr.c.write(op2 <= op1);
 | 
				
			||||||
 | 
					                        cpu.cpsr.v.write(((op1 ^ result) & (~op2 ^ result)) >> 31 & 1 == 1);
 | 
				
			||||||
 | 
					                    } else {
 | 
				
			||||||
 | 
					                        // RSB Specific
 | 
				
			||||||
 | 
					                        cpu.cpsr.c.write(op1 <= op2);
 | 
				
			||||||
 | 
					                        cpu.cpsr.v.write(((op2 ^ result) & (~op1 ^ result)) >> 31 & 1 == 1);
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					                0x4, 0x5 => if (S and rd != 0xF) {
 | 
				
			||||||
 | 
					                    // ADD, ADC Flags
 | 
				
			||||||
 | 
					                    cpu.cpsr.n.write(result >> 31 & 1 == 1);
 | 
				
			||||||
 | 
					                    cpu.cpsr.z.write(result == 0);
 | 
				
			||||||
 | 
					                    cpu.cpsr.c.write(overflow);
 | 
				
			||||||
 | 
					                    cpu.cpsr.v.write(((op1 ^ result) & (op2 ^ result)) >> 31 & 1 == 1);
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					                0x6, 0x7 => if (S and rd != 0xF) {
 | 
				
			||||||
 | 
					                    // SBC, RSC Flags
 | 
				
			||||||
 | 
					                    cpu.cpsr.n.write(result >> 31 & 1 == 1);
 | 
				
			||||||
 | 
					                    cpu.cpsr.z.write(result == 0);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    if (kind == 0x6) {
 | 
				
			||||||
 | 
					                        // SBC specific
 | 
				
			||||||
 | 
					                        const subtrahend = @as(u64, op2) -% old_carry +% 1;
 | 
				
			||||||
 | 
					                        cpu.cpsr.c.write(subtrahend <= op1);
 | 
				
			||||||
 | 
					                        cpu.cpsr.v.write(((op1 ^ result) & (~op2 ^ result)) >> 31 & 1 == 1);
 | 
				
			||||||
 | 
					                    } else {
 | 
				
			||||||
 | 
					                        // RSC Specific
 | 
				
			||||||
 | 
					                        const subtrahend = @as(u64, op1) -% old_carry +% 1;
 | 
				
			||||||
 | 
					                        cpu.cpsr.c.write(subtrahend <= op2);
 | 
				
			||||||
 | 
					                        cpu.cpsr.v.write(((op2 ^ result) & (~op1 ^ result)) >> 31 & 1 == 1);
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					                0x8, 0x9, 0xA, 0xB => {
 | 
				
			||||||
 | 
					                    // Test Operation Flags
 | 
				
			||||||
 | 
					                    cpu.cpsr.n.write(result >> 31 & 1 == 1);
 | 
				
			||||||
 | 
					                    cpu.cpsr.z.write(result == 0);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    if (kind == 0xA) {
 | 
				
			||||||
 | 
					                        // CMP specific
 | 
				
			||||||
 | 
					                        cpu.cpsr.c.write(op2 <= op1);
 | 
				
			||||||
 | 
					                        cpu.cpsr.v.write(((op1 ^ result) & (~op2 ^ result)) >> 31 & 1 == 1);
 | 
				
			||||||
 | 
					                    } else if (kind == 0xB) {
 | 
				
			||||||
 | 
					                        // CMN specific
 | 
				
			||||||
 | 
					                        cpu.cpsr.c.write(overflow);
 | 
				
			||||||
 | 
					                        cpu.cpsr.v.write(((op1 ^ result) & (op2 ^ result)) >> 31 & 1 == 1);
 | 
				
			||||||
 | 
					                    } else {
 | 
				
			||||||
 | 
					                        // TST, TEQ specific
 | 
				
			||||||
 | 
					                        // Barrel Shifter should always calc CPSR C in TST
 | 
				
			||||||
 | 
					                        if (!S) _ = exec(true, cpu, opcode);
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }.inner;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn sbc(left: u32, right: u32, old_carry: u1) u32 {
 | 
				
			||||||
 | 
					    // TODO: Make your own version (thanks peach.bot)
 | 
				
			||||||
 | 
					    const subtrahend = @as(u64, right) -% old_carry +% 1;
 | 
				
			||||||
 | 
					    const ret = @truncate(u32, left -% subtrahend);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return ret;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn add(overflow: *bool, left: u32, right: u32) u32 {
 | 
				
			||||||
 | 
					    var ret: u32 = undefined;
 | 
				
			||||||
 | 
					    overflow.* = @addWithOverflow(u32, left, right, &ret);
 | 
				
			||||||
 | 
					    return ret;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn adc(overflow: *bool, left: u32, right: u32, old_carry: u1) u32 {
 | 
				
			||||||
 | 
					    var ret: u32 = undefined;
 | 
				
			||||||
 | 
					    const first = @addWithOverflow(u32, left, right, &ret);
 | 
				
			||||||
 | 
					    const second = @addWithOverflow(u32, ret, old_carry, &ret);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    overflow.* = first or second;
 | 
				
			||||||
 | 
					    return ret;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fn undefinedTestBehaviour(cpu: *Arm7tdmi) void {
 | 
				
			||||||
 | 
					    @setCold(true);
 | 
				
			||||||
 | 
					    cpu.setCpsr(cpu.spsr.raw);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										58
									
								
								src/core/cpu/arm/half_signed_data_transfer.zig
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										58
									
								
								src/core/cpu/arm/half_signed_data_transfer.zig
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,58 @@
 | 
				
			|||||||
 | 
					const std = @import("std");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const Bus = @import("../../Bus.zig");
 | 
				
			||||||
 | 
					const Arm7tdmi = @import("../../cpu.zig").Arm7tdmi;
 | 
				
			||||||
 | 
					const InstrFn = @import("../../cpu.zig").arm.InstrFn;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const sext = @import("../../../util.zig").sext;
 | 
				
			||||||
 | 
					const rotr = @import("../../../util.zig").rotr;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn halfAndSignedDataTransfer(comptime P: bool, comptime U: bool, comptime I: bool, comptime W: bool, comptime L: bool) InstrFn {
 | 
				
			||||||
 | 
					    return struct {
 | 
				
			||||||
 | 
					        fn inner(cpu: *Arm7tdmi, bus: *Bus, opcode: u32) void {
 | 
				
			||||||
 | 
					            const rn = opcode >> 16 & 0xF;
 | 
				
			||||||
 | 
					            const rd = opcode >> 12 & 0xF;
 | 
				
			||||||
 | 
					            const rm = opcode & 0xF;
 | 
				
			||||||
 | 
					            const imm_offset_high = opcode >> 8 & 0xF;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            const base = cpu.r[rn] + if (!L and rn == 0xF) 4 else @as(u32, 0);
 | 
				
			||||||
 | 
					            const offset = if (I) imm_offset_high << 4 | rm else cpu.r[rm];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            const modified_base = if (U) base +% offset else base -% offset;
 | 
				
			||||||
 | 
					            var address = if (P) modified_base else base;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            var result: u32 = undefined;
 | 
				
			||||||
 | 
					            if (L) {
 | 
				
			||||||
 | 
					                switch (@truncate(u2, opcode >> 5)) {
 | 
				
			||||||
 | 
					                    0b01 => {
 | 
				
			||||||
 | 
					                        // LDRH
 | 
				
			||||||
 | 
					                        const value = bus.read(u16, address);
 | 
				
			||||||
 | 
					                        result = rotr(u32, value, 8 * (address & 1));
 | 
				
			||||||
 | 
					                    },
 | 
				
			||||||
 | 
					                    0b10 => {
 | 
				
			||||||
 | 
					                        // LDRSB
 | 
				
			||||||
 | 
					                        result = sext(u32, u8, bus.read(u8, address));
 | 
				
			||||||
 | 
					                    },
 | 
				
			||||||
 | 
					                    0b11 => {
 | 
				
			||||||
 | 
					                        // LDRSH
 | 
				
			||||||
 | 
					                        result = if (address & 1 == 1) blk: {
 | 
				
			||||||
 | 
					                            break :blk sext(u32, u8, bus.read(u8, address));
 | 
				
			||||||
 | 
					                        } else blk: {
 | 
				
			||||||
 | 
					                            break :blk sext(u32, u16, bus.read(u16, address));
 | 
				
			||||||
 | 
					                        };
 | 
				
			||||||
 | 
					                    },
 | 
				
			||||||
 | 
					                    0b00 => unreachable, // SWP
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					                if (opcode >> 5 & 0x01 == 0x01) {
 | 
				
			||||||
 | 
					                    // STRH
 | 
				
			||||||
 | 
					                    bus.write(u16, address, @truncate(u16, cpu.r[rd]));
 | 
				
			||||||
 | 
					                } else unreachable; // SWP
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            address = modified_base;
 | 
				
			||||||
 | 
					            if (W and P or !P) cpu.r[rn] = address;
 | 
				
			||||||
 | 
					            if (L) cpu.r[rd] = result; // // This emulates the LDR rd == rn behaviour
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }.inner;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										57
									
								
								src/core/cpu/arm/multiply.zig
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										57
									
								
								src/core/cpu/arm/multiply.zig
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,57 @@
 | 
				
			|||||||
 | 
					const Bus = @import("../../Bus.zig");
 | 
				
			||||||
 | 
					const Arm7tdmi = @import("../../cpu.zig").Arm7tdmi;
 | 
				
			||||||
 | 
					const InstrFn = @import("../../cpu.zig").arm.InstrFn;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn multiply(comptime A: bool, comptime S: bool) InstrFn {
 | 
				
			||||||
 | 
					    return struct {
 | 
				
			||||||
 | 
					        fn inner(cpu: *Arm7tdmi, _: *Bus, opcode: u32) void {
 | 
				
			||||||
 | 
					            const rd = opcode >> 16 & 0xF;
 | 
				
			||||||
 | 
					            const rn = opcode >> 12 & 0xF;
 | 
				
			||||||
 | 
					            const rs = opcode >> 8 & 0xF;
 | 
				
			||||||
 | 
					            const rm = opcode & 0xF;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            const temp: u64 = @as(u64, cpu.r[rm]) * @as(u64, cpu.r[rs]) + if (A) cpu.r[rn] else 0;
 | 
				
			||||||
 | 
					            const result = @truncate(u32, temp);
 | 
				
			||||||
 | 
					            cpu.r[rd] = result;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (S) {
 | 
				
			||||||
 | 
					                cpu.cpsr.n.write(result >> 31 & 1 == 1);
 | 
				
			||||||
 | 
					                cpu.cpsr.z.write(result == 0);
 | 
				
			||||||
 | 
					                // V is unaffected, C is *actually* undefined in ARMv4
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }.inner;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn multiplyLong(comptime U: bool, comptime A: bool, comptime S: bool) InstrFn {
 | 
				
			||||||
 | 
					    return struct {
 | 
				
			||||||
 | 
					        fn inner(cpu: *Arm7tdmi, _: *Bus, opcode: u32) void {
 | 
				
			||||||
 | 
					            const rd_hi = opcode >> 16 & 0xF;
 | 
				
			||||||
 | 
					            const rd_lo = opcode >> 12 & 0xF;
 | 
				
			||||||
 | 
					            const rs = opcode >> 8 & 0xF;
 | 
				
			||||||
 | 
					            const rm = opcode & 0xF;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (U) {
 | 
				
			||||||
 | 
					                // Signed (WHY IS IT U THEN?)
 | 
				
			||||||
 | 
					                var result: i64 = @as(i64, @bitCast(i32, cpu.r[rm])) * @as(i64, @bitCast(i32, cpu.r[rs]));
 | 
				
			||||||
 | 
					                if (A) result +%= @bitCast(i64, @as(u64, cpu.r[rd_hi]) << 32 | @as(u64, cpu.r[rd_lo]));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                cpu.r[rd_hi] = @bitCast(u32, @truncate(i32, result >> 32));
 | 
				
			||||||
 | 
					                cpu.r[rd_lo] = @bitCast(u32, @truncate(i32, result));
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					                // Unsigned
 | 
				
			||||||
 | 
					                var result: u64 = @as(u64, cpu.r[rm]) * @as(u64, cpu.r[rs]);
 | 
				
			||||||
 | 
					                if (A) result +%= @as(u64, cpu.r[rd_hi]) << 32 | @as(u64, cpu.r[rd_lo]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                cpu.r[rd_hi] = @truncate(u32, result >> 32);
 | 
				
			||||||
 | 
					                cpu.r[rd_lo] = @truncate(u32, result);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (S) {
 | 
				
			||||||
 | 
					                cpu.cpsr.z.write(cpu.r[rd_hi] == 0 and cpu.r[rd_lo] == 0);
 | 
				
			||||||
 | 
					                cpu.cpsr.n.write(cpu.r[rd_hi] >> 31 & 1 == 1);
 | 
				
			||||||
 | 
					                // C and V are set to meaningless values
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }.inner;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										59
									
								
								src/core/cpu/arm/psr_transfer.zig
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										59
									
								
								src/core/cpu/arm/psr_transfer.zig
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,59 @@
 | 
				
			|||||||
 | 
					const std = @import("std");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const Bus = @import("../../Bus.zig");
 | 
				
			||||||
 | 
					const Arm7tdmi = @import("../../cpu.zig").Arm7tdmi;
 | 
				
			||||||
 | 
					const InstrFn = @import("../../cpu.zig").arm.InstrFn;
 | 
				
			||||||
 | 
					const PSR = @import("../../cpu.zig").PSR;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const log = std.log.scoped(.PsrTransfer);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const rotr = @import("../../../util.zig").rotr;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn psrTransfer(comptime I: bool, comptime R: bool, comptime kind: u2) InstrFn {
 | 
				
			||||||
 | 
					    return struct {
 | 
				
			||||||
 | 
					        fn inner(cpu: *Arm7tdmi, _: *Bus, opcode: u32) void {
 | 
				
			||||||
 | 
					            switch (kind) {
 | 
				
			||||||
 | 
					                0b00 => {
 | 
				
			||||||
 | 
					                    // MRS
 | 
				
			||||||
 | 
					                    const rd = opcode >> 12 & 0xF;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    if (R and !cpu.hasSPSR()) log.err("Tried to read SPSR from User/System Mode", .{});
 | 
				
			||||||
 | 
					                    cpu.r[rd] = if (R) cpu.spsr.raw else cpu.cpsr.raw;
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					                0b10 => {
 | 
				
			||||||
 | 
					                    // MSR
 | 
				
			||||||
 | 
					                    const field_mask = @truncate(u4, opcode >> 16 & 0xF);
 | 
				
			||||||
 | 
					                    const rm_idx = opcode & 0xF;
 | 
				
			||||||
 | 
					                    const right = if (I) rotr(u32, opcode & 0xFF, (opcode >> 8 & 0xF) * 2) else cpu.r[rm_idx];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    if (R and !cpu.hasSPSR()) log.err("Tried to write to SPSR in User/System Mode", .{});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    if (R) {
 | 
				
			||||||
 | 
					                        // arm.gba seems to expect the SPSR to do somethign in SYS mode,
 | 
				
			||||||
 | 
					                        // so we just assume that despite writing to the SPSR in USR or SYS mode
 | 
				
			||||||
 | 
					                        // being UNPREDICTABLE, it just magically has a working SPSR somehow
 | 
				
			||||||
 | 
					                        cpu.spsr.raw = fieldMask(&cpu.spsr, field_mask, right);
 | 
				
			||||||
 | 
					                    } else {
 | 
				
			||||||
 | 
					                        if (cpu.isPrivileged()) cpu.setCpsr(fieldMask(&cpu.cpsr, field_mask, right));
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					                else => cpu.panic("[CPU/PSR Transfer] Bits 21:220 of {X:0>8} are undefined", .{opcode}),
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }.inner;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fn fieldMask(psr: *const PSR, field_mask: u4, right: u32) u32 {
 | 
				
			||||||
 | 
					    // This bitwise ORs bits 3 and 0 of the field mask into a u2
 | 
				
			||||||
 | 
					    // We do this because we only care about bits 7:0 and 31:28 of the CPSR
 | 
				
			||||||
 | 
					    const bits = @truncate(u2, (field_mask >> 2 & 0x2) | (field_mask & 1));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const mask: u32 = switch (bits) {
 | 
				
			||||||
 | 
					        0b00 => 0x0000_0000,
 | 
				
			||||||
 | 
					        0b01 => 0x0000_00FF,
 | 
				
			||||||
 | 
					        0b10 => 0xF000_0000,
 | 
				
			||||||
 | 
					        0b11 => 0xF000_00FF,
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return (psr.raw & ~mask) | (right & mask);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										31
									
								
								src/core/cpu/arm/single_data_swap.zig
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								src/core/cpu/arm/single_data_swap.zig
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,31 @@
 | 
				
			|||||||
 | 
					const std = @import("std");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const Bus = @import("../../Bus.zig");
 | 
				
			||||||
 | 
					const Arm7tdmi = @import("../../cpu.zig").Arm7tdmi;
 | 
				
			||||||
 | 
					const InstrFn = @import("../../cpu.zig").arm.InstrFn;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const rotr = @import("../../../util.zig").rotr;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn singleDataSwap(comptime B: bool) InstrFn {
 | 
				
			||||||
 | 
					    return struct {
 | 
				
			||||||
 | 
					        fn inner(cpu: *Arm7tdmi, bus: *Bus, opcode: u32) void {
 | 
				
			||||||
 | 
					            const rn = opcode >> 16 & 0xF;
 | 
				
			||||||
 | 
					            const rd = opcode >> 12 & 0xF;
 | 
				
			||||||
 | 
					            const rm = opcode & 0xF;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            const address = cpu.r[rn];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (B) {
 | 
				
			||||||
 | 
					                // SWPB
 | 
				
			||||||
 | 
					                const value = bus.read(u8, address);
 | 
				
			||||||
 | 
					                bus.write(u8, address, @truncate(u8, cpu.r[rm]));
 | 
				
			||||||
 | 
					                cpu.r[rd] = value;
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					                // SWP
 | 
				
			||||||
 | 
					                const value = rotr(u32, bus.read(u32, address), 8 * (address & 0x3));
 | 
				
			||||||
 | 
					                bus.write(u32, address, cpu.r[rm]);
 | 
				
			||||||
 | 
					                cpu.r[rd] = value;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }.inner;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										60
									
								
								src/core/cpu/arm/single_data_transfer.zig
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										60
									
								
								src/core/cpu/arm/single_data_transfer.zig
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,60 @@
 | 
				
			|||||||
 | 
					const std = @import("std");
 | 
				
			||||||
 | 
					const util = @import("../../../util.zig");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const shifter = @import("../barrel_shifter.zig");
 | 
				
			||||||
 | 
					const Bus = @import("../../Bus.zig");
 | 
				
			||||||
 | 
					const Arm7tdmi = @import("../../cpu.zig").Arm7tdmi;
 | 
				
			||||||
 | 
					const InstrFn = @import("../../cpu.zig").arm.InstrFn;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const rotr = @import("../../../util.zig").rotr;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn singleDataTransfer(comptime I: bool, comptime P: bool, comptime U: bool, comptime B: bool, comptime W: bool, comptime L: bool) InstrFn {
 | 
				
			||||||
 | 
					    return struct {
 | 
				
			||||||
 | 
					        fn inner(cpu: *Arm7tdmi, bus: *Bus, opcode: u32) void {
 | 
				
			||||||
 | 
					            const rn = opcode >> 16 & 0xF;
 | 
				
			||||||
 | 
					            const rd = opcode >> 12 & 0xF;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // rn is r15 and L is not set, the PC is 12 ahead
 | 
				
			||||||
 | 
					            const base = cpu.r[rn] + if (!L and rn == 0xF) 4 else @as(u32, 0);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            const offset = if (I) shifter.immediate(false, cpu, opcode) else opcode & 0xFFF;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            const modified_base = if (U) base +% offset else base -% offset;
 | 
				
			||||||
 | 
					            var address = if (P) modified_base else base;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            var result: u32 = undefined;
 | 
				
			||||||
 | 
					            if (L) {
 | 
				
			||||||
 | 
					                if (B) {
 | 
				
			||||||
 | 
					                    // LDRB
 | 
				
			||||||
 | 
					                    result = bus.read(u8, address);
 | 
				
			||||||
 | 
					                } else {
 | 
				
			||||||
 | 
					                    // LDR
 | 
				
			||||||
 | 
					                    const value = bus.read(u32, address);
 | 
				
			||||||
 | 
					                    result = rotr(u32, value, 8 * (address & 0x3));
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					                if (B) {
 | 
				
			||||||
 | 
					                    // STRB
 | 
				
			||||||
 | 
					                    const value = cpu.r[rd] + if (rd == 0xF) 4 else @as(u32, 0); // PC is 12 ahead
 | 
				
			||||||
 | 
					                    bus.write(u8, address, @truncate(u8, value));
 | 
				
			||||||
 | 
					                } else {
 | 
				
			||||||
 | 
					                    // STR
 | 
				
			||||||
 | 
					                    const value = cpu.r[rd] + if (rd == 0xF) 4 else @as(u32, 0);
 | 
				
			||||||
 | 
					                    bus.write(u32, address, value);
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            address = modified_base;
 | 
				
			||||||
 | 
					            if (W and P or !P) {
 | 
				
			||||||
 | 
					                cpu.r[rn] = address;
 | 
				
			||||||
 | 
					                if (rn == 0xF) cpu.pipe.reload(cpu);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (L) {
 | 
				
			||||||
 | 
					                // This emulates the LDR rd == rn behaviour
 | 
				
			||||||
 | 
					                cpu.r[rd] = result;
 | 
				
			||||||
 | 
					                if (rd == 0xF) cpu.pipe.reload(cpu);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }.inner;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										23
									
								
								src/core/cpu/arm/software_interrupt.zig
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								src/core/cpu/arm/software_interrupt.zig
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,23 @@
 | 
				
			|||||||
 | 
					const Bus = @import("../../Bus.zig");
 | 
				
			||||||
 | 
					const Arm7tdmi = @import("../../cpu.zig").Arm7tdmi;
 | 
				
			||||||
 | 
					const InstrFn = @import("../../cpu.zig").arm.InstrFn;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn armSoftwareInterrupt() InstrFn {
 | 
				
			||||||
 | 
					    return struct {
 | 
				
			||||||
 | 
					        fn inner(cpu: *Arm7tdmi, _: *Bus, _: u32) void {
 | 
				
			||||||
 | 
					            // Copy Values from Current Mode
 | 
				
			||||||
 | 
					            const ret_addr = cpu.r[15] - 4;
 | 
				
			||||||
 | 
					            const cpsr = cpu.cpsr.raw;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // Switch Mode
 | 
				
			||||||
 | 
					            cpu.changeMode(.Supervisor);
 | 
				
			||||||
 | 
					            cpu.cpsr.t.write(false); // Force ARM Mode
 | 
				
			||||||
 | 
					            cpu.cpsr.i.write(true); // Disable normal interrupts
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            cpu.r[14] = ret_addr; // Resume Execution
 | 
				
			||||||
 | 
					            cpu.spsr.raw = cpsr; // Previous mode CPSR
 | 
				
			||||||
 | 
					            cpu.r[15] = 0x0000_0008;
 | 
				
			||||||
 | 
					            cpu.pipe.reload(cpu);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }.inner;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										149
									
								
								src/core/cpu/barrel_shifter.zig
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										149
									
								
								src/core/cpu/barrel_shifter.zig
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,149 @@
 | 
				
			|||||||
 | 
					const std = @import("std");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const Arm7tdmi = @import("../cpu.zig").Arm7tdmi;
 | 
				
			||||||
 | 
					const CPSR = @import("../cpu.zig").PSR;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const rotr = @import("../../util.zig").rotr;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn exec(comptime S: bool, cpu: *Arm7tdmi, opcode: u32) u32 {
 | 
				
			||||||
 | 
					    var result: u32 = undefined;
 | 
				
			||||||
 | 
					    if (opcode >> 4 & 1 == 1) {
 | 
				
			||||||
 | 
					        result = register(S, cpu, opcode);
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					        result = immediate(S, cpu, opcode);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return result;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fn register(comptime S: bool, cpu: *Arm7tdmi, opcode: u32) u32 {
 | 
				
			||||||
 | 
					    const rs_idx = opcode >> 8 & 0xF;
 | 
				
			||||||
 | 
					    const rm = cpu.r[opcode & 0xF];
 | 
				
			||||||
 | 
					    const rs = @truncate(u8, cpu.r[rs_idx]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return switch (@truncate(u2, opcode >> 5)) {
 | 
				
			||||||
 | 
					        0b00 => lsl(S, &cpu.cpsr, rm, rs),
 | 
				
			||||||
 | 
					        0b01 => lsr(S, &cpu.cpsr, rm, rs),
 | 
				
			||||||
 | 
					        0b10 => asr(S, &cpu.cpsr, rm, rs),
 | 
				
			||||||
 | 
					        0b11 => ror(S, &cpu.cpsr, rm, rs),
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn immediate(comptime S: bool, cpu: *Arm7tdmi, opcode: u32) u32 {
 | 
				
			||||||
 | 
					    const amount = @truncate(u8, opcode >> 7 & 0x1F);
 | 
				
			||||||
 | 
					    const rm = cpu.r[opcode & 0xF];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    var result: u32 = undefined;
 | 
				
			||||||
 | 
					    if (amount == 0) {
 | 
				
			||||||
 | 
					        switch (@truncate(u2, opcode >> 5)) {
 | 
				
			||||||
 | 
					            0b00 => {
 | 
				
			||||||
 | 
					                // LSL #0
 | 
				
			||||||
 | 
					                result = rm;
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            0b01 => {
 | 
				
			||||||
 | 
					                // LSR #0 aka LSR #32
 | 
				
			||||||
 | 
					                if (S) cpu.cpsr.c.write(rm >> 31 & 1 == 1);
 | 
				
			||||||
 | 
					                result = 0x0000_0000;
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            0b10 => {
 | 
				
			||||||
 | 
					                // ASR #0 aka ASR #32
 | 
				
			||||||
 | 
					                result = @bitCast(u32, @bitCast(i32, rm) >> 31);
 | 
				
			||||||
 | 
					                if (S) cpu.cpsr.c.write(result >> 31 & 1 == 1);
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            0b11 => {
 | 
				
			||||||
 | 
					                // ROR #0 aka RRX
 | 
				
			||||||
 | 
					                const carry: u32 = @boolToInt(cpu.cpsr.c.read());
 | 
				
			||||||
 | 
					                if (S) cpu.cpsr.c.write(rm & 1 == 1);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                result = (carry << 31) | (rm >> 1);
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					        switch (@truncate(u2, opcode >> 5)) {
 | 
				
			||||||
 | 
					            0b00 => result = lsl(S, &cpu.cpsr, rm, amount),
 | 
				
			||||||
 | 
					            0b01 => result = lsr(S, &cpu.cpsr, rm, amount),
 | 
				
			||||||
 | 
					            0b10 => result = asr(S, &cpu.cpsr, rm, amount),
 | 
				
			||||||
 | 
					            0b11 => result = ror(S, &cpu.cpsr, rm, amount),
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return result;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn lsl(comptime S: bool, cpsr: *CPSR, rm: u32, total_amount: u8) u32 {
 | 
				
			||||||
 | 
					    const amount = @truncate(u5, total_amount);
 | 
				
			||||||
 | 
					    const bit_count: u8 = @typeInfo(u32).Int.bits;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    var result: u32 = 0x0000_0000;
 | 
				
			||||||
 | 
					    if (total_amount < bit_count) {
 | 
				
			||||||
 | 
					        // We can perform a well-defined shift here
 | 
				
			||||||
 | 
					        result = rm << amount;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (S and total_amount != 0) {
 | 
				
			||||||
 | 
					            const carry_bit = @truncate(u5, bit_count - amount);
 | 
				
			||||||
 | 
					            cpsr.c.write(rm >> carry_bit & 1 == 1);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					        if (S) {
 | 
				
			||||||
 | 
					            if (total_amount == bit_count) {
 | 
				
			||||||
 | 
					                // Shifted all bits out, carry bit is bit 0 of rm
 | 
				
			||||||
 | 
					                cpsr.c.write(rm & 1 == 1);
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					                cpsr.c.write(false);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return result;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn lsr(comptime S: bool, cpsr: *CPSR, rm: u32, total_amount: u32) u32 {
 | 
				
			||||||
 | 
					    const amount = @truncate(u5, total_amount);
 | 
				
			||||||
 | 
					    const bit_count: u8 = @typeInfo(u32).Int.bits;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    var result: u32 = 0x0000_0000;
 | 
				
			||||||
 | 
					    if (total_amount < bit_count) {
 | 
				
			||||||
 | 
					        // We can perform a well-defined shift
 | 
				
			||||||
 | 
					        result = rm >> amount;
 | 
				
			||||||
 | 
					        if (S and total_amount != 0) cpsr.c.write(rm >> (amount - 1) & 1 == 1);
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					        if (S) {
 | 
				
			||||||
 | 
					            if (total_amount == bit_count) {
 | 
				
			||||||
 | 
					                // LSR #32
 | 
				
			||||||
 | 
					                cpsr.c.write(rm >> 31 & 1 == 1);
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					                // All bits have been shifted out, including carry bit
 | 
				
			||||||
 | 
					                cpsr.c.write(false);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return result;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn asr(comptime S: bool, cpsr: *CPSR, rm: u32, total_amount: u8) u32 {
 | 
				
			||||||
 | 
					    const amount = @truncate(u5, total_amount);
 | 
				
			||||||
 | 
					    const bit_count: u8 = @typeInfo(u32).Int.bits;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    var result: u32 = 0x0000_0000;
 | 
				
			||||||
 | 
					    if (total_amount < bit_count) {
 | 
				
			||||||
 | 
					        result = @bitCast(u32, @bitCast(i32, rm) >> amount);
 | 
				
			||||||
 | 
					        if (S and total_amount != 0) cpsr.c.write(rm >> (amount - 1) & 1 == 1);
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					        // ASR #32 and ASR #>32 have the same result
 | 
				
			||||||
 | 
					        result = @bitCast(u32, @bitCast(i32, rm) >> 31);
 | 
				
			||||||
 | 
					        if (S) cpsr.c.write(result >> 31 & 1 == 1);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return result;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn ror(comptime S: bool, cpsr: *CPSR, rm: u32, total_amount: u8) u32 {
 | 
				
			||||||
 | 
					    const result = rotr(u32, rm, total_amount);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (S and total_amount != 0) {
 | 
				
			||||||
 | 
					        cpsr.c.write(result >> 31 & 1 == 1);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return result;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										102
									
								
								src/core/cpu/thumb/alu.zig
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										102
									
								
								src/core/cpu/thumb/alu.zig
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,102 @@
 | 
				
			|||||||
 | 
					const Bus = @import("../../Bus.zig");
 | 
				
			||||||
 | 
					const Arm7tdmi = @import("../../cpu.zig").Arm7tdmi;
 | 
				
			||||||
 | 
					const InstrFn = @import("../../cpu.zig").thumb.InstrFn;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const adc = @import("../arm/data_processing.zig").adc;
 | 
				
			||||||
 | 
					const sbc = @import("../arm/data_processing.zig").sbc;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const lsl = @import("../barrel_shifter.zig").lsl;
 | 
				
			||||||
 | 
					const lsr = @import("../barrel_shifter.zig").lsr;
 | 
				
			||||||
 | 
					const asr = @import("../barrel_shifter.zig").asr;
 | 
				
			||||||
 | 
					const ror = @import("../barrel_shifter.zig").ror;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn fmt4(comptime op: u4) InstrFn {
 | 
				
			||||||
 | 
					    return struct {
 | 
				
			||||||
 | 
					        fn inner(cpu: *Arm7tdmi, _: *Bus, opcode: u16) void {
 | 
				
			||||||
 | 
					            const rs = opcode >> 3 & 0x7;
 | 
				
			||||||
 | 
					            const rd = opcode & 0x7;
 | 
				
			||||||
 | 
					            const carry = @boolToInt(cpu.cpsr.c.read());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            const op1 = cpu.r[rd];
 | 
				
			||||||
 | 
					            const op2 = cpu.r[rs];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            var result: u32 = undefined;
 | 
				
			||||||
 | 
					            var overflow: bool = undefined;
 | 
				
			||||||
 | 
					            switch (op) {
 | 
				
			||||||
 | 
					                0x0 => result = op1 & op2, // AND
 | 
				
			||||||
 | 
					                0x1 => result = op1 ^ op2, // EOR
 | 
				
			||||||
 | 
					                0x2 => result = lsl(true, &cpu.cpsr, op1, @truncate(u8, op2)), // LSL
 | 
				
			||||||
 | 
					                0x3 => result = lsr(true, &cpu.cpsr, op1, @truncate(u8, op2)), // LSR
 | 
				
			||||||
 | 
					                0x4 => result = asr(true, &cpu.cpsr, op1, @truncate(u8, op2)), // ASR
 | 
				
			||||||
 | 
					                0x5 => result = adc(&overflow, op1, op2, carry), // ADC
 | 
				
			||||||
 | 
					                0x6 => result = sbc(op1, op2, carry), // SBC
 | 
				
			||||||
 | 
					                0x7 => result = ror(true, &cpu.cpsr, op1, @truncate(u8, op2)), // ROR
 | 
				
			||||||
 | 
					                0x8 => result = op1 & op2, // TST
 | 
				
			||||||
 | 
					                0x9 => result = 0 -% op2, // NEG
 | 
				
			||||||
 | 
					                0xA => result = op1 -% op2, // CMP
 | 
				
			||||||
 | 
					                0xB => overflow = @addWithOverflow(u32, op1, op2, &result), // CMN
 | 
				
			||||||
 | 
					                0xC => result = op1 | op2, // ORR
 | 
				
			||||||
 | 
					                0xD => result = @truncate(u32, @as(u64, op2) * @as(u64, op1)),
 | 
				
			||||||
 | 
					                0xE => result = op1 & ~op2,
 | 
				
			||||||
 | 
					                0xF => result = ~op2,
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // Write to Destination Register
 | 
				
			||||||
 | 
					            switch (op) {
 | 
				
			||||||
 | 
					                0x8, 0xA, 0xB => {},
 | 
				
			||||||
 | 
					                else => cpu.r[rd] = result,
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // Write Flags
 | 
				
			||||||
 | 
					            switch (op) {
 | 
				
			||||||
 | 
					                0x0, 0x1, 0x2, 0x3, 0x4, 0x7, 0xC, 0xE, 0xF => {
 | 
				
			||||||
 | 
					                    // Logic Operations
 | 
				
			||||||
 | 
					                    cpu.cpsr.n.write(result >> 31 & 1 == 1);
 | 
				
			||||||
 | 
					                    cpu.cpsr.z.write(result == 0);
 | 
				
			||||||
 | 
					                    // C set by Barrel Shifter, V is unaffected
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					                0x8, 0xA => {
 | 
				
			||||||
 | 
					                    // Test Flags
 | 
				
			||||||
 | 
					                    // CMN (0xB) is handled with ADC
 | 
				
			||||||
 | 
					                    cpu.cpsr.n.write(result >> 31 & 1 == 1);
 | 
				
			||||||
 | 
					                    cpu.cpsr.z.write(result == 0);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    if (op == 0xA) {
 | 
				
			||||||
 | 
					                        // CMP specific
 | 
				
			||||||
 | 
					                        cpu.cpsr.c.write(op2 <= op1);
 | 
				
			||||||
 | 
					                        cpu.cpsr.v.write(((op1 ^ result) & (~op2 ^ result)) >> 31 & 1 == 1);
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					                0x5, 0xB => {
 | 
				
			||||||
 | 
					                    // ADC, CMN
 | 
				
			||||||
 | 
					                    cpu.cpsr.n.write(result >> 31 & 1 == 1);
 | 
				
			||||||
 | 
					                    cpu.cpsr.z.write(result == 0);
 | 
				
			||||||
 | 
					                    cpu.cpsr.c.write(overflow);
 | 
				
			||||||
 | 
					                    cpu.cpsr.v.write(((op1 ^ result) & (op2 ^ result)) >> 31 & 1 == 1);
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					                0x6 => {
 | 
				
			||||||
 | 
					                    // SBC
 | 
				
			||||||
 | 
					                    cpu.cpsr.n.write(result >> 31 & 1 == 1);
 | 
				
			||||||
 | 
					                    cpu.cpsr.z.write(result == 0);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    const subtrahend = @as(u64, op2) -% carry +% 1;
 | 
				
			||||||
 | 
					                    cpu.cpsr.c.write(subtrahend <= op1);
 | 
				
			||||||
 | 
					                    cpu.cpsr.v.write(((op1 ^ result) & (~op2 ^ result)) >> 31 & 1 == 1);
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					                0x9 => {
 | 
				
			||||||
 | 
					                    // NEG
 | 
				
			||||||
 | 
					                    cpu.cpsr.n.write(result >> 31 & 1 == 1);
 | 
				
			||||||
 | 
					                    cpu.cpsr.z.write(result == 0);
 | 
				
			||||||
 | 
					                    cpu.cpsr.c.write(op2 <= 0);
 | 
				
			||||||
 | 
					                    cpu.cpsr.v.write(((0 ^ result) & (~op2 ^ result)) >> 31 & 1 == 1);
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					                0xD => {
 | 
				
			||||||
 | 
					                    // Multiplication
 | 
				
			||||||
 | 
					                    cpu.cpsr.n.write(result >> 31 & 1 == 1);
 | 
				
			||||||
 | 
					                    cpu.cpsr.z.write(result == 0);
 | 
				
			||||||
 | 
					                    // V is unaffected, assuming similar behaviour to ARMv4 MUL C is undefined
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }.inner;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										101
									
								
								src/core/cpu/thumb/block_data_transfer.zig
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										101
									
								
								src/core/cpu/thumb/block_data_transfer.zig
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,101 @@
 | 
				
			|||||||
 | 
					const Bus = @import("../../Bus.zig");
 | 
				
			||||||
 | 
					const Arm7tdmi = @import("../../cpu.zig").Arm7tdmi;
 | 
				
			||||||
 | 
					const InstrFn = @import("../../cpu.zig").thumb.InstrFn;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn fmt14(comptime L: bool, comptime R: bool) InstrFn {
 | 
				
			||||||
 | 
					    return struct {
 | 
				
			||||||
 | 
					        fn inner(cpu: *Arm7tdmi, bus: *Bus, opcode: u16) void {
 | 
				
			||||||
 | 
					            const count = @boolToInt(R) + countRlist(opcode);
 | 
				
			||||||
 | 
					            const start = cpu.r[13] - if (!L) count * 4 else 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            var end = cpu.r[13];
 | 
				
			||||||
 | 
					            if (L) {
 | 
				
			||||||
 | 
					                end += count * 4;
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					                end -= 4;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            var address = start;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            var i: u4 = 0;
 | 
				
			||||||
 | 
					            while (i < 8) : (i += 1) {
 | 
				
			||||||
 | 
					                if (opcode >> i & 1 == 1) {
 | 
				
			||||||
 | 
					                    if (L) {
 | 
				
			||||||
 | 
					                        cpu.r[i] = bus.read(u32, address);
 | 
				
			||||||
 | 
					                    } else {
 | 
				
			||||||
 | 
					                        bus.write(u32, address, cpu.r[i]);
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    address += 4;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (R) {
 | 
				
			||||||
 | 
					                if (L) {
 | 
				
			||||||
 | 
					                    const value = bus.read(u32, address);
 | 
				
			||||||
 | 
					                    cpu.r[15] = value & ~@as(u32, 1);
 | 
				
			||||||
 | 
					                    cpu.pipe.reload(cpu);
 | 
				
			||||||
 | 
					                } else {
 | 
				
			||||||
 | 
					                    bus.write(u32, address, cpu.r[14]);
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                address += 4;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            cpu.r[13] = if (L) end else start;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }.inner;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn fmt15(comptime L: bool, comptime rb: u3) InstrFn {
 | 
				
			||||||
 | 
					    return struct {
 | 
				
			||||||
 | 
					        fn inner(cpu: *Arm7tdmi, bus: *Bus, opcode: u16) void {
 | 
				
			||||||
 | 
					            var address = cpu.r[rb];
 | 
				
			||||||
 | 
					            const end_address = cpu.r[rb] + 4 * countRlist(opcode);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (opcode & 0xFF == 0) {
 | 
				
			||||||
 | 
					                if (L) {
 | 
				
			||||||
 | 
					                    cpu.r[15] = bus.read(u32, address);
 | 
				
			||||||
 | 
					                    cpu.pipe.reload(cpu);
 | 
				
			||||||
 | 
					                } else {
 | 
				
			||||||
 | 
					                    bus.write(u32, address, cpu.r[15] + 2);
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                cpu.r[rb] += 0x40;
 | 
				
			||||||
 | 
					                return;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            var i: u4 = 0;
 | 
				
			||||||
 | 
					            var first_write = true;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            while (i < 8) : (i += 1) {
 | 
				
			||||||
 | 
					                if (opcode >> i & 1 == 1) {
 | 
				
			||||||
 | 
					                    if (L) {
 | 
				
			||||||
 | 
					                        cpu.r[i] = bus.read(u32, address);
 | 
				
			||||||
 | 
					                    } else {
 | 
				
			||||||
 | 
					                        bus.write(u32, address, cpu.r[i]);
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    if (!L and first_write) {
 | 
				
			||||||
 | 
					                        cpu.r[rb] = end_address;
 | 
				
			||||||
 | 
					                        first_write = false;
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    address += 4;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (L and opcode >> rb & 1 != 1) cpu.r[rb] = address;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }.inner;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					inline fn countRlist(opcode: u16) u32 {
 | 
				
			||||||
 | 
					    var count: u32 = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    comptime var i: u4 = 0;
 | 
				
			||||||
 | 
					    inline while (i < 8) : (i += 1) {
 | 
				
			||||||
 | 
					        if (opcode >> (7 - i) & 1 == 1) count += 1;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return count;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										54
									
								
								src/core/cpu/thumb/branch.zig
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										54
									
								
								src/core/cpu/thumb/branch.zig
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,54 @@
 | 
				
			|||||||
 | 
					const Bus = @import("../../Bus.zig");
 | 
				
			||||||
 | 
					const Arm7tdmi = @import("../../cpu.zig").Arm7tdmi;
 | 
				
			||||||
 | 
					const InstrFn = @import("../../cpu.zig").thumb.InstrFn;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const checkCond = @import("../../cpu.zig").checkCond;
 | 
				
			||||||
 | 
					const sext = @import("../../../util.zig").sext;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn fmt16(comptime cond: u4) InstrFn {
 | 
				
			||||||
 | 
					    return struct {
 | 
				
			||||||
 | 
					        fn inner(cpu: *Arm7tdmi, _: *Bus, opcode: u16) void {
 | 
				
			||||||
 | 
					            // B
 | 
				
			||||||
 | 
					            if (cond == 0xE or cond == 0xF)
 | 
				
			||||||
 | 
					                cpu.panic("[CPU/THUMB.16] Undefined conditional branch with condition {}", .{cond});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (!checkCond(cpu.cpsr, cond)) return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            cpu.r[15] +%= sext(u32, u8, opcode & 0xFF) << 1;
 | 
				
			||||||
 | 
					            cpu.pipe.reload(cpu);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }.inner;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn fmt18() InstrFn {
 | 
				
			||||||
 | 
					    return struct {
 | 
				
			||||||
 | 
					        // B but conditional
 | 
				
			||||||
 | 
					        fn inner(cpu: *Arm7tdmi, _: *Bus, opcode: u16) void {
 | 
				
			||||||
 | 
					            cpu.r[15] +%= sext(u32, u11, opcode & 0x7FF) << 1;
 | 
				
			||||||
 | 
					            cpu.pipe.reload(cpu);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }.inner;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn fmt19(comptime is_low: bool) InstrFn {
 | 
				
			||||||
 | 
					    return struct {
 | 
				
			||||||
 | 
					        fn inner(cpu: *Arm7tdmi, _: *Bus, opcode: u16) void {
 | 
				
			||||||
 | 
					            // BL
 | 
				
			||||||
 | 
					            const offset = opcode & 0x7FF;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (is_low) {
 | 
				
			||||||
 | 
					                // Instruction 2
 | 
				
			||||||
 | 
					                const next_opcode = cpu.r[15] - 2;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                cpu.r[15] = cpu.r[14] +% (offset << 1);
 | 
				
			||||||
 | 
					                cpu.r[14] = next_opcode | 1;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                cpu.pipe.reload(cpu);
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					                // Instruction 1
 | 
				
			||||||
 | 
					                const lr_offset = sext(u32, u11, offset) << 12;
 | 
				
			||||||
 | 
					                cpu.r[14] = (cpu.r[15] +% lr_offset) & ~@as(u32, 1);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }.inner;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										201
									
								
								src/core/cpu/thumb/data_processing.zig
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										201
									
								
								src/core/cpu/thumb/data_processing.zig
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,201 @@
 | 
				
			|||||||
 | 
					const std = @import("std");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const Bus = @import("../../Bus.zig");
 | 
				
			||||||
 | 
					const Arm7tdmi = @import("../../cpu.zig").Arm7tdmi;
 | 
				
			||||||
 | 
					const InstrFn = @import("../../cpu.zig").thumb.InstrFn;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const add = @import("../arm/data_processing.zig").add;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const lsl = @import("../barrel_shifter.zig").lsl;
 | 
				
			||||||
 | 
					const lsr = @import("../barrel_shifter.zig").lsr;
 | 
				
			||||||
 | 
					const asr = @import("../barrel_shifter.zig").asr;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn fmt1(comptime op: u2, comptime offset: u5) InstrFn {
 | 
				
			||||||
 | 
					    return struct {
 | 
				
			||||||
 | 
					        fn inner(cpu: *Arm7tdmi, _: *Bus, opcode: u16) void {
 | 
				
			||||||
 | 
					            const rs = opcode >> 3 & 0x7;
 | 
				
			||||||
 | 
					            const rd = opcode & 0x7;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            const result = switch (op) {
 | 
				
			||||||
 | 
					                0b00 => blk: {
 | 
				
			||||||
 | 
					                    // LSL
 | 
				
			||||||
 | 
					                    if (offset == 0) {
 | 
				
			||||||
 | 
					                        break :blk cpu.r[rs];
 | 
				
			||||||
 | 
					                    } else {
 | 
				
			||||||
 | 
					                        break :blk lsl(true, &cpu.cpsr, cpu.r[rs], offset);
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					                0b01 => blk: {
 | 
				
			||||||
 | 
					                    // LSR
 | 
				
			||||||
 | 
					                    if (offset == 0) {
 | 
				
			||||||
 | 
					                        cpu.cpsr.c.write(cpu.r[rs] >> 31 & 1 == 1);
 | 
				
			||||||
 | 
					                        break :blk @as(u32, 0);
 | 
				
			||||||
 | 
					                    } else {
 | 
				
			||||||
 | 
					                        break :blk lsr(true, &cpu.cpsr, cpu.r[rs], offset);
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					                0b10 => blk: {
 | 
				
			||||||
 | 
					                    // ASR
 | 
				
			||||||
 | 
					                    if (offset == 0) {
 | 
				
			||||||
 | 
					                        cpu.cpsr.c.write(cpu.r[rs] >> 31 & 1 == 1);
 | 
				
			||||||
 | 
					                        break :blk @bitCast(u32, @bitCast(i32, cpu.r[rs]) >> 31);
 | 
				
			||||||
 | 
					                    } else {
 | 
				
			||||||
 | 
					                        break :blk asr(true, &cpu.cpsr, cpu.r[rs], offset);
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					                else => cpu.panic("[CPU/THUMB.1] 0b{b:0>2} is not a valid op", .{op}),
 | 
				
			||||||
 | 
					            };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // Equivalent to an ARM MOVS
 | 
				
			||||||
 | 
					            cpu.r[rd] = result;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // Write Flags
 | 
				
			||||||
 | 
					            cpu.cpsr.n.write(result >> 31 & 1 == 1);
 | 
				
			||||||
 | 
					            cpu.cpsr.z.write(result == 0);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }.inner;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn fmt5(comptime op: u2, comptime h1: u1, comptime h2: u1) InstrFn {
 | 
				
			||||||
 | 
					    return struct {
 | 
				
			||||||
 | 
					        fn inner(cpu: *Arm7tdmi, _: *Bus, opcode: u16) void {
 | 
				
			||||||
 | 
					            const rs = @as(u4, h2) << 3 | (opcode >> 3 & 0x7);
 | 
				
			||||||
 | 
					            const rd = @as(u4, h1) << 3 | (opcode & 0x7);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            const op1 = cpu.r[rd];
 | 
				
			||||||
 | 
					            const op2 = cpu.r[rs];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            var result: u32 = undefined;
 | 
				
			||||||
 | 
					            var overflow: bool = undefined;
 | 
				
			||||||
 | 
					            switch (op) {
 | 
				
			||||||
 | 
					                0b00 => result = add(&overflow, op1, op2), // ADD
 | 
				
			||||||
 | 
					                0b01 => result = op1 -% op2, // CMP
 | 
				
			||||||
 | 
					                0b10 => result = op2, // MOV
 | 
				
			||||||
 | 
					                0b11 => {},
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // Write to Destination Register
 | 
				
			||||||
 | 
					            switch (op) {
 | 
				
			||||||
 | 
					                0b01 => {}, // Test Instruction
 | 
				
			||||||
 | 
					                0b11 => {
 | 
				
			||||||
 | 
					                    // BX
 | 
				
			||||||
 | 
					                    const is_thumb = op2 & 1 == 1;
 | 
				
			||||||
 | 
					                    cpu.r[15] = op2 & ~@as(u32, 1);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    cpu.cpsr.t.write(is_thumb);
 | 
				
			||||||
 | 
					                    cpu.pipe.reload(cpu);
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					                else => {
 | 
				
			||||||
 | 
					                    cpu.r[rd] = result;
 | 
				
			||||||
 | 
					                    if (rd == 0xF) {
 | 
				
			||||||
 | 
					                        cpu.r[15] &= ~@as(u32, 1);
 | 
				
			||||||
 | 
					                        cpu.pipe.reload(cpu);
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // Write Flags
 | 
				
			||||||
 | 
					            switch (op) {
 | 
				
			||||||
 | 
					                0b01 => {
 | 
				
			||||||
 | 
					                    // CMP
 | 
				
			||||||
 | 
					                    cpu.cpsr.n.write(result >> 31 & 1 == 1);
 | 
				
			||||||
 | 
					                    cpu.cpsr.z.write(result == 0);
 | 
				
			||||||
 | 
					                    cpu.cpsr.c.write(op2 <= op1);
 | 
				
			||||||
 | 
					                    cpu.cpsr.v.write(((op1 ^ result) & (~op2 ^ result)) >> 31 & 1 == 1);
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					                0b00, 0b10, 0b11 => {}, // MOV and Branch Instruction
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }.inner;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn fmt2(comptime I: bool, is_sub: bool, rn: u3) InstrFn {
 | 
				
			||||||
 | 
					    return struct {
 | 
				
			||||||
 | 
					        fn inner(cpu: *Arm7tdmi, _: *Bus, opcode: u16) void {
 | 
				
			||||||
 | 
					            const rs = opcode >> 3 & 0x7;
 | 
				
			||||||
 | 
					            const rd = @truncate(u3, opcode);
 | 
				
			||||||
 | 
					            const op1 = cpu.r[rs];
 | 
				
			||||||
 | 
					            const op2: u32 = if (I) rn else cpu.r[rn];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (is_sub) {
 | 
				
			||||||
 | 
					                // SUB
 | 
				
			||||||
 | 
					                const result = op1 -% op2;
 | 
				
			||||||
 | 
					                cpu.r[rd] = result;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                cpu.cpsr.n.write(result >> 31 & 1 == 1);
 | 
				
			||||||
 | 
					                cpu.cpsr.z.write(result == 0);
 | 
				
			||||||
 | 
					                cpu.cpsr.c.write(op2 <= op1);
 | 
				
			||||||
 | 
					                cpu.cpsr.v.write(((op1 ^ result) & (~op2 ^ result)) >> 31 & 1 == 1);
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					                // ADD
 | 
				
			||||||
 | 
					                var overflow: bool = undefined;
 | 
				
			||||||
 | 
					                const result = add(&overflow, op1, op2);
 | 
				
			||||||
 | 
					                cpu.r[rd] = result;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                cpu.cpsr.n.write(result >> 31 & 1 == 1);
 | 
				
			||||||
 | 
					                cpu.cpsr.z.write(result == 0);
 | 
				
			||||||
 | 
					                cpu.cpsr.c.write(overflow);
 | 
				
			||||||
 | 
					                cpu.cpsr.v.write(((op1 ^ result) & (op2 ^ result)) >> 31 & 1 == 1);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }.inner;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn fmt3(comptime op: u2, comptime rd: u3) InstrFn {
 | 
				
			||||||
 | 
					    return struct {
 | 
				
			||||||
 | 
					        fn inner(cpu: *Arm7tdmi, _: *Bus, opcode: u16) void {
 | 
				
			||||||
 | 
					            const op1 = cpu.r[rd];
 | 
				
			||||||
 | 
					            const op2: u32 = opcode & 0xFF; // Offset
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            var overflow: bool = undefined;
 | 
				
			||||||
 | 
					            const result: u32 = switch (op) {
 | 
				
			||||||
 | 
					                0b00 => op2, // MOV
 | 
				
			||||||
 | 
					                0b01 => op1 -% op2, // CMP
 | 
				
			||||||
 | 
					                0b10 => add(&overflow, op1, op2), // ADD
 | 
				
			||||||
 | 
					                0b11 => op1 -% op2, // SUB
 | 
				
			||||||
 | 
					            };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // Write to Register
 | 
				
			||||||
 | 
					            if (op != 0b01) cpu.r[rd] = result;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // Write Flags
 | 
				
			||||||
 | 
					            cpu.cpsr.n.write(result >> 31 & 1 == 1);
 | 
				
			||||||
 | 
					            cpu.cpsr.z.write(result == 0);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            switch (op) {
 | 
				
			||||||
 | 
					                0b00 => {}, // MOV | C set by Barrel Shifter, V is unaffected
 | 
				
			||||||
 | 
					                0b01, 0b11 => {
 | 
				
			||||||
 | 
					                    // SUB, CMP
 | 
				
			||||||
 | 
					                    cpu.cpsr.c.write(op2 <= op1);
 | 
				
			||||||
 | 
					                    cpu.cpsr.v.write(((op1 ^ result) & (~op2 ^ result)) >> 31 & 1 == 1);
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					                0b10 => {
 | 
				
			||||||
 | 
					                    // ADD
 | 
				
			||||||
 | 
					                    cpu.cpsr.c.write(overflow);
 | 
				
			||||||
 | 
					                    cpu.cpsr.v.write(((op1 ^ result) & (op2 ^ result)) >> 31 & 1 == 1);
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }.inner;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn fmt12(comptime isSP: bool, comptime rd: u3) InstrFn {
 | 
				
			||||||
 | 
					    return struct {
 | 
				
			||||||
 | 
					        fn inner(cpu: *Arm7tdmi, _: *Bus, opcode: u16) void {
 | 
				
			||||||
 | 
					            // ADD
 | 
				
			||||||
 | 
					            const left = if (isSP) cpu.r[13] else cpu.r[15] & ~@as(u32, 2);
 | 
				
			||||||
 | 
					            const right = (opcode & 0xFF) << 2;
 | 
				
			||||||
 | 
					            cpu.r[rd] = left + right;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }.inner;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn fmt13(comptime S: bool) InstrFn {
 | 
				
			||||||
 | 
					    return struct {
 | 
				
			||||||
 | 
					        fn inner(cpu: *Arm7tdmi, _: *Bus, opcode: u16) void {
 | 
				
			||||||
 | 
					            // ADD
 | 
				
			||||||
 | 
					            const offset = (opcode & 0x7F) << 2;
 | 
				
			||||||
 | 
					            cpu.r[13] = if (S) cpu.r[13] - offset else cpu.r[13] + offset;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }.inner;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										150
									
								
								src/core/cpu/thumb/data_transfer.zig
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										150
									
								
								src/core/cpu/thumb/data_transfer.zig
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,150 @@
 | 
				
			|||||||
 | 
					const std = @import("std");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const Bus = @import("../../Bus.zig");
 | 
				
			||||||
 | 
					const Arm7tdmi = @import("../../cpu.zig").Arm7tdmi;
 | 
				
			||||||
 | 
					const InstrFn = @import("../../cpu.zig").thumb.InstrFn;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const rotr = @import("../../../util.zig").rotr;
 | 
				
			||||||
 | 
					const sext = @import("../../../util.zig").sext;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn fmt6(comptime rd: u3) InstrFn {
 | 
				
			||||||
 | 
					    return struct {
 | 
				
			||||||
 | 
					        fn inner(cpu: *Arm7tdmi, bus: *Bus, opcode: u16) void {
 | 
				
			||||||
 | 
					            // LDR
 | 
				
			||||||
 | 
					            const offset = (opcode & 0xFF) << 2;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // Bit 1 of the PC intentionally ignored
 | 
				
			||||||
 | 
					            cpu.r[rd] = bus.read(u32, (cpu.r[15] & ~@as(u32, 2)) + offset);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }.inner;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn fmt78(comptime op: u2, comptime T: bool) InstrFn {
 | 
				
			||||||
 | 
					    return struct {
 | 
				
			||||||
 | 
					        fn inner(cpu: *Arm7tdmi, bus: *Bus, opcode: u16) void {
 | 
				
			||||||
 | 
					            const ro = opcode >> 6 & 0x7;
 | 
				
			||||||
 | 
					            const rb = opcode >> 3 & 0x7;
 | 
				
			||||||
 | 
					            const rd = opcode & 0x7;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            const address = cpu.r[rb] +% cpu.r[ro];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (T) {
 | 
				
			||||||
 | 
					                // Format 8
 | 
				
			||||||
 | 
					                switch (op) {
 | 
				
			||||||
 | 
					                    0b00 => {
 | 
				
			||||||
 | 
					                        // STRH
 | 
				
			||||||
 | 
					                        bus.write(u16, address, @truncate(u16, cpu.r[rd]));
 | 
				
			||||||
 | 
					                    },
 | 
				
			||||||
 | 
					                    0b01 => {
 | 
				
			||||||
 | 
					                        // LDSB
 | 
				
			||||||
 | 
					                        cpu.r[rd] = sext(u32, u8, bus.read(u8, address));
 | 
				
			||||||
 | 
					                    },
 | 
				
			||||||
 | 
					                    0b10 => {
 | 
				
			||||||
 | 
					                        // LDRH
 | 
				
			||||||
 | 
					                        const value = bus.read(u16, address);
 | 
				
			||||||
 | 
					                        cpu.r[rd] = rotr(u32, value, 8 * (address & 1));
 | 
				
			||||||
 | 
					                    },
 | 
				
			||||||
 | 
					                    0b11 => {
 | 
				
			||||||
 | 
					                        // LDRSH
 | 
				
			||||||
 | 
					                        cpu.r[rd] = if (address & 1 == 1) blk: {
 | 
				
			||||||
 | 
					                            break :blk sext(u32, u8, bus.read(u8, address));
 | 
				
			||||||
 | 
					                        } else blk: {
 | 
				
			||||||
 | 
					                            break :blk sext(u32, u16, bus.read(u16, address));
 | 
				
			||||||
 | 
					                        };
 | 
				
			||||||
 | 
					                    },
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					                // Format 7
 | 
				
			||||||
 | 
					                switch (op) {
 | 
				
			||||||
 | 
					                    0b00 => {
 | 
				
			||||||
 | 
					                        // STR
 | 
				
			||||||
 | 
					                        bus.write(u32, address, cpu.r[rd]);
 | 
				
			||||||
 | 
					                    },
 | 
				
			||||||
 | 
					                    0b01 => {
 | 
				
			||||||
 | 
					                        // STRB
 | 
				
			||||||
 | 
					                        bus.write(u8, address, @truncate(u8, cpu.r[rd]));
 | 
				
			||||||
 | 
					                    },
 | 
				
			||||||
 | 
					                    0b10 => {
 | 
				
			||||||
 | 
					                        // LDR
 | 
				
			||||||
 | 
					                        const value = bus.read(u32, address);
 | 
				
			||||||
 | 
					                        cpu.r[rd] = rotr(u32, value, 8 * (address & 0x3));
 | 
				
			||||||
 | 
					                    },
 | 
				
			||||||
 | 
					                    0b11 => {
 | 
				
			||||||
 | 
					                        // LDRB
 | 
				
			||||||
 | 
					                        cpu.r[rd] = bus.read(u8, address);
 | 
				
			||||||
 | 
					                    },
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }.inner;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn fmt9(comptime B: bool, comptime L: bool, comptime offset: u5) InstrFn {
 | 
				
			||||||
 | 
					    return struct {
 | 
				
			||||||
 | 
					        fn inner(cpu: *Arm7tdmi, bus: *Bus, opcode: u16) void {
 | 
				
			||||||
 | 
					            const rb = opcode >> 3 & 0x7;
 | 
				
			||||||
 | 
					            const rd = opcode & 0x7;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (L) {
 | 
				
			||||||
 | 
					                if (B) {
 | 
				
			||||||
 | 
					                    // LDRB
 | 
				
			||||||
 | 
					                    const address = cpu.r[rb] + offset;
 | 
				
			||||||
 | 
					                    cpu.r[rd] = bus.read(u8, address);
 | 
				
			||||||
 | 
					                } else {
 | 
				
			||||||
 | 
					                    // LDR
 | 
				
			||||||
 | 
					                    const address = cpu.r[rb] + (@as(u32, offset) << 2);
 | 
				
			||||||
 | 
					                    const value = bus.read(u32, address);
 | 
				
			||||||
 | 
					                    cpu.r[rd] = rotr(u32, value, 8 * (address & 0x3));
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					                if (B) {
 | 
				
			||||||
 | 
					                    // STRB
 | 
				
			||||||
 | 
					                    const address = cpu.r[rb] + offset;
 | 
				
			||||||
 | 
					                    bus.write(u8, address, @truncate(u8, cpu.r[rd]));
 | 
				
			||||||
 | 
					                } else {
 | 
				
			||||||
 | 
					                    // STR
 | 
				
			||||||
 | 
					                    const address = cpu.r[rb] + (@as(u32, offset) << 2);
 | 
				
			||||||
 | 
					                    bus.write(u32, address, cpu.r[rd]);
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }.inner;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn fmt10(comptime L: bool, comptime offset: u5) InstrFn {
 | 
				
			||||||
 | 
					    return struct {
 | 
				
			||||||
 | 
					        fn inner(cpu: *Arm7tdmi, bus: *Bus, opcode: u16) void {
 | 
				
			||||||
 | 
					            const rb = opcode >> 3 & 0x7;
 | 
				
			||||||
 | 
					            const rd = opcode & 0x7;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            const address = cpu.r[rb] + (@as(u6, offset) << 1);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (L) {
 | 
				
			||||||
 | 
					                // LDRH
 | 
				
			||||||
 | 
					                const value = bus.read(u16, address);
 | 
				
			||||||
 | 
					                cpu.r[rd] = rotr(u32, value, 8 * (address & 1));
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					                // STRH
 | 
				
			||||||
 | 
					                bus.write(u16, address, @truncate(u16, cpu.r[rd]));
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }.inner;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn fmt11(comptime L: bool, comptime rd: u3) InstrFn {
 | 
				
			||||||
 | 
					    return struct {
 | 
				
			||||||
 | 
					        fn inner(cpu: *Arm7tdmi, bus: *Bus, opcode: u16) void {
 | 
				
			||||||
 | 
					            const offset = (opcode & 0xFF) << 2;
 | 
				
			||||||
 | 
					            const address = cpu.r[13] + offset;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (L) {
 | 
				
			||||||
 | 
					                // LDR
 | 
				
			||||||
 | 
					                const value = bus.read(u32, address);
 | 
				
			||||||
 | 
					                cpu.r[rd] = rotr(u32, value, 8 * (address & 0x3));
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					                // STR
 | 
				
			||||||
 | 
					                bus.write(u32, address, cpu.r[rd]);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }.inner;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										23
									
								
								src/core/cpu/thumb/software_interrupt.zig
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								src/core/cpu/thumb/software_interrupt.zig
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,23 @@
 | 
				
			|||||||
 | 
					const Bus = @import("../../Bus.zig");
 | 
				
			||||||
 | 
					const Arm7tdmi = @import("../../cpu.zig").Arm7tdmi;
 | 
				
			||||||
 | 
					const InstrFn = @import("../../cpu.zig").thumb.InstrFn;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn fmt17() InstrFn {
 | 
				
			||||||
 | 
					    return struct {
 | 
				
			||||||
 | 
					        fn inner(cpu: *Arm7tdmi, _: *Bus, _: u16) void {
 | 
				
			||||||
 | 
					            // Copy Values from Current Mode
 | 
				
			||||||
 | 
					            const ret_addr = cpu.r[15] - 2;
 | 
				
			||||||
 | 
					            const cpsr = cpu.cpsr.raw;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // Switch Mode
 | 
				
			||||||
 | 
					            cpu.changeMode(.Supervisor);
 | 
				
			||||||
 | 
					            cpu.cpsr.t.write(false); // Force ARM Mode
 | 
				
			||||||
 | 
					            cpu.cpsr.i.write(true); // Disable normal interrupts
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            cpu.r[14] = ret_addr; // Resume Execution
 | 
				
			||||||
 | 
					            cpu.spsr.raw = cpsr; // Previous mode CPSR
 | 
				
			||||||
 | 
					            cpu.r[15] = 0x0000_0008;
 | 
				
			||||||
 | 
					            cpu.pipe.reload(cpu);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }.inner;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										162
									
								
								src/core/emu.zig
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										162
									
								
								src/core/emu.zig
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,162 @@
 | 
				
			|||||||
 | 
					const std = @import("std");
 | 
				
			||||||
 | 
					const SDL = @import("sdl2");
 | 
				
			||||||
 | 
					const config = @import("../config.zig");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const Bus = @import("Bus.zig");
 | 
				
			||||||
 | 
					const Scheduler = @import("scheduler.zig").Scheduler;
 | 
				
			||||||
 | 
					const Arm7tdmi = @import("cpu.zig").Arm7tdmi;
 | 
				
			||||||
 | 
					const FpsTracker = @import("../util.zig").FpsTracker;
 | 
				
			||||||
 | 
					const FilePaths = @import("../util.zig").FilePaths;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const Timer = std.time.Timer;
 | 
				
			||||||
 | 
					const Thread = std.Thread;
 | 
				
			||||||
 | 
					const Atomic = std.atomic.Atomic;
 | 
				
			||||||
 | 
					const Allocator = std.mem.Allocator;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// 228 Lines which consist of 308 dots (which are 4 cycles long)
 | 
				
			||||||
 | 
					const cycles_per_frame: u64 = 228 * (308 * 4); //280896
 | 
				
			||||||
 | 
					const clock_rate: u64 = 1 << 24; // 16.78MHz
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// TODO: Don't truncate this, be more accurate w/ timing
 | 
				
			||||||
 | 
					// 59.6046447754ns (truncated to just 59ns)
 | 
				
			||||||
 | 
					const clock_period: u64 = std.time.ns_per_s / clock_rate;
 | 
				
			||||||
 | 
					const frame_period = (clock_period * cycles_per_frame);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// 59.7275005696Hz
 | 
				
			||||||
 | 
					pub const frame_rate = @intToFloat(f64, std.time.ns_per_s) /
 | 
				
			||||||
 | 
					    ((@intToFloat(f64, std.time.ns_per_s) / @intToFloat(f64, clock_rate)) * @intToFloat(f64, cycles_per_frame));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const log = std.log.scoped(.Emulation);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const RunKind = enum {
 | 
				
			||||||
 | 
					    Unlimited,
 | 
				
			||||||
 | 
					    UnlimitedFPS,
 | 
				
			||||||
 | 
					    Limited,
 | 
				
			||||||
 | 
					    LimitedFPS,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn run(quit: *Atomic(bool), scheduler: *Scheduler, cpu: *Arm7tdmi, tracker: *FpsTracker) void {
 | 
				
			||||||
 | 
					    const audio_sync = config.config().guest.audio_sync;
 | 
				
			||||||
 | 
					    if (audio_sync) log.info("Audio sync enabled", .{});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (config.config().guest.video_sync) {
 | 
				
			||||||
 | 
					        inner(.LimitedFPS, audio_sync, quit, scheduler, cpu, tracker);
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					        inner(.UnlimitedFPS, audio_sync, quit, scheduler, cpu, tracker);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fn inner(comptime kind: RunKind, audio_sync: bool, quit: *Atomic(bool), scheduler: *Scheduler, cpu: *Arm7tdmi, tracker: ?*FpsTracker) void {
 | 
				
			||||||
 | 
					    if (kind == .UnlimitedFPS or kind == .LimitedFPS) {
 | 
				
			||||||
 | 
					        std.debug.assert(tracker != null);
 | 
				
			||||||
 | 
					        log.info("FPS tracking enabled", .{});
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    switch (kind) {
 | 
				
			||||||
 | 
					        .Unlimited, .UnlimitedFPS => {
 | 
				
			||||||
 | 
					            log.info("Emulation w/out video sync", .{});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            while (!quit.load(.SeqCst)) {
 | 
				
			||||||
 | 
					                runFrame(scheduler, cpu);
 | 
				
			||||||
 | 
					                audioSync(audio_sync, cpu.bus.apu.stream, &cpu.bus.apu.is_buffer_full);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                if (kind == .UnlimitedFPS) tracker.?.tick();
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        .Limited, .LimitedFPS => {
 | 
				
			||||||
 | 
					            log.info("Emulation w/ video sync", .{});
 | 
				
			||||||
 | 
					            var timer = Timer.start() catch @panic("failed to initalize std.timer.Timer");
 | 
				
			||||||
 | 
					            var wake_time: u64 = frame_period;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            while (!quit.load(.SeqCst)) {
 | 
				
			||||||
 | 
					                runFrame(scheduler, cpu);
 | 
				
			||||||
 | 
					                const new_wake_time = videoSync(&timer, wake_time);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                // Spin to make up the difference of OS scheduler innacuracies
 | 
				
			||||||
 | 
					                // If we happen to also be syncing to audio, we choose to spin on
 | 
				
			||||||
 | 
					                // the amount of time needed for audio to catch up rather than
 | 
				
			||||||
 | 
					                // our expected wake-up time
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                audioSync(audio_sync, cpu.bus.apu.stream, &cpu.bus.apu.is_buffer_full);
 | 
				
			||||||
 | 
					                if (!audio_sync) spinLoop(&timer, wake_time);
 | 
				
			||||||
 | 
					                wake_time = new_wake_time;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                if (kind == .LimitedFPS) tracker.?.tick();
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn runFrame(sched: *Scheduler, cpu: *Arm7tdmi) void {
 | 
				
			||||||
 | 
					    const frame_end = sched.tick + cycles_per_frame;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    while (sched.tick < frame_end) {
 | 
				
			||||||
 | 
					        if (!cpu.stepDmaTransfer()) {
 | 
				
			||||||
 | 
					            if (cpu.isHalted()) {
 | 
				
			||||||
 | 
					                // Fast-forward to next Event
 | 
				
			||||||
 | 
					                sched.tick = sched.queue.peek().?.tick;
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					                cpu.step();
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (sched.tick >= sched.nextTimestamp()) sched.handleEvent(cpu);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fn audioSync(audio_sync: bool, stream: *SDL.SDL_AudioStream, is_buffer_full: *bool) void {
 | 
				
			||||||
 | 
					    const sample_size = 2 * @sizeOf(u16);
 | 
				
			||||||
 | 
					    const max_buf_size: c_int = 0x400;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Determine whether the APU is busy right at this moment
 | 
				
			||||||
 | 
					    var still_full: bool = SDL.SDL_AudioStreamAvailable(stream) > sample_size * if (is_buffer_full.*) max_buf_size >> 1 else max_buf_size;
 | 
				
			||||||
 | 
					    defer is_buffer_full.* = still_full; // Update APU Busy status right before exiting scope
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // If Busy is false, there's no need to sync here
 | 
				
			||||||
 | 
					    if (!still_full) return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    while (true) {
 | 
				
			||||||
 | 
					        still_full = SDL.SDL_AudioStreamAvailable(stream) > sample_size * max_buf_size >> 1;
 | 
				
			||||||
 | 
					        if (!audio_sync or !still_full) break;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fn videoSync(timer: *Timer, wake_time: u64) u64 {
 | 
				
			||||||
 | 
					    // Use the OS scheduler to put the emulation thread to sleep
 | 
				
			||||||
 | 
					    const recalculated = sleep(timer, wake_time);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // If sleep() determined we need to adjust our wake up time, do so
 | 
				
			||||||
 | 
					    // otherwise predict our next wake up time according to the frame period
 | 
				
			||||||
 | 
					    return recalculated orelse wake_time + frame_period;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// TODO: Better sleep impl?
 | 
				
			||||||
 | 
					fn sleep(timer: *Timer, wake_time: u64) ?u64 {
 | 
				
			||||||
 | 
					    // const step = std.time.ns_per_ms * 10; // 10ms
 | 
				
			||||||
 | 
					    const timestamp = timer.read();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // ns_late is non zero if we are late.
 | 
				
			||||||
 | 
					    const ns_late = timestamp -| wake_time;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // If we're more than a frame late, skip the rest of this loop
 | 
				
			||||||
 | 
					    // Recalculate what our new wake time should be so that we can
 | 
				
			||||||
 | 
					    // get "back on track"
 | 
				
			||||||
 | 
					    if (ns_late > frame_period) return timestamp + frame_period;
 | 
				
			||||||
 | 
					    const sleep_for = frame_period - ns_late;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // // Employ several sleep calls in periods of 10ms
 | 
				
			||||||
 | 
					    // // By doing this the behaviour should average out to be
 | 
				
			||||||
 | 
					    // // more consistent
 | 
				
			||||||
 | 
					    // const loop_count = sleep_for / step; // How many groups of 10ms
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // var i: usize = 0;
 | 
				
			||||||
 | 
					    // while (i < loop_count) : (i += 1) std.time.sleep(step);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    std.time.sleep(sleep_for);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return null;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fn spinLoop(timer: *Timer, wake_time: u64) void {
 | 
				
			||||||
 | 
					    while (true) if (timer.read() > wake_time) break;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										1114
									
								
								src/core/ppu.zig
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1114
									
								
								src/core/ppu.zig
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										40
									
								
								src/core/ppu/Oam.zig
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										40
									
								
								src/core/ppu/Oam.zig
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,40 @@
 | 
				
			|||||||
 | 
					const std = @import("std");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const Allocator = std.mem.Allocator;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const buf_len = 0x400;
 | 
				
			||||||
 | 
					const Self = @This();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					buf: []u8,
 | 
				
			||||||
 | 
					allocator: Allocator,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn read(self: *const Self, comptime T: type, address: usize) T {
 | 
				
			||||||
 | 
					    const addr = address & 0x3FF;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return switch (T) {
 | 
				
			||||||
 | 
					        u32, u16, u8 => std.mem.readIntSliceLittle(T, self.buf[addr..][0..@sizeOf(T)]),
 | 
				
			||||||
 | 
					        else => @compileError("OAM: Unsupported read width"),
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn write(self: *Self, comptime T: type, address: usize, value: T) void {
 | 
				
			||||||
 | 
					    const addr = address & 0x3FF;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    switch (T) {
 | 
				
			||||||
 | 
					        u32, u16 => std.mem.writeIntSliceLittle(T, self.buf[addr..][0..@sizeOf(T)], value),
 | 
				
			||||||
 | 
					        u8 => return, // 8-bit writes are explicitly ignored
 | 
				
			||||||
 | 
					        else => @compileError("OAM: Unsupported write width"),
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn init(allocator: Allocator) !Self {
 | 
				
			||||||
 | 
					    const buf = try allocator.alloc(u8, buf_len);
 | 
				
			||||||
 | 
					    std.mem.set(u8, buf, 0);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return Self{ .buf = buf, .allocator = allocator };
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn deinit(self: *Self) void {
 | 
				
			||||||
 | 
					    self.allocator.free(self.buf);
 | 
				
			||||||
 | 
					    self.* = undefined;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										47
									
								
								src/core/ppu/Palette.zig
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										47
									
								
								src/core/ppu/Palette.zig
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,47 @@
 | 
				
			|||||||
 | 
					const std = @import("std");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const Allocator = std.mem.Allocator;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const buf_len = 0x400;
 | 
				
			||||||
 | 
					const Self = @This();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					buf: []u8,
 | 
				
			||||||
 | 
					allocator: Allocator,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn read(self: *const Self, comptime T: type, address: usize) T {
 | 
				
			||||||
 | 
					    const addr = address & 0x3FF;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return switch (T) {
 | 
				
			||||||
 | 
					        u32, u16, u8 => std.mem.readIntSliceLittle(T, self.buf[addr..][0..@sizeOf(T)]),
 | 
				
			||||||
 | 
					        else => @compileError("PALRAM: Unsupported read width"),
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn write(self: *Self, comptime T: type, address: usize, value: T) void {
 | 
				
			||||||
 | 
					    const addr = address & 0x3FF;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    switch (T) {
 | 
				
			||||||
 | 
					        u32, u16 => std.mem.writeIntSliceLittle(T, self.buf[addr..][0..@sizeOf(T)], value),
 | 
				
			||||||
 | 
					        u8 => {
 | 
				
			||||||
 | 
					            const align_addr = addr & ~@as(u32, 1); // Aligned to Halfword boundary
 | 
				
			||||||
 | 
					            std.mem.writeIntSliceLittle(u16, self.buf[align_addr..][0..@sizeOf(u16)], @as(u16, value) * 0x101);
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        else => @compileError("PALRAM: Unsupported write width"),
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn init(allocator: Allocator) !Self {
 | 
				
			||||||
 | 
					    const buf = try allocator.alloc(u8, buf_len);
 | 
				
			||||||
 | 
					    std.mem.set(u8, buf, 0);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return Self{ .buf = buf, .allocator = allocator };
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn deinit(self: *Self) void {
 | 
				
			||||||
 | 
					    self.allocator.free(self.buf);
 | 
				
			||||||
 | 
					    self.* = undefined;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn backdrop(self: *const Self) u16 {
 | 
				
			||||||
 | 
					    return self.read(u16, 0);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										60
									
								
								src/core/ppu/Vram.zig
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										60
									
								
								src/core/ppu/Vram.zig
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,60 @@
 | 
				
			|||||||
 | 
					const std = @import("std");
 | 
				
			||||||
 | 
					const io = @import("../bus/io.zig");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const Allocator = std.mem.Allocator;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const buf_len = 0x18000;
 | 
				
			||||||
 | 
					const Self = @This();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					buf: []u8,
 | 
				
			||||||
 | 
					allocator: Allocator,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn read(self: *const Self, comptime T: type, address: usize) T {
 | 
				
			||||||
 | 
					    const addr = Self.mirror(address);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return switch (T) {
 | 
				
			||||||
 | 
					        u32, u16, u8 => std.mem.readIntSliceLittle(T, self.buf[addr..][0..@sizeOf(T)]),
 | 
				
			||||||
 | 
					        else => @compileError("VRAM: Unsupported read width"),
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn write(self: *Self, comptime T: type, dispcnt: io.DisplayControl, address: usize, value: T) void {
 | 
				
			||||||
 | 
					    const mode: u3 = dispcnt.bg_mode.read();
 | 
				
			||||||
 | 
					    const idx = Self.mirror(address);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    switch (T) {
 | 
				
			||||||
 | 
					        u32, u16 => std.mem.writeIntSliceLittle(T, self.buf[idx..][0..@sizeOf(T)], value),
 | 
				
			||||||
 | 
					        u8 => {
 | 
				
			||||||
 | 
					            // Ignore write if it falls within the boundaries of OBJ VRAM
 | 
				
			||||||
 | 
					            switch (mode) {
 | 
				
			||||||
 | 
					                0, 1, 2 => if (0x0001_0000 <= idx) return,
 | 
				
			||||||
 | 
					                else => if (0x0001_4000 <= idx) return,
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            const align_idx = idx & ~@as(u32, 1); // Aligned to a halfword boundary
 | 
				
			||||||
 | 
					            std.mem.writeIntSliceLittle(u16, self.buf[align_idx..][0..@sizeOf(u16)], @as(u16, value) * 0x101);
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        else => @compileError("VRAM: Unsupported write width"),
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn init(allocator: Allocator) !Self {
 | 
				
			||||||
 | 
					    const buf = try allocator.alloc(u8, buf_len);
 | 
				
			||||||
 | 
					    std.mem.set(u8, buf, 0);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return Self{ .buf = buf, .allocator = allocator };
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn deinit(self: *Self) void {
 | 
				
			||||||
 | 
					    self.allocator.free(self.buf);
 | 
				
			||||||
 | 
					    self.* = undefined;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fn mirror(address: usize) usize {
 | 
				
			||||||
 | 
					    // Mirrored in steps of 128K (64K + 32K + 32K) (abcc)
 | 
				
			||||||
 | 
					    const addr = address & 0x1FFFF;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // If the address is within 96K we don't do anything,
 | 
				
			||||||
 | 
					    // otherwise we want to mirror the last 32K (addresses between 64K and 96K)
 | 
				
			||||||
 | 
					    return if (addr < buf_len) addr else 0x10000 + (addr & 0x7FFF);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										130
									
								
								src/core/scheduler.zig
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										130
									
								
								src/core/scheduler.zig
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,130 @@
 | 
				
			|||||||
 | 
					const std = @import("std");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const Bus = @import("Bus.zig");
 | 
				
			||||||
 | 
					const Arm7tdmi = @import("cpu.zig").Arm7tdmi;
 | 
				
			||||||
 | 
					const Clock = @import("bus/gpio.zig").Clock;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const Order = std.math.Order;
 | 
				
			||||||
 | 
					const PriorityQueue = std.PriorityQueue;
 | 
				
			||||||
 | 
					const Allocator = std.mem.Allocator;
 | 
				
			||||||
 | 
					const log = std.log.scoped(.Scheduler);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub const Scheduler = struct {
 | 
				
			||||||
 | 
					    const Self = @This();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    tick: u64,
 | 
				
			||||||
 | 
					    queue: PriorityQueue(Event, void, lessThan),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn init(allocator: Allocator) Self {
 | 
				
			||||||
 | 
					        var sched = Self{ .tick = 0, .queue = PriorityQueue(Event, void, lessThan).init(allocator, {}) };
 | 
				
			||||||
 | 
					        sched.queue.add(.{ .kind = .HeatDeath, .tick = std.math.maxInt(u64) }) catch unreachable;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return sched;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn deinit(self: *Self) void {
 | 
				
			||||||
 | 
					        self.queue.deinit();
 | 
				
			||||||
 | 
					        self.* = undefined;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub inline fn now(self: *const Self) u64 {
 | 
				
			||||||
 | 
					        return self.tick;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn handleEvent(self: *Self, cpu: *Arm7tdmi) void {
 | 
				
			||||||
 | 
					        if (self.queue.removeOrNull()) |event| {
 | 
				
			||||||
 | 
					            const late = self.tick - event.tick;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            switch (event.kind) {
 | 
				
			||||||
 | 
					                .HeatDeath => {
 | 
				
			||||||
 | 
					                    log.err("u64 overflow. This *actually* should never happen.", .{});
 | 
				
			||||||
 | 
					                    unreachable;
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					                .Draw => {
 | 
				
			||||||
 | 
					                    // The end of a VDraw
 | 
				
			||||||
 | 
					                    cpu.bus.ppu.drawScanline();
 | 
				
			||||||
 | 
					                    cpu.bus.ppu.onHdrawEnd(cpu, late);
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					                .TimerOverflow => |id| {
 | 
				
			||||||
 | 
					                    switch (id) {
 | 
				
			||||||
 | 
					                        0 => cpu.bus.tim[0].onTimerExpire(cpu, late),
 | 
				
			||||||
 | 
					                        1 => cpu.bus.tim[1].onTimerExpire(cpu, late),
 | 
				
			||||||
 | 
					                        2 => cpu.bus.tim[2].onTimerExpire(cpu, late),
 | 
				
			||||||
 | 
					                        3 => cpu.bus.tim[3].onTimerExpire(cpu, late),
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					                .ApuChannel => |id| {
 | 
				
			||||||
 | 
					                    switch (id) {
 | 
				
			||||||
 | 
					                        0 => cpu.bus.apu.ch1.onToneSweepEvent(late),
 | 
				
			||||||
 | 
					                        1 => cpu.bus.apu.ch2.onToneEvent(late),
 | 
				
			||||||
 | 
					                        2 => cpu.bus.apu.ch3.onWaveEvent(late),
 | 
				
			||||||
 | 
					                        3 => cpu.bus.apu.ch4.onNoiseEvent(late),
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					                .RealTimeClock => {
 | 
				
			||||||
 | 
					                    const device = &cpu.bus.pak.gpio.device;
 | 
				
			||||||
 | 
					                    if (device.kind != .Rtc or device.ptr == null) return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    const clock = @ptrCast(*Clock, @alignCast(@alignOf(*Clock), device.ptr.?));
 | 
				
			||||||
 | 
					                    clock.onClockUpdate(late);
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					                .FrameSequencer => cpu.bus.apu.onSequencerTick(late),
 | 
				
			||||||
 | 
					                .SampleAudio => cpu.bus.apu.sampleAudio(late),
 | 
				
			||||||
 | 
					                .HBlank => cpu.bus.ppu.onHblankEnd(cpu, late), // The end of a HBlank
 | 
				
			||||||
 | 
					                .VBlank => cpu.bus.ppu.onHdrawEnd(cpu, late), // The end of a VBlank
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /// Removes the **first** scheduled event of type `needle`
 | 
				
			||||||
 | 
					    pub fn removeScheduledEvent(self: *Self, needle: EventKind) void {
 | 
				
			||||||
 | 
					        var it = self.queue.iterator();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        var i: usize = 0;
 | 
				
			||||||
 | 
					        while (it.next()) |event| : (i += 1) {
 | 
				
			||||||
 | 
					            if (std.meta.eql(event.kind, needle)) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                // This invalidates the iterator
 | 
				
			||||||
 | 
					                _ = self.queue.removeIndex(i);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                // Since removing something from the PQ invalidates the iterator,
 | 
				
			||||||
 | 
					                // this implementation can safely only remove the first instance of
 | 
				
			||||||
 | 
					                // a Scheduled Event. Exit Early
 | 
				
			||||||
 | 
					                break;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn push(self: *Self, kind: EventKind, end: u64) void {
 | 
				
			||||||
 | 
					        self.queue.add(.{ .kind = kind, .tick = self.now() + end }) catch unreachable;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub inline fn nextTimestamp(self: *const Self) u64 {
 | 
				
			||||||
 | 
					        @setRuntimeSafety(false);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Typically you'd use PriorityQueue.peek here, but there's always at least a HeatDeath
 | 
				
			||||||
 | 
					        // event in the PQ so we can just do this instead. Should be faster in ReleaseSafe
 | 
				
			||||||
 | 
					        return self.queue.items[0].tick;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub const Event = struct {
 | 
				
			||||||
 | 
					    kind: EventKind,
 | 
				
			||||||
 | 
					    tick: u64,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fn lessThan(_: void, a: Event, b: Event) Order {
 | 
				
			||||||
 | 
					    return std.math.order(a.tick, b.tick);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub const EventKind = union(enum) {
 | 
				
			||||||
 | 
					    HeatDeath,
 | 
				
			||||||
 | 
					    HBlank,
 | 
				
			||||||
 | 
					    VBlank,
 | 
				
			||||||
 | 
					    Draw,
 | 
				
			||||||
 | 
					    TimerOverflow: u2,
 | 
				
			||||||
 | 
					    SampleAudio,
 | 
				
			||||||
 | 
					    FrameSequencer,
 | 
				
			||||||
 | 
					    ApuChannel: u2,
 | 
				
			||||||
 | 
					    RealTimeClock,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
							
								
								
									
										152
									
								
								src/cpu.zig
									
									
									
									
									
								
							
							
						
						
									
										152
									
								
								src/cpu.zig
									
									
									
									
									
								
							@@ -1,152 +0,0 @@
 | 
				
			|||||||
const std = @import("std");
 | 
					 | 
				
			||||||
const Bus = @import("bus.zig").Bus;
 | 
					 | 
				
			||||||
const Scheduler = @import("scheduler.zig").Scheduler;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const comptimeDataProcessing = @import("cpu/data_processing.zig").comptimeDataProcessing;
 | 
					 | 
				
			||||||
const comptimeSingleDataTransfer = @import("cpu/single_data_transfer.zig").comptimeSingleDataTransfer;
 | 
					 | 
				
			||||||
const comptimeHalfSignedDataTransfer = @import("cpu/half_signed_data_transfer.zig").comptimeHalfSignedDataTransfer;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
pub const InstrFn = fn (*ARM7TDMI, *Bus, u32) void;
 | 
					 | 
				
			||||||
const ARM_LUT: [0x1000]InstrFn = populate();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
pub const ARM7TDMI = struct {
 | 
					 | 
				
			||||||
    r: [16]u32,
 | 
					 | 
				
			||||||
    sch: *Scheduler,
 | 
					 | 
				
			||||||
    bus: *Bus,
 | 
					 | 
				
			||||||
    cpsr: CPSR,
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    pub fn new(scheduler: *Scheduler, bus: *Bus) @This() {
 | 
					 | 
				
			||||||
        const cpsr: u32 = 0x0000_00DF;
 | 
					 | 
				
			||||||
        return .{
 | 
					 | 
				
			||||||
            .r = [_]u32{0x00} ** 16,
 | 
					 | 
				
			||||||
            .sch = scheduler,
 | 
					 | 
				
			||||||
            .bus = bus,
 | 
					 | 
				
			||||||
            .cpsr = @bitCast(CPSR, cpsr),
 | 
					 | 
				
			||||||
        };
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    pub inline fn step(self: *@This()) u64 {
 | 
					 | 
				
			||||||
        const opcode = self.fetch();
 | 
					 | 
				
			||||||
        // Debug
 | 
					 | 
				
			||||||
        std.debug.print("R15: 0x{X:}\n", .{ opcode });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        ARM_LUT[armIdx(opcode)](self, self.bus, opcode);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        return 1;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    fn fetch(self: *@This()) u32 {
 | 
					 | 
				
			||||||
        const word = self.bus.readWord(self.r[15]);
 | 
					 | 
				
			||||||
        self.r[15] += 4;
 | 
					 | 
				
			||||||
        return word;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    fn fakePC(self: *const @This()) u32 {
 | 
					 | 
				
			||||||
        return self.r[15] + 4;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
fn armIdx(opcode: u32) u12 {
 | 
					 | 
				
			||||||
    return @truncate(u12, opcode >> 20 & 0xFF) << 4 | @truncate(u12, opcode >> 8 & 0xF);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
fn populate() [0x1000]InstrFn {
 | 
					 | 
				
			||||||
    return comptime {
 | 
					 | 
				
			||||||
        @setEvalBranchQuota(0x5000);
 | 
					 | 
				
			||||||
        var lut = [_]InstrFn{undefined_instr} ** 0x1000;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        var i: usize = 0;
 | 
					 | 
				
			||||||
        while (i < lut.len) : (i += 1) {
 | 
					 | 
				
			||||||
            if (i >> 10 & 0x3 == 0b00) {
 | 
					 | 
				
			||||||
                const I = i >> 9 & 0x01 == 0x01;
 | 
					 | 
				
			||||||
                const S = i >> 4 & 0x01 == 0x01;
 | 
					 | 
				
			||||||
                const instrKind = i >> 5 & 0x0F;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                lut[i] = comptimeDataProcessing(I, S, instrKind);
 | 
					 | 
				
			||||||
            } 
 | 
					 | 
				
			||||||
            
 | 
					 | 
				
			||||||
            if (i >> 9 & 0x7 == 0b000 and i >> 6 & 0x01 == 0x00 and i & 0xF == 0x0) {
 | 
					 | 
				
			||||||
                // Halfword and Signed Data Transfer with register offset
 | 
					 | 
				
			||||||
                const P = i >> 8 & 0x01 == 0x01;
 | 
					 | 
				
			||||||
                const U = i >> 7 & 0x01 == 0x01;
 | 
					 | 
				
			||||||
                const I = true;
 | 
					 | 
				
			||||||
                const W = i >> 5 & 0x01 == 0x01;
 | 
					 | 
				
			||||||
                const L = i >> 4 & 0x01 == 0x01;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                lut[i] = comptimeHalfSignedDataTransfer(P, U, I, W, L);
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            if (i >> 9 & 0x7 == 0b000 and i >> 6 & 0x01 == 0x01) {
 | 
					 | 
				
			||||||
                // Halfword and Signed Data Tranfer with immediate offset
 | 
					 | 
				
			||||||
                const P = i >> 8 & 0x01 == 0x01;
 | 
					 | 
				
			||||||
                const U = i >> 7 & 0x01 == 0x01;
 | 
					 | 
				
			||||||
                const I = false;
 | 
					 | 
				
			||||||
                const W = i >> 5 & 0x01 == 0x01;
 | 
					 | 
				
			||||||
                const L = i >> 4 & 0x01 == 0x01;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                lut[i] = comptimeHalfSignedDataTransfer(P, U, I, W, L);
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            
 | 
					 | 
				
			||||||
            if (i >> 10 & 0x3 == 0b01 and i & 0x01 == 0x00) {
 | 
					 | 
				
			||||||
                const I = i >> 9 & 0x01 == 0x01;
 | 
					 | 
				
			||||||
                const P = i >> 8 & 0x01 == 0x01;
 | 
					 | 
				
			||||||
                const U = i >> 7 & 0x01 == 0x01;
 | 
					 | 
				
			||||||
                const B = i >> 6 & 0x01 == 0x01;
 | 
					 | 
				
			||||||
                const W = i >> 5 & 0x01 == 0x01;
 | 
					 | 
				
			||||||
                const L = i >> 4 & 0x01 == 0x01;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                lut[i] = comptimeSingleDataTransfer(I, P, U, B, W, L);
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            
 | 
					 | 
				
			||||||
            if (i >> 9 & 0x7 == 0b101) {
 | 
					 | 
				
			||||||
                const L = i >> 8 & 0x01 == 0x01;
 | 
					 | 
				
			||||||
                lut[i] = comptimeBranch(L);
 | 
					 | 
				
			||||||
            } 
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        return lut;
 | 
					 | 
				
			||||||
    };
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const CPSR = packed struct {
 | 
					 | 
				
			||||||
    n: bool, // Negative / Less Than
 | 
					 | 
				
			||||||
    z: bool, // Zero
 | 
					 | 
				
			||||||
    c: bool, // Carry / Borrow / Extend
 | 
					 | 
				
			||||||
    v: bool, // Overflow
 | 
					 | 
				
			||||||
    _: u20,
 | 
					 | 
				
			||||||
    i: bool, // IRQ Disable
 | 
					 | 
				
			||||||
    f: bool, // FIQ Diable
 | 
					 | 
				
			||||||
    t: bool, // State 
 | 
					 | 
				
			||||||
    m: Mode, // Mode 
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const Mode = enum(u5) {
 | 
					 | 
				
			||||||
    User = 0b10000,
 | 
					 | 
				
			||||||
    Fiq = 0b10001,
 | 
					 | 
				
			||||||
    Irq = 0b10010,
 | 
					 | 
				
			||||||
    Supervisor = 0b10011,
 | 
					 | 
				
			||||||
    Abort = 0b10111,
 | 
					 | 
				
			||||||
    Undefined = 0b11011,
 | 
					 | 
				
			||||||
    System = 0b11111,
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
fn undefined_instr(_: *ARM7TDMI, _: *Bus, opcode: u32) void {
 | 
					 | 
				
			||||||
    const id = armIdx(opcode);
 | 
					 | 
				
			||||||
    std.debug.panic("[0x{X:}] 0x{X:} is an illegal opcode", .{ id, opcode });
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
fn comptimeBranch(comptime L: bool) InstrFn {
 | 
					 | 
				
			||||||
    return struct {
 | 
					 | 
				
			||||||
        fn branch(cpu: *ARM7TDMI, _: *Bus, opcode: u32) void {
 | 
					 | 
				
			||||||
            if (L) {
 | 
					 | 
				
			||||||
                cpu.r[14] = cpu.r[15] - 4;
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            const offset = @bitCast(i32, (opcode << 2) << 8) >> 8;
 | 
					 | 
				
			||||||
            cpu.r[15] = cpu.fakePC() + @bitCast(u32, offset);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }.branch;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@@ -1,56 +0,0 @@
 | 
				
			|||||||
const std = @import("std");
 | 
					 | 
				
			||||||
const cpu_mod = @import("../cpu.zig");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const Bus = @import("../bus.zig").Bus;
 | 
					 | 
				
			||||||
const ARM7TDMI = cpu_mod.ARM7TDMI;
 | 
					 | 
				
			||||||
const InstrFn = cpu_mod.InstrFn;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
pub fn comptimeDataProcessing(comptime I: bool, comptime S: bool, comptime instrKind: u4) InstrFn {
 | 
					 | 
				
			||||||
    return struct {
 | 
					 | 
				
			||||||
        fn dataProcessing(cpu: *ARM7TDMI, _: *Bus, opcode: u32) void {
 | 
					 | 
				
			||||||
            const rd = opcode >> 12 & 0xF;
 | 
					 | 
				
			||||||
            const op1 = opcode >> 16 & 0xF;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            var op2: u32 = undefined;
 | 
					 | 
				
			||||||
            if (I) {
 | 
					 | 
				
			||||||
                op2 = std.math.rotr(u32, opcode & 0xFF, (opcode >> 8 & 0xF) << 1);
 | 
					 | 
				
			||||||
            } else {
 | 
					 | 
				
			||||||
                op2 = reg_op2(cpu, opcode);
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            switch (instrKind) {
 | 
					 | 
				
			||||||
                0x4 => {
 | 
					 | 
				
			||||||
                    cpu.r[rd] = cpu.r[op1] + op2;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    if (S) std.debug.panic("TODO: implement ADD condition codes", .{});
 | 
					 | 
				
			||||||
                },
 | 
					 | 
				
			||||||
                0xD => {
 | 
					 | 
				
			||||||
                    cpu.r[rd] = op2;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    if (S) std.debug.panic("TODO: implement MOV condition codes", .{});
 | 
					 | 
				
			||||||
                },
 | 
					 | 
				
			||||||
                else => std.debug.panic("TODO: implement data processing type {}", .{instrKind}),
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }.dataProcessing;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
fn reg_op2(cpu: *const ARM7TDMI, opcode: u32) u32 {
 | 
					 | 
				
			||||||
    var amount: u32 = undefined;
 | 
					 | 
				
			||||||
    if (opcode >> 4 & 0x01 == 0x01) {
 | 
					 | 
				
			||||||
        amount = cpu.r[opcode >> 8 & 0xF] & 0xFF;
 | 
					 | 
				
			||||||
    } else {
 | 
					 | 
				
			||||||
        amount = opcode >> 7 & 0x1F;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    const rm = opcode & 0xF;
 | 
					 | 
				
			||||||
    const r_val = cpu.r[rm];
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    return switch (opcode >> 5 & 0x03) {
 | 
					 | 
				
			||||||
        0b00 => r_val << @truncate(u5, amount),
 | 
					 | 
				
			||||||
        0b01 => r_val >> @truncate(u5, amount),
 | 
					 | 
				
			||||||
        0b10 => @bitCast(u32, @bitCast(i32, r_val) >> @truncate(u5, amount)),
 | 
					 | 
				
			||||||
        0b11 => std.math.rotr(u32, r_val, amount),
 | 
					 | 
				
			||||||
        else => unreachable,
 | 
					 | 
				
			||||||
    };
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@@ -1,67 +0,0 @@
 | 
				
			|||||||
const std = @import("std");
 | 
					 | 
				
			||||||
const cpu_mod = @import("../cpu.zig");
 | 
					 | 
				
			||||||
const util = @import("../util.zig");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const Bus = @import("../bus.zig").Bus;
 | 
					 | 
				
			||||||
const ARM7TDMI = cpu_mod.ARM7TDMI;
 | 
					 | 
				
			||||||
const InstrFn = cpu_mod.InstrFn;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
pub fn comptimeHalfSignedDataTransfer(comptime P: bool, comptime U: bool, comptime I: bool, comptime W: bool, comptime L: bool) InstrFn {
 | 
					 | 
				
			||||||
    return struct {
 | 
					 | 
				
			||||||
        fn halfSignedDataTransfer(cpu: *ARM7TDMI, bus: *Bus, opcode: u32) void {
 | 
					 | 
				
			||||||
            const rn = opcode >> 16 & 0xF;
 | 
					 | 
				
			||||||
            const rd = opcode >> 12 & 0xF;
 | 
					 | 
				
			||||||
            const rm = opcode & 0xF;
 | 
					 | 
				
			||||||
            const imm_offset_high = opcode >> 8 & 0xF;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            const base = cpu.r[rn];
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            var offset: u32 = undefined;
 | 
					 | 
				
			||||||
            if (I) {
 | 
					 | 
				
			||||||
                offset = imm_offset_high << 4 | rm;
 | 
					 | 
				
			||||||
            } else {
 | 
					 | 
				
			||||||
                offset = cpu.r[rm];
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            const modified_base = if (U) base + offset else base - offset;
 | 
					 | 
				
			||||||
            var address = if (P) modified_base else base;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            if (L) {
 | 
					 | 
				
			||||||
                switch(@truncate(u2, opcode >> 5)) {
 | 
					 | 
				
			||||||
                    0b00 => {
 | 
					 | 
				
			||||||
                        // SWP 
 | 
					 | 
				
			||||||
                        std.debug.panic("TODO: Implement SWP", .{});
 | 
					 | 
				
			||||||
                    },
 | 
					 | 
				
			||||||
                    0b01 => {
 | 
					 | 
				
			||||||
                        // LDRH
 | 
					 | 
				
			||||||
                        const halfword = bus.readHalfWord(address);
 | 
					 | 
				
			||||||
                        cpu.r[rd] = @as(u32, halfword);
 | 
					 | 
				
			||||||
                    },
 | 
					 | 
				
			||||||
                    0b10 => {
 | 
					 | 
				
			||||||
                        // LDRSB
 | 
					 | 
				
			||||||
                        const byte = bus.readByte(address);
 | 
					 | 
				
			||||||
                        cpu.r[rd] = util.u32_sign_extend(@as(u32, byte), 8);
 | 
					 | 
				
			||||||
                    },
 | 
					 | 
				
			||||||
                    0b11 => {
 | 
					 | 
				
			||||||
                        // LDRSH
 | 
					 | 
				
			||||||
                        const halfword = bus.readHalfWord(address);
 | 
					 | 
				
			||||||
                        cpu.r[rd] = util.u32_sign_extend(@as(u32, halfword), 16);
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
            } else {
 | 
					 | 
				
			||||||
                if (opcode >> 5 & 0x01 == 0x01) {
 | 
					 | 
				
			||||||
                    // STRH
 | 
					 | 
				
			||||||
                    const src = @truncate(u16, cpu.r[rd]);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    bus.writeHalfWord(address + 2, src);
 | 
					 | 
				
			||||||
                    bus.writeHalfWord(address, src);
 | 
					 | 
				
			||||||
                } else {
 | 
					 | 
				
			||||||
                    std.debug.panic("TODO Figure out if this is also SWP", .{});
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            address = modified_base;
 | 
					 | 
				
			||||||
            if (W and P) cpu.r[rn] = address;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }.halfSignedDataTransfer;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@@ -1,64 +0,0 @@
 | 
				
			|||||||
const std = @import("std");
 | 
					 | 
				
			||||||
const util = @import("../util.zig");
 | 
					 | 
				
			||||||
const mod_cpu = @import("../cpu.zig");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const ARM7TDMI = mod_cpu.ARM7TDMI;
 | 
					 | 
				
			||||||
const InstrFn = mod_cpu.InstrFn;
 | 
					 | 
				
			||||||
const Bus = @import("../bus.zig").Bus;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
pub fn comptimeSingleDataTransfer(comptime I: bool, comptime P: bool, comptime U: bool, comptime B: bool, comptime W: bool, comptime L: bool) InstrFn {
 | 
					 | 
				
			||||||
    return struct {
 | 
					 | 
				
			||||||
        fn singleDataTransfer(cpu: *ARM7TDMI, bus: *Bus, opcode: u32) void {
 | 
					 | 
				
			||||||
            const rn = opcode >> 16 & 0xF;
 | 
					 | 
				
			||||||
            const rd = opcode >> 12 & 0xF;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            const base = cpu.r[rn];
 | 
					 | 
				
			||||||
            const offset = if (I) opcode & 0xFFF else registerOffset(cpu, opcode);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            const modified_base = if (U) base + offset else base - offset;
 | 
					 | 
				
			||||||
            var address = if (P) modified_base else base;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            if (L) {
 | 
					 | 
				
			||||||
                if (B) {
 | 
					 | 
				
			||||||
                    // LDRB
 | 
					 | 
				
			||||||
                    cpu.r[rd] = bus.readByte(address);
 | 
					 | 
				
			||||||
                } else {
 | 
					 | 
				
			||||||
                    // LDR
 | 
					 | 
				
			||||||
                    std.debug.panic("Implement LDR", .{});
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
            } else {
 | 
					 | 
				
			||||||
                if (B) {
 | 
					 | 
				
			||||||
                    // STRB
 | 
					 | 
				
			||||||
                    const src = @truncate(u8, cpu.r[rd]);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    bus.writeByte(address + 3, src);
 | 
					 | 
				
			||||||
                    bus.writeByte(address + 2, src);
 | 
					 | 
				
			||||||
                    bus.writeByte(address + 1, src);
 | 
					 | 
				
			||||||
                    bus.writeByte(address, src);
 | 
					 | 
				
			||||||
                } else {
 | 
					 | 
				
			||||||
                    // STR
 | 
					 | 
				
			||||||
                    std.debug.panic("Implement STR", .{});
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            address = modified_base;
 | 
					 | 
				
			||||||
            if (W and P) cpu.r[rn] = address;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            // TODO: W-bit forces non-privledged mode for the transfer
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }.singleDataTransfer;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
fn registerOffset(cpu: *ARM7TDMI, opcode: u32) u32 {
 | 
					 | 
				
			||||||
    const amount = opcode >> 7 & 0x1F;
 | 
					 | 
				
			||||||
    const rm = opcode & 0xF;
 | 
					 | 
				
			||||||
    const r_val = cpu.r[rm];
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    return switch (opcode >> 5 & 0x03) {
 | 
					 | 
				
			||||||
        0b00 => r_val << @truncate(u5, amount),
 | 
					 | 
				
			||||||
        0b01 => r_val >> @truncate(u5, amount),
 | 
					 | 
				
			||||||
        0b10 => @bitCast(u32, @bitCast(i32, r_val) >> @truncate(u5, amount)),
 | 
					 | 
				
			||||||
        0b11 => std.math.rotr(u32, r_val, amount),
 | 
					 | 
				
			||||||
        else => unreachable,
 | 
					 | 
				
			||||||
    };
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
							
								
								
									
										19
									
								
								src/emu.zig
									
									
									
									
									
								
							
							
						
						
									
										19
									
								
								src/emu.zig
									
									
									
									
									
								
							@@ -1,19 +0,0 @@
 | 
				
			|||||||
const _ = @import("std");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const Scheduler = @import("scheduler.zig").Scheduler;
 | 
					 | 
				
			||||||
const ARM7TDMI = @import("cpu.zig").ARM7TDMI;
 | 
					 | 
				
			||||||
const Bus = @import("bus.zig").Bus;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const CYCLES_PER_FRAME: u64 = 10_000; // TODO: What is this?
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
pub fn runFrame(sch: *Scheduler, cpu: *ARM7TDMI, bus: *Bus) void {
 | 
					 | 
				
			||||||
    const frame_end = sch.tick + CYCLES_PER_FRAME;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    while (sch.tick < frame_end) {
 | 
					 | 
				
			||||||
        while (sch.tick < sch.nextTimestamp()) {
 | 
					 | 
				
			||||||
            sch.tick += cpu.step();
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        sch.handleEvent(cpu, bus);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
							
								
								
									
										146
									
								
								src/main.zig
									
									
									
									
									
								
							
							
						
						
									
										146
									
								
								src/main.zig
									
									
									
									
									
								
							@@ -1,25 +1,145 @@
 | 
				
			|||||||
const std = @import("std");
 | 
					const std = @import("std");
 | 
				
			||||||
 | 
					const builtin = @import("builtin");
 | 
				
			||||||
 | 
					const known_folders = @import("known_folders");
 | 
				
			||||||
 | 
					const clap = @import("clap");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const Scheduler = @import("scheduler.zig").Scheduler;
 | 
					const config = @import("config.zig");
 | 
				
			||||||
const Bus = @import("bus.zig").Bus;
 | 
					 | 
				
			||||||
const ARM7TDMI = @import("cpu.zig").ARM7TDMI;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
const emu = @import("emu.zig");
 | 
					const Gui = @import("platform.zig").Gui;
 | 
				
			||||||
 | 
					const Bus = @import("core/Bus.zig");
 | 
				
			||||||
 | 
					const Arm7tdmi = @import("core/cpu.zig").Arm7tdmi;
 | 
				
			||||||
 | 
					const Scheduler = @import("core/scheduler.zig").Scheduler;
 | 
				
			||||||
 | 
					const FilePaths = @import("util.zig").FilePaths;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const Allocator = std.mem.Allocator;
 | 
				
			||||||
 | 
					const log = std.log.scoped(.Cli);
 | 
				
			||||||
 | 
					const width = @import("core/ppu.zig").width;
 | 
				
			||||||
 | 
					const height = @import("core/ppu.zig").height;
 | 
				
			||||||
 | 
					pub const log_level = if (builtin.mode != .Debug) .info else std.log.default_level;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// CLI Arguments + Help Text
 | 
				
			||||||
 | 
					const params = clap.parseParamsComptime(
 | 
				
			||||||
 | 
					    \\-h, --help            Display this help and exit.
 | 
				
			||||||
 | 
					    \\-s, --skip            Skip BIOS.
 | 
				
			||||||
 | 
					    \\-b, --bios <str>      Optional path to a GBA BIOS ROM.
 | 
				
			||||||
 | 
					    \\<str>                 Path to the GBA GamePak ROM.
 | 
				
			||||||
 | 
					    \\
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
pub fn main() anyerror!void {
 | 
					pub fn main() anyerror!void {
 | 
				
			||||||
 | 
					    // Main Allocator for ZBA
 | 
				
			||||||
    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
 | 
					    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
 | 
				
			||||||
    const alloc = gpa.allocator();
 | 
					    defer std.debug.assert(!gpa.deinit());
 | 
				
			||||||
    // defer gpa.deinit();
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    var bus = try Bus.withPak(alloc, "./bin/demo/beeg/beeg.gba");
 | 
					    const allocator = gpa.allocator();
 | 
				
			||||||
    var scheduler = Scheduler.new(alloc);
 | 
					 | 
				
			||||||
    var cpu = ARM7TDMI.new(&scheduler, &bus);
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    while (true) {
 | 
					    // Determine the Data Directory (stores saves, config file, etc.)
 | 
				
			||||||
        emu.runFrame(&scheduler, &cpu, &bus);
 | 
					    const data_path = blk: {
 | 
				
			||||||
 | 
					        const result = known_folders.getPath(allocator, .data);
 | 
				
			||||||
 | 
					        const option = result catch |e| exitln("interrupted while attempting to find a data directory: {}", .{e});
 | 
				
			||||||
 | 
					        const path = option orelse exitln("no valid data directory could be found", .{});
 | 
				
			||||||
 | 
					        ensureDirectoriesExist(path) catch |e| exitln("failed to create directories under \"{s}\": {}", .{ path, e });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        break :blk path;
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					    defer allocator.free(data_path);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Parse CLI
 | 
				
			||||||
 | 
					    const result = clap.parse(clap.Help, ¶ms, clap.parsers.default, .{}) catch |e| exitln("failed to parse cli: {}", .{e});
 | 
				
			||||||
 | 
					    defer result.deinit();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // TODO: Move config file to XDG Config directory?
 | 
				
			||||||
 | 
					    const config_path = configFilePath(allocator, data_path) catch |e| exitln("failed to determine the config file path for ZBA: {}", .{e});
 | 
				
			||||||
 | 
					    defer allocator.free(config_path);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    config.load(allocator, config_path) catch |e| exitln("failed to read config file: {}", .{e});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const paths = handleArguments(allocator, data_path, &result) catch |e| exitln("failed to handle cli arguments: {}", .{e});
 | 
				
			||||||
 | 
					    defer if (paths.save) |path| allocator.free(path);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const log_file = if (config.config().debug.cpu_trace) blk: {
 | 
				
			||||||
 | 
					        break :blk std.fs.cwd().createFile("zba.log", .{}) catch |e| exitln("failed to create trace log file: {}", .{e});
 | 
				
			||||||
 | 
					    } else null;
 | 
				
			||||||
 | 
					    defer if (log_file) |file| file.close();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // TODO: Take Emulator Init Code out of main.zig
 | 
				
			||||||
 | 
					    var scheduler = Scheduler.init(allocator);
 | 
				
			||||||
 | 
					    defer scheduler.deinit();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    var bus: Bus = undefined;
 | 
				
			||||||
 | 
					    var cpu = Arm7tdmi.init(&scheduler, &bus, log_file);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    bus.init(allocator, &scheduler, &cpu, paths) catch |e| exitln("failed to init zba bus: {}", .{e});
 | 
				
			||||||
 | 
					    defer bus.deinit();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (config.config().guest.skip_bios or result.args.skip or paths.bios == null) {
 | 
				
			||||||
 | 
					        cpu.fastBoot();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    var gui = Gui.init(&bus.pak.title, &bus.apu, width, height);
 | 
				
			||||||
 | 
					    defer gui.deinit();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    gui.run(&cpu, &scheduler) catch |e| exitln("failed to run gui thread: {}", .{e});
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
test "basic test" {
 | 
					pub fn handleArguments(allocator: Allocator, data_path: []const u8, result: *const clap.Result(clap.Help, ¶ms, clap.parsers.default)) !FilePaths {
 | 
				
			||||||
    try std.testing.expectEqual(10, 3 + 7);
 | 
					    const rom_path = romPath(result);
 | 
				
			||||||
 | 
					    log.info("ROM path: {s}", .{rom_path});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const bios_path = result.args.bios;
 | 
				
			||||||
 | 
					    if (bios_path) |path| log.info("BIOS path: {s}", .{path}) else log.warn("No BIOS provided", .{});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const save_path = try std.fs.path.join(allocator, &[_][]const u8{ data_path, "zba", "save" });
 | 
				
			||||||
 | 
					    log.info("Save path: {s}", .{save_path});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return .{
 | 
				
			||||||
 | 
					        .rom = rom_path,
 | 
				
			||||||
 | 
					        .bios = bios_path,
 | 
				
			||||||
 | 
					        .save = save_path,
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fn configFilePath(allocator: Allocator, data_path: []const u8) ![]const u8 {
 | 
				
			||||||
 | 
					    const path = try std.fs.path.join(allocator, &[_][]const u8{ data_path, "zba", "config.toml" });
 | 
				
			||||||
 | 
					    errdefer allocator.free(path);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // We try to create the file exclusively, meaning that we err out if the file already exists.
 | 
				
			||||||
 | 
					    // All we care about is a file being there so we can just ignore that error in particular and
 | 
				
			||||||
 | 
					    // continue down the happy pathj
 | 
				
			||||||
 | 
					    std.fs.accessAbsolute(path, .{}) catch |e| {
 | 
				
			||||||
 | 
					        if (e != error.FileNotFound) return e;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const config_file = try std.fs.createFileAbsolute(path, .{});
 | 
				
			||||||
 | 
					        defer config_file.close();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        try config_file.writeAll(@embedFile("../example.toml"));
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return path;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fn ensureDirectoriesExist(data_path: []const u8) !void {
 | 
				
			||||||
 | 
					    var dir = try std.fs.openDirAbsolute(data_path, .{});
 | 
				
			||||||
 | 
					    defer dir.close();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // We want to make sure: %APPDATA%/zba and %APPDATA%/zba/save exist
 | 
				
			||||||
 | 
					    // (~/.local/share/zba/save for linux, ??? for macOS)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Will recursively create directories
 | 
				
			||||||
 | 
					    try dir.makePath("zba" ++ [_]u8{std.fs.path.sep} ++ "save");
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fn romPath(result: *const clap.Result(clap.Help, ¶ms, clap.parsers.default)) []const u8 {
 | 
				
			||||||
 | 
					    return switch (result.positionals.len) {
 | 
				
			||||||
 | 
					        1 => result.positionals[0],
 | 
				
			||||||
 | 
					        0 => exitln("ZBA requires a path to a GamePak ROM", .{}),
 | 
				
			||||||
 | 
					        else => exitln("ZBA received too many positional arguments.", .{}),
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fn exitln(comptime format: []const u8, args: anytype) noreturn {
 | 
				
			||||||
 | 
					    const stderr = std.io.getStdErr().writer();
 | 
				
			||||||
 | 
					    stderr.print(format, args) catch {}; // Just exit already...
 | 
				
			||||||
 | 
					    stderr.writeByte('\n') catch {};
 | 
				
			||||||
 | 
					    std.os.exit(1);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										35
									
								
								src/pak.zig
									
									
									
									
									
								
							
							
						
						
									
										35
									
								
								src/pak.zig
									
									
									
									
									
								
							@@ -1,35 +0,0 @@
 | 
				
			|||||||
const std = @import("std");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const Allocator = std.mem.Allocator;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
pub const GamePak = struct {
 | 
					 | 
				
			||||||
    buf: []u8,
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    pub fn fromPath(alloc: Allocator, path: []const u8) !@This() {
 | 
					 | 
				
			||||||
        const file = try std.fs.cwd().openFile(path, .{ .read = true });
 | 
					 | 
				
			||||||
        defer file.close();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        const len = try file.getEndPos();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        return @This(){
 | 
					 | 
				
			||||||
            .buf = try file.readToEndAlloc(alloc, len),
 | 
					 | 
				
			||||||
        };
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    pub fn readWord(self: *const @This(), addr: u32) u32 {
 | 
					 | 
				
			||||||
        return (@as(u32, self.buf[addr + 3]) << 24) | (@as(u32, self.buf[addr + 2]) << 16) | (@as(u32, self.buf[addr + 1]) << 8) | (@as(u32, self.buf[addr]));
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    pub fn readHalfWord(self: *const @This(), addr: u32) u16 {
 | 
					 | 
				
			||||||
        return (@as(u16, self.buf[addr + 1]) << 8) | @as(u16, self.buf[addr]);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    pub fn writeHalfWord(self: *@This(), addr: u32, halfword: u16) void {
 | 
					 | 
				
			||||||
        self.buf[addr + 1] = @truncate(u8, halfword >> 8);
 | 
					 | 
				
			||||||
        self.buf[addr] = @truncate(u8, halfword);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    pub fn readByte(self: *const @This(), addr: u32) u8 {
 | 
					 | 
				
			||||||
        return self.buf[addr];
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
							
								
								
									
										302
									
								
								src/platform.zig
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										302
									
								
								src/platform.zig
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,302 @@
 | 
				
			|||||||
 | 
					const std = @import("std");
 | 
				
			||||||
 | 
					const SDL = @import("sdl2");
 | 
				
			||||||
 | 
					const gl = @import("gl");
 | 
				
			||||||
 | 
					const emu = @import("core/emu.zig");
 | 
				
			||||||
 | 
					const config = @import("config.zig");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const Apu = @import("core/apu.zig").Apu;
 | 
				
			||||||
 | 
					const Arm7tdmi = @import("core/cpu.zig").Arm7tdmi;
 | 
				
			||||||
 | 
					const Scheduler = @import("core/scheduler.zig").Scheduler;
 | 
				
			||||||
 | 
					const FpsTracker = @import("util.zig").FpsTracker;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const span = @import("util.zig").span;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const pitch = @import("core/ppu.zig").framebuf_pitch;
 | 
				
			||||||
 | 
					const gba_width = @import("core/ppu.zig").width;
 | 
				
			||||||
 | 
					const gba_height = @import("core/ppu.zig").height;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const default_title: []const u8 = "ZBA";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub const Gui = struct {
 | 
				
			||||||
 | 
					    const Self = @This();
 | 
				
			||||||
 | 
					    const SDL_GLContext = *anyopaque; // SDL.SDL_GLContext is a ?*anyopaque
 | 
				
			||||||
 | 
					    const log = std.log.scoped(.Gui);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // zig fmt: off
 | 
				
			||||||
 | 
					    const vertices: [32]f32 = [_]f32{
 | 
				
			||||||
 | 
					        // Positions        // Colours      // Texture Coords
 | 
				
			||||||
 | 
					         1.0, -1.0, 0.0,    1.0, 0.0, 0.0,  1.0, 1.0, // Top Right
 | 
				
			||||||
 | 
					         1.0,  1.0, 0.0,    0.0, 1.0, 0.0,  1.0, 0.0, // Bottom Right
 | 
				
			||||||
 | 
					        -1.0,  1.0, 0.0,    0.0, 0.0, 1.0,  0.0, 0.0, // Bottom Left
 | 
				
			||||||
 | 
					        -1.0, -1.0, 0.0,    1.0, 1.0, 0.0,  0.0, 1.0, // Top Left
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const indices: [6]u32 = [_]u32{
 | 
				
			||||||
 | 
					        0, 1, 3, // First Triangle
 | 
				
			||||||
 | 
					        1, 2, 3, // Second Triangle
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					    // zig fmt: on
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    window: *SDL.SDL_Window,
 | 
				
			||||||
 | 
					    ctx: SDL_GLContext,
 | 
				
			||||||
 | 
					    title: []const u8,
 | 
				
			||||||
 | 
					    audio: Audio,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    program_id: gl.GLuint,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn init(title: *const [12]u8, apu: *Apu, width: i32, height: i32) Self {
 | 
				
			||||||
 | 
					        if (SDL.SDL_Init(SDL.SDL_INIT_VIDEO | SDL.SDL_INIT_EVENTS | SDL.SDL_INIT_AUDIO) < 0) panic();
 | 
				
			||||||
 | 
					        if (SDL.SDL_GL_SetAttribute(SDL.SDL_GL_CONTEXT_PROFILE_MASK, SDL.SDL_GL_CONTEXT_PROFILE_CORE) < 0) panic();
 | 
				
			||||||
 | 
					        if (SDL.SDL_GL_SetAttribute(SDL.SDL_GL_CONTEXT_MAJOR_VERSION, 3) < 0) panic();
 | 
				
			||||||
 | 
					        if (SDL.SDL_GL_SetAttribute(SDL.SDL_GL_CONTEXT_MAJOR_VERSION, 3) < 0) panic();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const win_scale = @intCast(c_int, config.config().host.win_scale);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const window = SDL.SDL_CreateWindow(
 | 
				
			||||||
 | 
					            default_title.ptr,
 | 
				
			||||||
 | 
					            SDL.SDL_WINDOWPOS_CENTERED,
 | 
				
			||||||
 | 
					            SDL.SDL_WINDOWPOS_CENTERED,
 | 
				
			||||||
 | 
					            @as(c_int, width * win_scale),
 | 
				
			||||||
 | 
					            @as(c_int, height * win_scale),
 | 
				
			||||||
 | 
					            SDL.SDL_WINDOW_OPENGL | SDL.SDL_WINDOW_SHOWN,
 | 
				
			||||||
 | 
					        ) orelse panic();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const ctx = SDL.SDL_GL_CreateContext(window) orelse panic();
 | 
				
			||||||
 | 
					        if (SDL.SDL_GL_MakeCurrent(window, ctx) < 0) panic();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        gl.load(ctx, Self.glGetProcAddress) catch @panic("gl.load failed");
 | 
				
			||||||
 | 
					        if (SDL.SDL_GL_SetSwapInterval(@boolToInt(config.config().host.vsync)) < 0) panic();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const program_id = compileShaders();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return Self{
 | 
				
			||||||
 | 
					            .window = window,
 | 
				
			||||||
 | 
					            .title = span(title),
 | 
				
			||||||
 | 
					            .ctx = ctx,
 | 
				
			||||||
 | 
					            .program_id = program_id,
 | 
				
			||||||
 | 
					            .audio = Audio.init(apu),
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fn compileShaders() gl.GLuint {
 | 
				
			||||||
 | 
					        // TODO: Panic on Shader Compiler Failure + Error Message
 | 
				
			||||||
 | 
					        const vert_shader = @embedFile("shader/pixelbuf.vert");
 | 
				
			||||||
 | 
					        const frag_shader = @embedFile("shader/pixelbuf.frag");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const vs = gl.createShader(gl.VERTEX_SHADER);
 | 
				
			||||||
 | 
					        defer gl.deleteShader(vs);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        gl.shaderSource(vs, 1, &[_][*c]const u8{vert_shader}, 0);
 | 
				
			||||||
 | 
					        gl.compileShader(vs);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const fs = gl.createShader(gl.FRAGMENT_SHADER);
 | 
				
			||||||
 | 
					        defer gl.deleteShader(fs);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        gl.shaderSource(fs, 1, &[_][*c]const u8{frag_shader}, 0);
 | 
				
			||||||
 | 
					        gl.compileShader(fs);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const program = gl.createProgram();
 | 
				
			||||||
 | 
					        gl.attachShader(program, vs);
 | 
				
			||||||
 | 
					        gl.attachShader(program, fs);
 | 
				
			||||||
 | 
					        gl.linkProgram(program);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return program;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Returns the VAO ID since it's used in run()
 | 
				
			||||||
 | 
					    fn generateBuffers() [3]c_uint {
 | 
				
			||||||
 | 
					        var vao_id: c_uint = undefined;
 | 
				
			||||||
 | 
					        var vbo_id: c_uint = undefined;
 | 
				
			||||||
 | 
					        var ebo_id: c_uint = undefined;
 | 
				
			||||||
 | 
					        gl.genVertexArrays(1, &vao_id);
 | 
				
			||||||
 | 
					        gl.genBuffers(1, &vbo_id);
 | 
				
			||||||
 | 
					        gl.genBuffers(1, &ebo_id);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        gl.bindVertexArray(vao_id);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        gl.bindBuffer(gl.ARRAY_BUFFER, vbo_id);
 | 
				
			||||||
 | 
					        gl.bufferData(gl.ARRAY_BUFFER, @sizeOf(@TypeOf(vertices)), &vertices, gl.STATIC_DRAW);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, ebo_id);
 | 
				
			||||||
 | 
					        gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, @sizeOf(@TypeOf(indices)), &indices, gl.STATIC_DRAW);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Position
 | 
				
			||||||
 | 
					        gl.vertexAttribPointer(0, 3, gl.FLOAT, gl.FALSE, 8 * @sizeOf(f32), @intToPtr(?*anyopaque, 0)); // lmao
 | 
				
			||||||
 | 
					        gl.enableVertexAttribArray(0);
 | 
				
			||||||
 | 
					        // Colour
 | 
				
			||||||
 | 
					        gl.vertexAttribPointer(1, 3, gl.FLOAT, gl.FALSE, 8 * @sizeOf(f32), @intToPtr(?*anyopaque, (3 * @sizeOf(f32))));
 | 
				
			||||||
 | 
					        gl.enableVertexAttribArray(1);
 | 
				
			||||||
 | 
					        // Texture Coord
 | 
				
			||||||
 | 
					        gl.vertexAttribPointer(2, 2, gl.FLOAT, gl.FALSE, 8 * @sizeOf(f32), @intToPtr(?*anyopaque, (6 * @sizeOf(f32))));
 | 
				
			||||||
 | 
					        gl.enableVertexAttribArray(2);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return .{ vao_id, vbo_id, ebo_id };
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fn generateTexture(buf: []const u8) c_uint {
 | 
				
			||||||
 | 
					        var tex_id: c_uint = undefined;
 | 
				
			||||||
 | 
					        gl.genTextures(1, &tex_id);
 | 
				
			||||||
 | 
					        gl.bindTexture(gl.TEXTURE_2D, tex_id);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
 | 
				
			||||||
 | 
					        // gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
 | 
				
			||||||
 | 
					        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gba_width, gba_height, 0, gl.RGBA, gl.UNSIGNED_INT_8_8_8_8, buf.ptr);
 | 
				
			||||||
 | 
					        // gl.generateMipmap(gl.TEXTURE_2D); // TODO: Remove?
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return tex_id;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn run(self: *Self, cpu: *Arm7tdmi, scheduler: *Scheduler) !void {
 | 
				
			||||||
 | 
					        var quit = std.atomic.Atomic(bool).init(false);
 | 
				
			||||||
 | 
					        var tracker = FpsTracker.init();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const thread = try std.Thread.spawn(.{}, emu.run, .{ &quit, scheduler, cpu, &tracker });
 | 
				
			||||||
 | 
					        defer thread.join();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        var title_buf: [0x100]u8 = [_]u8{0} ** 0x100;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const vao_id = Self.generateBuffers()[0];
 | 
				
			||||||
 | 
					        _ = Self.generateTexture(cpu.bus.ppu.framebuf.get(.Renderer));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        emu_loop: while (true) {
 | 
				
			||||||
 | 
					            var event: SDL.SDL_Event = undefined;
 | 
				
			||||||
 | 
					            while (SDL.SDL_PollEvent(&event) != 0) {
 | 
				
			||||||
 | 
					                switch (event.type) {
 | 
				
			||||||
 | 
					                    SDL.SDL_QUIT => break :emu_loop,
 | 
				
			||||||
 | 
					                    SDL.SDL_KEYDOWN => {
 | 
				
			||||||
 | 
					                        const io = &cpu.bus.io;
 | 
				
			||||||
 | 
					                        const key_code = event.key.keysym.sym;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                        switch (key_code) {
 | 
				
			||||||
 | 
					                            SDL.SDLK_UP => io.keyinput.up.unset(),
 | 
				
			||||||
 | 
					                            SDL.SDLK_DOWN => io.keyinput.down.unset(),
 | 
				
			||||||
 | 
					                            SDL.SDLK_LEFT => io.keyinput.left.unset(),
 | 
				
			||||||
 | 
					                            SDL.SDLK_RIGHT => io.keyinput.right.unset(),
 | 
				
			||||||
 | 
					                            SDL.SDLK_x => io.keyinput.a.unset(),
 | 
				
			||||||
 | 
					                            SDL.SDLK_z => io.keyinput.b.unset(),
 | 
				
			||||||
 | 
					                            SDL.SDLK_a => io.keyinput.shoulder_l.unset(),
 | 
				
			||||||
 | 
					                            SDL.SDLK_s => io.keyinput.shoulder_r.unset(),
 | 
				
			||||||
 | 
					                            SDL.SDLK_RETURN => io.keyinput.start.unset(),
 | 
				
			||||||
 | 
					                            SDL.SDLK_RSHIFT => io.keyinput.select.unset(),
 | 
				
			||||||
 | 
					                            else => {},
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					                    },
 | 
				
			||||||
 | 
					                    SDL.SDL_KEYUP => {
 | 
				
			||||||
 | 
					                        const io = &cpu.bus.io;
 | 
				
			||||||
 | 
					                        const key_code = event.key.keysym.sym;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                        switch (key_code) {
 | 
				
			||||||
 | 
					                            SDL.SDLK_UP => io.keyinput.up.set(),
 | 
				
			||||||
 | 
					                            SDL.SDLK_DOWN => io.keyinput.down.set(),
 | 
				
			||||||
 | 
					                            SDL.SDLK_LEFT => io.keyinput.left.set(),
 | 
				
			||||||
 | 
					                            SDL.SDLK_RIGHT => io.keyinput.right.set(),
 | 
				
			||||||
 | 
					                            SDL.SDLK_x => io.keyinput.a.set(),
 | 
				
			||||||
 | 
					                            SDL.SDLK_z => io.keyinput.b.set(),
 | 
				
			||||||
 | 
					                            SDL.SDLK_a => io.keyinput.shoulder_l.set(),
 | 
				
			||||||
 | 
					                            SDL.SDLK_s => io.keyinput.shoulder_r.set(),
 | 
				
			||||||
 | 
					                            SDL.SDLK_RETURN => io.keyinput.start.set(),
 | 
				
			||||||
 | 
					                            SDL.SDLK_RSHIFT => io.keyinput.select.set(),
 | 
				
			||||||
 | 
					                            SDL.SDLK_i => log.err("Sample Count: {}", .{@intCast(u32, SDL.SDL_AudioStreamAvailable(cpu.bus.apu.stream)) / (2 * @sizeOf(u16))}),
 | 
				
			||||||
 | 
					                            SDL.SDLK_j => log.err("Scheduler Capacity: {} | Scheduler Event Count: {}", .{ scheduler.queue.capacity(), scheduler.queue.count() }),
 | 
				
			||||||
 | 
					                            SDL.SDLK_k => {
 | 
				
			||||||
 | 
					                                // Dump IWRAM to file
 | 
				
			||||||
 | 
					                                log.info("PC: 0x{X:0>8}", .{cpu.r[15]});
 | 
				
			||||||
 | 
					                                log.info("LR: 0x{X:0>8}", .{cpu.r[14]});
 | 
				
			||||||
 | 
					                                // const iwram_file = try std.fs.cwd().createFile("iwram.bin", .{});
 | 
				
			||||||
 | 
					                                // defer iwram_file.close();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                                // try iwram_file.writeAll(cpu.bus.iwram.buf);
 | 
				
			||||||
 | 
					                            },
 | 
				
			||||||
 | 
					                            else => {},
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					                    },
 | 
				
			||||||
 | 
					                    else => {},
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // Emulator has an internal Double Buffer
 | 
				
			||||||
 | 
					            const framebuf = cpu.bus.ppu.framebuf.get(.Renderer);
 | 
				
			||||||
 | 
					            gl.texSubImage2D(gl.TEXTURE_2D, 0, 0, 0, gba_width, gba_height, gl.RGBA, gl.UNSIGNED_INT_8_8_8_8, framebuf.ptr);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            gl.useProgram(self.program_id);
 | 
				
			||||||
 | 
					            gl.bindVertexArray(vao_id);
 | 
				
			||||||
 | 
					            gl.drawElements(gl.TRIANGLES, 6, gl.UNSIGNED_INT, null);
 | 
				
			||||||
 | 
					            SDL.SDL_GL_SwapWindow(self.window);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            const dyn_title = std.fmt.bufPrint(&title_buf, "ZBA | {s} [Emu: {}fps] ", .{ self.title, tracker.value() }) catch unreachable;
 | 
				
			||||||
 | 
					            SDL.SDL_SetWindowTitle(self.window, dyn_title.ptr);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        quit.store(true, .SeqCst); // Terminate Emulator Thread
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn deinit(self: *Self) void {
 | 
				
			||||||
 | 
					        self.audio.deinit();
 | 
				
			||||||
 | 
					        // TODO: Buffer deletions
 | 
				
			||||||
 | 
					        gl.deleteProgram(self.program_id);
 | 
				
			||||||
 | 
					        SDL.SDL_GL_DeleteContext(self.ctx);
 | 
				
			||||||
 | 
					        SDL.SDL_DestroyWindow(self.window);
 | 
				
			||||||
 | 
					        SDL.SDL_Quit();
 | 
				
			||||||
 | 
					        self.* = undefined;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fn glGetProcAddress(ctx: SDL.SDL_GLContext, proc: [:0]const u8) ?*anyopaque {
 | 
				
			||||||
 | 
					        _ = ctx;
 | 
				
			||||||
 | 
					        return SDL.SDL_GL_GetProcAddress(proc.ptr);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const Audio = struct {
 | 
				
			||||||
 | 
					    const Self = @This();
 | 
				
			||||||
 | 
					    const log = std.log.scoped(.PlatformAudio);
 | 
				
			||||||
 | 
					    const sample_rate = @import("core/apu.zig").host_sample_rate;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    device: SDL.SDL_AudioDeviceID,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fn init(apu: *Apu) Self {
 | 
				
			||||||
 | 
					        var have: SDL.SDL_AudioSpec = undefined;
 | 
				
			||||||
 | 
					        var want: SDL.SDL_AudioSpec = std.mem.zeroes(SDL.SDL_AudioSpec);
 | 
				
			||||||
 | 
					        want.freq = sample_rate;
 | 
				
			||||||
 | 
					        want.format = SDL.AUDIO_U16;
 | 
				
			||||||
 | 
					        want.channels = 2;
 | 
				
			||||||
 | 
					        want.samples = 0x100;
 | 
				
			||||||
 | 
					        want.callback = Self.callback;
 | 
				
			||||||
 | 
					        want.userdata = apu;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const device = SDL.SDL_OpenAudioDevice(null, 0, &want, &have, 0);
 | 
				
			||||||
 | 
					        if (device == 0) panic();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        SDL.SDL_PauseAudioDevice(device, 0); // Unpause Audio
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return .{ .device = device };
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fn deinit(self: *Self) void {
 | 
				
			||||||
 | 
					        SDL.SDL_CloseAudioDevice(self.device);
 | 
				
			||||||
 | 
					        self.* = undefined;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    export fn callback(userdata: ?*anyopaque, stream: [*c]u8, len: c_int) void {
 | 
				
			||||||
 | 
					        const apu = @ptrCast(*Apu, @alignCast(@alignOf(*Apu), userdata));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // TODO: Find a better way to mute this
 | 
				
			||||||
 | 
					        if (!config.config().host.mute) {
 | 
				
			||||||
 | 
					            _ = SDL.SDL_AudioStreamGet(apu.stream, stream, len);
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            // FIXME: I don't think this hack to remove DC Offset is acceptable :thinking:
 | 
				
			||||||
 | 
					            std.mem.set(u8, stream[0..@intCast(usize, len)], 0x40);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // If we don't write anything, play silence otherwise garbage will be played
 | 
				
			||||||
 | 
					        // if (written == 0) std.mem.set(u8, stream[0..@intCast(usize, len)], 0x40);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fn panic() noreturn {
 | 
				
			||||||
 | 
					    const str = @as(?[*:0]const u8, SDL.SDL_GetError()) orelse "unknown error";
 | 
				
			||||||
 | 
					    @panic(std.mem.sliceTo(str, 0));
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -1,57 +0,0 @@
 | 
				
			|||||||
const std = @import("std");
 | 
					 | 
				
			||||||
const ARM7TDMI = @import("cpu.zig").ARM7TDMI;
 | 
					 | 
				
			||||||
const Bus = @import("bus.zig").Bus;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const Order = std.math.Order;
 | 
					 | 
				
			||||||
const PriorityQueue = std.PriorityQueue;
 | 
					 | 
				
			||||||
const Allocator = std.mem.Allocator;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
pub const Scheduler = struct {
 | 
					 | 
				
			||||||
    tick: u64,
 | 
					 | 
				
			||||||
    queue: PriorityQueue(Event, void, lessThan),
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    pub fn new(alloc: Allocator) @This() {
 | 
					 | 
				
			||||||
        var scheduler = Scheduler{ .tick = 0, .queue = PriorityQueue(Event, void, lessThan).init(alloc, {}) };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        scheduler.queue.add(.{
 | 
					 | 
				
			||||||
            .kind = EventKind.HeatDeath,
 | 
					 | 
				
			||||||
            .tick = std.math.maxInt(u64),
 | 
					 | 
				
			||||||
        }) catch unreachable;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        return scheduler;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    pub fn handleEvent(self: *@This(), _: *ARM7TDMI, _: *Bus) void {
 | 
					 | 
				
			||||||
        const should_handle = if (self.queue.peek()) |e| self.tick >= e.tick else false;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if (should_handle) {
 | 
					 | 
				
			||||||
            const event = self.queue.remove();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            switch (event.kind) {
 | 
					 | 
				
			||||||
                .HeatDeath => {
 | 
					 | 
				
			||||||
                    std.debug.panic("Somehow, a u64 overflowed", .{});
 | 
					 | 
				
			||||||
                },
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    pub inline fn nextTimestamp(self: *@This()) u64 {
 | 
					 | 
				
			||||||
        if (self.queue.peek()) |e| {
 | 
					 | 
				
			||||||
            return e.tick;
 | 
					 | 
				
			||||||
        } else unreachable;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
pub const Event = struct {
 | 
					 | 
				
			||||||
    kind: EventKind,
 | 
					 | 
				
			||||||
    tick: u64,
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
fn lessThan(context: void, a: Event, b: Event) Order {
 | 
					 | 
				
			||||||
    _ = context;
 | 
					 | 
				
			||||||
    return std.math.order(a.tick, b.tick);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
pub const EventKind = enum {
 | 
					 | 
				
			||||||
    HeatDeath,
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
							
								
								
									
										25
									
								
								src/shader/pixelbuf.frag
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								src/shader/pixelbuf.frag
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,25 @@
 | 
				
			|||||||
 | 
					#version 330 core
 | 
				
			||||||
 | 
					out vec4 frag_color;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					in vec3 color;
 | 
				
			||||||
 | 
					in vec2 uv;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					uniform sampler2D screen;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void main() {
 | 
				
			||||||
 | 
						// https://near.sh/video/color-emulation
 | 
				
			||||||
 | 
						// Thanks to Talarubi + Near for the Colour Correction
 | 
				
			||||||
 | 
						// Thanks to fleur + mattrb for the Shader Impl
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						vec4 color = texture(screen, uv);
 | 
				
			||||||
 | 
						color.rgb = pow(color.rgb, vec3(4.0)); // LCD Gamma
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
 | 
						frag_color = vec4(
 | 
				
			||||||
 | 
							pow(vec3(
 | 
				
			||||||
 | 
							  	  0 * color.b +  50 * color.g + 255 * color.r,
 | 
				
			||||||
 | 
						     	 30 * color.b + 230 * color.g +  10 * color.r,
 | 
				
			||||||
 | 
								220 * color.b +  10 * color.g +  50 * color.r
 | 
				
			||||||
 | 
							) / 255, vec3(1.0 / 2.2)), // Out Gamma
 | 
				
			||||||
 | 
						1.0); 
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
							
								
								
									
										13
									
								
								src/shader/pixelbuf.vert
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								src/shader/pixelbuf.vert
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,13 @@
 | 
				
			|||||||
 | 
					#version 330 core
 | 
				
			||||||
 | 
					layout (location = 0) in vec3 pos;
 | 
				
			||||||
 | 
					layout (location = 1) in vec3 in_color;
 | 
				
			||||||
 | 
					layout (location = 2) in vec2 in_uv;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					out vec3 color;
 | 
				
			||||||
 | 
					out vec2 uv;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void main() {
 | 
				
			||||||
 | 
						color = in_color;
 | 
				
			||||||
 | 
						uv = in_uv;
 | 
				
			||||||
 | 
						gl_Position = vec4(pos, 1.0);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										345
									
								
								src/util.zig
									
									
									
									
									
								
							
							
						
						
									
										345
									
								
								src/util.zig
									
									
									
									
									
								
							@@ -1,7 +1,346 @@
 | 
				
			|||||||
const std = @import("std");
 | 
					const std = @import("std");
 | 
				
			||||||
 | 
					const builtin = @import("builtin");
 | 
				
			||||||
 | 
					const config = @import("config.zig");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const Log2Int = std.math.Log2Int;
 | 
				
			||||||
 | 
					const Arm7tdmi = @import("core/cpu.zig").Arm7tdmi;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
pub fn u32_sign_extend(value: u32, bitSize: anytype) u32 {
 | 
					const Allocator = std.mem.Allocator;
 | 
				
			||||||
    const amount: u5 = 32 - bitSize;
 | 
					
 | 
				
			||||||
    return @bitCast(u32, @bitCast(i32, value << amount) >> amount);
 | 
					// Sign-Extend value of type `T` to type `U`
 | 
				
			||||||
 | 
					pub fn sext(comptime T: type, comptime U: type, value: T) T {
 | 
				
			||||||
 | 
					    // U must have less bits than T
 | 
				
			||||||
 | 
					    comptime std.debug.assert(@typeInfo(U).Int.bits <= @typeInfo(T).Int.bits);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const iT = std.meta.Int(.signed, @typeInfo(T).Int.bits);
 | 
				
			||||||
 | 
					    const ExtU = if (@typeInfo(U).Int.signedness == .unsigned) T else iT;
 | 
				
			||||||
 | 
					    const shift = @intCast(Log2Int(T), @typeInfo(T).Int.bits - @typeInfo(U).Int.bits);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return @bitCast(T, @bitCast(iT, @as(ExtU, @truncate(U, value)) << shift) >> shift);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// See https://godbolt.org/z/W3en9Eche
 | 
				
			||||||
 | 
					pub inline fn rotr(comptime T: type, x: T, r: anytype) T {
 | 
				
			||||||
 | 
					    if (@typeInfo(T).Int.signedness == .signed)
 | 
				
			||||||
 | 
					        @compileError("cannot rotate signed integer");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const ar = @intCast(Log2Int(T), @mod(r, @typeInfo(T).Int.bits));
 | 
				
			||||||
 | 
					    return x >> ar | x << (1 +% ~ar);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub const FpsTracker = struct {
 | 
				
			||||||
 | 
					    const Self = @This();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fps: u32,
 | 
				
			||||||
 | 
					    count: std.atomic.Atomic(u32),
 | 
				
			||||||
 | 
					    timer: std.time.Timer,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn init() Self {
 | 
				
			||||||
 | 
					        return .{
 | 
				
			||||||
 | 
					            .fps = 0,
 | 
				
			||||||
 | 
					            .count = std.atomic.Atomic(u32).init(0),
 | 
				
			||||||
 | 
					            .timer = std.time.Timer.start() catch unreachable,
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn tick(self: *Self) void {
 | 
				
			||||||
 | 
					        _ = self.count.fetchAdd(1, .Monotonic);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn value(self: *Self) u32 {
 | 
				
			||||||
 | 
					        if (self.timer.read() >= std.time.ns_per_s) {
 | 
				
			||||||
 | 
					            self.fps = self.count.swap(0, .SeqCst);
 | 
				
			||||||
 | 
					            self.timer.reset();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return self.fps;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn intToBytes(comptime T: type, value: anytype) [@sizeOf(T)]u8 {
 | 
				
			||||||
 | 
					    comptime std.debug.assert(@typeInfo(T) == .Int);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    var result: [@sizeOf(T)]u8 = undefined;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    var i: Log2Int(T) = 0;
 | 
				
			||||||
 | 
					    while (i < result.len) : (i += 1) result[i] = @truncate(u8, value >> i * @bitSizeOf(u8));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return result;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// The Title from the GBA Cartridge is an Uppercase ASCII string which is
 | 
				
			||||||
 | 
					/// null-padded to 12 bytes
 | 
				
			||||||
 | 
					///
 | 
				
			||||||
 | 
					/// This function returns a slice of the ASCII string without the null terminator(s)
 | 
				
			||||||
 | 
					/// (essentially, a proper Zig/Rust/Any modern language String)
 | 
				
			||||||
 | 
					pub fn span(title: *const [12]u8) []const u8 {
 | 
				
			||||||
 | 
					    const end = std.mem.indexOfScalar(u8, title, '\x00');
 | 
				
			||||||
 | 
					    return title[0 .. end orelse title.len];
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					test "span" {
 | 
				
			||||||
 | 
					    var example: *const [12]u8 = "POKEMON_EMER";
 | 
				
			||||||
 | 
					    try std.testing.expectEqualSlices(u8, "POKEMON_EMER", span(example));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    example = "POKEMON_EME\x00";
 | 
				
			||||||
 | 
					    try std.testing.expectEqualSlices(u8, "POKEMON_EME", span(example));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    example = "POKEMON_EM\x00\x00";
 | 
				
			||||||
 | 
					    try std.testing.expectEqualSlices(u8, "POKEMON_EM", span(example));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    example = "POKEMON_E\x00\x00\x00";
 | 
				
			||||||
 | 
					    try std.testing.expectEqualSlices(u8, "POKEMON_E", span(example));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    example = "POKEMON_\x00\x00\x00\x00";
 | 
				
			||||||
 | 
					    try std.testing.expectEqualSlices(u8, "POKEMON_", span(example));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    example = "POKEMON\x00\x00\x00\x00\x00";
 | 
				
			||||||
 | 
					    try std.testing.expectEqualSlices(u8, "POKEMON", span(example));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    example = "POKEMO\x00\x00\x00\x00\x00\x00";
 | 
				
			||||||
 | 
					    try std.testing.expectEqualSlices(u8, "POKEMO", span(example));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    example = "POKEM\x00\x00\x00\x00\x00\x00\x00";
 | 
				
			||||||
 | 
					    try std.testing.expectEqualSlices(u8, "POKEM", span(example));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    example = "POKE\x00\x00\x00\x00\x00\x00\x00\x00";
 | 
				
			||||||
 | 
					    try std.testing.expectEqualSlices(u8, "POKE", span(example));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    example = "POK\x00\x00\x00\x00\x00\x00\x00\x00\x00";
 | 
				
			||||||
 | 
					    try std.testing.expectEqualSlices(u8, "POK", span(example));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    example = "PO\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00";
 | 
				
			||||||
 | 
					    try std.testing.expectEqualSlices(u8, "PO", span(example));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    example = "P\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00";
 | 
				
			||||||
 | 
					    try std.testing.expectEqualSlices(u8, "P", span(example));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    example = "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00";
 | 
				
			||||||
 | 
					    try std.testing.expectEqualSlices(u8, "", span(example));
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// Creates a copy of a title with all Filesystem-invalid characters replaced
 | 
				
			||||||
 | 
					///
 | 
				
			||||||
 | 
					/// e.g. POKEPIN R/S to POKEPIN R_S
 | 
				
			||||||
 | 
					pub fn escape(title: [12]u8) [12]u8 {
 | 
				
			||||||
 | 
					    var ret: [12]u8 = title;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    //TODO: Add more replacements
 | 
				
			||||||
 | 
					    std.mem.replaceScalar(u8, &ret, '/', '_');
 | 
				
			||||||
 | 
					    std.mem.replaceScalar(u8, &ret, '\\', '_');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return ret;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub const FilePaths = struct {
 | 
				
			||||||
 | 
					    rom: []const u8,
 | 
				
			||||||
 | 
					    bios: ?[]const u8,
 | 
				
			||||||
 | 
					    save: ?[]const u8,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub const io = struct {
 | 
				
			||||||
 | 
					    pub const read = struct {
 | 
				
			||||||
 | 
					        pub fn todo(comptime log: anytype, comptime format: []const u8, args: anytype) u8 {
 | 
				
			||||||
 | 
					            log.debug(format, args);
 | 
				
			||||||
 | 
					            return 0;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        pub fn undef(comptime T: type, log: anytype, comptime format: []const u8, args: anytype) ?T {
 | 
				
			||||||
 | 
					            const unhandled_io = config.config().debug.unhandled_io;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            log.warn(format, args);
 | 
				
			||||||
 | 
					            if (builtin.mode == .Debug and !unhandled_io) std.debug.panic("TODO: Implement I/O Register", .{});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            return null;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub const write = struct {
 | 
				
			||||||
 | 
					        pub fn undef(log: anytype, comptime format: []const u8, args: anytype) void {
 | 
				
			||||||
 | 
					            const unhandled_io = config.config().debug.unhandled_io;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            log.warn(format, args);
 | 
				
			||||||
 | 
					            if (builtin.mode == .Debug and !unhandled_io) std.debug.panic("TODO: Implement I/O Register", .{});
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub const Logger = struct {
 | 
				
			||||||
 | 
					    const Self = @This();
 | 
				
			||||||
 | 
					    const FmtArgTuple = std.meta.Tuple(&.{ u32, u32, u32, u32, u32, u32, u32, u32, u32, u32, u32, u32, u32, u32, u32, u32, u32, u32 });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    buf: std.io.BufferedWriter(4096 << 2, std.fs.File.Writer),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn init(file: std.fs.File) Self {
 | 
				
			||||||
 | 
					        return .{
 | 
				
			||||||
 | 
					            .buf = .{ .unbuffered_writer = file.writer() },
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn print(self: *Self, comptime format: []const u8, args: anytype) !void {
 | 
				
			||||||
 | 
					        try self.buf.writer().print(format, args);
 | 
				
			||||||
 | 
					        try self.buf.flush(); // FIXME: On panics, whatever is in the buffer isn't written to file
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn mgbaLog(self: *Self, cpu: *const Arm7tdmi, opcode: u32) void {
 | 
				
			||||||
 | 
					        const fmt_base = "{X:0>8} {X:0>8} {X:0>8} {X:0>8} {X:0>8} {X:0>8} {X:0>8} {X:0>8} {X:0>8} {X:0>8} {X:0>8} {X:0>8} {X:0>8} {X:0>8} {X:0>8} {X:0>8} cpsr: {X:0>8} | ";
 | 
				
			||||||
 | 
					        const thumb_fmt = fmt_base ++ "{X:0>4}:\n";
 | 
				
			||||||
 | 
					        const arm_fmt = fmt_base ++ "{X:0>8}:\n";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (cpu.cpsr.t.read()) {
 | 
				
			||||||
 | 
					            if (opcode >> 11 == 0x1E) {
 | 
				
			||||||
 | 
					                // Instruction 1 of a BL Opcode, print in ARM mode
 | 
				
			||||||
 | 
					                const low = cpu.bus.dbgRead(u16, cpu.r[15]);
 | 
				
			||||||
 | 
					                const bl_opcode = @as(u32, opcode) << 16 | low;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                self.print(arm_fmt, Self.fmtArgs(cpu, bl_opcode)) catch @panic("failed to write to log file");
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					                self.print(thumb_fmt, Self.fmtArgs(cpu, opcode)) catch @panic("failed to write to log file");
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            self.print(arm_fmt, Self.fmtArgs(cpu, opcode)) catch @panic("failed to write to log file");
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fn fmtArgs(cpu: *const Arm7tdmi, opcode: u32) FmtArgTuple {
 | 
				
			||||||
 | 
					        return .{
 | 
				
			||||||
 | 
					            cpu.r[0],
 | 
				
			||||||
 | 
					            cpu.r[1],
 | 
				
			||||||
 | 
					            cpu.r[2],
 | 
				
			||||||
 | 
					            cpu.r[3],
 | 
				
			||||||
 | 
					            cpu.r[4],
 | 
				
			||||||
 | 
					            cpu.r[5],
 | 
				
			||||||
 | 
					            cpu.r[6],
 | 
				
			||||||
 | 
					            cpu.r[7],
 | 
				
			||||||
 | 
					            cpu.r[8],
 | 
				
			||||||
 | 
					            cpu.r[9],
 | 
				
			||||||
 | 
					            cpu.r[10],
 | 
				
			||||||
 | 
					            cpu.r[11],
 | 
				
			||||||
 | 
					            cpu.r[12],
 | 
				
			||||||
 | 
					            cpu.r[13],
 | 
				
			||||||
 | 
					            cpu.r[14],
 | 
				
			||||||
 | 
					            cpu.r[15] - if (cpu.cpsr.t.read()) 2 else @as(u32, 4),
 | 
				
			||||||
 | 
					            cpu.cpsr.raw,
 | 
				
			||||||
 | 
					            opcode,
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub const audio = struct {
 | 
				
			||||||
 | 
					    const _io = @import("core/bus/io.zig");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const ToneSweep = @import("core/apu/ToneSweep.zig");
 | 
				
			||||||
 | 
					    const Tone = @import("core/apu/Tone.zig");
 | 
				
			||||||
 | 
					    const Wave = @import("core/apu/Wave.zig");
 | 
				
			||||||
 | 
					    const Noise = @import("core/apu/Noise.zig");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub const length = struct {
 | 
				
			||||||
 | 
					        const FrameSequencer = @import("core/apu.zig").FrameSequencer;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /// Update State of Ch1, Ch2 and Ch3 length timer
 | 
				
			||||||
 | 
					        pub fn update(comptime T: type, self: *T, fs: *const FrameSequencer, nrx34: _io.Frequency) void {
 | 
				
			||||||
 | 
					            comptime std.debug.assert(T == ToneSweep or T == Tone or T == Wave);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // Write to NRx4 when FS's next step is not one that clocks the length counter
 | 
				
			||||||
 | 
					            if (!fs.isLengthNext()) {
 | 
				
			||||||
 | 
					                // If length_enable was disabled but is now enabled and length timer is not 0 already,
 | 
				
			||||||
 | 
					                // decrement the length timer
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                if (!self.freq.length_enable.read() and nrx34.length_enable.read() and self.len_dev.timer != 0) {
 | 
				
			||||||
 | 
					                    self.len_dev.timer -= 1;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    // If Length Timer is now 0 and trigger is clear, disable the channel
 | 
				
			||||||
 | 
					                    if (self.len_dev.timer == 0 and !nrx34.trigger.read()) self.enabled = false;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        pub const ch4 = struct {
 | 
				
			||||||
 | 
					            /// update state of ch4 length timer
 | 
				
			||||||
 | 
					            pub fn update(self: *Noise, fs: *const FrameSequencer, nr44: _io.NoiseControl) void {
 | 
				
			||||||
 | 
					                // Write to NRx4 when FS's next step is not one that clocks the length counter
 | 
				
			||||||
 | 
					                if (!fs.isLengthNext()) {
 | 
				
			||||||
 | 
					                    // If length_enable was disabled but is now enabled and length timer is not 0 already,
 | 
				
			||||||
 | 
					                    // decrement the length timer
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    if (!self.cnt.length_enable.read() and nr44.length_enable.read() and self.len_dev.timer != 0) {
 | 
				
			||||||
 | 
					                        self.len_dev.timer -= 1;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                        // If Length Timer is now 0 and trigger is clear, disable the channel
 | 
				
			||||||
 | 
					                        if (self.len_dev.timer == 0 and !nr44.trigger.read()) self.enabled = false;
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// Sets the high bits of an integer to a value
 | 
				
			||||||
 | 
					pub inline fn setHi(comptime T: type, left: T, right: HalfInt(T)) T {
 | 
				
			||||||
 | 
					    return switch (T) {
 | 
				
			||||||
 | 
					        u32 => (left & 0xFFFF_0000) | right,
 | 
				
			||||||
 | 
					        u16 => (left & 0xFF00) | right,
 | 
				
			||||||
 | 
					        u8 => (left & 0xF0) | right,
 | 
				
			||||||
 | 
					        else => @compileError("unsupported type"),
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// sets the low bits of an integer to a value
 | 
				
			||||||
 | 
					pub inline fn setLo(comptime T: type, left: T, right: HalfInt(T)) T {
 | 
				
			||||||
 | 
					    return switch (T) {
 | 
				
			||||||
 | 
					        u32 => (left & 0x0000_FFFF) | @as(u32, right) << 16,
 | 
				
			||||||
 | 
					        u16 => (left & 0x00FF) | @as(u16, right) << 8,
 | 
				
			||||||
 | 
					        u8 => (left & 0x0F) | @as(u8, right) << 4,
 | 
				
			||||||
 | 
					        else => @compileError("unsupported type"),
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// The Integer type which corresponds to T with exactly half the amount of bits
 | 
				
			||||||
 | 
					fn HalfInt(comptime T: type) type {
 | 
				
			||||||
 | 
					    const type_info = @typeInfo(T);
 | 
				
			||||||
 | 
					    comptime std.debug.assert(type_info == .Int); // Type must be an integer
 | 
				
			||||||
 | 
					    comptime std.debug.assert(type_info.Int.bits % 2 == 0); // Type must have an even amount of bits
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return std.meta.Int(type_info.Int.signedness, type_info.Int.bits >> 1);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// Double Buffering Implementation
 | 
				
			||||||
 | 
					pub const FrameBuffer = struct {
 | 
				
			||||||
 | 
					    const Self = @This();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    layers: [2][]u8,
 | 
				
			||||||
 | 
					    buf: []u8,
 | 
				
			||||||
 | 
					    current: u1,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    allocator: Allocator,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // TODO: Rename
 | 
				
			||||||
 | 
					    const Device = enum { Emulator, Renderer };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn init(allocator: Allocator, comptime len: comptime_int) !Self {
 | 
				
			||||||
 | 
					        const buf = try allocator.alloc(u8, len * 2);
 | 
				
			||||||
 | 
					        std.mem.set(u8, buf, 0);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return .{
 | 
				
			||||||
 | 
					            // Front and Back Framebuffers
 | 
				
			||||||
 | 
					            .layers = [_][]u8{ buf[0..][0..len], buf[len..][0..len] },
 | 
				
			||||||
 | 
					            .buf = buf,
 | 
				
			||||||
 | 
					            .current = 0,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            .allocator = allocator,
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn deinit(self: *Self) void {
 | 
				
			||||||
 | 
					        self.allocator.free(self.buf);
 | 
				
			||||||
 | 
					        self.* = undefined;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn swap(self: *Self) void {
 | 
				
			||||||
 | 
					        self.current = ~self.current;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn get(self: *Self, comptime dev: Device) []u8 {
 | 
				
			||||||
 | 
					        return self.layers[if (dev == .Emulator) self.current else ~self.current];
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user