Fix many bugs, refactor many things..

Passing: Armwrestler, cpu_test by Dead_Body

Former-commit-id: 80d815d110c5341515dd01c476a0d7e25ecb66a8
This commit is contained in:
Michel Heily 2019-11-09 00:55:09 +02:00
parent 2bd8b56bc6
commit 3a1d5c10ce
18 changed files with 751 additions and 541 deletions

View file

@ -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

View file

@ -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.

View file

@ -0,0 +1 @@
de93ded308a68b66c3fe94fe4933737d60a6aba9

View file

@ -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);
}
};
} }
} }
} }

View file

@ -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)
}
} }
} }

View file

@ -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));

View file

@ -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 {

View file

@ -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"),
}
}
}

View file

@ -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),

View file

@ -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 {

View file

@ -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 {

View file

@ -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",
}
}

View file

@ -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))
} }
} }

View file

@ -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);
} }
} }

View file

@ -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 => {}
} }
} }

View file

@ -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)
} }
} }

View file

@ -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> {

View file

@ -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,