├── .gitignore ├── .travis.yml ├── CONTRIBUTING.md ├── Cargo.lock ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md ├── Rustendo-64.png ├── appveyor.yml ├── appveyor_rust_install.ps1 └── src ├── debugger ├── command.rs └── mod.rs ├── main.rs └── n64 ├── audio_interface.rs ├── cpu ├── cp0 │ ├── cp0.rs │ ├── mod.rs │ ├── reg_config.rs │ └── reg_status.rs ├── cpu.rs ├── instruction.rs ├── mod.rs └── opcode.rs ├── interconnect.rs ├── mem_map.rs ├── mod.rs ├── n64.rs ├── peripheral_interface.rs ├── pif.rs ├── rdp.rs ├── rsp.rs ├── serial_interface.rs └── video_interface.rs /.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: rust 2 | rust: 3 | - stable 4 | - beta 5 | - nightly 6 | notifications: 7 | email: 8 | - jake@fusetools.com 9 | before_script: 10 | - rustc --version 11 | - cargo --version 12 | branches: 13 | only: 14 | - master 15 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | ## Contribution 2 | 3 | Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions. 4 | 5 | I do accept (and appreciate!) pull requests, but please, try to keep them to _small, meaningful, isolated changes only_ that I can go over completely on the stream. Significant outside contributions, as cool as they may be, somewhat defeat the most important part of this project - **documenting everything through livecoding**. I'm happy to accept small cosmetic changes/bugfixes, but please, consider what the larger audience as a whole might be missing out on when they don't get to see the thought process and resources that went into making the contribution (which is unfortunately what happens whenever I accept a PR). 6 | 7 | If you'd like to see a particular library or coding style used somewhere, opening an issue is much preferred over a PR, so we can discuss it beforehand and implement it live. This also keeps people from stepping on each others' toes and implementing the same things (yes, this has happened already). 8 | 9 | Issues, especially pertaining to accuracy/bugfixes, are always more than welcome! 10 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | [root] 2 | name = "rustendo64" 3 | version = "0.1.0" 4 | dependencies = [ 5 | "byteorder 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", 6 | "enum_primitive 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", 7 | "nom 1.2.3 (registry+https://github.com/rust-lang/crates.io-index)", 8 | "num 0.1.30 (registry+https://github.com/rust-lang/crates.io-index)", 9 | ] 10 | 11 | [[package]] 12 | name = "advapi32-sys" 13 | version = "0.1.2" 14 | source = "registry+https://github.com/rust-lang/crates.io-index" 15 | dependencies = [ 16 | "winapi 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)", 17 | "winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", 18 | ] 19 | 20 | [[package]] 21 | name = "byteorder" 22 | version = "0.4.2" 23 | source = "registry+https://github.com/rust-lang/crates.io-index" 24 | 25 | [[package]] 26 | name = "enum_primitive" 27 | version = "0.1.0" 28 | source = "registry+https://github.com/rust-lang/crates.io-index" 29 | dependencies = [ 30 | "num 0.1.30 (registry+https://github.com/rust-lang/crates.io-index)", 31 | ] 32 | 33 | [[package]] 34 | name = "libc" 35 | version = "0.2.6" 36 | source = "registry+https://github.com/rust-lang/crates.io-index" 37 | 38 | [[package]] 39 | name = "nom" 40 | version = "1.2.3" 41 | source = "registry+https://github.com/rust-lang/crates.io-index" 42 | 43 | [[package]] 44 | name = "num" 45 | version = "0.1.30" 46 | source = "registry+https://github.com/rust-lang/crates.io-index" 47 | dependencies = [ 48 | "rand 0.3.13 (registry+https://github.com/rust-lang/crates.io-index)", 49 | "rustc-serialize 0.3.16 (registry+https://github.com/rust-lang/crates.io-index)", 50 | ] 51 | 52 | [[package]] 53 | name = "rand" 54 | version = "0.3.13" 55 | source = "registry+https://github.com/rust-lang/crates.io-index" 56 | dependencies = [ 57 | "advapi32-sys 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", 58 | "libc 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", 59 | "winapi 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)", 60 | ] 61 | 62 | [[package]] 63 | name = "rustc-serialize" 64 | version = "0.3.16" 65 | source = "registry+https://github.com/rust-lang/crates.io-index" 66 | 67 | [[package]] 68 | name = "winapi" 69 | version = "0.2.5" 70 | source = "registry+https://github.com/rust-lang/crates.io-index" 71 | 72 | [[package]] 73 | name = "winapi-build" 74 | version = "0.1.1" 75 | source = "registry+https://github.com/rust-lang/crates.io-index" 76 | 77 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rustendo64" 3 | version = "0.1.0" 4 | authors = ["ferris "] 5 | license = "MIT/Apache-2.0" 6 | 7 | [dependencies] 8 | byteorder = "0.4.2" 9 | num = "0.1.30" 10 | enum_primitive = "0.1.0" 11 | nom = "^1.2.3" -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright 2016 Jake Taylor 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Copyright (c) 2016 Jake Taylor 2 | 3 | Permission is hereby granted, free of charge, to any 4 | person obtaining a copy of this software and associated 5 | documentation files (the "Software"), to deal in the 6 | Software without restriction, including without 7 | limitation the rights to use, copy, modify, merge, 8 | publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software 10 | is furnished to do so, subject to the following 11 | conditions: 12 | 13 | The above copyright notice and this permission notice 14 | shall be included in all copies or substantial portions 15 | of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 18 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 19 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 20 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 21 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 22 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 23 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 24 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 25 | DEALINGS IN THE SOFTWARE. 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # rustendo64 [![Build Status](https://travis-ci.org/yupferris/rustendo64.svg?branch=master)](https://travis-ci.org/yupferris/rustendo64) [![Build Status](https://ci.appveyor.com/api/projects/status/owjloq84v91147nd/branch/master?svg=true)](https://ci.appveyor.com/project/yupferris/rustendo64/branch/master) 2 | 3 | ![rustendo64](Rustendo-64.png) 4 | 5 | Livecoding a Nintendo 64 emulator in Rust :D 6 | 7 | ## Follow along 8 | This is a WIP emulator that we're building from scratch live. I haven't prepared hardly anything in advance before starting this endeavor (I'm new to the n64 hardware myself). The entire process is currently being streamed on [Twitch](http://www.twitch.tv/ferrisstreamsstuff), and each segment is being recorded and uploaded to [this YouTube playlist](https://www.youtube.com/playlist?list=PL-sXmdrqqYYcznDg4xwAJWQgNL2gRray2). For stream times and announcements, you can check out [my Twitter](https://twitter.com/ferristweetsnow). 9 | 10 | At the end of each episode, I mark the latest commit with a tag so you can see where we finished. Check the [releases](https://github.com/yupferris/rustendo64/releases) for this repo to see those. 11 | 12 | ## Helpful tools 13 | - [Hex Fiend](http://ridiculousfish.com/hexfiend/) 14 | - [Online disassembler](https://www.onlinedisassembler.com/odaweb/) 15 | - [Dash](https://kapeli.com/dash) (OS X / iOS) for documentation. Compatible alternatives for other platforms can be found linked from [Dash Docset Links](https://kapeli.com/docset_links). 16 | 17 | ## Literature 18 | - [n64dev repo doc's](https://github.com/mikeryan/n64dev/tree/master/docs) 19 | - [VR4300 datasheet](http://datasheets.chipdb.org/NEC/Vr-Series/Vr43xx/U10504EJ7V0UMJ1.pdf) 20 | - [MIPS R4000 technical overview](https://www.hotchips.org/wp-content/uploads/hc_archives/hc03/2_Mon/HC3.S1/HC3.1.2.pdf) 21 | - [MIPS pipeline info](http://www.ece.ucsb.edu/~strukov/ece154aFall2013/viewgraphs/pipelinedMIPS.pdf) 22 | - [R3000 pipeline specification](https://www.researchgate.net/publication/2643911_Pipeline_Specification_of_a_MIPS_R3000_CPU) (not necessarily applicable to the VR4300, but very likely to be the same) 23 | - [Forum post where we found some boot info](http://www.emutalk.net/threads/53938-N64-tech-documentation) 24 | - [Detailed N64 memory map](http://infrid.com/rcp64/docfiles/n64maps.txt) 25 | - [Alternate MIPS register names](http://www.cs.umd.edu/class/sum2003/cmsc311/Notes/Mips/altReg.html) 26 | 27 | ## Test ROM's 28 | * [Turtle's enormous public domain ROM repository](https://github.com/vgturtle127/N64-PD-ROMS) 29 | * [Pouet list](http://www.pouet.net/prodlist.php?platform[0]=Nintendo+64&page=1) 30 | * [Zophar](http://www.zophar.net/pdroms/n64.html) 31 | * [PDROMs](http://pdroms.de/news/nintendo64/) 32 | * [Micro-64](http://micro-64.com/features/aafeatures.shtml) 33 | * [PeterLemon's ROMs](https://github.com/PeterLemon/N64) 34 | 35 | ## Building and Running 36 | Currently, the only dependency for building is Rust itself, which can be downloaded [here](https://www.rust-lang.org/downloads.html). 37 | 38 | An N64 BIOS (PIF ROM) is required to boot the emulator. The ROM I've been testing with thus far has a SHA-1 of `9174eadc0f0ea2654c95fd941406ab46b9dc9bdd`. 39 | 40 | ## License 41 | 42 | Licensed under either of 43 | 44 | * Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) 45 | * MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) 46 | 47 | at your option. 48 | 49 | ## Contribution 50 | 51 | Please read [Contribution notes](CONTRIBUTING.md) before submitting a PR! 52 | -------------------------------------------------------------------------------- /Rustendo-64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yupferris/rustendo64/5aafc7a307ba942dca607a6d3300e35092cb8deb/Rustendo-64.png -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | ## Operating System (VM environment) ## 2 | 3 | # Rust needs at least Visual Studio 2013 Appveyor OS for MSVC targets. 4 | os: Visual Studio 2015 5 | 6 | ## Build Matrix ## 7 | 8 | # This configuration will setup a build for each channel & target combination (12 windows 9 | # combinations in all). 10 | # 11 | # There are 3 channels: stable, beta, and nightly. 12 | # 13 | # The values for target are the set of windows Rust build targets. Each value is of the form 14 | # 15 | # ARCH-pc-windows-TOOLCHAIN 16 | # 17 | # Where ARCH is the target architecture, either x86_64 or i686, and TOOLCHAIN is the linker 18 | # toolchain to use, either msvc or gnu. See https://www.rust-lang.org/downloads.html#win-foot for 19 | # a description of the toolchain differences. 20 | # 21 | # Comment out channel/target combos you do not wish to build in CI. 22 | environment: 23 | matrix: 24 | 25 | ### MSVC Toolchains ### 26 | 27 | # Stable 64-bit MSVC 28 | - channel: stable 29 | target: x86_64-pc-windows-msvc 30 | # Stable 32-bit MSVC 31 | - channel: stable 32 | target: i686-pc-windows-msvc 33 | # Beta 64-bit MSVC 34 | - channel: beta 35 | target: x86_64-pc-windows-msvc 36 | # Beta 32-bit MSVC 37 | - channel: beta 38 | target: i686-pc-windows-msvc 39 | # Nightly 64-bit MSVC 40 | - channel: nightly 41 | target: x86_64-pc-windows-msvc 42 | # Nightly 32-bit MSVC 43 | - channel: nightly 44 | target: i686-pc-windows-msvc 45 | 46 | ### GNU Toolchains ### 47 | 48 | # Stable 64-bit GNU 49 | - channel: stable 50 | target: x86_64-pc-windows-gnu 51 | # Stable 32-bit GNU 52 | - channel: stable 53 | target: i686-pc-windows-gnu 54 | # Beta 64-bit GNU 55 | - channel: beta 56 | target: x86_64-pc-windows-gnu 57 | # Beta 32-bit GNU 58 | - channel: beta 59 | target: i686-pc-windows-gnu 60 | # Nightly 64-bit GNU 61 | - channel: nightly 62 | target: x86_64-pc-windows-gnu 63 | # Nightly 32-bit GNU 64 | - channel: nightly 65 | target: i686-pc-windows-gnu 66 | 67 | ### Allowed failures ### 68 | 69 | # See Appveyor documentation for specific details. In short, place any channel or targets you wish 70 | # to allow build failures on (usually nightly at least is a wise choice). This will prevent a build 71 | # or test failure in the matching channels/targets from failing the entire build. 72 | matrix: 73 | allow_failures: 74 | - channel: nightly 75 | 76 | # If you only care about stable channel build failures, uncomment the following line: 77 | #- channel: beta 78 | 79 | # 32-bit MSVC isn't stablized yet, so you may optionally allow failures there (uncomment line): 80 | #- target: i686-pc-windows-msvc 81 | 82 | ## Install Script ## 83 | 84 | # This is the most important part of the Appveyor configuration. This installs the version of Rust 85 | # specified by the 'channel' and 'target' environment variables from the build matrix. By default, 86 | # Rust will be installed to C:\Rust for easy usage, but this path can be overridden by setting the 87 | # RUST_INSTALL_DIR environment variable. The URL to download rust distributions defaults to 88 | # https://static.rust-lang.org/dist/ but can overridden by setting the RUST_DOWNLOAD_URL environment 89 | # variable. 90 | # 91 | # For simple configurations, instead of using the build matrix, you can override the channel and 92 | # target environment variables with the -channel and -target script arguments. 93 | # 94 | # If no channel or target arguments or environment variables are specified, will default to stable 95 | # channel and x86_64-pc-windows-msvc target. 96 | # 97 | # The file appveyor_rust_install.ps1 must exist in the root directory of the repository. 98 | install: 99 | - ps: .\appveyor_rust_install.ps1 100 | 101 | # Alternative install command for simple configurations without build matrix (uncomment line and 102 | # comment above line): 103 | #- ps: .\appveyor_rust_install.ps1 -channel stable -target x86_64-pc-windows-msvc 104 | 105 | ## Build Script ## 106 | 107 | # Uses 'cargo build' to build. Alternatively, the project may call rustc directly or perform other 108 | # build commands. Rust will automatically be placed in the PATH environment variable. 109 | build_script: 110 | - cmd: cargo build --verbose 111 | 112 | ## Build Script ## 113 | 114 | # Uses 'cargo test' to run tests. Alternatively, the project may call compiled programs directly or 115 | # perform other testing commands. Rust will automatically be placed in the PATH environment 116 | # variable. 117 | test_script: 118 | - cmd: cargo test --verbose 119 | 120 | # Only build against the branches that will have pull requests built against them (master). 121 | # Otherwise creating feature branches on this repository and a pull requests against them will 122 | # cause each commit to be tested twice, once for the branch and once for the pull request. 123 | branches: 124 | only: 125 | - master 126 | -------------------------------------------------------------------------------- /appveyor_rust_install.ps1: -------------------------------------------------------------------------------- 1 | ##### Appveyor Rust Install Script ##### 2 | 3 | # This is the most important part of the Appveyor configuration. This installs the version of Rust 4 | # specified by the "channel" and "target" environment variables from the build matrix. By default, 5 | # Rust will be installed to C:\Rust for easy usage, but this path can be overridden by setting the 6 | # RUST_INSTALL_DIR environment variable. The URL to download rust distributions defaults to 7 | # https://static.rust-lang.org/dist/ but can overridden by setting the RUST_DOWNLOAD_URL environment 8 | # variable. 9 | # 10 | # For simple configurations, instead of using the build matrix, you can override the channel and 11 | # target environment variables with the --channel and --target script arguments. 12 | # 13 | # If no channel or target arguments or environment variables are specified, will default to stable 14 | # channel and x86_64-pc-windows-msvc target. 15 | 16 | param([string]$channel=${env:channel}, [string]$target=${env:target}) 17 | 18 | # Initialize our parameters from arguments and environment variables, falling back to defaults 19 | if (!$channel) { 20 | $channel = "stable" 21 | } 22 | if (!$target) { 23 | $target = "x86_64-pc-windows-msvc" 24 | } 25 | 26 | $downloadUrl = "https://static.rust-lang.org/dist/" 27 | if ($env:RUST_DOWNLOAD_URL) { 28 | $downloadUrl = $env:RUST_DOWNLOAD_URL 29 | } 30 | 31 | $installDir = "C:\Rust" 32 | if ($env:RUST_INSTALL_DIR) { 33 | $installUrl = $env:RUST_INSTALL_DIR 34 | } 35 | 36 | # Download manifest so we can find actual filename of installer to download. Needed mostly for 37 | # stable channel. 38 | echo "Downloading $channel channel manifest" 39 | $manifest = "${env:Temp}\channel-rust-${channel}" 40 | Start-FileDownload "${downloadUrl}channel-rust-${channel}" -FileName "$manifest" 41 | 42 | # Search the manifest lines for the correct filename based on target 43 | $match = Get-Content "$manifest" | Select-String -pattern "${target}.exe" -simplematch 44 | 45 | if (!$match -or !$match.line) { 46 | throw "Could not find $target in $channel channel manifest" 47 | } 48 | 49 | $installer = $match.line 50 | 51 | # Download installer 52 | echo "Downloading ${downloadUrl}$installer" 53 | Start-FileDownload "${downloadUrl}$installer" -FileName "${env:Temp}\$installer" 54 | 55 | # Execute installer and wait for it to finish 56 | echo "Installing $installer to $installDir" 57 | &"${env:Temp}\$installer" /VERYSILENT /NORESTART /DIR="$installDir" | Write-Output 58 | 59 | # Add Rust to the path. 60 | $env:Path += ";${installDir}\bin;C:\MinGW\bin" 61 | 62 | echo "Installation of $channel Rust $target completed" 63 | 64 | # Test and display installed version information for rustc and cargo 65 | rustc -V 66 | cargo -V -------------------------------------------------------------------------------- /src/debugger/command.rs: -------------------------------------------------------------------------------- 1 | use std::borrow::Cow; 2 | use std::str::{self, FromStr}; 3 | 4 | use nom::{IResult, eof, space, digit}; 5 | 6 | #[derive(Debug, Clone, Copy)] 7 | pub enum Command { 8 | Step(usize), 9 | Exit, 10 | Repeat, 11 | } 12 | 13 | impl FromStr for Command { 14 | type Err = Cow<'static, str>; 15 | 16 | fn from_str(s: &str) -> Result { 17 | match command(s.as_bytes()) { 18 | IResult::Done(_, c) => Ok(c), 19 | err => Err(format!("Unable to parse command: {:?}", err).into()) 20 | } 21 | } 22 | } 23 | 24 | named!( 25 | command, 26 | chain!( 27 | c: alt_complete!( 28 | step | 29 | exit | 30 | repeat) ~ 31 | eof, 32 | || c)); 33 | 34 | named!( 35 | step, 36 | chain!( 37 | alt_complete!(tag!("step") | tag!("s")) ~ 38 | count: opt!(preceded!(space, usize_parser)), 39 | || Command::Step(count.unwrap_or(1)))); 40 | 41 | named!( 42 | exit, 43 | map!( 44 | alt_complete!(tag!("exit") | tag!("quit") | tag!("e") | tag!("q")), 45 | |_| Command::Exit)); 46 | 47 | named!( 48 | repeat, 49 | value!(Command::Repeat)); 50 | 51 | named!( 52 | usize_parser, 53 | map_res!( 54 | map_res!( 55 | digit, 56 | str::from_utf8), 57 | FromStr::from_str)); 58 | -------------------------------------------------------------------------------- /src/debugger/mod.rs: -------------------------------------------------------------------------------- 1 | mod command; 2 | 3 | use std::io::{stdin, stdout}; 4 | use std::io::prelude::*; 5 | use std::borrow::Cow; 6 | use n64::cpu::Instruction; 7 | use n64::cpu::opcode::Opcode::*; 8 | use n64::mem_map; 9 | use n64::mem_map::Addr::*; 10 | use n64::N64; 11 | use self::command::Command; 12 | 13 | pub struct Debugger { 14 | n64: N64, 15 | 16 | last_command: Option, 17 | } 18 | 19 | impl Debugger { 20 | pub fn new(n64: N64) -> Debugger { 21 | Debugger { 22 | n64: n64, 23 | 24 | last_command: None, 25 | } 26 | } 27 | 28 | pub fn run(&mut self) { 29 | loop { 30 | print!("r64> "); 31 | stdout().flush().unwrap(); 32 | 33 | let command = match (read_stdin().parse(), self.last_command) { 34 | (Ok(Command::Repeat), Some(c)) => Ok(c), 35 | (Ok(Command::Repeat), None) => Err("No last command".into()), 36 | (Ok(c), _) => Ok(c), 37 | (Err(e), _) => Err(e), 38 | }; 39 | 40 | match command { 41 | Ok(Command::Step(count)) => self.step(count), 42 | Ok(Command::Exit) => break, 43 | Ok(Command::Repeat) => unreachable!(), 44 | Err(ref e) => println!("{}", e), 45 | } 46 | 47 | self.last_command = command.ok(); 48 | } 49 | } 50 | 51 | pub fn step(&mut self, count: usize) { 52 | for _ in 0..count { 53 | let current_pc = self.n64.cpu().current_pc_phys(); 54 | let addr = mem_map::map_addr(current_pc as u32); 55 | let instr = Instruction(match addr { 56 | PifRom(offset) => self.n64.interconnect().pif().read_boot_rom(offset), 57 | _ => panic!("Debugger can't inspect address: {:?}", addr), 58 | }); 59 | 60 | print!("{:018X}: ", current_pc); 61 | 62 | match instr.opcode() { 63 | Special => print!("{:?} (Special)", instr.special_op()), 64 | RegImm => print!("{:?} (RegImm)", instr.reg_imm_op()), 65 | _ => print!("{:?}", instr), 66 | } 67 | 68 | if self.n64.cpu().will_execute_from_delay_slot() { 69 | println!(" (DELAY)"); 70 | } else { 71 | println!(""); 72 | } 73 | 74 | self.n64.step(); 75 | } 76 | } 77 | } 78 | 79 | fn read_stdin() -> String { 80 | let mut input = String::new(); 81 | stdin().read_line(&mut input).unwrap(); 82 | input.trim().into() 83 | } 84 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | #![deny(trivial_casts, trivial_numeric_casts)] 2 | 3 | extern crate byteorder; 4 | 5 | extern crate num; 6 | 7 | #[macro_use] 8 | extern crate enum_primitive; 9 | 10 | #[macro_use] 11 | extern crate nom; 12 | 13 | mod n64; 14 | mod debugger; 15 | 16 | use std::env; 17 | use std::fs::File; 18 | use std::io::Read; 19 | use std::path::Path; 20 | use debugger::Debugger; 21 | use n64::N64; 22 | 23 | fn main() { 24 | let pif_file_name = env::args().nth(1).unwrap(); 25 | let rom_file_name = env::args().nth(2).unwrap(); 26 | 27 | let pif = read_bin(pif_file_name); 28 | let rom = read_bin(rom_file_name); 29 | 30 | let n64 = N64::new(pif, rom); 31 | let mut debugger = Debugger::new(n64); 32 | debugger.run(); 33 | } 34 | 35 | fn read_bin>(path: P) -> Box<[u8]> { 36 | let mut file = File::open(path).unwrap(); 37 | let mut file_buf = Vec::new(); 38 | file.read_to_end(&mut file_buf).unwrap(); 39 | file_buf.into_boxed_slice() 40 | } 41 | -------------------------------------------------------------------------------- /src/n64/audio_interface.rs: -------------------------------------------------------------------------------- 1 | #[derive(Default)] 2 | pub struct AudioInterface { 3 | dram_addr: u32, 4 | length: u32, 5 | } 6 | 7 | impl AudioInterface { 8 | pub fn read_dram_addr_reg(&self) -> u32 { 9 | self.dram_addr 10 | } 11 | 12 | pub fn write_dram_addr_reg(&mut self, value: u32) { 13 | self.dram_addr = value & 0x00ff_ffff; 14 | } 15 | 16 | pub fn read_len_reg(&self) -> u32 { 17 | self.length 18 | } 19 | 20 | pub fn write_len_reg(&mut self, value: u32) { 21 | self.length = value & 0x0003_fff8; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/n64/cpu/cp0/cp0.rs: -------------------------------------------------------------------------------- 1 | use super::{reg_config, reg_status}; 2 | 3 | #[derive(Debug, Default)] 4 | pub struct Cp0 { 5 | reg_status: reg_status::RegStatus, 6 | reg_config: reg_config::RegConfig, 7 | } 8 | 9 | impl Cp0 { 10 | pub fn write_reg(&mut self, index: u32, data: u64) { 11 | match index { 12 | 12 => self.reg_status = (data as u32).into(), 13 | 16 => self.reg_config = (data as u32).into(), 14 | _ => panic!("Unrecognized Cp0 reg: {}, {:#018x}", index, data), 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/n64/cpu/cp0/mod.rs: -------------------------------------------------------------------------------- 1 | mod cp0; 2 | mod reg_config; 3 | mod reg_status; 4 | 5 | pub use self::cp0::Cp0; 6 | -------------------------------------------------------------------------------- /src/n64/cpu/cp0/reg_config.rs: -------------------------------------------------------------------------------- 1 | #[derive(Debug, Default)] 2 | pub struct RegConfig { 3 | // EP 4 | data_transfer_pattern: DataTransferPattern, 5 | 6 | // BE 7 | endianness: Endianness, 8 | 9 | cu: bool, 10 | kseg0_cache_enable_bits: [bool; 3], 11 | } 12 | 13 | impl RegConfig { 14 | fn kseg0_cache_enabled(&self) -> bool { 15 | !(!self.kseg0_cache_enable_bits[0] && 16 | self.kseg0_cache_enable_bits[1] && 17 | !self.kseg0_cache_enable_bits[2]) 18 | } 19 | } 20 | 21 | impl From for RegConfig { 22 | fn from(value: u32) -> Self { 23 | RegConfig { 24 | data_transfer_pattern: value.into(), 25 | 26 | endianness: value.into(), 27 | 28 | cu: (value & (1 << 3)) != 0, 29 | kseg0_cache_enable_bits: [ 30 | (value & (1 << 0)) != 0, 31 | (value & (1 << 1)) != 0, 32 | (value & (1 << 2)) != 0, 33 | ] 34 | } 35 | } 36 | } 37 | 38 | #[derive(Debug)] 39 | enum DataTransferPattern { 40 | Normal, // D 41 | DxxDxx, 42 | } 43 | 44 | impl Default for DataTransferPattern { 45 | fn default() -> Self { 46 | DataTransferPattern::Normal 47 | } 48 | } 49 | 50 | impl From for DataTransferPattern { 51 | fn from(value: u32) -> Self { 52 | match (value >> 24) & 0b1111 { 53 | 0 => DataTransferPattern::Normal, 54 | 6 => DataTransferPattern::DxxDxx, 55 | _ => panic!("Invalid data transfer pattern (EP): {:#x}", value), 56 | } 57 | } 58 | } 59 | 60 | #[derive(Debug)] 61 | enum Endianness { 62 | Little, 63 | Big, 64 | } 65 | 66 | impl Default for Endianness { 67 | fn default() -> Self { 68 | Endianness::Big 69 | } 70 | } 71 | 72 | impl From for Endianness { 73 | fn from(value: u32) -> Self { 74 | match (value >> 15) & 0b1 { 75 | 0 => Endianness::Little, 76 | 1 => Endianness::Big, 77 | _ => unreachable!(), 78 | } 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/n64/cpu/cp0/reg_status.rs: -------------------------------------------------------------------------------- 1 | #[derive(Debug, Default)] 2 | pub struct RegStatus { 3 | // CU 4 | coprocessor_usability: [bool; 4], 5 | 6 | // RP 7 | low_power: bool, 8 | 9 | // FR 10 | additional_fp_regs: bool, 11 | 12 | // RE 13 | reverse_endian: bool, 14 | 15 | // DS 16 | diagnostic_status: DiagnosticStatus, 17 | 18 | // IM(7:0) 19 | interrupt_mask: InterruptMask, 20 | 21 | // KX 22 | kernel_mode_64bit_addressing: bool, 23 | 24 | // SX 25 | supervisor_mode_64bit_addressing: bool, 26 | 27 | // UX 28 | user_mode_64bit_addressing: bool, 29 | 30 | // KSU 31 | mode: Mode, 32 | 33 | // ERL 34 | error_level: bool, 35 | 36 | // EXL 37 | exception_level: bool, 38 | 39 | // IE 40 | interrupts_enabled: bool, 41 | } 42 | 43 | impl From for RegStatus { 44 | fn from(value: u32) -> Self { 45 | RegStatus { 46 | coprocessor_usability: [ 47 | (value & (1 << 28)) != 0, 48 | (value & (1 << 29)) != 0, 49 | (value & (1 << 30)) != 0, 50 | (value & (1 << 31)) != 0], 51 | 52 | low_power: (value & (1 << 27)) != 0, 53 | additional_fp_regs: (value & (1 << 26)) != 0, 54 | reverse_endian: (value & (1 << 25)) != 0, 55 | 56 | diagnostic_status: value.into(), 57 | interrupt_mask: value.into(), 58 | 59 | kernel_mode_64bit_addressing: (value & (1 << 7)) != 0, 60 | supervisor_mode_64bit_addressing: (value & (1 << 6)) != 0, 61 | user_mode_64bit_addressing: (value & (1 << 5)) != 0, 62 | 63 | mode: value.into(), 64 | 65 | error_level: (value & (1 << 2)) != 0, 66 | exception_level: (value & (1 << 1)) != 0, 67 | interrupts_enabled: (value & (1 << 0)) != 0, 68 | } 69 | } 70 | } 71 | 72 | #[derive(Debug, Default)] 73 | struct DiagnosticStatus { 74 | // ITS 75 | instruction_trace_support: bool, 76 | 77 | // BEV 78 | // TODO: Better name? 79 | tlb_general_exception_vector_location: TLBGeneralExceptionVectorLocation, 80 | 81 | // TS 82 | tlb_shutdown: bool, 83 | 84 | // SR 85 | soft_reset_or_nmi_occurred: bool, 86 | 87 | // CH 88 | condition_bit: bool, 89 | } 90 | 91 | impl From for DiagnosticStatus { 92 | fn from(value: u32) -> Self { 93 | DiagnosticStatus { 94 | instruction_trace_support: (value & (1 << 24)) != 0, 95 | 96 | tlb_general_exception_vector_location: value.into(), 97 | 98 | tlb_shutdown: (value & (1 << 21)) != 0, 99 | soft_reset_or_nmi_occurred: (value & (1 << 20)) != 0, 100 | condition_bit: (value & (1 << 18)) != 0, 101 | } 102 | } 103 | } 104 | 105 | // TODO: Better name? 106 | #[derive(Debug)] 107 | enum TLBGeneralExceptionVectorLocation { 108 | Normal, 109 | Bootstrap, 110 | } 111 | 112 | impl Default for TLBGeneralExceptionVectorLocation { 113 | fn default() -> Self { 114 | TLBGeneralExceptionVectorLocation::Normal 115 | } 116 | } 117 | 118 | impl From for TLBGeneralExceptionVectorLocation { 119 | fn from(value: u32) -> Self { 120 | match (value >> 22) & 0b1 { 121 | 0 => TLBGeneralExceptionVectorLocation::Normal, 122 | 1 => TLBGeneralExceptionVectorLocation::Bootstrap, 123 | _ => unreachable!(), 124 | } 125 | } 126 | } 127 | 128 | #[derive(Debug, Default)] 129 | struct InterruptMask { 130 | // IM(7) 131 | timer_interrupt: bool, 132 | 133 | // IM(6:2) 134 | external_interrupt_write_req: [bool; 5], 135 | 136 | // IM(1:0) 137 | software_interrupt_cause_reg: [bool; 2], 138 | } 139 | 140 | impl From for InterruptMask { 141 | fn from(value: u32) -> Self { 142 | InterruptMask { 143 | timer_interrupt: (value & (1 << 15)) != 0, 144 | 145 | external_interrupt_write_req: [ 146 | (value & (1 << 10)) != 0, 147 | (value & (1 << 11)) != 0, 148 | (value & (1 << 12)) != 0, 149 | (value & (1 << 13)) != 0, 150 | (value & (1 << 14)) != 0], 151 | 152 | software_interrupt_cause_reg: [ 153 | (value & (1 << 8)) != 0, 154 | (value & (1 << 9)) != 0] 155 | } 156 | } 157 | } 158 | 159 | #[derive(Debug)] 160 | enum Mode { 161 | Kernel, 162 | Supervisor, 163 | User, 164 | } 165 | 166 | impl Default for Mode { 167 | fn default() -> Self { 168 | Mode::Kernel 169 | } 170 | } 171 | 172 | impl From for Mode { 173 | fn from(value: u32) -> Self { 174 | match (value >> 3) & 0b11 { 175 | 0b00 => Mode::Kernel, 176 | 0b01 => Mode::Supervisor, 177 | 0b10 => Mode::User, 178 | _ => panic!("Invalid cp0 KSU bits: {:#b}", value), 179 | } 180 | } 181 | } 182 | -------------------------------------------------------------------------------- /src/n64/cpu/cpu.rs: -------------------------------------------------------------------------------- 1 | use super::{cp0, Instruction}; 2 | use super::opcode::Opcode::*; 3 | use super::opcode::RegImmOpcode::*; 4 | use super::opcode::SpecialOpcode::*; 5 | use super::super::Interconnect; 6 | 7 | use std::fmt; 8 | 9 | const NUM_GPR: usize = 32; 10 | 11 | enum SignExtendResult { 12 | Yes, 13 | No, 14 | } 15 | 16 | enum WriteLink { 17 | Yes, 18 | No, 19 | } 20 | 21 | pub struct Cpu { 22 | reg_gpr: [u64; NUM_GPR], 23 | reg_fpr: [f64; NUM_GPR], 24 | 25 | reg_pc: u64, 26 | 27 | reg_hi: u64, 28 | reg_lo: u64, 29 | 30 | reg_llbit: bool, // TODO: Enum type 31 | 32 | reg_fcr0: u32, 33 | reg_fcr31: u32, 34 | 35 | cp0: cp0::Cp0, 36 | 37 | delay_slot_pc: Option, 38 | } 39 | 40 | impl Cpu { 41 | pub fn new() -> Cpu { 42 | Cpu { 43 | reg_gpr: [0; NUM_GPR], 44 | reg_fpr: [0.0; NUM_GPR], 45 | 46 | reg_pc: 0xffff_ffff_bfc0_0000, // TODO: Move to const 47 | 48 | reg_hi: 0, 49 | reg_lo: 0, 50 | 51 | reg_llbit: false, 52 | 53 | reg_fcr0: 0, 54 | reg_fcr31: 0, 55 | 56 | cp0: cp0::Cp0::default(), 57 | 58 | delay_slot_pc: None, 59 | } 60 | } 61 | 62 | pub fn current_pc_virt(&self) -> u64 { 63 | self.delay_slot_pc.unwrap_or(self.reg_pc) 64 | } 65 | 66 | pub fn current_pc_phys(&self) -> u64 { 67 | self.virt_addr_to_phys_addr(self.current_pc_virt()) 68 | } 69 | 70 | pub fn will_execute_from_delay_slot(&self) -> bool { 71 | self.delay_slot_pc.is_some() 72 | } 73 | 74 | pub fn step(&mut self, interconnect: &mut Interconnect) { 75 | if let Some(pc) = self.delay_slot_pc { 76 | let instr = self.read_instruction(interconnect, pc); 77 | self.delay_slot_pc = None; 78 | 79 | self.execute_instruction(interconnect, instr); 80 | } else { 81 | let instr = self.read_instruction(interconnect, self.reg_pc); 82 | 83 | self.reg_pc += 4; 84 | self.execute_instruction(interconnect, instr); 85 | } 86 | } 87 | 88 | fn read_instruction(&self, interconnect: &mut Interconnect, addr: u64) -> Instruction { 89 | Instruction(self.read_word(interconnect, addr)) 90 | } 91 | 92 | fn execute_instruction(&mut self, interconnect: &mut Interconnect, instr: Instruction) { 93 | match instr.opcode() { 94 | Special => { 95 | match instr.special_op() { 96 | Sll => self.reg_instr(instr, |_, rt, sa| rt << sa), 97 | Srl => self.reg_instr(instr, |_, rt, sa| { 98 | let rt = rt as u32; 99 | (rt >> sa) as u64 100 | }), 101 | 102 | Sllv => 103 | self.reg_instr(instr, |rs, rt, _| { 104 | // TODO: Check to see if this is actually sa 105 | let shift = rs & 0b11111; 106 | rt << shift 107 | }), 108 | 109 | Srlv => 110 | self.reg_instr(instr, |rs, rt, _| { 111 | let rs = rs as u32; 112 | let rt = rt as u32; 113 | let shift = rs & 0b11111; 114 | (rt >> shift) as u64 115 | }), 116 | 117 | Jr => { 118 | let delay_slot_pc = self.reg_pc; 119 | 120 | // Update PC before executing delay slot instruction 121 | self.reg_pc = self.read_reg_gpr(instr.rs()); 122 | 123 | self.delay_slot_pc = Some(delay_slot_pc); 124 | } 125 | 126 | Multu => { 127 | let rs = self.read_reg_gpr(instr.rs()) as u32; 128 | let rt = self.read_reg_gpr(instr.rt()) as u32; 129 | 130 | let res = (rs as u64) * (rt as u64); 131 | 132 | // TODO: Undefined if last 2 instructions were 133 | // MFHI or MFLO 134 | self.reg_lo = (res as i32) as u64; 135 | self.reg_hi = ((res >> 32) as i32) as u64; 136 | } 137 | 138 | Mfhi => { 139 | let value = self.reg_hi; 140 | self.write_reg_gpr(instr.rd() as usize, value); 141 | } 142 | Mflo => { 143 | let value = self.reg_lo; 144 | self.write_reg_gpr(instr.rd() as usize, value); 145 | } 146 | 147 | Addu => self.reg_instr(instr, |rs, rt, _| rs.wrapping_add(rt)), 148 | Subu => self.reg_instr(instr, |rs, rt, _| rs.wrapping_sub(rt)), 149 | 150 | And => self.reg_instr(instr, |rs, rt, _| rs & rt), 151 | Or => self.reg_instr(instr, |rs, rt, _| rs | rt), 152 | Xor => self.reg_instr(instr, |rs, rt, _| rs ^ rt), 153 | 154 | Sltu => self.reg_instr(instr, |rs, rt, _| if rs < rt { 1 } else { 0 }), 155 | } 156 | } 157 | 158 | RegImm => { 159 | match instr.reg_imm_op() { 160 | Bgezal => { self.branch(instr, WriteLink::Yes, |rs, _| (rs as i64) >= 0); } 161 | } 162 | } 163 | 164 | Addi => 165 | self.imm_instr(instr, SignExtendResult::Yes, |rs, _, imm_sign_extended| { 166 | // TODO: Handle overflow exception 167 | rs + imm_sign_extended 168 | }), 169 | Addiu => self.imm_instr(instr, SignExtendResult::Yes, |rs, _, imm_sign_extended| rs.wrapping_add(imm_sign_extended)), 170 | 171 | Andi => self.imm_instr(instr, SignExtendResult::No, |rs, imm, _| rs & imm), 172 | Ori => self.imm_instr(instr, SignExtendResult::No, |rs, imm, _| rs | imm), 173 | 174 | Lui => self.imm_instr(instr, SignExtendResult::Yes, |_, imm, _| imm << 16), 175 | 176 | Mtc0 => { 177 | let data = self.read_reg_gpr(instr.rt()); 178 | self.cp0.write_reg(instr.rd(), data); 179 | } 180 | 181 | Beq => { self.branch(instr, WriteLink::No, |rs, rt| rs == rt); } 182 | Bne => { self.branch(instr, WriteLink::No, |rs, rt| rs != rt); } 183 | 184 | Beql => self.branch_likely(instr, |rs, rt| rs == rt), 185 | Bnel => self.branch_likely(instr, |rs, rt| rs != rt), 186 | 187 | Lw => { 188 | let base = instr.rs(); 189 | 190 | let sign_extended_offset = instr.offset_sign_extended(); 191 | let virt_addr = self.read_reg_gpr(base).wrapping_add(sign_extended_offset); 192 | let mem = (self.read_word(interconnect, virt_addr) as i32) as u64; 193 | self.write_reg_gpr(instr.rt(), mem); 194 | } 195 | 196 | Sw => { 197 | let base = instr.rs(); 198 | 199 | let sign_extended_offset = instr.offset_sign_extended(); 200 | let virt_addr = self.read_reg_gpr(base).wrapping_add(sign_extended_offset); 201 | let mem = self.read_reg_gpr(instr.rt()) as u32; 202 | self.write_word(interconnect, virt_addr, mem); 203 | } 204 | } 205 | } 206 | 207 | fn imm_instr(&mut self, instr: Instruction, sign_extend_result: SignExtendResult, f: F) 208 | where F: FnOnce(u64, u64, u64) -> u64 209 | { 210 | let rs = self.read_reg_gpr(instr.rs()); 211 | let imm = instr.imm() as u64; 212 | let imm_sign_extended = instr.imm_sign_extended(); 213 | let value = f(rs, imm, imm_sign_extended); 214 | let sign_extended_value = (value as i32) as u64; 215 | let value = match sign_extend_result { 216 | SignExtendResult::Yes => sign_extended_value, 217 | _ => value, 218 | }; 219 | self.write_reg_gpr(instr.rt(), value); 220 | } 221 | 222 | fn reg_instr(&mut self, instr: Instruction, f: F) 223 | where F: FnOnce(u64, u64, u32) -> u64 224 | { 225 | let rs = self.read_reg_gpr(instr.rs()); 226 | let rt = self.read_reg_gpr(instr.rt()); 227 | let sa = instr.sa(); 228 | let value = f(rs, rt, sa); 229 | let sign_extended_value = (value as i32) as u64; 230 | self.write_reg_gpr(instr.rd() as usize, sign_extended_value); 231 | } 232 | 233 | fn branch(&mut self, instr: Instruction, write_link: WriteLink, f: F) -> bool 234 | where F: FnOnce(u64, u64) -> bool 235 | { 236 | let rs = self.read_reg_gpr(instr.rs()); 237 | let rt = self.read_reg_gpr(instr.rt()); 238 | let is_taken = f(rs, rt); 239 | 240 | let delay_slot_pc = self.reg_pc; 241 | 242 | if let WriteLink::Yes = write_link { 243 | let link_address = delay_slot_pc + 4; 244 | self.write_reg_gpr(31, link_address); 245 | } 246 | 247 | if is_taken { 248 | let sign_extended_offset = instr.offset_sign_extended() << 2; 249 | // Update PC before executing delay slot instruction 250 | self.reg_pc = self.reg_pc.wrapping_add(sign_extended_offset); 251 | 252 | self.delay_slot_pc = Some(delay_slot_pc); 253 | } 254 | 255 | is_taken 256 | } 257 | 258 | fn branch_likely(&mut self, instr: Instruction, f: F) 259 | where F: FnOnce(u64, u64) -> bool 260 | { 261 | if !self.branch(instr, WriteLink::No, f) { 262 | // Skip over delay slot instruction when not branching 263 | self.reg_pc = self.reg_pc.wrapping_add(4); 264 | } 265 | } 266 | 267 | fn read_word(&self, interconnect: &mut Interconnect, virt_addr: u64) -> u32 { 268 | let phys_addr = self.virt_addr_to_phys_addr(virt_addr); 269 | interconnect.read_word(phys_addr as u32) 270 | } 271 | 272 | fn write_word(&mut self, interconnect: &mut Interconnect, virt_addr: u64, value: u32) { 273 | let phys_addr = self.virt_addr_to_phys_addr(virt_addr); 274 | interconnect.write_word(phys_addr as u32, value); 275 | } 276 | 277 | fn virt_addr_to_phys_addr(&self, virt_addr: u64) -> u64 { 278 | // See Table 5-3 in the VR4300 User's Manual 279 | let addr_bit_values = (virt_addr >> 29) & 0b111; 280 | 281 | if addr_bit_values == 0b101 { 282 | // kseg1 283 | virt_addr - 0xffff_ffff_a000_0000 284 | } else { 285 | // TODO 286 | panic!("Unrecognized virtual address: {:#x}", virt_addr); 287 | } 288 | } 289 | 290 | fn write_reg_gpr(&mut self, index: usize, value: u64) { 291 | if index != 0 { 292 | self.reg_gpr[index] = value; 293 | } 294 | } 295 | 296 | fn read_reg_gpr(&self, index: usize) -> u64 { 297 | match index { 298 | 0 => 0, 299 | _ => self.reg_gpr[index], 300 | } 301 | } 302 | } 303 | 304 | impl fmt::Debug for Cpu { 305 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 306 | const REGS_PER_LINE: usize = 2; 307 | const REG_NAMES: [&'static str; NUM_GPR] = [ 308 | "r0", "at", "v0", "v1", "a0", "a1", "a2", "a3", 309 | "t0", "t1", "t2", "t3", "t4", "t5", "t6", "t7", 310 | "s0", "s1", "s2", "s3", "s4", "s5", "s6", "s7", 311 | "t8", "t9", "k0", "k1", "gp", "sp", "s8", "ra", 312 | ]; 313 | 314 | try!(write!(f, "\nCPU General Purpose Registers:")); 315 | for reg_num in 0..NUM_GPR { 316 | if (reg_num % REGS_PER_LINE) == 0 { 317 | try!(writeln!(f, "")); 318 | } 319 | try!(write!(f, 320 | "{reg_name}/gpr{num:02}: {value:#018X} ", 321 | num = reg_num, 322 | reg_name = REG_NAMES[reg_num], 323 | value = self.reg_gpr[reg_num], 324 | )); 325 | } 326 | 327 | try!(write!(f, "\n\nCPU Floating Point Registers:")); 328 | for reg_num in 0..NUM_GPR { 329 | if (reg_num % REGS_PER_LINE) == 0 { 330 | try!(writeln!(f, "")); 331 | } 332 | try!(write!(f, 333 | "fpr{num:02}: {value:21} ", 334 | num = reg_num, 335 | value = self.reg_fpr[reg_num])); 336 | } 337 | 338 | try!(writeln!(f, "\n\nCPU Special Registers:")); 339 | try!(writeln!(f, 340 | "\ 341 | reg_pc: {:#018X}\n\ 342 | reg_hi: {:#018X}\n\ 343 | reg_lo: {:#018X}\n\ 344 | reg_llbit: {}\n\ 345 | reg_fcr0: {:#010X}\n\ 346 | reg_fcr31: {:#010X}\n\ 347 | ", 348 | self.reg_pc, 349 | self.reg_hi, 350 | self.reg_lo, 351 | self.reg_llbit, 352 | self.reg_fcr0, 353 | self.reg_fcr31 354 | )); 355 | 356 | writeln!(f, "{:#?}", self.cp0) 357 | } 358 | } 359 | -------------------------------------------------------------------------------- /src/n64/cpu/instruction.rs: -------------------------------------------------------------------------------- 1 | use std::fmt; 2 | 3 | use super::opcode::{Opcode, RegImmOpcode, SpecialOpcode}; 4 | 5 | use num::FromPrimitive; 6 | 7 | #[derive(Clone, Copy)] 8 | pub struct Instruction(pub u32); 9 | 10 | impl Instruction { 11 | #[inline(always)] 12 | pub fn opcode(&self) -> Opcode { 13 | let value = (self.0 >> 26) & 0b111111; 14 | Opcode::from_u32(value).unwrap_or_else(|| { 15 | panic!("Unrecognized instruction: {:#010x} (op: {:#08b})", self.0, value) 16 | }) 17 | } 18 | 19 | #[inline(always)] 20 | pub fn rs(&self) -> usize { 21 | ((self.0 >> 21) & 0b11111) as usize 22 | } 23 | 24 | #[inline(always)] 25 | pub fn rt(&self) -> usize { 26 | ((self.0 >> 16) & 0b11111) as usize 27 | } 28 | 29 | #[inline(always)] 30 | pub fn rd(&self) -> u32 { 31 | (self.0 >> 11) & 0b11111 32 | } 33 | 34 | #[inline(always)] 35 | pub fn sa(&self) -> u32 { 36 | (self.0 >> 6) & 0b11111 37 | } 38 | 39 | #[inline(always)] 40 | pub fn imm(&self) -> u32 { 41 | self.0 & 0xffff 42 | } 43 | 44 | #[inline(always)] 45 | pub fn imm_sign_extended(&self) -> u64 { 46 | (self.imm() as i16) as u64 47 | } 48 | 49 | #[inline(always)] 50 | pub fn offset(&self) -> u32 { 51 | self.imm() 52 | } 53 | 54 | #[inline(always)] 55 | pub fn offset_sign_extended(&self) -> u64 { 56 | (self.offset() as i16) as u64 57 | } 58 | 59 | #[inline(always)] 60 | pub fn special_op(&self) -> SpecialOpcode { 61 | let value = self.0 & 0b111111; 62 | SpecialOpcode::from_u32(value).unwrap_or_else(|| { 63 | panic!("Unrecognized special opcode: {:#010x} (op: {:#08b})", self.0, value) 64 | }) 65 | } 66 | 67 | #[inline(always)] 68 | pub fn reg_imm_op(&self) -> RegImmOpcode { 69 | let value = (self.0 >> 16) & 0b11111; 70 | RegImmOpcode::from_u32(value).unwrap_or_else(|| { 71 | panic!("Unrecognized reg imm opcode: {:#010x} (op: {:#08b})", self.0, value) 72 | }) 73 | } 74 | } 75 | 76 | impl fmt::Debug for Instruction { 77 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 78 | match self.opcode() { 79 | Opcode::Special => write!(f, "{:?}", self.special_op()), 80 | _ => write!(f, "{:?}", self.opcode()), 81 | } 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/n64/cpu/mod.rs: -------------------------------------------------------------------------------- 1 | mod cpu; 2 | mod cp0; 3 | pub mod opcode; 4 | mod instruction; 5 | 6 | pub use self::cpu::Cpu; 7 | pub use self::instruction::Instruction; 8 | -------------------------------------------------------------------------------- /src/n64/cpu/opcode.rs: -------------------------------------------------------------------------------- 1 | enum_from_primitive! { 2 | #[derive(Debug)] 3 | pub enum Opcode { 4 | Special = 0b000000, 5 | RegImm = 0b000001, 6 | 7 | Addi = 0b001000, 8 | Addiu = 0b001001, 9 | 10 | Andi = 0b001100, 11 | Ori = 0b001101, 12 | 13 | Lui = 0b001111, 14 | Mtc0 = 0b010000, 15 | 16 | Beq = 0b000100, 17 | Bne = 0b000101, 18 | 19 | Beql = 0b010100, 20 | Bnel = 0b010101, 21 | 22 | Lw = 0b100011, 23 | 24 | Sw = 0b101011, 25 | } 26 | } 27 | 28 | enum_from_primitive! { 29 | #[derive(Debug)] 30 | pub enum SpecialOpcode { 31 | Sll = 0b000000, 32 | 33 | Srl = 0b000010, 34 | 35 | Sllv = 0b000100, 36 | 37 | Srlv = 0b000110, 38 | 39 | Jr = 0b001000, 40 | 41 | Multu = 0b011001, 42 | 43 | Mfhi = 0b010000, 44 | Mflo = 0b010010, 45 | 46 | Addu = 0b100001, 47 | 48 | Subu = 0b100011, 49 | And = 0b100100, 50 | Or = 0b100101, 51 | Xor = 0b100110, 52 | 53 | Sltu = 0b101011, 54 | } 55 | } 56 | 57 | enum_from_primitive! { 58 | #[derive(Debug)] 59 | pub enum RegImmOpcode { 60 | Bgezal = 0b10001, 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/n64/interconnect.rs: -------------------------------------------------------------------------------- 1 | use byteorder::{BigEndian, ByteOrder}; 2 | 3 | use super::{AudioInterface, PeripheralInterface, Pif, Rdp, Rsp, SerialInterface, VideoInterface}; 4 | use super::mem_map::{self, Addr}; 5 | 6 | use std::fmt; 7 | 8 | const RDRAM_SIZE: usize = 4 * 1024 * 1024; 9 | 10 | pub struct Interconnect { 11 | pif: Pif, 12 | 13 | rsp: Rsp, 14 | rdp: Rdp, 15 | 16 | ai: AudioInterface, 17 | vi: VideoInterface, 18 | 19 | pi: PeripheralInterface, 20 | 21 | si: SerialInterface, 22 | 23 | cart_rom: Box<[u8]>, 24 | 25 | rdram: Box<[u16]>, 26 | } 27 | 28 | impl Interconnect { 29 | pub fn new(boot_rom: Box<[u8]>, cart_rom: Box<[u8]>) -> Interconnect { 30 | Interconnect { 31 | pif: Pif::new(boot_rom), 32 | 33 | rdp: Rdp, 34 | rsp: Rsp::new(), 35 | 36 | ai: AudioInterface::default(), 37 | vi: VideoInterface::default(), 38 | 39 | pi: PeripheralInterface::default(), 40 | 41 | si: SerialInterface::default(), 42 | 43 | cart_rom: cart_rom, 44 | 45 | rdram: vec![0; RDRAM_SIZE].into_boxed_slice(), 46 | } 47 | } 48 | 49 | pub fn pif(&self) -> &Pif { 50 | &self.pif 51 | } 52 | 53 | pub fn read_word(&self, addr: u32) -> u32 { 54 | match mem_map::map_addr(addr) { 55 | Addr::PifRom(offset) => self.pif.read_boot_rom(offset), 56 | Addr::PifRam(offset) => self.pif.read_ram(offset), 57 | 58 | Addr::CartDom1(offset) => BigEndian::read_u32(&self.cart_rom[offset as usize..]), 59 | 60 | Addr::SpDmem(offset) => self.rsp.read_dmem(offset), 61 | Addr::SpImem(offset) => self.rsp.read_imem(offset), 62 | 63 | Addr::SpStatusReg => self.rsp.read_status_reg(), 64 | Addr::SpDmaBusyReg => self.rsp.read_dma_busy_reg(), 65 | 66 | Addr::DpcStatusReg => self.rdp.read_status_reg(), 67 | 68 | Addr::AiDramAddrReg => self.ai.read_dram_addr_reg(), 69 | Addr::AiLenReg => self.ai.read_len_reg(), 70 | 71 | Addr::ViIntrReg => self.vi.read_intr_reg(), 72 | Addr::ViCurrentReg => self.vi.read_current_reg(), 73 | Addr::ViHStartReg => self.vi.read_h_start_reg(), 74 | 75 | Addr::PiStatusReg => self.pi.read_status_reg(), 76 | Addr::PiBsdDom1LatReg => self.pi.read_bsd_dom1_lat_reg(), 77 | Addr::PiBsdDom1PwdReg => self.pi.read_bsd_dom1_pwd_reg(), 78 | Addr::PiBsdDom1PgsReg => self.pi.read_bsd_dom1_pgs_reg(), 79 | Addr::PiBsdDom1RlsReg => self.pi.read_bsd_dom1_rls_reg(), 80 | 81 | Addr::SiStatusReg => self.si.read_status_reg(), 82 | } 83 | } 84 | 85 | pub fn write_word(&mut self, addr: u32, value: u32) { 86 | match mem_map::map_addr(addr) { 87 | Addr::PifRom(_) => panic!("Cannot write to PIF ROM"), 88 | Addr::PifRam(offset) => self.pif.write_ram(offset, value), 89 | 90 | Addr::CartDom1(offset) => panic!("Cannot write to cart ROM"), 91 | 92 | Addr::SpDmem(offset) => self.rsp.write_dmem(offset, value), 93 | Addr::SpImem(offset) => self.rsp.write_imem(offset, value), 94 | 95 | Addr::SpStatusReg => self.rsp.write_status_reg(value), 96 | Addr::SpDmaBusyReg => self.rsp.write_dma_busy_reg(value), 97 | 98 | Addr::DpcStatusReg => self.rdp.write_status_reg(value), 99 | 100 | Addr::AiDramAddrReg => self.ai.write_dram_addr_reg(value), 101 | Addr::AiLenReg => self.ai.write_len_reg(value), 102 | 103 | Addr::ViIntrReg => self.vi.write_intr_reg(value), 104 | Addr::ViCurrentReg => self.vi.write_current_reg(value), 105 | Addr::ViHStartReg => self.vi.write_h_start_reg(value), 106 | 107 | Addr::PiStatusReg => self.pi.write_status_reg(value), 108 | Addr::PiBsdDom1LatReg => self.pi.write_bsd_dom1_lat_reg(value), 109 | Addr::PiBsdDom1PwdReg => self.pi.write_bsd_dom1_pwd_reg(value), 110 | Addr::PiBsdDom1PgsReg => self.pi.write_bsd_dom1_pgs_reg(value), 111 | Addr::PiBsdDom1RlsReg => self.pi.write_bsd_dom1_rls_reg(value), 112 | 113 | Addr::SiStatusReg => self.si.write_status_reg(value), 114 | } 115 | } 116 | } 117 | 118 | impl fmt::Debug for Interconnect { 119 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 120 | write!(f, "TODO: Impl Debug for Interconnect") 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /src/n64/mem_map.rs: -------------------------------------------------------------------------------- 1 | const PIF_ROM_START: u32 = 0x1fc0_0000; 2 | const PIF_ROM_LENGTH: u32 = 0x0000_07c0; 3 | const PIF_ROM_END: u32 = PIF_ROM_START + PIF_ROM_LENGTH - 1; 4 | 5 | const PIF_RAM_START: u32 = 0x1fc0_07c0; 6 | pub const PIF_RAM_LENGTH: u32 = 0x0000_0040; 7 | const PIF_RAM_END: u32 = PIF_RAM_START + PIF_RAM_LENGTH - 1; 8 | 9 | const CART_DOM1_ADDR2_START: u32 = 0x1000_0000; 10 | const CART_DOM1_ADDR2_LENGTH: u32 = 0x0fc0_0000; 11 | const CART_DOM1_ADDR2_END: u32 = CART_DOM1_ADDR2_START + CART_DOM1_ADDR2_LENGTH - 1; 12 | 13 | const SP_DMEM_START: u32 = 0x0400_0000; 14 | pub const SP_DMEM_LENGTH: u32 = 0x0000_1000; 15 | const SP_DMEM_END: u32 = SP_DMEM_START + SP_DMEM_LENGTH - 1; 16 | 17 | const SP_IMEM_START: u32 = 0x0400_1000; 18 | pub const SP_IMEM_LENGTH: u32 = 0x0000_1000; 19 | const SP_IMEM_END: u32 = SP_IMEM_START + SP_IMEM_LENGTH - 1; 20 | 21 | const SP_BASE_REG: u32 = 0x0404_0000; 22 | const SP_STATUS_REG: u32 = 0x0404_0010; 23 | const SP_DMA_BUSY_REG: u32 = 0x0404_0018; 24 | 25 | const DPC_BASE_REG: u32 = 0x0410_0000; 26 | const DPC_STATUS_REG: u32 = 0x0410_000c; 27 | 28 | const AI_BASE_REG: u32 = 0x0450_0000; 29 | const AI_DRAM_ADDR_REG: u32 = 0x0450_0000; 30 | const AI_LEN_REG: u32 = 0x0450_0004; 31 | 32 | const VI_BASE_REG: u32 = 0x0440_0000; 33 | const VI_INTR_REG: u32 = 0x0440_000c; 34 | const VI_CURRENT_REG: u32 = 0x0440_0010; 35 | const VI_H_START_REG: u32 = 0x0440_0024; 36 | 37 | const PI_BASE_REG: u32 = 0x0460_0000; 38 | const PI_STATUS_REG: u32 = 0x0460_0010; 39 | const PI_BSD_DOM1_LAT_REG: u32 = 0x0460_0014; 40 | const PI_BSD_DOM1_PWD_REG: u32 = 0x0460_0018; 41 | const PI_BSD_DOM1_PGS_REG: u32 = 0x0460_001c; 42 | const PI_BSD_DOM1_RLS_REG: u32 = 0x0460_0020; 43 | 44 | const SI_BASE_REG: u32 = 0x0480_0000; 45 | const SI_STATUS_REG: u32 = 0x0480_0018; 46 | 47 | #[derive(Debug)] 48 | pub enum Addr { 49 | PifRom(u32), 50 | PifRam(u32), 51 | 52 | CartDom1(u32), 53 | 54 | SpDmem(u32), 55 | SpImem(u32), 56 | 57 | SpStatusReg, 58 | SpDmaBusyReg, 59 | 60 | DpcStatusReg, 61 | 62 | AiDramAddrReg, 63 | AiLenReg, 64 | 65 | ViIntrReg, 66 | ViCurrentReg, 67 | ViHStartReg, 68 | 69 | PiStatusReg, 70 | PiBsdDom1LatReg, 71 | PiBsdDom1PwdReg, 72 | PiBsdDom1PgsReg, 73 | PiBsdDom1RlsReg, 74 | 75 | SiStatusReg, 76 | } 77 | 78 | pub fn map_addr(addr: u32) -> Addr { 79 | match addr { 80 | PIF_ROM_START ... PIF_ROM_END => 81 | Addr::PifRom(addr - PIF_ROM_START), 82 | PIF_RAM_START ... PIF_RAM_END => 83 | Addr::PifRam(addr - PIF_RAM_START), 84 | 85 | CART_DOM1_ADDR2_START ... CART_DOM1_ADDR2_END => 86 | Addr::CartDom1(addr - CART_DOM1_ADDR2_START), 87 | 88 | SP_DMEM_START ... SP_DMEM_END => 89 | Addr::SpDmem(addr - SP_DMEM_START), 90 | 91 | SP_IMEM_START ... SP_IMEM_END => 92 | Addr::SpImem(addr - SP_IMEM_START), 93 | 94 | SP_STATUS_REG => Addr::SpStatusReg, 95 | SP_DMA_BUSY_REG => Addr::SpDmaBusyReg, 96 | 97 | DPC_STATUS_REG => Addr::DpcStatusReg, 98 | 99 | AI_DRAM_ADDR_REG => Addr::AiDramAddrReg, 100 | AI_LEN_REG => Addr::AiLenReg, 101 | 102 | VI_INTR_REG => Addr::ViIntrReg, 103 | VI_CURRENT_REG => Addr::ViCurrentReg, 104 | VI_H_START_REG => Addr::ViHStartReg, 105 | 106 | PI_STATUS_REG => Addr::PiStatusReg, 107 | PI_BSD_DOM1_LAT_REG => Addr::PiBsdDom1LatReg, 108 | PI_BSD_DOM1_PWD_REG => Addr::PiBsdDom1PwdReg, 109 | PI_BSD_DOM1_PGS_REG => Addr::PiBsdDom1PgsReg, 110 | PI_BSD_DOM1_RLS_REG => Addr::PiBsdDom1RlsReg, 111 | 112 | SI_STATUS_REG => Addr::SiStatusReg, 113 | 114 | _ => panic!("Unrecognized physical address: {:#x}", addr), 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /src/n64/mod.rs: -------------------------------------------------------------------------------- 1 | mod audio_interface; 2 | pub mod cpu; 3 | mod interconnect; 4 | pub mod mem_map; 5 | mod n64; 6 | mod peripheral_interface; 7 | mod pif; 8 | mod rdp; 9 | mod rsp; 10 | mod serial_interface; 11 | mod video_interface; 12 | 13 | pub use self::audio_interface::AudioInterface; 14 | pub use self::cpu::Cpu; 15 | pub use self::interconnect::Interconnect; 16 | pub use self::n64::N64; 17 | pub use self::peripheral_interface::PeripheralInterface; 18 | pub use self::pif::Pif; 19 | pub use self::rdp::Rdp; 20 | pub use self::rsp::Rsp; 21 | pub use self::serial_interface::SerialInterface; 22 | pub use self::video_interface::VideoInterface; 23 | -------------------------------------------------------------------------------- /src/n64/n64.rs: -------------------------------------------------------------------------------- 1 | use super::{Cpu, Interconnect}; 2 | 3 | #[derive(Debug)] 4 | pub struct N64 { 5 | cpu: Cpu, 6 | interconnect: Interconnect, 7 | } 8 | 9 | impl N64 { 10 | pub fn new(boot_rom: Box<[u8]>, cart_rom: Box<[u8]>) -> N64 { 11 | N64 { 12 | cpu: Cpu::new(), 13 | interconnect: Interconnect::new(boot_rom, cart_rom), 14 | } 15 | } 16 | 17 | pub fn cpu(&self) -> &Cpu { 18 | &self.cpu 19 | } 20 | 21 | pub fn interconnect(&self) -> &Interconnect { 22 | &self.interconnect 23 | } 24 | 25 | pub fn step(&mut self) { 26 | self.cpu.step(&mut self.interconnect); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/n64/peripheral_interface.rs: -------------------------------------------------------------------------------- 1 | #[derive(Default)] 2 | pub struct PeripheralInterface; 3 | 4 | impl PeripheralInterface { 5 | pub fn read_status_reg(&self) -> u32 { 6 | // TODO: Proper impl 7 | 0 8 | } 9 | 10 | pub fn write_status_reg(&mut self, value: u32) { 11 | if (value & (1 << 0)) != 0 { 12 | println!("WARNING: PI reset controller bit written but not yet implemented"); 13 | } 14 | 15 | if (value & (1 << 1)) != 0 { 16 | // TODO: Affect MI_INTR_REG 17 | println!("WARNING: PI clear intr bit written but not yet implemented"); 18 | } 19 | } 20 | 21 | pub fn read_bsd_dom1_lat_reg(&self) -> u32 { 22 | // TODO: Proper impl (probably not necessary) 23 | 0 24 | } 25 | 26 | pub fn write_bsd_dom1_lat_reg(&mut self, value: u32) { 27 | // TODO: Proper impl (probably not necessary) 28 | println!("PI_BSD_DOM1_LAT_REG written: {:#x}", value); 29 | } 30 | 31 | pub fn read_bsd_dom1_pwd_reg(&self) -> u32 { 32 | // TODO: Proper impl (probably not necessary) 33 | 0 34 | } 35 | 36 | pub fn write_bsd_dom1_pwd_reg(&mut self, value: u32) { 37 | // TODO: Proper impl (probably not necessary) 38 | println!("PI_BSD_DOM1_PWD_REG written: {:#x}", value); 39 | } 40 | 41 | pub fn read_bsd_dom1_pgs_reg(&self) -> u32 { 42 | // TODO: Proper impl (probably not necessary) 43 | 0 44 | } 45 | 46 | pub fn write_bsd_dom1_pgs_reg(&mut self, value: u32) { 47 | // TODO: Proper impl (probably not necessary) 48 | println!("PI_BSD_DOM1_PGS_REG written: {:#x}", value); 49 | } 50 | 51 | pub fn read_bsd_dom1_rls_reg(&self) -> u32 { 52 | // TODO: Proper impl (probably not necessary) 53 | 0 54 | } 55 | 56 | pub fn write_bsd_dom1_rls_reg(&mut self, value: u32) { 57 | // TODO: Proper impl (probably not necessary) 58 | println!("PI_BSD_DOM1_RLS_REG written: {:#x}", value); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/n64/pif.rs: -------------------------------------------------------------------------------- 1 | use byteorder::{BigEndian, ByteOrder}; 2 | 3 | use super::mem_map::PIF_RAM_LENGTH; 4 | 5 | pub struct Pif { 6 | boot_rom: Box<[u8]>, 7 | ram: Box<[u8]>, 8 | } 9 | 10 | impl Pif { 11 | pub fn new(boot_rom: Box<[u8]>) -> Pif { 12 | Pif { 13 | boot_rom: boot_rom, 14 | 15 | ram: vec![0; PIF_RAM_LENGTH as usize].into_boxed_slice(), 16 | } 17 | } 18 | 19 | pub fn read_boot_rom(&self, addr: u32) -> u32 { 20 | BigEndian::read_u32(&self.boot_rom[addr as usize..]) 21 | } 22 | 23 | pub fn read_ram(&self, addr: u32) -> u32 { 24 | BigEndian::read_u32(&self.ram[addr as usize..]) 25 | } 26 | 27 | pub fn write_ram(&mut self, addr: u32, value: u32) { 28 | BigEndian::write_u32(&mut self.ram[addr as usize..], value); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/n64/rdp.rs: -------------------------------------------------------------------------------- 1 | pub struct Rdp; 2 | 3 | impl Rdp { 4 | pub fn read_status_reg(&self) -> u32 { 5 | // TODO: Proper impl 6 | 0 7 | } 8 | 9 | pub fn write_status_reg(&mut self, value: u32) { 10 | // TODO 11 | panic!("Write to rdp status reg: {:#?}", value); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/n64/rsp.rs: -------------------------------------------------------------------------------- 1 | use byteorder::{BigEndian, ByteOrder}; 2 | 3 | use super::mem_map::{SP_DMEM_LENGTH, SP_IMEM_LENGTH}; 4 | 5 | pub struct Rsp { 6 | dmem: Box<[u8]>, 7 | imem: Box<[u8]>, 8 | 9 | halt: bool, 10 | broke: bool, 11 | interrupt_enable: bool, 12 | } 13 | 14 | impl Rsp { 15 | pub fn new() -> Rsp { 16 | // TODO: Check for correct init hw state 17 | Rsp { 18 | dmem: vec![0; SP_DMEM_LENGTH as usize].into_boxed_slice(), 19 | imem: vec![0; SP_IMEM_LENGTH as usize].into_boxed_slice(), 20 | 21 | halt: true, 22 | broke: false, 23 | interrupt_enable: false, 24 | } 25 | } 26 | 27 | pub fn read_dmem(&self, offset: u32) -> u32 { 28 | BigEndian::read_u32(&self.dmem[offset as usize..]) 29 | } 30 | 31 | pub fn write_dmem(&mut self, offset: u32, value: u32) { 32 | BigEndian::write_u32(&mut self.dmem[offset as usize..], value); 33 | } 34 | 35 | pub fn read_imem(&self, offset: u32) -> u32 { 36 | BigEndian::read_u32(&self.imem[offset as usize..]) 37 | } 38 | 39 | pub fn write_imem(&mut self, offset: u32, value: u32) { 40 | BigEndian::write_u32(&mut self.imem[offset as usize..], value); 41 | } 42 | 43 | // TODO: Read general regs 44 | pub fn read_status_reg(&self) -> u32 { 45 | (if self.halt { 1 } else { 0 } << 0) | 46 | (if self.interrupt_enable { 1 } else { 0 } << 1) 47 | } 48 | 49 | pub fn write_status_reg(&mut self, value: u32) { 50 | // TODO: What happens if both a set and clear bit are set? 51 | if (value & (1 << 0)) != 0 { 52 | self.halt = false; 53 | } 54 | if (value & (1 << 1)) != 0 { 55 | self.halt = true; 56 | } 57 | 58 | if (value & (1 << 2)) != 0 { 59 | self.broke = false; 60 | } 61 | if (value & (1 << 3)) != 0 { 62 | self.interrupt_enable = false; 63 | } 64 | 65 | // TODO: Remaining bits 66 | if (value & 0xfffffff0) != 0 { 67 | panic!("Write to unsupported rsp status bits: {:#?}", value); 68 | } 69 | } 70 | 71 | pub fn read_dma_busy_reg(&self) -> u32 { 72 | // TODO: Proper impl 73 | 0 74 | } 75 | 76 | pub fn write_dma_busy_reg(&self, value: u32) { 77 | panic!("Attempted write to SP_DMA_BUSY: {:#?}", value); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/n64/serial_interface.rs: -------------------------------------------------------------------------------- 1 | #[derive(Default)] 2 | pub struct SerialInterface; 3 | 4 | impl SerialInterface { 5 | pub fn read_status_reg(&self) -> u32 { 6 | // TODO: Proper impl 7 | 0 8 | } 9 | 10 | pub fn write_status_reg(&mut self, value: u32) { 11 | panic!("Writes to SI status reg not yet implemented"); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/n64/video_interface.rs: -------------------------------------------------------------------------------- 1 | #[derive(Default)] 2 | pub struct VideoInterface { 3 | interrupt_half_line: u32, 4 | 5 | active_video_start: u32, 6 | active_video_end: u32, 7 | } 8 | 9 | impl VideoInterface { 10 | pub fn read_intr_reg(&self) -> u32 { 11 | self.interrupt_half_line 12 | } 13 | 14 | pub fn write_intr_reg(&mut self, value: u32) { 15 | self.interrupt_half_line = value & 0x0000_03ff; 16 | } 17 | 18 | pub fn read_current_reg(&self) -> u32 { 19 | // TODO: Proper impl 20 | 0 21 | } 22 | 23 | pub fn write_current_reg(&mut self, value: u32) { 24 | // TODO: Clear interrupt 25 | } 26 | 27 | pub fn read_h_start_reg(&self) -> u32 { 28 | (self.active_video_start << 16) | self.active_video_end 29 | } 30 | 31 | pub fn write_h_start_reg(&mut self, value: u32) { 32 | self.active_video_start = (value >> 16) & 0x0000_03ff; 33 | self.active_video_end = value & 0x0000_03ff; 34 | } 35 | } 36 | --------------------------------------------------------------------------------