Fix many bugs, refactor many things..
Passing: Armwrestler, cpu_test by Dead_Body Former-commit-id: 80d815d110c5341515dd01c476a0d7e25ecb66a8
This commit is contained in:
parent
2bd8b56bc6
commit
3a1d5c10ce
|
@ -24,5 +24,5 @@ bitflags = "1.1.0"
|
||||||
zip = "0.5.3"
|
zip = "0.5.3"
|
||||||
|
|
||||||
[profile.dev]
|
[profile.dev]
|
||||||
opt-level = 1
|
opt-level = 0
|
||||||
debug = true
|
debug = true
|
19
README.md
19
README.md
|
@ -2,19 +2,15 @@
|
||||||
|
|
||||||
[![Build Status](https://travis-ci.com/michelhe/rustboyadvance-ng.svg?branch=master)](https://travis-ci.com/michelhe/rustboyadvance-ng)
|
[![Build Status](https://travis-ci.com/michelhe/rustboyadvance-ng.svg?branch=master)](https://travis-ci.com/michelhe/rustboyadvance-ng)
|
||||||
|
|
||||||
RustBoyAdvance-NG aims to be a Nintendo GameBoy Advance emulator and debugger, written in the rust programming language.
|
RustBoyAdvance-NG Nintendo GameBoy Advance emulator and debugger, written in the rust programming language.
|
||||||
|
|
||||||
RustBoyAdvance-NG currently has implemented
|
Currently passing armwrestler tests, and displays some of TONC's Demos.
|
||||||
- Dumbed-down ARM/THUMB mode disassembling
|
|
||||||
- Some ARM/THUMB instruction are implemented, but not all of them.
|
|
||||||
- A neat debugger REPL
|
|
||||||
|
|
||||||
But the way for full emulation is way far ahead, because most of the ARM/THUMB instructions are not yet implemented.
|
![TONC bigmap.gba demo ](img/tonc_bigmap_demo.gif)
|
||||||
|
|
||||||
# Using the REPL
|
# Using the REPL
|
||||||
|
|
||||||
You need to have rust installed, and somehow legally obtain a gba bios binary.
|
You need to have rust installed, and somehow legally obtain a gba bios binary.
|
||||||
Currently to test the debugger, any binary file containing arm mode instructions will do.
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
$ cargo run -- debug
|
$ cargo run -- debug
|
||||||
|
@ -41,6 +37,11 @@ You know what they say, *third time's a charm*.
|
||||||
- [GBATEK](http://problemkaputt.de/gbatek.htm)
|
- [GBATEK](http://problemkaputt.de/gbatek.htm)
|
||||||
A single webpage written by *no$gba* developer Martin Korth.
|
A single webpage written by *no$gba* developer Martin Korth.
|
||||||
This page has pretty much everything. Seriously, it's the best.
|
This page has pretty much everything. Seriously, it's the best.
|
||||||
- [NanoboyAdvance](https://github.com/fleroviux/NanoboyAdvance)]
|
- [TONC](https://www.coranac.com/tonc/text/)
|
||||||
|
A comprehensive GBA dev guide that I used a-lot in order to understand the GBA system.
|
||||||
|
Comes with neat demo roms that really helped me during development and debugging.
|
||||||
|
- [NanoboyAdvance](https://github.com/fleroviux/NanoboyAdvance)
|
||||||
A GameBoy Advance emulator written in C++17 by a nice person called fleroviux.
|
A GameBoy Advance emulator written in C++17 by a nice person called fleroviux.
|
||||||
I've used this emulator to search for a tough bug in mine.
|
I've used this emulator to search for a tough bug in mine.
|
||||||
|
- [Eggvance](https://github.com/jsmolka/eggvance/tree/master/tests)
|
||||||
|
A GameBoy Advance emulator written in C++, with really useful CPU test roms.
|
1
img/tonc_bigmap_demo.gif.REMOVED.git-id
Normal file
1
img/tonc_bigmap_demo.gif.REMOVED.git-id
Normal file
|
@ -0,0 +1 @@
|
||||||
|
de93ded308a68b66c3fe94fe4933737d60a6aba9
|
|
@ -1,8 +1,8 @@
|
||||||
use std::time;
|
|
||||||
use std::fs::File;
|
|
||||||
use std::path::Path;
|
|
||||||
use std::ffi::OsStr;
|
use std::ffi::OsStr;
|
||||||
|
use std::fs::File;
|
||||||
use std::io::prelude::*;
|
use std::io::prelude::*;
|
||||||
|
use std::path::Path;
|
||||||
|
use std::time;
|
||||||
|
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate clap;
|
extern crate clap;
|
||||||
|
@ -20,13 +20,11 @@ use rustboyadvance_ng::core::{GBAError, GBAResult, GameBoyAdvance};
|
||||||
use rustboyadvance_ng::debugger::Debugger;
|
use rustboyadvance_ng::debugger::Debugger;
|
||||||
use rustboyadvance_ng::util::read_bin_file;
|
use rustboyadvance_ng::util::read_bin_file;
|
||||||
|
|
||||||
|
|
||||||
fn load_rom(path: &str) -> GBAResult<Vec<u8>> {
|
fn load_rom(path: &str) -> GBAResult<Vec<u8>> {
|
||||||
if path.ends_with(".zip") {
|
if path.ends_with(".zip") {
|
||||||
let zipfile = File::open(path)?;
|
let zipfile = File::open(path)?;
|
||||||
let mut archive = zip::ZipArchive::new(zipfile)?;
|
let mut archive = zip::ZipArchive::new(zipfile)?;
|
||||||
for i in 0..archive.len()
|
for i in 0..archive.len() {
|
||||||
{
|
|
||||||
let mut file = archive.by_index(i)?;
|
let mut file = archive.by_index(i)?;
|
||||||
if file.name().ends_with(".gba") {
|
if file.name().ends_with(".gba") {
|
||||||
let mut buf = Vec::new();
|
let mut buf = Vec::new();
|
||||||
|
@ -88,11 +86,15 @@ fn run_emulator(matches: &ArgMatches) -> GBAResult<()> {
|
||||||
loop {
|
loop {
|
||||||
let start_time = time::Instant::now();
|
let start_time = time::Instant::now();
|
||||||
gba.frame();
|
gba.frame();
|
||||||
let time_passed = start_time.elapsed();
|
if !no_framerate_limit {
|
||||||
if time_passed <= frame_time {
|
let time_passed = start_time.elapsed();
|
||||||
if !no_framerate_limit {
|
let delay = frame_time.checked_sub(time_passed);
|
||||||
::std::thread::sleep(frame_time - time_passed);
|
match delay {
|
||||||
}
|
None => {}
|
||||||
|
Some(delay) => {
|
||||||
|
::std::thread::sleep(delay);
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -84,6 +84,20 @@ impl BarrelShifterValue {
|
||||||
}
|
}
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
pub fn shifted_register(
|
||||||
|
reg: usize,
|
||||||
|
shift_by: ShiftRegisterBy,
|
||||||
|
bs_op: BarrelShiftOpCode,
|
||||||
|
added: Option<bool>,
|
||||||
|
) -> BarrelShifterValue {
|
||||||
|
let shft_reg = ShiftedRegister {
|
||||||
|
reg,
|
||||||
|
shift_by,
|
||||||
|
bs_op,
|
||||||
|
added,
|
||||||
|
};
|
||||||
|
BarrelShifterValue::ShiftedRegister(shft_reg)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Core {
|
impl Core {
|
||||||
|
@ -272,7 +286,7 @@ impl Core {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn alu_sub_flags(a: i32, b: i32, carry: &mut bool, overflow: &mut bool) -> i32 {
|
pub fn alu_sub_flags(a: i32, b: i32, carry: &mut bool, overflow: &mut bool) -> i32 {
|
||||||
let res = a.wrapping_sub(b);
|
let res = a.wrapping_sub(b);
|
||||||
*carry = (b as u32) <= (a as u32);
|
*carry = (b as u32) <= (a as u32);
|
||||||
let (_, would_overflow) = a.overflowing_sub(b);
|
let (_, would_overflow) = a.overflowing_sub(b);
|
||||||
|
@ -280,7 +294,7 @@ impl Core {
|
||||||
res
|
res
|
||||||
}
|
}
|
||||||
|
|
||||||
fn alu_add_flags(a: i32, b: i32, carry: &mut bool, overflow: &mut bool) -> i32 {
|
pub fn alu_add_flags(a: i32, b: i32, carry: &mut bool, overflow: &mut bool) -> i32 {
|
||||||
let res = a.wrapping_add(b) as u32;
|
let res = a.wrapping_add(b) as u32;
|
||||||
*carry = res < a as u32 || res < b as u32;
|
*carry = res < a as u32 || res < b as u32;
|
||||||
let (_, would_overflow) = a.overflowing_add(b);
|
let (_, would_overflow) = a.overflowing_add(b);
|
||||||
|
@ -288,63 +302,10 @@ impl Core {
|
||||||
res as i32
|
res as i32
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(non_snake_case)]
|
pub fn alu_update_flags(&mut self, result: i32, is_arithmetic: bool, c: bool, v: bool) {
|
||||||
pub fn alu(&mut self, opcode: AluOpCode, op1: i32, op2: i32) -> i32 {
|
|
||||||
use AluOpCode::*;
|
|
||||||
let C = self.cpsr.C() as i32;
|
|
||||||
|
|
||||||
match opcode {
|
|
||||||
AND => op1 & op2,
|
|
||||||
EOR => op1 ^ op2,
|
|
||||||
SUB => op1.wrapping_sub(op2),
|
|
||||||
RSB => op2.wrapping_sub(op1),
|
|
||||||
ADD => op1.wrapping_add(op2),
|
|
||||||
ADC => op1.wrapping_add(op2).wrapping_add(C),
|
|
||||||
SBC => op1.wrapping_sub(op2.wrapping_add(1 - C)),
|
|
||||||
RSC => op2.wrapping_sub(op1.wrapping_add(1 - C)),
|
|
||||||
ORR => op1 | op2,
|
|
||||||
MOV => op2,
|
|
||||||
BIC => op1 & (!op2),
|
|
||||||
MVN => !op2,
|
|
||||||
_ => panic!("{} should be a PSR transfer", opcode),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(non_snake_case)]
|
|
||||||
pub fn alu_flags(&mut self, opcode: AluOpCode, op1: i32, op2: i32) -> Option<i32> {
|
|
||||||
use AluOpCode::*;
|
|
||||||
let mut carry = self.bs_carry_out;
|
|
||||||
let C = self.cpsr.C() as i32;
|
|
||||||
let mut overflow = self.cpsr.V();
|
|
||||||
|
|
||||||
let result = match opcode {
|
|
||||||
AND | TST => op1 & op2,
|
|
||||||
EOR | TEQ => op1 ^ op2,
|
|
||||||
SUB | CMP => Self::alu_sub_flags(op1, op2, &mut carry, &mut overflow),
|
|
||||||
RSB => Self::alu_sub_flags(op2, op1, &mut carry, &mut overflow),
|
|
||||||
ADD | CMN => Self::alu_add_flags(op1, op2, &mut carry, &mut overflow),
|
|
||||||
ADC => Self::alu_add_flags(op1, op2.wrapping_add(C), &mut carry, &mut overflow),
|
|
||||||
SBC => Self::alu_sub_flags(op1, op2.wrapping_add(1 - C), &mut carry, &mut overflow),
|
|
||||||
RSC => Self::alu_sub_flags(op2, op1.wrapping_add(1 - C), &mut carry, &mut overflow),
|
|
||||||
ORR => op1 | op2,
|
|
||||||
MOV => op2,
|
|
||||||
BIC => op1 & (!op2),
|
|
||||||
MVN => !op2,
|
|
||||||
};
|
|
||||||
|
|
||||||
self.cpsr.set_N(result < 0);
|
self.cpsr.set_N(result < 0);
|
||||||
self.cpsr.set_Z(result == 0);
|
self.cpsr.set_Z(result == 0);
|
||||||
if opcode.is_arithmetic() {
|
self.cpsr.set_C(c);
|
||||||
self.cpsr.set_C(carry);
|
self.cpsr.set_V(v);
|
||||||
self.cpsr.set_V(overflow);
|
|
||||||
} else {
|
|
||||||
self.cpsr.set_C(self.bs_carry_out)
|
|
||||||
}
|
|
||||||
|
|
||||||
if opcode.is_setting_flags() {
|
|
||||||
None
|
|
||||||
} else {
|
|
||||||
Some(result)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -161,8 +161,8 @@ impl Core {
|
||||||
/// Add x=1I cycles if Op2 shifted-by-register. Add y=1S+1N cycles if Rd=R15.
|
/// Add x=1I cycles if Op2 shifted-by-register. Add y=1S+1N cycles if Rd=R15.
|
||||||
fn exec_data_processing(&mut self, sb: &mut SysBus, insn: ArmInstruction) -> CpuExecResult {
|
fn exec_data_processing(&mut self, sb: &mut SysBus, insn: ArmInstruction) -> CpuExecResult {
|
||||||
self.S_cycle32(sb, self.pc);
|
self.S_cycle32(sb, self.pc);
|
||||||
let op1 = if insn.rn() == REG_PC {
|
let mut op1 = if insn.rn() == REG_PC {
|
||||||
self.pc as i32
|
(insn.pc + 8) as i32
|
||||||
} else {
|
} else {
|
||||||
self.get_reg(insn.rn()) as i32
|
self.get_reg(insn.rn()) as i32
|
||||||
};
|
};
|
||||||
|
@ -171,6 +171,14 @@ impl Core {
|
||||||
let opcode = insn.opcode().unwrap();
|
let opcode = insn.opcode().unwrap();
|
||||||
|
|
||||||
let op2 = insn.operand2()?;
|
let op2 = insn.operand2()?;
|
||||||
|
match op2 {
|
||||||
|
BarrelShifterValue::ShiftedRegister(_) => {
|
||||||
|
if insn.rn() == REG_PC {
|
||||||
|
op1 += 4;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
let op2 = self.decode_operand2(op2, s_flag)? as i32;
|
let op2 = self.decode_operand2(op2, s_flag)? as i32;
|
||||||
|
|
||||||
if !s_flag {
|
if !s_flag {
|
||||||
|
@ -185,10 +193,49 @@ impl Core {
|
||||||
|
|
||||||
let rd = insn.rd();
|
let rd = insn.rd();
|
||||||
|
|
||||||
|
use AluOpCode::*;
|
||||||
|
let C = self.cpsr.C() as i32;
|
||||||
let alu_res = if s_flag {
|
let alu_res = if s_flag {
|
||||||
self.alu_flags(opcode, op1, op2)
|
let mut carry = self.bs_carry_out;
|
||||||
|
let mut overflow = self.cpsr.V();
|
||||||
|
let result = match opcode {
|
||||||
|
AND | TST => op1 & op2,
|
||||||
|
EOR | TEQ => op1 ^ op2,
|
||||||
|
SUB | CMP => Self::alu_sub_flags(op1, op2, &mut carry, &mut overflow),
|
||||||
|
RSB => Self::alu_sub_flags(op2, op1, &mut carry, &mut overflow),
|
||||||
|
ADD | CMN => Self::alu_add_flags(op1, op2, &mut carry, &mut overflow),
|
||||||
|
ADC => Self::alu_add_flags(op1, op2.wrapping_add(C), &mut carry, &mut overflow),
|
||||||
|
SBC => Self::alu_sub_flags(op1, op2.wrapping_add(1 - C), &mut carry, &mut overflow),
|
||||||
|
RSC => Self::alu_sub_flags(op2, op1.wrapping_add(1 - C), &mut carry, &mut overflow),
|
||||||
|
ORR => op1 | op2,
|
||||||
|
MOV => op2,
|
||||||
|
BIC => op1 & (!op2),
|
||||||
|
MVN => !op2,
|
||||||
|
};
|
||||||
|
|
||||||
|
self.alu_update_flags(result, opcode.is_arithmetic(), carry, overflow);
|
||||||
|
|
||||||
|
if opcode.is_setting_flags() {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(result)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
Some(self.alu(opcode, op1, op2))
|
Some(match opcode {
|
||||||
|
AND => op1 & op2,
|
||||||
|
EOR => op1 ^ op2,
|
||||||
|
SUB => op1.wrapping_sub(op2),
|
||||||
|
RSB => op2.wrapping_sub(op1),
|
||||||
|
ADD => op1.wrapping_add(op2),
|
||||||
|
ADC => op1.wrapping_add(op2).wrapping_add(C),
|
||||||
|
SBC => op1.wrapping_sub(op2.wrapping_add(1 - C)),
|
||||||
|
RSC => op2.wrapping_sub(op1.wrapping_add(1 - C)),
|
||||||
|
ORR => op1 | op2,
|
||||||
|
MOV => op2,
|
||||||
|
BIC => op1 & (!op2),
|
||||||
|
MVN => !op2,
|
||||||
|
_ => panic!("{} should be a PSR transfer", opcode),
|
||||||
|
})
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Some(result) = alu_res {
|
if let Some(result) = alu_res {
|
||||||
|
@ -252,10 +299,10 @@ impl Core {
|
||||||
};
|
};
|
||||||
if insn.transfer_size() == 1 {
|
if insn.transfer_size() == 1 {
|
||||||
self.N_cycle8(sb, addr);
|
self.N_cycle8(sb, addr);
|
||||||
sb.write_8(addr, value as u8);
|
self.write_8(addr, value as u8, sb);
|
||||||
} else {
|
} else {
|
||||||
self.N_cycle32(sb, addr);
|
self.N_cycle32(sb, addr);
|
||||||
sb.write_32(addr & !0x3, value);
|
self.write_32(addr & !0x3, value, sb);
|
||||||
};
|
};
|
||||||
self.N_cycle32(sb, self.pc);
|
self.N_cycle32(sb, self.pc);
|
||||||
}
|
}
|
||||||
|
@ -322,7 +369,7 @@ impl Core {
|
||||||
match insn.halfword_data_transfer_type().unwrap() {
|
match insn.halfword_data_transfer_type().unwrap() {
|
||||||
ArmHalfwordTransferType::UnsignedHalfwords => {
|
ArmHalfwordTransferType::UnsignedHalfwords => {
|
||||||
self.N_cycle32(sb, addr);
|
self.N_cycle32(sb, addr);
|
||||||
sb.write_16(addr, value as u16);
|
self.write_16(addr, value as u16, sb);
|
||||||
self.N_cycle32(sb, self.pc);
|
self.N_cycle32(sb, self.pc);
|
||||||
}
|
}
|
||||||
_ => panic!("invalid HS flags for L=0"),
|
_ => panic!("invalid HS flags for L=0"),
|
||||||
|
@ -369,71 +416,82 @@ impl Core {
|
||||||
|
|
||||||
let psr_transfer = psr_user_flag & is_load & rlist.bit(REG_PC);
|
let psr_transfer = psr_user_flag & is_load & rlist.bit(REG_PC);
|
||||||
|
|
||||||
if is_load {
|
if rlist != 0 {
|
||||||
self.add_cycle();
|
if is_load {
|
||||||
self.N_cycle32(sb, self.pc);
|
self.add_cycle();
|
||||||
for r in 0..16 {
|
self.N_cycle32(sb, self.pc);
|
||||||
let r = if ascending { r } else { 15 - r };
|
for r in 0..16 {
|
||||||
if rlist.bit(r) {
|
let r = if ascending { r } else { 15 - r };
|
||||||
if r == rn {
|
if rlist.bit(r) {
|
||||||
writeback = false;
|
if r == rn {
|
||||||
}
|
writeback = false;
|
||||||
if full {
|
}
|
||||||
addr = addr.wrapping_add(step);
|
if full {
|
||||||
}
|
addr = addr.wrapping_add(step);
|
||||||
|
|
||||||
let val = sb.read_32(addr as Addr);
|
|
||||||
self.S_cycle32(sb, self.pc);
|
|
||||||
if user_bank_transfer {
|
|
||||||
self.set_reg_user(r, val);
|
|
||||||
} else {
|
|
||||||
self.set_reg(r, val);
|
|
||||||
}
|
|
||||||
|
|
||||||
if r == REG_PC {
|
|
||||||
if psr_transfer {
|
|
||||||
self.transfer_spsr_mode();
|
|
||||||
}
|
}
|
||||||
self.flush_pipeline(sb);
|
|
||||||
}
|
|
||||||
|
|
||||||
if !full {
|
let val = sb.read_32(addr as Addr);
|
||||||
addr = addr.wrapping_add(step);
|
self.S_cycle32(sb, self.pc);
|
||||||
|
if user_bank_transfer {
|
||||||
|
self.set_reg_user(r, val);
|
||||||
|
} else {
|
||||||
|
self.set_reg(r, val);
|
||||||
|
}
|
||||||
|
|
||||||
|
if r == REG_PC {
|
||||||
|
if psr_transfer {
|
||||||
|
self.transfer_spsr_mode();
|
||||||
|
}
|
||||||
|
self.flush_pipeline(sb);
|
||||||
|
}
|
||||||
|
|
||||||
|
if !full {
|
||||||
|
addr = addr.wrapping_add(step);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
let mut first = true;
|
||||||
|
for r in 0..16 {
|
||||||
|
let r = if ascending { r } else { 15 - r };
|
||||||
|
if rlist.bit(r) {
|
||||||
|
if full {
|
||||||
|
addr = addr.wrapping_add(step);
|
||||||
|
}
|
||||||
|
|
||||||
|
let val = if r == REG_PC {
|
||||||
|
insn.pc + 12
|
||||||
|
} else {
|
||||||
|
if user_bank_transfer {
|
||||||
|
self.get_reg_user(r)
|
||||||
|
} else {
|
||||||
|
self.get_reg(r)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
if first {
|
||||||
|
self.N_cycle32(sb, addr as u32);
|
||||||
|
first = false;
|
||||||
|
} else {
|
||||||
|
self.S_cycle32(sb, addr as u32);
|
||||||
|
}
|
||||||
|
self.write_32(addr as Addr, val, sb);
|
||||||
|
|
||||||
|
if !full {
|
||||||
|
addr = addr.wrapping_add(step);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.N_cycle32(sb, self.pc);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let mut first = true;
|
if is_load {
|
||||||
for r in 0..16 {
|
let val = self.ldr_word(addr as u32, sb);
|
||||||
let r = if ascending { r } else { 15 - r };
|
self.set_reg(REG_PC, val & !3);
|
||||||
if rlist.bit(r) {
|
self.flush_pipeline(sb);
|
||||||
if full {
|
} else {
|
||||||
addr = addr.wrapping_add(step);
|
self.write_32(addr as u32, self.pc + 4, sb);
|
||||||
}
|
|
||||||
|
|
||||||
let val = if r == REG_PC {
|
|
||||||
insn.pc + 12
|
|
||||||
} else {
|
|
||||||
if user_bank_transfer {
|
|
||||||
self.get_reg_user(r)
|
|
||||||
} else {
|
|
||||||
self.get_reg(r)
|
|
||||||
}
|
|
||||||
};
|
|
||||||
if first {
|
|
||||||
self.N_cycle32(sb, addr as u32);
|
|
||||||
first = false;
|
|
||||||
} else {
|
|
||||||
self.S_cycle32(sb, addr as u32);
|
|
||||||
}
|
|
||||||
sb.write_32(addr as Addr, val);
|
|
||||||
|
|
||||||
if !full {
|
|
||||||
addr = addr.wrapping_add(step);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
self.N_cycle32(sb, self.pc);
|
addr = addr.wrapping_add(step * 0x10);
|
||||||
}
|
}
|
||||||
|
|
||||||
if writeback {
|
if writeback {
|
||||||
|
@ -456,7 +514,7 @@ impl Core {
|
||||||
|
|
||||||
let op1 = self.get_reg(rm) as i32;
|
let op1 = self.get_reg(rm) as i32;
|
||||||
let op2 = self.get_reg(rs) as i32;
|
let op2 = self.get_reg(rs) as i32;
|
||||||
let mut result = (op1 * op2) as u32;
|
let mut result = op1.wrapping_mul(op2) as u32;
|
||||||
|
|
||||||
if insn.accumulate_flag() {
|
if insn.accumulate_flag() {
|
||||||
result = result.wrapping_add(self.get_reg(rn));
|
result = result.wrapping_add(self.get_reg(rn));
|
||||||
|
|
|
@ -54,6 +54,7 @@ pub struct Core {
|
||||||
gpr_previous: [u32; 15],
|
gpr_previous: [u32; 15],
|
||||||
|
|
||||||
memreq: Addr,
|
memreq: Addr,
|
||||||
|
pub breakpoints: Vec<u32>,
|
||||||
|
|
||||||
pub verbose: bool,
|
pub verbose: bool,
|
||||||
}
|
}
|
||||||
|
@ -74,7 +75,7 @@ impl Core {
|
||||||
|
|
||||||
pub fn get_reg(&self, r: usize) -> u32 {
|
pub fn get_reg(&self, r: usize) -> u32 {
|
||||||
match r {
|
match r {
|
||||||
0...14 => self.gpr[r],
|
0..=14 => self.gpr[r],
|
||||||
15 => self.pc,
|
15 => self.pc,
|
||||||
_ => panic!("invalid register {}", r),
|
_ => panic!("invalid register {}", r),
|
||||||
}
|
}
|
||||||
|
@ -124,6 +125,18 @@ impl Core {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn write_32(&mut self, addr: Addr, value: u32, bus: &mut SysBus) {
|
||||||
|
bus.write_32(addr & !0x3, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn write_16(&mut self, addr: Addr, value: u16, bus: &mut SysBus) {
|
||||||
|
bus.write_16(addr & !0x1, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn write_8(&mut self, addr: Addr, value: u8, bus: &mut SysBus) {
|
||||||
|
bus.write_8(addr, value);
|
||||||
|
}
|
||||||
|
|
||||||
/// Helper function for "ldr" instruction that handles misaligned addresses
|
/// Helper function for "ldr" instruction that handles misaligned addresses
|
||||||
pub fn ldr_word(&mut self, addr: Addr, bus: &SysBus) -> u32 {
|
pub fn ldr_word(&mut self, addr: Addr, bus: &SysBus) -> u32 {
|
||||||
if addr & 0x3 != 0 {
|
if addr & 0x3 != 0 {
|
||||||
|
|
|
@ -27,27 +27,11 @@ impl ThumbInstruction {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn fmt_thumb_mul(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
||||||
write!(
|
|
||||||
f,
|
|
||||||
"mul\t{Rd}, {Rs}",
|
|
||||||
Rd = reg_string(self.rd()),
|
|
||||||
Rs = reg_string(self.rs())
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn fmt_thumb_alu_ops(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
fn fmt_thumb_alu_ops(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
let (op, shft) = self.alu_opcode();
|
|
||||||
if let Some(BarrelShifterValue::ShiftedRegister(x)) = shft {
|
|
||||||
write!(f, "{}", x.bs_op)?;
|
|
||||||
} else if op == AluOpCode::RSB {
|
|
||||||
write!(f, "neg")?;
|
|
||||||
} else {
|
|
||||||
write!(f, "{}", op)?;
|
|
||||||
}
|
|
||||||
write!(
|
write!(
|
||||||
f,
|
f,
|
||||||
"\t{Rd}, {Rs}",
|
"{op}\t{Rd}, {Rs}",
|
||||||
|
op = self.format4_alu_op(),
|
||||||
Rd = reg_string(self.rd()),
|
Rd = reg_string(self.rd()),
|
||||||
Rs = reg_string(self.rs())
|
Rs = reg_string(self.rs())
|
||||||
)
|
)
|
||||||
|
@ -290,7 +274,6 @@ impl fmt::Display for ThumbInstruction {
|
||||||
ThumbFormat::MoveShiftedReg => self.fmt_thumb_move_shifted_reg(f),
|
ThumbFormat::MoveShiftedReg => self.fmt_thumb_move_shifted_reg(f),
|
||||||
ThumbFormat::AddSub => self.fmt_thumb_add_sub(f),
|
ThumbFormat::AddSub => self.fmt_thumb_add_sub(f),
|
||||||
ThumbFormat::DataProcessImm => self.fmt_thumb_data_process_imm(f),
|
ThumbFormat::DataProcessImm => self.fmt_thumb_data_process_imm(f),
|
||||||
ThumbFormat::Mul => self.fmt_thumb_mul(f),
|
|
||||||
ThumbFormat::AluOps => self.fmt_thumb_alu_ops(f),
|
ThumbFormat::AluOps => self.fmt_thumb_alu_ops(f),
|
||||||
ThumbFormat::HiRegOpOrBranchExchange => self.fmt_thumb_high_reg_op_or_bx(f),
|
ThumbFormat::HiRegOpOrBranchExchange => self.fmt_thumb_high_reg_op_or_bx(f),
|
||||||
ThumbFormat::LdrPc => self.fmt_thumb_ldr_pc(f),
|
ThumbFormat::LdrPc => self.fmt_thumb_ldr_pc(f),
|
||||||
|
@ -332,3 +315,27 @@ impl fmt::Display for OpFormat5 {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for ThumbAluOps {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
use ThumbAluOps::*;
|
||||||
|
match self {
|
||||||
|
AND => write!(f, "and"),
|
||||||
|
EOR => write!(f, "eor"),
|
||||||
|
LSL => write!(f, "lsl"),
|
||||||
|
LSR => write!(f, "lsr"),
|
||||||
|
ASR => write!(f, "asr"),
|
||||||
|
ADC => write!(f, "adc"),
|
||||||
|
SBC => write!(f, "sbc"),
|
||||||
|
ROR => write!(f, "ror"),
|
||||||
|
TST => write!(f, "tst"),
|
||||||
|
NEG => write!(f, "neg"),
|
||||||
|
CMP => write!(f, "cmp"),
|
||||||
|
CMN => write!(f, "cmn"),
|
||||||
|
ORR => write!(f, "orr"),
|
||||||
|
MUL => write!(f, "mul"),
|
||||||
|
BIC => write!(f, "bic"),
|
||||||
|
MVN => write!(f, "mvn"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,18 +1,20 @@
|
||||||
|
use crate::core::arm7tdmi::alu::AluOpCode;
|
||||||
use crate::core::arm7tdmi::bus::Bus;
|
use crate::core::arm7tdmi::bus::Bus;
|
||||||
use crate::core::arm7tdmi::cpu::{Core, CpuExecResult};
|
use crate::core::arm7tdmi::cpu::{Core, CpuExecResult};
|
||||||
use crate::core::arm7tdmi::alu::AluOpCode;
|
|
||||||
use crate::core::arm7tdmi::*;
|
use crate::core::arm7tdmi::*;
|
||||||
use crate::core::sysbus::SysBus;
|
use crate::core::sysbus::SysBus;
|
||||||
|
|
||||||
|
use crate::bit::BitIndex;
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
fn push(cpu: &mut Core, bus: &mut SysBus, r: usize) {
|
fn push(cpu: &mut Core, bus: &mut SysBus, r: usize) {
|
||||||
cpu.gpr[REG_SP] -= 4;
|
cpu.gpr[REG_SP] -= 4;
|
||||||
let stack_addr = cpu.gpr[REG_SP];
|
let stack_addr = cpu.gpr[REG_SP] & !3;
|
||||||
bus.write_32(stack_addr, cpu.get_reg(r))
|
bus.write_32(stack_addr, cpu.get_reg(r))
|
||||||
}
|
}
|
||||||
fn pop(cpu: &mut Core, bus: &mut SysBus, r: usize) {
|
fn pop(cpu: &mut Core, bus: &mut SysBus, r: usize) {
|
||||||
let stack_addr = cpu.gpr[REG_SP];
|
let stack_addr = cpu.gpr[REG_SP] & !3;
|
||||||
let val = bus.read_32(stack_addr);
|
let val = cpu.ldr_word(stack_addr, bus);
|
||||||
cpu.set_reg(r, val);
|
cpu.set_reg(r, val);
|
||||||
cpu.gpr[REG_SP] = stack_addr + 4;
|
cpu.gpr[REG_SP] = stack_addr + 4;
|
||||||
}
|
}
|
||||||
|
@ -31,14 +33,10 @@ impl Core {
|
||||||
added: None,
|
added: None,
|
||||||
})
|
})
|
||||||
.unwrap() as i32;
|
.unwrap() as i32;
|
||||||
self.cpsr.set_C(self.bs_carry_out);
|
|
||||||
|
|
||||||
let rd = insn.rd();
|
self.set_reg(insn.rd(), op2 as u32);
|
||||||
let op1 = self.get_reg(rd) as i32;
|
self.alu_update_flags(op2, false, self.bs_carry_out, self.cpsr.V());
|
||||||
let result = self.alu_flags(AluOpCode::MOV, op1, op2);
|
|
||||||
if let Some(result) = result {
|
|
||||||
self.set_reg(rd, result as u32);
|
|
||||||
}
|
|
||||||
self.S_cycle16(sb, self.pc + 2);
|
self.S_cycle16(sb, self.pc + 2);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -50,16 +48,17 @@ impl Core {
|
||||||
} else {
|
} else {
|
||||||
self.get_reg(insn.rn()) as i32
|
self.get_reg(insn.rn()) as i32
|
||||||
};
|
};
|
||||||
let arm_alu_op = if insn.is_subtract() {
|
|
||||||
AluOpCode::SUB
|
|
||||||
} else {
|
|
||||||
AluOpCode::ADD
|
|
||||||
};
|
|
||||||
|
|
||||||
let result = self.alu_flags(arm_alu_op, op1, op2);
|
let mut carry = self.cpsr.C();
|
||||||
if let Some(result) = result {
|
let mut overflow = self.cpsr.V();
|
||||||
self.set_reg(insn.rd(), result as u32);
|
let result = if insn.is_subtract() {
|
||||||
}
|
Self::alu_sub_flags(op1, op2, &mut carry, &mut overflow)
|
||||||
|
} else {
|
||||||
|
Self::alu_add_flags(op1, op2, &mut carry, &mut overflow)
|
||||||
|
};
|
||||||
|
self.alu_update_flags(result, true, carry, overflow);
|
||||||
|
self.set_reg(insn.rd(), result as u32);
|
||||||
|
|
||||||
self.S_cycle16(sb, self.pc + 2);
|
self.S_cycle16(sb, self.pc + 2);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -69,49 +68,82 @@ impl Core {
|
||||||
sb: &mut SysBus,
|
sb: &mut SysBus,
|
||||||
insn: ThumbInstruction,
|
insn: ThumbInstruction,
|
||||||
) -> CpuExecResult {
|
) -> CpuExecResult {
|
||||||
let arm_alu_op: AluOpCode = insn.format3_op().into();
|
use OpFormat3::*;
|
||||||
|
let op = insn.format3_op();
|
||||||
let op1 = self.get_reg(insn.rd()) as i32;
|
let op1 = self.get_reg(insn.rd()) as i32;
|
||||||
let op2 = ((insn.raw & 0xff) as i8) as u8 as i32;
|
let op2_imm = (insn.raw & 0xff) as i32;
|
||||||
let result = self.alu_flags(arm_alu_op, op1, op2);
|
let mut carry = self.cpsr.C();
|
||||||
if let Some(result) = result {
|
let mut overflow = self.cpsr.V();
|
||||||
|
let result = match op {
|
||||||
|
MOV => op2_imm,
|
||||||
|
CMP | SUB => Self::alu_sub_flags(op1, op2_imm, &mut carry, &mut overflow),
|
||||||
|
ADD => Self::alu_add_flags(op1, op2_imm, &mut carry, &mut overflow),
|
||||||
|
};
|
||||||
|
let arithmetic = op == ADD || op == SUB;
|
||||||
|
self.alu_update_flags(result, arithmetic, carry, overflow);
|
||||||
|
if op != CMP {
|
||||||
self.set_reg(insn.rd(), result as u32);
|
self.set_reg(insn.rd(), result as u32);
|
||||||
}
|
}
|
||||||
self.S_cycle16(sb, self.pc + 2);
|
self.S_cycle16(sb, self.pc + 2);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn exec_thumb_mul(&mut self, sb: &mut SysBus, insn: ThumbInstruction) -> CpuExecResult {
|
|
||||||
let op1 = self.get_reg(insn.rd()) as i32;
|
|
||||||
let op2 = self.get_reg(insn.rs()) as i32;
|
|
||||||
let m = self.get_required_multipiler_array_cycles(op2);
|
|
||||||
for _ in 0..m {
|
|
||||||
self.add_cycle();
|
|
||||||
}
|
|
||||||
let result = op1.wrapping_mul(op2) as u32;
|
|
||||||
self.cpsr.set_N((result as i32) < 0);
|
|
||||||
self.cpsr.set_Z(result == 0);
|
|
||||||
self.gpr[insn.rd()] = result;
|
|
||||||
self.S_cycle16(sb, self.pc + 2);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn exec_thumb_alu_ops(&mut self, sb: &mut SysBus, insn: ThumbInstruction) -> CpuExecResult {
|
fn exec_thumb_alu_ops(&mut self, sb: &mut SysBus, insn: ThumbInstruction) -> CpuExecResult {
|
||||||
let rd = insn.rd();
|
let rd = insn.rd();
|
||||||
|
let rs = insn.rs();
|
||||||
|
let dst = self.get_reg(rd) as i32;
|
||||||
|
let src = self.get_reg(rs) as i32;
|
||||||
|
|
||||||
let (arm_alu_op, shft) = insn.alu_opcode();
|
let mut carry = self.cpsr.C();
|
||||||
let op1 = if arm_alu_op == AluOpCode::RSB {
|
let c = self.cpsr.C() as i32;
|
||||||
self.get_reg(insn.rs()) as i32
|
let mut overflow = self.cpsr.V();
|
||||||
} else {
|
|
||||||
self.get_reg(rd) as i32
|
|
||||||
};
|
|
||||||
let op2 = if let Some(shft) = shft {
|
|
||||||
self.get_barrel_shifted_value(shft)
|
|
||||||
} else {
|
|
||||||
self.get_reg(insn.rs()) as i32
|
|
||||||
};
|
|
||||||
|
|
||||||
let result = self.alu_flags(arm_alu_op, op1, op2);
|
use ThumbAluOps::*;
|
||||||
if let Some(result) = result {
|
let op = insn.format4_alu_op();
|
||||||
|
let result = match op {
|
||||||
|
AND | TST => dst & src,
|
||||||
|
EOR => dst ^ src,
|
||||||
|
LSL | LSR | ASR | ROR => {
|
||||||
|
// TODO optimize this second match, keeping it here for code clearity
|
||||||
|
let bs_op = match op {
|
||||||
|
LSL => BarrelShiftOpCode::LSL,
|
||||||
|
LSR => BarrelShiftOpCode::LSR,
|
||||||
|
ASR => BarrelShiftOpCode::ASR,
|
||||||
|
ROR => BarrelShiftOpCode::ROR,
|
||||||
|
_ => unreachable!(),
|
||||||
|
};
|
||||||
|
let shft = BarrelShifterValue::shifted_register(
|
||||||
|
rd,
|
||||||
|
ShiftRegisterBy::ByRegister(rs),
|
||||||
|
bs_op,
|
||||||
|
Some(true),
|
||||||
|
);
|
||||||
|
let result = self.get_barrel_shifted_value(shft) as i32;
|
||||||
|
carry = self.bs_carry_out;
|
||||||
|
result
|
||||||
|
}
|
||||||
|
ADC => Self::alu_add_flags(dst, src, &mut carry, &mut overflow).wrapping_add(c),
|
||||||
|
SBC => Self::alu_sub_flags(dst, src, &mut carry, &mut overflow).wrapping_sub(1 - c),
|
||||||
|
NEG => Self::alu_sub_flags(0, src, &mut carry, &mut overflow),
|
||||||
|
CMP => Self::alu_sub_flags(dst, src, &mut carry, &mut overflow),
|
||||||
|
CMN => Self::alu_add_flags(dst, src, &mut carry, &mut overflow),
|
||||||
|
ORR => dst | src,
|
||||||
|
MUL => {
|
||||||
|
let m = self.get_required_multipiler_array_cycles(src);
|
||||||
|
for _ in 0..m {
|
||||||
|
self.add_cycle();
|
||||||
|
}
|
||||||
|
// TODO - meaningless values?
|
||||||
|
carry = false;
|
||||||
|
overflow = false;
|
||||||
|
dst.wrapping_mul(src)
|
||||||
|
}
|
||||||
|
BIC => dst & (!src),
|
||||||
|
MVN => !src,
|
||||||
|
};
|
||||||
|
self.alu_update_flags(result, op.is_arithmetic(), carry, overflow);
|
||||||
|
|
||||||
|
if !op.is_setting_flags() {
|
||||||
self.set_reg(rd, result as u32);
|
self.set_reg(rd, result as u32);
|
||||||
}
|
}
|
||||||
self.S_cycle16(sb, self.pc + 2);
|
self.S_cycle16(sb, self.pc + 2);
|
||||||
|
@ -133,43 +165,49 @@ impl Core {
|
||||||
sb: &mut SysBus,
|
sb: &mut SysBus,
|
||||||
insn: ThumbInstruction,
|
insn: ThumbInstruction,
|
||||||
) -> CpuExecResult {
|
) -> CpuExecResult {
|
||||||
if OpFormat5::BX == insn.format5_op() {
|
let op = insn.format5_op();
|
||||||
self.exec_thumb_bx(sb, insn)
|
let dst_reg = if insn.flag(ThumbInstruction::FLAG_H1) {
|
||||||
|
insn.rd() + 8
|
||||||
} else {
|
} else {
|
||||||
let dst_reg = if insn.flag(ThumbInstruction::FLAG_H1) {
|
insn.rd()
|
||||||
insn.rd() + 8
|
};
|
||||||
} else {
|
let src_reg = if insn.flag(ThumbInstruction::FLAG_H2) {
|
||||||
insn.rd()
|
insn.rs() + 8
|
||||||
};
|
} else {
|
||||||
let src_reg = if insn.flag(ThumbInstruction::FLAG_H2) {
|
insn.rs()
|
||||||
insn.rs() + 8
|
};
|
||||||
} else {
|
let op1 = self.get_reg(dst_reg) as i32;
|
||||||
insn.rs()
|
let op2 = self.get_reg(src_reg) as i32;
|
||||||
};
|
|
||||||
let arm_alu_op: AluOpCode = insn.format5_op().into();
|
match op {
|
||||||
let set_flags = arm_alu_op.is_setting_flags();
|
OpFormat5::BX => return self.exec_thumb_bx(sb, insn),
|
||||||
let op1 = self.get_reg(dst_reg) as i32;
|
OpFormat5::ADD => {
|
||||||
let op2 = self.get_reg(src_reg) as i32;
|
self.set_reg(dst_reg, op1.wrapping_add(op2) as u32);
|
||||||
let alu_res = if set_flags {
|
if dst_reg == REG_PC {
|
||||||
self.alu_flags(arm_alu_op, op1, op2)
|
self.flush_pipeline(sb);
|
||||||
} else {
|
}
|
||||||
Some(self.alu(arm_alu_op, op1, op2))
|
}
|
||||||
};
|
OpFormat5::CMP => {
|
||||||
if let Some(result) = alu_res {
|
let mut carry = self.cpsr.C();
|
||||||
self.set_reg(dst_reg, result as u32);
|
let mut overflow = self.cpsr.V();
|
||||||
|
let result = Self::alu_sub_flags(op1, op2, &mut carry, &mut overflow);
|
||||||
|
self.alu_update_flags(result, true, carry, overflow);
|
||||||
|
}
|
||||||
|
OpFormat5::MOV => {
|
||||||
|
self.set_reg(dst_reg, op2 as u32);
|
||||||
if dst_reg == REG_PC {
|
if dst_reg == REG_PC {
|
||||||
self.flush_pipeline(sb);
|
self.flush_pipeline(sb);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
self.S_cycle16(sb, self.pc + 2);
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
self.S_cycle16(sb, self.pc + 2);
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn exec_thumb_ldr_pc(&mut self, sb: &mut SysBus, insn: ThumbInstruction) -> CpuExecResult {
|
fn exec_thumb_ldr_pc(&mut self, sb: &mut SysBus, insn: ThumbInstruction) -> CpuExecResult {
|
||||||
let addr = (insn.pc & !0b10) + 4 + (insn.word8() as Addr);
|
let addr = (insn.pc & !0b10) + 4 + (insn.word8() as Addr);
|
||||||
self.S_cycle16(sb, self.pc + 2);
|
self.S_cycle16(sb, self.pc + 2);
|
||||||
let data = sb.read_32(addr);
|
let data = self.ldr_word(addr, sb);
|
||||||
self.N_cycle16(sb, addr);
|
self.N_cycle16(sb, addr);
|
||||||
|
|
||||||
self.set_reg(insn.rd(), data);
|
self.set_reg(insn.rd(), data);
|
||||||
|
@ -202,10 +240,10 @@ impl Core {
|
||||||
let value = self.get_reg(insn.rd());
|
let value = self.get_reg(insn.rd());
|
||||||
if insn.is_transferring_bytes() {
|
if insn.is_transferring_bytes() {
|
||||||
self.N_cycle8(sb, addr);
|
self.N_cycle8(sb, addr);
|
||||||
sb.write_8(addr, value as u8);
|
self.write_8(addr, value as u8, sb);
|
||||||
} else {
|
} else {
|
||||||
self.N_cycle32(sb, addr);
|
self.N_cycle32(sb, addr);
|
||||||
sb.write_32(addr, value);
|
self.write_32(addr, value, sb);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -236,7 +274,7 @@ impl Core {
|
||||||
(false, false) =>
|
(false, false) =>
|
||||||
/* strh */
|
/* strh */
|
||||||
{
|
{
|
||||||
sb.write_16(addr, self.gpr[rd] as u16);
|
self.write_16(addr, self.gpr[rd] as u16, sb);
|
||||||
self.N_cycle16(sb, addr);
|
self.N_cycle16(sb, addr);
|
||||||
}
|
}
|
||||||
(false, true) =>
|
(false, true) =>
|
||||||
|
@ -273,7 +311,7 @@ impl Core {
|
||||||
sb: &mut SysBus,
|
sb: &mut SysBus,
|
||||||
insn: ThumbInstruction,
|
insn: ThumbInstruction,
|
||||||
) -> CpuExecResult {
|
) -> CpuExecResult {
|
||||||
let offset = if insn.is_transferring_bytes() {
|
let offset = if insn.raw.bit(12) {
|
||||||
insn.offset5()
|
insn.offset5()
|
||||||
} else {
|
} else {
|
||||||
(insn.offset5() << 3) >> 1
|
(insn.offset5() << 3) >> 1
|
||||||
|
@ -295,7 +333,7 @@ impl Core {
|
||||||
self.add_cycle();
|
self.add_cycle();
|
||||||
self.gpr[insn.rd()] = data as u32;
|
self.gpr[insn.rd()] = data as u32;
|
||||||
} else {
|
} else {
|
||||||
sb.write_16(addr, self.gpr[insn.rd()] as u16);
|
self.write_16(addr, self.gpr[insn.rd()] as u16, sb);
|
||||||
self.N_cycle16(sb, addr);
|
self.N_cycle16(sb, addr);
|
||||||
}
|
}
|
||||||
self.N_cycle16(sb, self.pc + 2);
|
self.N_cycle16(sb, self.pc + 2);
|
||||||
|
@ -329,12 +367,12 @@ impl Core {
|
||||||
addr: Addr,
|
addr: Addr,
|
||||||
) -> CpuExecResult {
|
) -> CpuExecResult {
|
||||||
if insn.is_load() {
|
if insn.is_load() {
|
||||||
let data = sb.read_32(addr);
|
let data = self.ldr_word(addr, sb);
|
||||||
self.S_cycle16(sb, addr);
|
self.S_cycle16(sb, addr);
|
||||||
self.add_cycle();
|
self.add_cycle();
|
||||||
self.gpr[insn.rd()] = data;
|
self.gpr[insn.rd()] = data;
|
||||||
} else {
|
} else {
|
||||||
sb.write_32(addr, self.gpr[insn.rd()]);
|
self.write_32(addr, self.gpr[insn.rd()], sb);
|
||||||
self.N_cycle16(sb, addr);
|
self.N_cycle16(sb, addr);
|
||||||
}
|
}
|
||||||
self.N_cycle16(sb, self.pc + 2);
|
self.N_cycle16(sb, self.pc + 2);
|
||||||
|
@ -344,9 +382,8 @@ impl Core {
|
||||||
fn exec_thumb_add_sp(&mut self, sb: &mut SysBus, insn: ThumbInstruction) -> CpuExecResult {
|
fn exec_thumb_add_sp(&mut self, sb: &mut SysBus, insn: ThumbInstruction) -> CpuExecResult {
|
||||||
let op1 = self.gpr[REG_SP] as i32;
|
let op1 = self.gpr[REG_SP] as i32;
|
||||||
let op2 = insn.sword7();
|
let op2 = insn.sword7();
|
||||||
let arm_alu_op = AluOpCode::ADD;
|
|
||||||
|
|
||||||
self.gpr[REG_SP] = self.alu(arm_alu_op, op1, op2) as u32;
|
self.gpr[REG_SP] = op1.wrapping_add(op2) as u32;
|
||||||
self.S_cycle16(sb, self.pc + 2);
|
self.S_cycle16(sb, self.pc + 2);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -401,41 +438,60 @@ impl Core {
|
||||||
let is_load = insn.is_load();
|
let is_load = insn.is_load();
|
||||||
let rb = insn.rb();
|
let rb = insn.rb();
|
||||||
|
|
||||||
let mut addr = self.gpr[rb];
|
let mut addr = self.gpr[rb] & !3;
|
||||||
let rlist = insn.register_list();
|
let rlist = insn.register_list();
|
||||||
self.N_cycle16(sb, self.pc);
|
self.N_cycle16(sb, self.pc);
|
||||||
let mut first = true;
|
let mut first = true;
|
||||||
if is_load {
|
let mut writeback = true;
|
||||||
for r in 0..8 {
|
|
||||||
if rlist.bit(r) {
|
if rlist != 0 {
|
||||||
let val = sb.read_32(addr);
|
if is_load {
|
||||||
if first {
|
for r in 0..8 {
|
||||||
first = false;
|
if rlist.bit(r) {
|
||||||
|
if r == rb {
|
||||||
|
writeback = false;
|
||||||
|
}
|
||||||
|
let val = self.ldr_word(addr, sb);
|
||||||
|
if first {
|
||||||
|
first = false;
|
||||||
|
self.add_cycle();
|
||||||
|
} else {
|
||||||
|
self.S_cycle16(sb, addr);
|
||||||
|
}
|
||||||
|
addr += 4;
|
||||||
self.add_cycle();
|
self.add_cycle();
|
||||||
} else {
|
self.set_reg(r, val);
|
||||||
self.S_cycle16(sb, addr);
|
}
|
||||||
|
}
|
||||||
|
self.S_cycle16(sb, self.pc + 2);
|
||||||
|
} else {
|
||||||
|
for r in 0..8 {
|
||||||
|
if rlist.bit(r) {
|
||||||
|
if first {
|
||||||
|
first = false;
|
||||||
|
} else {
|
||||||
|
self.S_cycle16(sb, addr);
|
||||||
|
}
|
||||||
|
self.write_32(addr, self.gpr[r], sb);
|
||||||
|
addr += 4;
|
||||||
}
|
}
|
||||||
addr += 4;
|
|
||||||
self.add_cycle();
|
|
||||||
self.set_reg(r, val);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
self.S_cycle16(sb, self.pc + 2);
|
|
||||||
} else {
|
} else {
|
||||||
for r in 0..8 {
|
// From gbatek.htm: Empty Rlist: R15 loaded/stored (ARMv4 only), and Rb=Rb+40h (ARMv4-v5).
|
||||||
if rlist.bit(r) {
|
if is_load {
|
||||||
if first {
|
let val = self.ldr_word(addr, sb);
|
||||||
first = false;
|
self.set_reg(REG_PC, val & !1);
|
||||||
} else {
|
self.flush_pipeline(sb);
|
||||||
self.S_cycle16(sb, addr);
|
} else {
|
||||||
}
|
self.write_32(addr, self.pc + 2, sb);
|
||||||
sb.write_32(addr, self.gpr[r]);
|
|
||||||
addr += 4;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
addr += 0x40;
|
||||||
}
|
}
|
||||||
|
|
||||||
self.gpr[rb] = addr as u32;
|
if writeback {
|
||||||
|
self.gpr[rb] = addr;
|
||||||
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -493,7 +549,6 @@ impl Core {
|
||||||
ThumbFormat::MoveShiftedReg => self.exec_thumb_move_shifted_reg(bus, insn),
|
ThumbFormat::MoveShiftedReg => self.exec_thumb_move_shifted_reg(bus, insn),
|
||||||
ThumbFormat::AddSub => self.exec_thumb_add_sub(bus, insn),
|
ThumbFormat::AddSub => self.exec_thumb_add_sub(bus, insn),
|
||||||
ThumbFormat::DataProcessImm => self.exec_thumb_data_process_imm(bus, insn),
|
ThumbFormat::DataProcessImm => self.exec_thumb_data_process_imm(bus, insn),
|
||||||
ThumbFormat::Mul => self.exec_thumb_mul(bus, insn),
|
|
||||||
ThumbFormat::AluOps => self.exec_thumb_alu_ops(bus, insn),
|
ThumbFormat::AluOps => self.exec_thumb_alu_ops(bus, insn),
|
||||||
ThumbFormat::HiRegOpOrBranchExchange => self.exec_thumb_hi_reg_op_or_bx(bus, insn),
|
ThumbFormat::HiRegOpOrBranchExchange => self.exec_thumb_hi_reg_op_or_bx(bus, insn),
|
||||||
ThumbFormat::LdrPc => self.exec_thumb_ldr_pc(bus, insn),
|
ThumbFormat::LdrPc => self.exec_thumb_ldr_pc(bus, insn),
|
||||||
|
|
|
@ -43,8 +43,6 @@ pub enum ThumbFormat {
|
||||||
AddSub,
|
AddSub,
|
||||||
/// Format 3
|
/// Format 3
|
||||||
DataProcessImm,
|
DataProcessImm,
|
||||||
/// Belongs to Format 4, but decoded seperatly because AluOpCode doesn't have MUL
|
|
||||||
Mul,
|
|
||||||
/// Format 4
|
/// Format 4
|
||||||
AluOps,
|
AluOps,
|
||||||
/// Format 5
|
/// Format 5
|
||||||
|
@ -98,8 +96,6 @@ impl InstructionDecoder for ThumbInstruction {
|
||||||
Ok(MoveShiftedReg)
|
Ok(MoveShiftedReg)
|
||||||
} else if raw & 0xe000 == 0x2000 {
|
} else if raw & 0xe000 == 0x2000 {
|
||||||
Ok(DataProcessImm)
|
Ok(DataProcessImm)
|
||||||
} else if raw & 0xffc0 == 0x4340 {
|
|
||||||
Ok(Mul)
|
|
||||||
} else if raw & 0xfc00 == 0x4000 {
|
} else if raw & 0xfc00 == 0x4000 {
|
||||||
Ok(AluOps)
|
Ok(AluOps)
|
||||||
} else if raw & 0xfc00 == 0x4400 {
|
} else if raw & 0xfc00 == 0x4400 {
|
||||||
|
@ -183,6 +179,43 @@ pub enum OpFormat5 {
|
||||||
BX = 3,
|
BX = 3,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Primitive, PartialEq)]
|
||||||
|
pub enum ThumbAluOps {
|
||||||
|
AND = 0b0000,
|
||||||
|
EOR = 0b0001,
|
||||||
|
LSL = 0b0010,
|
||||||
|
LSR = 0b0011,
|
||||||
|
ASR = 0b0100,
|
||||||
|
ADC = 0b0101,
|
||||||
|
SBC = 0b0110,
|
||||||
|
ROR = 0b0111,
|
||||||
|
TST = 0b1000,
|
||||||
|
NEG = 0b1001,
|
||||||
|
CMP = 0b1010,
|
||||||
|
CMN = 0b1011,
|
||||||
|
ORR = 0b1100,
|
||||||
|
MUL = 0b1101,
|
||||||
|
BIC = 0b1110,
|
||||||
|
MVN = 0b1111,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ThumbAluOps {
|
||||||
|
pub fn is_setting_flags(&self) -> bool {
|
||||||
|
use ThumbAluOps::*;
|
||||||
|
match self {
|
||||||
|
TST | CMP | CMN => true,
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn is_arithmetic(&self) -> bool {
|
||||||
|
use ThumbAluOps::*;
|
||||||
|
match self {
|
||||||
|
ADC | SBC | NEG | CMP | CMN => true,
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl From<OpFormat5> for AluOpCode {
|
impl From<OpFormat5> for AluOpCode {
|
||||||
fn from(op: OpFormat5) -> AluOpCode {
|
fn from(op: OpFormat5) -> AluOpCode {
|
||||||
match op {
|
match op {
|
||||||
|
@ -245,49 +278,8 @@ impl ThumbInstruction {
|
||||||
OpFormat5::from_u8(self.raw.bit_range(8..10) as u8).unwrap()
|
OpFormat5::from_u8(self.raw.bit_range(8..10) as u8).unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn alu_opcode(&self) -> (AluOpCode, Option<BarrelShifterValue>) {
|
pub fn format4_alu_op(&self) -> ThumbAluOps {
|
||||||
use ShiftRegisterBy::*;
|
ThumbAluOps::from_u16(self.raw.bit_range(6..10)).unwrap()
|
||||||
match self.raw.bit_range(6..10) {
|
|
||||||
0b0010 => (
|
|
||||||
AluOpCode::MOV,
|
|
||||||
Some(BarrelShifterValue::ShiftedRegister(ShiftedRegister {
|
|
||||||
reg: self.rd(),
|
|
||||||
shift_by: ByRegister(self.rs()),
|
|
||||||
bs_op: BarrelShiftOpCode::LSL,
|
|
||||||
added: Some(true),
|
|
||||||
})),
|
|
||||||
),
|
|
||||||
0b0011 => (
|
|
||||||
AluOpCode::MOV,
|
|
||||||
Some(BarrelShifterValue::ShiftedRegister(ShiftedRegister {
|
|
||||||
reg: self.rd(),
|
|
||||||
shift_by: ByRegister(self.rs()),
|
|
||||||
bs_op: BarrelShiftOpCode::LSR,
|
|
||||||
added: Some(true),
|
|
||||||
})),
|
|
||||||
),
|
|
||||||
0b0100 => (
|
|
||||||
AluOpCode::MOV,
|
|
||||||
Some(BarrelShifterValue::ShiftedRegister(ShiftedRegister {
|
|
||||||
reg: self.rd(),
|
|
||||||
shift_by: ByRegister(self.rs()),
|
|
||||||
bs_op: BarrelShiftOpCode::ASR,
|
|
||||||
added: Some(true),
|
|
||||||
})),
|
|
||||||
),
|
|
||||||
0b0111 => (
|
|
||||||
AluOpCode::MOV,
|
|
||||||
Some(BarrelShifterValue::ShiftedRegister(ShiftedRegister {
|
|
||||||
reg: self.rd(),
|
|
||||||
shift_by: ByRegister(self.rs()),
|
|
||||||
bs_op: BarrelShiftOpCode::ROR,
|
|
||||||
added: Some(true),
|
|
||||||
})),
|
|
||||||
),
|
|
||||||
0b1001 => (AluOpCode::RSB, Some(BarrelShifterValue::ImmediateValue(0))),
|
|
||||||
0b1101 => panic!("tried to decode MUL"),
|
|
||||||
op => (AluOpCode::from_u16(op).unwrap(), None),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn offset5(&self) -> i8 {
|
pub fn offset5(&self) -> i8 {
|
||||||
|
|
|
@ -455,7 +455,7 @@ impl SyncedIoDevice for Gpu {
|
||||||
.set_vcount(self.dispstat.vcount_setting() == self.current_scanline as u16);
|
.set_vcount(self.dispstat.vcount_setting() == self.current_scanline as u16);
|
||||||
}
|
}
|
||||||
if self.dispstat.vcount_irq_enable() && self.dispstat.get_vcount() {
|
if self.dispstat.vcount_irq_enable() && self.dispstat.get_vcount() {
|
||||||
irqs.set_LCD_VCounterMatch(true);;
|
irqs.set_LCD_VCounterMatch(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
match self.state {
|
match self.state {
|
||||||
|
|
|
@ -7,125 +7,6 @@ use super::gpu::regs::WindowFlags;
|
||||||
use super::keypad;
|
use super::keypad;
|
||||||
use super::sysbus::BoxedMemory;
|
use super::sysbus::BoxedMemory;
|
||||||
|
|
||||||
pub mod consts {
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
pub const IO_BASE: Addr = 0x0400_0000;
|
|
||||||
|
|
||||||
// LCD I/O Registers
|
|
||||||
pub const REG_DISPCNT: Addr = IO_BASE + 0x_0000; // 2 R/W LCD Control
|
|
||||||
pub const REG_DISPSTAT: Addr = IO_BASE + 0x_0004; // 2 R/W General LCD Status (STAT,LYC)
|
|
||||||
pub const REG_VCOUNT: Addr = IO_BASE + 0x_0006; // 2 R Vertical Counter (LY)
|
|
||||||
pub const REG_BG0CNT: Addr = IO_BASE + 0x_0008; // 2 R/W BG0 Control
|
|
||||||
pub const REG_BG1CNT: Addr = IO_BASE + 0x_000A; // 2 R/W BG1 Control
|
|
||||||
pub const REG_BG2CNT: Addr = IO_BASE + 0x_000C; // 2 R/W BG2 Control
|
|
||||||
pub const REG_BG3CNT: Addr = IO_BASE + 0x_000E; // 2 R/W BG3 Control
|
|
||||||
pub const REG_BG0HOFS: Addr = IO_BASE + 0x_0010; // 2 W BG0 X-Offset
|
|
||||||
pub const REG_BG0VOFS: Addr = IO_BASE + 0x_0012; // 2 W BG0 Y-Offset
|
|
||||||
pub const REG_BG1HOFS: Addr = IO_BASE + 0x_0014; // 2 W BG1 X-Offset
|
|
||||||
pub const REG_BG1VOFS: Addr = IO_BASE + 0x_0016; // 2 W BG1 Y-Offset
|
|
||||||
pub const REG_BG2HOFS: Addr = IO_BASE + 0x_0018; // 2 W BG2 X-Offset
|
|
||||||
pub const REG_BG2VOFS: Addr = IO_BASE + 0x_001A; // 2 W BG2 Y-Offset
|
|
||||||
pub const REG_BG3HOFS: Addr = IO_BASE + 0x_001C; // 2 W BG3 X-Offset
|
|
||||||
pub const REG_BG3VOFS: Addr = IO_BASE + 0x_001E; // 2 W BG3 Y-Offset
|
|
||||||
pub const REG_BG2PA: Addr = IO_BASE + 0x_0020; // 2 W BG2 Rotation/Scaling Parameter A (dx)
|
|
||||||
pub const REG_BG2PB: Addr = IO_BASE + 0x_0022; // 2 W BG2 Rotation/Scaling Parameter B (dmx)
|
|
||||||
pub const REG_BG2PC: Addr = IO_BASE + 0x_0024; // 2 W BG2 Rotation/Scaling Parameter C (dy)
|
|
||||||
pub const REG_BG2PD: Addr = IO_BASE + 0x_0026; // 2 W BG2 Rotation/Scaling Parameter D (dmy)
|
|
||||||
pub const REG_BG2X_L: Addr = IO_BASE + 0x_0028; // 4 W BG2 Reference Point X-Coordinate, lower 16 bit
|
|
||||||
pub const REG_BG2X_H: Addr = IO_BASE + 0x_002A; // 4 W BG2 Reference Point X-Coordinate, upper 16 bit
|
|
||||||
pub const REG_BG2Y_L: Addr = IO_BASE + 0x_002C; // 4 W BG2 Reference Point Y-Coordinate, lower 16 bit
|
|
||||||
pub const REG_BG2Y_H: Addr = IO_BASE + 0x_002E; // 4 W BG2 Reference Point Y-Coordinate, upper 16 bit
|
|
||||||
pub const REG_BG3PA: Addr = IO_BASE + 0x_0030; // 2 W BG3 Rotation/Scaling Parameter A (dx)
|
|
||||||
pub const REG_BG3PB: Addr = IO_BASE + 0x_0032; // 2 W BG3 Rotation/Scaling Parameter B (dmx)
|
|
||||||
pub const REG_BG3PC: Addr = IO_BASE + 0x_0034; // 2 W BG3 Rotation/Scaling Parameter C (dy)
|
|
||||||
pub const REG_BG3PD: Addr = IO_BASE + 0x_0036; // 2 W BG3 Rotation/Scaling Parameter D (dmy)
|
|
||||||
pub const REG_BG3X_L: Addr = IO_BASE + 0x_0038; // 4 W BG3 Reference Point X-Coordinate, lower 16 bit
|
|
||||||
pub const REG_BG3X_H: Addr = IO_BASE + 0x_003A; // 4 W BG3 Reference Point X-Coordinate, upper 16 bit
|
|
||||||
pub const REG_BG3Y_L: Addr = IO_BASE + 0x_003C; // 4 W BG3 Reference Point Y-Coordinate, lower 16 bit
|
|
||||||
pub const REG_BG3Y_H: Addr = IO_BASE + 0x_003E; // 4 W BG3 Reference Point Y-Coordinate, upper 16 bit
|
|
||||||
pub const REG_WIN0H: Addr = IO_BASE + 0x_0040; // 2 W Window 0 Horizontal Dimensions
|
|
||||||
pub const REG_WIN1H: Addr = IO_BASE + 0x_0042; // 2 W Window 1 Horizontal Dimensions
|
|
||||||
pub const REG_WIN0V: Addr = IO_BASE + 0x_0044; // 2 W Window 0 Vertical Dimensions
|
|
||||||
pub const REG_WIN1V: Addr = IO_BASE + 0x_0046; // 2 W Window 1 Vertical Dimensions
|
|
||||||
pub const REG_WININ: Addr = IO_BASE + 0x_0048; // 2 R/W Inside of Window 0 and 1
|
|
||||||
pub const REG_WINOUT: Addr = IO_BASE + 0x_004A; // 2 R/W Inside of OBJ Window & Outside of Windows
|
|
||||||
pub const REG_MOSAIC: Addr = IO_BASE + 0x_004C; // 2 W Mosaic Size
|
|
||||||
pub const REG_BLDCNT: Addr = IO_BASE + 0x_0050; // 2 R/W Color Special Effects Selection
|
|
||||||
pub const REG_BLDALPHA: Addr = IO_BASE + 0x_0052; // 2 R/W Alpha Blending Coefficients
|
|
||||||
pub const REG_BLDY: Addr = IO_BASE + 0x_0054; // 2 W Brightness (Fade-In/Out) Coefficient
|
|
||||||
// Sound Registers
|
|
||||||
pub const REG_SOUND1CNT_L: Addr = IO_BASE + 0x_0060; // 2 R/W Channel 1 Sweep register (NR10)
|
|
||||||
pub const REG_SOUND1CNT_H: Addr = IO_BASE + 0x_0062; // 2 R/W Channel 1 Duty/Length/Envelope (NR11, NR12)
|
|
||||||
pub const REG_SOUND1CNT_X: Addr = IO_BASE + 0x_0064; // 2 R/W Channel 1 Frequency/Control (NR13, NR14)
|
|
||||||
pub const REG_SOUND2CNT_L: Addr = IO_BASE + 0x_0068; // 2 R/W Channel 2 Duty/Length/Envelope (NR21, NR22)
|
|
||||||
pub const REG_SOUND2CNT_H: Addr = IO_BASE + 0x_006C; // 2 R/W Channel 2 Frequency/Control (NR23, NR24)
|
|
||||||
pub const REG_SOUND3CNT_L: Addr = IO_BASE + 0x_0070; // 2 R/W Channel 3 Stop/Wave RAM select (NR30)
|
|
||||||
pub const REG_SOUND3CNT_H: Addr = IO_BASE + 0x_0072; // 2 R/W Channel 3 Length/Volume (NR31, NR32)
|
|
||||||
pub const REG_SOUND3CNT_X: Addr = IO_BASE + 0x_0074; // 2 R/W Channel 3 Frequency/Control (NR33, NR34)
|
|
||||||
pub const REG_SOUND4CNT_L: Addr = IO_BASE + 0x_0078; // 2 R/W Channel 4 Length/Envelope (NR41, NR42)
|
|
||||||
pub const REG_SOUND4CNT_H: Addr = IO_BASE + 0x_007C; // 2 R/W Channel 4 Frequency/Control (NR43, NR44)
|
|
||||||
pub const REG_SOUNDCNT_L: Addr = IO_BASE + 0x_0080; // 2 R/W Control Stereo/Volume/Enable (NR50, NR51)
|
|
||||||
pub const REG_SOUNDCNT_H: Addr = IO_BASE + 0x_0082; // 2 R/W Control Mixing/DMA Control
|
|
||||||
pub const REG_SOUNDCNT_X: Addr = IO_BASE + 0x_0084; // 2 R/W Control Sound on/off (NR52)
|
|
||||||
pub const REG_SOUNDBIAS: Addr = IO_BASE + 0x_0088; // 2 BIOS Sound PWM Control
|
|
||||||
pub const REG_WAVE_RAM: Addr = IO_BASE + 0x_0090; // Channel 3 Wave Pattern RAM (2 banks!!)
|
|
||||||
pub const REG_FIFO_A: Addr = IO_BASE + 0x_00A0; // 4 W Channel A FIFO, Data 0-3
|
|
||||||
pub const REG_FIFO_B: Addr = IO_BASE + 0x_00A4; // 4 W Channel B FIFO, Data 0-3
|
|
||||||
// DMA Transfer Channels
|
|
||||||
pub const REG_DMA0SAD: Addr = IO_BASE + 0x_00B0; // 4 W DMA 0 Source Address
|
|
||||||
pub const REG_DMA0DAD: Addr = IO_BASE + 0x_00B4; // 4 W DMA 0 Destination Address
|
|
||||||
pub const REG_DMA0CNT_L: Addr = IO_BASE + 0x_00B8; // 2 W DMA 0 Word Count
|
|
||||||
pub const REG_DMA0CNT_H: Addr = IO_BASE + 0x_00BA; // 2 R/W DMA 0 Control
|
|
||||||
pub const REG_DMA1SAD: Addr = IO_BASE + 0x_00BC; // 4 W DMA 1 Source Address
|
|
||||||
pub const REG_DMA1DAD: Addr = IO_BASE + 0x_00C0; // 4 W DMA 1 Destination Address
|
|
||||||
pub const REG_DMA1CNT_L: Addr = IO_BASE + 0x_00C4; // 2 W DMA 1 Word Count
|
|
||||||
pub const REG_DMA1CNT_H: Addr = IO_BASE + 0x_00C6; // 2 R/W DMA 1 Control
|
|
||||||
pub const REG_DMA2SAD: Addr = IO_BASE + 0x_00C8; // 4 W DMA 2 Source Address
|
|
||||||
pub const REG_DMA2DAD: Addr = IO_BASE + 0x_00CC; // 4 W DMA 2 Destination Address
|
|
||||||
pub const REG_DMA2CNT_L: Addr = IO_BASE + 0x_00D0; // 2 W DMA 2 Word Count
|
|
||||||
pub const REG_DMA2CNT_H: Addr = IO_BASE + 0x_00D2; // 2 R/W DMA 2 Control
|
|
||||||
pub const REG_DMA3SAD: Addr = IO_BASE + 0x_00D4; // 4 W DMA 3 Source Address
|
|
||||||
pub const REG_DMA3DAD: Addr = IO_BASE + 0x_00D8; // 4 W DMA 3 Destination Address
|
|
||||||
pub const REG_DMA3CNT_L: Addr = IO_BASE + 0x_00DC; // 2 W DMA 3 Word Count
|
|
||||||
pub const REG_DMA3CNT_H: Addr = IO_BASE + 0x_00DE; // 2 R/W DMA 3 Control
|
|
||||||
// Timer Registers
|
|
||||||
pub const REG_TM0CNT_L: Addr = IO_BASE + 0x_0100; // 2 R/W Timer 0 Counter/Reload
|
|
||||||
pub const REG_TM0CNT_H: Addr = IO_BASE + 0x_0102; // 2 R/W Timer 0 Control
|
|
||||||
pub const REG_TM1CNT_L: Addr = IO_BASE + 0x_0104; // 2 R/W Timer 1 Counter/Reload
|
|
||||||
pub const REG_TM1CNT_H: Addr = IO_BASE + 0x_0106; // 2 R/W Timer 1 Control
|
|
||||||
pub const REG_TM2CNT_L: Addr = IO_BASE + 0x_0108; // 2 R/W Timer 2 Counter/Reload
|
|
||||||
pub const REG_TM2CNT_H: Addr = IO_BASE + 0x_010A; // 2 R/W Timer 2 Control
|
|
||||||
pub const REG_TM3CNT_L: Addr = IO_BASE + 0x_010C; // 2 R/W Timer 3 Counter/Reload
|
|
||||||
pub const REG_TM3CNT_H: Addr = IO_BASE + 0x_010E; // 2 R/W Timer 3 Control
|
|
||||||
// Serial Communication (1)
|
|
||||||
pub const REG_SIODATA32: Addr = IO_BASE + 0x_0120; // 4 R/W SIO Data (Normal-32bit Mode; shared with below)
|
|
||||||
pub const REG_SIOMULTI0: Addr = IO_BASE + 0x_0120; // 2 R/W SIO Data 0 (Parent) (Multi-Player Mode)
|
|
||||||
pub const REG_SIOMULTI1: Addr = IO_BASE + 0x_0122; // 2 R/W SIO Data 1 (1st Child) (Multi-Player Mode)
|
|
||||||
pub const REG_SIOMULTI2: Addr = IO_BASE + 0x_0124; // 2 R/W SIO Data 2 (2nd Child) (Multi-Player Mode)
|
|
||||||
pub const REG_SIOMULTI3: Addr = IO_BASE + 0x_0126; // 2 R/W SIO Data 3 (3rd Child) (Multi-Player Mode)
|
|
||||||
pub const REG_SIOCNT: Addr = IO_BASE + 0x_0128; // 2 R/W SIO Control Register
|
|
||||||
pub const REG_SIOMLT_SEND: Addr = IO_BASE + 0x_012A; // 2 R/W SIO Data (Local of MultiPlayer; shared below)
|
|
||||||
pub const REG_SIODATA8: Addr = IO_BASE + 0x_012A; // 2 R/W SIO Data (Normal-8bit and UART Mode)
|
|
||||||
// Keypad Input
|
|
||||||
pub const REG_KEYINPUT: Addr = IO_BASE + 0x_0130; // 2 R Key Status
|
|
||||||
pub const REG_KEYCNT: Addr = IO_BASE + 0x_0132; // 2 R/W Key Interrupt Control
|
|
||||||
// Serial Communication (2)
|
|
||||||
pub const REG_RCNT: Addr = IO_BASE + 0x_0134; // 2 R/W SIO Mode Select/General Purpose Data
|
|
||||||
pub const REG_IR: Addr = IO_BASE + 0x_0136; // - - Ancient - Infrared Register (Prototypes only)
|
|
||||||
pub const REG_JOYCNT: Addr = IO_BASE + 0x_0140; // 2 R/W SIO JOY Bus Control
|
|
||||||
pub const REG_JOY_RECV: Addr = IO_BASE + 0x_0150; // 4 R/W SIO JOY Bus Receive Data
|
|
||||||
pub const REG_JOY_TRANS: Addr = IO_BASE + 0x_0154; // 4 R/W SIO JOY Bus Transmit Data
|
|
||||||
pub const REG_JOYSTAT: Addr = IO_BASE + 0x_0158; // 2 R/? SIO JOY Bus Receive Status
|
|
||||||
// Interrupt, Waitstate, and Power-Down Control
|
|
||||||
pub const REG_IE: Addr = IO_BASE + 0x_0200; // 2 R/W Interrupt Enable Register
|
|
||||||
pub const REG_IF: Addr = IO_BASE + 0x_0202; // 2 R/W Interrupt Request Flags / IRQ Acknowledge
|
|
||||||
pub const REG_WAITCNT: Addr = IO_BASE + 0x_0204; // 2 R/W Game Pak Waitstate Control
|
|
||||||
pub const REG_IME: Addr = IO_BASE + 0x_0208; // 2 R/W Interrupt Master Enable Register
|
|
||||||
pub const REG_POSTFLG: Addr = IO_BASE + 0x_0300; // 1 R/W Undocumented - Post Boot Flag
|
|
||||||
pub const REG_HALTCNT: Addr = IO_BASE + 0x_0301; // 1 W Undocumented - Power Down Control
|
|
||||||
}
|
|
||||||
|
|
||||||
use consts::*;
|
use consts::*;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
@ -140,7 +21,7 @@ pub struct IoRegs {
|
||||||
impl IoRegs {
|
impl IoRegs {
|
||||||
pub fn new(io: Rc<RefCell<IoDevices>>) -> IoRegs {
|
pub fn new(io: Rc<RefCell<IoDevices>>) -> IoRegs {
|
||||||
IoRegs {
|
IoRegs {
|
||||||
mem: BoxedMemory::new(vec![0; 0x400].into_boxed_slice(), 0x3ff),
|
mem: BoxedMemory::new(vec![0; 0x800].into_boxed_slice()),
|
||||||
io: io,
|
io: io,
|
||||||
post_boot_flag: false,
|
post_boot_flag: false,
|
||||||
keyinput: keypad::KEYINPUT_ALL_RELEASED,
|
keyinput: keypad::KEYINPUT_ALL_RELEASED,
|
||||||
|
@ -200,7 +81,12 @@ impl Bus for IoRegs {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn read_8(&self, addr: Addr) -> u8 {
|
fn read_8(&self, addr: Addr) -> u8 {
|
||||||
self.read_16(addr) as u8
|
let t = self.read_16(addr & !1);
|
||||||
|
if addr & 1 != 0 {
|
||||||
|
(t >> 8) as u8
|
||||||
|
} else {
|
||||||
|
t as u8
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn write_32(&mut self, addr: Addr, value: u32) {
|
fn write_32(&mut self, addr: Addr, value: u32) {
|
||||||
|
@ -211,12 +97,12 @@ impl Bus for IoRegs {
|
||||||
fn write_16(&mut self, addr: Addr, value: u16) {
|
fn write_16(&mut self, addr: Addr, value: u16) {
|
||||||
let mut io = self.io.borrow_mut();
|
let mut io = self.io.borrow_mut();
|
||||||
match addr + IO_BASE {
|
match addr + IO_BASE {
|
||||||
REG_DISPCNT => io.gpu.dispcnt.0 |= value,
|
REG_DISPCNT => io.gpu.dispcnt.0 = value,
|
||||||
REG_DISPSTAT => io.gpu.dispstat.0 |= value,
|
REG_DISPSTAT => io.gpu.dispstat.0 |= value & !3,
|
||||||
REG_BG0CNT => io.gpu.bg[0].bgcnt.0 |= value,
|
REG_BG0CNT => io.gpu.bg[0].bgcnt.0 = value,
|
||||||
REG_BG1CNT => io.gpu.bg[1].bgcnt.0 |= value,
|
REG_BG1CNT => io.gpu.bg[1].bgcnt.0 = value,
|
||||||
REG_BG2CNT => io.gpu.bg[2].bgcnt.0 |= value,
|
REG_BG2CNT => io.gpu.bg[2].bgcnt.0 = value,
|
||||||
REG_BG3CNT => io.gpu.bg[3].bgcnt.0 |= value,
|
REG_BG3CNT => io.gpu.bg[3].bgcnt.0 = value,
|
||||||
REG_BG0HOFS => io.gpu.bg[0].bghofs = value & 0x1ff,
|
REG_BG0HOFS => io.gpu.bg[0].bghofs = value & 0x1ff,
|
||||||
REG_BG0VOFS => io.gpu.bg[0].bgvofs = value & 0x1ff,
|
REG_BG0VOFS => io.gpu.bg[0].bgvofs = value & 0x1ff,
|
||||||
REG_BG1HOFS => io.gpu.bg[1].bghofs = value & 0x1ff,
|
REG_BG1HOFS => io.gpu.bg[1].bghofs = value & 0x1ff,
|
||||||
|
@ -311,24 +197,25 @@ impl Bus for IoRegs {
|
||||||
REG_POSTFLG => self.post_boot_flag = value != 0,
|
REG_POSTFLG => self.post_boot_flag = value != 0,
|
||||||
REG_HALTCNT => {}
|
REG_HALTCNT => {}
|
||||||
_ => {
|
_ => {
|
||||||
|
let ioreg_addr = IO_BASE + addr;
|
||||||
|
println!(
|
||||||
|
"Unimplemented write to {:x} {}",
|
||||||
|
ioreg_addr,
|
||||||
|
io_reg_string(ioreg_addr)
|
||||||
|
);
|
||||||
self.mem.write_16(addr, value);
|
self.mem.write_16(addr, value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn write_8(&mut self, addr: Addr, value: u8) {
|
fn write_8(&mut self, addr: Addr, value: u8) {
|
||||||
if addr & 1 != 0 {
|
let t = self.read_16(addr & !1);
|
||||||
let addr = addr & !1;
|
let t = if addr & 1 != 0 {
|
||||||
let t = self.read_16(addr);
|
(t & 0xff) | (value as u16) << 8
|
||||||
let upper = (value as u16);
|
|
||||||
let lower = t & 0xff;
|
|
||||||
self.write_16(addr, (upper << 8) | lower);
|
|
||||||
} else {
|
} else {
|
||||||
let t = self.read_16(addr);
|
(t & 0xff00) | (value as u16)
|
||||||
let upper = t << 8;
|
};
|
||||||
let lower = (value as u16);
|
self.write_16(addr, t);
|
||||||
self.write_16(addr, (upper << 8) | lower);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -348,3 +235,226 @@ bitfield! {
|
||||||
PHI_terminal_output, _: 12, 11;
|
PHI_terminal_output, _: 12, 11;
|
||||||
prefetch, _: 14;
|
prefetch, _: 14;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[rustfmt::skip]
|
||||||
|
pub mod consts {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
pub const IO_BASE: Addr = 0x0400_0000;
|
||||||
|
|
||||||
|
pub const REG_DISPCNT: Addr = 0x0400_0000; // 2 R/W LCD Control
|
||||||
|
pub const REG_GREENSWAP: Addr = 0x0400_0002; // 2 R/W Undocumented - Green Swap
|
||||||
|
pub const REG_DISPSTAT: Addr = 0x0400_0004; // 2 R/W General LCD Status (STAT,LYC)
|
||||||
|
pub const REG_VCOUNT: Addr = 0x0400_0006; // 2 R Vertical Counter (LY)
|
||||||
|
pub const REG_BG0CNT: Addr = 0x0400_0008; // 2 R/W BG0 Control
|
||||||
|
pub const REG_BG1CNT: Addr = 0x0400_000A; // 2 R/W BG1 Control
|
||||||
|
pub const REG_BG2CNT: Addr = 0x0400_000C; // 2 R/W BG2 Control
|
||||||
|
pub const REG_BG3CNT: Addr = 0x0400_000E; // 2 R/W BG3 Control
|
||||||
|
pub const REG_BG0HOFS: Addr = 0x0400_0010; // 2 W BG0 X-Offset
|
||||||
|
pub const REG_BG0VOFS: Addr = 0x0400_0012; // 2 W BG0 Y-Offset
|
||||||
|
pub const REG_BG1HOFS: Addr = 0x0400_0014; // 2 W BG1 X-Offset
|
||||||
|
pub const REG_BG1VOFS: Addr = 0x0400_0016; // 2 W BG1 Y-Offset
|
||||||
|
pub const REG_BG2HOFS: Addr = 0x0400_0018; // 2 W BG2 X-Offset
|
||||||
|
pub const REG_BG2VOFS: Addr = 0x0400_001A; // 2 W BG2 Y-Offset
|
||||||
|
pub const REG_BG3HOFS: Addr = 0x0400_001C; // 2 W BG3 X-Offset
|
||||||
|
pub const REG_BG3VOFS: Addr = 0x0400_001E; // 2 W BG3 Y-Offset
|
||||||
|
pub const REG_BG2PA: Addr = 0x0400_0020; // 2 W BG2 Rotation/Scaling Parameter A (dx)
|
||||||
|
pub const REG_BG2PB: Addr = 0x0400_0022; // 2 W BG2 Rotation/Scaling Parameter B (dmx)
|
||||||
|
pub const REG_BG2PC: Addr = 0x0400_0024; // 2 W BG2 Rotation/Scaling Parameter C (dy)
|
||||||
|
pub const REG_BG2PD: Addr = 0x0400_0026; // 2 W BG2 Rotation/Scaling Parameter D (dmy)
|
||||||
|
pub const REG_BG2X_L: Addr = 0x0400_0028; // 4 W BG2 Reference Point X-Coordinate, lower 16 bit
|
||||||
|
pub const REG_BG2X_H: Addr = 0x0400_002A; // 4 W BG2 Reference Point X-Coordinate, upper 16 bit
|
||||||
|
pub const REG_BG2Y_L: Addr = 0x0400_002C; // 4 W BG2 Reference Point Y-Coordinate, lower 16 bit
|
||||||
|
pub const REG_BG2Y_H: Addr = 0x0400_002E; // 4 W BG2 Reference Point Y-Coordinate, upper 16 bit
|
||||||
|
pub const REG_BG3PA: Addr = 0x0400_0030; // 2 W BG3 Rotation/Scaling Parameter A (dx)
|
||||||
|
pub const REG_BG3PB: Addr = 0x0400_0032; // 2 W BG3 Rotation/Scaling Parameter B (dmx)
|
||||||
|
pub const REG_BG3PC: Addr = 0x0400_0034; // 2 W BG3 Rotation/Scaling Parameter C (dy)
|
||||||
|
pub const REG_BG3PD: Addr = 0x0400_0036; // 2 W BG3 Rotation/Scaling Parameter D (dmy)
|
||||||
|
pub const REG_BG3X_L: Addr = 0x0400_0038; // 4 W BG3 Reference Point X-Coordinate, lower 16 bit
|
||||||
|
pub const REG_BG3X_H: Addr = 0x0400_003A; // 4 W BG3 Reference Point X-Coordinate, upper 16 bit
|
||||||
|
pub const REG_BG3Y_L: Addr = 0x0400_003C; // 4 W BG3 Reference Point Y-Coordinate, lower 16 bit
|
||||||
|
pub const REG_BG3Y_H: Addr = 0x0400_003E; // 4 W BG3 Reference Point Y-Coordinate, upper 16 bit
|
||||||
|
pub const REG_WIN0H: Addr = 0x0400_0040; // 2 W Window 0 Horizontal Dimensions
|
||||||
|
pub const REG_WIN1H: Addr = 0x0400_0042; // 2 W Window 1 Horizontal Dimensions
|
||||||
|
pub const REG_WIN0V: Addr = 0x0400_0044; // 2 W Window 0 Vertical Dimensions
|
||||||
|
pub const REG_WIN1V: Addr = 0x0400_0046; // 2 W Window 1 Vertical Dimensions
|
||||||
|
pub const REG_WININ: Addr = 0x0400_0048; // 2 R/W Inside of Window 0 and 1
|
||||||
|
pub const REG_WINOUT: Addr = 0x0400_004A; // 2 R/W Inside of OBJ Window & Outside of Windows
|
||||||
|
pub const REG_MOSAIC: Addr = 0x0400_004C; // 2 W Mosaic Size
|
||||||
|
pub const REG_BLDCNT: Addr = 0x0400_0050; // 2 R/W Color Special Effects Selection
|
||||||
|
pub const REG_BLDALPHA: Addr = 0x0400_0052; // 2 R/W Alpha Blending Coefficients
|
||||||
|
pub const REG_BLDY: Addr = 0x0400_0054; // 2 W Brightness (Fade-In/Out) Coefficient
|
||||||
|
pub const REG_SOUND1CNT_L: Addr = 0x0400_0060; // 2 R/W Channel 1 Sweep register (NR10)
|
||||||
|
pub const REG_SOUND1CNT_H: Addr = 0x0400_0062; // 2 R/W Channel 1 Duty/Length/Envelope (NR11, NR12)
|
||||||
|
pub const REG_SOUND1CNT_X: Addr = 0x0400_0064; // 2 R/W Channel 1 Frequency/Control (NR13, NR14)
|
||||||
|
pub const REG_SOUND2CNT_L: Addr = 0x0400_0068; // 2 R/W Channel 2 Duty/Length/Envelope (NR21, NR22)
|
||||||
|
pub const REG_SOUND2CNT_H: Addr = 0x0400_006C; // 2 R/W Channel 2 Frequency/Control (NR23, NR24)
|
||||||
|
pub const REG_SOUND3CNT_L: Addr = 0x0400_0070; // 2 R/W Channel 3 Stop/Wave RAM select (NR30)
|
||||||
|
pub const REG_SOUND3CNT_H: Addr = 0x0400_0072; // 2 R/W Channel 3 Length/Volume (NR31, NR32)
|
||||||
|
pub const REG_SOUND3CNT_X: Addr = 0x0400_0074; // 2 R/W Channel 3 Frequency/Control (NR33, NR34)
|
||||||
|
pub const REG_SOUND4CNT_L: Addr = 0x0400_0078; // 2 R/W Channel 4 Length/Envelope (NR41, NR42)
|
||||||
|
pub const REG_SOUND4CNT_H: Addr = 0x0400_007C; // 2 R/W Channel 4 Frequency/Control (NR43, NR44)
|
||||||
|
pub const REG_SOUNDCNT_L: Addr = 0x0400_0080; // 2 R/W Control Stereo/Volume/Enable (NR50, NR51)
|
||||||
|
pub const REG_SOUNDCNT_H: Addr = 0x0400_0082; // 2 R/W Control Mixing/DMA Control
|
||||||
|
pub const REG_SOUNDCNT_X: Addr = 0x0400_0084; // 2 R/W Control Sound on/off (NR52)
|
||||||
|
pub const REG_SOUNDBIAS: Addr = 0x0400_0088; // 2 BIOS Sound PWM Control
|
||||||
|
pub const REG_WAVE_RAM: Addr = 0x0400_0090; // Channel 3 Wave Pattern RAM (2 banks!!)
|
||||||
|
pub const REG_FIFO_A: Addr = 0x0400_00A0; // 4 W Channel A FIFO, Data 0-3
|
||||||
|
pub const REG_FIFO_B: Addr = 0x0400_00A4; // 4 W Channel B FIFO, Data 0-3
|
||||||
|
pub const REG_DMA0SAD: Addr = 0x0400_00B0; // 4 W DMA 0 Source Address
|
||||||
|
pub const REG_DMA0DAD: Addr = 0x0400_00B4; // 4 W DMA 0 Destination Address
|
||||||
|
pub const REG_DMA0CNT_L: Addr = 0x0400_00B8; // 2 W DMA 0 Word Count
|
||||||
|
pub const REG_DMA0CNT_H: Addr = 0x0400_00BA; // 2 R/W DMA 0 Control
|
||||||
|
pub const REG_DMA1SAD: Addr = 0x0400_00BC; // 4 W DMA 1 Source Address
|
||||||
|
pub const REG_DMA1DAD: Addr = 0x0400_00C0; // 4 W DMA 1 Destination Address
|
||||||
|
pub const REG_DMA1CNT_L: Addr = 0x0400_00C4; // 2 W DMA 1 Word Count
|
||||||
|
pub const REG_DMA1CNT_H: Addr = 0x0400_00C6; // 2 R/W DMA 1 Control
|
||||||
|
pub const REG_DMA2SAD: Addr = 0x0400_00C8; // 4 W DMA 2 Source Address
|
||||||
|
pub const REG_DMA2DAD: Addr = 0x0400_00CC; // 4 W DMA 2 Destination Address
|
||||||
|
pub const REG_DMA2CNT_L: Addr = 0x0400_00D0; // 2 W DMA 2 Word Count
|
||||||
|
pub const REG_DMA2CNT_H: Addr = 0x0400_00D2; // 2 R/W DMA 2 Control
|
||||||
|
pub const REG_DMA3SAD: Addr = 0x0400_00D4; // 4 W DMA 3 Source Address
|
||||||
|
pub const REG_DMA3DAD: Addr = 0x0400_00D8; // 4 W DMA 3 Destination Address
|
||||||
|
pub const REG_DMA3CNT_L: Addr = 0x0400_00DC; // 2 W DMA 3 Word Count
|
||||||
|
pub const REG_DMA3CNT_H: Addr = 0x0400_00DE; // 2 R/W DMA 3 Control
|
||||||
|
pub const REG_TM0CNT_L: Addr = 0x0400_0100; // 2 R/W Timer 0 Counter/Reload
|
||||||
|
pub const REG_TM0CNT_H: Addr = 0x0400_0102; // 2 R/W Timer 0 Control
|
||||||
|
pub const REG_TM1CNT_L: Addr = 0x0400_0104; // 2 R/W Timer 1 Counter/Reload
|
||||||
|
pub const REG_TM1CNT_H: Addr = 0x0400_0106; // 2 R/W Timer 1 Control
|
||||||
|
pub const REG_TM2CNT_L: Addr = 0x0400_0108; // 2 R/W Timer 2 Counter/Reload
|
||||||
|
pub const REG_TM2CNT_H: Addr = 0x0400_010A; // 2 R/W Timer 2 Control
|
||||||
|
pub const REG_TM3CNT_L: Addr = 0x0400_010C; // 2 R/W Timer 3 Counter/Reload
|
||||||
|
pub const REG_TM3CNT_H: Addr = 0x0400_010E; // 2 R/W Timer 3 Control
|
||||||
|
pub const REG_SIODATA32: Addr = 0x0400_0120; // 4 R/W SIO Data (Normal-32bit Mode; shared with below)
|
||||||
|
pub const REG_SIOMULTI0: Addr = 0x0400_0120; // 2 R/W SIO Data 0 (Parent) (Multi-Player Mode)
|
||||||
|
pub const REG_SIOMULTI1: Addr = 0x0400_0122; // 2 R/W SIO Data 1 (1st Child) (Multi-Player Mode)
|
||||||
|
pub const REG_SIOMULTI2: Addr = 0x0400_0124; // 2 R/W SIO Data 2 (2nd Child) (Multi-Player Mode)
|
||||||
|
pub const REG_SIOMULTI3: Addr = 0x0400_0126; // 2 R/W SIO Data 3 (3rd Child) (Multi-Player Mode)
|
||||||
|
pub const REG_SIOCNT: Addr = 0x0400_0128; // 2 R/W SIO Control Register
|
||||||
|
pub const REG_SIOMLT_SEND: Addr = 0x0400_012A; // 2 R/W SIO Data (Local of MultiPlayer; shared below)
|
||||||
|
pub const REG_SIODATA8: Addr = 0x0400_012A; // 2 R/W SIO Data (Normal-8bit and UART Mode)
|
||||||
|
pub const REG_KEYINPUT: Addr = 0x0400_0130; // 2 R Key Status
|
||||||
|
pub const REG_KEYCNT: Addr = 0x0400_0132; // 2 R/W Key Interrupt Control
|
||||||
|
pub const REG_RCNT: Addr = 0x0400_0134; // 2 R/W SIO Mode Select/General Purpose Data
|
||||||
|
pub const REG_IR: Addr = 0x0400_0136; // - - Ancient - Infrared Register (Prototypes only)
|
||||||
|
pub const REG_JOYCNT: Addr = 0x0400_0140; // 2 R/W SIO JOY Bus Control
|
||||||
|
pub const REG_JOY_RECV: Addr = 0x0400_0150; // 4 R/W SIO JOY Bus Receive Data
|
||||||
|
pub const REG_JOY_TRANS: Addr = 0x0400_0154; // 4 R/W SIO JOY Bus Transmit Data
|
||||||
|
pub const REG_JOYSTAT: Addr = 0x0400_0158; // 2 R/? SIO JOY Bus Receive Status
|
||||||
|
pub const REG_IE: Addr = 0x0400_0200; // 2 R/W Interrupt Enable Register
|
||||||
|
pub const REG_IF: Addr = 0x0400_0202; // 2 R/W Interrupt Request Flags / IRQ Acknowledge
|
||||||
|
pub const REG_WAITCNT: Addr = 0x0400_0204; // 2 R/W Game Pak Waitstate Control
|
||||||
|
pub const REG_IME: Addr = 0x0400_0208; // 2 R/W Interrupt Master Enable Register
|
||||||
|
pub const REG_POSTFLG: Addr = 0x0400_0300; // 1 R/W Undocumented - Post Boot Flag
|
||||||
|
pub const REG_HALTCNT: Addr = 0x0400_0301; // 1 W Undocumented - Power Down Control
|
||||||
|
}
|
||||||
|
|
||||||
|
fn io_reg_string(addr: u32) -> &'static str {
|
||||||
|
match addr {
|
||||||
|
REG_DISPCNT => "REG_DISPCNT",
|
||||||
|
REG_DISPSTAT => "REG_DISPSTAT",
|
||||||
|
REG_VCOUNT => "REG_VCOUNT",
|
||||||
|
REG_BG0CNT => "REG_BG0CNT",
|
||||||
|
REG_BG1CNT => "REG_BG1CNT",
|
||||||
|
REG_BG2CNT => "REG_BG2CNT",
|
||||||
|
REG_BG3CNT => "REG_BG3CNT",
|
||||||
|
REG_BG0HOFS => "REG_BG0HOFS",
|
||||||
|
REG_BG0VOFS => "REG_BG0VOFS",
|
||||||
|
REG_BG1HOFS => "REG_BG1HOFS",
|
||||||
|
REG_BG1VOFS => "REG_BG1VOFS",
|
||||||
|
REG_BG2HOFS => "REG_BG2HOFS",
|
||||||
|
REG_BG2VOFS => "REG_BG2VOFS",
|
||||||
|
REG_BG3HOFS => "REG_BG3HOFS",
|
||||||
|
REG_BG3VOFS => "REG_BG3VOFS",
|
||||||
|
REG_BG2PA => "REG_BG2PA",
|
||||||
|
REG_BG2PB => "REG_BG2PB",
|
||||||
|
REG_BG2PC => "REG_BG2PC",
|
||||||
|
REG_BG2PD => "REG_BG2PD",
|
||||||
|
REG_BG2X_L => "REG_BG2X_L",
|
||||||
|
REG_BG2X_H => "REG_BG2X_H",
|
||||||
|
REG_BG2Y_L => "REG_BG2Y_L",
|
||||||
|
REG_BG2Y_H => "REG_BG2Y_H",
|
||||||
|
REG_BG3PA => "REG_BG3PA",
|
||||||
|
REG_BG3PB => "REG_BG3PB",
|
||||||
|
REG_BG3PC => "REG_BG3PC",
|
||||||
|
REG_BG3PD => "REG_BG3PD",
|
||||||
|
REG_BG3X_L => "REG_BG3X_L",
|
||||||
|
REG_BG3X_H => "REG_BG3X_H",
|
||||||
|
REG_BG3Y_L => "REG_BG3Y_L",
|
||||||
|
REG_BG3Y_H => "REG_BG3Y_H",
|
||||||
|
REG_WIN0H => "REG_WIN0H",
|
||||||
|
REG_WIN1H => "REG_WIN1H",
|
||||||
|
REG_WIN0V => "REG_WIN0V",
|
||||||
|
REG_WIN1V => "REG_WIN1V",
|
||||||
|
REG_WININ => "REG_WININ",
|
||||||
|
REG_WINOUT => "REG_WINOUT",
|
||||||
|
REG_MOSAIC => "REG_MOSAIC",
|
||||||
|
REG_BLDCNT => "REG_BLDCNT",
|
||||||
|
REG_BLDALPHA => "REG_BLDALPHA",
|
||||||
|
REG_BLDY => "REG_BLDY",
|
||||||
|
REG_SOUND1CNT_L => "REG_SOUND1CNT_L",
|
||||||
|
REG_SOUND1CNT_H => "REG_SOUND1CNT_H",
|
||||||
|
REG_SOUND1CNT_X => "REG_SOUND1CNT_X",
|
||||||
|
REG_SOUND2CNT_L => "REG_SOUND2CNT_L",
|
||||||
|
REG_SOUND2CNT_H => "REG_SOUND2CNT_H",
|
||||||
|
REG_SOUND3CNT_L => "REG_SOUND3CNT_L",
|
||||||
|
REG_SOUND3CNT_H => "REG_SOUND3CNT_H",
|
||||||
|
REG_SOUND3CNT_X => "REG_SOUND3CNT_X",
|
||||||
|
REG_SOUND4CNT_L => "REG_SOUND4CNT_L",
|
||||||
|
REG_SOUND4CNT_H => "REG_SOUND4CNT_H",
|
||||||
|
REG_SOUNDCNT_L => "REG_SOUNDCNT_L",
|
||||||
|
REG_SOUNDCNT_H => "REG_SOUNDCNT_H",
|
||||||
|
REG_SOUNDCNT_X => "REG_SOUNDCNT_X",
|
||||||
|
REG_SOUNDBIAS => "REG_SOUNDBIAS",
|
||||||
|
REG_WAVE_RAM => "REG_WAVE_RAM",
|
||||||
|
REG_FIFO_A => "REG_FIFO_A",
|
||||||
|
REG_FIFO_B => "REG_FIFO_B",
|
||||||
|
REG_DMA0SAD => "REG_DMA0SAD",
|
||||||
|
REG_DMA0DAD => "REG_DMA0DAD",
|
||||||
|
REG_DMA0CNT_L => "REG_DMA0CNT_L",
|
||||||
|
REG_DMA0CNT_H => "REG_DMA0CNT_H",
|
||||||
|
REG_DMA1SAD => "REG_DMA1SAD",
|
||||||
|
REG_DMA1DAD => "REG_DMA1DAD",
|
||||||
|
REG_DMA1CNT_L => "REG_DMA1CNT_L",
|
||||||
|
REG_DMA1CNT_H => "REG_DMA1CNT_H",
|
||||||
|
REG_DMA2SAD => "REG_DMA2SAD",
|
||||||
|
REG_DMA2DAD => "REG_DMA2DAD",
|
||||||
|
REG_DMA2CNT_L => "REG_DMA2CNT_L",
|
||||||
|
REG_DMA2CNT_H => "REG_DMA2CNT_H",
|
||||||
|
REG_DMA3SAD => "REG_DMA3SAD",
|
||||||
|
REG_DMA3DAD => "REG_DMA3DAD",
|
||||||
|
REG_DMA3CNT_L => "REG_DMA3CNT_L",
|
||||||
|
REG_DMA3CNT_H => "REG_DMA3CNT_H",
|
||||||
|
REG_TM0CNT_L => "REG_TM0CNT_L",
|
||||||
|
REG_TM0CNT_H => "REG_TM0CNT_H",
|
||||||
|
REG_TM1CNT_L => "REG_TM1CNT_L",
|
||||||
|
REG_TM1CNT_H => "REG_TM1CNT_H",
|
||||||
|
REG_TM2CNT_L => "REG_TM2CNT_L",
|
||||||
|
REG_TM2CNT_H => "REG_TM2CNT_H",
|
||||||
|
REG_TM3CNT_L => "REG_TM3CNT_L",
|
||||||
|
REG_TM3CNT_H => "REG_TM3CNT_H",
|
||||||
|
REG_SIODATA32 => "REG_SIODATA32",
|
||||||
|
REG_SIOMULTI0 => "REG_SIOMULTI0",
|
||||||
|
REG_SIOMULTI1 => "REG_SIOMULTI1",
|
||||||
|
REG_SIOMULTI2 => "REG_SIOMULTI2",
|
||||||
|
REG_SIOMULTI3 => "REG_SIOMULTI3",
|
||||||
|
REG_SIOCNT => "REG_SIOCNT",
|
||||||
|
REG_SIOMLT_SEND => "REG_SIOMLT_SEND",
|
||||||
|
REG_SIODATA8 => "REG_SIODATA8",
|
||||||
|
REG_KEYINPUT => "REG_KEYINPUT",
|
||||||
|
REG_KEYCNT => "REG_KEYCNT",
|
||||||
|
REG_RCNT => "REG_RCNT",
|
||||||
|
REG_IR => "REG_IR",
|
||||||
|
REG_JOYCNT => "REG_JOYCNT",
|
||||||
|
REG_JOY_RECV => "REG_JOY_RECV",
|
||||||
|
REG_JOY_TRANS => "REG_JOY_TRANS",
|
||||||
|
REG_JOYSTAT => "REG_JOYSTAT",
|
||||||
|
REG_IE => "REG_IE",
|
||||||
|
REG_IF => "REG_IF",
|
||||||
|
REG_WAITCNT => "REG_WAITCNT",
|
||||||
|
REG_IME => "REG_IME",
|
||||||
|
REG_POSTFLG => "REG_POSTFLG",
|
||||||
|
REG_HALTCNT => "REG_HALTCNT",
|
||||||
|
_ => "UNKNOWN",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -53,4 +53,4 @@ impl From<zip::result::ZipError> for GBAError {
|
||||||
fn from(err: zip::result::ZipError) -> GBAError {
|
fn from(err: zip::result::ZipError) -> GBAError {
|
||||||
GBAError::IO(::std::io::Error::from(::std::io::ErrorKind::InvalidInput))
|
GBAError::IO(::std::io::Error::from(::std::io::ErrorKind::InvalidInput))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -74,51 +74,45 @@ impl fmt::Display for MemoryAccess {
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct BoxedMemory {
|
pub struct BoxedMemory {
|
||||||
pub mem: Box<[u8]>,
|
pub mem: Box<[u8]>,
|
||||||
mask: u32,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl BoxedMemory {
|
impl BoxedMemory {
|
||||||
pub fn new(boxed_slice: Box<[u8]>, mask: u32) -> BoxedMemory {
|
pub fn new(boxed_slice: Box<[u8]>) -> BoxedMemory {
|
||||||
BoxedMemory {
|
BoxedMemory { mem: boxed_slice }
|
||||||
mem: boxed_slice,
|
|
||||||
mask: mask,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Bus for BoxedMemory {
|
impl Bus for BoxedMemory {
|
||||||
fn read_32(&self, addr: Addr) -> u32 {
|
fn read_32(&self, addr: Addr) -> u32 {
|
||||||
(&self.mem[(addr & self.mask) as usize..])
|
(&self.mem[addr as usize..])
|
||||||
.read_u32::<LittleEndian>()
|
.read_u32::<LittleEndian>()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn read_16(&self, addr: Addr) -> u16 {
|
fn read_16(&self, addr: Addr) -> u16 {
|
||||||
(&self.mem[(addr & self.mask) as usize..])
|
(&self.mem[addr as usize..])
|
||||||
.read_u16::<LittleEndian>()
|
.read_u16::<LittleEndian>()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn read_8(&self, addr: Addr) -> u8 {
|
fn read_8(&self, addr: Addr) -> u8 {
|
||||||
(&self.mem[(addr & self.mask) as usize..])[0]
|
(&self.mem[addr as usize..])[0]
|
||||||
}
|
}
|
||||||
|
|
||||||
fn write_32(&mut self, addr: Addr, value: u32) {
|
fn write_32(&mut self, addr: Addr, value: u32) {
|
||||||
(&mut self.mem[(addr & self.mask) as usize..])
|
(&mut self.mem[addr as usize..])
|
||||||
.write_u32::<LittleEndian>(value)
|
.write_u32::<LittleEndian>(value)
|
||||||
.unwrap()
|
.unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn write_16(&mut self, addr: Addr, value: u16) {
|
fn write_16(&mut self, addr: Addr, value: u16) {
|
||||||
(&mut self.mem[(addr & self.mask) as usize..])
|
(&mut self.mem[addr as usize..])
|
||||||
.write_u16::<LittleEndian>(value)
|
.write_u16::<LittleEndian>(value)
|
||||||
.unwrap()
|
.unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn write_8(&mut self, addr: Addr, value: u8) {
|
fn write_8(&mut self, addr: Addr, value: u8) {
|
||||||
(&mut self.mem[(addr & self.mask) as usize..])
|
(&mut self.mem[addr as usize..]).write_u8(value).unwrap()
|
||||||
.write_u8(value)
|
|
||||||
.unwrap()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -171,56 +165,70 @@ impl SysBus {
|
||||||
SysBus {
|
SysBus {
|
||||||
io: io,
|
io: io,
|
||||||
|
|
||||||
bios: BoxedMemory::new(bios_rom.into_boxed_slice(), 0xff_ffff),
|
bios: BoxedMemory::new(bios_rom.into_boxed_slice()),
|
||||||
onboard_work_ram: BoxedMemory::new(
|
onboard_work_ram: BoxedMemory::new(vec![0; WORK_RAM_SIZE].into_boxed_slice()),
|
||||||
vec![0; WORK_RAM_SIZE].into_boxed_slice(),
|
internal_work_ram: BoxedMemory::new(vec![0; INTERNAL_RAM_SIZE].into_boxed_slice()),
|
||||||
(WORK_RAM_SIZE as u32) - 1,
|
|
||||||
),
|
|
||||||
internal_work_ram: BoxedMemory::new(
|
|
||||||
vec![0; INTERNAL_RAM_SIZE].into_boxed_slice(),
|
|
||||||
0x7fff,
|
|
||||||
),
|
|
||||||
ioregs: ioregs,
|
ioregs: ioregs,
|
||||||
palette_ram: BoxedMemory::new(
|
palette_ram: BoxedMemory::new(vec![0; PALETTE_RAM_SIZE].into_boxed_slice()),
|
||||||
vec![0; PALETTE_RAM_SIZE].into_boxed_slice(),
|
vram: BoxedMemory::new(vec![0; VIDEO_RAM_SIZE].into_boxed_slice()),
|
||||||
(PALETTE_RAM_SIZE as u32) - 1,
|
oam: BoxedMemory::new(vec![0; OAM_SIZE].into_boxed_slice()),
|
||||||
),
|
|
||||||
vram: BoxedMemory::new(
|
|
||||||
vec![0; VIDEO_RAM_SIZE].into_boxed_slice(),
|
|
||||||
(VIDEO_RAM_SIZE as u32) - 1,
|
|
||||||
),
|
|
||||||
oam: BoxedMemory::new(vec![0; OAM_SIZE].into_boxed_slice(), (OAM_SIZE as u32) - 1),
|
|
||||||
gamepak: gamepak,
|
gamepak: gamepak,
|
||||||
dummy: DummyBus([0; 4]),
|
dummy: DummyBus([0; 4]),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn map(&self, addr: Addr) -> &Bus {
|
fn map(&self, addr: Addr) -> (&Bus, Addr) {
|
||||||
|
let ofs = addr & 0x00ff_ffff;
|
||||||
match addr & 0xff000000 {
|
match addr & 0xff000000 {
|
||||||
BIOS_ADDR => &self.bios,
|
BIOS_ADDR => (&self.bios, ofs),
|
||||||
EWRAM_ADDR => &self.onboard_work_ram,
|
EWRAM_ADDR => (&self.onboard_work_ram, ofs & 0x3_ffff),
|
||||||
IWRAM_ADDR => &self.internal_work_ram,
|
IWRAM_ADDR => (&self.internal_work_ram, ofs & 0x7fff),
|
||||||
IOMEM_ADDR => &self.ioregs,
|
IOMEM_ADDR => (&self.ioregs, {
|
||||||
PALRAM_ADDR => &self.palette_ram,
|
if ofs & 0xffff == 0x8000 {
|
||||||
VRAM_ADDR => &self.vram,
|
0x800
|
||||||
OAM_ADDR => &self.oam,
|
} else {
|
||||||
GAMEPAK_WS0_ADDR | GAMEPAK_WS1_ADDR | GAMEPAK_WS2_ADDR => &self.gamepak,
|
ofs & 0x7ff
|
||||||
_ => &self.dummy,
|
}
|
||||||
|
}),
|
||||||
|
PALRAM_ADDR => (&self.palette_ram, ofs & 0x3ff),
|
||||||
|
VRAM_ADDR => (&self.vram, {
|
||||||
|
let mut ofs = ofs & ((VIDEO_RAM_SIZE as u32) - 1);
|
||||||
|
if ofs > 0x18000 {
|
||||||
|
ofs -= 0x8000;
|
||||||
|
}
|
||||||
|
ofs
|
||||||
|
}),
|
||||||
|
OAM_ADDR => (&self.oam, ofs & 0x3ff),
|
||||||
|
GAMEPAK_WS0_ADDR | GAMEPAK_WS1_ADDR | GAMEPAK_WS2_ADDR => (&self.gamepak, ofs),
|
||||||
|
_ => (&self.dummy, ofs),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// TODO proc-macro for generating this function
|
/// TODO proc-macro for generating this function
|
||||||
fn map_mut(&mut self, addr: Addr) -> &mut Bus {
|
fn map_mut(&mut self, addr: Addr) -> (&mut Bus, Addr) {
|
||||||
|
let ofs = addr & 0x00ff_ffff;
|
||||||
match addr & 0xff000000 {
|
match addr & 0xff000000 {
|
||||||
BIOS_ADDR => &mut self.bios,
|
BIOS_ADDR => (&mut self.bios, ofs),
|
||||||
EWRAM_ADDR => &mut self.onboard_work_ram,
|
EWRAM_ADDR => (&mut self.onboard_work_ram, ofs & 0x3_ffff),
|
||||||
IWRAM_ADDR => &mut self.internal_work_ram,
|
IWRAM_ADDR => (&mut self.internal_work_ram, ofs & 0x7fff),
|
||||||
IOMEM_ADDR => &mut self.ioregs,
|
IOMEM_ADDR => (&mut self.ioregs, {
|
||||||
PALRAM_ADDR => &mut self.palette_ram,
|
if ofs & 0xffff == 0x8000 {
|
||||||
VRAM_ADDR => &mut self.vram,
|
0x800
|
||||||
OAM_ADDR => &mut self.oam,
|
} else {
|
||||||
GAMEPAK_WS0_ADDR | GAMEPAK_WS1_ADDR | GAMEPAK_WS2_ADDR => &mut self.gamepak,
|
ofs & 0x7ff
|
||||||
_ => &mut self.dummy,
|
}
|
||||||
|
}),
|
||||||
|
PALRAM_ADDR => (&mut self.palette_ram, ofs & 0x3ff),
|
||||||
|
VRAM_ADDR => (&mut self.vram, {
|
||||||
|
let mut ofs = ofs & ((VIDEO_RAM_SIZE as u32) - 1);
|
||||||
|
if ofs > 0x18000 {
|
||||||
|
ofs -= 0x8000;
|
||||||
|
}
|
||||||
|
ofs
|
||||||
|
}),
|
||||||
|
OAM_ADDR => (&mut self.oam, ofs & 0x3ff),
|
||||||
|
GAMEPAK_WS0_ADDR | GAMEPAK_WS1_ADDR | GAMEPAK_WS2_ADDR => (&mut self.gamepak, ofs),
|
||||||
|
_ => (&mut self.dummy, ofs),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -274,26 +282,32 @@ impl SysBus {
|
||||||
|
|
||||||
impl Bus for SysBus {
|
impl Bus for SysBus {
|
||||||
fn read_32(&self, addr: Addr) -> u32 {
|
fn read_32(&self, addr: Addr) -> u32 {
|
||||||
self.map(addr).read_32(addr & 0xff_ffff)
|
let (dev, addr) = self.map(addr);
|
||||||
|
dev.read_32(addr & 0xff_fffc)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn read_16(&self, addr: Addr) -> u16 {
|
fn read_16(&self, addr: Addr) -> u16 {
|
||||||
self.map(addr).read_16(addr & 0xff_ffff)
|
let (dev, addr) = self.map(addr);
|
||||||
|
dev.read_16(addr & 0xff_fffe)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn read_8(&self, addr: Addr) -> u8 {
|
fn read_8(&self, addr: Addr) -> u8 {
|
||||||
self.map(addr).read_8(addr & 0xff_ffff)
|
let (dev, addr) = self.map(addr);
|
||||||
|
dev.read_8(addr & 0xff_ffff)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn write_32(&mut self, addr: Addr, value: u32) {
|
fn write_32(&mut self, addr: Addr, value: u32) {
|
||||||
self.map_mut(addr).write_32(addr & 0xff_ffff, value)
|
let (dev, addr) = self.map_mut(addr);
|
||||||
|
dev.write_32(addr & 0xff_fffc, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn write_16(&mut self, addr: Addr, value: u16) {
|
fn write_16(&mut self, addr: Addr, value: u16) {
|
||||||
self.map_mut(addr).write_16(addr & 0xff_ffff, value)
|
let (dev, addr) = self.map_mut(addr);
|
||||||
|
dev.write_16(addr & 0xff_fffe, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn write_8(&mut self, addr: Addr, value: u8) {
|
fn write_8(&mut self, addr: Addr, value: u8) {
|
||||||
self.map_mut(addr).write_8(addr & 0xff_ffff, value)
|
let (dev, addr) = self.map_mut(addr);
|
||||||
|
dev.write_8(addr & 0xff_ffff, value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -100,17 +100,15 @@ impl SyncedIoDevice for Timers {
|
||||||
for i in 0..4 {
|
for i in 0..4 {
|
||||||
if self[i].timer_ctl.enabled() && !self[i].timer_ctl.cascade() {
|
if self[i].timer_ctl.enabled() && !self[i].timer_ctl.cascade() {
|
||||||
match self[i].add_cycles(cycles, irqs) {
|
match self[i].add_cycles(cycles, irqs) {
|
||||||
TimerAction::Overflow(num_overflows) => {
|
TimerAction::Overflow(num_overflows) => match i {
|
||||||
match i {
|
3 => {}
|
||||||
3 => {}
|
_ => {
|
||||||
_ => {
|
let next_i = i + 1;
|
||||||
let next_i = i + 1;
|
if self[next_i].timer_ctl.cascade() {
|
||||||
if self[next_i].timer_ctl.cascade() {
|
self[next_i].add_cycles(num_overflows, irqs);
|
||||||
self[next_i].add_cycles(num_overflows, irqs);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
TimerAction::Increment => {}
|
TimerAction::Increment => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -137,19 +137,19 @@ impl Command {
|
||||||
debugger.stop();
|
debugger.stop();
|
||||||
}
|
}
|
||||||
AddBreakpoint(addr) => {
|
AddBreakpoint(addr) => {
|
||||||
if !debugger.breakpoints.contains(&addr) {
|
if !debugger.gba.cpu.breakpoints.contains(&addr) {
|
||||||
let new_index = debugger.breakpoints.len();
|
let new_index = debugger.gba.cpu.breakpoints.len();
|
||||||
debugger.breakpoints.push(addr);
|
debugger.gba.cpu.breakpoints.push(addr);
|
||||||
println!("added breakpoint [{}] 0x{:08x}", new_index, addr);
|
println!("added breakpoint [{}] 0x{:08x}", new_index, addr);
|
||||||
} else {
|
} else {
|
||||||
println!("breakpoint already exists!")
|
println!("breakpoint already exists!")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
DelBreakpoint(addr) => debugger.delete_breakpoint(addr),
|
DelBreakpoint(addr) => debugger.delete_breakpoint(addr),
|
||||||
ClearBreakpoints => debugger.breakpoints.clear(),
|
ClearBreakpoints => debugger.gba.cpu.breakpoints.clear(),
|
||||||
ListBreakpoints => {
|
ListBreakpoints => {
|
||||||
println!("breakpoint list:");
|
println!("breakpoint list:");
|
||||||
for (i, b) in debugger.breakpoints.iter().enumerate() {
|
for (i, b) in debugger.gba.cpu.breakpoints.iter().enumerate() {
|
||||||
println!("[{}] 0x{:08x}", i, b)
|
println!("[{}] 0x{:08x}", i, b)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -36,7 +36,6 @@ type DebuggerResult<T> = Result<T, DebuggerError>;
|
||||||
pub struct Debugger {
|
pub struct Debugger {
|
||||||
pub gba: GameBoyAdvance,
|
pub gba: GameBoyAdvance,
|
||||||
running: bool,
|
running: bool,
|
||||||
breakpoints: Vec<u32>,
|
|
||||||
pub previous_command: Option<Command>,
|
pub previous_command: Option<Command>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -44,7 +43,6 @@ impl Debugger {
|
||||||
pub fn new(gba: GameBoyAdvance) -> Debugger {
|
pub fn new(gba: GameBoyAdvance) -> Debugger {
|
||||||
Debugger {
|
Debugger {
|
||||||
gba: gba,
|
gba: gba,
|
||||||
breakpoints: Vec::new(),
|
|
||||||
running: false,
|
running: false,
|
||||||
previous_command: None,
|
previous_command: None,
|
||||||
}
|
}
|
||||||
|
@ -52,7 +50,7 @@ impl Debugger {
|
||||||
|
|
||||||
pub fn check_breakpoint(&self) -> Option<u32> {
|
pub fn check_breakpoint(&self) -> Option<u32> {
|
||||||
let next_pc = self.gba.cpu.get_next_pc();
|
let next_pc = self.gba.cpu.get_next_pc();
|
||||||
for bp in &self.breakpoints {
|
for bp in &self.gba.cpu.breakpoints {
|
||||||
if *bp == next_pc {
|
if *bp == next_pc {
|
||||||
return Some(next_pc);
|
return Some(next_pc);
|
||||||
}
|
}
|
||||||
|
@ -62,7 +60,7 @@ impl Debugger {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn delete_breakpoint(&mut self, addr: u32) {
|
pub fn delete_breakpoint(&mut self, addr: u32) {
|
||||||
self.breakpoints.retain(|&a| a != addr);
|
self.gba.cpu.breakpoints.retain(|&a| a != addr);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn decode_reg(&self, s: &str) -> DebuggerResult<usize> {
|
fn decode_reg(&self, s: &str) -> DebuggerResult<usize> {
|
||||||
|
|
|
@ -21,7 +21,7 @@ fn draw_tile(
|
||||||
let index = io
|
let index = io
|
||||||
.gpu
|
.gpu
|
||||||
.read_pixel_index(&gba.sysbus, tile_addr, x, y, pixel_format);
|
.read_pixel_index(&gba.sysbus, tile_addr, x, y, pixel_format);
|
||||||
let color = io.gpu.get_palette_color(&gba.sysbus, index as u32, 0);
|
let color = io.gpu.get_palette_color(&gba.sysbus, index as u32, 0, 0);
|
||||||
canvas.set_draw_color(Color::RGB(
|
canvas.set_draw_color(Color::RGB(
|
||||||
(color.r() as u8) << 3,
|
(color.r() as u8) << 3,
|
||||||
(color.g() as u8) << 3,
|
(color.g() as u8) << 3,
|
||||||
|
|
Reference in a new issue