├── .github └── workflows │ └── ci.yaml ├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md ├── debugger ├── Cargo.toml ├── app.rs ├── app.ui ├── background.rs ├── gobject │ ├── breakpoint_object.rs │ ├── callstack_object.rs │ ├── disassembled_instruction.rs │ └── mod.rs ├── main.rs └── style.css └── src ├── ai.rs ├── cpu.rs ├── cpu ├── disassembler.rs ├── float.rs ├── instruction.rs ├── mmu.rs ├── ops.rs ├── ops │ ├── branch.rs │ ├── condition.rs │ ├── float.rs │ ├── integer.rs │ ├── load_store.rs │ └── system.rs ├── optable.rs ├── spr.rs └── util.rs ├── di.rs ├── disc.rs ├── dol.rs ├── dsp.rs ├── dsp └── cpu.rs ├── exi.rs ├── gp_fifo.rs ├── lib.rs ├── main.rs ├── mem.rs ├── memory_interface.rs ├── pe.rs ├── pi.rs ├── si.rs ├── timers.rs ├── utils.rs ├── vi.rs ├── video.rs └── video ├── bp.rs ├── cp.rs └── xf.rs /.github/workflows/ci.yaml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | # This workflow run tests and build for each push 4 | 5 | on: 6 | workflow_dispatch: 7 | push: 8 | branches: 9 | - master 10 | 11 | jobs: 12 | 13 | build: 14 | runs-on: ubuntu-22.04 15 | steps: 16 | - uses: actions/checkout@v2 17 | 18 | - name: Update local toolchain 19 | run: | 20 | rustup update 21 | rustup component add clippy 22 | rustup install stable 23 | 24 | - name: Toolchain info 25 | run: | 26 | cargo --version --verbose 27 | rustc --version 28 | cargo clippy --version 29 | 30 | - name: Install missing dependencies 31 | run: | 32 | sudo apt-get update -y 33 | sudo apt-get install -y libx11-dev xserver-xorg-dev xorg-dev libpango1.0 libgraphene-1.0-dev libgtk-4-bin libgtk-4-common libgtk-4-dev 34 | 35 | - name: Lint 36 | run: | 37 | cargo fmt -- --check 38 | cargo clippy -- -D warnings 39 | cargo fmt -p debugger -- --check 40 | cargo clippy -p debugger -- -D warnings 41 | 42 | - name: Test 43 | run: | 44 | cargo check 45 | cargo test --all 46 | 47 | - name: Build 48 | run: | 49 | cargo build --release 50 | 51 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | *.bin 3 | *.dol 4 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rustcube" 3 | version = "0.1.0" 4 | authors = ["Michael Sierks "] 5 | license = "MIT/Apache-2.0" 6 | edition = "2021" 7 | 8 | [workspace] 9 | members = ["debugger"] 10 | 11 | [[bin]] 12 | name = "rustcube" 13 | doc = false 14 | 15 | [dependencies] 16 | bitfield = "0.14" 17 | byteorder = "1.4" 18 | getopts = "0.2" 19 | env_logger = "0.8.2" 20 | minifb = "0.27" 21 | log = { version = "0.4", features= ["std"] } 22 | 23 | [profile.dev] 24 | opt-level = 3 25 | overflow-checks = true 26 | -------------------------------------------------------------------------------- /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 Michael Sierks 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 Michael Sierks 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 | # rustcube 2 | 3 | A Gamecube emulator in the Rust programming language. Work is in progress to boot the Gamecube BIOS. 4 | 5 | ## Build and Run 6 | 7 | Rustcube is built with [Cargo, the Rust package manager](https://www.rust-lang.org/). 8 | 9 | Currently, Rustcube takes a single argument to run. This can be the Gamecube bios(IPL.bin), The one I've been testing with has a SHA-1 of `015808f637a984acde6a06efa7546e278293c6ee`. You could also run DOL, ISO and GCM files. 10 | 11 | You can build and run the emulator with: 12 | 13 | ``` 14 | cargo run -- 15 | ``` 16 | 17 | Enable debug logging 18 | 19 | ``` 20 | RUST_LOG=debug cargo run -- 21 | ``` 22 | 23 | ## Debugging 24 | 25 | A basic debugger has been created with gtk-rs. Though it is very much a work in progress, which means it's missing many features and may not function correctly. 26 | 27 | Run the debugger with following: 28 | ``` 29 | cargo run -p debugger -- 30 | ``` 31 | 32 | ## License 33 | 34 | Licensed under either of 35 | 36 | * Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) 37 | * MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) 38 | 39 | at your option. 40 | 41 | ## Contribution 42 | 43 | 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. 44 | -------------------------------------------------------------------------------- /debugger/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "debugger" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | async-channel = "1.7" 8 | once_cell = "1.0" 9 | rustcube = { path = "../" } 10 | env_logger = "0.8.2" 11 | getopts = "0.2" 12 | log = { version = "0.4", features = ["std"] } 13 | 14 | [[bin]] 15 | name = "debugger" 16 | path = "main.rs" 17 | 18 | [dependencies.gtk] 19 | package = "gtk4" 20 | version = "*" 21 | features = ["v4_2"] 22 | -------------------------------------------------------------------------------- /debugger/background.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | Breakpoint, BreakpointAccessType, BreakpointType, Breakpoints, Callstack, Disassembly, Event, 3 | Memory, Registers, 4 | }; 5 | 6 | use async_channel::{Receiver, Sender}; 7 | use rustcube::cpu::disassembler::Disassembler; 8 | use rustcube::{cpu::spr::*, Event as ctxEvent}; 9 | 10 | pub enum BgEvent { 11 | Continue, 12 | Step, 13 | Stop, 14 | Breakpoint(BreakpointType, u32, u32, bool, bool), 15 | BreakpointRemove(BreakpointType, u32), 16 | BreakpointClear, 17 | BreakpointToggle(u32), 18 | MemoryDump(u32), 19 | } 20 | 21 | pub async fn run(mut ctx: rustcube::Context, tx: Sender, rx: Receiver) { 22 | let disassembler = Disassembler::default(); 23 | 24 | let mut window_start = 0; 25 | let mut memory_dump_address = 0x8000_3000; //0x8000_0000; 26 | let regs = Box::new(Registers::new(&ctx)); 27 | let disassembly = Box::new(Disassembly::new(&mut ctx, &disassembler, &mut window_start)); 28 | let callstack = Box::new(Callstack::new(&mut ctx)); 29 | let memory = Box::new(Memory::new(&mut ctx, memory_dump_address)); 30 | let breakpoints = Box::new(Breakpoints::new(&ctx)); 31 | let _ = tx.send(Event::Registers(regs)).await; 32 | let _ = tx.send(Event::Disassembly(disassembly)).await; 33 | let _ = tx.send(Event::Callstack(callstack)).await; 34 | let _ = tx.send(Event::Memory(memory)).await; 35 | let _ = tx.send(Event::Breakpoints(breakpoints)).await; 36 | 37 | while let Ok(event) = rx.recv().await { 38 | match event { 39 | BgEvent::Breakpoint(bp_type, start_addr, end_addr, break_on_write, break_on_read) => { 40 | match bp_type { 41 | BreakpointType::Break => { 42 | ctx.add_breakpoint(start_addr); 43 | } 44 | BreakpointType::Watch => { 45 | ctx.add_watchpoint(start_addr, end_addr, break_on_write, break_on_read); 46 | } 47 | } 48 | let breakpoints = Box::new(Breakpoints::new(&ctx)); 49 | let disassembly = 50 | Box::new(Disassembly::new(&mut ctx, &disassembler, &mut window_start)); 51 | let _ = tx.send(Event::Breakpoints(breakpoints)).await; 52 | let _ = tx.send(Event::Disassembly(disassembly)).await; 53 | } 54 | BgEvent::BreakpointRemove(bp_type, num) => { 55 | match bp_type { 56 | BreakpointType::Break => { 57 | ctx.remove_breakpoint(num as usize); 58 | } 59 | BreakpointType::Watch => { 60 | ctx.remove_watchpoint(num as usize); 61 | } 62 | } 63 | let breakpoints = Box::new(Breakpoints::new(&ctx)); 64 | let _ = tx.send(Event::Breakpoints(breakpoints)).await; 65 | let disassembly = 66 | Box::new(Disassembly::new(&mut ctx, &disassembler, &mut window_start)); 67 | let _ = tx.send(Event::Disassembly(disassembly)).await; 68 | } 69 | BgEvent::BreakpointClear => { 70 | ctx.breakpoints_clear(); 71 | ctx.watchpoints_clear(); 72 | let _ = tx 73 | .send(Event::Breakpoints(Box::new(Breakpoints::new(&ctx)))) 74 | .await; 75 | let disassembly = 76 | Box::new(Disassembly::new(&mut ctx, &disassembler, &mut window_start)); 77 | let _ = tx.send(Event::Disassembly(disassembly)).await; 78 | } 79 | BgEvent::BreakpointToggle(addr) => { 80 | if ctx.breakpoints().contains(&addr) { 81 | if let Some(pos) = ctx.breakpoints().iter().position(|x| *x == addr) { 82 | ctx.remove_breakpoint(pos); 83 | } 84 | } else { 85 | ctx.add_breakpoint(addr); 86 | } 87 | let breakpoints = Box::new(Breakpoints::new(&ctx)); 88 | let _ = tx.send(Event::Breakpoints(breakpoints)).await; 89 | } 90 | BgEvent::Continue => { 91 | while rx.is_empty() { 92 | if let Some(event) = ctx.step() { 93 | match event { 94 | ctxEvent::Break => { 95 | info!("Breakpoint {:#x}", ctx.cpu().pc()); 96 | break; 97 | } 98 | ctxEvent::WatchRead(addr) => { 99 | info!("Watchpoint Read {:#x}", addr); 100 | break; 101 | } 102 | ctxEvent::WatchWrite(addr) => { 103 | info!("Watchpoint Write {:#x}", addr); 104 | break; 105 | } 106 | ctxEvent::Halted => break, 107 | } 108 | } 109 | } 110 | 111 | let regs = Box::new(Registers::new(&ctx)); 112 | let disassembly = 113 | Box::new(Disassembly::new(&mut ctx, &disassembler, &mut window_start)); 114 | let callstack = Box::new(Callstack::new(&mut ctx)); 115 | let memory = Box::new(Memory::new(&mut ctx, memory_dump_address)); 116 | let _ = tx.send(Event::Paused).await; 117 | let _ = tx.send(Event::Registers(regs)).await; 118 | let _ = tx.send(Event::Disassembly(disassembly)).await; 119 | let _ = tx.send(Event::Callstack(callstack)).await; 120 | let _ = tx.send(Event::Memory(memory)).await; 121 | } 122 | BgEvent::Step => { 123 | if let Some(event) = ctx.step() { 124 | match event { 125 | ctxEvent::Break => info!("Breakpoint {:#x}", ctx.cpu().pc()), 126 | ctxEvent::WatchRead(addr) => info!("Watchpoint Read {:#x}", addr), 127 | ctxEvent::WatchWrite(addr) => info!("Watchpoint Write {:#x}", addr), 128 | ctxEvent::Halted => (), 129 | } 130 | } 131 | 132 | let regs = Box::new(Registers::new(&ctx)); 133 | let disassembly = 134 | Box::new(Disassembly::new(&mut ctx, &disassembler, &mut window_start)); 135 | let callstack = Box::new(Callstack::new(&mut ctx)); 136 | let memory = Box::new(Memory::new(&mut ctx, memory_dump_address)); 137 | let _ = tx.send(Event::Registers(regs)).await; 138 | let _ = tx.send(Event::Disassembly(disassembly)).await; 139 | let _ = tx.send(Event::Callstack(callstack)).await; 140 | let _ = tx.send(Event::Memory(memory)).await; 141 | } 142 | BgEvent::Stop => {} 143 | BgEvent::MemoryDump(addr) => { 144 | memory_dump_address = addr; 145 | let memory = Box::new(Memory::new(&mut ctx, memory_dump_address)); 146 | let _ = tx.send(Event::Memory(memory)).await; 147 | } 148 | } 149 | } 150 | } 151 | 152 | impl Breakpoints { 153 | pub fn new(ctx: &rustcube::Context) -> Self { 154 | let mut breakpoints = Vec::new(); 155 | 156 | for (num, bp) in ctx.breakpoints().iter().enumerate() { 157 | breakpoints.push(Breakpoint { 158 | type_: BreakpointType::Break, 159 | num: num as u32, 160 | start_address: *bp, 161 | end_address: 0, 162 | access_type: BreakpointAccessType::Read, 163 | }); 164 | } 165 | 166 | for (num, wp) in ctx.watchpoints().iter().enumerate() { 167 | let access_type = match (wp.break_on_read, wp.break_on_write) { 168 | (false, false) => unreachable!(), 169 | (false, true) => BreakpointAccessType::Write, 170 | (true, false) => BreakpointAccessType::Read, 171 | (true, true) => BreakpointAccessType::ReadWrite, 172 | }; 173 | 174 | breakpoints.push(Breakpoint { 175 | type_: BreakpointType::Watch, 176 | num: num as u32, 177 | start_address: wp.start_addr, 178 | end_address: wp.end_addr, 179 | access_type, 180 | }); 181 | } 182 | 183 | Breakpoints { breakpoints } 184 | } 185 | } 186 | 187 | impl Callstack { 188 | pub fn new(ctx: &mut rustcube::Context) -> Self { 189 | // attempt to traverse callstack 190 | let lr = ctx.cpu().lr(); 191 | let mut addresses = Vec::new(); 192 | 193 | if lr != 0 { 194 | addresses.push(lr); 195 | 196 | let mut sp = ctx.cpu().gpr()[1]; 197 | let mut count = 0; 198 | 199 | while sp != 0 && count < 80 { 200 | let address = ctx.read_u32(sp.wrapping_add(4)); 201 | 202 | if address != 0 { 203 | addresses.push(address); 204 | } 205 | 206 | sp = ctx.read_u32(sp); 207 | 208 | // TODO last frame ??? 209 | if sp == 0xFFFF_FFFF { 210 | break; 211 | } 212 | 213 | count += 1; 214 | } 215 | } 216 | 217 | Callstack { addresses } 218 | } 219 | } 220 | 221 | impl Disassembly { 222 | pub fn new( 223 | ctx: &mut rustcube::Context, 224 | disassem: &Disassembler, 225 | window_start: &mut u32, 226 | ) -> Self { 227 | let mut instructions = Vec::new(); 228 | let mut pc = ctx.cpu().pc().wrapping_sub(100); 229 | 230 | let window_size = 200; // keep around 200 till scroll bug resolved: https://gitlab.gnome.org/GNOME/gtk/-/issues/2971 231 | 232 | if pc < *window_start || pc > *window_start + window_size { 233 | *window_start = pc; 234 | } 235 | 236 | pc = *window_start; 237 | 238 | for _ in 0..window_size { 239 | let addr = ctx.cpu().translate_instr_address(pc); 240 | let code = ctx.read_instruction(addr); 241 | let disinstr = disassem.decode(pc, code, true); 242 | 243 | instructions.push(disinstr); 244 | 245 | pc = pc.wrapping_add(4); 246 | } 247 | 248 | Disassembly { 249 | pc: ctx.cpu().pc(), 250 | instructions, 251 | breakpoints: ctx.breakpoints().clone(), 252 | } 253 | } 254 | } 255 | 256 | impl Memory { 257 | pub fn new(ctx: &mut rustcube::Context, address: u32) -> Self { 258 | let mut data = Vec::new(); 259 | let start = address & 0xFFFF_FFF0; 260 | let end = start + 0x200; 261 | 262 | for ea in (start..end).step_by(16) { 263 | let val1 = ctx.debug_read_u32(ea); 264 | let val2 = ctx.debug_read_u32(ea + 4); 265 | let val3 = ctx.debug_read_u32(ea + 8); 266 | let val4 = ctx.debug_read_u32(ea + 12); 267 | data.push(( 268 | ea, 269 | [ 270 | (val1 >> 24) as u8, 271 | (val1 >> 16) as u8, 272 | (val1 >> 8) as u8, 273 | val1 as u8, 274 | (val2 >> 24) as u8, 275 | (val2 >> 16) as u8, 276 | (val2 >> 8) as u8, 277 | val2 as u8, 278 | (val3 >> 24) as u8, 279 | (val3 >> 16) as u8, 280 | (val3 >> 8) as u8, 281 | val3 as u8, 282 | (val4 >> 24) as u8, 283 | (val4 >> 16) as u8, 284 | (val4 >> 8) as u8, 285 | val4 as u8, 286 | ], 287 | )); 288 | } 289 | Memory { data } 290 | } 291 | } 292 | 293 | impl Registers { 294 | pub fn new(ctx: &rustcube::Context) -> Self { 295 | let spr = *ctx.cpu().spr(); 296 | 297 | let spr_32 = [ 298 | ("XER", spr[SPR_XER]), 299 | ("LR", spr[SPR_LR]), 300 | ("CTR", spr[SPR_CTR]), 301 | ("DSISR", spr[SPR_DSISR]), 302 | ("DAR", spr[SPR_DAR]), 303 | ("DEC", spr[SPR_DEC]), 304 | ("SDR1", spr[SPR_SDR1]), 305 | ("SRR0", spr[SPR_SRR0]), 306 | ("SRR1", spr[SPR_SRR1]), 307 | ("SPRG0", spr[SPR_SPRG0]), 308 | ("SPRG1", spr[SPR_SPRG0 + 1]), 309 | ("SPRG2", spr[SPR_SPRG0 + 2]), 310 | ("SPRG3", spr[SPR_SPRG0 + 3]), 311 | ("EAR", spr[SPR_EAR]), 312 | ("PVR", spr[SPR_PVR]), 313 | ("GQR0", spr[SPR_GQR0]), 314 | ("GQR1", spr[SPR_GQR0 + 1]), 315 | ("GQR2", spr[SPR_GQR0 + 2]), 316 | ("GQR3", spr[SPR_GQR0 + 3]), 317 | ("GQR4", spr[SPR_GQR0 + 4]), 318 | ("GQR5", spr[SPR_GQR0 + 5]), 319 | ("GQR6", spr[SPR_GQR0 + 6]), 320 | ("GQR7", spr[SPR_GQR0 + 7]), 321 | ("HID0", spr[SPR_HID0]), 322 | ("HID1", spr[SPR_HID1]), 323 | ("HID2", spr[SPR_HID2]), 324 | ("WPAR", spr[SPR_WPAR]), 325 | ("MMCR0", spr[SPR_MMCR0]), 326 | ("MMCR1", spr[SPR_MMCR1]), 327 | ("UMMCR0", spr[SPR_UMMCR0]), 328 | ("UMMCR1", spr[SPR_UMMCR1]), 329 | ("UPMC1", spr[SPR_UPMC1]), 330 | ("UPMC2", spr[SPR_UPMC2]), 331 | ("UPMC3", spr[SPR_UPMC3]), 332 | ("UPMC4", spr[SPR_UPMC4]), 333 | ("USIA", spr[SPR_USIA]), 334 | ("PMC1", spr[SPR_PMC1]), 335 | ("PMC2", spr[SPR_PMC2]), 336 | ("PMC3", spr[SPR_PMC3]), 337 | ("PMC4", spr[SPR_PMC4]), 338 | ("SIA", spr[SPR_SIA]), 339 | ("IABR", spr[SPR_IABR]), 340 | ("DABR", spr[SPR_DABR]), 341 | ("L2CR", spr[SPR_L2CR]), 342 | ("ICTC", spr[SPR_ICTC]), 343 | ("THRM1", spr[SPR_THRM1]), 344 | ("THRM2", spr[SPR_THRM1 + 1]), 345 | ("THRM3", spr[SPR_THRM1 + 2]), 346 | ]; 347 | let spr_64 = [ 348 | ("TB", ((spr[SPR_TBU] as u64) << 32) | (spr[SPR_TBL] as u64)), 349 | ( 350 | "IBAT0", 351 | ((spr[SPR_IBAT0U] as u64) << 32) | (spr[SPR_IBAT0L] as u64), 352 | ), 353 | ( 354 | "IBAT1", 355 | ((spr[SPR_IBAT1U] as u64) << 32) | (spr[SPR_IBAT1L] as u64), 356 | ), 357 | ( 358 | "IBAT2", 359 | ((spr[SPR_IBAT2U] as u64) << 32) | (spr[SPR_IBAT2L] as u64), 360 | ), 361 | ( 362 | "IBAT3", 363 | ((spr[SPR_IBAT3U] as u64) << 32) | (spr[SPR_IBAT3L] as u64), 364 | ), 365 | ( 366 | "DBAT0", 367 | ((spr[SPR_DBAT0U] as u64) << 32) | (spr[SPR_DBAT0L] as u64), 368 | ), 369 | ( 370 | "DBAT1", 371 | ((spr[SPR_DBAT1U] as u64) << 32) | (spr[SPR_DBAT1L] as u64), 372 | ), 373 | ( 374 | "DBAT2", 375 | ((spr[SPR_DBAT2U] as u64) << 32) | (spr[SPR_DBAT2L] as u64), 376 | ), 377 | ( 378 | "DBAT3", 379 | ((spr[SPR_DBAT3U] as u64) << 32) | (spr[SPR_DBAT3L] as u64), 380 | ), 381 | ( 382 | "DMA", 383 | ((spr[SPR_DMAU] as u64) << 32) | (spr[SPR_DMAU + 1] as u64), 384 | ), 385 | ]; 386 | 387 | Registers { 388 | gpr: *ctx.cpu().gpr(), 389 | fpr: ctx.cpu().fpr().clone(), 390 | spr_32, 391 | spr_64, 392 | } 393 | } 394 | } 395 | -------------------------------------------------------------------------------- /debugger/gobject/breakpoint_object.rs: -------------------------------------------------------------------------------- 1 | use glib::Object; 2 | use gtk::glib; 3 | 4 | glib::wrapper! { 5 | pub struct BreakpointObject(ObjectSubclass); 6 | } 7 | 8 | impl BreakpointObject { 9 | pub fn new( 10 | type_: String, 11 | num: u32, 12 | address: String, 13 | condition: String, 14 | start_address: u32, 15 | end_address: u32, 16 | ) -> Self { 17 | Object::new(&[ 18 | ("type", &type_), 19 | ("num", &num), 20 | ("address", &address), 21 | ("condition", &condition), 22 | ("startAddress", &start_address), 23 | ("endAddress", &end_address), 24 | ]) 25 | } 26 | } 27 | 28 | mod imp { 29 | 30 | use std::cell::{Cell, RefCell}; 31 | 32 | use gtk::glib; 33 | use gtk::glib::{ParamSpec, Value}; 34 | use gtk::prelude::*; 35 | use gtk::subclass::prelude::*; 36 | 37 | // Object holding the state 38 | #[derive(Default)] 39 | pub struct BreakpointObject { 40 | type_: RefCell, 41 | num: Cell, 42 | address: RefCell, 43 | condition: RefCell, 44 | start_address: Cell, 45 | end_address: Cell, 46 | } 47 | 48 | // The central trait for subclassing a GObject 49 | #[glib::object_subclass] 50 | impl ObjectSubclass for BreakpointObject { 51 | const NAME: &'static str = "BreakpointObject"; 52 | type Type = super::BreakpointObject; 53 | type ParentType = glib::Object; 54 | type Interfaces = (); 55 | } 56 | 57 | // Trait shared by all GObjects 58 | impl ObjectImpl for BreakpointObject { 59 | fn properties() -> &'static [glib::ParamSpec] { 60 | use once_cell::sync::Lazy; 61 | static PROPERTIES: Lazy> = Lazy::new(|| { 62 | vec![ 63 | glib::ParamSpecString::new( 64 | "type", 65 | "Type", 66 | "breakpoint or watchpoint", 67 | None, // Default value 68 | glib::ParamFlags::READWRITE, 69 | ), 70 | glib::ParamSpecUInt::builder("num").build(), 71 | glib::ParamSpecString::new( 72 | "address", 73 | "address", 74 | "breakpoint address", 75 | None, 76 | glib::ParamFlags::READWRITE, 77 | ), 78 | glib::ParamSpecString::new( 79 | "condition", 80 | "Condition", 81 | "Read, Write, ReadOrWrite", 82 | None, 83 | glib::ParamFlags::READWRITE, 84 | ), 85 | glib::ParamSpecUInt::builder("startAddress").build(), 86 | glib::ParamSpecUInt::builder("endAddress").build(), 87 | ] 88 | }); 89 | PROPERTIES.as_ref() 90 | } 91 | 92 | fn set_property(&self, _id: usize, value: &Value, pspec: &ParamSpec) { 93 | match pspec.name() { 94 | "type" => { 95 | let type_ = value 96 | .get() 97 | .expect("type conformity checked by `Object::set_property`"); 98 | self.type_.replace(type_); 99 | } 100 | "num" => { 101 | let num = value.get().unwrap(); 102 | self.num.replace(num); 103 | } 104 | "address" => { 105 | let address = value 106 | .get() 107 | .expect("type conformity checked by `Object::set_property`"); 108 | self.address.replace(address); 109 | } 110 | "condition" => { 111 | let condition = value 112 | .get() 113 | .expect("type conformity checked by `Object::set_property`"); 114 | self.condition.replace(condition); 115 | } 116 | "startAddress" => { 117 | let start_address = value.get().unwrap(); 118 | self.start_address.replace(start_address); 119 | } 120 | "endAddress" => { 121 | let end_address = value.get().unwrap(); 122 | self.end_address.replace(end_address); 123 | } 124 | _ => unimplemented!(), 125 | } 126 | } 127 | 128 | fn property(&self, _id: usize, pspec: &glib::ParamSpec) -> Value { 129 | match pspec.name() { 130 | "type" => self.type_.borrow().to_value(), 131 | "num" => self.num.get().to_value(), 132 | "address" => self.address.borrow().to_value(), 133 | "condition" => self.condition.borrow().to_value(), 134 | "startAddress" => self.start_address.get().to_value(), 135 | "endAddress" => self.end_address.get().to_value(), 136 | _ => unimplemented!(), 137 | } 138 | } 139 | 140 | fn constructed(&self) { 141 | self.parent_constructed(); 142 | } 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /debugger/gobject/callstack_object.rs: -------------------------------------------------------------------------------- 1 | use glib::Object; 2 | use gtk::glib; 3 | 4 | glib::wrapper! { 5 | pub struct CallstackObject(ObjectSubclass); 6 | } 7 | 8 | impl CallstackObject { 9 | pub fn new(value: String) -> Self { 10 | Object::new(&[("value", &value)]) 11 | } 12 | } 13 | 14 | mod imp { 15 | 16 | use std::cell::RefCell; 17 | 18 | use gtk::glib; 19 | use gtk::glib::{ParamSpec, Value}; 20 | use gtk::prelude::*; 21 | use gtk::subclass::prelude::*; 22 | 23 | // Object holding the state 24 | #[derive(Default)] 25 | pub struct CallstackObject { 26 | value: RefCell, 27 | } 28 | 29 | // The central trait for subclassing a GObject 30 | #[glib::object_subclass] 31 | impl ObjectSubclass for CallstackObject { 32 | const NAME: &'static str = "CallstackObject"; 33 | type Type = super::CallstackObject; 34 | type ParentType = glib::Object; 35 | type Interfaces = (); 36 | } 37 | 38 | // Trait shared by all GObjects 39 | impl ObjectImpl for CallstackObject { 40 | fn properties() -> &'static [glib::ParamSpec] { 41 | use once_cell::sync::Lazy; 42 | static PROPERTIES: Lazy> = Lazy::new(|| { 43 | vec![glib::ParamSpecString::new( 44 | "value", 45 | "Value", 46 | "Whether to auto-update or not", 47 | None, // Default value 48 | glib::ParamFlags::READWRITE, 49 | )] 50 | }); 51 | PROPERTIES.as_ref() 52 | } 53 | 54 | fn set_property(&self, _id: usize, value: &Value, pspec: &ParamSpec) { 55 | match pspec.name() { 56 | "value" => { 57 | let value = value 58 | .get() 59 | .expect("type conformity checked by `Object::set_property`"); 60 | self.value.replace(value); 61 | } 62 | _ => unimplemented!(), 63 | } 64 | } 65 | 66 | fn property(&self, _id: usize, pspec: &glib::ParamSpec) -> Value { 67 | match pspec.name() { 68 | "value" => self.value.borrow().to_value(), 69 | _ => unimplemented!(), 70 | } 71 | } 72 | 73 | fn constructed(&self) { 74 | self.parent_constructed(); 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /debugger/gobject/disassembled_instruction.rs: -------------------------------------------------------------------------------- 1 | use glib::Object; 2 | use gtk::glib; 3 | 4 | glib::wrapper! { 5 | pub struct DisassembledInstruction(ObjectSubclass); 6 | } 7 | 8 | impl DisassembledInstruction { 9 | pub fn new( 10 | address: String, 11 | instruction: String, 12 | opcode: String, 13 | operands: String, 14 | class: String, 15 | ) -> Self { 16 | Object::new(&[ 17 | ("address", &address), 18 | ("instruction", &instruction), 19 | ("opcode", &opcode), 20 | ("operands", &operands), 21 | ("class", &class), 22 | ]) 23 | } 24 | } 25 | 26 | mod imp { 27 | 28 | use std::cell::RefCell; 29 | 30 | use gtk::glib; 31 | use gtk::glib::{ParamSpec, Value}; 32 | use gtk::prelude::*; 33 | use gtk::subclass::prelude::*; 34 | 35 | // Object holding the state 36 | #[derive(Default)] 37 | pub struct DisassembledInstruction { 38 | address: RefCell, 39 | instruction: RefCell, 40 | opcode: RefCell, 41 | operands: RefCell, 42 | class: RefCell, 43 | } 44 | 45 | // The central trait for subclassing a GObject 46 | #[glib::object_subclass] 47 | impl ObjectSubclass for DisassembledInstruction { 48 | const NAME: &'static str = "DisassembledInstruction"; 49 | type Type = super::DisassembledInstruction; 50 | type ParentType = glib::Object; 51 | type Interfaces = (); 52 | } 53 | 54 | // Trait shared by all GObjects 55 | impl ObjectImpl for DisassembledInstruction { 56 | fn properties() -> &'static [glib::ParamSpec] { 57 | use once_cell::sync::Lazy; 58 | static PROPERTIES: Lazy> = Lazy::new(|| { 59 | vec![ 60 | glib::ParamSpecString::new( 61 | "address", 62 | "Address", 63 | "Whether to auto-update or not", 64 | None, 65 | glib::ParamFlags::READWRITE, 66 | ), 67 | glib::ParamSpecString::new( 68 | "instruction", 69 | "Instruction", 70 | "Whether to auto-update or not", 71 | None, 72 | glib::ParamFlags::READWRITE, 73 | ), 74 | glib::ParamSpecString::new( 75 | "opcode", 76 | "Opcode", 77 | "Whether to auto-update or not", 78 | None, 79 | glib::ParamFlags::READWRITE, 80 | ), 81 | glib::ParamSpecString::new( 82 | "operands", 83 | "Operands", 84 | "Whether to auto-update or not", 85 | None, 86 | glib::ParamFlags::READWRITE, 87 | ), 88 | glib::ParamSpecString::new( 89 | "class", 90 | "Class", 91 | "Whether to auto-update or not", 92 | None, 93 | glib::ParamFlags::READWRITE, 94 | ), 95 | ] 96 | }); 97 | PROPERTIES.as_ref() 98 | } 99 | 100 | fn set_property(&self, _id: usize, value: &Value, pspec: &ParamSpec) { 101 | match pspec.name() { 102 | "address" => { 103 | let address = value 104 | .get() 105 | .expect("type conformity checked by `Object::set_property`"); 106 | self.address.replace(address); 107 | } 108 | "instruction" => { 109 | let instruction = value 110 | .get() 111 | .expect("type conformity checked by `Object::set_property`"); 112 | self.instruction.replace(instruction); 113 | } 114 | "opcode" => { 115 | let opcode = value 116 | .get() 117 | .expect("type conformity checked by `Object::set_property`"); 118 | self.opcode.replace(opcode); 119 | } 120 | "operands" => { 121 | let operands = value 122 | .get() 123 | .expect("type conformity checked by `Object::set_property`"); 124 | self.operands.replace(operands); 125 | } 126 | "class" => { 127 | let class = value 128 | .get() 129 | .expect("type conformity checked by `Object::set_property`"); 130 | self.class.replace(class); 131 | } 132 | _ => unimplemented!(), 133 | } 134 | } 135 | 136 | fn property(&self, _id: usize, pspec: &glib::ParamSpec) -> Value { 137 | match pspec.name() { 138 | "address" => self.address.borrow().to_value(), 139 | "instruction" => self.instruction.borrow().to_value(), 140 | "opcode" => self.opcode.borrow().to_value(), 141 | "operands" => self.operands.borrow().to_value(), 142 | "class" => self.class.borrow().to_value(), 143 | _ => unimplemented!(), 144 | } 145 | } 146 | 147 | fn constructed(&self) { 148 | self.parent_constructed(); 149 | } 150 | } 151 | } 152 | -------------------------------------------------------------------------------- /debugger/gobject/mod.rs: -------------------------------------------------------------------------------- 1 | mod breakpoint_object; 2 | mod callstack_object; 3 | mod disassembled_instruction; 4 | 5 | pub use breakpoint_object::BreakpointObject; 6 | pub use callstack_object::CallstackObject; 7 | pub use disassembled_instruction::DisassembledInstruction; 8 | -------------------------------------------------------------------------------- /debugger/main.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | extern crate log; 3 | 4 | mod app; 5 | mod background; 6 | mod gobject; 7 | 8 | use self::app::App; 9 | use self::background::BgEvent; 10 | use env_logger::Env; 11 | use getopts::Options; 12 | use gtk::glib; 13 | use gtk::prelude::*; 14 | use rustcube::cpu::disassembler::DecodedInstruction; 15 | use rustcube::cpu::{NUM_FPR, NUM_GPR}; 16 | use std::env; 17 | use std::path::Path; 18 | 19 | const APP_ID: &str = "com.rustcube-debugger"; 20 | 21 | pub type DynResult = Result>; 22 | 23 | pub struct Registers { 24 | gpr: [u32; NUM_GPR], 25 | fpr: [rustcube::cpu::Fpr; NUM_FPR], 26 | spr_32: [(&'static str, u32); 48], 27 | spr_64: [(&'static str, u64); 10], 28 | } 29 | 30 | impl Default for Registers { 31 | fn default() -> Self { 32 | Registers { 33 | gpr: Default::default(), 34 | fpr: Default::default(), 35 | spr_32: [("", 0); 48], 36 | spr_64: [("", 0); 10], 37 | } 38 | } 39 | } 40 | 41 | pub struct Disassembly { 42 | pc: u32, 43 | instructions: Vec, 44 | breakpoints: Vec, 45 | } 46 | 47 | pub struct Callstack { 48 | addresses: Vec, 49 | } 50 | 51 | pub struct Memory { 52 | data: Vec<(u32, [u8; 16])>, 53 | } 54 | 55 | pub enum BreakpointType { 56 | Break, 57 | Watch, 58 | } 59 | 60 | enum BreakpointAccessType { 61 | Read, 62 | Write, 63 | ReadWrite, 64 | } 65 | 66 | pub struct Breakpoint { 67 | type_: BreakpointType, 68 | num: u32, 69 | start_address: u32, 70 | end_address: u32, 71 | access_type: BreakpointAccessType, 72 | } 73 | 74 | pub struct Breakpoints { 75 | breakpoints: Vec, 76 | } 77 | 78 | pub enum Event { 79 | Breakpoints(Box), 80 | Callstack(Box), 81 | Closed, 82 | Disassembly(Box), 83 | Registers(Box), 84 | Memory(Box), 85 | Paused, 86 | } 87 | 88 | fn print_usage(program: &str, opts: &Options) { 89 | let brief = format!("Usage: {program} [options] IPL_FILE"); 90 | print!("{}", opts.usage(&brief)); 91 | } 92 | 93 | fn load_css() { 94 | let provider = gtk::CssProvider::new(); 95 | 96 | provider.load_from_data(include_bytes!("style.css")); 97 | 98 | gtk::StyleContext::add_provider_for_display( 99 | >k::gdk::Display::default().expect("Could not connect to a display."), 100 | &provider, 101 | gtk::STYLE_PROVIDER_PRIORITY_APPLICATION, 102 | ); 103 | } 104 | 105 | fn main() -> DynResult<()> { 106 | env_logger::Builder::from_env(Env::default().default_filter_or("info")).init(); 107 | 108 | let args: Vec = env::args().collect(); 109 | let program = args[0].clone(); 110 | 111 | let mut opts = Options::new(); 112 | opts.optflag("h", "help", "print this help menu"); 113 | 114 | let matches = match opts.parse(&args[1..]) { 115 | Ok(m) => m, 116 | Err(f) => panic!("{}", f.to_string()), 117 | }; 118 | 119 | if matches.opt_present("h") { 120 | print_usage(&program, &opts); 121 | return Ok(()); 122 | } 123 | 124 | let file_name = if !matches.free.is_empty() { 125 | matches.free[0].clone() 126 | } else { 127 | print_usage(&program, &opts); 128 | return Ok(()); 129 | }; 130 | 131 | let app = gtk::Application::new(Some(APP_ID), Default::default()); 132 | 133 | app.connect_startup(|_| load_css()); 134 | 135 | app.connect_activate(move |app| { 136 | let (tx, rx) = async_channel::unbounded(); 137 | let tx2 = tx.clone(); 138 | let (btx, brx) = async_channel::unbounded(); 139 | let file_name = file_name.clone(); 140 | 141 | std::thread::spawn(move || { 142 | let mut emu_ctx = rustcube::Context::default(); 143 | 144 | let file_name = Path::new(&file_name); 145 | 146 | match file_name.extension() { 147 | Some(ext) => { 148 | if ext == "dol" { 149 | emu_ctx.load_dol(file_name); 150 | } else if ext == "iso" || ext == "gcm" { 151 | emu_ctx.load_iso(file_name); 152 | } else { 153 | // assume ipl 154 | emu_ctx.load_ipl(file_name); 155 | } 156 | } 157 | None => emu_ctx.load_ipl(file_name), 158 | } 159 | 160 | let ctx = glib::MainContext::new(); 161 | ctx.with_thread_default(|| { 162 | ctx.block_on(background::run(emu_ctx, tx, brx)); 163 | }) 164 | .unwrap(); 165 | }); 166 | 167 | let mut app = App::new(app, tx2, btx); 168 | 169 | let event_handler = async move { 170 | while let Ok(event) = rx.recv().await { 171 | match event { 172 | Event::Breakpoints(bps) => app.update_breakpoints(*bps), 173 | Event::Callstack(cs) => app.update_callstack(*cs), 174 | Event::Closed => unimplemented!(), 175 | Event::Disassembly(disassembly) => app.update_disassembly(*disassembly), 176 | Event::Registers(regs) => app.update_registers(*regs), 177 | Event::Memory(mem) => app.update_memory(*mem), 178 | Event::Paused => app.paused(), 179 | } 180 | } 181 | }; 182 | 183 | glib::MainContext::default().spawn_local(event_handler); 184 | }); 185 | 186 | app.run_with_args(&[""; 0]); 187 | 188 | Ok(()) 189 | } 190 | -------------------------------------------------------------------------------- /debugger/style.css: -------------------------------------------------------------------------------- 1 | .toolbar { 2 | border-bottom: 1px solid lightgrey; 3 | } 4 | .toolbar button { 5 | padding: 10px; 6 | border: none; 7 | } 8 | /* Delete later */ 9 | list row label { 10 | font-family: 'Monospace'; 11 | font-size: 14px; 12 | } 13 | 14 | .disassembly-column-view { 15 | font-family: 'Monospace'; 16 | font-size: 14px; 17 | } 18 | .disassembly-column-view listview row cell { 19 | padding: 0; 20 | } 21 | .disassembly-column-view listview row cell label { 22 | padding: 2px 4px; 23 | } 24 | 25 | .breakpoint-column-view { 26 | font-family: 'Monospace'; 27 | font-size: 14px; 28 | } 29 | .breakpoint-column-view listview row cell { 30 | padding: 0; 31 | } 32 | .breakpoint-column-view listview row cell label { 33 | padding: 2px 4px; 34 | } 35 | 36 | #current-instruction { 37 | background-color: #00ff0066; 38 | } 39 | #breakpoint-instruction { 40 | background-color: #ff000066; 41 | } 42 | .memory-toolbar label { 43 | padding: 0 4px; 44 | } 45 | .memory-toolbar entry { 46 | padding: 0 4px; 47 | } 48 | .memory-column-view { 49 | font-family: 'Monospace'; 50 | font-size: 14px; 51 | border-width: 20px; 52 | } 53 | .memory-column-view box label:first-child { 54 | border-bottom-color: #cccccc; 55 | border-bottom-style: solid; 56 | border-bottom-width: 1px; 57 | } 58 | .memory-column-view > box:first-child > label, 59 | .memory-column-view > box:nth-child(2) > label { 60 | border-right-color: #cccccc; 61 | border-right-style: solid; 62 | border-right-width: 1px; 63 | } 64 | .memory-column-view > box > label { 65 | padding: 2px 4px; 66 | color: black; 67 | background-color: white; 68 | } 69 | .dump-tree-view button, .registers-tree-view button, .stack-tree-view button { 70 | padding: 0px 10px 0px 5px; 71 | font-size: 14px; 72 | } 73 | .registers-tree-view, .stack-tree-view { 74 | font-family: 'Monospace'; 75 | font-size: 14px; 76 | } 77 | -------------------------------------------------------------------------------- /src/ai.rs: -------------------------------------------------------------------------------- 1 | use crate::pi::{clear_interrupt, set_interrupt, PI_INTERRUPT_AI}; 2 | use crate::Context; 3 | 4 | const AI_CONTROL_STATUS: u32 = 0x00; 5 | const AI_VOLUME: u32 = 0x04; 6 | const AI_SAMPLE_COUNTER: u32 = 0x08; 7 | const AI_INTERRUPT_TIMING: u32 = 0x0c; 8 | 9 | // 0 - 48 kHz 10 | // 1 - 32 kHz 11 | static SAMPLE_RATE: [u32; 2] = [48000, 32000]; 12 | 13 | #[derive(Debug, Default)] 14 | pub struct AudioInterface { 15 | control: ControlRegister, 16 | volume: u32, 17 | sample_counter: u32, 18 | interrupt_timing: u32, 19 | sample_rate: u32, 20 | cycles_per_sample: u32, 21 | cpu_ticks: u64, 22 | } 23 | 24 | bitfield! { 25 | #[derive(Copy, Clone, Default)] 26 | pub struct ControlRegister(u32); 27 | impl Debug; 28 | // PSTAT (Playing Status) 29 | pub pstat, set_pstat : 0; 30 | // AFR (Auxiliary Frequency Register) 31 | pub afr, set_afr : 1; 32 | // AIINTMSK (Audio interface Interrupt Mask) 33 | pub aiintmsk, set_aiintmsk : 2; 34 | // AIINT (Audio Interface Interrupt Status and clear) 35 | pub aiint, set_aiint : 3; 36 | // AIINTVLD (Audio Interface Interrupt Valid) 37 | pub ai_interrupt_valid, set_ai_interrupt_valid : 4; 38 | // SCRESET (Sample Counter Reset) 39 | pub screset, set_screset : 5; 40 | // DSP (Sample Rate) 41 | pub dsp, _ : 6; 42 | } 43 | 44 | impl From for ControlRegister { 45 | fn from(v: u32) -> Self { 46 | ControlRegister(v) 47 | } 48 | } 49 | 50 | impl From for u32 { 51 | fn from(s: ControlRegister) -> u32 { 52 | s.0 53 | } 54 | } 55 | 56 | pub fn read_u32(ctx: &mut Context, register: u32) -> u32 { 57 | match register { 58 | AI_CONTROL_STATUS => ctx.ai.control.into(), 59 | AI_SAMPLE_COUNTER => ctx.ai.sample_counter, 60 | AI_VOLUME => ctx.ai.volume, 61 | _ => { 62 | warn!("read_u32 unrecognized ai register {:#x}", register); 63 | 0 64 | } 65 | } 66 | } 67 | 68 | pub fn write_u32(ctx: &mut Context, register: u32, val: u32) { 69 | match register { 70 | AI_CONTROL_STATUS => { 71 | let new_config = ControlRegister(val); 72 | let config = &mut ctx.ai.control; 73 | 74 | if new_config.aiintmsk() != config.aiintmsk() { 75 | info!("Change ai_interrupt_mask to {}", new_config.aiintmsk()); 76 | config.set_aiintmsk(new_config.aiintmsk()); 77 | } 78 | 79 | if new_config.ai_interrupt_valid() != config.ai_interrupt_valid() { 80 | info!( 81 | "Change ai_interrupt_valid to {}", 82 | new_config.ai_interrupt_valid() 83 | ); 84 | config.set_ai_interrupt_valid(new_config.ai_interrupt_valid()); 85 | } 86 | 87 | if new_config.afr() != config.afr() { 88 | config.set_afr(new_config.afr()); 89 | ctx.ai.sample_rate = SAMPLE_RATE[config.afr() as usize]; 90 | ctx.ai.cycles_per_sample = 486000000 / ctx.ai.sample_rate; 91 | } 92 | 93 | if new_config.aiint() { 94 | info!("CLEAR INTERRUPT"); 95 | config.set_aiint(false); 96 | } 97 | 98 | if new_config.pstat() != config.pstat() { 99 | config.set_pstat(new_config.pstat()); 100 | 101 | if new_config.pstat() { 102 | info!("start streaming audio"); 103 | } else { 104 | info!("stop streaming audio"); 105 | } 106 | 107 | ctx.ai.cpu_ticks = ctx.timers.get_ticks(); 108 | } 109 | 110 | if new_config.screset() { 111 | ctx.ai.sample_counter = 0; 112 | 113 | ctx.ai.cpu_ticks = ctx.timers.get_ticks(); 114 | } 115 | 116 | if config.aiint() && config.aiintmsk() { 117 | panic!("interrupt"); 118 | } 119 | 120 | update_interrupts(ctx); 121 | } 122 | AI_VOLUME => ctx.ai.volume = val, 123 | AI_INTERRUPT_TIMING => ctx.ai.interrupt_timing = val, 124 | _ => panic!("write_u32 unrecognized ai register {register:#x}"), 125 | } 126 | } 127 | 128 | fn update_interrupts(ctx: &mut Context) { 129 | if ctx.ai.control.aiint() && ctx.ai.control.aiintmsk() { 130 | set_interrupt(ctx, PI_INTERRUPT_AI); 131 | } else { 132 | clear_interrupt(ctx, PI_INTERRUPT_AI); 133 | } 134 | } 135 | 136 | pub fn update(ctx: &mut Context) { 137 | let ticks = ctx.get_ticks(); 138 | if ticks - ctx.ai.cpu_ticks > 600 { 139 | ctx.ai.cpu_ticks = ticks; 140 | } else { 141 | return; 142 | } 143 | 144 | if !ctx.ai.control.pstat() { 145 | return; 146 | } 147 | 148 | if ctx.ai.sample_counter > ctx.ai.interrupt_timing { 149 | ctx.ai.control.set_aiint(true); 150 | 151 | update_interrupts(ctx); 152 | } 153 | 154 | ctx.ai.sample_counter += 1; 155 | } 156 | -------------------------------------------------------------------------------- /src/cpu/float.rs: -------------------------------------------------------------------------------- 1 | pub const QUANTIZE_FLOAT: u32 = 0; // Single-precision floating-point (no conversion) 2 | pub const QUANTIZE_U8: u32 = 4; // unsigned 8 bit integer 3 | pub const QUANTIZE_U16: u32 = 5; // unsigned 16 bit integer 4 | pub const QUANTIZE_I8: u32 = 6; // signed 8 bit integer 5 | pub const QUANTIZE_I16: u32 = 7; // signed 16 bit integer 6 | 7 | // Paired-single store scale 8 | const QUANTIZE_TABLE: [f32; 64] = [ 9 | (1_u32 << 0) as f32, 10 | (1_u32 << 1) as f32, 11 | (1_u32 << 2) as f32, 12 | (1_u32 << 3) as f32, 13 | (1_u32 << 4) as f32, 14 | (1_u32 << 5) as f32, 15 | (1_u32 << 6) as f32, 16 | (1_u32 << 7) as f32, 17 | (1_u32 << 8) as f32, 18 | (1_u32 << 9) as f32, 19 | (1_u32 << 10) as f32, 20 | (1_u32 << 11) as f32, 21 | (1_u32 << 12) as f32, 22 | (1_u32 << 13) as f32, 23 | (1_u32 << 14) as f32, 24 | (1_u32 << 15) as f32, 25 | (1_u32 << 16) as f32, 26 | (1_u32 << 17) as f32, 27 | (1_u32 << 18) as f32, 28 | (1_u32 << 19) as f32, 29 | (1_u32 << 20) as f32, 30 | (1_u32 << 21) as f32, 31 | (1_u32 << 22) as f32, 32 | (1_u32 << 23) as f32, 33 | (1_u32 << 24) as f32, 34 | (1_u32 << 25) as f32, 35 | (1_u32 << 26) as f32, 36 | (1_u32 << 27) as f32, 37 | (1_u32 << 28) as f32, 38 | (1_u32 << 29) as f32, 39 | (1_u32 << 30) as f32, 40 | (1_u32 << 31) as f32, 41 | 1.0 / (1_u64 << 32) as f32, 42 | 1.0 / (1_u32 << 31) as f32, 43 | 1.0 / (1_u32 << 30) as f32, 44 | 1.0 / (1_u32 << 29) as f32, 45 | 1.0 / (1_u32 << 28) as f32, 46 | 1.0 / (1_u32 << 27) as f32, 47 | 1.0 / (1_u32 << 26) as f32, 48 | 1.0 / (1_u32 << 25) as f32, 49 | 1.0 / (1_u32 << 24) as f32, 50 | 1.0 / (1_u32 << 23) as f32, 51 | 1.0 / (1_u32 << 22) as f32, 52 | 1.0 / (1_u32 << 21) as f32, 53 | 1.0 / (1_u32 << 20) as f32, 54 | 1.0 / (1_u32 << 19) as f32, 55 | 1.0 / (1_u32 << 18) as f32, 56 | 1.0 / (1_u32 << 17) as f32, 57 | 1.0 / (1_u32 << 16) as f32, 58 | 1.0 / (1_u32 << 15) as f32, 59 | 1.0 / (1_u32 << 14) as f32, 60 | 1.0 / (1_u32 << 13) as f32, 61 | 1.0 / (1_u32 << 12) as f32, 62 | 1.0 / (1_u32 << 11) as f32, 63 | 1.0 / (1_u32 << 10) as f32, 64 | 1.0 / (1_u32 << 9) as f32, 65 | 1.0 / (1_u32 << 8) as f32, 66 | 1.0 / (1_u32 << 7) as f32, 67 | 1.0 / (1_u32 << 6) as f32, 68 | 1.0 / (1_u32 << 5) as f32, 69 | 1.0 / (1_u32 << 4) as f32, 70 | 1.0 / (1_u32 << 3) as f32, 71 | 1.0 / (1_u32 << 2) as f32, 72 | 1.0 / (1_u32 << 1) as f32, 73 | ]; 74 | 75 | // paired-single load scale 76 | const DEQUANTIZE_TABLE: [f32; 64] = [ 77 | 1.0 / (1_u32 << 0) as f32, 78 | 1.0 / (1_u32 << 1) as f32, 79 | 1.0 / (1_u32 << 2) as f32, 80 | 1.0 / (1_u32 << 3) as f32, 81 | 1.0 / (1_u32 << 4) as f32, 82 | 1.0 / (1_u32 << 5) as f32, 83 | 1.0 / (1_u32 << 6) as f32, 84 | 1.0 / (1_u32 << 7) as f32, 85 | 1.0 / (1_u32 << 8) as f32, 86 | 1.0 / (1_u32 << 9) as f32, 87 | 1.0 / (1_u32 << 10) as f32, 88 | 1.0 / (1_u32 << 11) as f32, 89 | 1.0 / (1_u32 << 12) as f32, 90 | 1.0 / (1_u32 << 13) as f32, 91 | 1.0 / (1_u32 << 14) as f32, 92 | 1.0 / (1_u32 << 15) as f32, 93 | 1.0 / (1_u32 << 16) as f32, 94 | 1.0 / (1_u32 << 17) as f32, 95 | 1.0 / (1_u32 << 18) as f32, 96 | 1.0 / (1_u32 << 19) as f32, 97 | 1.0 / (1_u32 << 20) as f32, 98 | 1.0 / (1_u32 << 21) as f32, 99 | 1.0 / (1_u32 << 22) as f32, 100 | 1.0 / (1_u32 << 23) as f32, 101 | 1.0 / (1_u32 << 24) as f32, 102 | 1.0 / (1_u32 << 25) as f32, 103 | 1.0 / (1_u32 << 26) as f32, 104 | 1.0 / (1_u32 << 27) as f32, 105 | 1.0 / (1_u32 << 28) as f32, 106 | 1.0 / (1_u32 << 29) as f32, 107 | 1.0 / (1_u32 << 30) as f32, 108 | 1.0 / (1_u32 << 31) as f32, 109 | (1_u64 << 32) as f32, 110 | (1_u32 << 31) as f32, 111 | (1_u32 << 30) as f32, 112 | (1_u32 << 29) as f32, 113 | (1_u32 << 28) as f32, 114 | (1_u32 << 27) as f32, 115 | (1_u32 << 26) as f32, 116 | (1_u32 << 25) as f32, 117 | (1_u32 << 24) as f32, 118 | (1_u32 << 23) as f32, 119 | (1_u32 << 22) as f32, 120 | (1_u32 << 21) as f32, 121 | (1_u32 << 20) as f32, 122 | (1_u32 << 19) as f32, 123 | (1_u32 << 18) as f32, 124 | (1_u32 << 17) as f32, 125 | (1_u32 << 16) as f32, 126 | (1_u32 << 15) as f32, 127 | (1_u32 << 14) as f32, 128 | (1_u32 << 13) as f32, 129 | (1_u32 << 12) as f32, 130 | (1_u32 << 11) as f32, 131 | (1_u32 << 10) as f32, 132 | (1_u32 << 9) as f32, 133 | (1_u32 << 8) as f32, 134 | (1_u32 << 7) as f32, 135 | (1_u32 << 6) as f32, 136 | (1_u32 << 5) as f32, 137 | (1_u32 << 4) as f32, 138 | (1_u32 << 3) as f32, 139 | (1_u32 << 2) as f32, 140 | (1_u32 << 1) as f32, 141 | ]; 142 | 143 | pub fn quantize(mut value: f32, st_type: u32, st_scale: u32) -> u32 { 144 | value *= QUANTIZE_TABLE[st_scale as usize]; 145 | 146 | match st_type { 147 | QUANTIZE_FLOAT => f32::to_bits(value), 148 | QUANTIZE_U8 => (value.clamp(u8::MIN as f32, u8::MAX as f32) as u8) as u32, 149 | QUANTIZE_U16 => (value.clamp(u16::MIN as f32, u16::MAX as f32) as u16) as u32, 150 | QUANTIZE_I8 => ((value.clamp(i8::MIN as f32, i8::MAX as f32) as i8) as i32) as u32, 151 | QUANTIZE_I16 => ((value.clamp(i16::MIN as f32, i16::MAX as f32) as i16) as i32) as u32, 152 | _ => { 153 | warn!("Unrecognized quantize type {st_type}."); 154 | f32::to_bits(value) 155 | } 156 | } 157 | } 158 | 159 | pub fn dequantize(value: u32, ld_type: u32, ld_scale: u32) -> f32 { 160 | let result = match ld_type { 161 | QUANTIZE_FLOAT => f32::from_bits(value), 162 | QUANTIZE_U8 => (value as u8) as f32, 163 | QUANTIZE_U16 => (value as u16) as f32, 164 | QUANTIZE_I8 => (value as i8) as f32, 165 | QUANTIZE_I16 => (value as i16) as f32, 166 | _ => { 167 | warn!("unrecognized dequantize unknown type {ld_type}."); 168 | f32::from_bits(value) 169 | } 170 | }; 171 | 172 | result * DEQUANTIZE_TABLE[ld_scale as usize] 173 | } 174 | 175 | pub trait Nan { 176 | fn is_snan(&self) -> bool; 177 | #[allow(dead_code)] 178 | fn is_qnan(&self) -> bool; 179 | } 180 | 181 | impl Nan for f32 { 182 | fn is_snan(&self) -> bool { 183 | let v = f32::to_bits(*self); 184 | v & 0x7FC0_0000 == 0x7F80_0000 && v & 0x003F_FFFF != 0 185 | } 186 | 187 | fn is_qnan(&self) -> bool { 188 | let v = f32::to_bits(*self); 189 | v & 0x7FC0_0000 == 0x7FC0_0000 190 | } 191 | } 192 | 193 | impl Nan for f64 { 194 | fn is_snan(&self) -> bool { 195 | let v = f64::to_bits(*self); 196 | v & 0x7FF8_0000_0000_0000 == 0x7FF0_0000_0000_0000 && v & 0x000F_FFFF_FFFF_FFFF != 0 197 | } 198 | 199 | fn is_qnan(&self) -> bool { 200 | let v = f64::to_bits(*self); 201 | v & 0x7FF8_0000_0000_0000 == 0x7FF8_0000_0000_0000 202 | } 203 | } 204 | 205 | #[cfg(test)] 206 | mod tests { 207 | use super::*; 208 | 209 | #[test] 210 | fn f32_is_snan() { 211 | let snan = f32::from_bits(0xFF800001); 212 | 213 | assert!(snan.is_nan()); 214 | assert!(snan.is_snan()); 215 | assert!(!snan.is_qnan()); 216 | 217 | let snan = f32::from_bits(0xFF800301); 218 | 219 | assert!(snan.is_nan()); 220 | assert!(snan.is_snan()); 221 | assert!(!snan.is_qnan()); 222 | } 223 | 224 | #[test] 225 | fn f64_is_snan() { 226 | let snan = f64::from_bits(0x7FF0000000000001); 227 | 228 | assert!(snan.is_nan()); 229 | assert!(snan.is_snan()); 230 | assert!(!snan.is_qnan()); 231 | 232 | let snan = f64::from_bits(0x7FF0000000020001); 233 | 234 | assert!(snan.is_nan()); 235 | assert!(snan.is_snan()); 236 | assert!(!snan.is_qnan()); 237 | } 238 | 239 | #[test] 240 | fn f64_is_qnan() { 241 | let qnan = f64::from_bits(0x7FF8000000000001); 242 | 243 | assert!(qnan.is_nan()); 244 | assert!(!qnan.is_snan()); 245 | assert!(qnan.is_qnan()); 246 | 247 | let qnan = f64::from_bits(0x7FF8000000020001); 248 | 249 | assert!(qnan.is_nan()); 250 | assert!(!qnan.is_snan()); 251 | assert!(qnan.is_qnan()); 252 | } 253 | 254 | #[test] 255 | fn f632_is_qnan() { 256 | let qnan = f32::from_bits(0xFFC00001); 257 | 258 | assert!(qnan.is_nan()); 259 | assert!(!qnan.is_snan()); 260 | assert!(qnan.is_qnan()); 261 | 262 | let qnan = f32::from_bits(0xFFC00301); 263 | 264 | assert!(qnan.is_nan()); 265 | assert!(!qnan.is_snan()); 266 | assert!(qnan.is_qnan()); 267 | } 268 | } 269 | -------------------------------------------------------------------------------- /src/cpu/mmu.rs: -------------------------------------------------------------------------------- 1 | use super::MachineStateRegister; 2 | 3 | #[derive(Default, Clone, Copy, Debug)] 4 | pub struct Bat { 5 | bepi: u16, // Block Effect Page Index 6 | bl: u16, // Block-length Mask 7 | vs: bool, // Supervisor state valid bit -- allows root access 8 | vp: bool, // Problem state valid bit -- allows user access 9 | brpn: u16, // Block Real Page Number 10 | wimg: u8, // Storage Access Controls 11 | pp: u8, // Protection bits for Bat Ares (00 No Access, 01 Read Only, 10 Read/Write) 12 | } 13 | 14 | #[derive(Debug, Default)] 15 | pub struct Mmu { 16 | pub dbat: [Bat; 4], 17 | pub ibat: [Bat; 4], 18 | } 19 | 20 | impl Mmu { 21 | pub fn write_ibatu(&mut self, index: usize, value: u32) { 22 | let bat = &mut self.ibat[index]; 23 | 24 | // FixMe: validate BAT value 25 | // MSRIR | MSRDR = 1 26 | // (Vs & ~MSRPR) | (Vp & MSRPR) = 1 27 | 28 | bat.bepi = ((value >> 17) & 0x7FFF) as u16; 29 | bat.bl = ((value >> 2) & 0x7FF) as u16; 30 | bat.vs = ((value >> 1) & 1) != 0; 31 | bat.vp = (value & 1) != 0; 32 | } 33 | 34 | pub fn write_dbatu(&mut self, index: usize, value: u32) { 35 | let bat = &mut self.dbat[index]; 36 | 37 | // FixMe: validate BAT value 38 | // MSRIR | MSRDR = 1 39 | // (Vs & ~MSRPR) | (Vp & MSRPR) = 1 40 | 41 | bat.bepi = ((value >> 17) & 0x7FFF) as u16; 42 | bat.bl = ((value >> 2) & 0x7FF) as u16; 43 | bat.vs = ((value >> 1) & 1) != 0; 44 | bat.vp = (value & 1) != 0; 45 | } 46 | 47 | pub fn write_ibatl(&mut self, index: usize, value: u32) { 48 | let bat = &mut self.ibat[index]; 49 | 50 | // FixMe: validate BAT value 51 | 52 | bat.brpn = ((value >> 17) & 0x7FFF) as u16; 53 | bat.wimg = ((value >> 3) & 0x1F) as u8; 54 | bat.pp = (value & 3) as u8; 55 | } 56 | 57 | pub fn write_dbatl(&mut self, index: usize, value: u32) { 58 | let bat = &mut self.dbat[index]; 59 | 60 | // FixMe: validate BAT value 61 | 62 | bat.brpn = ((value >> 17) & 0x7FFF) as u16; 63 | bat.wimg = ((value >> 3) & 0x1F) as u8; 64 | bat.pp = (value & 3) as u8; 65 | } 66 | } 67 | 68 | pub fn translate_address(bats: &[Bat; 4], msr: MachineStateRegister, ea: u32) -> u32 { 69 | // Block Address Translation 70 | for bat in bats { 71 | let ea_15 = (ea >> 17) as u16; 72 | let ea_bepi = (ea_15 & 0x7800) ^ ((ea_15 & 0x7FF) & (!bat.bl)); 73 | 74 | if ea_bepi == bat.bepi && ((!msr.pr() && bat.vs) || (msr.pr() && bat.vp)) { 75 | let upper = u32::from(bat.brpn ^ ((ea_15 & 0x7FF) & bat.bl)); 76 | let lower = ea & 0x1FFFF; 77 | 78 | return (upper << 17) ^ lower; 79 | } 80 | } 81 | 82 | // Segment Address Translation 83 | unimplemented!("MMU page/segment address translation {:#x} {:}", ea, ea); 84 | } 85 | -------------------------------------------------------------------------------- /src/cpu/ops.rs: -------------------------------------------------------------------------------- 1 | pub mod branch; 2 | pub mod condition; 3 | pub mod float; 4 | pub mod integer; 5 | pub mod load_store; 6 | pub mod system; 7 | -------------------------------------------------------------------------------- /src/cpu/ops/branch.rs: -------------------------------------------------------------------------------- 1 | use crate::cpu::instruction::Instruction; 2 | use crate::cpu::util::*; 3 | use crate::cpu::{SPR_CTR, SPR_LR}; 4 | use crate::Context; 5 | 6 | const BO_DONT_DECREMENT: u8 = 0x4; 7 | 8 | pub fn op_bx(ctx: &mut Context, instr: Instruction) { 9 | let address = sign_ext_26(instr.li() << 2) as u32; 10 | 11 | if instr.aa() { 12 | ctx.cpu.nia = address; 13 | } else { 14 | ctx.cpu.nia = ctx.cpu.cia.wrapping_add(address); 15 | } 16 | 17 | if instr.lk() { 18 | ctx.cpu.spr[SPR_LR] = ctx.cpu.cia.wrapping_add(4); 19 | } 20 | 21 | ctx.tick(1); 22 | } 23 | 24 | pub fn op_bcx(ctx: &mut Context, instr: Instruction) { 25 | let bo = instr.bo(); 26 | 27 | if bo & BO_DONT_DECREMENT == 0 { 28 | ctx.cpu.spr[SPR_CTR] = ctx.cpu.spr[SPR_CTR].wrapping_sub(1); 29 | } 30 | 31 | let ctr_ok = (bo >> 2) & 1 != 0 || (((ctx.cpu.spr[SPR_CTR] != 0) as u8 ^ (bo >> 1)) & 1) != 0; 32 | let cond_ok = (bo >> 4) & 1 != 0 || (ctx.cpu.cr.get_bit(instr.bi()) == (bo >> 3) & 1); 33 | 34 | if ctr_ok && cond_ok { 35 | let address = sign_ext_16(instr.bd() << 2) as u32; 36 | 37 | if instr.aa() { 38 | ctx.cpu.nia = address; 39 | } else { 40 | ctx.cpu.nia = ctx.cpu.cia.wrapping_add(address); 41 | } 42 | 43 | if instr.lk() { 44 | ctx.cpu.spr[SPR_LR] = ctx.cpu.cia.wrapping_add(4); 45 | } 46 | } 47 | 48 | ctx.tick(1); 49 | } 50 | 51 | pub fn op_bcctrx(ctx: &mut Context, instr: Instruction) { 52 | let bo = instr.bo(); 53 | 54 | // FIXME: check this logic 55 | if bo & BO_DONT_DECREMENT == 0 { 56 | panic!("bcctrx: Invalid instruction, BO[2] = 0"); 57 | } 58 | 59 | let cond_ok = ((bo >> 4) | (ctx.cpu.cr.get_bit(instr.bi()) == ((bo >> 3) & 1)) as u8) & 1; 60 | 61 | if cond_ok != 0 { 62 | ctx.cpu.nia = ctx.cpu.spr[SPR_CTR] & (!3); 63 | 64 | if instr.lk() { 65 | ctx.cpu.spr[SPR_LR] = ctx.cpu.cia.wrapping_add(4); 66 | } 67 | } 68 | 69 | ctx.tick(1); 70 | } 71 | 72 | pub fn op_bclrx(ctx: &mut Context, instr: Instruction) { 73 | let bo = instr.bo(); 74 | 75 | if bo & BO_DONT_DECREMENT == 0 { 76 | ctx.cpu.spr[SPR_CTR] = ctx.cpu.spr[SPR_CTR].wrapping_sub(1); 77 | } 78 | 79 | let ctr_ok = ((bo >> 2) | ((ctx.cpu.spr[SPR_CTR] != 0) as u8 ^ (bo >> 1))) & 1; 80 | let cond_ok = ((bo >> 4) | (ctx.cpu.cr.get_bit(instr.bi()) == ((bo >> 3) & 1)) as u8) & 1; 81 | 82 | if ctr_ok != 0 && cond_ok != 0 { 83 | ctx.cpu.nia = ctx.cpu.spr[SPR_LR] & (!3); 84 | 85 | if instr.lk() { 86 | ctx.cpu.spr[SPR_LR] = ctx.cpu.cia.wrapping_add(4); 87 | } 88 | } 89 | 90 | ctx.tick(1); 91 | } 92 | 93 | #[cfg(test)] 94 | mod tests { 95 | use super::*; 96 | use crate::cpu::ops::integer::{op_addi, op_addic_rc}; 97 | use crate::cpu::ops::system::op_mtspr; 98 | 99 | #[test] 100 | fn op_bcx() { 101 | let mut ctx = Context::default(); 102 | 103 | // addi 8,0,3 104 | let (rd, ra, simm) = (8, 0, 0x3); 105 | let instr = Instruction::new_addi(rd, ra, simm); 106 | 107 | op_addi(&mut ctx, instr); 108 | 109 | assert_eq!(ctx.cpu.gpr[rd], 0x0000_0003); 110 | 111 | // mtctr 8 112 | let instr = Instruction::new_mtspr(0x9, 0x8); 113 | op_mtspr(&mut ctx, instr); 114 | 115 | // check counter register is set to 0x3 116 | assert_eq!(ctx.cpu.spr[SPR_CTR], 0x0000_0003); 117 | 118 | // addic. 9,8,0x1 119 | let (rd, ra, simm) = (9, 8, 0x1); 120 | let instr = Instruction::new_addic_rc(rd, ra, simm); 121 | 122 | op_addic_rc(&mut ctx, instr); 123 | 124 | assert_eq!(ctx.cpu.gpr[rd], 0x0000_0004); 125 | assert_eq!(ctx.cpu.cr.get_cr0(), 0x0000_0004); 126 | 127 | // bc 0xC,1,0x456 128 | let (bo, bi, bd) = (0xC, 1, 0x456); 129 | let instr = Instruction::new_bcx(bo, bi, bd); 130 | 131 | super::op_bcx(&mut ctx, instr); 132 | 133 | assert_eq!(ctx.cpu.nia, 0xFFF0_1258); 134 | 135 | // bcl 0x8,1,0x456 136 | let (bo, bi, bd, lk) = (0x8, 1, 0x456, 1); 137 | let instr = Instruction::new_bcx(bo, bi, bd).set_lk(lk); 138 | 139 | super::op_bcx(&mut ctx, instr); 140 | 141 | assert_eq!(ctx.cpu.spr[SPR_CTR], 0x2); 142 | assert_eq!(ctx.cpu.spr[SPR_LR], 0xFFF0_0104); 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /src/cpu/ops/condition.rs: -------------------------------------------------------------------------------- 1 | use crate::cpu::instruction::Instruction; 2 | use crate::Context; 3 | 4 | pub fn op_crand(_ctx: &mut Context, _instr: Instruction) { 5 | unimplemented!("op_crand"); 6 | } 7 | 8 | pub fn op_crandc(_ctx: &mut Context, _instr: Instruction) { 9 | unimplemented!("op_crandc"); 10 | } 11 | 12 | pub fn op_creqv(_ctx: &mut Context, _instr: Instruction) { 13 | unimplemented!("op_creqv"); 14 | } 15 | 16 | pub fn op_crnand(_ctx: &mut Context, _instr: Instruction) { 17 | unimplemented!("op_crnand"); 18 | } 19 | 20 | pub fn op_crnor(_ctx: &mut Context, _instr: Instruction) { 21 | unimplemented!("op_crnor"); 22 | } 23 | 24 | pub fn op_cror(_ctx: &mut Context, _instr: Instruction) { 25 | unimplemented!("op_cror"); 26 | } 27 | 28 | pub fn op_crorc(_ctx: &mut Context, _instr: Instruction) { 29 | unimplemented!("op_crorc"); 30 | } 31 | 32 | pub fn op_crxor(ctx: &mut Context, instr: Instruction) { 33 | let d = ctx.cpu.cr.get_bit(instr.a()) ^ ctx.cpu.cr.get_bit(instr.b()); 34 | 35 | ctx.cpu.cr.set_bit(instr.d(), d); 36 | 37 | ctx.tick(1); 38 | } 39 | 40 | pub fn op_mcrf(_ctx: &mut Context, _instr: Instruction) { 41 | unimplemented!("op_mcrf"); 42 | } 43 | 44 | pub fn op_mcrxr(_ctx: &mut Context, _instr: Instruction) { 45 | unimplemented!("op_mcrxr"); 46 | } 47 | 48 | pub fn op_mfcr(ctx: &mut Context, instr: Instruction) { 49 | ctx.cpu.gpr[instr.d()] = ctx.cpu.cr.as_u32(); 50 | 51 | ctx.tick(1); 52 | } 53 | 54 | pub fn op_mtcrf(ctx: &mut Context, instr: Instruction) { 55 | let crm = instr.crm(); 56 | 57 | if crm == 0xFF { 58 | ctx.cpu.cr.set(ctx.cpu.gpr[instr.s()]); 59 | } else { 60 | unimplemented!("op_mtcrf crm != 0xFF"); 61 | } 62 | 63 | ctx.tick(1); 64 | } 65 | -------------------------------------------------------------------------------- /src/cpu/ops/float.rs: -------------------------------------------------------------------------------- 1 | use crate::cpu::float::Nan; 2 | use crate::cpu::instruction::Instruction; 3 | use crate::cpu::EXCEPTION_FPU_UNAVAILABLE; 4 | use crate::Context; 5 | 6 | pub fn op_fabsx(_ctx: &mut Context, _instr: Instruction) { 7 | unimplemented!("op_fabsx"); 8 | } 9 | 10 | pub fn op_faddsx(ctx: &mut Context, instr: Instruction) { 11 | if !ctx.cpu.msr.fp() { 12 | ctx.cpu.exceptions |= EXCEPTION_FPU_UNAVAILABLE; 13 | return; 14 | } 15 | 16 | let fra = ctx.cpu.fpr[instr.b()].ps0_as_f64(); 17 | let frb = ctx.cpu.fpr[instr.b()].ps0_as_f64(); 18 | 19 | let result = fra + frb; 20 | 21 | ctx.cpu.fpr[instr.d()].set_ps0_f64(result); 22 | 23 | if ctx.cpu.hid2.pse() { 24 | ctx.cpu.fpr[instr.d()].set_ps1_f64(result); 25 | } 26 | 27 | if instr.rc() { 28 | ctx.cpu.update_cr1(); 29 | } 30 | 31 | ctx.tick(1); 32 | } 33 | 34 | pub fn op_faddx(_ctx: &mut Context, _instr: Instruction) { 35 | unimplemented!("op_faddx"); 36 | } 37 | 38 | pub fn op_fcmpo(ctx: &mut Context, instr: Instruction) { 39 | if !ctx.cpu.msr.fp() { 40 | ctx.cpu.exceptions |= EXCEPTION_FPU_UNAVAILABLE; 41 | return; 42 | } 43 | 44 | let fra = ctx.cpu.fpr[instr.a()].ps0_as_f64(); 45 | let frb = ctx.cpu.fpr[instr.b()].ps0_as_f64(); 46 | 47 | let c = if fra.is_nan() || frb.is_nan() { 48 | if fra.is_snan() || frb.is_snan() { 49 | ctx.cpu.fpscr.set_vxsnan(true); 50 | if !ctx.cpu.fpscr.ve() { 51 | ctx.cpu.fpscr.set_vxvc(true); 52 | } 53 | } else { 54 | ctx.cpu.fpscr.set_vxsnan(true); 55 | } 56 | 0b1 // ? 57 | } else if fra < frb { 58 | 0x8 // < 59 | } else if fra > frb { 60 | 0x4 // > 61 | } else { 62 | 0x2 // = 63 | }; 64 | 65 | ctx.cpu.fpscr.set_fpcc(c); 66 | 67 | ctx.cpu.cr.set_field(instr.crfd(), c); 68 | 69 | ctx.tick(1); 70 | } 71 | 72 | pub fn op_fcmpu(ctx: &mut Context, instr: Instruction) { 73 | if !ctx.cpu.msr.fp() { 74 | ctx.cpu.exceptions |= EXCEPTION_FPU_UNAVAILABLE; 75 | return; 76 | } 77 | 78 | let fra = ctx.cpu.fpr[instr.a()].ps0_as_f64(); 79 | let frb = ctx.cpu.fpr[instr.b()].ps0_as_f64(); 80 | 81 | let c = if fra.is_nan() || frb.is_nan() { 82 | if fra.is_snan() || frb.is_snan() { 83 | ctx.cpu.fpscr.set_vxsnan(true); 84 | } 85 | 0b1 // ? 86 | } else if fra < frb { 87 | 0x8 // < 88 | } else if fra > frb { 89 | 0x4 // > 90 | } else { 91 | 0x2 // = 92 | }; 93 | 94 | ctx.cpu.fpscr.set_fpcc(c); 95 | 96 | ctx.cpu.cr.set_field(instr.crfd(), c); 97 | 98 | ctx.tick(1); 99 | } 100 | 101 | pub fn op_fctiwzx(ctx: &mut Context, instr: Instruction) { 102 | let frb = ctx.cpu.fpr[instr.b()].ps0_as_f64(); 103 | 104 | // TODO: implement more accurate conversion 105 | let result = ((frb as i32) as u32) as u64; 106 | 107 | ctx.cpu.fpr[instr.d()].set_ps0(result); 108 | 109 | if instr.rc() { 110 | ctx.cpu.update_cr1(); 111 | } 112 | 113 | ctx.tick(1); 114 | } 115 | 116 | pub fn op_fctiwx(_ctx: &mut Context, _instr: Instruction) { 117 | unimplemented!("op_fctiwx"); 118 | } 119 | 120 | pub fn op_fdivsx(ctx: &mut Context, instr: Instruction) { 121 | let fra = ctx.cpu.fpr[instr.a()].ps0_as_f64(); 122 | let frb = ctx.cpu.fpr[instr.b()].ps0_as_f64(); 123 | 124 | let result = fra / frb; 125 | 126 | if frb.is_nan() { 127 | panic!(); 128 | } 129 | 130 | ctx.cpu.fpr[instr.d()].set_ps0_f64(result); 131 | 132 | if ctx.cpu.hid2.pse() { 133 | ctx.cpu.fpr[instr.d()].set_ps1_f64(result); 134 | } 135 | 136 | if instr.rc() { 137 | ctx.cpu.update_cr1(); 138 | } 139 | 140 | ctx.tick(17); 141 | } 142 | 143 | pub fn op_fdivx(_ctx: &mut Context, _instr: Instruction) { 144 | unimplemented!("op_fdivx"); 145 | } 146 | 147 | pub fn op_fmaddsx(_ctx: &mut Context, _instr: Instruction) { 148 | unimplemented!("op_fmaddsx"); 149 | } 150 | 151 | pub fn op_fmaddx(_ctx: &mut Context, _instr: Instruction) { 152 | unimplemented!("op_fmaddx"); 153 | } 154 | 155 | // FIXME: Verify paired single functionality with HID2[PSE] value 156 | pub fn op_fmrx(ctx: &mut Context, instr: Instruction) { 157 | if !ctx.cpu.msr.fp() { 158 | ctx.cpu.exceptions |= EXCEPTION_FPU_UNAVAILABLE; 159 | return; 160 | } 161 | 162 | let frb = ctx.cpu.fpr[instr.b()].ps0(); 163 | 164 | ctx.cpu.fpr[instr.d()].set_ps0(frb); 165 | 166 | if ctx.cpu.hid2.pse() { 167 | ctx.cpu.fpr[instr.d()].set_ps1(frb); 168 | } 169 | 170 | if instr.rc() { 171 | ctx.cpu.update_cr1(); 172 | } 173 | 174 | ctx.tick(1); 175 | } 176 | 177 | pub fn op_fmsubsx(_ctx: &mut Context, _instr: Instruction) { 178 | unimplemented!("op_fmsubsx"); 179 | } 180 | 181 | pub fn op_fmsubx(_ctx: &mut Context, _instr: Instruction) { 182 | unimplemented!("op_fmsubx"); 183 | } 184 | 185 | pub fn op_fmulsx(ctx: &mut Context, instr: Instruction) { 186 | if !ctx.cpu.msr.fp() { 187 | ctx.cpu.exceptions |= EXCEPTION_FPU_UNAVAILABLE; 188 | return; 189 | } 190 | 191 | let result = ctx.cpu.fpr[instr.a()].ps0_as_f64() * ctx.cpu.fpr[instr.c()].ps0_as_f64(); 192 | 193 | ctx.cpu.fpr[instr.d()].set_ps0_f64(result); 194 | 195 | if ctx.cpu.hid2.pse() { 196 | ctx.cpu.fpr[instr.d()].set_ps1_f64(result); 197 | } 198 | 199 | if instr.rc() { 200 | ctx.cpu.update_cr1(); 201 | } 202 | 203 | ctx.tick(1); 204 | } 205 | 206 | pub fn op_fmulx(ctx: &mut Context, instr: Instruction) { 207 | if !ctx.cpu.msr.fp() { 208 | ctx.cpu.exceptions |= EXCEPTION_FPU_UNAVAILABLE; 209 | return; 210 | } 211 | 212 | let fra = ctx.cpu.fpr[instr.a()].ps0_as_f64(); 213 | let frc = ctx.cpu.fpr[instr.c()].ps0_as_f64(); 214 | 215 | let result = fra * frc; 216 | 217 | ctx.cpu.fpr[instr.d()].set_ps0_f64(result); 218 | 219 | if instr.rc() { 220 | ctx.cpu.update_cr1(); 221 | } 222 | } 223 | 224 | pub fn op_fnabsx(_ctx: &mut Context, _instr: Instruction) { 225 | unimplemented!("op_fnabsx"); 226 | } 227 | 228 | pub fn op_fnegx(ctx: &mut Context, instr: Instruction) { 229 | ctx.cpu.fpr[instr.d()].set_ps0(ctx.cpu.fpr[instr.b()].ps0() | (1_u64 << 63)); 230 | 231 | ctx.tick(1); 232 | } 233 | 234 | pub fn op_fnmaddsx(_ctx: &mut Context, _instr: Instruction) { 235 | unimplemented!("op_fnmaddsx"); 236 | } 237 | 238 | pub fn op_fnmaddx(_ctx: &mut Context, _instr: Instruction) { 239 | unimplemented!("op_fnmaddx"); 240 | } 241 | 242 | pub fn op_fnmsubsx(_ctx: &mut Context, _instr: Instruction) { 243 | unimplemented!("op_fnsubsx"); 244 | } 245 | 246 | pub fn op_fnmsubx(ctx: &mut Context, instr: Instruction) { 247 | if !ctx.cpu.msr.fp() { 248 | ctx.cpu.exceptions |= EXCEPTION_FPU_UNAVAILABLE; 249 | return; 250 | } 251 | 252 | let fra = ctx.cpu.fpr[instr.a()].ps0_as_f64(); 253 | let frb = ctx.cpu.fpr[instr.b()].ps0_as_f64(); 254 | let frc = ctx.cpu.fpr[instr.c()].ps0_as_f64(); 255 | 256 | let result = fra.mul_add(frc, -frb); 257 | 258 | ctx.cpu.fpr[instr.d()].set_ps0_f64(result); 259 | 260 | if instr.rc() { 261 | ctx.cpu.update_cr1(); 262 | } 263 | 264 | ctx.tick(2); 265 | } 266 | 267 | pub fn op_fresx(_ctx: &mut Context, _instr: Instruction) { 268 | unimplemented!("op_fresx"); 269 | } 270 | 271 | pub fn op_frspx(ctx: &mut Context, instr: Instruction) { 272 | if !ctx.cpu.msr.fp() { 273 | ctx.cpu.exceptions |= EXCEPTION_FPU_UNAVAILABLE; 274 | return; 275 | } 276 | 277 | let frb = ctx.cpu.fpr[instr.b()].ps0_as_f64(); 278 | 279 | if frb.is_nan() { 280 | unimplemented!(); 281 | } 282 | 283 | ctx.cpu.fpr[instr.d()].set_ps0_f64(frb); 284 | 285 | if ctx.cpu.hid2.pse() { 286 | ctx.cpu.fpr[instr.d()].set_ps1_f64(frb); 287 | } 288 | 289 | if instr.rc() { 290 | ctx.cpu.update_cr1(); 291 | } 292 | } 293 | 294 | pub fn op_frsqrtex(ctx: &mut Context, instr: Instruction) { 295 | let frb = ctx.cpu.fpr[instr.b()].ps0_as_f64(); 296 | 297 | ctx.cpu.fpr[instr.d()].set_ps0_f64(1.0 / frb.sqrt()); 298 | 299 | if instr.rc() { 300 | ctx.cpu.update_cr1(); 301 | } 302 | 303 | ctx.tick(2); 304 | } 305 | 306 | pub fn op_fselx(_ctx: &mut Context, _instr: Instruction) { 307 | unimplemented!("op_fselx"); 308 | } 309 | 310 | pub fn op_fsubsx(ctx: &mut Context, instr: Instruction) { 311 | if !ctx.cpu.msr.fp() { 312 | ctx.cpu.exceptions |= EXCEPTION_FPU_UNAVAILABLE; 313 | return; 314 | } 315 | 316 | let result = ctx.cpu.fpr[instr.a()].ps0_as_f64() - ctx.cpu.fpr[instr.b()].ps0_as_f64(); 317 | 318 | ctx.cpu.fpr[instr.d()].set_ps0_f64(result); 319 | 320 | if ctx.cpu.hid2.pse() { 321 | ctx.cpu.fpr[instr.d()].set_ps1_f64(result); 322 | } 323 | 324 | if instr.rc() { 325 | ctx.cpu.update_cr1(); 326 | } 327 | 328 | ctx.tick(1); 329 | } 330 | 331 | pub fn op_ps_absx(_ctx: &mut Context, _instr: Instruction) { 332 | unimplemented!("op_ps_absx"); 333 | } 334 | 335 | pub fn op_ps_addx(ctx: &mut Context, instr: Instruction) { 336 | let fra = ctx.cpu.fpr[instr.a()].ps0_as_f64(); 337 | let frb = ctx.cpu.fpr[instr.b()].ps0_as_f64(); 338 | 339 | ctx.cpu.fpr[instr.d()].set_ps0_f64(fra + frb); 340 | 341 | let fra = ctx.cpu.fpr[instr.a()].ps1_as_f64(); 342 | let frb = ctx.cpu.fpr[instr.b()].ps1_as_f64(); 343 | 344 | ctx.cpu.fpr[instr.d()].set_ps1_f64(fra + frb); 345 | 346 | if instr.rc() { 347 | ctx.cpu.update_cr1(); 348 | } 349 | 350 | ctx.tick(1); 351 | } 352 | 353 | pub fn op_ps_cmpo0(_ctx: &mut Context, _instr: Instruction) { 354 | unimplemented!("op_ps_cmpo0"); 355 | } 356 | 357 | pub fn op_ps_cmpo1(_ctx: &mut Context, _instr: Instruction) { 358 | unimplemented!("op_ps_cmpo1"); 359 | } 360 | 361 | pub fn op_ps_cmpu0(_ctx: &mut Context, _instr: Instruction) { 362 | unimplemented!("op_ps_cmpu0"); 363 | } 364 | 365 | pub fn op_ps_cmpu1(_ctx: &mut Context, _instr: Instruction) { 366 | unimplemented!("op_ps_cmpu1"); 367 | } 368 | 369 | pub fn op_ps_divx(_ctx: &mut Context, _instr: Instruction) { 370 | unimplemented!("op_ps_divx"); 371 | } 372 | 373 | pub fn op_ps_maddx(ctx: &mut Context, instr: Instruction) { 374 | let fra = ctx.cpu.fpr[instr.a()].ps0_as_f64(); 375 | let frb = ctx.cpu.fpr[instr.b()].ps0_as_f64(); 376 | let frc = ctx.cpu.fpr[instr.c()].ps0_as_f64(); 377 | 378 | ctx.cpu.fpr[instr.d()].set_ps0_f64(fra.mul_add(frc, frb)); 379 | 380 | let fra = ctx.cpu.fpr[instr.a()].ps1_as_f64(); 381 | let frb = ctx.cpu.fpr[instr.b()].ps1_as_f64(); 382 | let frc = ctx.cpu.fpr[instr.c()].ps1_as_f64(); 383 | 384 | ctx.cpu.fpr[instr.d()].set_ps1_f64(fra.mul_add(frc, frb)); 385 | 386 | if instr.rc() { 387 | ctx.cpu.update_cr1(); 388 | } 389 | 390 | ctx.tick(1); 391 | } 392 | 393 | pub fn op_ps_madds0x(_ctx: &mut Context, _instr: Instruction) { 394 | unimplemented!("op_ps_madds0x"); 395 | } 396 | 397 | pub fn op_ps_madds1x(_ctx: &mut Context, _instr: Instruction) { 398 | unimplemented!("op_ps_madds1x"); 399 | } 400 | 401 | pub fn op_ps_merge00x(ctx: &mut Context, instr: Instruction) { 402 | if !ctx.cpu.msr.fp() { 403 | ctx.cpu.exceptions |= EXCEPTION_FPU_UNAVAILABLE; 404 | return; 405 | } 406 | 407 | let fra = ctx.cpu.fpr[instr.a()].ps0(); 408 | let frb = ctx.cpu.fpr[instr.b()].ps0(); 409 | 410 | ctx.cpu.fpr[instr.d()].set_ps0(fra); 411 | ctx.cpu.fpr[instr.d()].set_ps1(frb); 412 | 413 | if instr.rc() { 414 | ctx.cpu.update_cr1(); 415 | } 416 | 417 | ctx.tick(1); 418 | } 419 | 420 | pub fn op_ps_merge01x(ctx: &mut Context, instr: Instruction) { 421 | if !ctx.cpu.msr.fp() { 422 | ctx.cpu.exceptions |= EXCEPTION_FPU_UNAVAILABLE; 423 | return; 424 | } 425 | 426 | let fra = ctx.cpu.fpr[instr.a()].ps0(); 427 | let frb = ctx.cpu.fpr[instr.b()].ps1(); 428 | 429 | ctx.cpu.fpr[instr.d()].set_ps0(fra); 430 | ctx.cpu.fpr[instr.d()].set_ps1(frb); 431 | 432 | if instr.rc() { 433 | ctx.cpu.update_cr1(); 434 | } 435 | 436 | ctx.tick(1); 437 | } 438 | 439 | pub fn op_ps_merge10x(ctx: &mut Context, instr: Instruction) { 440 | if !ctx.cpu.msr.fp() { 441 | ctx.cpu.exceptions |= EXCEPTION_FPU_UNAVAILABLE; 442 | return; 443 | } 444 | 445 | let fra = ctx.cpu.fpr[instr.a()].ps1(); 446 | let frb = ctx.cpu.fpr[instr.b()].ps0(); 447 | 448 | ctx.cpu.fpr[instr.d()].set_ps0(fra); 449 | ctx.cpu.fpr[instr.d()].set_ps1(frb); 450 | 451 | if instr.rc() { 452 | ctx.cpu.update_cr1(); 453 | } 454 | 455 | ctx.tick(1); 456 | } 457 | 458 | pub fn op_ps_merge11x(ctx: &mut Context, instr: Instruction) { 459 | if !ctx.cpu.msr.fp() { 460 | ctx.cpu.exceptions |= EXCEPTION_FPU_UNAVAILABLE; 461 | return; 462 | } 463 | 464 | let fra = ctx.cpu.fpr[instr.a()].ps1(); 465 | let frb = ctx.cpu.fpr[instr.b()].ps1(); 466 | 467 | ctx.cpu.fpr[instr.d()].set_ps0(fra); 468 | ctx.cpu.fpr[instr.d()].set_ps1(frb); 469 | 470 | if instr.rc() { 471 | ctx.cpu.update_cr1(); 472 | } 473 | 474 | ctx.tick(1); 475 | } 476 | 477 | pub fn op_ps_mrx(_ctx: &mut Context, _instr: Instruction) { 478 | unimplemented!("op_ps_mrx"); 479 | } 480 | 481 | pub fn op_ps_msubx(_ctx: &mut Context, _instr: Instruction) { 482 | unimplemented!("op_ps_msubx"); 483 | } 484 | 485 | pub fn op_ps_mulx(ctx: &mut Context, instr: Instruction) { 486 | let fra = ctx.cpu.fpr[instr.a()].ps0_as_f64(); 487 | let frc = ctx.cpu.fpr[instr.c()].ps0_as_f64(); 488 | 489 | let result = fra * frc; 490 | 491 | ctx.cpu.fpr[instr.d()].set_ps0_f64(result); 492 | 493 | if instr.rc() { 494 | ctx.cpu.update_cr1(); 495 | } 496 | 497 | ctx.tick(2); 498 | } 499 | 500 | pub fn op_ps_muls0x(_ctx: &mut Context, _instr: Instruction) { 501 | unimplemented!("op_ps_muls0x"); 502 | } 503 | 504 | pub fn op_ps_muls1x(_ctx: &mut Context, _instr: Instruction) { 505 | unimplemented!("op_ps_muls1x"); 506 | } 507 | 508 | pub fn op_ps_nabsx(_ctx: &mut Context, _instr: Instruction) { 509 | unimplemented!("op_ps_nabsx"); 510 | } 511 | 512 | pub fn op_ps_negx(_ctx: &mut Context, _instr: Instruction) { 513 | unimplemented!("op_ps_negx"); 514 | } 515 | 516 | pub fn op_ps_nmaddx(_ctx: &mut Context, _instr: Instruction) { 517 | unimplemented!("op_ps_nmaddx"); 518 | } 519 | 520 | pub fn op_ps_nmsubx(_ctx: &mut Context, _instr: Instruction) { 521 | unimplemented!("op_ps_nmsubx"); 522 | } 523 | 524 | pub fn op_ps_resx(_ctx: &mut Context, _instr: Instruction) { 525 | unimplemented!("op_resx"); 526 | } 527 | 528 | pub fn op_ps_rsqrtex(_ctx: &mut Context, _instr: Instruction) { 529 | unimplemented!("op_ps_rsqrtex"); 530 | } 531 | 532 | pub fn op_ps_selx(_ctx: &mut Context, _instr: Instruction) { 533 | unimplemented!("op_selx"); 534 | } 535 | 536 | pub fn op_ps_subx(_ctx: &mut Context, _instr: Instruction) { 537 | unimplemented!("op_ps_subx"); 538 | } 539 | 540 | pub fn op_ps_sum0x(_ctx: &mut Context, _instr: Instruction) { 541 | unimplemented!("op_ps_sum0x"); 542 | } 543 | 544 | pub fn op_ps_sum1x(_ctx: &mut Context, _instr: Instruction) { 545 | unimplemented!("op_ps_sum1x"); 546 | } 547 | 548 | pub fn op_fsubx(ctx: &mut Context, instr: Instruction) { 549 | if !ctx.cpu.msr.fp() { 550 | ctx.cpu.exceptions |= EXCEPTION_FPU_UNAVAILABLE; 551 | return; 552 | } 553 | 554 | let result = ctx.cpu.fpr[instr.a()].ps0_as_f64() - ctx.cpu.fpr[instr.b()].ps0_as_f64(); 555 | 556 | ctx.cpu.fpr[instr.d()].set_ps0_f64(result); 557 | 558 | if ctx.cpu.hid2.pse() { 559 | ctx.cpu.fpr[instr.d()].set_ps1_f64(result); 560 | } 561 | 562 | if instr.rc() { 563 | ctx.cpu.update_cr1(); 564 | } 565 | 566 | ctx.tick(1); 567 | } 568 | 569 | pub fn op_mcrfs(_ctx: &mut Context, _instr: Instruction) { 570 | unimplemented!("op_mcrfs"); 571 | } 572 | 573 | pub fn op_mffsx(ctx: &mut Context, instr: Instruction) { 574 | ctx.cpu.fpr[instr.d()].set_ps0(ctx.cpu.fpscr.0 as u64); 575 | 576 | if instr.rc() { 577 | ctx.cpu.update_cr1(); 578 | } 579 | 580 | ctx.tick(1); 581 | } 582 | 583 | // TODO: test this implementation 584 | pub fn op_mtfsb0x(ctx: &mut Context, instr: Instruction) { 585 | let b = 0x8000_0000_u32 >> instr.crbd(); 586 | 587 | ctx.cpu.fpscr.0 &= !b; 588 | 589 | if instr.rc() { 590 | ctx.cpu.update_cr1(); 591 | } 592 | 593 | ctx.tick(3); 594 | } 595 | 596 | // TODO: test this implementation 597 | pub fn op_mtfsb1x(ctx: &mut Context, instr: Instruction) { 598 | let b = 0x8000_0000_u32 >> instr.crbd(); 599 | 600 | ctx.cpu.fpscr.0 |= b; 601 | 602 | if instr.rc() { 603 | ctx.cpu.update_cr1(); 604 | } 605 | 606 | ctx.tick(3); 607 | } 608 | 609 | pub fn op_mtfsfix(_ctx: &mut Context, _instr: Instruction) { 610 | unimplemented!("op_mtfsfix"); 611 | } 612 | 613 | // TODO: test this implementation 614 | pub fn op_mtfsfx(ctx: &mut Context, instr: Instruction) { 615 | let (mut m, mut i) = (0, 7); 616 | let fm = instr.fm(); 617 | 618 | while i >= 0 { 619 | if (fm >> i) & 1 != 0 { 620 | m |= 0xF; 621 | } 622 | m <<= 4; 623 | i -= 1; 624 | } 625 | 626 | ctx.cpu.fpscr.0 = (ctx.cpu.fpscr.0 & !m) | (ctx.cpu.fpr[instr.b()].ps0() as u32 & m); 627 | 628 | if instr.rc() { 629 | ctx.cpu.update_cr1(); 630 | } 631 | } 632 | -------------------------------------------------------------------------------- /src/cpu/ops/load_store.rs: -------------------------------------------------------------------------------- 1 | use crate::cpu::float::*; 2 | use crate::cpu::instruction::Instruction; 3 | use crate::cpu::spr::*; 4 | use crate::cpu::util::{convert_to_double, convert_to_single, sign_ext_12}; 5 | use crate::cpu::{Gqr, EXCEPTION_FPU_UNAVAILABLE, EXCEPTION_PROGRAM}; 6 | use crate::Context; 7 | 8 | fn get_ea(ctx: &Context, instr: Instruction) -> u32 { 9 | if instr.a() == 0 { 10 | (instr.simm() as i32) as u32 11 | } else { 12 | ctx.cpu.gpr[instr.a()].wrapping_add((instr.simm() as i32) as u32) 13 | } 14 | } 15 | 16 | fn get_ea_u(ctx: &Context, instr: Instruction) -> u32 { 17 | ctx.cpu.gpr[instr.a()].wrapping_add((instr.simm() as i32) as u32) 18 | } 19 | 20 | fn get_ea_x(ctx: &Context, instr: Instruction) -> u32 { 21 | if instr.a() == 0 { 22 | ctx.cpu.gpr[instr.b()] 23 | } else { 24 | ctx.cpu.gpr[instr.a()].wrapping_add(ctx.cpu.gpr[instr.b()]) 25 | } 26 | } 27 | 28 | fn get_ea_ux(ctx: &Context, instr: Instruction) -> u32 { 29 | ctx.cpu.gpr[instr.a()].wrapping_add(ctx.cpu.gpr[instr.b()]) 30 | } 31 | 32 | pub fn op_dcbf(ctx: &mut Context, _instr: Instruction) { 33 | // don't do anything 34 | 35 | ctx.tick(3); 36 | } 37 | 38 | pub fn op_dcbi(ctx: &mut Context, _instr: Instruction) { 39 | // don't do anything 40 | 41 | ctx.tick(3); 42 | } 43 | 44 | pub fn op_dcbst(ctx: &mut Context, _instr: Instruction) { 45 | ctx.tick(3); 46 | } 47 | 48 | pub fn op_dcbt(ctx: &mut Context, _instr: Instruction) { 49 | ctx.tick(2); 50 | } 51 | 52 | pub fn op_dcbtst(ctx: &mut Context, _instr: Instruction) { 53 | ctx.tick(2); 54 | } 55 | 56 | // Ignore this for the time being 57 | pub fn op_dcbz(ctx: &mut Context, _instr: Instruction) { 58 | ctx.tick(3); 59 | } 60 | 61 | // Ignore this for the time being 62 | pub fn op_dcbz_l(ctx: &mut Context, _instr: Instruction) { 63 | ctx.tick(3); 64 | unimplemented!(); 65 | } 66 | 67 | pub fn op_eciwx(_ctx: &mut Context, _instr: Instruction) { 68 | unimplemented!("op_eciwx"); 69 | } 70 | 71 | pub fn op_ecowx(_ctx: &mut Context, _instr: Instruction) { 72 | unimplemented!("op_ecowx"); 73 | } 74 | 75 | pub fn op_icbi(ctx: &mut Context, _instr: Instruction) { 76 | // don't do anything 77 | 78 | ctx.tick(3); 79 | } 80 | 81 | pub fn op_lbz(ctx: &mut Context, instr: Instruction) { 82 | ctx.cpu.gpr[instr.d()] = u32::from(ctx.read_u8(get_ea(ctx, instr))); 83 | 84 | ctx.tick(2); 85 | } 86 | 87 | pub fn op_lbzu(ctx: &mut Context, instr: Instruction) { 88 | if instr.a() == 0 || instr.a() == instr.d() { 89 | panic!("lbzu: invalid instruction"); 90 | } 91 | 92 | let ea = ctx.cpu.gpr[instr.a()].wrapping_add(instr.simm() as u32); 93 | 94 | ctx.cpu.gpr[instr.d()] = u32::from(ctx.read_u8(ea)); 95 | ctx.cpu.gpr[instr.a()] = ea; 96 | 97 | ctx.tick(2); 98 | } 99 | 100 | pub fn op_lbzux(_ctx: &mut Context, _instr: Instruction) { 101 | unimplemented!("op_lbzux"); 102 | } 103 | 104 | pub fn op_lbzx(ctx: &mut Context, instr: Instruction) { 105 | ctx.cpu.gpr[instr.d()] = u32::from(ctx.read_u8(get_ea_x(ctx, instr))); 106 | 107 | ctx.tick(2); 108 | } 109 | 110 | pub fn op_lfd(ctx: &mut Context, instr: Instruction) { 111 | let ea = get_ea(ctx, instr); 112 | 113 | // FIXME: check for DSI exception ??? 114 | let val = ctx.read_u64(ea); 115 | 116 | ctx.cpu.fpr[instr.d()].set_ps0(val); 117 | 118 | ctx.tick(2); 119 | } 120 | 121 | pub fn op_lfdu(_ctx: &mut Context, _instr: Instruction) { 122 | unimplemented!("op_lfdu"); 123 | } 124 | 125 | pub fn op_lfdux(_ctx: &mut Context, _instr: Instruction) { 126 | unimplemented!("op_lfdux"); 127 | } 128 | 129 | pub fn op_lfdx(_ctx: &mut Context, _instr: Instruction) { 130 | unimplemented!("op_lfdx"); 131 | } 132 | 133 | pub fn op_lfs(ctx: &mut Context, instr: Instruction) { 134 | let ea = get_ea(ctx, instr); 135 | 136 | let val = convert_to_double(ctx.read_u32(ea)); 137 | 138 | ctx.cpu.fpr[instr.d()].set_ps0(val); 139 | 140 | if ctx.cpu.hid2.pse() { 141 | ctx.cpu.fpr[instr.d()].set_ps1(val); 142 | } 143 | 144 | ctx.tick(2); 145 | } 146 | 147 | pub fn op_lfsu(ctx: &mut Context, instr: Instruction) { 148 | let ea = get_ea_u(ctx, instr); 149 | 150 | let val = convert_to_double(ctx.read_u32(ea)); 151 | 152 | ctx.cpu.fpr[instr.d()].set_ps0(val); 153 | 154 | if ctx.cpu.hid2.pse() { 155 | ctx.cpu.fpr[instr.d()].set_ps1(val); 156 | } 157 | 158 | ctx.cpu.gpr[instr.a()] = ea; 159 | } 160 | 161 | pub fn op_lfsux(_ctx: &mut Context, _instr: Instruction) { 162 | unimplemented!("op_lfsux"); 163 | } 164 | 165 | pub fn op_lfsx(_ctx: &mut Context, _instr: Instruction) { 166 | unimplemented!("op_lfsx"); 167 | } 168 | 169 | pub fn op_lha(ctx: &mut Context, instr: Instruction) { 170 | let ea = get_ea(ctx, instr); 171 | 172 | ctx.cpu.gpr[instr.d()] = ((ctx.read_u16(ea) as i16) as i32) as u32; 173 | 174 | ctx.tick(2); 175 | } 176 | 177 | pub fn op_lhau(_ctx: &mut Context, _instr: Instruction) { 178 | unimplemented!("op_lhau"); 179 | } 180 | 181 | pub fn op_lhaux(_ctx: &mut Context, _instr: Instruction) { 182 | unimplemented!("op_lhaux"); 183 | } 184 | 185 | pub fn op_lhax(_ctx: &mut Context, _instr: Instruction) { 186 | unimplemented!("op_lhax"); 187 | } 188 | 189 | pub fn op_lhbrx(_ctx: &mut Context, _instr: Instruction) { 190 | unimplemented!("op_lhbrx"); 191 | } 192 | 193 | pub fn op_lhz(ctx: &mut Context, instr: Instruction) { 194 | ctx.cpu.gpr[instr.d()] = u32::from(ctx.read_u16(get_ea(ctx, instr))); 195 | 196 | ctx.tick(2); 197 | } 198 | 199 | pub fn op_lhzu(ctx: &mut Context, instr: Instruction) { 200 | let ea = get_ea_u(ctx, instr); 201 | 202 | ctx.cpu.gpr[instr.d()] = u32::from(ctx.read_u16(ea)); 203 | ctx.cpu.gpr[instr.a()] = ea; 204 | 205 | ctx.tick(2); 206 | } 207 | 208 | pub fn op_lhzux(_ctx: &mut Context, _instr: Instruction) { 209 | unimplemented!("op_lhzux"); 210 | } 211 | 212 | pub fn op_lhzx(ctx: &mut Context, instr: Instruction) { 213 | ctx.cpu.gpr[instr.d()] = ctx.read_u16(get_ea_x(ctx, instr)) as u32; 214 | } 215 | 216 | pub fn op_lmw(ctx: &mut Context, instr: Instruction) { 217 | let mut ea = get_ea(ctx, instr); 218 | let mut r = instr.d(); 219 | let n = (32 - r) as u32; 220 | 221 | while r < 32 { 222 | ctx.cpu.gpr[r] = ctx.read_u32(ea); 223 | 224 | r += 1; 225 | ea += 4; 226 | } 227 | 228 | ctx.tick(2 + n); 229 | } 230 | 231 | pub fn op_lswi(_ctx: &mut Context, _instr: Instruction) { 232 | unimplemented!("op_lswi"); 233 | } 234 | 235 | pub fn op_lswx(_ctx: &mut Context, _instr: Instruction) { 236 | unimplemented!("op_lswx"); 237 | } 238 | 239 | pub fn op_lwarx(_ctx: &mut Context, _instr: Instruction) { 240 | unimplemented!("op_lwarx"); 241 | } 242 | 243 | pub fn op_lwbrx(_ctx: &mut Context, _instr: Instruction) { 244 | unimplemented!("op_lwbrx"); 245 | } 246 | 247 | pub fn op_lwz(ctx: &mut Context, instr: Instruction) { 248 | ctx.cpu.gpr[instr.d()] = ctx.read_u32(get_ea(ctx, instr)); 249 | 250 | ctx.tick(2); 251 | } 252 | 253 | pub fn op_lwzu(ctx: &mut Context, instr: Instruction) { 254 | let ea = get_ea_u(ctx, instr); 255 | 256 | ctx.cpu.gpr[instr.d()] = ctx.read_u32(ea); 257 | ctx.cpu.gpr[instr.a()] = ea; 258 | 259 | ctx.tick(2); 260 | } 261 | 262 | pub fn op_lwzux(_ctx: &mut Context, _instr: Instruction) { 263 | unimplemented!("op_lwzux"); 264 | } 265 | 266 | pub fn op_lwzx(ctx: &mut Context, instr: Instruction) { 267 | ctx.cpu.gpr[instr.d()] = ctx.read_u32(get_ea_x(ctx, instr)); 268 | 269 | ctx.tick(2); 270 | } 271 | 272 | pub fn op_psq_l(ctx: &mut Context, instr: Instruction) { 273 | if !ctx.cpu.hid2.pse() { 274 | ctx.cpu.exceptions |= EXCEPTION_PROGRAM; 275 | return; 276 | } 277 | 278 | if !ctx.cpu.msr.fp() { 279 | ctx.cpu.exceptions |= EXCEPTION_FPU_UNAVAILABLE; 280 | return; 281 | } 282 | 283 | let ea = if instr.a() == 0 { 284 | sign_ext_12(instr.uimm_1()) as u32 285 | } else { 286 | ctx.cpu.gpr[instr.a()].wrapping_add(sign_ext_12(instr.uimm_1()) as u32) 287 | }; 288 | 289 | let gqr = Gqr(ctx.cpu.spr[SPR_GQR0 + instr.i()]); 290 | let ld_type = gqr.lt(); 291 | let ld_scale = gqr.ls(); 292 | 293 | if instr.w() { 294 | let val = match ld_type { 295 | QUANTIZE_U8 | QUANTIZE_I8 => ctx.read_u8(ea) as u32, 296 | QUANTIZE_U16 | QUANTIZE_I16 => ctx.read_u16(ea) as u32, 297 | _ => ctx.read_u32(ea), 298 | }; 299 | 300 | ctx.cpu.fpr[instr.d()].set_ps0_f64(dequantize(val, ld_type, ld_scale) as f64); 301 | ctx.cpu.fpr[instr.d()].set_ps1_f64(1.0); 302 | } else { 303 | let (val1, val2) = match ld_type { 304 | QUANTIZE_U8 | QUANTIZE_I8 => (ctx.read_u8(ea) as u32, ctx.read_u8(ea + 1) as u32), 305 | QUANTIZE_U16 | QUANTIZE_I16 => (ctx.read_u16(ea) as u32, ctx.read_u16(ea + 2) as u32), 306 | _ => (ctx.read_u32(ea), ctx.read_u32(ea + 3)), 307 | }; 308 | ctx.cpu.fpr[instr.d()].set_ps0_f64(dequantize(val1, ld_type, ld_scale) as f64); 309 | ctx.cpu.fpr[instr.d()].set_ps1_f64(dequantize(val2, ld_type, ld_scale) as f64); 310 | } 311 | 312 | ctx.tick(3); 313 | } 314 | 315 | pub fn op_psq_lu(_ctx: &mut Context, _instr: Instruction) { 316 | unimplemented!("op_psq_lu"); 317 | } 318 | 319 | pub fn op_psq_lux(_ctx: &mut Context, _instr: Instruction) { 320 | unimplemented!("op_psq_lux"); 321 | } 322 | 323 | pub fn op_psq_lx(_ctx: &mut Context, _instr: Instruction) { 324 | unimplemented!("op_psq_lx"); 325 | } 326 | 327 | pub fn op_psq_st(ctx: &mut Context, instr: Instruction) { 328 | if !ctx.cpu.hid2.pse() { 329 | ctx.cpu.exceptions |= EXCEPTION_PROGRAM; 330 | return; 331 | } 332 | 333 | if !ctx.cpu.msr.fp() { 334 | ctx.cpu.exceptions |= EXCEPTION_FPU_UNAVAILABLE; 335 | return; 336 | } 337 | 338 | let ea = if instr.a() == 0 { 339 | sign_ext_12(instr.uimm_1()) as u32 340 | } else { 341 | ctx.cpu.gpr[instr.a()].wrapping_add(sign_ext_12(instr.uimm_1()) as u32) 342 | }; 343 | 344 | let gqr = Gqr(ctx.cpu.spr[SPR_GQR0 + instr.i()]); 345 | let st_type = gqr.st(); 346 | let st_scale = gqr.ls(); 347 | 348 | let ps0 = ctx.cpu.fpr[instr.s()].ps0() as f32; 349 | let ps1 = ctx.cpu.fpr[instr.s()].ps1() as f32; 350 | 351 | if instr.w() { 352 | match st_type { 353 | QUANTIZE_U8 | QUANTIZE_I8 => { 354 | ctx.write_u8(ea, quantize(ps0, st_type, st_scale) as u8); 355 | } 356 | QUANTIZE_U16 | QUANTIZE_I16 => { 357 | ctx.write_u16(ea, quantize(ps0, st_type, st_scale) as u16); 358 | } 359 | _ => ctx.write_u32(ea, quantize(ps0, st_type, st_scale)), 360 | } 361 | } else { 362 | // TODO: complete in one write, not two 363 | match st_type { 364 | QUANTIZE_U8 | QUANTIZE_I8 => { 365 | ctx.write_u8(ea, quantize(ps0, st_type, st_scale) as u8); 366 | ctx.write_u8(ea + 1, quantize(ps1, st_type, st_scale) as u8); 367 | } 368 | QUANTIZE_U16 | QUANTIZE_I16 => { 369 | ctx.write_u16(ea, quantize(ps0, st_type, st_scale) as u16); 370 | ctx.write_u16(ea + 2, quantize(ps1, st_type, st_scale) as u16); 371 | } 372 | _ => { 373 | ctx.write_u64( 374 | ea, 375 | ((quantize(ps0, st_type, st_scale) as u64) << 32) 376 | | (quantize(ps1, st_type, st_scale) as u64), 377 | ); 378 | } 379 | } 380 | } 381 | 382 | ctx.tick(2); 383 | } 384 | 385 | pub fn op_psq_stu(_ctx: &mut Context, _instr: Instruction) { 386 | unimplemented!("op_psq_stu"); 387 | } 388 | 389 | pub fn op_psq_stux(_ctx: &mut Context, _instr: Instruction) { 390 | unimplemented!("op_psq_stux"); 391 | } 392 | 393 | pub fn op_psq_stx(_ctx: &mut Context, _instr: Instruction) { 394 | unimplemented!("op_psq_stx"); 395 | } 396 | 397 | pub fn op_stb(ctx: &mut Context, instr: Instruction) { 398 | ctx.write_u8(get_ea(ctx, instr), ctx.cpu.gpr[instr.s()] as u8); 399 | 400 | ctx.tick(2); 401 | } 402 | 403 | pub fn op_stbu(ctx: &mut Context, instr: Instruction) { 404 | let ea = get_ea_u(ctx, instr); 405 | 406 | ctx.write_u8(ea, ctx.cpu.gpr[instr.s()] as u8); 407 | 408 | ctx.cpu.gpr[instr.a()] = ea; 409 | 410 | ctx.tick(2); 411 | } 412 | 413 | pub fn op_stbux(_ctx: &mut Context, _instr: Instruction) { 414 | unimplemented!("op_stbux"); 415 | } 416 | 417 | pub fn op_stbx(ctx: &mut Context, instr: Instruction) { 418 | ctx.write_u8(get_ea_x(ctx, instr), ctx.cpu.gpr[instr.s()] as u8); 419 | 420 | ctx.tick(2); 421 | } 422 | 423 | pub fn op_stfd(ctx: &mut Context, instr: Instruction) { 424 | let ea = get_ea(ctx, instr); 425 | 426 | ctx.write_u64(ea, ctx.cpu.fpr[instr.s()].ps0()); 427 | 428 | ctx.tick(2); 429 | } 430 | 431 | pub fn op_stfdu(_ctx: &mut Context, _instr: Instruction) { 432 | unimplemented!("op_stfdu"); 433 | } 434 | 435 | pub fn op_stfdux(_ctx: &mut Context, _instr: Instruction) { 436 | unimplemented!("op_stfdux"); 437 | } 438 | 439 | pub fn op_stfdx(_ctx: &mut Context, _instr: Instruction) { 440 | unimplemented!("op_stfdx"); 441 | } 442 | 443 | pub fn op_stfiwx(_ctx: &mut Context, _instr: Instruction) { 444 | unimplemented!("op_stfiwx"); 445 | } 446 | 447 | pub fn op_stfs(ctx: &mut Context, instr: Instruction) { 448 | let ea = get_ea(ctx, instr); 449 | 450 | let val = ctx.cpu.fpr[instr.s()].ps0(); 451 | 452 | ctx.write_u32(ea, convert_to_single(val)); 453 | 454 | ctx.tick(2); 455 | } 456 | 457 | pub fn op_stfsu(ctx: &mut Context, instr: Instruction) { 458 | let ea = get_ea_u(ctx, instr); 459 | 460 | let val = ctx.cpu.fpr[instr.s()].ps0(); 461 | 462 | ctx.write_u32(ea, convert_to_single(val)); 463 | 464 | ctx.cpu.gpr[instr.a()] = ea; 465 | 466 | ctx.tick(2); 467 | } 468 | 469 | pub fn op_stfsux(_ctx: &mut Context, _instr: Instruction) { 470 | unimplemented!("op_stfsux"); 471 | } 472 | 473 | pub fn op_stfsx(ctx: &mut Context, instr: Instruction) { 474 | let ea = get_ea_x(ctx, instr); 475 | 476 | let val = ctx.cpu.fpr[instr.s()].ps0(); 477 | 478 | ctx.write_u32(ea, convert_to_single(val)); 479 | } 480 | 481 | pub fn op_sth(ctx: &mut Context, instr: Instruction) { 482 | ctx.write_u16(get_ea(ctx, instr), ctx.cpu.gpr[instr.s()] as u16); 483 | 484 | ctx.tick(2); 485 | } 486 | 487 | pub fn op_sthbrx(_ctx: &mut Context, _instr: Instruction) { 488 | unimplemented!("op_sthbrx"); 489 | } 490 | 491 | pub fn op_sthu(ctx: &mut Context, instr: Instruction) { 492 | let ea = get_ea_u(ctx, instr); 493 | 494 | ctx.write_u16(ea, ctx.cpu.gpr[instr.s()] as u16); 495 | 496 | ctx.cpu.gpr[instr.a()] = ea; 497 | 498 | ctx.tick(2); 499 | } 500 | 501 | pub fn op_sthux(_ctx: &mut Context, _instr: Instruction) { 502 | unimplemented!("op_sthux"); 503 | } 504 | 505 | pub fn op_sthx(ctx: &mut Context, instr: Instruction) { 506 | ctx.write_u16(get_ea_x(ctx, instr), ctx.cpu.gpr[instr.s()] as u16); 507 | 508 | ctx.tick(2); 509 | } 510 | 511 | // FIXME: handle alignment interrupt if ea is not multiple of 4 512 | pub fn op_stmw(ctx: &mut Context, instr: Instruction) { 513 | let mut ea = get_ea(ctx, instr); 514 | let mut r = instr.s(); 515 | let n = (32 - r) as u32; 516 | 517 | while r < 32 { 518 | ctx.write_u32(ea, ctx.cpu.gpr[r]); 519 | 520 | r += 1; 521 | ea += 4; 522 | } 523 | 524 | ctx.tick(2 + n); 525 | } 526 | 527 | pub fn op_stswi(_ctx: &mut Context, _instr: Instruction) { 528 | unimplemented!("op_stswi"); 529 | } 530 | 531 | pub fn op_stswx(_ctx: &mut Context, _instr: Instruction) { 532 | unimplemented!("op_stswx"); 533 | } 534 | 535 | pub fn op_stw(ctx: &mut Context, instr: Instruction) { 536 | //if ctx.cpu.cia == 0x8130_04c4 { 537 | // TODO: remove this at some point 538 | // set console type to latest Devkit HW, which results in OSReport output going to UART 539 | // ctx.write_u32(get_ea(ctx, instr), 0x1000_0006); 540 | //} else { 541 | ctx.write_u32(get_ea(ctx, instr), ctx.cpu.gpr[instr.s()]); 542 | //} 543 | 544 | ctx.tick(2); 545 | } 546 | 547 | pub fn op_stwbrx(_ctx: &mut Context, _instr: Instruction) { 548 | unimplemented!("op_stwbrx"); 549 | } 550 | 551 | pub fn op_stwcx_rc(_ctx: &mut Context, _instr: Instruction) { 552 | unimplemented!("op_stwcx_rc"); 553 | } 554 | 555 | pub fn op_stwu(ctx: &mut Context, instr: Instruction) { 556 | if instr.a() == 0 { 557 | panic!("stwu: invalid instruction"); 558 | } 559 | 560 | let ea = get_ea_u(ctx, instr); 561 | 562 | ctx.write_u32(ea, ctx.cpu.gpr[instr.s()]); 563 | 564 | ctx.cpu.gpr[instr.a()] = ea; 565 | 566 | ctx.tick(2); 567 | } 568 | 569 | pub fn op_stwux(ctx: &mut Context, instr: Instruction) { 570 | if instr.a() == 0 { 571 | panic!("stwu: invalid instruction"); 572 | } 573 | 574 | let ea = get_ea_ux(ctx, instr); 575 | 576 | ctx.write_u32(ea, ctx.cpu.gpr[instr.s()]); 577 | 578 | ctx.cpu.gpr[instr.a()] = ea; 579 | 580 | ctx.tick(2); 581 | } 582 | 583 | pub fn op_stwx(ctx: &mut Context, instr: Instruction) { 584 | let ea = get_ea_x(ctx, instr); 585 | 586 | ctx.write_u32(ea, ctx.cpu.gpr[instr.s()]); 587 | 588 | ctx.tick(2); 589 | } 590 | 591 | pub fn op_tlbie(_ctx: &mut Context, _instr: Instruction) { 592 | unimplemented!("op_tlbie"); 593 | } 594 | 595 | #[cfg(test)] 596 | mod tests { 597 | use super::*; 598 | // load and store ops 599 | #[test] 600 | fn op_dcbf() { 601 | let mut ctx = Context::default(); 602 | 603 | let (ra, rb) = (4, 3); 604 | let instr = Instruction::new_dcbf(ra, rb); 605 | 606 | super::op_dcbf(&mut ctx, instr); 607 | } 608 | } 609 | -------------------------------------------------------------------------------- /src/cpu/ops/system.rs: -------------------------------------------------------------------------------- 1 | use crate::cpu::instruction::Instruction; 2 | use crate::cpu::spr::*; 3 | use crate::cpu::{EXCEPTION_PROGRAM, EXCEPTION_SYSTEM_CALL}; 4 | use crate::Context; 5 | 6 | pub fn op_eieio(_ctx: &mut Context, _instr: Instruction) { 7 | unimplemented!("op_eieio"); 8 | } 9 | 10 | pub fn op_isync(ctx: &mut Context, _instr: Instruction) { 11 | // don't do anything 12 | 13 | ctx.tick(2); 14 | } 15 | 16 | pub fn op_mfmsr(ctx: &mut Context, instr: Instruction) { 17 | if ctx.cpu.msr.pr() { 18 | panic!("privelege level"); 19 | } 20 | 21 | ctx.cpu.gpr[instr.d()] = ctx.cpu.msr.0; 22 | 23 | // TODO: check privilege level 24 | 25 | ctx.tick(1); 26 | } 27 | 28 | pub fn op_mfspr(ctx: &mut Context, instr: Instruction) { 29 | let i = instr.spr(); 30 | 31 | ctx.cpu.gpr[instr.s()] = ctx.cpu.spr[i]; 32 | 33 | match i { 34 | SPR_XER => ctx.cpu.gpr[instr.s()] = ctx.cpu.xer.into(), 35 | SPR_TBL => unimplemented!(), 36 | SPR_TBU => unimplemented!(), 37 | _ => (), 38 | } 39 | 40 | // TODO: check privilege level 41 | if (SPR_IBAT0U..=SPR_DBAT3L).contains(&i) { 42 | ctx.tick(3); 43 | } else { 44 | ctx.tick(1); 45 | } 46 | } 47 | 48 | pub fn op_mfsr(_ctx: &mut Context, _instr: Instruction) { 49 | unimplemented!("op_mfsr"); 50 | } 51 | 52 | pub fn op_mfsrin(_ctx: &mut Context, _instr: Instruction) { 53 | unimplemented!("op_mfsrin"); 54 | } 55 | 56 | pub fn op_mftb(ctx: &mut Context, instr: Instruction) { 57 | let timebase = ctx.timers.get_timebase(); 58 | 59 | ctx.cpu.spr[SPR_TBL] = (timebase & 0xFFFF_FFFF) as u32; 60 | ctx.cpu.spr[SPR_TBU] = (timebase >> 32) as u32; 61 | 62 | if instr.tbr() == TBR_TBL { 63 | ctx.cpu.gpr[instr.d()] = ctx.cpu.spr[SPR_TBL]; 64 | } else if instr.tbr() == TBR_TBU { 65 | ctx.cpu.gpr[instr.d()] = ctx.cpu.spr[SPR_TBU]; 66 | } else { 67 | panic!("mftb unknown tbr {:#x}", instr.tbr()); 68 | } 69 | 70 | ctx.tick(1); 71 | } 72 | 73 | pub fn op_mtmsr(ctx: &mut Context, instr: Instruction) { 74 | ctx.cpu.msr = ctx.cpu.gpr[instr.s()].into(); 75 | 76 | if ctx.cpu.msr.pr() { 77 | panic!("privelege level"); 78 | } 79 | 80 | // TODO: check privilege level 81 | 82 | ctx.tick(1); 83 | } 84 | 85 | pub fn op_mtspr(ctx: &mut Context, instr: Instruction) { 86 | let i = instr.spr(); 87 | let v = ctx.cpu.gpr[instr.s()]; 88 | 89 | ctx.cpu.spr[i] = v; 90 | 91 | match i { 92 | SPR_XER => ctx.cpu.xer = v.into(), 93 | _ => { 94 | if ctx.cpu.msr.pr() { 95 | // TODO: properly handle this case 96 | ctx.cpu.exceptions |= EXCEPTION_PROGRAM; 97 | panic!("mtspr: user privilege level prevents setting spr {i:#?}"); 98 | } 99 | 100 | match i { 101 | SPR_IBAT0U => ctx.cpu.mmu.write_ibatu(0, v), 102 | SPR_IBAT0L => ctx.cpu.mmu.write_ibatl(0, v), 103 | SPR_IBAT1U => ctx.cpu.mmu.write_ibatu(1, v), 104 | SPR_IBAT1L => ctx.cpu.mmu.write_ibatl(1, v), 105 | SPR_IBAT2U => ctx.cpu.mmu.write_ibatu(2, v), 106 | SPR_IBAT2L => ctx.cpu.mmu.write_ibatl(2, v), 107 | SPR_IBAT3U => ctx.cpu.mmu.write_ibatu(3, v), 108 | SPR_IBAT3L => ctx.cpu.mmu.write_ibatl(3, v), 109 | SPR_DBAT0U => ctx.cpu.mmu.write_dbatu(0, v), 110 | SPR_DBAT0L => ctx.cpu.mmu.write_dbatl(0, v), 111 | SPR_DBAT1U => ctx.cpu.mmu.write_dbatu(1, v), 112 | SPR_DBAT1L => ctx.cpu.mmu.write_dbatl(1, v), 113 | SPR_DBAT2U => ctx.cpu.mmu.write_dbatu(2, v), 114 | SPR_DBAT2L => ctx.cpu.mmu.write_dbatl(2, v), 115 | SPR_DBAT3U => ctx.cpu.mmu.write_dbatu(3, v), 116 | SPR_DBAT3L => ctx.cpu.mmu.write_dbatl(3, v), 117 | SPR_DEC => unimplemented!("Software Triggered Decrementer"), 118 | SPR_HID2 => ctx.cpu.hid2 = v.into(), 119 | SPR_TBL => ctx.timers.set_timebase_lower(v), 120 | SPR_TBU => ctx.timers.set_timebase_upper(v), 121 | SPR_WPAR => { 122 | ctx.cpu.spr[i] &= !0x1F; 123 | info!("WPAR set to {:#x}", ctx.cpu.spr[i]); 124 | //ctx.gp_fifo.reset(); 125 | } 126 | _ => {} 127 | } 128 | } 129 | } 130 | 131 | ctx.tick(2); 132 | } 133 | 134 | pub fn op_mtsr(ctx: &mut Context, instr: Instruction) { 135 | ctx.cpu.sr[instr.sr()] = ctx.cpu.gpr[instr.s()]; 136 | 137 | // TODO: check privilege level -> supervisor level instruction 138 | 139 | ctx.tick(2); 140 | } 141 | 142 | pub fn op_mtsrin(ctx: &mut Context, instr: Instruction) { 143 | let v = ctx.cpu.gpr[instr.s()]; 144 | let i = (ctx.cpu.gpr[instr.b()] >> 28) as usize; 145 | 146 | ctx.cpu.sr[i] = v; 147 | 148 | ctx.tick(2); 149 | } 150 | 151 | pub fn op_rfi(ctx: &mut Context, _instr: Instruction) { 152 | let mask = 0x87C0_FF73; 153 | 154 | ctx.cpu.msr.0 = (ctx.cpu.msr.0 & !mask) | (ctx.cpu.spr[SPR_SRR1] & mask); 155 | 156 | ctx.cpu.msr.0 &= 0xFFFB_FFFF; 157 | 158 | ctx.cpu.nia = ctx.cpu.spr[SPR_SRR0] & 0xFFFF_FFFC; 159 | 160 | ctx.tick(2); 161 | } 162 | 163 | pub fn op_sc(ctx: &mut Context, _instr: Instruction) { 164 | ctx.cpu.exceptions |= EXCEPTION_SYSTEM_CALL; 165 | 166 | ctx.tick(2); 167 | } 168 | 169 | pub fn op_sync(ctx: &mut Context, _instr: Instruction) { 170 | // don't do anything 171 | 172 | ctx.tick(3); 173 | } 174 | 175 | pub fn op_tlbsync(_ctx: &mut Context, _instr: Instruction) { 176 | unimplemented!("op_tlbsync"); 177 | } 178 | 179 | #[cfg(test)] 180 | mod tests { 181 | use super::*; 182 | 183 | #[test] 184 | fn op_eieio() {} 185 | 186 | #[test] 187 | fn op_isync() {} 188 | 189 | #[test] 190 | fn op_mfmsr() { 191 | let mut ctx = Context::default(); 192 | 193 | let rd = 6; 194 | let instr = Instruction::new_mfmsr(rd); 195 | 196 | ctx.cpu.msr = 0x0D15_AA5E.into(); 197 | 198 | super::op_mfmsr(&mut ctx, instr); 199 | 200 | assert_eq!(ctx.cpu.gpr[rd], 0x0D15_AA5E); 201 | } 202 | 203 | #[test] 204 | fn op_mfspr() { 205 | let mut ctx = Context::default(); 206 | 207 | let (rd, spr) = (6, SPR_LR as u32); // FIXME: make spr a usize 208 | let instr = Instruction::new_mfspr(rd, spr); 209 | 210 | ctx.cpu.spr[SPR_LR] = 0xDEAD_BEEF; 211 | super::op_mfspr(&mut ctx, instr); 212 | 213 | assert_eq!(ctx.cpu.gpr[rd], 0xDEAD_BEEF); 214 | } 215 | 216 | #[test] 217 | fn op_mfsr() {} 218 | 219 | #[test] 220 | fn op_mfsrin() {} 221 | 222 | #[test] 223 | fn op_mftb() { 224 | let mut ctx = Context::default(); 225 | 226 | let (rd, tbr) = (6, TBR_TBL); // FIXME: make tbr usize 227 | let instr = Instruction::new_mftb(rd, tbr as u32); 228 | 229 | ctx.timers.tick(0x1784); 230 | super::op_mftb(&mut ctx, instr); 231 | 232 | assert_eq!(ctx.cpu.gpr[rd], 501); // FIXME: this needs to be better 233 | } 234 | 235 | #[test] 236 | fn op_mtmsr() { 237 | let mut ctx = Context::default(); 238 | 239 | let rs = 6; 240 | let instr = Instruction::new_mtmsr(rs); 241 | 242 | ctx.cpu.gpr[rs] = 0x0D15_AA5E; 243 | 244 | super::op_mtmsr(&mut ctx, instr); 245 | 246 | assert_eq!(ctx.cpu.msr.0, 0x0D15_AA5E); 247 | } 248 | 249 | #[test] 250 | fn op_mtspr() {} 251 | 252 | #[test] 253 | fn op_mtsrin() {} 254 | 255 | #[test] 256 | fn op_rfi() {} 257 | 258 | #[test] 259 | fn op_sc() { 260 | let mut ctx = Context::default(); 261 | 262 | let instr = Instruction::new_sc(); 263 | 264 | super::op_sc(&mut ctx, instr); 265 | 266 | assert_eq!(ctx.cpu.exceptions, EXCEPTION_SYSTEM_CALL); 267 | } 268 | 269 | #[test] 270 | fn op_sync() { 271 | let mut ctx = Context::default(); 272 | 273 | let instr = Instruction::new_sync(); 274 | 275 | super::op_sync(&mut ctx, instr); 276 | } 277 | 278 | #[test] 279 | #[should_panic] 280 | fn op_tlbsync() { 281 | let mut ctx = Context::default(); 282 | 283 | let instr = Instruction::new_tlbsync(); 284 | 285 | super::op_tlbsync(&mut ctx, instr); 286 | } 287 | } 288 | -------------------------------------------------------------------------------- /src/cpu/spr.rs: -------------------------------------------------------------------------------- 1 | pub const SPR_XER: usize = 1; 2 | pub const SPR_LR: usize = 8; 3 | pub const SPR_CTR: usize = 9; 4 | pub const SPR_DSISR: usize = 18; 5 | pub const SPR_DAR: usize = 19; 6 | pub const SPR_DEC: usize = 22; 7 | pub const SPR_SDR1: usize = 25; 8 | pub const SPR_SRR0: usize = 26; 9 | pub const SPR_SRR1: usize = 27; 10 | pub const SPR_SPRG0: usize = 272; 11 | pub const SPR_EAR: usize = 282; 12 | pub const SPR_TBL: usize = 284; 13 | pub const SPR_TBU: usize = 285; 14 | pub const SPR_PVR: usize = 287; 15 | pub const SPR_IBAT0U: usize = 528; 16 | pub const SPR_IBAT0L: usize = 529; 17 | pub const SPR_IBAT1U: usize = 530; 18 | pub const SPR_IBAT1L: usize = 531; 19 | pub const SPR_IBAT2U: usize = 532; 20 | pub const SPR_IBAT2L: usize = 533; 21 | pub const SPR_IBAT3U: usize = 534; 22 | pub const SPR_IBAT3L: usize = 535; 23 | pub const SPR_DBAT0U: usize = 536; 24 | pub const SPR_DBAT0L: usize = 537; 25 | pub const SPR_DBAT1U: usize = 538; 26 | pub const SPR_DBAT1L: usize = 539; 27 | pub const SPR_DBAT2U: usize = 540; 28 | pub const SPR_DBAT2L: usize = 541; 29 | pub const SPR_DBAT3U: usize = 542; 30 | pub const SPR_DBAT3L: usize = 543; 31 | pub const SPR_GQR0: usize = 912; 32 | pub const SPR_HID2: usize = 920; 33 | pub const SPR_WPAR: usize = 921; 34 | pub const SPR_DMAU: usize = 922; 35 | pub const SPR_UMMCR0: usize = 936; 36 | pub const SPR_UPMC1: usize = 937; 37 | pub const SPR_UPMC2: usize = 938; 38 | pub const SPR_USIA: usize = 939; 39 | pub const SPR_UMMCR1: usize = 940; 40 | pub const SPR_UPMC3: usize = 941; 41 | pub const SPR_UPMC4: usize = 942; 42 | pub const SPR_MMCR0: usize = 952; 43 | pub const SPR_PMC1: usize = 953; 44 | pub const SPR_PMC2: usize = 954; 45 | pub const SPR_SIA: usize = 955; 46 | pub const SPR_MMCR1: usize = 956; 47 | pub const SPR_PMC3: usize = 957; 48 | pub const SPR_PMC4: usize = 958; 49 | pub const SPR_IABR: usize = 1010; 50 | pub const SPR_HID0: usize = 1008; 51 | pub const SPR_HID1: usize = 1009; 52 | pub const SPR_DABR: usize = 1013; 53 | pub const SPR_L2CR: usize = 1017; 54 | pub const SPR_ICTC: usize = 1019; 55 | pub const SPR_THRM1: usize = 1020; 56 | 57 | pub const TBR_TBL: usize = 268; 58 | pub const TBR_TBU: usize = 269; 59 | -------------------------------------------------------------------------------- /src/cpu/util.rs: -------------------------------------------------------------------------------- 1 | pub fn bon(bo: u8, n: u8) -> u8 { 2 | (bo >> (4 - n)) & 1 3 | } 4 | 5 | pub fn convert_to_double(v: u32) -> u64 { 6 | let x = v as u64; 7 | let mut exp = (x >> 23) & 0xFF; 8 | let mut frac = x & 0x007F_FFFF; 9 | 10 | // Normalize Operand 11 | if exp > 0 && exp < 255 { 12 | let y = (exp >> 7) ^ 0x1; 13 | let z = (y << 61) | (y << 60) | (y << 59); 14 | ((x & 0xC000_0000) << 32) | z | ((x & 0x3FFF_FFFF) << 29) 15 | // Denormalize Operand 16 | } else if exp == 0 && frac != 0 { 17 | exp = 1023 - 126; 18 | while (frac & 0x0080_0000) == 0 { 19 | frac <<= 1; 20 | exp -= 1; 21 | } 22 | 23 | ((x & 0x8000_0000) << 32) | (exp << 52) | ((frac & 0x007F_FFFF) << 29) 24 | // Infinity / QNaN / SNaN / Zero 25 | } else { 26 | let y = exp >> 7; 27 | let z = (y << 61) | (y << 60) | (y << 59); 28 | ((x & 0xC000_0000) << 32) | z | ((x & 0x3FFF_FFFF) << 29) 29 | } 30 | } 31 | 32 | pub fn convert_to_single(x: u64) -> u32 { 33 | let exp64 = ((x >> 52) & 0x7FF) as u32; 34 | 35 | // No Denormalization (includes Zero/ Infinity / NaN) 36 | if exp64 > 896 || x & 0x7FFF_FFFF == 0 { 37 | (((x >> 32) as u32) & 0xC000_0000) | (((x >> 29) as u32) & 0x3FFF_FFFF) 38 | // Denormalization 39 | } else if exp64 >= 874 { 40 | // TODO: simplify ??? 41 | let mut exp = (exp64 as i16) - 1023; 42 | let mut frac = 0x8000_0000_0000_0000 | (x << 12); 43 | while exp < -126 { 44 | frac >>= 1; 45 | exp += 1; 46 | } 47 | (((x >> 32) & 0x8000_0000) | (frac >> 40)) as u32 48 | // Undefined 49 | } else { 50 | // According to dolphin, determined through hardware tests 51 | (((x >> 32) & 0xC000_0000) | ((x >> 29) & 0x3FFF_FFFF)) as u32 52 | } 53 | } 54 | 55 | pub fn sign_ext_12(x: u16) -> i32 { 56 | if x & 0x800 != 0 { 57 | i32::from(x | 0xF000) 58 | } else { 59 | i32::from(x) 60 | } 61 | } 62 | 63 | // Note: A cast from a signed value widens with signed-extension 64 | // A cast from an unsigned value widens with zero-extension 65 | pub fn sign_ext_16(x: u16) -> i32 { 66 | i32::from(x as i16) 67 | } 68 | 69 | pub fn sign_ext_26(x: u32) -> i32 { 70 | if x & 0x0200_0000 != 0 { 71 | (x | 0xFC00_0000) as i32 72 | } else { 73 | x as i32 74 | } 75 | } 76 | 77 | // TODO: Potential performance improvement by placing all mask combinations in array 78 | pub fn mask(mb: u8, me: u8) -> u32 { 79 | let mut mask: u32 = 0xFFFF_FFFF >> mb; 80 | 81 | if me >= 31 { 82 | mask ^= 0; 83 | } else { 84 | mask ^= 0xFFFF_FFFF >> (me + 1) 85 | }; 86 | 87 | if me < mb { 88 | !mask 89 | } else { 90 | mask 91 | } 92 | } 93 | 94 | /// Helper to check if operation results in an overflow. This is determined by checking if both 95 | /// operands signs bits are the same but the results sign bit is different. 96 | /// 97 | /// Note: Overflow flag is only relavent to signed arithmetic 98 | pub fn check_overflowed(a: u32, b: u32, result: u32) -> bool { 99 | ((a ^ result) & (b ^ result)) >> 31 != 0 100 | } 101 | 102 | #[cfg(test)] 103 | mod tests { 104 | 105 | // TODO: expand on these test cases 106 | #[test] 107 | fn convert_to_double() { 108 | let test_values: [(f32, f64); 3] = [ 109 | (0.0, 0.0), 110 | (1.0, 1.0), 111 | (1.1754942e-38, 1.1754942106924411e-38), 112 | ]; 113 | 114 | for t in test_values.iter() { 115 | let result = f64::from_bits(super::convert_to_double(f32::to_bits(t.0))); 116 | 117 | assert_eq!(result, t.1); 118 | } 119 | } 120 | 121 | // TODO: expand on these test cases 122 | #[test] 123 | fn convert_to_single() { 124 | let test_values: [(f64, f32); 4] = [ 125 | (0.0, 0.0), 126 | (1.0, 1.0), 127 | (4.484155085839414e-44, 4.3e-44), 128 | (1.4693679385492415e-39, 1.469368e-39), 129 | ]; 130 | 131 | for t in test_values.iter() { 132 | let result = f32::from_bits(super::convert_to_single(f64::to_bits(t.0))); 133 | 134 | assert_eq!(result, t.1); 135 | } 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /src/di.rs: -------------------------------------------------------------------------------- 1 | use crate::pi::{clear_interrupt, set_interrupt, PI_INTERRUPT_DI}; 2 | use crate::Context; 3 | use crate::Disc; 4 | 5 | const STATUS: u32 = 0x00; 6 | const COVER_STATUS: u32 = 0x04; 7 | const DICMDBUF0: u32 = 0x08; 8 | const DICMDBUF1: u32 = 0x0C; 9 | const DICMDBUF2: u32 = 0x10; 10 | const DIMAR: u32 = 0x14; 11 | const DILENGTH: u32 = 0x18; 12 | const DICR: u32 = 0x1C; 13 | //const DIIMMBUF: u32 = 0x20; 14 | const DICFG: u32 = 0x24; 15 | 16 | const DI_COMMAND_INQUIRY: u8 = 0x12; 17 | //const DI_COMMAND_READ: u8 = 0xA8; 18 | //const DI_COMMAND_SEEK: u8 = 0xAB; 19 | 20 | #[derive(Default)] 21 | pub struct DvdInterface { 22 | status: StatusRegister, 23 | cover_status: CoverStatusRegister, 24 | command_buff_0: u32, 25 | command_buff_1: u32, 26 | command_buff_2: u32, 27 | dma_address: u32, 28 | dma_transfer_length: u32, 29 | control: ControlRegister, 30 | config: u32, 31 | disc: Option, 32 | } 33 | 34 | impl DvdInterface { 35 | pub fn set_disc(&mut self, disc: Option) { 36 | self.disc = disc; 37 | } 38 | } 39 | 40 | bitfield! { 41 | #[derive(Copy, Clone, Default)] 42 | pub struct StatusRegister(u32); 43 | impl Debug; 44 | pub di_break, _ : 0; 45 | pub device_int_mask, _ : 1; 46 | pub device_int, _ : 2; 47 | pub transfer_int_mask, _ : 3; 48 | pub transfer_int, _ : 4; 49 | pub break_int_mask, _ : 5; 50 | pub break_int, _ : 6; 51 | } 52 | 53 | impl From for StatusRegister { 54 | fn from(v: u32) -> Self { 55 | StatusRegister(v) 56 | } 57 | } 58 | 59 | impl From for u32 { 60 | fn from(s: StatusRegister) -> u32 { 61 | s.0 62 | } 63 | } 64 | 65 | bitfield! { 66 | #[derive(Copy, Clone, Default)] 67 | pub struct CoverStatusRegister(u32); 68 | impl Debug; 69 | pub cover, _ : 0; 70 | pub cover_int_mask, set_cover_int_mask : 1; 71 | pub cover_int, set_cover_int : 2; 72 | } 73 | 74 | impl From for CoverStatusRegister { 75 | fn from(v: u32) -> Self { 76 | CoverStatusRegister(v) 77 | } 78 | } 79 | 80 | impl From for u32 { 81 | fn from(s: CoverStatusRegister) -> u32 { 82 | s.0 83 | } 84 | } 85 | 86 | bitfield! { 87 | #[derive(Copy, Clone, Default)] 88 | pub struct ControlRegister(u32); 89 | impl Debug; 90 | pub tstart, set_tstart : 0; 91 | pub dma, _ : 1; 92 | pub rw, _ : 2; 93 | } 94 | 95 | impl From for ControlRegister { 96 | fn from(v: u32) -> Self { 97 | ControlRegister(v) 98 | } 99 | } 100 | 101 | pub fn read_u32(ctx: &mut Context, register: u32) -> u32 { 102 | match register { 103 | COVER_STATUS => ctx.di.cover_status.into(), 104 | DICFG => ctx.di.config, 105 | _ => { 106 | warn!("read_u32 unrecognized di register {:#x}", register); 107 | 0 108 | } 109 | } 110 | } 111 | 112 | pub fn write_u32(ctx: &mut Context, register: u32, val: u32) { 113 | match register { 114 | STATUS => { 115 | ctx.di.status = val.into(); 116 | update_interrupts(ctx); 117 | } 118 | COVER_STATUS => { 119 | let tmp: CoverStatusRegister = val.into(); 120 | 121 | ctx.di.cover_status.set_cover_int_mask(tmp.cover_int_mask()); 122 | 123 | if tmp.cover_int() { 124 | ctx.di.cover_status.set_cover_int(false); 125 | } 126 | 127 | ctx.di.cover_status = val.into(); 128 | update_interrupts(ctx); 129 | } 130 | DICMDBUF0 => ctx.di.command_buff_0 = val, 131 | DICMDBUF1 => ctx.di.command_buff_1 = val, 132 | DICMDBUF2 => ctx.di.command_buff_2 = val, 133 | DIMAR => ctx.di.dma_address = val, 134 | DILENGTH => ctx.di.dma_transfer_length = val, 135 | DICR => { 136 | ctx.di.control = val.into(); 137 | if ctx.di.control.tstart() { 138 | // Execute Command 139 | match (ctx.di.command_buff_0 >> 24) as u8 { 140 | DI_COMMAND_INQUIRY => (), // Not sure what happens here 141 | //DI_COMMAND_READ => (), 142 | //DI_COMMAND_SEEK => (), 143 | _ => warn!("Unrecognized command {:#x}", ctx.di.command_buff_0), 144 | } 145 | } 146 | 147 | ctx.di.control.set_tstart(false); 148 | } 149 | _ => warn!("write_u32 unrecognized di register {:#x}", register), 150 | } 151 | } 152 | 153 | fn update_interrupts(ctx: &mut Context) { 154 | if ctx.di.status.device_int() && ctx.di.status.device_int_mask() 155 | || ctx.di.status.transfer_int() && ctx.di.status.transfer_int_mask() 156 | || ctx.di.status.break_int() && ctx.di.status.break_int_mask() 157 | || ctx.di.cover_status.cover_int() && ctx.di.cover_status.cover_int_mask() 158 | { 159 | set_interrupt(ctx, PI_INTERRUPT_DI); 160 | } else { 161 | clear_interrupt(ctx, PI_INTERRUPT_DI); 162 | } 163 | } 164 | -------------------------------------------------------------------------------- /src/disc.rs: -------------------------------------------------------------------------------- 1 | use byteorder::{BigEndian, ByteOrder}; 2 | use std::fs; 3 | use std::io::prelude::*; 4 | use std::io::{Error, ErrorKind, Read, SeekFrom}; 5 | use std::path::Path; 6 | 7 | use crate::cpu::{spr::SPR_LR, step}; 8 | use crate::Context; 9 | 10 | const DISC_MAGIC: u32 = 0xC2339F3D; 11 | 12 | pub struct Disc { 13 | file: std::fs::File, 14 | } 15 | 16 | #[derive(Debug)] 17 | struct Header { 18 | game_code: u32, 19 | maker_code: u16, 20 | game_name: String, 21 | bootfile_offset: u32, 22 | fst_offset: u32, 23 | fst_size: u32, 24 | } 25 | 26 | impl Disc { 27 | pub fn open>(path: P) -> Result { 28 | let mut buff = [0; 0x440]; 29 | let mut file = fs::File::open(path)?; 30 | 31 | file.read_exact(&mut buff)?; 32 | 33 | let magic = BigEndian::read_u32(&buff[0x1C..]); 34 | 35 | if magic != DISC_MAGIC { 36 | return Err(Error::new( 37 | ErrorKind::InvalidData, 38 | "Not a valid gamecube image", 39 | )); 40 | } 41 | 42 | let game_code = BigEndian::read_u32(&buff[0x0..]); 43 | 44 | let maker_code = BigEndian::read_u16(&buff[0x04..]); 45 | 46 | let game_name = String::from_utf8_lossy(&buff[0x20..0x3FF]) 47 | .into_owned() 48 | .trim_matches(char::from(0)) 49 | .to_string(); 50 | let bootfile_offset = BigEndian::read_u32(&buff[0x420..]); 51 | let fst_offset = BigEndian::read_u32(&buff[0x424..]); 52 | let fst_size = BigEndian::read_u32(&buff[0x428..]); 53 | 54 | let header = Header { 55 | game_code, 56 | maker_code, 57 | game_name, 58 | bootfile_offset, 59 | fst_offset, 60 | fst_size, 61 | }; 62 | 63 | info!( 64 | "Reading Disc: game_code {:#x} | maker_code {:#x} | game_name {:} | bootfile_offset: {:#x} | fst_offset {:#x} | fst_size {:#x}", 65 | header.game_code, header.maker_code, header.game_name, header.bootfile_offset, header.fst_offset, header.fst_size 66 | ); 67 | 68 | Ok(Disc { file }) 69 | } 70 | 71 | /// Execute apploader 72 | pub fn load(&mut self, ctx: &mut Context) -> Result<(), Error> { 73 | // TODO: Write disk header information to 0x8000_00F4 74 | 75 | let mut buff = [0; 0x20]; 76 | 77 | self.file.seek(SeekFrom::Start(0x2440))?; 78 | 79 | self.file.read_exact(&mut buff)?; 80 | 81 | let apploader_date = String::from_utf8_lossy(&buff[0x00..0x09]) 82 | .into_owned() 83 | .trim_matches(char::from(0)) 84 | .to_string(); 85 | 86 | let apploader_entrypoint = BigEndian::read_u32(&buff[0x10..]); 87 | let apploader_size = BigEndian::read_u32(&buff[0x14..]); 88 | let trailer_size = BigEndian::read_u32(&buff[0x18..]); 89 | 90 | info!( 91 | "Apploader: date {:} | entrypoint {:#x} | size {:#x} | trailer_size: {:}", 92 | apploader_date, apploader_entrypoint, apploader_size, trailer_size 93 | ); 94 | 95 | let mut buff = vec![0; apploader_size as usize]; 96 | 97 | self.file.read_exact(buff.as_mut_slice())?; 98 | 99 | ctx.write(0x8120_0000, buff.as_slice()); 100 | 101 | let base_addr = 0x8130_0000; 102 | 103 | ctx.write_u32(base_addr, 0x4E80_0020); // Set dummy OSReport -> BLR 104 | 105 | ctx.cpu.set_pc(apploader_entrypoint); 106 | 107 | ctx.cpu.mut_gpr()[3] = base_addr + 0x4; // AplInit 108 | ctx.cpu.mut_gpr()[4] = base_addr + 0x8; // AplMain 109 | ctx.cpu.mut_gpr()[5] = base_addr + 0xC; // AplClose 110 | 111 | ctx.cpu.mut_spr()[SPR_LR] = 0; 112 | 113 | while ctx.cpu.pc() != 0 { 114 | step(ctx); 115 | } 116 | 117 | let apl_init = ctx.read_u32(base_addr + 0x4); 118 | let apl_main = ctx.read_u32(base_addr + 0x8); 119 | let apl_close = ctx.read_u32(base_addr + 0xC); 120 | 121 | info!( 122 | "Apploader: init {:#x} | main {:#x} | close {:#x}", 123 | apl_init, apl_main, apl_close 124 | ); 125 | 126 | // Execute AplInit 127 | ctx.cpu.set_pc(apl_init); 128 | ctx.cpu.mut_gpr()[3] = 0x8130_0000; // OSReport callback 129 | ctx.cpu.mut_spr()[SPR_LR] = 0; 130 | 131 | while ctx.cpu.pc() != 0 { 132 | step(ctx); 133 | } 134 | 135 | // Execute AplMain 136 | while ctx.cpu.gpr()[3] != 0 { 137 | ctx.cpu.mut_gpr()[3] = base_addr + 0x4; // AplInit 138 | ctx.cpu.mut_gpr()[4] = base_addr + 0x8; // AplMain 139 | ctx.cpu.mut_gpr()[5] = base_addr + 0xC; // AplClose 140 | 141 | ctx.cpu.mut_spr()[SPR_LR] = 0; 142 | ctx.cpu.set_pc(apl_main); 143 | while ctx.cpu.pc() != 0 { 144 | step(ctx); 145 | } 146 | 147 | let addr = ctx.read_u32(base_addr + 0x4); 148 | let size = ctx.read_u32(base_addr + 0x8) as usize; 149 | let offset = ctx.read_u32(base_addr + 0xC) as u64; 150 | 151 | if size > 0 { 152 | let mut buff = vec![0; size]; 153 | 154 | self.file.seek(SeekFrom::Start(offset))?; 155 | 156 | self.file.read_exact(&mut buff)?; 157 | 158 | ctx.write(addr, buff.as_slice()); 159 | 160 | info!( 161 | "Apploader Transfer: destAddr {:#x} | size {:#x} | offset {:#x}", 162 | addr, size, offset 163 | ); 164 | } 165 | } 166 | 167 | // Execute AplClose 168 | ctx.cpu.mut_spr()[SPR_LR] = 0; 169 | ctx.cpu.set_pc(apl_close); 170 | while ctx.cpu.pc() != 0 { 171 | step(ctx); 172 | } 173 | 174 | ctx.cpu.set_pc(ctx.cpu.gpr()[3]); 175 | 176 | Ok(()) 177 | } 178 | } 179 | -------------------------------------------------------------------------------- /src/dol.rs: -------------------------------------------------------------------------------- 1 | use byteorder::{BigEndian, ByteOrder}; 2 | use std::fs; 3 | use std::io::prelude::*; 4 | use std::io::{Error, Read, SeekFrom}; 5 | use std::path::Path; 6 | 7 | use super::Context; 8 | 9 | const NUM_TEXT: usize = 7; 10 | const NUM_DATA: usize = 11; 11 | 12 | #[derive(Default)] 13 | struct Header { 14 | //text_offset: [u32; NUM_TEXT], 15 | //data_offset: [u32; NUM_DATA], 16 | text_address: [u32; NUM_TEXT], 17 | data_address: [u32; NUM_DATA], 18 | //text_size: [u32; NUM_TEXT], 19 | //data_size: [u32; NUM_DATA], 20 | //bss_address: u32, 21 | //bss_size: u32, 22 | entry_point: u32, 23 | } 24 | 25 | pub struct Dol { 26 | header: Header, 27 | text_sections: Vec>, 28 | data_sections: Vec>, 29 | } 30 | 31 | impl Dol { 32 | pub fn open>(path: P) -> Result { 33 | let mut buff = [0; 0xE4]; 34 | let mut file = fs::File::open(path)?; 35 | 36 | file.read_exact(&mut buff)?; 37 | 38 | let mut offset; 39 | let mut text_offset = [0; NUM_TEXT]; 40 | let mut text_address = [0; NUM_TEXT]; 41 | let mut text_size = [0; NUM_TEXT]; 42 | let mut data_offset = [0; NUM_DATA]; 43 | let mut data_address = [0; NUM_DATA]; 44 | let mut data_size = [0; NUM_DATA]; 45 | let mut text_sections = Vec::with_capacity(NUM_TEXT); 46 | let mut data_sections = Vec::with_capacity(NUM_DATA); 47 | 48 | for x in 0..NUM_TEXT { 49 | offset = x * 4; 50 | text_offset[x] = BigEndian::read_u32(&buff[offset..]); 51 | text_address[x] = BigEndian::read_u32(&buff[0x48 + offset..]); 52 | text_size[x] = BigEndian::read_u32(&buff[0x90 + offset..]); 53 | 54 | if text_offset[x] > 0 { 55 | let mut section = vec![0; text_size[x] as usize]; 56 | file.seek(SeekFrom::Start(u64::from(text_offset[x])))?; 57 | file.read_exact(section.as_mut_slice())?; 58 | text_sections.push(section); 59 | } else { 60 | break; 61 | } 62 | } 63 | 64 | for x in 0..NUM_DATA { 65 | offset = x * 4; 66 | data_offset[x] = BigEndian::read_u32(&buff[0x1C + offset..]); 67 | data_address[x] = BigEndian::read_u32(&buff[0x64 + offset..]); 68 | data_size[x] = BigEndian::read_u32(&buff[0xAC + offset..]); 69 | 70 | if data_offset[x] > 0 { 71 | let mut section = vec![0; data_size[x] as usize]; 72 | file.seek(SeekFrom::Start(u64::from(data_offset[x])))?; 73 | file.read_exact(section.as_mut_slice())?; 74 | data_sections.push(section); 75 | } else { 76 | break; 77 | } 78 | } 79 | 80 | let header = Header { 81 | //text_offset, 82 | //data_offset, 83 | text_address, 84 | data_address, 85 | //text_size, 86 | //data_size, 87 | //bss_address: BigEndian::read_u32(&buff[0xD8..]), 88 | //bss_size: BigEndian::read_u32(&buff[0xDC..]), 89 | entry_point: BigEndian::read_u32(&buff[0xE0..]), 90 | }; 91 | 92 | Ok(Dol { 93 | header, 94 | text_sections, 95 | data_sections, 96 | }) 97 | } 98 | 99 | pub fn get_entry_point(&self) -> u32 { 100 | self.header.entry_point 101 | } 102 | 103 | pub fn load(&self, ctx: &mut Context) { 104 | for (x, section) in self.text_sections.iter().enumerate() { 105 | ctx.write(self.header.text_address[x], section.as_slice()); 106 | } 107 | 108 | for (x, section) in self.data_sections.iter().enumerate() { 109 | ctx.write(self.header.data_address[x], section.as_slice()); 110 | } 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /src/exi.rs: -------------------------------------------------------------------------------- 1 | use std::cell::RefCell; 2 | use std::rc::Rc; 3 | 4 | use crate::{Context, Memory, BOOTROM_SIZE}; 5 | 6 | const STATUS: u32 = 0x00; 7 | const DMA_ADDRESS: u32 = 0x04; 8 | const DMA_LENGTH: u32 = 0x08; 9 | const DMA_CONTROL: u32 = 0x0C; 10 | const IMM_DATA: u32 = 0x10; 11 | const NUM_CHANNELS: usize = 3; 12 | const NUM_DEVICES: usize = 3; 13 | 14 | const TRANSFER_TYPE_READ: u32 = 0; 15 | const TRANSFER_TYPE_WRITE: u32 = 1; 16 | //const TRANSFER_TYPE_RW: u32 = 2; 17 | 18 | const AD16_ID: u32 = 0x04120000; 19 | 20 | const AD16_COMMAND_INIT: u8 = 0x00; 21 | const AD16_COMMAND_READ: u8 = 0xa2; 22 | const AD16_COMMAND_WRITE: u8 = 0xa0; 23 | 24 | pub struct ExternalInterface { 25 | status: [StatusRegister; NUM_CHANNELS], 26 | control: [ControlRegister; NUM_CHANNELS], 27 | dma_address: [u32; NUM_CHANNELS], 28 | dma_length: [u32; NUM_CHANNELS], 29 | imm_data: [u32; NUM_CHANNELS], 30 | devices: [Option>; NUM_CHANNELS * NUM_DEVICES], 31 | } 32 | 33 | impl ExternalInterface { 34 | pub fn new(bootrom: Rc>>) -> Self { 35 | let mut exi = ExternalInterface { 36 | status: Default::default(), 37 | control: Default::default(), 38 | dma_address: Default::default(), 39 | dma_length: Default::default(), 40 | imm_data: Default::default(), 41 | devices: Default::default(), 42 | }; 43 | 44 | let device_ad16 = DeviceAd16::default(); 45 | 46 | exi.devices[1] = Some(Box::new(DeviceIpl::new(bootrom))); 47 | exi.devices[2 * NUM_CHANNELS] = Some(Box::new(device_ad16)); 48 | 49 | exi 50 | } 51 | } 52 | 53 | bitfield! { 54 | #[derive(Copy, Clone, Default)] 55 | pub struct StatusRegister(u32); 56 | impl Debug; 57 | pub exi_interrupt_mask, set_exi_interrupt_mask : 0; 58 | pub exi_interrupt_status, _ : 1; 59 | pub tc_interrupt_mask, set_tc_interrupt_mask : 2; 60 | pub tc_interrupt, _ : 3; 61 | pub clock_frequency, set_clock_frequency : 6, 4; 62 | pub device_select, set_device_select : 9, 7; 63 | pub ext_interrupt_mask, _ : 10; 64 | pub ext_insertion_interrupt_status, _ : 11; 65 | pub device_connected, _ : 12; 66 | pub rom_descramble, set_rom_descramble : 13; 67 | } 68 | 69 | impl From for StatusRegister { 70 | fn from(v: u32) -> Self { 71 | StatusRegister(v) 72 | } 73 | } 74 | 75 | impl From for u32 { 76 | fn from(s: StatusRegister) -> u32 { 77 | s.0 78 | } 79 | } 80 | 81 | impl StatusRegister { 82 | fn get_selected_device(&self) -> u8 { 83 | match self.device_select() { 84 | 1 => 0, 85 | 2 => 1, 86 | 4 => 2, 87 | _ => 0, // FixMe: handle this case properly instead of default to 0 88 | } 89 | } 90 | } 91 | 92 | bitfield! { 93 | #[derive(Copy, Clone, Default)] 94 | pub struct ControlRegister(u32); 95 | impl Debug; 96 | pub transfer_start, set_transfer_start : 0; 97 | pub transfer_mode, _ : 1; 98 | pub transfer_type, _ : 3, 2; 99 | pub transfer_len, _ : 5, 4; 100 | } 101 | 102 | impl From for u32 { 103 | fn from(s: ControlRegister) -> u32 { 104 | s.0 105 | } 106 | } 107 | 108 | pub fn read_u32(ctx: &mut Context, channel: u32, register: u32) -> u32 { 109 | let c = channel as usize; 110 | 111 | match register { 112 | STATUS => ctx.exi.status[c].into(), 113 | DMA_ADDRESS => ctx.exi.dma_address[c], 114 | DMA_LENGTH => ctx.exi.dma_length[c], 115 | DMA_CONTROL => ctx.exi.control[c].into(), 116 | IMM_DATA => ctx.exi.imm_data[c], 117 | _ => { 118 | panic!("read_u32 unrecognized register {register:#x}"); 119 | } 120 | } 121 | } 122 | 123 | pub fn write_u32(ctx: &mut Context, channel: u32, register: u32, val: u32) { 124 | let c = channel as usize; 125 | 126 | match register { 127 | STATUS => { 128 | let mut status = ctx.exi.status[c]; 129 | let new_status = StatusRegister(val); 130 | 131 | status.set_exi_interrupt_mask(new_status.exi_interrupt_mask()); 132 | status.set_tc_interrupt_mask(new_status.tc_interrupt_mask()); 133 | status.set_clock_frequency(new_status.clock_frequency()); 134 | 135 | if c == 0 && !status.rom_descramble() { 136 | status.set_rom_descramble(new_status.rom_descramble()); 137 | } 138 | 139 | status.set_device_select(new_status.device_select()); 140 | 141 | let device_index = c * NUM_CHANNELS + status.get_selected_device() as usize; 142 | 143 | ctx.exi.status[c] = status; 144 | 145 | if let Some(device) = ctx.exi.devices[device_index].as_mut() { 146 | device.device_select(); 147 | } 148 | } 149 | DMA_ADDRESS => ctx.exi.dma_address[c] = val, 150 | DMA_LENGTH => ctx.exi.dma_length[c] = val, 151 | DMA_CONTROL => { 152 | let mut control = ControlRegister(val); 153 | 154 | if control.transfer_start() { 155 | let device_index = 156 | c * NUM_CHANNELS + ctx.exi.status[c].get_selected_device() as usize; 157 | 158 | match ctx.exi.devices[device_index].as_mut() { 159 | Some(device) => { 160 | if control.transfer_mode() { 161 | // DMA Mode 162 | let dma_address = ctx.exi.dma_address[c]; 163 | let dma_length = ctx.exi.dma_length[c]; 164 | 165 | if control.transfer_type() == TRANSFER_TYPE_READ { 166 | device.dma_read(&mut ctx.mem, dma_address, dma_length); 167 | } else if control.transfer_type() == TRANSFER_TYPE_WRITE { 168 | device.dma_write(&mut ctx.mem, dma_address, dma_length); 169 | } 170 | } else { 171 | // Immediate Mode 172 | let transfer_len = control.transfer_len() + 1; 173 | 174 | if control.transfer_type() == TRANSFER_TYPE_READ { 175 | ctx.exi.imm_data[c] = device.imm_read(transfer_len as u8); 176 | } else if control.transfer_type() == TRANSFER_TYPE_WRITE { 177 | device.imm_write(ctx.exi.imm_data[c], transfer_len as u8); 178 | } 179 | } 180 | } 181 | None => warn!( 182 | "no device on this channel frequency {}:{}", 183 | c, 184 | ctx.exi.status[c].get_selected_device(), 185 | ), 186 | } 187 | 188 | control.set_transfer_start(false); 189 | } 190 | 191 | ctx.exi.control[c] = control; 192 | } 193 | IMM_DATA => ctx.exi.imm_data[c] = val, 194 | _ => panic!("write_u32 unrecognized register {register:#x}:{val}"), 195 | } 196 | } 197 | 198 | pub trait Device { 199 | fn device_select(&mut self); 200 | 201 | fn transfer_byte(&mut self, _byte: &mut u8) {} 202 | 203 | fn imm_read(&mut self, mut len: u8) -> u32 { 204 | let mut result: u32 = 0; 205 | let mut position = 0; 206 | 207 | while len > 0 { 208 | len -= 1; 209 | let mut byte: u8 = 0; 210 | self.transfer_byte(&mut byte); 211 | result |= (byte as u32) << (24 - (position * 8)); 212 | position += 1; 213 | } 214 | 215 | result 216 | } 217 | 218 | fn imm_write(&mut self, mut value: u32, mut len: u8) { 219 | while len > 0 { 220 | len -= 1; 221 | let mut byte = (value >> 24) as u8; 222 | self.transfer_byte(&mut byte); 223 | value <<= 8; 224 | } 225 | } 226 | 227 | fn dma_read(&mut self, mem: &mut Memory, mut address: u32, mut len: u32) { 228 | while len > 0 { 229 | len -= 1; 230 | let mut byte = 0; 231 | self.transfer_byte(&mut byte); 232 | mem.write_u8(address, byte); 233 | address += 1; 234 | } 235 | } 236 | 237 | fn dma_write(&mut self, mem: &mut Memory, mut address: u32, mut len: u32) { 238 | while len > 0 { 239 | len -= 1; 240 | let mut byte = mem.read_u8(address); 241 | self.transfer_byte(&mut byte); 242 | address += 1; 243 | } 244 | } 245 | } 246 | 247 | #[derive(Default)] 248 | pub struct DeviceAd16 { 249 | position: usize, 250 | command: u8, 251 | register: u32, 252 | } 253 | 254 | impl Device for DeviceAd16 { 255 | fn device_select(&mut self) { 256 | self.position = 0; 257 | self.command = 0; 258 | } 259 | 260 | fn transfer_byte(&mut self, byte: &mut u8) { 261 | if self.position == 0 { 262 | self.command = *byte; 263 | } else { 264 | match self.command { 265 | AD16_COMMAND_INIT => { 266 | self.register = AD16_ID; 267 | 268 | if self.position > 1 && self.position < 6 { 269 | let pos = self.position - 2; 270 | *byte = (self.register >> (24 - (pos * 8))) as u8; 271 | } 272 | } 273 | AD16_COMMAND_READ => { 274 | if self.position < 4 { 275 | let pos = self.position - 1; 276 | *byte = (self.register >> (24 - (pos * 8))) as u8; 277 | } 278 | } 279 | AD16_COMMAND_WRITE => { 280 | if self.position < 4 { 281 | self.register |= *byte as u32; 282 | self.register <<= 8 283 | } 284 | if self.position == 3 { 285 | let msg = match self.register { 286 | 0x0100_0000 => "Init", 287 | 0x0200_0000 => "Cache line 0x3e0 prefetched", // ??? 288 | 0x0300_0000 => "rest of cache line 0x3e0 prefetched", // ??? 289 | 0x0400_0000 => "Memory test passed", 290 | 0x0500_0000 | 0x0600_0000 | 0x0700_0000 => "Memory test failed", 291 | 0x0800_0000 => "IPL and OS Init called", 292 | 0x0900_0000 => "DVD Init", 293 | 0x0A00_0000 => "Card Init", 294 | 0x0B00_0000 => "VI Init", 295 | 0x0C00_0000 => "PAD Init", 296 | _ => "unknown", 297 | }; 298 | 299 | info!("AD16: {:#010x} {:}", self.register, msg); 300 | } 301 | } 302 | _ => (), 303 | } 304 | } 305 | 306 | self.position += 1; 307 | } 308 | } 309 | 310 | pub struct DeviceIpl { 311 | position: u32, 312 | address: u32, 313 | command: u32, 314 | offset: usize, 315 | write: bool, 316 | bootrom: Rc>>, 317 | sram: [u8; 64], 318 | uart: String, 319 | rtc: [u8; 4], 320 | } 321 | 322 | impl DeviceIpl { 323 | pub fn new(bootrom: Rc>>) -> DeviceIpl { 324 | DeviceIpl { 325 | position: 0, 326 | address: 0, 327 | command: 0, 328 | offset: 0, 329 | write: false, 330 | bootrom, 331 | sram: [ 332 | 0xFF, 0x6B, // checksum 1 333 | 0x00, 0x91, // checksum 2 334 | 0x00, 0x00, 0x00, 0x00, // ead 0 335 | 0x00, 0x00, 0x00, 0x00, // ead 1 336 | 0xFF, 0xFF, 0xFF, 0x40, // counter bias 337 | 0x00, // display offset h 338 | 0x00, // ntd 339 | 0x00, // language 340 | 0x2C, // flags 341 | 0x44, 0x4F, 0x4C, 0x50, 0x48, 0x49, 0x4E, 0x53, 0x4C, 0x4F, 0x54, 342 | 0x41, // flash id 343 | 0x44, 0x4F, 0x4C, 0x50, 0x48, 0x49, 0x4E, 0x53, 0x4C, 0x4F, 0x54, 344 | 0x42, // flash id 345 | 0x00, 0x00, 0x00, 0x00, // wireless keyboard id 346 | 0x00, 0x00, // wireless pad id 347 | 0x00, 0x00, // wireless pad id 348 | 0x00, 0x00, // wireless pad id 349 | 0x00, 0x00, // wireless pad id 350 | 0x00, // last dvd error code 351 | 0x00, // padding 352 | 0x6E, 0x6D, // flash id checksum 353 | 0x00, 0x00, // flash id checksum 354 | 0x00, 0x00, // padding 355 | ], 356 | uart: String::new(), 357 | rtc: [0x38, 0x62, 0x43, 0x80], 358 | } 359 | } 360 | } 361 | impl Device for DeviceIpl { 362 | fn device_select(&mut self) { 363 | self.position = 0; 364 | self.address = 0; 365 | self.command = 0; 366 | self.offset = 0; 367 | self.write = false; 368 | } 369 | 370 | fn transfer_byte(&mut self, byte: &mut u8) { 371 | // First 4 bytes are the address 372 | if self.position < 4 { 373 | self.address <<= 8; 374 | self.address |= *byte as u32; 375 | self.offset = 0; 376 | 377 | // check if command is complete 378 | if self.position == 3 { 379 | let device_name; 380 | 381 | self.command = self.address & 0x7FFF_FF00; 382 | self.write = self.address & 0x8000_0000 != 0; 383 | 384 | match self.command { 385 | 0x2000_0000 => { 386 | device_name = "RTC"; 387 | } 388 | 0x2000_0100 => { 389 | device_name = "SRAM"; 390 | } 391 | 0x2001_0000 => { 392 | device_name = "UART"; 393 | 394 | self.address >>= 6; 395 | } 396 | _ => { 397 | device_name = "MaskROM"; 398 | 399 | self.address >>= 6; 400 | 401 | if self.address > BOOTROM_SIZE as u32 { 402 | panic!("Exi DeviceIPL: position out of range: {:#x}", self.address); 403 | } 404 | } 405 | } 406 | 407 | let write_str = if self.write { "write" } else { "read" }; 408 | 409 | debug!( 410 | "ExpansionInterface: {} {} {:#010x}", 411 | device_name, write_str, self.address 412 | ); 413 | } 414 | } else { 415 | match self.command { 416 | 0x2000_0000 => { 417 | // RTC 418 | if self.write { 419 | self.rtc[(self.address & 0x03) as usize + self.offset] = *byte; 420 | } else { 421 | *byte = self.rtc[(self.address & 0x03) as usize + self.offset]; 422 | } 423 | } 424 | 0x2000_0100 => { 425 | // SRAM 426 | if self.write { 427 | self.sram[(self.address & 0x3F) as usize + self.offset] = *byte; 428 | } else { 429 | *byte = self.sram[(self.address & 0x3F) as usize + self.offset]; 430 | } 431 | } 432 | 0x2001_0000 => { 433 | // UART 434 | if self.write { 435 | let byte_char = *byte as char; 436 | 437 | if byte_char != '\0' { 438 | self.uart.push(byte_char); 439 | } 440 | 441 | if byte_char == '\r' { 442 | info!("UART: {}", self.uart); 443 | self.uart.clear(); 444 | } 445 | } else { 446 | *byte = 0x01; 447 | } 448 | } 449 | _ => { 450 | // MASKROM 451 | if !self.write { 452 | *byte = self.bootrom.borrow()[self.address as usize + self.offset]; 453 | } 454 | } 455 | } 456 | self.offset += 1; 457 | } 458 | 459 | self.position += 1; 460 | } 461 | } 462 | -------------------------------------------------------------------------------- /src/gp_fifo.rs: -------------------------------------------------------------------------------- 1 | use crate::video::cp; 2 | 3 | use crate::Context; 4 | 5 | pub const BURST_SIZE: usize = 32; 6 | const BUFFER_SIZE: usize = 128; 7 | 8 | #[derive(Debug)] 9 | pub struct GpFifo { 10 | buff: [u8; BUFFER_SIZE], 11 | pos: usize, 12 | } 13 | 14 | impl Default for GpFifo { 15 | fn default() -> Self { 16 | GpFifo { 17 | buff: [0; BUFFER_SIZE], 18 | pos: 0, 19 | } 20 | } 21 | } 22 | 23 | //impl GpFifo { 24 | // pub fn reset(&mut self) { 25 | // self.pos = 0; 26 | // info!("GPFifo buffer reset"); 27 | // } 28 | //} 29 | 30 | fn check_burst(ctx: &mut Context) { 31 | if ctx.gp_fifo.pos >= BURST_SIZE { 32 | let mut processed = 0; 33 | 34 | while ctx.gp_fifo.pos >= BURST_SIZE { 35 | ctx.mem.write( 36 | ctx.pi.fifo_write_pointer(), 37 | &ctx.gp_fifo.buff[processed..processed + BURST_SIZE], 38 | ); 39 | 40 | if ctx.pi.fifo_write_pointer() == ctx.pi.fifo_end() { 41 | ctx.pi.set_fifo_write_pointer(ctx.pi.fifo_start()); 42 | } else { 43 | ctx.pi 44 | .set_fifo_write_pointer(ctx.pi.fifo_write_pointer() + BURST_SIZE as u32); 45 | } 46 | 47 | processed += BURST_SIZE; 48 | ctx.gp_fifo.pos -= BURST_SIZE; 49 | 50 | cp::gather_pipe_burst(ctx); 51 | } 52 | 53 | if ctx.gp_fifo.pos > 0 { 54 | ctx.gp_fifo.buff.rotate_left(processed); 55 | } 56 | } 57 | } 58 | 59 | pub fn write_u8(ctx: &mut Context, val: u8) { 60 | ctx.gp_fifo.buff[ctx.gp_fifo.pos] = val; 61 | ctx.gp_fifo.pos += 1; 62 | 63 | check_burst(ctx); 64 | } 65 | 66 | pub fn write_u16(ctx: &mut Context, val: u16) { 67 | for x in val.to_be_bytes().iter() { 68 | ctx.gp_fifo.buff[ctx.gp_fifo.pos] = *x; 69 | ctx.gp_fifo.pos += 1; 70 | } 71 | 72 | check_burst(ctx); 73 | } 74 | 75 | pub fn write_u32(ctx: &mut Context, val: u32) { 76 | for x in val.to_be_bytes().iter() { 77 | ctx.gp_fifo.buff[ctx.gp_fifo.pos] = *x; 78 | ctx.gp_fifo.pos += 1; 79 | } 80 | 81 | check_burst(ctx); 82 | } 83 | 84 | pub fn write_u64(ctx: &mut Context, val: u64) { 85 | for x in val.to_be_bytes().iter() { 86 | ctx.gp_fifo.buff[ctx.gp_fifo.pos] = *x; 87 | ctx.gp_fifo.pos += 1; 88 | } 89 | 90 | check_burst(ctx); 91 | } 92 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | extern crate getopts; 2 | extern crate rustcube; 3 | 4 | use rustcube::Context; 5 | 6 | use env_logger::Env; 7 | use getopts::Options; 8 | use std::env; 9 | use std::path::Path; 10 | 11 | pub type DynResult = Result>; 12 | 13 | fn print_usage(program: &str, opts: &Options) { 14 | let brief = format!("Usage: {program} [options] IPL_FILE"); 15 | print!("{}", opts.usage(&brief)); 16 | } 17 | 18 | fn main() -> DynResult<()> { 19 | env_logger::Builder::from_env(Env::default().default_filter_or("info")).init(); 20 | 21 | let args: Vec = env::args().collect(); 22 | let program = args[0].clone(); 23 | 24 | let mut opts = Options::new(); 25 | opts.optflag("h", "help", "print this help menu"); 26 | 27 | let matches = match opts.parse(&args[1..]) { 28 | Ok(m) => m, 29 | Err(f) => panic!("{}", f.to_string()), 30 | }; 31 | 32 | if matches.opt_present("h") { 33 | print_usage(&program, &opts); 34 | return Ok(()); 35 | } 36 | 37 | let file_name = if !matches.free.is_empty() { 38 | Path::new(matches.free[0].as_str()) 39 | } else { 40 | print_usage(&program, &opts); 41 | return Ok(()); 42 | }; 43 | 44 | let mut ctx = Context::default(); 45 | 46 | match file_name.extension() { 47 | Some(ext) => { 48 | if ext == "dol" { 49 | ctx.load_dol(file_name); 50 | } else if ext == "iso" || ext == "gcm" { 51 | ctx.load_iso(file_name); 52 | } else { 53 | // assume ipl 54 | ctx.load_ipl(file_name); 55 | } 56 | } 57 | None => ctx.load_ipl(file_name), 58 | } 59 | 60 | loop { 61 | ctx.step(); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/mem.rs: -------------------------------------------------------------------------------- 1 | use byteorder::{BigEndian, ByteOrder}; 2 | 3 | use super::Context; 4 | 5 | /// Main Memory Size: 24MB 6 | pub const MEMORY_SIZE: usize = 0x180_0000; 7 | 8 | pub struct Memory { 9 | data: Box<[u8]>, 10 | } 11 | 12 | impl Default for Memory { 13 | fn default() -> Self { 14 | Memory { 15 | data: vec![0; MEMORY_SIZE].into_boxed_slice(), 16 | } 17 | } 18 | } 19 | 20 | impl Memory { 21 | pub fn read_u8(&self, addr: u32) -> u8 { 22 | self.data[addr as usize] 23 | } 24 | 25 | pub fn read_u16(&self, addr: u32) -> u16 { 26 | BigEndian::read_u16(&self.data[addr as usize..]) 27 | } 28 | 29 | pub fn read_u32(&self, addr: u32) -> u32 { 30 | BigEndian::read_u32(&self.data[addr as usize..]) 31 | } 32 | 33 | pub fn read_f32(&self, addr: u32) -> f32 { 34 | f32::from_bits(BigEndian::read_u32(&self.data[addr as usize..])) 35 | } 36 | 37 | #[allow(dead_code)] 38 | pub fn read_u64(&self, addr: u32) -> u64 { 39 | BigEndian::read_u64(&self.data[addr as usize..]) 40 | } 41 | 42 | #[allow(dead_code)] 43 | pub fn read(&self, addr: u32, buf: &mut [u8]) { 44 | for (i, elem) in buf.iter_mut().enumerate() { 45 | *elem = self.data[addr as usize + i]; 46 | } 47 | } 48 | 49 | #[allow(dead_code)] 50 | pub fn read_string(&self, mut addr: u32) -> String { 51 | let mut s = String::new(); 52 | loop { 53 | let res = self.read_u8(addr); 54 | if res == 0 { 55 | break; 56 | } 57 | s.push(res as char); 58 | 59 | addr += 1; 60 | } 61 | 62 | s 63 | } 64 | 65 | pub fn write_u8(&mut self, addr: u32, val: u8) { 66 | self.data[addr as usize] = val; 67 | } 68 | 69 | pub fn write(&mut self, addr: u32, buf: &[u8]) { 70 | for (i, elem) in buf.iter().enumerate() { 71 | self.data[addr as usize + i] = *elem; 72 | } 73 | } 74 | } 75 | 76 | //pub fn read(ctx: &mut Context, addr: u32, buf: &mut [u8]) { 77 | // for (i, elem) in buf.iter_mut().enumerate() { 78 | // *elem = ctx.mem.data[addr as usize + i]; 79 | // } 80 | //} 81 | 82 | pub fn read_u16(ctx: &mut Context, addr: u32) -> u16 { 83 | BigEndian::read_u16(&ctx.mem.data[addr as usize..]) 84 | } 85 | 86 | pub fn read_u32(ctx: &mut Context, addr: u32) -> u32 { 87 | BigEndian::read_u32(&ctx.mem.data[addr as usize..]) 88 | } 89 | 90 | pub fn read_u64(ctx: &mut Context, addr: u32) -> u64 { 91 | BigEndian::read_u64(&ctx.mem.data[addr as usize..]) 92 | } 93 | 94 | pub fn write(ctx: &mut Context, addr: u32, buf: &[u8]) { 95 | for (i, elem) in buf.iter().enumerate() { 96 | ctx.mem.data[addr as usize + i] = *elem; 97 | } 98 | } 99 | 100 | pub fn write_u16(ctx: &mut Context, addr: u32, val: u16) { 101 | BigEndian::write_u16(&mut ctx.mem.data[addr as usize..], val); 102 | } 103 | 104 | pub fn write_u32(ctx: &mut Context, addr: u32, val: u32) { 105 | BigEndian::write_u32(&mut ctx.mem.data[addr as usize..], val); 106 | } 107 | 108 | pub fn write_u64(ctx: &mut Context, addr: u32, val: u64) { 109 | BigEndian::write_u64(&mut ctx.mem.data[addr as usize..], val); 110 | } 111 | -------------------------------------------------------------------------------- /src/memory_interface.rs: -------------------------------------------------------------------------------- 1 | 2 | pub struct MemoryInterface; 3 | 4 | impl MemoryInterface { 5 | 6 | pub fn new() -> MemoryInterface { 7 | MemoryInterface 8 | } 9 | 10 | pub fn read_u32(&self, register: u32) -> u32 { 11 | println!("READ MI"); 12 | 0 13 | } 14 | 15 | pub fn write_u32(&mut self, register: u32, val: u32) { 16 | println!("WRITE MI"); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/pe.rs: -------------------------------------------------------------------------------- 1 | use crate::pi::{clear_interrupt, set_interrupt, PI_INTERRUPT_PE_FINISH, PI_INTERRUPT_PE_TOKEN}; 2 | use crate::Context; 3 | 4 | const PE_Z_CONFIG: u32 = 0x00; 5 | const PE_ALPHA_CONFIG: u32 = 0x02; 6 | const PE_DESTINATION_ALPHA: u32 = 0x04; 7 | const PE_ALPHA_MODE: u32 = 0x06; 8 | const PE_ALPHA_READ: u32 = 0x08; 9 | const PE_CONTROL: u32 = 0x0A; 10 | const PE_TOKEN: u32 = 0x0E; 11 | 12 | bitfield! { 13 | #[derive(Copy, Clone, Default)] 14 | pub struct ControlRegister(u16); 15 | impl Debug; 16 | // PE_TOKEN_ENABLE 17 | pub pe_token_enable, _ : 0; 18 | // PE_FINISH_ENABLE 19 | pub pe_finish_enable, _ : 1; 20 | // PE_TOKEN 21 | pub pe_token, set_pe_token : 2; 22 | // PE_FINISH 23 | pub pe_finish, set_pe_finish : 3; 24 | } 25 | 26 | impl From for ControlRegister { 27 | fn from(v: u16) -> Self { 28 | ControlRegister(v) 29 | } 30 | } 31 | 32 | impl From for u16 { 33 | fn from(s: ControlRegister) -> u16 { 34 | s.0 35 | } 36 | } 37 | 38 | #[derive(Default)] 39 | pub struct PixelEngine { 40 | z_config: u16, 41 | alpha_config: u16, 42 | destination_alpha: u16, 43 | alpha_mode: u16, 44 | alpha_read: u16, 45 | control: ControlRegister, 46 | token: u16, 47 | signal_token_interrupt: bool, 48 | signal_finish_interrupt: bool, 49 | } 50 | 51 | pub fn read_u16(ctx: &mut Context, register: u32) -> u16 { 52 | match register { 53 | PE_Z_CONFIG => ctx.pe.z_config, 54 | PE_ALPHA_CONFIG => ctx.pe.alpha_config, 55 | PE_DESTINATION_ALPHA => ctx.pe.destination_alpha, 56 | PE_ALPHA_MODE => ctx.pe.alpha_mode, 57 | PE_ALPHA_READ => ctx.pe.alpha_read, 58 | PE_CONTROL => ctx.pe.control.into(), 59 | PE_TOKEN => ctx.pe.token, 60 | _ => { 61 | warn!("read_u16 unrecoognized pe register {:#x}", register); 62 | 0 63 | } 64 | } 65 | } 66 | 67 | pub fn write_u16(ctx: &mut Context, register: u32, val: u16) { 68 | match register { 69 | PE_Z_CONFIG => ctx.pe.z_config = val, 70 | PE_ALPHA_CONFIG => ctx.pe.alpha_config = val, 71 | PE_DESTINATION_ALPHA => ctx.pe.destination_alpha = val, 72 | PE_ALPHA_MODE => ctx.pe.alpha_mode = val, 73 | PE_ALPHA_READ => ctx.pe.alpha_read = val, 74 | PE_CONTROL => { 75 | let control: ControlRegister = val.into(); 76 | 77 | if control.pe_token() { 78 | ctx.pe.signal_token_interrupt = false; 79 | } 80 | 81 | if control.pe_finish() { 82 | ctx.pe.signal_finish_interrupt = false; 83 | } 84 | 85 | ctx.pe.control = control; 86 | 87 | ctx.pe.control.set_pe_token(false); 88 | ctx.pe.control.set_pe_finish(false); 89 | 90 | update_interrupts(ctx); 91 | } 92 | PE_TOKEN => ctx.pe.token = val, 93 | _ => warn!("write_u16 unrecoognized pe register {:#x}", register), 94 | } 95 | } 96 | 97 | fn update_interrupts(ctx: &mut Context) { 98 | if ctx.pe.signal_token_interrupt && ctx.pe.control.pe_token_enable() { 99 | set_interrupt(ctx, PI_INTERRUPT_PE_TOKEN); 100 | } else { 101 | clear_interrupt(ctx, PI_INTERRUPT_PE_TOKEN); 102 | } 103 | 104 | if ctx.pe.signal_finish_interrupt && ctx.pe.control.pe_finish_enable() { 105 | set_interrupt(ctx, PI_INTERRUPT_PE_FINISH); 106 | } else { 107 | clear_interrupt(ctx, PI_INTERRUPT_PE_FINISH); 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /src/pi.rs: -------------------------------------------------------------------------------- 1 | use crate::Context; 2 | 3 | const PI_INTERRUPT_CAUSE: u32 = 0x00; 4 | const PI_INTERRUPT_MASK: u32 = 0x04; 5 | const PI_FIFO_BASE_START: u32 = 0x0C; 6 | const PI_FIFO_BASE_END: u32 = 0x10; 7 | const PI_FIFO_WRITE_POINTER: u32 = 0x14; 8 | //const PI_FIFO_RESET: u32 = 0x18; 9 | const PI_CONFIG: u32 = 0x24; 10 | const PI_REVISION: u32 = 0x2C; 11 | const PI_UNKNOWN: u32 = 0x30; 12 | 13 | // Flipper ID Revision C as per Dolphin Emulator 14 | const FLIPPER_REV: u32 = 0x2465_00B1; 15 | 16 | pub const PI_INTERRUPT_RSWST: u32 = 0x10000; // Reset Switch State (1 when pressed) 17 | pub const PI_INTERRUPT_HSP: u32 = 0x02000; // High Speed Port 18 | pub const PI_INTERRUPT_DEBUG: u32 = 0x01000; // Debug Hardware 19 | pub const PI_INTERRUPT_CP: u32 = 0x0800; // Command FIFO 20 | pub const PI_INTERRUPT_PE_FINISH: u32 = 0x0400; // GP FInished 21 | pub const PI_INTERRUPT_PE_TOKEN: u32 = 0x0200; // GP Token 22 | pub const PI_INTERRUPT_VI: u32 = 0x00100; // Video Interface 23 | pub const PI_INTERRUPT_MEM: u32 = 0x0080; // Memory Interface 24 | pub const PI_INTERRUPT_DSP: u32 = 0x0040; // DSP Interface 25 | pub const PI_INTERRUPT_AI: u32 = 0x0020; // Audio Interface Streaming 26 | pub const PI_INTERRUPT_EXI: u32 = 0x0010; // External Interface 27 | pub const PI_INTERRUPT_SI: u32 = 0x0008; // Serial Interface 28 | pub const PI_INTERRUPT_DI: u32 = 0x0004; // DVD Interface 29 | pub const PI_INTERRUPT_RSW: u32 = 0x0002; // Reset Switch 30 | pub const PI_INTERRUPT_ERROR: u32 = 0x0001; // GP Runtime Error 31 | 32 | #[derive(Debug)] 33 | pub struct ProcessorInterface { 34 | interrupt_cause: u32, 35 | interrupt_mask: u32, 36 | fifo_start: u32, 37 | fifo_end: u32, 38 | fifo_write_pointer: u32, 39 | config: u32, 40 | revision: u32, 41 | unknown: u32, 42 | } 43 | 44 | impl Default for ProcessorInterface { 45 | fn default() -> Self { 46 | ProcessorInterface { 47 | interrupt_mask: 0, 48 | interrupt_cause: 0, 49 | fifo_start: 0, 50 | fifo_end: 0, 51 | fifo_write_pointer: 0, 52 | config: 0, 53 | revision: FLIPPER_REV, 54 | unknown: 0, 55 | } 56 | } 57 | } 58 | 59 | impl ProcessorInterface { 60 | pub fn fifo_start(&self) -> u32 { 61 | self.fifo_start 62 | } 63 | 64 | pub fn fifo_end(&self) -> u32 { 65 | self.fifo_end 66 | } 67 | 68 | pub fn fifo_write_pointer(&self) -> u32 { 69 | self.fifo_write_pointer 70 | } 71 | 72 | pub fn set_fifo_write_pointer(&mut self, val: u32) { 73 | self.fifo_write_pointer = val; 74 | } 75 | } 76 | 77 | pub fn read_u32(ctx: &mut Context, register: u32) -> u32 { 78 | match register { 79 | PI_INTERRUPT_CAUSE => ctx.pi.interrupt_cause, 80 | PI_INTERRUPT_MASK => ctx.pi.interrupt_mask, 81 | PI_CONFIG => ctx.pi.config, 82 | PI_REVISION => ctx.pi.revision, 83 | _ => { 84 | warn!("read_u32 unrecognized register {register:#x}"); 85 | 0 86 | } 87 | } 88 | } 89 | 90 | pub fn write_u32(ctx: &mut Context, register: u32, val: u32) { 91 | match register { 92 | PI_INTERRUPT_CAUSE => { 93 | ctx.pi.interrupt_cause &= !val; 94 | update_exception(ctx); 95 | } 96 | PI_INTERRUPT_MASK => { 97 | ctx.pi.interrupt_mask = val; 98 | update_exception(ctx); 99 | } 100 | PI_FIFO_BASE_START => ctx.pi.fifo_start = val, 101 | PI_FIFO_BASE_END => ctx.pi.fifo_end = val, 102 | PI_FIFO_WRITE_POINTER => ctx.pi.fifo_write_pointer = val, 103 | PI_CONFIG => { 104 | // TODO: dig into the purpose of this register 105 | // not sure why the DVDlowReset writes 0x3, waits some amount of time, then writes 0x7 106 | ctx.pi.config = val; 107 | } 108 | PI_UNKNOWN => ctx.pi.unknown = val, 109 | _ => warn!("write_u32 unrecognized register {register:#x}"), 110 | } 111 | } 112 | 113 | pub fn clear_interrupt(ctx: &mut Context, cause: u32) { 114 | if ctx.pi.interrupt_cause & cause != 0 { 115 | info!("Interrupt {} (clear)", interrupt_name(cause)); 116 | } 117 | 118 | ctx.pi.interrupt_cause &= !cause; 119 | 120 | update_exception(ctx); 121 | } 122 | 123 | pub fn set_interrupt(ctx: &mut Context, cause: u32) { 124 | if ctx.pi.interrupt_cause & cause == 0 { 125 | info!("Interrupt {} (set)", interrupt_name(cause)); 126 | } 127 | 128 | ctx.pi.interrupt_cause |= cause; 129 | 130 | update_exception(ctx); 131 | } 132 | 133 | pub fn update_exception(ctx: &mut Context) { 134 | if ctx.pi.interrupt_cause & ctx.pi.interrupt_mask != 0 { 135 | ctx.cpu.external_interrupt(true); 136 | } else { 137 | ctx.cpu.external_interrupt(false); 138 | } 139 | } 140 | 141 | fn interrupt_name(interrupt: u32) -> &'static str { 142 | match interrupt { 143 | PI_INTERRUPT_ERROR => "PI_INTERRUPT_ERROR", 144 | PI_INTERRUPT_RSW => "PI_INTERRUPT_RSW", 145 | PI_INTERRUPT_DI => "PI_INTERRUPT_DI", 146 | PI_INTERRUPT_SI => "PI_INTERRUPT_SI", 147 | PI_INTERRUPT_EXI => "PI_INTERRUPT_EXI", 148 | PI_INTERRUPT_AI => "PI_INTERRUPT_AI", 149 | PI_INTERRUPT_DSP => "PI_INTERRUPT_DSP", 150 | PI_INTERRUPT_MEM => "PI_INTERRUPT_MEM", 151 | PI_INTERRUPT_VI => "PI_INTERRUPPT_VI", 152 | PI_INTERRUPT_PE_TOKEN => "PI_INTERRUPT_PE_TOKEN", 153 | PI_INTERRUPT_PE_FINISH => "PI_INTERUPT_PE_FINISH", 154 | PI_INTERRUPT_CP => "PI_INTERRUPT_CP", 155 | PI_INTERRUPT_DEBUG => "PI_INTERRUPT_DEBUG", 156 | PI_INTERRUPT_HSP => "PI_INTERRUPT_HSP", 157 | PI_INTERRUPT_RSWST => "PI_INTERRUPT_RSWST", 158 | _ => "UNKNOWN", 159 | } 160 | } 161 | -------------------------------------------------------------------------------- /src/si.rs: -------------------------------------------------------------------------------- 1 | use crate::Context; 2 | 3 | const SI_POLL: u32 = 0x30; 4 | const SI_COMM_CONTROL: u32 = 0x34; 5 | const SI_STATUS: u32 = 0x38; 6 | const SI_EXI_CLOCK_LOCK: u32 = 0x3C; 7 | const SI_IO_BUFFER: u32 = 0x80; 8 | 9 | #[derive(Debug, Default)] 10 | pub struct SerialInterface { 11 | poll: PollRegister, 12 | comm_cont_status: CommunicationControlStatusRegister, 13 | status: StatusRegister, 14 | clock_count: u32, 15 | } 16 | 17 | bitfield! { 18 | #[derive(Copy, Clone, Default)] 19 | pub struct PollRegister(u32); 20 | impl Debug; 21 | pub vblank_copy, _ : 3, 0; 22 | pub enable, _ : 7, 4; 23 | pub y_times, _ : 15, 8; 24 | pub x_lines, _ : 25, 16; 25 | } 26 | 27 | impl From for PollRegister { 28 | fn from(v: u32) -> Self { 29 | PollRegister(v) 30 | } 31 | } 32 | 33 | bitfield! { 34 | #[derive(Copy, Clone, Default)] 35 | pub struct CommunicationControlStatusRegister(u32); 36 | impl Debug; 37 | pub tstart, _ : 0; 38 | pub channel, _ : 2, 1; 39 | pub input_length, _ : 14, 8; 40 | pub output_length, _ : 22, 16; 41 | pub channel_enable, _ : 24; 42 | pub channel_number, _ : 26, 25; 43 | pub rdstint, set_rdstint : 27; // read status interrupt mask 44 | pub reat_interrupt_status, _ : 28; // read status interrupt status 45 | pub comm_error, _ : 29; 46 | pub transfer_complete_interrupt_mask, _ : 30; 47 | pub tcint, set_tcint : 31; // transfer complete interrupt status 48 | } 49 | 50 | impl From for CommunicationControlStatusRegister { 51 | fn from(v: u32) -> Self { 52 | CommunicationControlStatusRegister(v) 53 | } 54 | } 55 | 56 | impl From for u32 { 57 | fn from(s: CommunicationControlStatusRegister) -> u32 { 58 | s.0 59 | } 60 | } 61 | 62 | bitfield! { 63 | #[derive(Copy, Clone, Default)] 64 | pub struct StatusRegister(u32); 65 | impl Debug; 66 | pub joy_channel_3, _ : 5, 0; 67 | pub joy_channel_2, _ : 13, 8; 68 | pub joy_channel_1, _ : 21, 16; 69 | pub under_run_error, _ : 24; 70 | pub over_run_error, _ : 25; 71 | pub collision_error, _ : 26; 72 | pub no_response_error, _ : 27; 73 | pub write_status, _ : 28; 74 | pub read_status, _ : 29; 75 | pub write, _ : 31; 76 | } 77 | 78 | impl From for StatusRegister { 79 | fn from(v: u32) -> Self { 80 | StatusRegister(v) 81 | } 82 | } 83 | 84 | impl From for u32 { 85 | fn from(s: StatusRegister) -> u32 { 86 | s.0 87 | } 88 | } 89 | 90 | pub fn read_u32(ctx: &mut Context, register: u32) -> u32 { 91 | match register { 92 | SI_COMM_CONTROL => ctx.si.comm_cont_status.into(), 93 | SI_STATUS => ctx.si.status.into(), 94 | SI_EXI_CLOCK_LOCK => ctx.si.clock_count, 95 | _ => { 96 | warn!("read_u32 unrecognized register {:#x}", register); 97 | 0 98 | } 99 | } 100 | } 101 | 102 | pub fn write_u32(ctx: &mut Context, register: u32, val: u32) { 103 | match register { 104 | SI_POLL => ctx.si.poll = val.into(), 105 | SI_COMM_CONTROL => { 106 | let mut cont: CommunicationControlStatusRegister = val.into(); 107 | 108 | if cont.rdstint() { 109 | cont.set_rdstint(false); 110 | } 111 | if cont.tcint() { 112 | cont.set_tcint(false); 113 | } 114 | ctx.si.comm_cont_status = cont; 115 | 116 | if cont.tstart() { 117 | warn!("FIXME tstart"); 118 | } 119 | 120 | if !cont.tstart() { 121 | warn!("Update interrupt"); 122 | } 123 | } 124 | SI_STATUS => { 125 | ctx.si.status = val.into(); 126 | } 127 | SI_EXI_CLOCK_LOCK => ctx.si.clock_count = val, 128 | SI_IO_BUFFER => (), // ignore for now 129 | _ => warn!("write_u32 unrecognized register {:#x}:{}", register, val), 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /src/timers.rs: -------------------------------------------------------------------------------- 1 | // Note: CPU timebase and decrementer update at 1/4th the bus speed 2 | 3 | pub const CPU_CLOCK: u64 = 486000000; 4 | pub const BUS_CLOCK: u64 = 162000000; // One third cpu clock 5 | //const TIMER_CLOCK: u64 = BUS_CLOCK / 4; 6 | 7 | const TIMER_RATIO: u64 = 12; // 1/12th the cpu frequency 8 | 9 | #[derive(Default, Debug)] 10 | pub struct Timers { 11 | tb_start_value: u64, 12 | tb_ticks: u64, 13 | tb_start_ticks: u64, 14 | } 15 | 16 | impl Timers { 17 | // Used to advance cycle count of instruction 18 | pub fn tick(&mut self, ticks: u32) { 19 | self.tb_ticks = self.tb_ticks.wrapping_add(ticks as u64); 20 | } 21 | 22 | pub fn get_ticks(&self) -> u64 { 23 | self.tb_ticks 24 | } 25 | 26 | pub fn get_timebase(&mut self) -> u64 { 27 | self.tb_start_value + ((self.tb_ticks - self.tb_start_ticks) / TIMER_RATIO) 28 | } 29 | 30 | pub fn set_timebase_lower(&mut self, val: u32) { 31 | self.tb_start_ticks = self.tb_ticks; 32 | info!("Set Timebase Lower {val}"); 33 | self.tb_start_value = (self.tb_start_value & !0xFFFF_FFFF) | val as u64; 34 | } 35 | 36 | pub fn set_timebase_upper(&mut self, val: u32) { 37 | self.tb_start_ticks = self.tb_ticks; 38 | info!("Set Timebase Upper {val}"); 39 | self.tb_start_value = (self.tb_start_value & 0xFFFF_FFFF) | ((val as u64) << 32); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/utils.rs: -------------------------------------------------------------------------------- 1 | pub trait Halveable { 2 | type HalfSize; 3 | 4 | fn hi(self) -> Self::HalfSize; 5 | fn lo(self) -> Self::HalfSize; 6 | fn set_hi(self, v: Self::HalfSize) -> Self; 7 | fn set_lo(self, v: Self::HalfSize) -> Self; 8 | } 9 | 10 | impl Halveable for u32 { 11 | type HalfSize = u16; 12 | 13 | fn hi(self) -> Self::HalfSize { 14 | (self >> 16) as u16 15 | } 16 | 17 | fn lo(self) -> Self::HalfSize { 18 | self as u16 19 | } 20 | 21 | fn set_hi(self, v: Self::HalfSize) -> Self { 22 | (self & 0xFFFF) | ((v as u32) << 16) 23 | } 24 | 25 | fn set_lo(self, v: Self::HalfSize) -> Self { 26 | (self & 0xFFFF_0000) | (v as u32) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/video.rs: -------------------------------------------------------------------------------- 1 | mod bp; 2 | pub mod cp; 3 | mod xf; 4 | -------------------------------------------------------------------------------- /src/video/bp.rs: -------------------------------------------------------------------------------- 1 | use crate::mem::Memory; 2 | 3 | const IND_IMASK: u32 = 0x0F; 4 | const IND_CMD0: u32 = 0x10; 5 | const SCISSOR_0: u32 = 0x20; 6 | const SCISSOR_1: u32 = 0x21; 7 | const SU_LPSIZE: u32 = 0x22; 8 | const SU_COUNTER: u32 = 0x23; 9 | const RAS_COUNTER: u32 = 0x24; 10 | const RAS1_SS0: u32 = 0x25; 11 | const RAS1_SS1: u32 = 0x26; 12 | const RAS1_TREF0: u32 = 0x28; 13 | const RAS1_TREF1: u32 = 0x29; 14 | const RAS1_TREF2: u32 = 0x2A; 15 | const RAS1_TREF3: u32 = 0x2B; 16 | const RAS1_TREF4: u32 = 0x2C; 17 | const RAS1_TREF5: u32 = 0x2D; 18 | const RAS1_TREF6: u32 = 0x2E; 19 | const RAS1_TREF7: u32 = 0x2F; 20 | const SU_SSIZE0: u32 = 0x30; 21 | const SU_SSIZE1: u32 = 0x32; 22 | const SU_SSIZE2: u32 = 0x34; 23 | const SU_SSIZE3: u32 = 0x36; 24 | const SU_SSIZE4: u32 = 0x38; 25 | const SU_SSIZE5: u32 = 0x3A; 26 | const SU_SSIZE6: u32 = 0x3C; 27 | const SU_SSIZE7: u32 = 0x3E; 28 | const PE_ZMODE: u32 = 0x40; 29 | const PE_CMODE0: u32 = 0x41; 30 | const PE_CMODE1: u32 = 0x42; 31 | const PE_CONTROL: u32 = 0x43; // ZCOMPARE ??? 32 | const FIELD_MASK: u32 = 0x44; 33 | const PE_DONE: u32 = 0x45; 34 | const CLOCK_0: u32 = 0x46; 35 | const PE_TOKEN: u32 = 0x47; 36 | const PE_TOKEN_INT: u32 = 0x48; 37 | const EFB_BOXCOORD: u32 = 0x49; 38 | const EFB_BOXSIZE: u32 = 0x4A; 39 | const XFB_ADDR: u32 = 0x4B; 40 | const XFB_STRIDE: u32 = 0x4D; 41 | const DISP_COPY: u32 = 0x4E; 42 | const CLEAR_AR: u32 = 0x4F; 43 | const CLEAR_GB: u32 = 0x50; 44 | const CLEAR_Z: u32 = 0x51; 45 | const COPY_CONTROL: u32 = 0x52; 46 | const COPY_FILTER0: u32 = 0x53; 47 | const COPY_FILTER1: u32 = 0x54; 48 | const BOUNDING_BOX0: u32 = 0x55; 49 | const BOUNDING_BOX1: u32 = 0x56; 50 | const UNKNOWN: u32 = 0x58; 51 | const SCISSOR_BOX: u32 = 0x59; 52 | const UNKNOWN1: u32 = 0x66; 53 | const FIELD_MODE: u32 = 0x68; 54 | const CLOCK_1: u32 = 0x69; 55 | const FOG_RANGE: u32 = 0xE8; 56 | const TEV_FOG_PARAM_0: u32 = 0xEE; 57 | const TEV_FOG_PARAM_1: u32 = 0xEF; 58 | const TEV_FOG_PARAM_2: u32 = 0xF0; 59 | const TEV_FOG_PARAM_3: u32 = 0xF1; 60 | const TEV_FOG_COLOR: u32 = 0xF2; 61 | const TEV_ALPHAFUNC: u32 = 0xF3; 62 | const TEV_Z_ENV_0: u32 = 0xF4; 63 | const TEV_Z_ENV_1: u32 = 0xF5; 64 | const TEV_KSEL_0: u32 = 0xF6; 65 | 66 | #[derive(Default, Debug)] 67 | pub struct BlittingProcessor { 68 | imask: u32, 69 | clock_0: u32, 70 | clock_1: u32, 71 | copy_control: CopyControl, 72 | xfb_addr: u32, // Note: physical address shifted right by 5 73 | efb_coord: Coords, 74 | efb_boxsize: Coords, 75 | xfb_stride: u32, 76 | disp_copy_y_scale: u32, 77 | } 78 | 79 | bitfield! { 80 | #[derive(Copy, Clone, Default)] 81 | pub struct CopyControl(u32); 82 | impl Debug; 83 | pub rid, _ : 24; 84 | pub copy_to_xfb, _ : 14; 85 | pub frame_2_field_mode, _ : 13, 12; 86 | pub clear, _ : 11; 87 | pub scale_invert, _ : 10; 88 | pub disp_copy_gamma, _ : 8, 7; 89 | pub xfb_format, _ : 4; 90 | pub clamp2, _ : 1; 91 | pub clamp1, _ : 0; 92 | } 93 | 94 | bitfield! { 95 | #[derive(Copy, Clone, Default)] 96 | pub struct PeControl(u32); 97 | impl Debug; 98 | pub rid, _ : 24; 99 | pub z_comp_loc, _ : 6; 100 | pub z_format, _ : 5, 3; 101 | pub pixel_format, _ : 2, 0; 102 | } 103 | 104 | #[derive(Default, Debug)] 105 | struct Coords(u32); 106 | 107 | impl Coords { 108 | fn x(&self) -> u32 { 109 | (self.0 >> 10) & 0x3FF 110 | } 111 | 112 | fn y(&self) -> u32 { 113 | self.0 & 0x3FF 114 | } 115 | } 116 | 117 | impl BlittingProcessor { 118 | pub fn load(&mut self, value: u32, _: &mut Memory) { 119 | let reg = value >> 24; 120 | let new_value = value & 0xFF_FFFF; 121 | 122 | match reg { 123 | 0x0 => (), // GEN_MODE 124 | 0x01..=0x04 => (), // display copy filter 125 | IND_IMASK => self.imask = new_value, 126 | IND_CMD0..=0x1F => (), // tex indeirect 0 127 | SCISSOR_0 => (), // x0,y0 128 | SCISSOR_1 => (), // x1,y1 129 | SU_LPSIZE => (), // field mode .. line width - point 130 | SU_COUNTER => (), 131 | RAS_COUNTER => (), 132 | RAS1_SS0 => (), 133 | RAS1_SS1 => (), 134 | RAS1_TREF0 => (), // tev order 0 135 | RAS1_TREF1 => (), // tev order 1 136 | RAS1_TREF2 => (), // tev order 2 137 | RAS1_TREF3 => (), // tev order 3 138 | RAS1_TREF4 => (), // tev order 4 139 | RAS1_TREF5 => (), // tev order 5 140 | RAS1_TREF6 => (), // tev order 6 141 | RAS1_TREF7 => (), // tev order 7 142 | SU_SSIZE0 => (), // texture offset 0 143 | SU_SSIZE1 => (), // texture offset 1 144 | SU_SSIZE2 => (), // texture offset 2 145 | SU_SSIZE3 => (), // texture offset 3 146 | SU_SSIZE4 => (), // texture offset 4 147 | SU_SSIZE5 => (), // texture offset 5 148 | SU_SSIZE6 => (), // texture offset 6 149 | SU_SSIZE7 => (), // texture offset 7 150 | PE_ZMODE => (), 151 | PE_CMODE0 => (), 152 | PE_CMODE1 => (), 153 | PE_CONTROL => (), 154 | FIELD_MASK => (), 155 | PE_DONE => { 156 | panic!("PE_DONE"); 157 | } 158 | CLOCK_0 => self.clock_0 = new_value, 159 | PE_TOKEN => { 160 | panic!("PE_TOKEN"); 161 | 162 | // FIXME: test PE token 163 | } 164 | PE_TOKEN_INT => { 165 | panic!("PE_TOKEN_INT"); 166 | // FIXME: test PE token 167 | } 168 | EFB_BOXCOORD => self.efb_coord = Coords(new_value), 169 | EFB_BOXSIZE => self.efb_boxsize = Coords(new_value), 170 | XFB_ADDR => self.xfb_addr = new_value, 171 | XFB_STRIDE => self.xfb_stride = new_value, 172 | DISP_COPY => self.disp_copy_y_scale = new_value, 173 | CLEAR_AR => (), // set clear alpha and red components 174 | CLEAR_GB => (), // green and blue 175 | CLEAR_Z => (), // 24-bit Z 176 | COPY_CONTROL => { 177 | self.copy_control = CopyControl(new_value); 178 | 179 | let dest_addr = self.xfb_addr << 5; 180 | 181 | let src_rec_left = self.efb_coord.x() as i32; 182 | let src_rec_top = self.efb_coord.y() as i32; 183 | 184 | let src_rec_right = src_rec_left + self.efb_boxsize.x() as i32 + 1; 185 | let src_rec_bottom = src_rec_top + self.efb_boxsize.y() as i32 + 1; 186 | 187 | let mut y_scale = if self.copy_control.scale_invert() { 188 | 256 - (256.0 / self.disp_copy_y_scale as f32) as u32 189 | } else { 190 | (self.disp_copy_y_scale as f32 / 256.0) as u32 191 | }; 192 | 193 | y_scale &= 0x1ff; 194 | 195 | let num_xfb_lines = 1 + self.efb_boxsize.y() * y_scale; 196 | 197 | let height = num_xfb_lines; 198 | 199 | if self.copy_control.copy_to_xfb() { 200 | // EFB to XFB 201 | info!("RenderToXFB: destAddr: {:#x} | srcRect {{{} {} {} {}}} | fbWidth: {} | fbHeight: {} | fbStride: {} | yScale: {}", 202 | dest_addr, src_rec_left, src_rec_top, src_rec_right, src_rec_bottom, 203 | self.efb_boxsize.x() + 1, height, self.xfb_stride << 5, y_scale); 204 | } else { 205 | // EFB to texture 206 | panic!("Don't copy to XFB"); 207 | } 208 | 209 | if self.copy_control.clear() { 210 | info!("ToDo: Clear Screen"); 211 | } 212 | } // PE execute ??? trigger frpom efb to xfb 213 | COPY_FILTER0 => (), 214 | COPY_FILTER1 => (), 215 | BOUNDING_BOX0 => (), 216 | BOUNDING_BOX1 => (), 217 | UNKNOWN => (), 218 | SCISSOR_BOX => (), // scissor-box offset 219 | UNKNOWN1 => (), 220 | FIELD_MODE => (), 221 | CLOCK_1 => self.clock_1 = new_value, 222 | FOG_RANGE => (), 223 | 0xC0..=0xDF => (), // TEV 224 | TEV_FOG_PARAM_0 => (), 225 | TEV_FOG_PARAM_1 => (), 226 | TEV_FOG_PARAM_2 => (), 227 | TEV_FOG_PARAM_3 => (), 228 | TEV_FOG_COLOR => (), 229 | TEV_ALPHAFUNC => (), 230 | TEV_Z_ENV_0 => (), // z texture 0 231 | TEV_Z_ENV_1 => (), // z texture 1 232 | TEV_KSEL_0..=0xFD => (), 233 | _ => warn!("Unhandled BP Reg: {:#x} Value: {:#x}", reg, new_value), 234 | } 235 | } 236 | } 237 | -------------------------------------------------------------------------------- /src/video/cp.rs: -------------------------------------------------------------------------------- 1 | use crate::gp_fifo::BURST_SIZE; 2 | use crate::mem; 3 | use crate::utils::Halveable; 4 | use crate::video::bp::BlittingProcessor; 5 | use crate::video::xf::TransformUnit; 6 | use crate::Context; 7 | const CP_STATUS: u32 = 0x00; 8 | const CP_CONTROL: u32 = 0x02; 9 | const CP_CLEAR: u32 = 0x04; 10 | 11 | //const CP_TOKEN: u32 = 0x0E; 12 | //const CP_BOUNDING_BOX: u32 = 0x10; 13 | 14 | const CP_FIFO_BASE_LO: u32 = 0x20; 15 | const CP_FIFO_BASE_HI: u32 = 0x22; 16 | const CP_FIFO_END_LO: u32 = 0x24; 17 | const CP_FIFO_END_HI: u32 = 0x26; 18 | const CP_FIFO_HIGH_WATERMARK_LO: u32 = 0x28; 19 | const CP_FIFO_HIGH_WATERMARK_HI: u32 = 0x2A; 20 | const CP_FIFO_LOW_WATERMARK_LO: u32 = 0x2C; 21 | const CP_FIFO_LOW_WATERMARK_HI: u32 = 0x2E; 22 | const CP_FIFO_RW_DISTANCE_LO: u32 = 0x30; 23 | const CP_FIFO_RW_DISTANCE_HI: u32 = 0x32; 24 | const CP_FIFO_WRITE_POINTER_LO: u32 = 0x34; 25 | const CP_FIFO_WRITE_POINTER_HI: u32 = 0x36; 26 | const CP_FIFO_READ_POINTER_LO: u32 = 0x38; 27 | const CP_FIFO_READ_POINTER_HI: u32 = 0x3A; 28 | //const CP_FIFO_BREAKPOINT_LO: u32 = 0x3C; 29 | //const CP_FIFO_BREAKPOINT_HI: u32 = 0x3E; 30 | 31 | // GP Packet Opcodes 32 | const NO_OPERATION: u8 = 0x00; // NOP - No Operation 33 | const LOAD_CP_REG: u8 = 0x08; // Load CP REG 34 | const LOAD_XF_REG: u8 = 0x10; // Load XF REG 35 | 36 | //const LOAD_INDX_A: u8 = 0x20; // Load INDX A 37 | //const LOAD_INDX_B: u8 = 0x28; // Load INDX B 38 | //const LOAD_INDX_C: u8 = 0x30; // Load INDX C 39 | //const LOAD_INDX_D: u8 = 0x38; // Load INDX D 40 | //const CMD_CALL_DL: u8 = 0x40; // CALL DL - Call Displaylist 41 | 42 | const CMD_INV_VC: u8 = 0x48; // Invalidate Vertex Cache 43 | const LOAD_BP_REG: u8 = 0x61; // Load BP REG (SU_ByPassCmd) 44 | 45 | //0x80 QUADS - Draw Quads (*) 46 | //0x90 TRIANGLES - Draw Triangles (*) 47 | //0x98 TRIANGLESTRIP - Draw Triangle Strip (*) 48 | //0xA0 TRIANGLEFAN - Draw Triangle Fan (*) 49 | //0xA8 LINES - Draw Lines (*) 50 | //0xB0 LINESTRIP - Draw Line Strip (*) 51 | //0xB8 POINTS - Draw Points (*) 52 | 53 | // Internal CP Registers 54 | const MATIDX_REG_A: u8 = 0x30; // Texture Matrix Index 0-3 55 | const MATIDX_REG_B: u8 = 0x40; // Texture Matrix Index 4-7 56 | 57 | //const VCD_LO: u8 = 0x50; // Vertex Descriptor (VCD) Low 58 | //const VCD_HI: u8 = 0x60; // Vertex Descriptor (VCD) High 59 | const CP_VAT_REG_A: u8 = 0x70; // Vertex Attribute Table (VAT) group 0 60 | const CP_VAT_REG_B: u8 = 0x80; // Vertex Attribute Table (VAT) group 1 61 | const CP_VAT_REG_C: u8 = 0x90; // Vertex Attribute Table (VAT) group 2 62 | 63 | //const ARRAY_BASE: u8 = 0xA0; 64 | //const ARRAY_STRIDE: u8 = 0xb0; 65 | 66 | const NUM_VAT_REGS: usize = 8; 67 | const VAT_INDEX_MASK: u8 = 7; 68 | 69 | #[derive(Debug, Default)] 70 | pub struct CommandProcessor { 71 | status: StatusRegister, 72 | control: ControlRegister, 73 | clear: ClearRegister, 74 | //token: u16, 75 | //bounding_box_left: u16, 76 | //bounding_box_right: u16, 77 | //bounding_box_top: u16, 78 | //bounding_box_bottom: u16, 79 | fifo_base: u32, 80 | fifo_end: u32, 81 | fifo_high_watermark: u32, 82 | fifo_low_watermark: u32, 83 | fifo_rw_distance: u32, 84 | fifo_write_pointer: u32, 85 | fifo_read_pointer: u32, 86 | //fifo_breakpoint: u32, 87 | bp: BlittingProcessor, 88 | xf: TransformUnit, 89 | matrix_index_a: MatrixIndexA, 90 | matrix_index_b: MatrixIndexB, 91 | vat: [Vat; NUM_VAT_REGS], 92 | } 93 | 94 | impl CommandProcessor { 95 | fn fifo_size(&self) -> u32 { 96 | self.fifo_write_pointer - self.fifo_read_pointer 97 | } 98 | 99 | // Internal CP Registers 100 | fn load(&mut self, reg: u8, value: u32) { 101 | match reg & 0xF0 { 102 | MATIDX_REG_A => { 103 | self.matrix_index_a = value.into(); 104 | } 105 | MATIDX_REG_B => { 106 | self.matrix_index_b = value.into(); 107 | } 108 | CP_VAT_REG_A => { 109 | self.vat[(reg & VAT_INDEX_MASK) as usize].group0 = value.into(); 110 | } 111 | CP_VAT_REG_B => { 112 | self.vat[(reg & VAT_INDEX_MASK) as usize].group1 = value.into(); 113 | } 114 | CP_VAT_REG_C => { 115 | self.vat[(reg & VAT_INDEX_MASK) as usize].group2 = value.into(); 116 | } 117 | _ => warn!( 118 | "Unrecognized internal CP register, reg: {:#x}, value: {:#x}", 119 | reg, value 120 | ), 121 | } 122 | } 123 | } 124 | 125 | bitfield! { 126 | #[derive(Copy, Clone, Default)] 127 | pub struct StatusRegister(u16); 128 | impl Debug; 129 | pub gfx_fifo_overflow, _ : 0; 130 | pub gfx_fifo_underflow, _ : 1; 131 | pub gp_idle_for_reading, _ : 2; 132 | pub gp_idle_for_commands, _ : 3; 133 | pub breakpoint, _ : 4; 134 | } 135 | 136 | impl From for StatusRegister { 137 | fn from(v: u16) -> Self { 138 | StatusRegister(v) 139 | } 140 | } 141 | 142 | impl From for u16 { 143 | fn from(s: StatusRegister) -> u16 { 144 | s.0 145 | } 146 | } 147 | 148 | bitfield! { 149 | #[derive(Copy, Clone, Default)] 150 | pub struct ControlRegister(u16); 151 | impl Debug; 152 | pub gp_fifo_read_enable, _ : 0; 153 | pub cp_irq_enable, _ : 1; 154 | pub fifo_overflow_int_enable, _ : 2; 155 | pub fifo_underflow_int_enable, _ : 3; 156 | pub gp_link_enable, _ : 4; 157 | pub bp_enable, _ : 5; 158 | } 159 | 160 | impl From for ControlRegister { 161 | fn from(v: u16) -> Self { 162 | ControlRegister(v) 163 | } 164 | } 165 | 166 | impl From for u16 { 167 | fn from(s: ControlRegister) -> u16 { 168 | s.0 169 | } 170 | } 171 | 172 | bitfield! { 173 | #[derive(Copy, Clone, Default)] 174 | pub struct ClearRegister(u16); 175 | impl Debug; 176 | pub clear_fifo_overflow, _ : 0; 177 | pub clear_fifo_underflow, _ : 1; 178 | } 179 | 180 | bitfield! { 181 | #[derive(Default)] 182 | pub struct MatrixIndexA(u32); 183 | impl Debug; 184 | u8; 185 | get_pos_index, _: 5, 0; // POSIDX - Index for Position/Normal matrix 186 | get_tex_0_index, _: 11, 6; // TEX0IDX - Index for Texture 0 matrix 187 | get_tex_1_index, _: 17, 12; // TEX1IDX - Index for Texture 1 matrix 188 | get_tex_2_index, _: 23, 18; // TEX2IDX - Index for Texture 2 matrix 189 | get_tex_3_index, _: 29, 24; // TEX3IDX - Index for Texture 3 matrix 190 | } 191 | 192 | impl From for MatrixIndexA { 193 | fn from(v: u32) -> Self { 194 | MatrixIndexA(v) 195 | } 196 | } 197 | 198 | bitfield! { 199 | #[derive(Default)] 200 | pub struct MatrixIndexB(u32); 201 | impl Debug; 202 | u8; 203 | get_tex_4_index, _: 5, 0; // TEX4IDX - Index for Texture 4 matrix 204 | get_tex_5_index, _: 11, 6; // TEX5IDX - Index for Texture 5 matrix 205 | get_tex_6_index, _: 17, 12; // TEX6IDX - Index for Texture 6 matrix 206 | get_tex_7_index, _: 23, 18; // TEX7IDX - Index for Texture 7 matrix 207 | } 208 | 209 | impl From for MatrixIndexB { 210 | fn from(v: u32) -> Self { 211 | MatrixIndexB(v) 212 | } 213 | } 214 | 215 | bitfield! { 216 | #[derive(Default)] 217 | struct VatGroup0(u32); 218 | impl Debug; 219 | u32; 220 | get_normal_index_3, _: 31; // NORMALINDEX3 (0 - single index per normal, 1 - triple-index per nine-normal) 221 | get_byte_dequant, _: 30; // BYTEDEQUANT (should always be 1) 222 | get_tex_0_shift, _: 29, 25; // TEX0SHFT 223 | get_tex_0_format, _:24, 22; // TEX0FMT 224 | get_tex_0_count, _: 21; // TEX0CNT 225 | get_col_1_format, _: 20, 18; // COL1FMT (Specular) 226 | get_col_1_count, _: 17; // COL1CNT (Specular) 227 | get_col_0_format, _: 16, 14; // COL0FMT (Diffused) 228 | get_col_0_count, _: 13; // COL0CNT (Diffused) 229 | get_nrm_format, _: 12, 10; // NRMFMT 230 | get_nrm_count, _: 9; // NRMCNT 231 | get_pos_shift, _: 8, 4; // POSSHFT 232 | get_pos_format, _: 3, 1; // POSFMT 233 | get_pos_count, _: 0; // POSCNT 234 | } 235 | 236 | impl From for VatGroup0 { 237 | fn from(v: u32) -> Self { 238 | VatGroup0(v) 239 | } 240 | } 241 | 242 | bitfield! { 243 | #[derive(Default)] 244 | struct VatGroup1(u32); 245 | impl Debug; 246 | u32; 247 | get_vcache_enhance, _: 31; // VCACHE_ENHANCE (must always be 1) 248 | get_tex_4_format, _: 30, 28; // TEX4FMT 249 | get_tex_4_count, _: 27; // TEX4CNT 250 | get_tex_3_shift, _: 26, 22; // TEX3SHFT 251 | get_tex_3_format, _: 21, 19; // TEX3FMT 252 | get_tex_3_count, _: 18; // TEX3CNT 253 | get_tex_2_shift, _: 17, 13; // TEX2SHFT 254 | get_tex_2_format, _: 12, 10; // TEX2FMT 255 | get_tex_2_count, _: 9; // TEX2CNT 256 | get_tex_1_shift, _: 8, 4; // TEX1SHFT 257 | get_tex_1_format, _: 3, 1; // TEX1FMT 258 | get_tex_1_count, _: 0; // TEX1CNT 259 | } 260 | 261 | impl From for VatGroup1 { 262 | fn from(v: u32) -> Self { 263 | VatGroup1(v) 264 | } 265 | } 266 | 267 | bitfield! { 268 | #[derive(Default)] 269 | struct VatGroup2(u32); 270 | impl Debug; 271 | u32; 272 | get_tex_7_shift, _: 31, 27; // TEX7SHFT 273 | get_tex_7_format, _: 26, 24; // TEX7FMT 274 | get_tex_7_count, _: 23; // TEX7CNT 275 | get_tex_6_shift, _: 22, 18; // TEX6SHFT 276 | get_tex_6_format, _: 17, 15; // TEX6FMT 277 | get_tex_6_count, _: 14; // TEX6CNT 278 | get_tex_5_shift, _: 13, 9; // TEX5SHFT 279 | get_tex_5_format, _: 8, 6; // TEX5FMT 280 | get_tex_5_count, _: 5; // TEX5CNT 281 | get_tex_4_shift, _: 4, 0; // TEX4SHFT 282 | } 283 | 284 | impl From for VatGroup2 { 285 | fn from(v: u32) -> Self { 286 | VatGroup2(v) 287 | } 288 | } 289 | 290 | #[derive(Default, Debug)] 291 | struct Vat { 292 | group0: VatGroup0, 293 | group1: VatGroup1, 294 | group2: VatGroup2, 295 | } 296 | 297 | pub fn write_u16(ctx: &mut Context, register: u32, val: u16) { 298 | match register { 299 | CP_STATUS => ctx.cp.status = val.into(), 300 | CP_CONTROL => ctx.cp.control = val.into(), 301 | CP_CLEAR => ctx.cp.clear = ClearRegister(val), 302 | CP_FIFO_BASE_LO => ctx.cp.fifo_base = ctx.cp.fifo_base.set_lo(val), 303 | CP_FIFO_BASE_HI => ctx.cp.fifo_base = ctx.cp.fifo_base.set_hi(val), 304 | CP_FIFO_END_LO => ctx.cp.fifo_end = ctx.cp.fifo_end.set_lo(val), 305 | CP_FIFO_END_HI => ctx.cp.fifo_end = ctx.cp.fifo_end.set_hi(val), 306 | CP_FIFO_HIGH_WATERMARK_LO => { 307 | ctx.cp.fifo_high_watermark = ctx.cp.fifo_high_watermark.set_lo(val) 308 | } 309 | CP_FIFO_HIGH_WATERMARK_HI => { 310 | ctx.cp.fifo_high_watermark = ctx.cp.fifo_high_watermark.set_hi(val) 311 | } 312 | CP_FIFO_LOW_WATERMARK_LO => { 313 | ctx.cp.fifo_low_watermark = ctx.cp.fifo_low_watermark.set_lo(val) 314 | } 315 | CP_FIFO_LOW_WATERMARK_HI => { 316 | ctx.cp.fifo_low_watermark = ctx.cp.fifo_low_watermark.set_hi(val) 317 | } 318 | CP_FIFO_RW_DISTANCE_LO => ctx.cp.fifo_rw_distance = ctx.cp.fifo_rw_distance.set_lo(val), 319 | CP_FIFO_RW_DISTANCE_HI => ctx.cp.fifo_rw_distance = ctx.cp.fifo_rw_distance.set_hi(val), 320 | CP_FIFO_WRITE_POINTER_LO => { 321 | ctx.cp.fifo_write_pointer = ctx.cp.fifo_write_pointer.set_lo(val) 322 | } 323 | CP_FIFO_WRITE_POINTER_HI => { 324 | ctx.cp.fifo_write_pointer = ctx.cp.fifo_write_pointer.set_hi(val) 325 | } 326 | CP_FIFO_READ_POINTER_LO => ctx.cp.fifo_read_pointer = ctx.cp.fifo_read_pointer.set_lo(val), 327 | CP_FIFO_READ_POINTER_HI => ctx.cp.fifo_read_pointer = ctx.cp.fifo_read_pointer.set_hi(val), 328 | _ => warn!("write_u16 unrecognized cp register {:#x}", register), 329 | } 330 | } 331 | 332 | pub fn gather_pipe_burst(ctx: &mut Context) { 333 | if !ctx.cp.control.gp_link_enable() { 334 | panic!("cp::gather_pipe_burst disabled"); 335 | } 336 | 337 | ctx.cp.fifo_write_pointer += BURST_SIZE as u32; 338 | 339 | if ctx.cp.fifo_write_pointer == ctx.cp.fifo_end { 340 | ctx.cp.fifo_write_pointer = ctx.cp.fifo_base; 341 | } 342 | 343 | if ctx.cp.fifo_write_pointer >= ctx.cp.fifo_read_pointer { 344 | ctx.cp.fifo_rw_distance = ctx.cp.fifo_write_pointer - ctx.cp.fifo_read_pointer; 345 | } 346 | 347 | while ctx.cp.control.gp_fifo_read_enable() && ctx.cp.fifo_rw_distance != 0 { 348 | let opcode = ctx.mem.read_u8(ctx.cp.fifo_read_pointer); 349 | 350 | ctx.cp.fifo_read_pointer += 1; 351 | 352 | match opcode { 353 | NO_OPERATION => (), 354 | LOAD_BP_REG => { 355 | if ctx.cp.fifo_size() < 4 { 356 | ctx.cp.fifo_read_pointer -= 1; 357 | break; 358 | } 359 | 360 | let value = mem::read_u32(ctx, ctx.cp.fifo_read_pointer); 361 | 362 | ctx.cp.bp.load(value, &mut ctx.mem); 363 | 364 | ctx.cp.fifo_read_pointer += 4; 365 | } 366 | LOAD_CP_REG => { 367 | if ctx.cp.fifo_size() < 5 { 368 | ctx.cp.fifo_read_pointer -= 1; 369 | break; 370 | } 371 | 372 | let cmd = ctx.mem.read_u8(ctx.cp.fifo_read_pointer); 373 | let value = mem::read_u32(ctx, ctx.cp.fifo_read_pointer + 1); 374 | 375 | ctx.cp.load(cmd, value); 376 | 377 | ctx.cp.fifo_read_pointer += 5; 378 | } 379 | LOAD_XF_REG => { 380 | let cmd = mem::read_u32(ctx, ctx.cp.fifo_read_pointer); 381 | let xf_size = ((cmd >> 16) & 15) + 1; 382 | let xf_address = cmd & 0xFFFF; 383 | 384 | if ctx.cp.fifo_size() < (xf_size * 4) + 4 { 385 | ctx.cp.fifo_read_pointer -= 1; 386 | break; 387 | } 388 | 389 | ctx.cp.fifo_read_pointer += 4; // cmd 390 | 391 | ctx.cp 392 | .xf 393 | .load(xf_size, xf_address, &mut ctx.mem, ctx.cp.fifo_read_pointer); 394 | 395 | ctx.cp.fifo_read_pointer += xf_size * 4; 396 | } 397 | CMD_INV_VC => warn!("FIXME: Invalidate Vertex Cache"), 398 | _ => { 399 | if opcode & 0x80 != 0 { 400 | panic!("Vertex Opcode: {:#x}", opcode ^ 0x80); 401 | } else { 402 | println!("gp_fifo unexpected opcode {:#x}", opcode); 403 | panic!("Error, maybe dump here"); 404 | } 405 | } 406 | } 407 | 408 | if ctx.cp.fifo_read_pointer == ctx.cp.fifo_end { 409 | ctx.cp.fifo_read_pointer = ctx.cp.fifo_base; 410 | } 411 | 412 | ctx.cp.fifo_rw_distance = ctx.cp.fifo_write_pointer - ctx.cp.fifo_read_pointer; 413 | } 414 | } 415 | -------------------------------------------------------------------------------- /src/video/xf.rs: -------------------------------------------------------------------------------- 1 | use crate::mem; 2 | use crate::video::cp::{MatrixIndexA, MatrixIndexB}; 3 | 4 | const NUM_COLOR_CHANNELS: usize = 2; 5 | 6 | const MEM_SIZE: usize = 0x1000; 7 | 8 | const XF_ERROR: u32 = 0x1000; 9 | const XF_DIAGNOSTICS: u32 = 0x1001; 10 | const XF_STATE0: u32 = 0x1002; 11 | const XF_STATE1: u32 = 0x1003; 12 | const XF_CLOCK: u32 = 0x1004; 13 | const XF_CLIPDISABLE: u32 = 0x1005; 14 | const XF_PERF0: u32 = 0x1006; 15 | //const XF_PERF1: u32 = 0x1007; 16 | //const XF_VTXSPEC: u32 = 0x1008; 17 | const XF_NUMCOLORS: u32 = 0x1009; 18 | const XF_AMBIENT0: u32 = 0x100A; 19 | const XF_AMBIENT1: u32 = 0x100B; 20 | const XF_MATERIAL0: u32 = 0x100C; 21 | const XF_MATERIAL1: u32 = 0x100D; 22 | const XF_COLOR0: u32 = 0x100E; 23 | const XF_COLOR1: u32 = 0x100F; 24 | const XF_ALPHA0: u32 = 0x1010; 25 | const XF_ALPHA1: u32 = 0x1011; 26 | //const XF_DUALTEXTRANS: u32 = 0x1012; 27 | const XF_MATRIXINDA: u32 = 0x1018; 28 | const XF_MATRIXINDB: u32 = 0x1019; 29 | const XF_SCALEX: u32 = 0x101A; 30 | const XF_SCALEY: u32 = 0x101B; 31 | const XF_SCALEZ: u32 = 0x101C; 32 | const XF_OFFSETX: u32 = 0x101D; 33 | const XF_OFFSETY: u32 = 0x101E; 34 | const XF_OFFSETZ: u32 = 0x101F; 35 | //const XF_PROJECTIONA: u32 = 0x1020; 36 | //const XF_PROJECTIONB: u32 = 0x1021; 37 | //const XF_PROJECTIONC: u32 = 0x1022; 38 | //const XF_PROJECTIOND: u32 = 0x1023; 39 | //const XF_PROJECTIONE: u32 = 0x1024; 40 | //const XF_PROJECTIONF: u32 = 0x1025; 41 | //const XF_PROJECTORTHO: u32 = 0x1026; 42 | const XF_NUMTEX: u32 = 0x103F; 43 | const XF_TEXTURES0: u32 = 0x1040; 44 | const XF_TEXTURES7: u32 = 0x1047; 45 | const XF_DUALTEX0: u32 = 0x1050; 46 | const XF_DUALTEX7: u32 = 0x1057; 47 | 48 | #[derive(Debug)] 49 | pub struct TransformUnit { 50 | data: Box<[u8; MEM_SIZE]>, 51 | _dual_tex_trans_enabled: bool, 52 | num_color: u32, 53 | _num_tex: u32, 54 | ambient_color: [u32; NUM_COLOR_CHANNELS], 55 | material_color: [u32; NUM_COLOR_CHANNELS], 56 | color: [ColorControl; NUM_COLOR_CHANNELS], 57 | alpha: [AlphaControl; NUM_COLOR_CHANNELS], 58 | viewport: Viewport, 59 | matrix_index_a: MatrixIndexA, 60 | matrix_index_b: MatrixIndexB, 61 | } 62 | 63 | impl Default for TransformUnit { 64 | fn default() -> Self { 65 | TransformUnit { 66 | data: Box::new([0; MEM_SIZE]), 67 | _dual_tex_trans_enabled: false, 68 | num_color: 0, 69 | _num_tex: 0, 70 | ambient_color: [0; NUM_COLOR_CHANNELS], 71 | material_color: [0; NUM_COLOR_CHANNELS], 72 | color: Default::default(), 73 | alpha: Default::default(), 74 | viewport: Default::default(), 75 | matrix_index_a: Default::default(), 76 | matrix_index_b: Default::default(), 77 | } 78 | } 79 | } 80 | 81 | impl TransformUnit { 82 | pub fn load(&mut self, mut size: u32, mut address: u32, ram: &mut mem::Memory, mut index: u32) { 83 | if size > 0 { 84 | if address < 0x1000 { 85 | for i in 0..size { 86 | self.data[(address + i) as usize] = ram.read_u8(index + i); 87 | } 88 | } else { 89 | while size > 0 && address < XF_DUALTEX7 + 1 { 90 | match address { 91 | XF_ERROR | XF_DIAGNOSTICS | XF_STATE0 | XF_STATE1 | XF_CLOCK | XF_PERF0 => { 92 | } // ignore 93 | XF_CLIPDISABLE => {} // ignore for now 94 | XF_NUMCOLORS => { 95 | let data = ram.read_u32(index); 96 | 97 | if self.num_color != data { 98 | self.num_color = data; 99 | } 100 | } 101 | XF_AMBIENT0 | XF_AMBIENT1 => { 102 | let channel = address - XF_AMBIENT0; 103 | let data = ram.read_u32(index); 104 | 105 | self.ambient_color[channel as usize] = data; 106 | } 107 | XF_MATERIAL0 | XF_MATERIAL1 => { 108 | let channel = address - XF_MATERIAL0; 109 | let data = ram.read_u32(index); 110 | 111 | self.material_color[channel as usize] = data; 112 | } 113 | XF_ALPHA0 | XF_ALPHA1 => { 114 | let channel = address - XF_ALPHA0; 115 | 116 | self.alpha[channel as usize] = AlphaControl(ram.read_u32(index)); 117 | } 118 | XF_MATRIXINDA => { 119 | self.matrix_index_a = MatrixIndexA(ram.read_u32(index)); 120 | } 121 | XF_MATRIXINDB => { 122 | self.matrix_index_b = MatrixIndexB(ram.read_u32(index)); 123 | } 124 | XF_COLOR0 | XF_COLOR1 => { 125 | let channel = address - XF_COLOR0; 126 | 127 | self.color[channel as usize] = ColorControl(ram.read_u32(index)); 128 | } 129 | XF_SCALEX => { 130 | self.viewport.scalex = ram.read_f32(index); 131 | } 132 | XF_SCALEY => { 133 | self.viewport.scaley = ram.read_f32(index); 134 | } 135 | XF_SCALEZ => { 136 | self.viewport.scalez = ram.read_f32(index); 137 | } 138 | XF_OFFSETX => { 139 | self.viewport.offsetx = ram.read_f32(index); 140 | } 141 | XF_OFFSETY => { 142 | self.viewport.offsety = ram.read_f32(index); 143 | } 144 | XF_OFFSETZ => { 145 | self.viewport.offsetz = ram.read_f32(index); 146 | } 147 | XF_NUMTEX => {} 148 | XF_TEXTURES0..=XF_TEXTURES7 => {} 149 | XF_DUALTEX0..=XF_DUALTEX7 => {} 150 | _ => println!("XF unknown register write {:#x} {:#x}", address, size), 151 | } 152 | 153 | index += 4; 154 | size -= 1; 155 | address += 1; 156 | } 157 | } 158 | } else { 159 | panic!("XF zero size ???"); 160 | } 161 | } 162 | } 163 | 164 | bitfield! { 165 | #[derive(Default)] 166 | struct ColorControl(u32); 167 | impl Debug; 168 | get_light_7, _: 14; 169 | get_light_6, _: 13; 170 | get_light_5, _: 12; 171 | get_light_4, _: 11; 172 | get_atten_select, _: 10; 173 | u8, get_diffuse_atten, _: 8, 7; 174 | get_ambient_src, _: 6; 175 | get_light_3, _: 5; 176 | get_light_2, _: 4; 177 | get_light_1, _: 3; 178 | get_light_0, _: 2; 179 | get_light_func, _: 1; 180 | get_material_src, _: 0; 181 | } 182 | 183 | bitfield! { 184 | #[derive(Default)] 185 | struct AlphaControl(u32); 186 | impl Debug; 187 | get_light_7, _: 14; 188 | get_light_6, _: 13; 189 | get_light_5, _: 12; 190 | get_light_4, _: 11; 191 | get_atten_select, _: 10; 192 | u8, get_diffuse_atten, _: 8, 7; 193 | get_ambient_src, _: 6; 194 | get_light_3, _: 5; 195 | get_light_2, _: 4; 196 | get_light_1, _: 3; 197 | get_light_0, _: 2; 198 | get_light_func, _: 1; 199 | get_material_src, _: 0; 200 | } 201 | 202 | #[derive(Default, Debug)] 203 | struct Viewport { 204 | scalex: f32, 205 | scaley: f32, 206 | scalez: f32, 207 | offsetx: f32, 208 | offsety: f32, 209 | offsetz: f32, 210 | } 211 | --------------------------------------------------------------------------------