[WIP] Start working on arm7tdmi dispatch table.

Lookup tables are generally faster than matching in most architectures.
There is some trouble in decoding some arm data processing instructions
so this is non-default feature for now

* Crashes on armwrestler but many games seem to work


Former-commit-id: 3c06ea344ae0097947d8cd5bd05b1f882c7f743a
This commit is contained in:
Michel Heily 2020-04-04 15:33:36 +03:00
parent 8f11bebd2b
commit a523a37d32
13 changed files with 517 additions and 142 deletions

View file

@ -12,6 +12,7 @@ members = [
]
[dependencies]
lazy_static = {version="1.4.0", optional = true}
serde = {version = "1.0.104", features = ["derive"] }
bincode = "1.2.1"
byteorder = "1"
@ -41,6 +42,10 @@ gdbstub = {git = "https://github.com/daniel5151/gdbstub.git", optional = true, f
[features]
debugger = ["nom", "rustyline"]
gdb = ["gdbstub"]
# Uses lookup tables when executing instructions instead of `match` statements.
# Faster, but consumes more memory.
# Experimental and not thoroughly tested yet
arm7tdmi_dispatch_table = ["lazy_static"]
[profile.dev]
opt-level = 0

View file

@ -21,4 +21,5 @@ winres = "0.1"
[features]
debugger = ["rustboyadvance-ng/debugger"]
gdb = ["rustboyadvance-ng/gdb"]
gdb = ["rustboyadvance-ng/gdb"]
arm7tdmi_dispatch_table = ["rustboyadvance-ng/arm7tdmi_dispatch_table"]

View file

@ -113,7 +113,7 @@ impl ArmInstruction {
f,
"b{link}{cond}\t{ofs:#x}",
link = if self.link_flag() { "l" } else { "" },
cond = self.cond,
cond = self.cond(),
ofs = 8 + self.pc.wrapping_add(self.branch_offset() as Addr)
)
}
@ -152,7 +152,7 @@ impl ArmInstruction {
f,
"{opcode}{S}{cond}\t{Rd}, ",
opcode = opcode,
cond = self.cond,
cond = self.cond(),
S = self.set_cond_mark(),
Rd = reg_string(self.rd())
),
@ -160,14 +160,14 @@ impl ArmInstruction {
f,
"{opcode}{cond}\t{Rn}, ",
opcode = opcode,
cond = self.cond,
cond = self.cond(),
Rn = reg_string(self.rn())
),
_ => write!(
f,
"{opcode}{S}{cond}\t{Rd}, {Rn}, ",
opcode = opcode,
cond = self.cond,
cond = self.cond(),
S = self.set_cond_mark(),
Rd = reg_string(self.rd()),
Rn = reg_string(self.rn())
@ -230,7 +230,7 @@ impl ArmInstruction {
"{mnem}{B}{T}{cond}\t{Rd}, ",
mnem = if self.load_flag() { "ldr" } else { "str" },
B = if self.transfer_size() == 1 { "b" } else { "" },
cond = self.cond,
cond = self.cond(),
T = if !self.pre_index_flag() && self.write_back_flag() {
"t"
} else {
@ -249,7 +249,7 @@ impl ArmInstruction {
mnem = if self.load_flag() { "ldm" } else { "stm" },
inc_dec = if self.add_offset_flag() { 'i' } else { 'd' },
pre_post = if self.pre_index_flag() { 'b' } else { 'a' },
cond = self.cond,
cond = self.cond(),
Rn = reg_string(self.rn()),
auto_inc = if self.write_back_flag() { "!" } else { "" }
)?;
@ -283,7 +283,7 @@ impl ArmInstruction {
write!(
f,
"mrs{cond}\t{Rd}, {psr}",
cond = self.cond,
cond = self.cond(),
Rd = reg_string(self.rd()),
psr = if self.spsr_flag() { "SPSR" } else { "CPSR" }
)
@ -294,7 +294,7 @@ impl ArmInstruction {
write!(
f,
"msr{cond}\t{psr}, {Rm}",
cond = self.cond,
cond = self.cond(),
psr = if self.spsr_flag() { "SPSR" } else { "CPSR" },
Rm = reg_string(self.rm()),
)
@ -304,7 +304,7 @@ impl ArmInstruction {
write!(
f,
"msr{cond}\t{psr}, ",
cond = self.cond,
cond = self.cond(),
psr = if self.spsr_flag() { "SPSR_f" } else { "CPSR_f" },
)?;
if let Ok(Some(op)) = self.fmt_operand2(f) {
@ -327,7 +327,7 @@ impl ArmInstruction {
f,
"mla{S}{cond}\t{Rd}, {Rm}, {Rs}, {Rn}",
S = self.set_cond_mark(),
cond = self.cond,
cond = self.cond(),
Rd = reg_string(self.rd()),
Rm = reg_string(self.rm()),
Rs = reg_string(self.rs()),
@ -338,7 +338,7 @@ impl ArmInstruction {
f,
"mul{S}{cond}\t{Rd}, {Rm}, {Rs}",
S = self.set_cond_mark(),
cond = self.cond,
cond = self.cond(),
Rd = reg_string(self.rd()),
Rm = reg_string(self.rm()),
Rs = reg_string(self.rs()),
@ -365,7 +365,7 @@ impl ArmInstruction {
"mull"
},
S = self.set_cond_mark(),
cond = self.cond,
cond = self.cond(),
RdLo = reg_string(self.rd_lo()),
RdHi = reg_string(self.rd_hi()),
Rm = reg_string(self.rm()),
@ -379,7 +379,7 @@ impl ArmInstruction {
f,
"{mnem}{type}{cond}\t{Rd}, ",
mnem = if self.load_flag() { "ldr" } else { "str" },
cond = self.cond,
cond = self.cond(),
type = transfer_type,
Rd = reg_string(self.rd()),
)?;
@ -393,7 +393,7 @@ impl ArmInstruction {
write!(
f,
"swi{cond}\t#{comm:#x}",
cond = self.cond,
cond = self.cond(),
comm = self.swi_comment()
)
}
@ -403,7 +403,7 @@ impl ArmInstruction {
f,
"swp{B}{cond}\t{Rd}, {Rm}, [{Rn}]",
B = if self.transfer_size() == 1 { "b" } else { "" },
cond = self.cond,
cond = self.cond(),
Rd = reg_string(self.rd()),
Rm = reg_string(self.rm()),
Rn = reg_string(self.rn()),
@ -430,6 +430,7 @@ impl fmt::Display for ArmInstruction {
LDR_STR_HS_REG => self.fmt_ldr_str_hs(f),
SWI => self.fmt_swi(f),
SWP => self.fmt_swp(f),
Undefined => write!(f, "<Undefined>"),
}
}
}

View file

@ -1,7 +1,6 @@
use crate::bit::BitIndex;
use super::super::alu::*;
use super::ArmCond;
use crate::core::arm7tdmi::psr::RegPSR;
use crate::core::arm7tdmi::CpuAction;
use crate::core::arm7tdmi::{Addr, Core, CpuMode, CpuState, REG_LR, REG_PC};
@ -12,35 +11,34 @@ use super::*;
impl Core {
pub fn exec_arm(&mut self, bus: &mut SysBus, insn: &ArmInstruction) -> CpuAction {
if insn.cond != ArmCond::AL {
if !self.check_arm_cond(insn.cond) {
self.S_cycle32(bus, self.pc);
return CpuAction::AdvancePC;
}
}
match insn.fmt {
ArmFormat::BX => self.exec_bx(bus, insn),
ArmFormat::B_BL => self.exec_b_bl(bus, insn),
ArmFormat::DP => self.exec_data_processing(bus, insn),
ArmFormat::SWI => {
self.software_interrupt(bus, self.pc - 4, insn.swi_comment());
CpuAction::FlushPipeline
}
ArmFormat::LDR_STR => self.exec_ldr_str(bus, insn),
ArmFormat::LDR_STR_HS_IMM => self.exec_ldr_str_hs(bus, insn),
ArmFormat::LDR_STR_HS_REG => self.exec_ldr_str_hs(bus, insn),
ArmFormat::LDM_STM => self.exec_ldm_stm(bus, insn),
ArmFormat::MRS => self.move_from_status_register(bus, insn.rd(), insn.spsr_flag()),
ArmFormat::MSR_REG => self.exec_msr_reg(bus, insn),
ArmFormat::MSR_FLAGS => self.exec_msr_flags(bus, insn),
ArmFormat::MUL_MLA => self.exec_mul_mla(bus, insn),
ArmFormat::MULL_MLAL => self.exec_mull_mlal(bus, insn),
ArmFormat::BX => self.exec_arm_bx(bus, insn),
ArmFormat::B_BL => self.exec_arm_b_bl(bus, insn),
ArmFormat::DP => self.exec_arm_data_processing(bus, insn),
ArmFormat::SWI => self.exec_arm_swi(bus, insn),
ArmFormat::LDR_STR => self.exec_arm_ldr_str(bus, insn),
ArmFormat::LDR_STR_HS_IMM => self.exec_arm_ldr_str_hs(bus, insn),
ArmFormat::LDR_STR_HS_REG => self.exec_arm_ldr_str_hs(bus, insn),
ArmFormat::LDM_STM => self.exec_arm_ldm_stm(bus, insn),
ArmFormat::MRS => self.exec_arm_mrs(bus, insn),
ArmFormat::MSR_REG => self.exec_arm_msr_reg(bus, insn),
ArmFormat::MSR_FLAGS => self.exec_arm_msr_flags(bus, insn),
ArmFormat::MUL_MLA => self.exec_arm_mul_mla(bus, insn),
ArmFormat::MULL_MLAL => self.exec_arm_mull_mlal(bus, insn),
ArmFormat::SWP => self.exec_arm_swp(bus, insn),
ArmFormat::Undefined => panic!("Undefined instruction "),
}
}
pub fn arm_undefined(&mut self, _: &mut SysBus, insn: &ArmInstruction) -> CpuAction {
panic!(
"executing undefind arm instruction {:08x} at @{:08x}",
insn.raw, insn.pc
)
}
/// Cycles 2S+1N
fn exec_b_bl(&mut self, sb: &mut SysBus, insn: &ArmInstruction) -> CpuAction {
pub fn exec_arm_b_bl(&mut self, sb: &mut SysBus, insn: &ArmInstruction) -> CpuAction {
self.S_cycle32(sb, self.pc);
if insn.link_flag() {
self.set_reg(REG_LR, (insn.pc + (self.word_size() as u32)) & !0b1);
@ -73,7 +71,7 @@ impl Core {
}
/// Cycles 2S+1N
fn exec_bx(&mut self, sb: &mut SysBus, insn: &ArmInstruction) -> CpuAction {
pub fn exec_arm_bx(&mut self, sb: &mut SysBus, insn: &ArmInstruction) -> CpuAction {
self.branch_exchange(sb, self.get_reg(insn.rn()))
}
@ -94,7 +92,11 @@ impl Core {
CpuAction::AdvancePC
}
fn exec_msr_reg(&mut self, sb: &mut SysBus, insn: &ArmInstruction) -> CpuAction {
pub fn exec_arm_mrs(&mut self, sb: &mut SysBus, insn: &ArmInstruction) -> CpuAction {
self.move_from_status_register(sb, insn.rd(), insn.spsr_flag())
}
pub fn exec_arm_msr_reg(&mut self, sb: &mut SysBus, insn: &ArmInstruction) -> CpuAction {
self.write_status_register(sb, insn.spsr_flag(), self.get_reg(insn.rm()))
}
@ -129,7 +131,7 @@ impl Core {
CpuAction::AdvancePC
}
fn exec_msr_flags(&mut self, sb: &mut SysBus, insn: &ArmInstruction) -> CpuAction {
pub fn exec_arm_msr_flags(&mut self, sb: &mut SysBus, insn: &ArmInstruction) -> CpuAction {
self.S_cycle32(sb, self.pc);
let op = insn.operand2();
let op = self.decode_operand2(&op);
@ -164,7 +166,11 @@ impl Core {
///
/// Cycles: 1S+x+y (from GBATEK)
/// 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) -> CpuAction {
pub fn exec_arm_data_processing(
&mut self,
sb: &mut SysBus,
insn: &ArmInstruction,
) -> CpuAction {
use AluOpCode::*;
self.S_cycle32(sb, self.pc);
@ -275,7 +281,7 @@ impl Core {
/// STR{cond}{B}{T} Rd,<Address> | 2N | ---- | [Rn+/-<offset>]=Rd
/// ------------------------------------------------------------------------------
/// For LDR, add y=1S+1N if Rd=R15.
fn exec_ldr_str(&mut self, sb: &mut SysBus, insn: &ArmInstruction) -> CpuAction {
pub fn exec_arm_ldr_str(&mut self, sb: &mut SysBus, insn: &ArmInstruction) -> CpuAction {
let mut result = CpuAction::AdvancePC;
let load = insn.load_flag();
@ -352,7 +358,7 @@ impl Core {
result
}
fn exec_ldr_str_hs(&mut self, sb: &mut SysBus, insn: &ArmInstruction) -> CpuAction {
pub fn exec_arm_ldr_str_hs(&mut self, sb: &mut SysBus, insn: &ArmInstruction) -> CpuAction {
let mut result = CpuAction::AdvancePC;
let load = insn.load_flag();
@ -434,7 +440,7 @@ impl Core {
result
}
fn exec_ldm_stm(&mut self, sb: &mut SysBus, insn: &ArmInstruction) -> CpuAction {
pub fn exec_arm_ldm_stm(&mut self, sb: &mut SysBus, insn: &ArmInstruction) -> CpuAction {
let mut result = CpuAction::AdvancePC;
let mut full = insn.pre_index_flag();
@ -584,7 +590,7 @@ impl Core {
result
}
fn exec_mul_mla(&mut self, sb: &mut SysBus, insn: &ArmInstruction) -> CpuAction {
pub fn exec_arm_mul_mla(&mut self, sb: &mut SysBus, insn: &ArmInstruction) -> CpuAction {
let (rd, rn, rs, rm) = (insn.rd(), insn.rn(), insn.rs(), insn.rm());
// check validity
@ -619,7 +625,7 @@ impl Core {
CpuAction::AdvancePC
}
fn exec_mull_mlal(&mut self, sb: &mut SysBus, insn: &ArmInstruction) -> CpuAction {
pub fn exec_arm_mull_mlal(&mut self, sb: &mut SysBus, insn: &ArmInstruction) -> CpuAction {
let (rd_hi, rd_lo, rn, rs, rm) =
(insn.rd_hi(), insn.rd_lo(), insn.rn(), insn.rs(), insn.rm());
@ -666,7 +672,7 @@ impl Core {
CpuAction::AdvancePC
}
fn exec_arm_swp(&mut self, sb: &mut SysBus, insn: &ArmInstruction) -> CpuAction {
pub fn exec_arm_swp(&mut self, sb: &mut SysBus, insn: &ArmInstruction) -> CpuAction {
let base_addr = self.get_reg(insn.rn());
if insn.transfer_size() == 1 {
let t = sb.read_8(base_addr);
@ -686,4 +692,9 @@ impl Core {
CpuAction::AdvancePC
}
pub fn exec_arm_swi(&mut self, sb: &mut SysBus, insn: &ArmInstruction) -> CpuAction {
self.software_interrupt(sb, self.pc - 4, insn.swi_comment());
CpuAction::FlushPipeline
}
}

View file

@ -0,0 +1,161 @@
use super::super::super::SysBus;
use super::super::Core;
use super::super::CpuAction;
use super::{ArmFormat, ArmInstruction};
use bit::BitIndex;
pub type ArmInstructionHandler = fn(&mut Core, &mut SysBus, &ArmInstruction) -> CpuAction;
impl From<ArmFormat> for ArmInstructionHandler {
fn from(arm_fmt: ArmFormat) -> ArmInstructionHandler {
match arm_fmt {
ArmFormat::BranchExchange => Core::exec_arm_bx,
ArmFormat::BranchLink => Core::exec_arm_b_bl,
ArmFormat::DataProcessing => Core::exec_arm_data_processing,
ArmFormat::SoftwareInterrupt => Core::exec_arm_swi,
ArmFormat::SingleDataTransfer => Core::exec_arm_ldr_str,
ArmFormat::HalfwordDataTransferImmediateOffset => Core::exec_arm_ldr_str_hs,
ArmFormat::HalfwordDataTransferRegOffset => Core::exec_arm_ldr_str_hs,
ArmFormat::BlockDataTransfer => Core::exec_arm_ldm_stm,
ArmFormat::Multiply => Core::exec_arm_mul_mla,
ArmFormat::MultiplyLong => Core::exec_arm_mull_mlal,
ArmFormat::SingleDataSwap => Core::exec_arm_swp,
_ => Core::arm_undefined,
}
}
}
pub struct ArmInstructionInfo {
pub fmt: ArmFormat,
pub handler_fn: ArmInstructionHandler,
}
impl ArmInstructionInfo {
fn new(fmt: ArmFormat, handler_fn: ArmInstructionHandler) -> ArmInstructionInfo {
ArmInstructionInfo { fmt, handler_fn }
}
}
#[inline(always)]
pub fn arm_insn_hash(insn: u32) -> usize {
(((insn >> 16) & 0xff0) | ((insn >> 4) & 0x00f)) as usize
}
impl From<u32> for ArmFormat {
fn from(i: u32) -> ArmFormat {
use ArmFormat::*;
// match i.bit_range(26..28) {
// 0b00 => {
// match (i.bit_range(23..26), i.bit(22) as u32, i.bit_range(20..22), i.bit_range(4..8)) {
// (0b000, 0b0, , _, 0b1001) => Multiply,
// (0b001, _, _, 0b1001) => MultiplyLong,
// (0b010, _, 0b00, 0b1001) => SingleDataSwap,
// (0b010, 0b0, 0b10, 0b0001) => BranchExchange,
// _ => DataProcessing
// }
// }
// 0b01 => {
// if i.bit(4) {
// Undefined
// } else {
// SingleDataTransfer
// }
// }
// 0b10 => {
// }
// 0b11 {
// }
// }
if (0x0ff0_00f0 & i) == 0x0120_0010 {
BranchExchange
} else if (0x0e00_0000 & i) == 0x0a00_0000 {
BranchLink
} else if (0xe000_0010 & i) == 0x0600_0000 {
Undefined
} else if (0x0fb0_0ff0 & i) == 0x0100_0090 {
SingleDataSwap
} else if (0x0fc0_00f0 & i) == 0x0000_0090 {
Multiply
} else if (0x0f80_00f0 & i) == 0x0080_0090 {
MultiplyLong
} else if (0x0c00_0000 & i) == 0x0400_0000 {
SingleDataTransfer
} else if (0x0e40_0F90 & i) == 0x0000_0090 {
HalfwordDataTransferRegOffset
} else if (0x0e40_0090 & i) == 0x0040_0090 {
HalfwordDataTransferImmediateOffset
} else if (0x0e00_0000 & i) == 0x0800_0000 {
BlockDataTransfer
} else if (0x0f00_0000 & i) == 0x0f00_0000 {
SoftwareInterrupt
} else if (0x0c00_0000 & i) == 0x0000_0000 {
DataProcessing
} else {
Undefined
}
}
}
lazy_static! {
pub static ref ARM_FN_LUT: [ArmInstructionHandler; 256] = {
use std::mem::{self, MaybeUninit};
let mut lut: [MaybeUninit<ArmInstructionHandler>; 256] = unsafe {
MaybeUninit::uninit().assume_init()
};
for i in 0..256 {
lut[i] = MaybeUninit::new(Core::arm_undefined);
}
lut[ArmFormat::BranchExchange as usize] = MaybeUninit::new(Core::exec_arm_bx);
lut[ArmFormat::BranchLink as usize] = MaybeUninit::new(Core::exec_arm_b_bl);
lut[ArmFormat::DataProcessing as usize] = MaybeUninit::new(Core::exec_arm_data_processing);
lut[ArmFormat::SoftwareInterrupt as usize] = MaybeUninit::new(Core::exec_arm_swi);
lut[ArmFormat::SingleDataTransfer as usize] = MaybeUninit::new(Core::exec_arm_ldr_str);
lut[ArmFormat::HalfwordDataTransferImmediateOffset as usize] = MaybeUninit::new(Core::exec_arm_ldr_str_hs);
lut[ArmFormat::HalfwordDataTransferRegOffset as usize] = MaybeUninit::new(Core::exec_arm_ldr_str_hs);
lut[ArmFormat::BlockDataTransfer as usize] = MaybeUninit::new(Core::exec_arm_ldm_stm);
lut[ArmFormat::MoveFromStatus as usize] = MaybeUninit::new(Core::exec_arm_mrs);
lut[ArmFormat::MoveToStatus as usize] = MaybeUninit::new(Core::exec_arm_msr_reg);
lut[ArmFormat::MoveToFlags as usize] = MaybeUninit::new(Core::exec_arm_msr_flags);
lut[ArmFormat::Multiply as usize] = MaybeUninit::new(Core::exec_arm_mul_mla);
lut[ArmFormat::MultiplyLong as usize] = MaybeUninit::new(Core::exec_arm_mull_mlal);
lut[ArmFormat::SingleDataSwap as usize] = MaybeUninit::new(Core::exec_arm_swp);
lut[ArmFormat::Undefined as usize] = MaybeUninit::new(Core::arm_undefined);
// Everything is initialized. Transmute the array to the
// initialized type.
unsafe { mem::transmute::<_, [ArmInstructionHandler; 256]>(lut) }
};
// there are 0xfff different hashes
pub static ref ARM_LUT: [u8; 4096] = {
debug!("generating ARM lookup table");
use std::mem::{self, MaybeUninit};
let mut lut: [MaybeUninit<u8>; 4096] = unsafe {
MaybeUninit::uninit().assume_init()
};
for i in 0..4096 {
let x = ((i & 0xff0) << 16) | ((i & 0x00f) << 4);
let fmt = ArmFormat::from(x);
lut[i as usize] = MaybeUninit::new(fmt as u8);
}
// Everything is initialized. Transmute the array to the
// initialized type.
unsafe { mem::transmute::<_, [u8; 4096]>(lut) }
};
}

View file

@ -1,6 +1,11 @@
pub mod display;
pub mod exec;
#[cfg(feature = "arm7tdmi_dispatch_table")]
mod lut;
#[cfg(feature = "arm7tdmi_dispatch_table")]
pub use lut::*;
use serde::{Deserialize, Serialize};
use super::alu::*;
@ -90,6 +95,8 @@ pub enum ArmFormat {
MSR_REG,
/// Tanssfer immediate/register to PSR flags only
MSR_FLAGS,
Undefined,
}
#[derive(Debug, PartialEq, Primitive)]
@ -101,26 +108,29 @@ pub enum ArmHalfwordTransferType {
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
pub struct ArmInstruction {
pub cond: ArmCond,
pub fmt: ArmFormat,
pub raw: u32,
pub pc: Addr,
}
impl ArmInstruction {
pub fn new(raw: u32, pc: Addr, fmt: ArmFormat) -> ArmInstruction {
ArmInstruction { fmt, raw, pc }
}
}
impl InstructionDecoder for ArmInstruction {
type IntType = u32;
fn decode(raw: u32, addr: Addr) -> Self {
use ArmFormat::*;
let cond_code = raw.bit_range(28..32) as u8;
let cond = ArmCond::from_u8(cond_code).expect("invalid arm condition");
let fmt = if (0x0fff_fff0 & raw) == 0x012f_ff10 {
BX
} else if (0x0e00_0000 & raw) == 0x0a00_0000 {
B_BL
} else if (0xe000_0010 & raw) == 0x0600_0000 {
panic!("unknown instruction {:#x} at @{:#x}", raw, addr);
Undefined
} else if (0x0fb0_0ff0 & raw) == 0x0100_0090 {
SWP
} else if (0x0fc0_00f0 & raw) == 0x0000_0090 {
@ -146,11 +156,10 @@ impl InstructionDecoder for ArmInstruction {
} else if (0x0c00_0000 & raw) == 0x0000_0000 {
DP
} else {
panic!("unknown arm instruction {:#x} at @{:#x}", raw, addr);
Undefined
};
ArmInstruction {
cond: cond,
fmt: fmt,
raw: raw,
pc: addr,
@ -177,6 +186,10 @@ impl ArmInstruction {
}
}
pub fn cond(&self) -> ArmCond {
ArmCond::from_u32(self.raw.bit_range(28..32)).unwrap()
}
pub fn rn(&self) -> usize {
match self.fmt {
ArmFormat::MUL_MLA => self.raw.bit_range(12..16) as usize,

View file

@ -6,17 +6,29 @@ use serde::{Deserialize, Serialize};
#[cfg(feature = "debugger")]
use std::fmt;
use super::arm::ArmCond;
#[cfg(feature = "arm7tdmi_dispatch_table")]
use super::arm::{arm_insn_hash, ARM_LUT};
pub use super::exception::Exception;
#[cfg(feature = "arm7tdmi_dispatch_table")]
use super::thumb::THUMB_LUT;
use super::CpuAction;
#[cfg(feature = "debugger")]
use super::DecodedInstruction;
use super::{
arm::*, psr::RegPSR, thumb::ThumbInstruction, Addr, CpuMode, CpuState, InstructionDecoder,
};
use super::{arm::*, psr::RegPSR, thumb::ThumbInstruction, Addr, CpuMode, CpuState};
#[cfg(not(feature = "arm7tdmi_dispatch_table"))]
use super::InstructionDecoder;
use crate::core::bus::Bus;
use crate::core::sysbus::{MemoryAccessType::*, MemoryAccessWidth::*, SysBus};
use bit::BitIndex;
use num::FromPrimitive;
#[cfg(feature = "arm7tdmi_dispatch_table")]
use lazy_static;
#[derive(Serialize, Deserialize, Clone, Debug, Default)]
pub struct Core {
pub pc: u32,
@ -56,6 +68,13 @@ pub struct Core {
impl Core {
pub fn new() -> Core {
#[cfg(feature = "arm7tdmi_dispatch_table")]
{
lazy_static::initialize(&ARM_LUT);
lazy_static::initialize(&ARM_FN_LUT);
lazy_static::initialize(&THUMB_LUT);
}
let cpsr = RegPSR::new(0x0000_00D3);
Core {
memreq: 0xffff_0000, // set memreq to an invalid addr so the first load cycle will be non-sequential
@ -240,43 +259,37 @@ impl Core {
#[allow(non_snake_case)]
#[inline(always)]
pub(super) fn S_cycle32(&mut self, sb: &SysBus, addr: u32) {
self.cycles += 1;
self.cycles += sb.get_cycles(addr, Seq + MemoryAccess32);
self.cycles += sb.get_cycles(addr, Seq, MemoryAccess32);
}
#[allow(non_snake_case)]
#[inline(always)]
pub(super) fn S_cycle16(&mut self, sb: &SysBus, addr: u32) {
self.cycles += 1;
self.cycles += sb.get_cycles(addr, Seq + MemoryAccess16);
self.cycles += sb.get_cycles(addr, Seq, MemoryAccess16);
}
#[allow(non_snake_case)]
#[inline(always)]
pub(super) fn S_cycle8(&mut self, sb: &SysBus, addr: u32) {
self.cycles += 1;
self.cycles += sb.get_cycles(addr, Seq + MemoryAccess8);
self.cycles += sb.get_cycles(addr, Seq, MemoryAccess8);
}
#[allow(non_snake_case)]
#[inline(always)]
pub(super) fn N_cycle32(&mut self, sb: &SysBus, addr: u32) {
self.cycles += 1;
self.cycles += sb.get_cycles(addr, NonSeq + MemoryAccess32);
self.cycles += sb.get_cycles(addr, NonSeq, MemoryAccess32);
}
#[allow(non_snake_case)]
#[inline(always)]
pub(super) fn N_cycle16(&mut self, sb: &SysBus, addr: u32) {
self.cycles += 1;
self.cycles += sb.get_cycles(addr, NonSeq + MemoryAccess16);
self.cycles += sb.get_cycles(addr, NonSeq, MemoryAccess16);
}
#[allow(non_snake_case)]
#[inline(always)]
pub(super) fn N_cycle8(&mut self, sb: &SysBus, addr: u32) {
self.cycles += 1;
self.cycles += sb.get_cycles(addr, NonSeq + MemoryAccess8);
self.cycles += sb.get_cycles(addr, NonSeq, MemoryAccess8);
}
#[inline]
@ -301,32 +314,56 @@ impl Core {
}
}
fn step_arm_exec(&mut self, insn: u32, sb: &mut SysBus) {
let decoded_arm = ArmInstruction::decode(insn, self.pc.wrapping_sub(8));
#[cfg(feature = "debugger")]
{
self.gpr_previous = self.get_registers();
self.last_executed = Some(DecodedInstruction::Arm(decoded_arm.clone()));
}
let result = self.exec_arm(sb, &decoded_arm);
match result {
CpuAction::AdvancePC => self.advance_arm(),
CpuAction::FlushPipeline => {}
}
#[cfg(feature = "debugger")]
fn debugger_record_step(&mut self, d: DecodedInstruction) {
self.gpr_previous = self.get_registers();
self.last_executed = Some(d);
}
fn step_thumb_exec(&mut self, insn: u16, sb: &mut SysBus) {
let decoded_thumb = ThumbInstruction::decode(insn, self.pc.wrapping_sub(4));
#[cfg(feature = "arm7tdmi_dispatch_table")]
fn step_arm_exec(&mut self, insn: u32, sb: &mut SysBus) -> CpuAction {
let l1_index = ARM_LUT[arm_insn_hash(insn)] as usize;
let handler_fn = ARM_FN_LUT[l1_index];
// This is safe because the table can't hold invalid indices
let arm_format: ArmFormat = unsafe { std::mem::transmute(l1_index as u8) };
let arm_insn = ArmInstruction::new(insn, self.pc.wrapping_sub(8), arm_format);
#[cfg(feature = "debugger")]
{
self.gpr_previous = self.get_registers();
self.last_executed = Some(DecodedInstruction::Thumb(decoded_thumb.clone()));
}
let result = self.exec_thumb(sb, &decoded_thumb);
match result {
CpuAction::AdvancePC => self.advance_thumb(),
CpuAction::FlushPipeline => {}
}
self.debugger_record_step(DecodedInstruction::Arm(arm_insn.clone()));
(handler_fn)(self, sb, &arm_insn)
}
#[cfg(feature = "arm7tdmi_dispatch_table")]
fn step_thumb_exec(&mut self, insn: u16, sb: &mut SysBus) -> CpuAction {
let thumb_info = &THUMB_LUT[(insn >> 6) as usize];
let thumb_insn = ThumbInstruction::new(insn, self.pc.wrapping_sub(4), thumb_info.fmt);
#[cfg(feature = "debugger")]
self.debugger_record_step(DecodedInstruction::Thumb(thumb_insn.clone()));
(thumb_info.handler_fn)(self, sb, &thumb_insn)
}
#[cfg(not(feature = "arm7tdmi_dispatch_table"))]
fn step_arm_exec(&mut self, insn: u32, sb: &mut SysBus) -> CpuAction {
let arm_insn = ArmInstruction::decode(insn, self.pc.wrapping_sub(8));
#[cfg(feature = "debugger")]
self.debugger_record_step(DecodedInstruction::Arm(arm_insn.clone()));
self.exec_arm(sb, &arm_insn)
}
#[cfg(not(feature = "arm7tdmi_dispatch_table"))]
fn step_thumb_exec(&mut self, insn: u16, sb: &mut SysBus) -> CpuAction {
let thumb_insn = ThumbInstruction::decode(insn, self.pc.wrapping_sub(4));
#[cfg(feature = "debugger")]
self.debugger_record_step(DecodedInstruction::Thumb(thumb_insn.clone()));
self.exec_thumb(sb, &thumb_insn)
}
#[inline(always)]
@ -370,14 +407,29 @@ impl Core {
let insn = self.pipeline[0];
self.pipeline[0] = self.pipeline[1];
self.pipeline[1] = fetched_now;
self.step_arm_exec(insn, bus)
let cond =
ArmCond::from_u32(insn.bit_range(28..32)).expect("invalid arm condition");
if cond != ArmCond::AL {
if !self.check_arm_cond(cond) {
self.S_cycle32(bus, self.pc);
self.advance_arm();
return;
}
}
match self.step_arm_exec(insn, bus) {
CpuAction::AdvancePC => self.advance_arm(),
CpuAction::FlushPipeline => {}
}
}
CpuState::THUMB => {
let fetched_now = bus.read_16(pc);
let insn = self.pipeline[0];
self.pipeline[0] = self.pipeline[1];
self.pipeline[1] = fetched_now as u32;
self.step_thumb_exec(insn as u16, bus)
match self.step_thumb_exec(insn as u16, bus) {
CpuAction::AdvancePC => self.advance_thumb(),
CpuAction::FlushPipeline => {}
}
}
}
}

View file

@ -294,6 +294,7 @@ impl fmt::Display for ThumbInstruction {
ThumbFormat::Swi => self.fmt_thumb_swi(f),
ThumbFormat::Branch => self.fmt_thumb_branch(f),
ThumbFormat::BranchLongWithLink => self.fmt_thumb_branch_long_with_link(f),
ThumbFormat::Undefined => write!(f, "<Undefined>"),
}
}
}

View file

@ -18,7 +18,7 @@ fn pop(cpu: &mut Core, bus: &mut SysBus, r: usize) {
impl Core {
/// Format 1
fn exec_thumb_move_shifted_reg(
pub(in super::super) fn exec_thumb_move_shifted_reg(
&mut self,
sb: &mut SysBus,
insn: &ThumbInstruction,
@ -43,7 +43,11 @@ impl Core {
}
/// Format 2
fn exec_thumb_add_sub(&mut self, sb: &mut SysBus, insn: &ThumbInstruction) -> CpuAction {
pub(in super::super) fn exec_thumb_add_sub(
&mut self,
sb: &mut SysBus,
insn: &ThumbInstruction,
) -> CpuAction {
let rd = (insn.raw & 0b111) as usize;
let op1 = self.get_reg(insn.rs());
let op2 = if insn.is_immediate_operand() {
@ -68,7 +72,7 @@ impl Core {
}
/// Format 3
fn exec_thumb_data_process_imm(
pub(in super::super) fn exec_thumb_data_process_imm(
&mut self,
sb: &mut SysBus,
insn: &ThumbInstruction,
@ -96,7 +100,11 @@ impl Core {
}
/// Format 4
fn exec_thumb_alu_ops(&mut self, sb: &mut SysBus, insn: &ThumbInstruction) -> CpuAction {
pub(in super::super) fn exec_thumb_alu_ops(
&mut self,
sb: &mut SysBus,
insn: &ThumbInstruction,
) -> CpuAction {
let rd = (insn.raw & 0b111) as usize;
let rs = insn.rs();
let dst = self.get_reg(rd);
@ -153,7 +161,7 @@ impl Core {
}
/// Format 5
fn exec_thumb_hi_reg_op_or_bx(
pub(in super::super) fn exec_thumb_hi_reg_op_or_bx(
&mut self,
sb: &mut SysBus,
insn: &ThumbInstruction,
@ -205,7 +213,11 @@ impl Core {
}
/// Format 6
fn exec_thumb_ldr_pc(&mut self, sb: &mut SysBus, insn: &ThumbInstruction) -> CpuAction {
pub(in super::super) fn exec_thumb_ldr_pc(
&mut self,
sb: &mut SysBus,
insn: &ThumbInstruction,
) -> CpuAction {
let rd = insn.raw.bit_range(8..11) as usize;
let ofs = insn.word8() as Addr;
@ -261,7 +273,7 @@ impl Core {
}
/// Format 7
fn exec_thumb_ldr_str_reg_offset(
pub(in super::super) fn exec_thumb_ldr_str_reg_offset(
&mut self,
bus: &mut SysBus,
insn: &ThumbInstruction,
@ -272,7 +284,11 @@ impl Core {
}
/// Format 8
fn exec_thumb_ldr_str_shb(&mut self, sb: &mut SysBus, insn: &ThumbInstruction) -> CpuAction {
pub(in super::super) fn exec_thumb_ldr_str_shb(
&mut self,
sb: &mut SysBus,
insn: &ThumbInstruction,
) -> CpuAction {
let rb = insn.raw.bit_range(3..6) as usize;
let rd = (insn.raw & 0b111) as usize;
@ -318,7 +334,7 @@ impl Core {
}
/// Format 9
fn exec_thumb_ldr_str_imm_offset(
pub(in super::super) fn exec_thumb_ldr_str_imm_offset(
&mut self,
sb: &mut SysBus,
insn: &ThumbInstruction,
@ -335,7 +351,7 @@ impl Core {
}
/// Format 10
fn exec_thumb_ldr_str_halfword(
pub(in super::super) fn exec_thumb_ldr_str_halfword(
&mut self,
sb: &mut SysBus,
insn: &ThumbInstruction,
@ -359,7 +375,11 @@ impl Core {
}
/// Format 11
fn exec_thumb_ldr_str_sp(&mut self, sb: &mut SysBus, insn: &ThumbInstruction) -> CpuAction {
pub(in super::super) fn exec_thumb_ldr_str_sp(
&mut self,
sb: &mut SysBus,
insn: &ThumbInstruction,
) -> CpuAction {
let addr = self.gpr[REG_SP] + (insn.word8() as Addr);
let rd = insn.raw.bit_range(8..11) as usize;
if insn.is_load() {
@ -377,7 +397,11 @@ impl Core {
}
/// Format 12
fn exec_thumb_load_address(&mut self, sb: &mut SysBus, insn: &ThumbInstruction) -> CpuAction {
pub(in super::super) fn exec_thumb_load_address(
&mut self,
sb: &mut SysBus,
insn: &ThumbInstruction,
) -> CpuAction {
let rd = insn.raw.bit_range(8..11) as usize;
let result = if insn.flag(ThumbInstruction::FLAG_SP) {
self.gpr[REG_SP] + (insn.word8() as Addr)
@ -391,7 +415,11 @@ impl Core {
}
/// Format 13
fn exec_thumb_add_sp(&mut self, sb: &mut SysBus, insn: &ThumbInstruction) -> CpuAction {
pub(in super::super) fn exec_thumb_add_sp(
&mut self,
sb: &mut SysBus,
insn: &ThumbInstruction,
) -> CpuAction {
let op1 = self.gpr[REG_SP] as i32;
let op2 = insn.sword7();
@ -402,7 +430,11 @@ impl Core {
}
/// Format 14
fn exec_thumb_push_pop(&mut self, sb: &mut SysBus, insn: &ThumbInstruction) -> CpuAction {
pub(in super::super) fn exec_thumb_push_pop(
&mut self,
sb: &mut SysBus,
insn: &ThumbInstruction,
) -> CpuAction {
let mut result = CpuAction::AdvancePC;
// (From GBATEK) Execution Time: nS+1N+1I (POP), (n+1)S+2N+1I (POP PC), or (n-1)S+2N (PUSH).
@ -450,7 +482,11 @@ impl Core {
}
/// Format 15
fn exec_thumb_ldm_stm(&mut self, sb: &mut SysBus, insn: &ThumbInstruction) -> CpuAction {
pub(in super::super) fn exec_thumb_ldm_stm(
&mut self,
sb: &mut SysBus,
insn: &ThumbInstruction,
) -> CpuAction {
let mut result = CpuAction::AdvancePC;
// (From GBATEK) Execution Time: nS+1N+1I (POP), (n+1)S+2N+1I (POP PC), or (n-1)S+2N (PUSH).
@ -527,7 +563,7 @@ impl Core {
}
/// Format 16
fn exec_thumb_branch_with_cond(
pub(in super::super) fn exec_thumb_branch_with_cond(
&mut self,
sb: &mut SysBus,
insn: &ThumbInstruction,
@ -545,14 +581,22 @@ impl Core {
}
/// Format 17
fn exec_thumb_swi(&mut self, sb: &mut SysBus, _insn: &ThumbInstruction) -> CpuAction {
pub(in super::super) fn exec_thumb_swi(
&mut self,
sb: &mut SysBus,
_insn: &ThumbInstruction,
) -> CpuAction {
self.N_cycle16(sb, self.pc);
self.exception(sb, Exception::SoftwareInterrupt, self.pc - 2);
CpuAction::FlushPipeline
}
/// Format 18
fn exec_thumb_branch(&mut self, sb: &mut SysBus, insn: &ThumbInstruction) -> CpuAction {
pub(in super::super) fn exec_thumb_branch(
&mut self,
sb: &mut SysBus,
insn: &ThumbInstruction,
) -> CpuAction {
let offset = ((insn.offset11() << 21) >> 20) as i32;
self.pc = (self.pc as i32).wrapping_add(offset) as u32;
self.S_cycle16(sb, self.pc);
@ -561,7 +605,7 @@ impl Core {
}
/// Format 19
fn exec_thumb_branch_long_with_link(
pub(in super::super) fn exec_thumb_branch_long_with_link(
&mut self,
sb: &mut SysBus,
insn: &ThumbInstruction,
@ -584,6 +628,13 @@ impl Core {
}
}
pub fn thumb_undefined(&mut self, _: &mut SysBus, insn: &ThumbInstruction) -> CpuAction {
panic!(
"executing undefind thumb instruction {:04x} at @{:08x}",
insn.raw, insn.pc
)
}
pub fn exec_thumb(&mut self, bus: &mut SysBus, insn: &ThumbInstruction) -> CpuAction {
match insn.fmt {
ThumbFormat::MoveShiftedReg => self.exec_thumb_move_shifted_reg(bus, insn),
@ -605,6 +656,7 @@ impl Core {
ThumbFormat::Swi => self.exec_thumb_swi(bus, insn),
ThumbFormat::Branch => self.exec_thumb_branch(bus, insn),
ThumbFormat::BranchLongWithLink => self.exec_thumb_branch_long_with_link(bus, insn),
ThumbFormat::Undefined => self.thumb_undefined(bus, insn),
}
}
}

View file

@ -0,0 +1,65 @@
use super::super::super::SysBus;
use super::super::Core;
use super::super::CpuAction;
use super::super::InstructionDecoder;
use super::{ThumbFormat, ThumbInstruction};
pub type ThumbInstructionHandler = fn(&mut Core, &mut SysBus, &ThumbInstruction) -> CpuAction;
impl From<ThumbFormat> for ThumbInstructionHandler {
fn from(thumb_fmt: ThumbFormat) -> ThumbInstructionHandler {
match thumb_fmt {
ThumbFormat::MoveShiftedReg => Core::exec_thumb_move_shifted_reg,
ThumbFormat::AddSub => Core::exec_thumb_add_sub,
ThumbFormat::DataProcessImm => Core::exec_thumb_data_process_imm,
ThumbFormat::AluOps => Core::exec_thumb_alu_ops,
ThumbFormat::HiRegOpOrBranchExchange => Core::exec_thumb_hi_reg_op_or_bx,
ThumbFormat::LdrPc => Core::exec_thumb_ldr_pc,
ThumbFormat::LdrStrRegOffset => Core::exec_thumb_ldr_str_reg_offset,
ThumbFormat::LdrStrSHB => Core::exec_thumb_ldr_str_shb,
ThumbFormat::LdrStrImmOffset => Core::exec_thumb_ldr_str_imm_offset,
ThumbFormat::LdrStrHalfWord => Core::exec_thumb_ldr_str_halfword,
ThumbFormat::LdrStrSp => Core::exec_thumb_ldr_str_sp,
ThumbFormat::LoadAddress => Core::exec_thumb_load_address,
ThumbFormat::AddSp => Core::exec_thumb_add_sp,
ThumbFormat::PushPop => Core::exec_thumb_push_pop,
ThumbFormat::LdmStm => Core::exec_thumb_ldm_stm,
ThumbFormat::BranchConditional => Core::exec_thumb_branch_with_cond,
ThumbFormat::Swi => Core::exec_thumb_swi,
ThumbFormat::Branch => Core::exec_thumb_branch,
ThumbFormat::BranchLongWithLink => Core::exec_thumb_branch_long_with_link,
ThumbFormat::Undefined => Core::thumb_undefined,
}
}
}
pub struct ThumbInstructionInfo {
pub fmt: ThumbFormat,
pub handler_fn: ThumbInstructionHandler,
}
lazy_static! {
pub static ref THUMB_LUT: [ThumbInstructionInfo; 1024] = {
debug!("generating THUMB lookup table");
use std::mem::{self, MaybeUninit};
let mut lut: [MaybeUninit<ThumbInstructionInfo>; 1024] = unsafe {
MaybeUninit::uninit().assume_init()
};
for i in 0..1024 {
let insn = ThumbInstruction::decode(i << 6, 0);
let info = ThumbInstructionInfo {
fmt: insn.fmt,
handler_fn: insn.fmt.into()
};
lut[i as usize] = MaybeUninit::new(info);
}
// Everything is initialized. Transmute the array to the
// initialized type.
unsafe { mem::transmute::<_, [ThumbInstructionInfo; 1024]>(lut) }
};
}

View file

@ -1,13 +1,16 @@
use super::alu::*;
use super::arm::*;
use super::{Addr, InstructionDecoder};
use crate::bit::BitIndex;
use crate::byteorder::{LittleEndian, ReadBytesExt};
use crate::num::FromPrimitive;
use super::alu::*;
use super::arm::*;
use super::{Addr, InstructionDecoder};
pub mod display;
pub mod exec;
#[cfg(feature = "arm7tdmi_dispatch_table")]
mod lut;
#[cfg(feature = "arm7tdmi_dispatch_table")]
pub use lut::*;
#[derive(Serialize, Deserialize, Debug, Copy, Clone, PartialEq)]
pub enum ThumbFormat {
@ -49,6 +52,9 @@ pub enum ThumbFormat {
Branch,
/// Format 19
BranchLongWithLink,
/// Not an actual thumb format
Undefined,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
@ -58,6 +64,12 @@ pub struct ThumbInstruction {
pub pc: Addr,
}
impl ThumbInstruction {
pub fn new(raw: u16, pc: Addr, fmt: ThumbFormat) -> ThumbInstruction {
ThumbInstruction { fmt, raw, pc }
}
}
impl InstructionDecoder for ThumbInstruction {
type IntType = u16;
@ -103,14 +115,10 @@ impl InstructionDecoder for ThumbInstruction {
} else if raw & 0xf000 == 0xf000 {
BranchLongWithLink
} else {
panic!("unknown thumb instruction {:#x} at @{:#x}", raw, addr);
Undefined
};
ThumbInstruction {
fmt: fmt,
raw: raw,
pc: addr,
}
ThumbInstruction::new(raw, addr, fmt)
}
fn decode_from_bytes(bytes: &[u8], addr: Addr) -> Self {

View file

@ -85,23 +85,24 @@ impl Debugger {
while self.gba.cpu.last_executed.is_none() {
self.gba.cpu.step(&mut self.gba.sysbus);
}
let last_executed = self.gba.cpu.last_executed.unwrap();
print!(
"{}\t{}",
Colour::Black
.bold()
.italic()
.on(Colour::White)
.paint(format!("Executed at @0x{:08x}:", last_executed.get_pc(),)),
last_executed
);
println!(
"{}",
Colour::Purple.dimmed().italic().paint(format!(
"\t\t/// Next instruction at @0x{:08x}",
self.gba.cpu.get_next_pc()
))
);
if let Some(last_executed) = &self.gba.cpu.last_executed {
print!(
"{}\t{}",
Colour::Black
.bold()
.italic()
.on(Colour::White)
.paint(format!("Executed at @0x{:08x}:", last_executed.get_pc(),)),
last_executed
);
println!(
"{}",
Colour::Purple.dimmed().italic().paint(format!(
"\t\t/// Next instruction at @0x{:08x}",
self.gba.cpu.get_next_pc()
))
);
}
}
println!("{}\n", self.gba.cpu);
}

View file

@ -1,3 +1,7 @@
#[cfg(feature = "arm7tdmi_dispatch_table")]
#[macro_use]
extern crate lazy_static;
#[macro_use]
extern crate serde;