Major refactor: change memory to be based on a view, rather than some slice
This fixes a problem where we couldn't read memory in a way that wraps around, like the gameboy does.old-bit-manip
parent
10b9c00f72
commit
a8736052d1
11
src/cpu.rs
11
src/cpu.rs
|
@ -24,14 +24,13 @@ impl Processor {
|
|||
.memory
|
||||
.get_from(pc.into())
|
||||
.expect("program counter out of range");
|
||||
let (instruction, rest_memory) =
|
||||
parse::next_instruction(memory_view).expect("invalid instruction");
|
||||
let (next_pc, _carry) =
|
||||
pc.overflowing_add(u16::try_from(memory_view.len() - rest_memory.len()).unwrap());
|
||||
|
||||
let (instruction, bytes_read) =
|
||||
parse::next_instruction(&memory_view).expect("invalid instruction");
|
||||
|
||||
self.run_instruction(&instruction);
|
||||
// wow this code sucks; I may change the parse code to return a number of bytes read,
|
||||
// rather than a memory view.
|
||||
|
||||
let (next_pc, _carry) = pc.overflowing_add(bytes_read);
|
||||
self.registers.program_counter = next_pc;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
//! The `instructions` module holds structural definitions for all of the instructions that can run on the gameboy.
|
||||
|
||||
use super::parse::{self, ParseResult};
|
||||
use crate::memory::View;
|
||||
|
||||
use super::parse::{self, Error};
|
||||
|
||||
pub mod arith8;
|
||||
pub mod load16;
|
||||
|
@ -26,9 +28,12 @@ impl RunnableInstruction {
|
|||
/// `from_data` will produce an instruction from the given data and return the data after
|
||||
/// processing that operation.
|
||||
///
|
||||
/// This is only available for testing in order to get instructions that can be run standalone
|
||||
///
|
||||
/// # Errors
|
||||
/// Returns an error if the instruction couldn't be parsed.
|
||||
pub fn from_data(data: &[u8]) -> ParseResult {
|
||||
parse::next_instruction(data)
|
||||
pub fn from_data(data: &[u8]) -> Result<(RunnableInstruction, &[u8]), Error> {
|
||||
parse::next_instruction(&View::new_from_data(data, 0))
|
||||
.map(|(ins, offset)| (ins, &data[offset.into()..]))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
//! The `parse` module holds functions that will parse streams of data into an [`crate::instructions::Instruction`]
|
||||
|
||||
use crate::memory::View;
|
||||
|
||||
use super::instructions::RunnableInstruction;
|
||||
use thiserror::Error;
|
||||
|
||||
|
@ -17,20 +19,21 @@ pub enum Error {
|
|||
NotEnoughArgs(u8),
|
||||
}
|
||||
|
||||
/// `ParseResult` is the result of a Parse operation
|
||||
pub(super) type ParseResult<'a> = Result<(RunnableInstruction, &'a [u8]), Error>;
|
||||
/// `ParseResult` is the result of a Parse operation, which includes the parsed instruction and the number of bytes read.
|
||||
pub(super) type ParseOutput = (RunnableInstruction, u16);
|
||||
pub(super) type ParseResult = Result<ParseOutput, Error>;
|
||||
|
||||
/// `OpcodeParser` takes input data, parses out an opcode (and its associated arguments) if it can, and returns
|
||||
/// the remaining data after reading it.
|
||||
trait OpcodeParser {
|
||||
/// Parse an opcode and all of its data from the given data buffer. Returns either an error, or
|
||||
/// The parsed instruction, and a slice of all the data after the instruction
|
||||
fn parse_opcode(data: &[u8]) -> ParseResult;
|
||||
fn parse_opcode(data: &View) -> ParseResult;
|
||||
}
|
||||
|
||||
/// `next_instruction` will parse the next instruction from the given data stream. Returns either an error,
|
||||
/// or the parsed instruction, and a slice of all the data after the instruction
|
||||
pub fn next_instruction(data: &[u8]) -> ParseResult {
|
||||
pub fn next_instruction(data: &View) -> ParseResult {
|
||||
let parse_funcs = &[
|
||||
load8::immediate::Immediate8BitLoadParser::parse_opcode,
|
||||
load8::transfer::Between8BitRegisterParser::parse_opcode,
|
||||
|
@ -49,11 +52,10 @@ pub fn next_instruction(data: &[u8]) -> ParseResult {
|
|||
}
|
||||
}
|
||||
|
||||
let opcode = get_opcode_from_data(data)?;
|
||||
|
||||
let opcode = get_opcode_from_data(data);
|
||||
Err(Error::UnknownOpcode(opcode))
|
||||
}
|
||||
|
||||
fn get_opcode_from_data(data: &[u8]) -> Result<u8, Error> {
|
||||
data.first().copied().ok_or(Error::NoData)
|
||||
fn get_opcode_from_data(data: &View) -> u8 {
|
||||
data.get()
|
||||
}
|
||||
|
|
|
@ -1,116 +1,111 @@
|
|||
use crate::{
|
||||
cpu::{
|
||||
instructions::{arith8::EightBitArithmeticInstruction, Instruction, RunnableInstruction},
|
||||
parse::{self, Error, OpcodeParser, ParseResult},
|
||||
parse::{self, Error, OpcodeParser, ParseOutput, ParseResult},
|
||||
},
|
||||
memory::{GetViewTuple, View},
|
||||
register,
|
||||
};
|
||||
|
||||
pub struct EightBitAddParser;
|
||||
|
||||
impl OpcodeParser for EightBitAddParser {
|
||||
fn parse_opcode(data: &[u8]) -> ParseResult {
|
||||
let opcode = parse::get_opcode_from_data(data)?;
|
||||
fn parse_opcode(data: &View) -> ParseResult {
|
||||
let opcode = parse::get_opcode_from_data(data);
|
||||
match opcode {
|
||||
0x87 => build_add_register_to_a_data(register::SingleEightBit::A, data),
|
||||
0x80 => build_add_register_to_a_data(register::SingleEightBit::B, data),
|
||||
0x81 => build_add_register_to_a_data(register::SingleEightBit::C, data),
|
||||
0x82 => build_add_register_to_a_data(register::SingleEightBit::D, data),
|
||||
0x83 => build_add_register_to_a_data(register::SingleEightBit::E, data),
|
||||
0x84 => build_add_register_to_a_data(register::SingleEightBit::H, data),
|
||||
0x85 => build_add_register_to_a_data(register::SingleEightBit::L, data),
|
||||
0x86 => build_add_hl_address_to_a_data(data),
|
||||
0xC6 => build_add_immediate_to_a_data(data),
|
||||
0x88 => build_add_register_to_a_with_carry_data(register::SingleEightBit::B, data),
|
||||
0x89 => build_add_register_to_a_with_carry_data(register::SingleEightBit::C, data),
|
||||
0x8A => build_add_register_to_a_with_carry_data(register::SingleEightBit::D, data),
|
||||
0x8B => build_add_register_to_a_with_carry_data(register::SingleEightBit::E, data),
|
||||
0x8C => build_add_register_to_a_with_carry_data(register::SingleEightBit::H, data),
|
||||
0x8D => build_add_register_to_a_with_carry_data(register::SingleEightBit::L, data),
|
||||
0xCE => build_add_immediate_to_a_with_carry_data(data),
|
||||
0x87 => Ok(build_add_register_to_a_data(register::SingleEightBit::A)),
|
||||
0x80 => Ok(build_add_register_to_a_data(register::SingleEightBit::B)),
|
||||
0x81 => Ok(build_add_register_to_a_data(register::SingleEightBit::C)),
|
||||
0x82 => Ok(build_add_register_to_a_data(register::SingleEightBit::D)),
|
||||
0x83 => Ok(build_add_register_to_a_data(register::SingleEightBit::E)),
|
||||
0x84 => Ok(build_add_register_to_a_data(register::SingleEightBit::H)),
|
||||
0x85 => Ok(build_add_register_to_a_data(register::SingleEightBit::L)),
|
||||
0x86 => Ok(build_add_hl_address_to_a_data()),
|
||||
0xC6 => Ok(build_add_immediate_to_a_data(data)),
|
||||
0x88 => Ok(build_add_register_to_a_with_carry_data(
|
||||
register::SingleEightBit::B,
|
||||
)),
|
||||
0x89 => Ok(build_add_register_to_a_with_carry_data(
|
||||
register::SingleEightBit::C,
|
||||
)),
|
||||
0x8A => Ok(build_add_register_to_a_with_carry_data(
|
||||
register::SingleEightBit::D,
|
||||
)),
|
||||
0x8B => Ok(build_add_register_to_a_with_carry_data(
|
||||
register::SingleEightBit::E,
|
||||
)),
|
||||
0x8C => Ok(build_add_register_to_a_with_carry_data(
|
||||
register::SingleEightBit::H,
|
||||
)),
|
||||
0x8D => Ok(build_add_register_to_a_with_carry_data(
|
||||
register::SingleEightBit::L,
|
||||
)),
|
||||
0xCE => Ok(build_add_immediate_to_a_with_carry_data(data)),
|
||||
_ => Err(Error::UnknownOpcode(opcode)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn build_add_register_to_a_data(src: register::SingleEightBit, data: &[u8]) -> ParseResult {
|
||||
data.get(1..)
|
||||
.map(|remaining_data| {
|
||||
(
|
||||
RunnableInstruction {
|
||||
instruction: Instruction::EightBitArithmetic(
|
||||
EightBitArithmeticInstruction::AddSingleRegisterToA { src },
|
||||
),
|
||||
cycles: 4,
|
||||
},
|
||||
remaining_data,
|
||||
)
|
||||
})
|
||||
.ok_or(Error::NoData)
|
||||
fn build_add_register_to_a_data(src: register::SingleEightBit) -> ParseOutput {
|
||||
(
|
||||
RunnableInstruction {
|
||||
instruction: Instruction::EightBitArithmetic(
|
||||
EightBitArithmeticInstruction::AddSingleRegisterToA { src },
|
||||
),
|
||||
cycles: 4,
|
||||
},
|
||||
1,
|
||||
)
|
||||
}
|
||||
|
||||
fn build_add_register_to_a_with_carry_data(
|
||||
src: register::SingleEightBit,
|
||||
data: &[u8],
|
||||
) -> ParseResult {
|
||||
data.get(1..)
|
||||
.map(|remaining_data| {
|
||||
(
|
||||
RunnableInstruction {
|
||||
instruction: Instruction::EightBitArithmetic(
|
||||
EightBitArithmeticInstruction::AddSingleRegisterToAWithCarry { src },
|
||||
),
|
||||
cycles: 4,
|
||||
},
|
||||
remaining_data,
|
||||
)
|
||||
})
|
||||
.ok_or(Error::NoData)
|
||||
fn build_add_register_to_a_with_carry_data(src: register::SingleEightBit) -> ParseOutput {
|
||||
(
|
||||
RunnableInstruction {
|
||||
instruction: Instruction::EightBitArithmetic(
|
||||
EightBitArithmeticInstruction::AddSingleRegisterToAWithCarry { src },
|
||||
),
|
||||
cycles: 4,
|
||||
},
|
||||
1,
|
||||
)
|
||||
}
|
||||
|
||||
fn build_add_hl_address_to_a_data(data: &[u8]) -> ParseResult {
|
||||
data.get(1..)
|
||||
.map(|remaining_data| {
|
||||
(
|
||||
RunnableInstruction {
|
||||
instruction: Instruction::EightBitArithmetic(
|
||||
EightBitArithmeticInstruction::AddHLAddressToA,
|
||||
),
|
||||
cycles: 8,
|
||||
},
|
||||
remaining_data,
|
||||
)
|
||||
})
|
||||
.ok_or(Error::NoData)
|
||||
fn build_add_hl_address_to_a_data() -> ParseOutput {
|
||||
(
|
||||
RunnableInstruction {
|
||||
instruction: Instruction::EightBitArithmetic(
|
||||
EightBitArithmeticInstruction::AddHLAddressToA,
|
||||
),
|
||||
cycles: 8,
|
||||
},
|
||||
1,
|
||||
)
|
||||
}
|
||||
|
||||
fn build_add_immediate_to_a_data(data: &[u8]) -> ParseResult {
|
||||
let opcode = parse::get_opcode_from_data(data)?;
|
||||
let n = data.get(1).copied().ok_or(Error::NotEnoughArgs(opcode))?;
|
||||
fn build_add_immediate_to_a_data(data: &View) -> ParseOutput {
|
||||
let (_opcode, n) = data.get_tuple();
|
||||
|
||||
Ok((
|
||||
(
|
||||
RunnableInstruction {
|
||||
instruction: Instruction::EightBitArithmetic(
|
||||
EightBitArithmeticInstruction::AddImmediateToA { n },
|
||||
),
|
||||
cycles: 8,
|
||||
},
|
||||
// Guaranteed to exist because the above succeeded
|
||||
&data[2..],
|
||||
))
|
||||
2,
|
||||
)
|
||||
}
|
||||
|
||||
fn build_add_immediate_to_a_with_carry_data(data: &[u8]) -> ParseResult {
|
||||
let opcode = parse::get_opcode_from_data(data)?;
|
||||
let n = data.get(1).copied().ok_or(Error::NotEnoughArgs(opcode))?;
|
||||
fn build_add_immediate_to_a_with_carry_data(data: &View) -> ParseOutput {
|
||||
let (_opcode, n) = data.get_tuple();
|
||||
|
||||
Ok((
|
||||
(
|
||||
RunnableInstruction {
|
||||
instruction: Instruction::EightBitArithmetic(
|
||||
EightBitArithmeticInstruction::AddImmediateToAWithCarry { n },
|
||||
),
|
||||
cycles: 8,
|
||||
},
|
||||
&data[2..],
|
||||
))
|
||||
2,
|
||||
)
|
||||
}
|
||||
|
|
|
@ -1,64 +1,63 @@
|
|||
use crate::register;
|
||||
use crate::cpu::instructions::load16::SixteenBitLoadInstruction;
|
||||
use crate::cpu::parse::ParseOutput;
|
||||
use crate::cpu::{
|
||||
instructions::{Instruction, RunnableInstruction},
|
||||
parse::{self, Error, OpcodeParser, ParseResult},
|
||||
};
|
||||
use crate::memory::{GetViewTuple, View};
|
||||
use crate::register;
|
||||
|
||||
#[allow(clippy::module_name_repetitions)]
|
||||
pub struct Immediate16BitLoadParser;
|
||||
|
||||
impl OpcodeParser for Immediate16BitLoadParser {
|
||||
fn parse_opcode(data: &[u8]) -> ParseResult {
|
||||
let opcode = parse::get_opcode_from_data(data)?;
|
||||
fn parse_opcode(data: &View) -> ParseResult {
|
||||
let opcode = parse::get_opcode_from_data(data);
|
||||
|
||||
match opcode {
|
||||
0x01 => make_load_immediate_data(
|
||||
0x01 => Ok(make_load_immediate_data(
|
||||
register::SixteenBit::Combined(register::Combined::BC),
|
||||
data,
|
||||
),
|
||||
0x11 => make_load_immediate_data(
|
||||
)),
|
||||
0x11 => Ok(make_load_immediate_data(
|
||||
register::SixteenBit::Combined(register::Combined::DE),
|
||||
data,
|
||||
),
|
||||
0x21 => make_load_immediate_data(
|
||||
)),
|
||||
0x21 => Ok(make_load_immediate_data(
|
||||
register::SixteenBit::Combined(register::Combined::HL),
|
||||
data,
|
||||
),
|
||||
0x31 => make_load_immediate_data(
|
||||
)),
|
||||
0x31 => Ok(make_load_immediate_data(
|
||||
register::SixteenBit::Single(register::SingleSixteenBit::StackPointer),
|
||||
data,
|
||||
),
|
||||
0xF8 => make_load_effective_address(register::Combined::HL, data),
|
||||
)),
|
||||
0xF8 => Ok(make_load_effective_address(register::Combined::HL, data)),
|
||||
_ => Err(Error::UnknownOpcode(opcode)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn make_load_immediate_data(dst: register::SixteenBit, data: &[u8]) -> ParseResult {
|
||||
let opcode = parse::get_opcode_from_data(data)?;
|
||||
let args = data.get(1..=2).ok_or(Error::NotEnoughArgs(opcode))?;
|
||||
fn make_load_immediate_data(dst: register::SixteenBit, data: &View) -> ParseOutput {
|
||||
let (_opcode, lower_bytes, upper_bytes) = data.get_tuple();
|
||||
|
||||
// manual doesn't state this should be LE, but some inspection of games and googling
|
||||
// indicates it should be.
|
||||
let value = u16::from_le_bytes([args[0], args[1]]);
|
||||
Ok((
|
||||
let value = u16::from_le_bytes([lower_bytes, upper_bytes]);
|
||||
(
|
||||
RunnableInstruction {
|
||||
instruction: Instruction::SixteenBitLoad(
|
||||
SixteenBitLoadInstruction::LoadImmediateToRegister { value, dst },
|
||||
),
|
||||
cycles: 12,
|
||||
},
|
||||
// guaranteed to succeed given we already took the first two elements as args
|
||||
&data[3..],
|
||||
))
|
||||
3,
|
||||
)
|
||||
}
|
||||
|
||||
fn make_load_effective_address(dst: register::Combined, data: &[u8]) -> ParseResult {
|
||||
let opcode = parse::get_opcode_from_data(data)?;
|
||||
let unsigned_value = data.get(1).ok_or(Error::NotEnoughArgs(opcode))?;
|
||||
fn make_load_effective_address(dst: register::Combined, data: &View) -> ParseOutput {
|
||||
let (_opcode, unsigned_value) = data.get_tuple();
|
||||
let signed_value = {
|
||||
match i8::try_from(*unsigned_value) {
|
||||
match i8::try_from(unsigned_value) {
|
||||
Ok(value) => value,
|
||||
// Convert this from a two's complement unsigned to a signed
|
||||
Err(_err) => {
|
||||
|
@ -72,7 +71,7 @@ fn make_load_effective_address(dst: register::Combined, data: &[u8]) -> ParseRes
|
|||
}
|
||||
};
|
||||
|
||||
Ok((
|
||||
(
|
||||
RunnableInstruction {
|
||||
instruction: Instruction::SixteenBitLoad(
|
||||
SixteenBitLoadInstruction::LoadEffectiveAddress {
|
||||
|
@ -82,6 +81,6 @@ fn make_load_effective_address(dst: register::Combined, data: &[u8]) -> ParseRes
|
|||
),
|
||||
cycles: 12,
|
||||
},
|
||||
&data[2..],
|
||||
))
|
||||
2,
|
||||
)
|
||||
}
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
use crate::register;
|
||||
use crate::cpu::instructions::load16::SixteenBitLoadInstruction;
|
||||
use crate::cpu::instructions::{Instruction, RunnableInstruction};
|
||||
use crate::cpu::parse::{self, Error, OpcodeParser, ParseResult};
|
||||
use crate::cpu::parse::{self, Error, OpcodeParser, ParseOutput, ParseResult};
|
||||
use crate::memory::View;
|
||||
use crate::register;
|
||||
|
||||
#[allow(clippy::module_name_repetitions)]
|
||||
pub struct StackLoadParser;
|
||||
|
@ -13,47 +14,59 @@ enum Operation {
|
|||
}
|
||||
|
||||
impl OpcodeParser for StackLoadParser {
|
||||
fn parse_opcode(data: &[u8]) -> ParseResult {
|
||||
let opcode = parse::get_opcode_from_data(data)?;
|
||||
fn parse_opcode(data: &View) -> ParseResult {
|
||||
let opcode = parse::get_opcode_from_data(data);
|
||||
match opcode {
|
||||
0xF5 => make_stack_operation_data(Operation::Push, register::Combined::AF, data),
|
||||
0xC5 => make_stack_operation_data(Operation::Push, register::Combined::BC, data),
|
||||
0xD5 => make_stack_operation_data(Operation::Push, register::Combined::DE, data),
|
||||
0xE5 => make_stack_operation_data(Operation::Push, register::Combined::HL, data),
|
||||
0xF5 => Ok(make_stack_operation_data(
|
||||
Operation::Push,
|
||||
register::Combined::AF,
|
||||
)),
|
||||
0xC5 => Ok(make_stack_operation_data(
|
||||
Operation::Push,
|
||||
register::Combined::BC,
|
||||
)),
|
||||
0xD5 => Ok(make_stack_operation_data(
|
||||
Operation::Push,
|
||||
register::Combined::DE,
|
||||
)),
|
||||
0xE5 => Ok(make_stack_operation_data(
|
||||
Operation::Push,
|
||||
register::Combined::HL,
|
||||
)),
|
||||
|
||||
0xF1 => make_stack_operation_data(Operation::Pop, register::Combined::AF, data),
|
||||
0xC1 => make_stack_operation_data(Operation::Pop, register::Combined::BC, data),
|
||||
0xD1 => make_stack_operation_data(Operation::Pop, register::Combined::DE, data),
|
||||
0xE1 => make_stack_operation_data(Operation::Pop, register::Combined::HL, data),
|
||||
0xF1 => Ok(make_stack_operation_data(
|
||||
Operation::Pop,
|
||||
register::Combined::AF,
|
||||
)),
|
||||
0xC1 => Ok(make_stack_operation_data(
|
||||
Operation::Pop,
|
||||
register::Combined::BC,
|
||||
)),
|
||||
0xD1 => Ok(make_stack_operation_data(
|
||||
Operation::Pop,
|
||||
register::Combined::DE,
|
||||
)),
|
||||
0xE1 => Ok(make_stack_operation_data(
|
||||
Operation::Pop,
|
||||
register::Combined::HL,
|
||||
)),
|
||||
|
||||
_ => Err(Error::UnknownOpcode(opcode)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn make_stack_operation_data(
|
||||
operation: Operation,
|
||||
reg: register::Combined,
|
||||
data: &[u8],
|
||||
) -> ParseResult {
|
||||
data.get(1..)
|
||||
.map(|remaining_data| {
|
||||
let instruction = match operation {
|
||||
Operation::Push => RunnableInstruction {
|
||||
instruction: Instruction::SixteenBitLoad(SixteenBitLoadInstruction::Push {
|
||||
src: reg,
|
||||
}),
|
||||
cycles: 16,
|
||||
},
|
||||
Operation::Pop => RunnableInstruction {
|
||||
instruction: Instruction::SixteenBitLoad(SixteenBitLoadInstruction::Pop {
|
||||
dst: reg,
|
||||
}),
|
||||
cycles: 12,
|
||||
},
|
||||
};
|
||||
fn make_stack_operation_data(operation: Operation, reg: register::Combined) -> ParseOutput {
|
||||
let instruction = match operation {
|
||||
Operation::Push => RunnableInstruction {
|
||||
instruction: Instruction::SixteenBitLoad(SixteenBitLoadInstruction::Push { src: reg }),
|
||||
cycles: 16,
|
||||
},
|
||||
Operation::Pop => RunnableInstruction {
|
||||
instruction: Instruction::SixteenBitLoad(SixteenBitLoadInstruction::Pop { dst: reg }),
|
||||
cycles: 12,
|
||||
},
|
||||
};
|
||||
|
||||
(instruction, remaining_data)
|
||||
})
|
||||
.ok_or(Error::NoData)
|
||||
(instruction, 1)
|
||||
}
|
||||
|
|
|
@ -1,23 +1,24 @@
|
|||
use crate::register;
|
||||
use crate::cpu::instructions::load16::SixteenBitLoadInstruction;
|
||||
use crate::cpu::instructions::Instruction;
|
||||
use crate::cpu::parse::ParseOutput;
|
||||
use crate::cpu::{
|
||||
instructions::RunnableInstruction,
|
||||
parse::{self, Error, OpcodeParser, ParseResult},
|
||||
};
|
||||
use crate::memory::View;
|
||||
use crate::register;
|
||||
|
||||
pub struct Between16BitRegisterParser;
|
||||
|
||||
impl OpcodeParser for Between16BitRegisterParser {
|
||||
/// Parses an opcode that transfers an 8bit values between single 8 bit registers.
|
||||
fn parse_opcode(data: &[u8]) -> ParseResult {
|
||||
let opcode = parse::get_opcode_from_data(data)?;
|
||||
/// Parses an opcode that transfers an 16bit values between single 16 bit registers.
|
||||
fn parse_opcode(data: &View) -> ParseResult {
|
||||
let opcode = parse::get_opcode_from_data(data);
|
||||
match opcode {
|
||||
0xF9 => make_load_between_register_data(
|
||||
0xF9 => Ok(make_load_between_register_data(
|
||||
register::SixteenBit::Single(register::SingleSixteenBit::StackPointer),
|
||||
register::SixteenBit::Combined(register::Combined::HL),
|
||||
data,
|
||||
),
|
||||
)),
|
||||
_ => Err(Error::UnknownOpcode(opcode)),
|
||||
}
|
||||
}
|
||||
|
@ -26,19 +27,14 @@ impl OpcodeParser for Between16BitRegisterParser {
|
|||
fn make_load_between_register_data(
|
||||
dst: register::SixteenBit,
|
||||
src: register::SixteenBit,
|
||||
data: &[u8],
|
||||
) -> ParseResult {
|
||||
data.get(1..)
|
||||
.map(|remaining_data| {
|
||||
(
|
||||
RunnableInstruction {
|
||||
instruction: Instruction::SixteenBitLoad(
|
||||
SixteenBitLoadInstruction::LoadBetweenRegisters { dst, src },
|
||||
),
|
||||
cycles: 4,
|
||||
},
|
||||
remaining_data,
|
||||
)
|
||||
})
|
||||
.ok_or(Error::NoData)
|
||||
) -> ParseOutput {
|
||||
(
|
||||
RunnableInstruction {
|
||||
instruction: Instruction::SixteenBitLoad(
|
||||
SixteenBitLoadInstruction::LoadBetweenRegisters { dst, src },
|
||||
),
|
||||
cycles: 4,
|
||||
},
|
||||
1,
|
||||
)
|
||||
}
|
||||
|
|
|
@ -1,44 +1,42 @@
|
|||
use crate::register;
|
||||
use crate::cpu::instructions::load8::EightBitLoadInstruction;
|
||||
use crate::cpu::instructions::Instruction;
|
||||
use crate::cpu::parse::ParseOutput;
|
||||
use crate::cpu::{
|
||||
instructions::RunnableInstruction,
|
||||
parse::{self, Error, OpcodeParser, ParseResult},
|
||||
};
|
||||
use crate::memory::{GetViewTuple, View};
|
||||
use crate::register;
|
||||
|
||||
#[allow(clippy::module_name_repetitions)]
|
||||
pub struct Immediate8BitLoadParser;
|
||||
|
||||
impl OpcodeParser for Immediate8BitLoadParser {
|
||||
/// Parses an opcode that will transfer an immediate 8 bit value into a single 8 bit register.
|
||||
fn parse_opcode(data: &[u8]) -> ParseResult {
|
||||
let opcode = parse::get_opcode_from_data(data)?;
|
||||
fn parse_opcode(data: &View) -> ParseResult {
|
||||
let opcode = parse::get_opcode_from_data(data);
|
||||
|
||||
match opcode {
|
||||
0x3E => make_load_immediate_data(register::SingleEightBit::A, data),
|
||||
0x06 => make_load_immediate_data(register::SingleEightBit::B, data),
|
||||
0x0E => make_load_immediate_data(register::SingleEightBit::C, data),
|
||||
0x16 => make_load_immediate_data(register::SingleEightBit::D, data),
|
||||
0x1E => make_load_immediate_data(register::SingleEightBit::E, data),
|
||||
0x26 => make_load_immediate_data(register::SingleEightBit::H, data),
|
||||
0x2E => make_load_immediate_data(register::SingleEightBit::L, data),
|
||||
0x3E => Ok(make_load_immediate_data(register::SingleEightBit::A, data)),
|
||||
0x06 => Ok(make_load_immediate_data(register::SingleEightBit::B, data)),
|
||||
0x0E => Ok(make_load_immediate_data(register::SingleEightBit::C, data)),
|
||||
0x16 => Ok(make_load_immediate_data(register::SingleEightBit::D, data)),
|
||||
0x1E => Ok(make_load_immediate_data(register::SingleEightBit::E, data)),
|
||||
0x26 => Ok(make_load_immediate_data(register::SingleEightBit::H, data)),
|
||||
0x2E => Ok(make_load_immediate_data(register::SingleEightBit::L, data)),
|
||||
|
||||
_ => Err(Error::UnknownOpcode(opcode)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn make_load_immediate_data(register: register::SingleEightBit, data: &[u8]) -> ParseResult {
|
||||
let opcode = parse::get_opcode_from_data(data)?;
|
||||
let value = data.get(1).ok_or(Error::NotEnoughArgs(opcode))?;
|
||||
fn make_load_immediate_data(register: register::SingleEightBit, data: &View) -> ParseOutput {
|
||||
let (_opcode, value) = data.get_tuple();
|
||||
|
||||
Ok((
|
||||
(
|
||||
RunnableInstruction {
|
||||
instruction: Instruction::EightBitLoad(
|
||||
EightBitLoadInstruction::LoadImmediateToRegister {
|
||||
register,
|
||||
value: *value,
|
||||
},
|
||||
EightBitLoadInstruction::LoadImmediateToRegister { register, value },
|
||||
),
|
||||
// TODO: I don't love that the cycles are in a parsing function,
|
||||
// but ultimately I want the cycles to be as close to the opcode as possible
|
||||
|
@ -46,7 +44,6 @@ fn make_load_immediate_data(register: register::SingleEightBit, data: &[u8]) ->
|
|||
// more of the instructions complete
|
||||
cycles: 8,
|
||||
},
|
||||
// This can't fail if the previous .get(1) succeeded
|
||||
&data[2..],
|
||||
))
|
||||
2,
|
||||
)
|
||||
}
|
||||
|
|
|
@ -1,14 +1,15 @@
|
|||
use crate::register;
|
||||
use crate::cpu::instructions::load8::EightBitLoadInstruction;
|
||||
use crate::cpu::instructions::{Instruction, RunnableInstruction};
|
||||
use crate::cpu::parse::{self, Error, OpcodeParser, ParseResult};
|
||||
use crate::cpu::parse::{self, Error, OpcodeParser, ParseOutput, ParseResult};
|
||||
use crate::memory::{GetViewTuple, View};
|
||||
use crate::register;
|
||||
|
||||
#[allow(clippy::module_name_repetitions)]
|
||||
pub struct Memory8BitLoadParser;
|
||||
|
||||
impl OpcodeParser for Memory8BitLoadParser {
|
||||
// Parses a single 8 bit instruction to load an 8 bit value to/from memory
|
||||
fn parse_opcode(data: &[u8]) -> ParseResult {
|
||||
fn parse_opcode(data: &View) -> ParseResult {
|
||||
let parse_funcs = &[
|
||||
parse_load_from_register_to_address,
|
||||
parse_load_from_address_to_register,
|
||||
|
@ -24,155 +25,158 @@ impl OpcodeParser for Memory8BitLoadParser {
|
|||
}
|
||||
}
|
||||
|
||||
let opcode = parse::get_opcode_from_data(data)?;
|
||||
|
||||
let opcode = parse::get_opcode_from_data(data);
|
||||
Err(Error::UnknownOpcode(opcode))
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_load_from_register_to_address(data: &[u8]) -> ParseResult {
|
||||
let opcode = parse::get_opcode_from_data(data)?;
|
||||
fn parse_load_from_register_to_address(data: &View) -> ParseResult {
|
||||
let opcode = parse::get_opcode_from_data(data);
|
||||
match opcode {
|
||||
0x02 => {
|
||||
make_load_to_register_address(register::Combined::BC, register::SingleEightBit::A, data)
|
||||
}
|
||||
0x12 => {
|
||||
make_load_to_register_address(register::Combined::DE, register::SingleEightBit::A, data)
|
||||
}
|
||||
0x70 => {
|
||||
make_load_to_register_address(register::Combined::HL, register::SingleEightBit::B, data)
|
||||
}
|
||||
0x71 => {
|
||||
make_load_to_register_address(register::Combined::HL, register::SingleEightBit::C, data)
|
||||
}
|
||||
0x72 => {
|
||||
make_load_to_register_address(register::Combined::HL, register::SingleEightBit::D, data)
|
||||
}
|
||||
0x73 => {
|
||||
make_load_to_register_address(register::Combined::HL, register::SingleEightBit::E, data)
|
||||
}
|
||||
0x74 => {
|
||||
make_load_to_register_address(register::Combined::HL, register::SingleEightBit::H, data)
|
||||
}
|
||||
0x75 => {
|
||||
make_load_to_register_address(register::Combined::HL, register::SingleEightBit::L, data)
|
||||
}
|
||||
0x77 => {
|
||||
make_load_to_register_address(register::Combined::HL, register::SingleEightBit::A, data)
|
||||
}
|
||||
0x02 => Ok(make_load_to_register_address(
|
||||
register::Combined::BC,
|
||||
register::SingleEightBit::A,
|
||||
)),
|
||||
0x12 => Ok(make_load_to_register_address(
|
||||
register::Combined::DE,
|
||||
register::SingleEightBit::A,
|
||||
)),
|
||||
0x70 => Ok(make_load_to_register_address(
|
||||
register::Combined::HL,
|
||||
register::SingleEightBit::B,
|
||||
)),
|
||||
0x71 => Ok(make_load_to_register_address(
|
||||
register::Combined::HL,
|
||||
register::SingleEightBit::C,
|
||||
)),
|
||||
0x72 => Ok(make_load_to_register_address(
|
||||
register::Combined::HL,
|
||||
register::SingleEightBit::D,
|
||||
)),
|
||||
0x73 => Ok(make_load_to_register_address(
|
||||
register::Combined::HL,
|
||||
register::SingleEightBit::E,
|
||||
)),
|
||||
0x74 => Ok(make_load_to_register_address(
|
||||
register::Combined::HL,
|
||||
register::SingleEightBit::H,
|
||||
)),
|
||||
0x75 => Ok(make_load_to_register_address(
|
||||
register::Combined::HL,
|
||||
register::SingleEightBit::L,
|
||||
)),
|
||||
0x77 => Ok(make_load_to_register_address(
|
||||
register::Combined::HL,
|
||||
register::SingleEightBit::A,
|
||||
)),
|
||||
_ => Err(Error::UnknownOpcode(opcode)),
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_load_from_address_to_register(data: &[u8]) -> ParseResult {
|
||||
let opcode = parse::get_opcode_from_data(data)?;
|
||||
fn parse_load_from_address_to_register(data: &View) -> ParseResult {
|
||||
let opcode = parse::get_opcode_from_data(data);
|
||||
match opcode {
|
||||
0x0A => make_load_from_register_address(
|
||||
0x0A => Ok(make_load_from_register_address(
|
||||
register::SingleEightBit::A,
|
||||
register::Combined::BC,
|
||||
data,
|
||||
),
|
||||
0x1A => make_load_from_register_address(
|
||||
)),
|
||||
0x1A => Ok(make_load_from_register_address(
|
||||
register::SingleEightBit::A,
|
||||
register::Combined::DE,
|
||||
data,
|
||||
),
|
||||
0x46 => make_load_from_register_address(
|
||||
)),
|
||||
0x46 => Ok(make_load_from_register_address(
|
||||
register::SingleEightBit::B,
|
||||
register::Combined::HL,
|
||||
data,
|
||||
),
|
||||
0x4E => make_load_from_register_address(
|
||||
)),
|
||||
0x4E => Ok(make_load_from_register_address(
|
||||
register::SingleEightBit::C,
|
||||
register::Combined::HL,
|
||||
data,
|
||||
),
|
||||
0x56 => make_load_from_register_address(
|
||||
)),
|
||||
0x56 => Ok(make_load_from_register_address(
|
||||
register::SingleEightBit::D,
|
||||
register::Combined::HL,
|
||||
data,
|
||||
),
|
||||
0x5E => make_load_from_register_address(
|
||||
)),
|
||||
0x5E => Ok(make_load_from_register_address(
|
||||
register::SingleEightBit::E,
|
||||
register::Combined::HL,
|
||||
data,
|
||||
),
|
||||
0x66 => make_load_from_register_address(
|
||||
)),
|
||||
0x66 => Ok(make_load_from_register_address(
|
||||
register::SingleEightBit::H,
|
||||
register::Combined::HL,
|
||||
data,
|
||||
),
|
||||
0x6E => make_load_from_register_address(
|
||||
)),
|
||||
0x6E => Ok(make_load_from_register_address(
|
||||
register::SingleEightBit::L,
|
||||
register::Combined::HL,
|
||||
data,
|
||||
),
|
||||
0x7E => make_load_from_register_address(
|
||||
)),
|
||||
0x7E => Ok(make_load_from_register_address(
|
||||
register::SingleEightBit::A,
|
||||
register::Combined::HL,
|
||||
data,
|
||||
),
|
||||
)),
|
||||
|
||||
0xF2 => make_load_from_memory_relative_to_io_register_start(
|
||||
0xF2 => Ok(make_load_from_memory_relative_to_io_register_start(
|
||||
register::SingleEightBit::C,
|
||||
register::SingleEightBit::A,
|
||||
data,
|
||||
),
|
||||
0xE2 => make_load_to_memory_relative_to_io_register_start(
|
||||
)),
|
||||
0xE2 => Ok(make_load_to_memory_relative_to_io_register_start(
|
||||
register::SingleEightBit::C,
|
||||
register::SingleEightBit::A,
|
||||
data,
|
||||
)),
|
||||
0xF0 => Ok(
|
||||
make_load_from_memory_relative_to_io_register_start_by_immediate(
|
||||
register::SingleEightBit::A,
|
||||
data,
|
||||
),
|
||||
),
|
||||
0xF0 => make_load_from_memory_relative_to_io_register_start_by_immediate(
|
||||
register::SingleEightBit::A,
|
||||
data,
|
||||
),
|
||||
0xE0 => make_load_to_memory_relative_to_io_register_start_by_immediate(
|
||||
register::SingleEightBit::A,
|
||||
data,
|
||||
0xE0 => Ok(
|
||||
make_load_to_memory_relative_to_io_register_start_by_immediate(
|
||||
register::SingleEightBit::A,
|
||||
data,
|
||||
),
|
||||
),
|
||||
|
||||
_ => Err(Error::UnknownOpcode(opcode)),
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_load_immediate_instructions(data: &[u8]) -> ParseResult {
|
||||
let opcode = parse::get_opcode_from_data(data)?;
|
||||
fn parse_load_immediate_instructions(data: &View) -> ParseResult {
|
||||
let opcode = parse::get_opcode_from_data(data);
|
||||
match opcode {
|
||||
0x36 => make_load_n_to_hl_address(data),
|
||||
0xFA => make_load_from_immediate_address(register::SingleEightBit::A, data),
|
||||
0xEA => make_load_to_immediate_address(register::SingleEightBit::A, data),
|
||||
0x36 => Ok(make_load_n_to_hl_address(data)),
|
||||
0xFA => Ok(make_load_from_immediate_address(
|
||||
register::SingleEightBit::A,
|
||||
data,
|
||||
)),
|
||||
0xEA => Ok(make_load_to_immediate_address(
|
||||
register::SingleEightBit::A,
|
||||
data,
|
||||
)),
|
||||
_ => Err(Error::UnknownOpcode(opcode)),
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_load_from_register_to_address_and_do_arithmetic(data: &[u8]) -> ParseResult {
|
||||
let opcode = parse::get_opcode_from_data(data)?;
|
||||
fn parse_load_from_register_to_address_and_do_arithmetic(data: &View) -> ParseResult {
|
||||
let opcode = parse::get_opcode_from_data(data);
|
||||
match opcode {
|
||||
0x32 => make_load_to_address_and_do_arithmetic(
|
||||
0x32 => Ok(make_load_to_address_and_do_arithmetic(
|
||||
register::Combined::HL,
|
||||
register::SingleEightBit::A,
|
||||
|dst, src| EightBitLoadInstruction::LoadToRegisterAddressThenDec { dst, src },
|
||||
data,
|
||||
),
|
||||
0x22 => make_load_to_address_and_do_arithmetic(
|
||||
)),
|
||||
0x22 => Ok(make_load_to_address_and_do_arithmetic(
|
||||
register::Combined::HL,
|
||||
register::SingleEightBit::A,
|
||||
|dst, src| EightBitLoadInstruction::LoadToRegisterAddressThenInc { dst, src },
|
||||
data,
|
||||
),
|
||||
0x3A => make_load_from_address_and_do_arithmetic(
|
||||
)),
|
||||
0x3A => Ok(make_load_from_address_and_do_arithmetic(
|
||||
register::SingleEightBit::A,
|
||||
register::Combined::HL,
|
||||
|dst, src| EightBitLoadInstruction::LoadFromRegisterAddressThenDec { dst, src },
|
||||
data,
|
||||
),
|
||||
0x2A => make_load_from_address_and_do_arithmetic(
|
||||
)),
|
||||
0x2A => Ok(make_load_from_address_and_do_arithmetic(
|
||||
register::SingleEightBit::A,
|
||||
register::Combined::HL,
|
||||
|dst, src| EightBitLoadInstruction::LoadFromRegisterAddressThenInc { dst, src },
|
||||
data,
|
||||
),
|
||||
)),
|
||||
_ => Err(Error::UnknownOpcode(opcode)),
|
||||
}
|
||||
}
|
||||
|
@ -180,184 +184,155 @@ fn parse_load_from_register_to_address_and_do_arithmetic(data: &[u8]) -> ParseRe
|
|||
fn make_load_from_register_address(
|
||||
dst: register::SingleEightBit,
|
||||
src: register::Combined,
|
||||
data: &[u8],
|
||||
) -> ParseResult {
|
||||
data.get(1..)
|
||||
.map(|remaining_data| {
|
||||
(
|
||||
RunnableInstruction {
|
||||
instruction: Instruction::EightBitLoad(
|
||||
EightBitLoadInstruction::LoadFromRegisterAddress { src, dst },
|
||||
),
|
||||
cycles: 8,
|
||||
},
|
||||
remaining_data,
|
||||
)
|
||||
})
|
||||
.ok_or(Error::NoData)
|
||||
) -> ParseOutput {
|
||||
(
|
||||
RunnableInstruction {
|
||||
instruction: Instruction::EightBitLoad(
|
||||
EightBitLoadInstruction::LoadFromRegisterAddress { src, dst },
|
||||
),
|
||||
cycles: 8,
|
||||
},
|
||||
1,
|
||||
)
|
||||
}
|
||||
|
||||
fn make_load_to_register_address(
|
||||
dst: register::Combined,
|
||||
src: register::SingleEightBit,
|
||||
data: &[u8],
|
||||
) -> ParseResult {
|
||||
data.get(1..)
|
||||
.map(|remaining_data| {
|
||||
(
|
||||
RunnableInstruction {
|
||||
instruction: Instruction::EightBitLoad(
|
||||
EightBitLoadInstruction::LoadToRegisterAddress { src, dst },
|
||||
),
|
||||
cycles: 8,
|
||||
},
|
||||
remaining_data,
|
||||
)
|
||||
})
|
||||
.ok_or(Error::NoData)
|
||||
) -> ParseOutput {
|
||||
(
|
||||
RunnableInstruction {
|
||||
instruction: Instruction::EightBitLoad(
|
||||
EightBitLoadInstruction::LoadToRegisterAddress { src, dst },
|
||||
),
|
||||
cycles: 8,
|
||||
},
|
||||
1,
|
||||
)
|
||||
}
|
||||
|
||||
fn make_load_from_immediate_address(dst: register::SingleEightBit, data: &[u8]) -> ParseResult {
|
||||
let opcode = parse::get_opcode_from_data(data)?;
|
||||
let args = data.get(1..=2).ok_or(Error::NotEnoughArgs(opcode))?;
|
||||
fn make_load_from_immediate_address(dst: register::SingleEightBit, data: &View) -> ParseOutput {
|
||||
let (_opcode, lower_bytes, upper_bytes) = data.get_tuple();
|
||||
|
||||
// The manual states that the LSB of the address is specified first (i.e. little endian)
|
||||
let src_address = u16::from_le_bytes([args[0], args[1]]);
|
||||
Ok((
|
||||
let src_address = u16::from_le_bytes([lower_bytes, upper_bytes]);
|
||||
(
|
||||
RunnableInstruction {
|
||||
instruction: Instruction::EightBitLoad(
|
||||
EightBitLoadInstruction::LoadFromImmediateAddress { src_address, dst },
|
||||
),
|
||||
cycles: 16,
|
||||
},
|
||||
// This can't fail if get(1..=2) passed
|
||||
&data[3..],
|
||||
))
|
||||
3,
|
||||
)
|
||||
}
|
||||
|
||||
fn make_load_to_immediate_address(src: register::SingleEightBit, data: &[u8]) -> ParseResult {
|
||||
let opcode = parse::get_opcode_from_data(data)?;
|
||||
let args = data.get(1..=2).ok_or(Error::NotEnoughArgs(opcode))?;
|
||||
fn make_load_to_immediate_address(src: register::SingleEightBit, data: &View) -> ParseOutput {
|
||||
let (_opcode, lower_bytes, upper_bytes) = data.get_tuple();
|
||||
|
||||
// The manual states that the LSB of the address is specified first (i.e. little endian)
|
||||
let dst_address = u16::from_le_bytes([args[0], args[1]]);
|
||||
Ok((
|
||||
let dst_address = u16::from_le_bytes([lower_bytes, upper_bytes]);
|
||||
(
|
||||
RunnableInstruction {
|
||||
instruction: Instruction::EightBitLoad(
|
||||
EightBitLoadInstruction::LoadToImmediateAddress { src, dst_address },
|
||||
),
|
||||
cycles: 16,
|
||||
},
|
||||
// This can't fail if get(1..=2) passed
|
||||
&data[3..],
|
||||
))
|
||||
3,
|
||||
)
|
||||
}
|
||||
|
||||
fn make_load_n_to_hl_address(data: &[u8]) -> ParseResult {
|
||||
let opcode = parse::get_opcode_from_data(data)?;
|
||||
let value = data.get(1).ok_or(Error::NotEnoughArgs(opcode))?;
|
||||
Ok((
|
||||
fn make_load_n_to_hl_address(data: &View) -> ParseOutput {
|
||||
let (_opcode, value) = data.get_tuple();
|
||||
(
|
||||
RunnableInstruction {
|
||||
instruction: Instruction::EightBitLoad(EightBitLoadInstruction::LoadnToHLAddress {
|
||||
value: *value,
|
||||
value,
|
||||
}),
|
||||
cycles: 12,
|
||||
},
|
||||
// This can't fail if get(1) succeeeded
|
||||
&data[2..],
|
||||
))
|
||||
2,
|
||||
)
|
||||
}
|
||||
|
||||
fn make_load_from_memory_relative_to_io_register_start(
|
||||
offset_register: register::SingleEightBit,
|
||||
dst: register::SingleEightBit,
|
||||
data: &[u8],
|
||||
) -> ParseResult {
|
||||
data.get(1..)
|
||||
.map(|remaining_data| {
|
||||
(
|
||||
RunnableInstruction {
|
||||
instruction: Instruction::EightBitLoad(
|
||||
EightBitLoadInstruction::LoadFromMemoryRelativeToIORegisterStart {
|
||||
offset_register,
|
||||
dst,
|
||||
},
|
||||
),
|
||||
cycles: 8,
|
||||
) -> ParseOutput {
|
||||
(
|
||||
RunnableInstruction {
|
||||
instruction: Instruction::EightBitLoad(
|
||||
EightBitLoadInstruction::LoadFromMemoryRelativeToIORegisterStart {
|
||||
offset_register,
|
||||
dst,
|
||||
},
|
||||
// guaranteed to succeed given we found the opcode
|
||||
remaining_data,
|
||||
)
|
||||
})
|
||||
.ok_or(Error::NoData)
|
||||
),
|
||||
cycles: 8,
|
||||
},
|
||||
// guaranteed to succeed given we found the opcode
|
||||
1,
|
||||
)
|
||||
}
|
||||
|
||||
fn make_load_to_memory_relative_to_io_register_start(
|
||||
offset_register: register::SingleEightBit,
|
||||
src: register::SingleEightBit,
|
||||
data: &[u8],
|
||||
) -> ParseResult {
|
||||
data.get(1..)
|
||||
.map(|remaining_data| {
|
||||
(
|
||||
RunnableInstruction {
|
||||
instruction: Instruction::EightBitLoad(
|
||||
EightBitLoadInstruction::LoadToMemoryRelativeToIORegisterStart {
|
||||
src,
|
||||
offset_register,
|
||||
},
|
||||
),
|
||||
cycles: 8,
|
||||
) -> ParseOutput {
|
||||
(
|
||||
RunnableInstruction {
|
||||
instruction: Instruction::EightBitLoad(
|
||||
EightBitLoadInstruction::LoadToMemoryRelativeToIORegisterStart {
|
||||
src,
|
||||
offset_register,
|
||||
},
|
||||
// guaranteed to succeed given we found the opcode
|
||||
remaining_data,
|
||||
)
|
||||
})
|
||||
.ok_or(Error::NoData)
|
||||
),
|
||||
cycles: 8,
|
||||
},
|
||||
// guaranteed to succeed given we found the opcode
|
||||
1,
|
||||
)
|
||||
}
|
||||
|
||||
fn make_load_to_memory_relative_to_io_register_start_by_immediate(
|
||||
src: register::SingleEightBit,
|
||||
data: &[u8],
|
||||
) -> ParseResult {
|
||||
let opcode = parse::get_opcode_from_data(data)?;
|
||||
let value = data.get(1).ok_or(Error::NotEnoughArgs(opcode))?;
|
||||
data: &View,
|
||||
) -> ParseOutput {
|
||||
let (_opcode, offset) = data.get_tuple();
|
||||
|
||||
Ok((
|
||||
(
|
||||
RunnableInstruction {
|
||||
instruction: Instruction::EightBitLoad(
|
||||
EightBitLoadInstruction::LoadToMemoryRelativeToIORegisterStartByImmediate {
|
||||
src,
|
||||
offset: *value,
|
||||
offset,
|
||||
},
|
||||
),
|
||||
// guaranteed to succeed given we got the value
|
||||
cycles: 12,
|
||||
},
|
||||
&data[2..],
|
||||
))
|
||||
2,
|
||||
)
|
||||
}
|
||||
|
||||
fn make_load_from_memory_relative_to_io_register_start_by_immediate(
|
||||
dst: register::SingleEightBit,
|
||||
data: &[u8],
|
||||
) -> ParseResult {
|
||||
let opcode = parse::get_opcode_from_data(data)?;
|
||||
let value = data.get(1).ok_or(Error::NotEnoughArgs(opcode))?;
|
||||
data: &View,
|
||||
) -> ParseOutput {
|
||||
let (_opcode, offset) = data.get_tuple();
|
||||
|
||||
Ok((
|
||||
(
|
||||
RunnableInstruction {
|
||||
instruction: Instruction::EightBitLoad(
|
||||
EightBitLoadInstruction::LoadFromMemoryRelativeToIORegisterStartByImmediate {
|
||||
offset: *value,
|
||||
offset,
|
||||
dst,
|
||||
},
|
||||
),
|
||||
cycles: 12,
|
||||
},
|
||||
// guaranteed to succeed given we got the value
|
||||
&data[2..],
|
||||
))
|
||||
2,
|
||||
)
|
||||
}
|
||||
|
||||
/// `make_load_to_address_and_do_arithmetic` will make one of the instructions that load to memory and either increment
|
||||
|
@ -368,9 +343,8 @@ fn make_load_to_address_and_do_arithmetic<
|
|||
dst: register::Combined,
|
||||
src: register::SingleEightBit,
|
||||
make: F,
|
||||
data: &[u8],
|
||||
) -> ParseResult {
|
||||
make_load_and_do_arithmetic(|| make(dst, src), data)
|
||||
) -> ParseOutput {
|
||||
make_load_and_do_arithmetic(|| make(dst, src))
|
||||
}
|
||||
|
||||
/// `make_load_to_address_and_do_arithmetic` will make one of the instructions that load from memory and either increment
|
||||
|
@ -381,28 +355,20 @@ fn make_load_from_address_and_do_arithmetic<
|
|||
dst: register::SingleEightBit,
|
||||
src: register::Combined,
|
||||
make: F,
|
||||
data: &[u8],
|
||||
) -> ParseResult {
|
||||
make_load_and_do_arithmetic(|| make(dst, src), data)
|
||||
) -> ParseOutput {
|
||||
make_load_and_do_arithmetic(|| make(dst, src))
|
||||
}
|
||||
|
||||
/// `make_load_and_do_arithmetic` will parse a `RunnableInstruction` from one of the load+arithmetic isntructions,
|
||||
/// which will be produced by the given `make` function
|
||||
fn make_load_and_do_arithmetic<F: Fn() -> EightBitLoadInstruction>(
|
||||
make: F,
|
||||
data: &[u8],
|
||||
) -> ParseResult {
|
||||
data.get(1..)
|
||||
.map(|remaining_data| {
|
||||
let load_instruction = make();
|
||||
fn make_load_and_do_arithmetic<F: Fn() -> EightBitLoadInstruction>(make: F) -> ParseOutput {
|
||||
let load_instruction = make();
|
||||
|
||||
(
|
||||
RunnableInstruction {
|
||||
instruction: Instruction::EightBitLoad(load_instruction),
|
||||
cycles: 8,
|
||||
},
|
||||
remaining_data,
|
||||
)
|
||||
})
|
||||
.ok_or(Error::NoData)
|
||||
(
|
||||
RunnableInstruction {
|
||||
instruction: Instruction::EightBitLoad(load_instruction),
|
||||
cycles: 8,
|
||||
},
|
||||
1,
|
||||
)
|
||||
}
|
||||
|
|
|
@ -1,78 +1,80 @@
|
|||
use crate::register;
|
||||
use crate::cpu::instructions::load8::EightBitLoadInstruction;
|
||||
use crate::cpu::instructions::Instruction;
|
||||
use crate::cpu::parse::ParseOutput;
|
||||
use crate::cpu::{
|
||||
instructions::RunnableInstruction,
|
||||
parse::{self, Error, OpcodeParser, ParseResult},
|
||||
};
|
||||
use crate::memory::View;
|
||||
use crate::register;
|
||||
|
||||
pub struct Between8BitRegisterParser;
|
||||
|
||||
impl OpcodeParser for Between8BitRegisterParser {
|
||||
/// Parses an opcode that transfers an 8bit values between single 8 bit registers.
|
||||
fn parse_opcode(data: &[u8]) -> ParseResult {
|
||||
fn parse_opcode(data: &View) -> ParseResult {
|
||||
// I don't normally like doing this but this function was ridiculous with the line length wrapping
|
||||
// caused by not doing it
|
||||
#[allow(clippy::enum_glob_use)]
|
||||
use register::SingleEightBit::*;
|
||||
|
||||
let opcode = parse::get_opcode_from_data(data)?;
|
||||
let opcode = parse::get_opcode_from_data(data);
|
||||
match opcode {
|
||||
0x7f => make_load_between_register_data(A, A, data),
|
||||
0x78 => make_load_between_register_data(A, B, data),
|
||||
0x79 => make_load_between_register_data(A, C, data),
|
||||
0x7A => make_load_between_register_data(A, D, data),
|
||||
0x7B => make_load_between_register_data(A, E, data),
|
||||
0x7C => make_load_between_register_data(A, H, data),
|
||||
0x7D => make_load_between_register_data(A, L, data),
|
||||
0x7f => Ok(make_load_between_register_data(A, A)),
|
||||
0x78 => Ok(make_load_between_register_data(A, B)),
|
||||
0x79 => Ok(make_load_between_register_data(A, C)),
|
||||
0x7A => Ok(make_load_between_register_data(A, D)),
|
||||
0x7B => Ok(make_load_between_register_data(A, E)),
|
||||
0x7C => Ok(make_load_between_register_data(A, H)),
|
||||
0x7D => Ok(make_load_between_register_data(A, L)),
|
||||
|
||||
0x47 => make_load_between_register_data(B, A, data),
|
||||
0x40 => make_load_between_register_data(B, B, data),
|
||||
0x41 => make_load_between_register_data(B, C, data),
|
||||
0x42 => make_load_between_register_data(B, D, data),
|
||||
0x43 => make_load_between_register_data(B, E, data),
|
||||
0x44 => make_load_between_register_data(B, H, data),
|
||||
0x45 => make_load_between_register_data(B, L, data),
|
||||
0x47 => Ok(make_load_between_register_data(B, A)),
|
||||
0x40 => Ok(make_load_between_register_data(B, B)),
|
||||
0x41 => Ok(make_load_between_register_data(B, C)),
|
||||
0x42 => Ok(make_load_between_register_data(B, D)),
|
||||
0x43 => Ok(make_load_between_register_data(B, E)),
|
||||
0x44 => Ok(make_load_between_register_data(B, H)),
|
||||
0x45 => Ok(make_load_between_register_data(B, L)),
|
||||
|
||||
0x4F => make_load_between_register_data(C, A, data),
|
||||
0x48 => make_load_between_register_data(C, B, data),
|
||||
0x49 => make_load_between_register_data(C, C, data),
|
||||
0x4A => make_load_between_register_data(C, D, data),
|
||||
0x4B => make_load_between_register_data(C, E, data),
|
||||
0x4C => make_load_between_register_data(C, H, data),
|
||||
0x4D => make_load_between_register_data(C, L, data),
|
||||
0x4F => Ok(make_load_between_register_data(C, A)),
|
||||
0x48 => Ok(make_load_between_register_data(C, B)),
|
||||
0x49 => Ok(make_load_between_register_data(C, C)),
|
||||
0x4A => Ok(make_load_between_register_data(C, D)),
|
||||
0x4B => Ok(make_load_between_register_data(C, E)),
|
||||
0x4C => Ok(make_load_between_register_data(C, H)),
|
||||
0x4D => Ok(make_load_between_register_data(C, L)),
|
||||
|
||||
0x57 => make_load_between_register_data(D, A, data),
|
||||
0x50 => make_load_between_register_data(D, B, data),
|
||||
0x51 => make_load_between_register_data(D, C, data),
|
||||
0x52 => make_load_between_register_data(D, D, data),
|
||||
0x53 => make_load_between_register_data(D, E, data),
|
||||
0x54 => make_load_between_register_data(D, H, data),
|
||||
0x55 => make_load_between_register_data(D, L, data),
|
||||
0x57 => Ok(make_load_between_register_data(D, A)),
|
||||
0x50 => Ok(make_load_between_register_data(D, B)),
|
||||
0x51 => Ok(make_load_between_register_data(D, C)),
|
||||
0x52 => Ok(make_load_between_register_data(D, D)),
|
||||
0x53 => Ok(make_load_between_register_data(D, E)),
|
||||
0x54 => Ok(make_load_between_register_data(D, H)),
|
||||
0x55 => Ok(make_load_between_register_data(D, L)),
|
||||
|
||||
0x5F => make_load_between_register_data(E, A, data),
|
||||
0x58 => make_load_between_register_data(E, B, data),
|
||||
0x59 => make_load_between_register_data(E, C, data),
|
||||
0x5A => make_load_between_register_data(E, D, data),
|
||||
0x5B => make_load_between_register_data(E, E, data),
|
||||
0x5C => make_load_between_register_data(E, H, data),
|
||||
0x5D => make_load_between_register_data(E, L, data),
|
||||
0x5F => Ok(make_load_between_register_data(E, A)),
|
||||
0x58 => Ok(make_load_between_register_data(E, B)),
|
||||
0x59 => Ok(make_load_between_register_data(E, C)),
|
||||
0x5A => Ok(make_load_between_register_data(E, D)),
|
||||
0x5B => Ok(make_load_between_register_data(E, E)),
|
||||
0x5C => Ok(make_load_between_register_data(E, H)),
|
||||
0x5D => Ok(make_load_between_register_data(E, L)),
|
||||
|
||||
0x67 => make_load_between_register_data(H, A, data),
|
||||
0x60 => make_load_between_register_data(H, B, data),
|
||||
0x61 => make_load_between_register_data(H, C, data),
|
||||
0x62 => make_load_between_register_data(H, D, data),
|
||||
0x63 => make_load_between_register_data(H, E, data),
|
||||
0x64 => make_load_between_register_data(H, H, data),
|
||||
0x65 => make_load_between_register_data(H, L, data),
|
||||
0x67 => Ok(make_load_between_register_data(H, A)),
|
||||
0x60 => Ok(make_load_between_register_data(H, B)),
|
||||
0x61 => Ok(make_load_between_register_data(H, C)),
|
||||
0x62 => Ok(make_load_between_register_data(H, D)),
|
||||
0x63 => Ok(make_load_between_register_data(H, E)),
|
||||
0x64 => Ok(make_load_between_register_data(H, H)),
|
||||
0x65 => Ok(make_load_between_register_data(H, L)),
|
||||
|
||||
0x6F => make_load_between_register_data(L, A, data),
|
||||
0x68 => make_load_between_register_data(L, B, data),
|
||||
0x69 => make_load_between_register_data(L, C, data),
|
||||
0x6A => make_load_between_register_data(L, D, data),
|
||||
0x6B => make_load_between_register_data(L, E, data),
|
||||
0x6c => make_load_between_register_data(L, H, data),
|
||||
0x6D => make_load_between_register_data(L, L, data),
|
||||
0x6F => Ok(make_load_between_register_data(L, A)),
|
||||
0x68 => Ok(make_load_between_register_data(L, B)),
|
||||
0x69 => Ok(make_load_between_register_data(L, C)),
|
||||
0x6A => Ok(make_load_between_register_data(L, D)),
|
||||
0x6B => Ok(make_load_between_register_data(L, E)),
|
||||
0x6c => Ok(make_load_between_register_data(L, H)),
|
||||
0x6D => Ok(make_load_between_register_data(L, L)),
|
||||
|
||||
_ => Err(Error::UnknownOpcode(opcode)),
|
||||
}
|
||||
|
@ -82,19 +84,15 @@ impl OpcodeParser for Between8BitRegisterParser {
|
|||
fn make_load_between_register_data(
|
||||
dst: register::SingleEightBit,
|
||||
src: register::SingleEightBit,
|
||||
data: &[u8],
|
||||
) -> ParseResult {
|
||||
data.get(1..)
|
||||
.map(|remaining_data| {
|
||||
(
|
||||
RunnableInstruction {
|
||||
instruction: Instruction::EightBitLoad(
|
||||
EightBitLoadInstruction::LoadBetweenRegisters { dst, src },
|
||||
),
|
||||
cycles: 4,
|
||||
},
|
||||
remaining_data,
|
||||
)
|
||||
})
|
||||
.ok_or(Error::NoData)
|
||||
) -> ParseOutput {
|
||||
(
|
||||
RunnableInstruction {
|
||||
instruction: Instruction::EightBitLoad(EightBitLoadInstruction::LoadBetweenRegisters {
|
||||
dst,
|
||||
src,
|
||||
}),
|
||||
cycles: 4,
|
||||
},
|
||||
1,
|
||||
)
|
||||
}
|
||||
|
|
|
@ -1,9 +1,12 @@
|
|||
pub use self::view::{GetTuple as GetViewTuple, View};
|
||||
use thiserror::Error;
|
||||
|
||||
/// The location at which the IO registers start in memory. (see 2.13.1 of the manual)
|
||||
pub const IO_REGISTER_START_ADDRESS: usize = 0xFF00;
|
||||
const MAX_MEMORY_ADDRESS: usize = 0xFFFF;
|
||||
|
||||
mod view;
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum Error {
|
||||
#[error("attempted to fetch from invalid address {0:X}")]
|
||||
|
@ -34,12 +37,12 @@ impl Memory {
|
|||
.ok_or(Error::GetInvalidAddress(index))
|
||||
}
|
||||
|
||||
pub fn get_from(&self, index: usize) -> Result<&[u8], Error> {
|
||||
pub fn get_from(&self, index: usize) -> Result<View, Error> {
|
||||
if index > self.data.len() {
|
||||
return Err(Error::GetInvalidAddress(index));
|
||||
}
|
||||
|
||||
Ok(&self.data[index..])
|
||||
Ok(View::new(self, index))
|
||||
}
|
||||
|
||||
/// `set` will get a single byte value at the given address, and return the new value
|
||||
|
|
|
@ -0,0 +1,303 @@
|
|||
use super::Memory;
|
||||
|
||||
/// Holds a view of memory, starting at some offset
|
||||
pub struct View<'a> {
|
||||
underlying_memory: &'a [u8],
|
||||
start: usize,
|
||||
}
|
||||
|
||||
impl<'a> View<'a> {
|
||||
pub(crate) fn new(memory: &'a Memory, start: usize) -> Self {
|
||||
Self {
|
||||
underlying_memory: &memory.data,
|
||||
start,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn new_from_data(memory: &'a [u8], start: usize) -> Self {
|
||||
Self {
|
||||
underlying_memory: memory,
|
||||
start,
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the first value in this view
|
||||
pub fn get(&self) -> u8 {
|
||||
self.get_index(0)
|
||||
}
|
||||
|
||||
/// Get the nth value in this view. This will wrap around if index is too large
|
||||
pub fn get_index(&self, index: usize) -> u8 {
|
||||
let data_length = self.underlying_memory.len();
|
||||
self.underlying_memory[(self.start + index) % data_length]
|
||||
}
|
||||
|
||||
/// Construct a new view that has been advanced by `by` values. This will wrap around if index is too large
|
||||
pub fn advance(self, by: usize) -> Self {
|
||||
let data_length = self.underlying_memory.len();
|
||||
let next_idx = (self.start + by) % data_length;
|
||||
View {
|
||||
underlying_memory: self.underlying_memory,
|
||||
start: next_idx,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub trait GetTuple<T> {
|
||||
/// Get the type this is implemented on as a tuple, as dictated by type T
|
||||
fn get_tuple(&self) -> T;
|
||||
}
|
||||
|
||||
impl<T: ViewIntoTuple> GetTuple<T> for View<'_> {
|
||||
/// Get the first n elements in this view (where n is the length of the tuple in type T).
|
||||
/// This is implemented for all tuples up to length 6.
|
||||
///
|
||||
/// # Examples
|
||||
/// ```ignore
|
||||
/// # // This can't run because memory isn't public
|
||||
///
|
||||
/// let mut memory = Memory::new();
|
||||
/// memory.set(0xF0, 10).expect("failed to set memory value");
|
||||
/// memory.set(0xF1, 11).expect("failed to set memory value");
|
||||
/// memory.set(0xF2, 12).expect("failed to set memory value");
|
||||
/// memory.set(0xF3, 13).expect("failed to set memory value");
|
||||
|
||||
/// let view = View::new(&memory, 0xF0);
|
||||
/// assert_eq!(10, view.get_index(0));
|
||||
/// assert_eq!(11, view.get_index(1));
|
||||
/// assert_eq!(12, view.get_index(2));
|
||||
/// assert_eq!(13, view.get_index(3));
|
||||
/// ```
|
||||
fn get_tuple(&self) -> T {
|
||||
T::into_tuple(self)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The following macro implementation is "heavily inspired" (read: stolen with many tweaks) by itertools' `tuples` method.
|
||||
*
|
||||
* The implementation is <https://github.com/rust-itertools/itertools/blob/ad2e401862d80d34e380d343758f71a533dcf36b/src/tuple_impl.rs#L266-L331>
|
||||
*
|
||||
* itertools is licensed under the MIT license, which is expanded upon here
|
||||
*
|
||||
* Copyright (c) 2015
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any
|
||||
* person obtaining a copy of this software and associated
|
||||
* documentation files (the "Software"), to deal in the
|
||||
* Software without restriction, including without
|
||||
* limitation the rights to use, copy, modify, merge,
|
||||
* publish, distribute, sublicense, and/or sell copies of
|
||||
* the Software, and to permit persons to whom the Software
|
||||
* is furnished to do so, subject to the following
|
||||
* conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice
|
||||
* shall be included in all copies or substantial portions
|
||||
* of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
|
||||
* ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
|
||||
* TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
|
||||
* PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
|
||||
* SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
|
||||
* IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
* DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
macro_rules! get_for_each_ident{
|
||||
($arr:ident, $n: expr,) => {};
|
||||
($arr:ident, $($i:ident,)*) => {
|
||||
get_for_each_ident!($arr, 0, $($i,)*);
|
||||
};
|
||||
($view:ident, $n: expr, $i0:ident, $($i:ident,)*) => {
|
||||
$i0 = $view.get_index($n);
|
||||
get_for_each_ident!($view, $n + 1, $($i,)*);
|
||||
};
|
||||
}
|
||||
|
||||
/// Convert an identity and a tokentree to just that tree. Useful to abuse wildcard semantics.
|
||||
macro_rules! ignore_ident{
|
||||
($id:ident, $($t:tt)*) => {$($t)*};
|
||||
}
|
||||
|
||||
// this trait could _PROBABLY_ be converted into From<&View<'_>> with some trickery but the lifetimes
|
||||
// are making my head hurt and I don't think it's worth it for an implementation detail like this
|
||||
trait ViewIntoTuple {
|
||||
fn into_tuple(view: &View<'_>) -> Self;
|
||||
}
|
||||
|
||||
/*
|
||||
A implementation implemtnation of what the below macro will implement
|
||||
|
||||
impl <T: Copy + Default> TupleGetFrom<T> for (T, T, T) {
|
||||
fn get_from(arr: &[T]) -> Self {
|
||||
let (mut a, mut b, mut c) = Self::default();
|
||||
a = arr[0];
|
||||
b = arr[1];
|
||||
c = arr[2];
|
||||
|
||||
(a, b, c)
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
macro_rules! impl_tuple_get_from {
|
||||
($_: ident, $__: ident,) => {};
|
||||
// We recursively implement this for all tuples > 2
|
||||
($_: ident, $($idents: ident,)+) => {
|
||||
impl_tuple_get_from!($($idents,)*);
|
||||
|
||||
// implement the trait - we abuse the wildcard here with ignore_ident to implement on the appropriate length
|
||||
// u8 tuple - the idea here is this becomes (ignore_ident!(a, u8), ignore_ident!(b, u8), ...), which expands
|
||||
// to just (u8, u8...)
|
||||
impl ViewIntoTuple for ($(ignore_ident!($idents, u8),)*) {
|
||||
fn into_tuple(view: &View<'_>) -> Self {
|
||||
// Create a tuple of the correct length; View uses u8s, which has a default
|
||||
// (though this could be implemented generically with little effort)
|
||||
#[allow(unused_assignments)]
|
||||
let ($(mut $idents, )*) = Self::default();
|
||||
// Perform a View::get for each identity
|
||||
get_for_each_ident!(view, $($idents, )*);
|
||||
($($idents, )*)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// and kick off the implementation here
|
||||
impl_tuple_get_from!(ignore, a, b, c, d, e, f,);
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::memory::MAX_MEMORY_ADDRESS;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_can_get_single_value() {
|
||||
let mut memory = Memory::new();
|
||||
memory.set(0xF0, 10).expect("failed to set memory value");
|
||||
|
||||
let view = View::new(&memory, 0xF0);
|
||||
assert_eq!(10, view.get());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_can_get_single_value_by_index() {
|
||||
let mut memory = Memory::new();
|
||||
memory.set(0xF0, 10).expect("failed to set memory value");
|
||||
memory.set(0xF1, 11).expect("failed to set memory value");
|
||||
memory.set(0xF2, 12).expect("failed to set memory value");
|
||||
memory.set(0xF3, 13).expect("failed to set memory value");
|
||||
|
||||
let view = View::new(&memory, 0xF0);
|
||||
assert_eq!(10, view.get_index(0));
|
||||
assert_eq!(11, view.get_index(1));
|
||||
assert_eq!(12, view.get_index(2));
|
||||
assert_eq!(13, view.get_index(3));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn get_can_advance_view() {
|
||||
let mut memory = Memory::new();
|
||||
memory.set(0xF0, 10).expect("failed to set memory value");
|
||||
memory.set(0xF1, 11).expect("failed to set memory value");
|
||||
memory.set(0xF2, 12).expect("failed to set memory value");
|
||||
memory.set(0xF3, 13).expect("failed to set memory value");
|
||||
|
||||
let view = View::new(&memory, 0xF0);
|
||||
let advanced_view = view.advance(1);
|
||||
assert_eq!(11, advanced_view.get_index(0));
|
||||
assert_eq!(12, advanced_view.get_index(1));
|
||||
assert_eq!(13, advanced_view.get_index(2));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn get_can_advance_view_with_wraparound() {
|
||||
let mut memory = Memory::new();
|
||||
memory.set(0x01, 10).expect("failed to set memory value");
|
||||
|
||||
let view = View::new(&memory, MAX_MEMORY_ADDRESS);
|
||||
let advanced_view = view.advance(2);
|
||||
assert_eq!(10, advanced_view.get());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_can_get_two_values() {
|
||||
let mut memory = Memory::new();
|
||||
memory.set(0xF0, 10).expect("failed to set memory value");
|
||||
memory.set(0xF1, 11).expect("failed to set memory value");
|
||||
memory.set(0xF2, 12).expect("failed to set memory value");
|
||||
memory.set(0xF3, 13).expect("failed to set memory value");
|
||||
|
||||
let view = View::new(&memory, 0xF0);
|
||||
let (a, b) = view.get_tuple();
|
||||
assert_eq!(a, 10);
|
||||
assert_eq!(b, 11);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_can_get_three_values() {
|
||||
let mut memory = Memory::new();
|
||||
memory.set(0xF0, 10).expect("failed to set memory value");
|
||||
memory.set(0xF1, 11).expect("failed to set memory value");
|
||||
memory.set(0xF2, 12).expect("failed to set memory value");
|
||||
memory.set(0xF3, 13).expect("failed to set memory value");
|
||||
|
||||
let view = View::new(&memory, 0xF0);
|
||||
let (a, b, c) = view.get_tuple();
|
||||
assert_eq!(a, 10);
|
||||
assert_eq!(b, 11);
|
||||
assert_eq!(c, 12);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_can_get_four_values() {
|
||||
let mut memory = Memory::new();
|
||||
memory.set(0xF0, 10).expect("failed to set memory value");
|
||||
memory.set(0xF1, 11).expect("failed to set memory value");
|
||||
memory.set(0xF2, 12).expect("failed to set memory value");
|
||||
memory.set(0xF3, 13).expect("failed to set memory value");
|
||||
|
||||
let view = View::new(&memory, 0xF0);
|
||||
let (a, b, c, d) = view.get_tuple();
|
||||
assert_eq!(a, 10);
|
||||
assert_eq!(b, 11);
|
||||
assert_eq!(c, 12);
|
||||
assert_eq!(d, 13);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_getting_more_values_than_in_view_wraps_around() {
|
||||
let mut memory = Memory::new();
|
||||
memory.set(0, 100).expect("failed to set memory value");
|
||||
memory
|
||||
.set(MAX_MEMORY_ADDRESS - 4, 10)
|
||||
.expect("failed to set memory value");
|
||||
memory
|
||||
.set(MAX_MEMORY_ADDRESS - 3, 11)
|
||||
.expect("failed to set memory value");
|
||||
memory
|
||||
.set(MAX_MEMORY_ADDRESS - 2, 12)
|
||||
.expect("failed to set memory value");
|
||||
memory
|
||||
.set(MAX_MEMORY_ADDRESS - 1, 13)
|
||||
.expect("failed to set memory value");
|
||||
memory
|
||||
.set(MAX_MEMORY_ADDRESS, 14)
|
||||
.expect("failed to set memory value");
|
||||
|
||||
let view = View::new(&memory, MAX_MEMORY_ADDRESS - 4);
|
||||
|
||||
#[allow(clippy::many_single_char_names)]
|
||||
let (a, b, c, d, e, f) = view.get_tuple();
|
||||
assert_eq!(a, 10);
|
||||
assert_eq!(b, 11);
|
||||
assert_eq!(c, 12);
|
||||
assert_eq!(d, 13);
|
||||
assert_eq!(e, 14);
|
||||
assert_eq!(f, 100);
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue