diff options
27 files changed, 865 insertions, 562 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md index 57d7825..f361f14 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,13 @@ +# 0.2E + +* Update readme +* Implement remaining data-processing instructions with register operands in Thumb +* Extend memory writes according to region +* Fix colour decoding +* Improve logging +* Update code structure +* Implement read-only memory + # 0.2D * Reformat changelog in Markdown @@ -22,7 +22,7 @@ The emulator tries to read the configuration file at `${HOME}/.luma.toml`. If su * `image`: The path to the image file `video`: - * `scale`: The scale modifier applied to the screen (1-4294967295) + * `scale`: The scale modifier applied to the screen (1‐4294967295) If a path is parsed as a terminal parameter, the configuration at that location is read instead. @@ -30,13 +30,13 @@ If a path is parsed as a terminal parameter, the configuration at that location Currently, the emulator has limited support for the Arm instruction set. All of the instructions used in the provided test program are – however – implemented. -The entire memory space (`0x00000000` to `0x0E00FFFF`) is available, however, no I/O-mapped addresses are currently functional. +The entire memory space (`0x00000000` to `0x0E00FFFF`) is available, however, no I/O‐mapped addresses are currently functional. Improved support is, of course, planned. # Copyright & License -Copyright 2021-2023 Gabriel Bjørnager Jensen. +Copyright 2021‐2023 Gabriel Bjørnager Jensen. This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. diff --git a/src/luma.rs b/src/luma.rs index 26c0014..9fd490a 100644 --- a/src/luma.rs +++ b/src/luma.rs @@ -21,8 +21,6 @@ If not, see <https://www.gnu.org/licenses/>. */ -use sdl2::pixels::Color; - pub mod app; pub mod configuration; pub mod cpu; @@ -32,7 +30,7 @@ pub mod state; pub const VERSION: (u32, u32) = ( 0x0, // major - 0x2D, // minor + 0x2E, // minor ); pub enum Error { @@ -51,7 +49,7 @@ impl Error { Error::OutOfBounds( address) => format!("out-of-bounds address {address:#010X} (limit is {:#010X})", MEMORY_LENGTH), }; - eprintln!("trap: {message}"); + eprintln!("\u{1B}[1m\u{1B}[91mtrap\u{1B}[0m: {message}"); } } @@ -67,17 +65,26 @@ pub const SCREEN_SIZE: (u8, u8) = ( 0xA0, // height ); -pub const fn decode_colour(colour: u16) -> Color { - let red = ((colour & 0b0000000000011111) << 0x3) as u8; - let green = ((colour & 0b0000001111100000) >> 0x2) as u8; - let blue = ((colour & 0b0111110000000000) >> 0x7) as u8; +macro_rules! log { + () => { + eprintln!(); + }; - return Color::RGB(red, green, blue); + ($($message: tt)*) => {{ + if cfg!(debug_assertions) { + eprintln!("{}", format!($($message)?)); + } + }}; } +pub(crate) use log; + +macro_rules! log_assignment { + ($name: expr, $value: expr) => {{ + use crate::luma::log; -pub fn log(message: &str) { - // This optimises the function away. - if cfg!(debug_assertions) { - eprintln!("{message}"); - } + if cfg!(debug_assertions) { + log!("\u{1B}[3m\u{B7} \u{1B}[1m{}\u{1B}[22m = {}\u{1B}[0m", format!($name), format!($value)); + } + }}; } +pub(crate) use log_assignment; diff --git a/src/luma/app/draw_video.rs b/src/luma/app/draw_video.rs index 5df407e..4acb012 100644 --- a/src/luma/app/draw_video.rs +++ b/src/luma/app/draw_video.rs @@ -21,7 +21,7 @@ If not, see <https://www.gnu.org/licenses/>. */ -use crate::luma::{decode_colour, SCREEN_SIZE}; +use crate::luma::SCREEN_SIZE; use crate::luma::app::App; use sdl2::pixels::Color; @@ -33,10 +33,11 @@ impl App { let mut palette: [Color; 0x100] = [Color::RGB(0x00, 0x00, 0x00); 0x100]; - for (index, value) in (&agb_palette[0x0..0x100]).into_iter().enumerate() { - let colour = decode_colour(*value); + for (index, element) in palette.iter_mut().enumerate() { + let value = unsafe { *agb_palette.get_unchecked(index) }; - palette[index] = colour; + let colour = decode_colour(value); + *element = colour; } for pixel_y in 0x0..SCREEN_SIZE.1 { @@ -60,3 +61,11 @@ impl App { self.canvas.present(); } } + +fn decode_colour(colour: u16) -> Color { + let red = ((colour & 0b0000000000011111) as f32 / 31.0 * 255.0) as u8; + let green = ((colour & 0b0000001111100000) as f32 / 992.0 * 255.0) as u8; + let blue = ((colour & 0b0111110000000000) as f32 / 31744.0 * 255.0) as u8; + + return Color::RGB(red, green, blue); +} diff --git a/src/luma/app/run.rs b/src/luma/app/run.rs index 959bc71..6c00153 100644 --- a/src/luma/app/run.rs +++ b/src/luma/app/run.rs @@ -53,8 +53,6 @@ impl App { self.sync_video(frame_start); } - cpu.kill()?; - return Ok(()); } } diff --git a/src/luma/cpu.rs b/src/luma/cpu.rs index a31c47d..28f6844 100644 --- a/src/luma/cpu.rs +++ b/src/luma/cpu.rs @@ -27,28 +27,16 @@ use crate::luma::state::State; use std::sync::{Arc, Mutex}; use std::sync::atomic::AtomicBool; -pub mod add; pub mod boot; -pub mod branch; -pub mod compare; -pub mod r#continue; pub mod decode_arm; pub mod decode_thumb; -pub mod load; -pub mod r#move; -pub mod store; -pub mod subtract; - -pub type Decoder = fn(&mut Cpu) -> Instruction; - -pub struct Cpu { - state: Arc<Mutex<State>>, - cycle: u64, - dead: Arc<AtomicBool>, - - instruction_size: u32, - decoder: Decoder, -} +pub mod isa_arithmetic; +pub mod isa_bitwise; +pub mod isa_branch; +pub mod isa_logic; +pub mod isa_memory; +pub mod isa_move; +pub mod r#continue; mod exchange; mod test_predicate; @@ -61,6 +49,17 @@ pub use exchange::*; #[allow(unused_imports)] pub use test_predicate::*; +pub type Decoder = fn(&mut Cpu) -> Instruction; + +pub struct Cpu { + state: Arc<Mutex<State>>, + cycle: u64, + dead: Arc<AtomicBool>, + + instruction_size: u32, + decoder: Decoder, +} + impl Cpu { pub fn new(state: State) -> Self { return Self { diff --git a/src/luma/cpu/add.rs b/src/luma/cpu/add.rs deleted file mode 100644 index c2594a3..0000000 --- a/src/luma/cpu/add.rs +++ /dev/null @@ -1,50 +0,0 @@ -/* - Copyright 2021-2023 Gabriel Jensen. - - This file is part of Luma. - - Luma is free software: you can redistribute it - and/or modify it under the terms of the GNU - Affero General Public License as published by - the Free Software Foundation, either version 3 - of the License, or (at your option) any later - version. - - Luma is distributed in the hope that it will be - useful, but WITHOUT ANY WARRANTY; without even - the implied warranty of MERCHANTABILITY or - FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Affero General Public License for more details. - - You should have received a copy of the GNU - Affero General Public License along with Luma. - If not, see <https://www.gnu.org/licenses/>. -*/ - -use crate::luma::log; -use crate::luma::cpu::Cpu; - -impl Cpu { - pub(super) fn add_immediate(&mut self, destination: u8, base: u8, immediate: u32) { - log(&format!("add r{destination}, r{base}, {immediate:#X}")); - - let mut state = self.state.lock().unwrap(); - - let base_value = state.read_register(base); - - let value = base_value.wrapping_add(immediate); - state.write_register(destination, value); - } - - pub(super) fn add_register(&mut self, destination: u8, base: u8, add: u8) { - log(&format!("add r{destination}, r{base}, r{add}")); - - let mut state = self.state.lock().unwrap(); - - let base_value = state.read_register(base); - let add_value = state.read_register(add); - - let value = base_value.wrapping_add(add_value); - state.write_register(destination, value); - } -} diff --git a/src/luma/cpu/boot.rs b/src/luma/cpu/boot.rs index f92176b..4a089eb 100644 --- a/src/luma/cpu/boot.rs +++ b/src/luma/cpu/boot.rs @@ -73,32 +73,55 @@ impl Cpu { use Instruction::*; match instruction { - AddImmediate( destination, base, immediate) => self.add_immediate( destination, base, immediate), - AddRegister( destination, base, immediate) => self.add_register( destination, base, immediate), - Branch( offset) => self.branch( offset), - BranchExchange( source) => self.branch_exchange( source), - BranchLink( offset) => self.branch_link( offset), - BranchLinkPrefix( offset) => self.branch_link_prefix( offset), - BranchLinkSuffix( offset) => self.branch_link_suffix( offset), - CompareImmediate( register, immediate) => self.compare_immediate( register, immediate), - CompareRegister( left, right) => self.compare_register( left, right), - LoadHalfword( destination, base, offset) => self.load_halfword( destination, base, offset), - LoadImmediateOffset( destination, base, offset) => self.load_immediate_offset( destination, base, offset), - LoadPc( destination, offset) => self.load_pc( destination, offset), - MoveImmediate( destination, immediate) => self.move_immediate( destination, immediate), - MoveImmediateArithmeticShiftRight( destination, base, shift) => self.move_immediate_arithmetic_shift_right(destination, base, shift), - MoveImmediateLogicalShiftLeftImmediate( destination, base, shift) => self.move_immediate_logical_shift_left( destination, base, shift), - MoveImmediateLogicalShiftRightImmediate(destination, base, shift) => self.move_immediate_logical_shift_right( destination, base, shift), - MoveRegister( destination, source) => self.move_register( destination, source), - MoveRegisterArithmeticShiftRight( destination, base, shift) => self.move_register_arithmetic_shift_right( destination, base, shift), - MoveRegisterLogicalShiftLeftImmediate( destination, base, shift) => self.move_register_logical_shift_left( destination, base, shift), - MoveRegisterLogicalShiftRightImmediate( destination, base, shift) => self.move_register_logical_shift_right( destination, base, shift), - StoreByteImmediateOffset( source, base, offset) => self.store_byte_immediate_offset( source, base, offset), - StoreByteRegisterOffset( source, base, offset) => self.store_byte_register_offset( source, base, offset), - StoreHalfword( source, base, offset) => self.store_halfword( source, base, offset), - StoreImmediateOffset( source, base, offset) => self.store_immediate_offset( source, base, offset), - SubtractImmediate( destination, base, immediate) => self.subtract_immediate( destination, base, immediate), - SubtractRegister( destination, base, immediate) => self.subtract_register( destination, base, immediate), + // Arithmetic: + AddCarryRegister( rd, rn, rm) => self.isa_add_carry_register( rd, rn, rm), + AddImmediate( rd, rn, imm) => self.isa_add_immediate( rd, rn, imm), + AddRegister( rd, rn, rm) => self.isa_add_register( rd, rn, rm), + MultiplyRegister( rd, rm, rs) => self.isa_multiply_register( rd, rm, rs), + ReverseSubtractImmediate(rd, rm, imm) => self.isa_reverse_subtract_immediate(rd, rm, imm), + SubtractCarryRegister( rd, rn, rm) => self.isa_subtract_carry_register( rd, rn, rm), + SubtractImmediate( rd, rn, imm) => self.isa_subtract_immediate( rd, rn, imm), + SubtractRegister( rd, rn, rm) => self.isa_subtract_register( rd, rn, rm), + + // Bitwise: + AndRegister( rd, rn, rm) => self.isa_and_register( rd, rn, rm), + BitClearRegister( rd, rn, rm) => self.isa_bit_clear_register( rd, rn, rm), + LogicalOrRegister( rd, rn, rm) => self.isa_logical_or_register( rd, rn, rm), + ExclusiveOrRegister(rd, rn, rm) => self.isa_exclusive_or_register(rd, rn, rm), + + // Branch: + Branch( imm) => self.isa_branch( imm), + BranchExchange( rm) => self.isa_branch_exchange( rm), + BranchLink( imm) => self.isa_branch_link( imm), + BranchLinkPrefix(imm) => self.isa_branch_link_prefix(imm), + BranchLinkSuffix(imm) => self.isa_branch_link_suffix(imm), + + // Logic: + CompareImmediate( rn, imm) => self.isa_compare_immediate( rn, imm), + CompareNegativeRegister(rn, rm) => self.isa_compare_negative_register(rn, rm), + CompareRegister( rn, rm) => self.isa_compare_register( rn, rm), + TestRegister( rn, rm) => self.isa_test_register( rn, rm), + + // Memory: + LoadHalfword( rd, rn, imm) => self.isa_load_halfword( rd, rn, imm), + LoadImmediateOffset( rd, rn, imm) => self.isa_load_immediate_offset( rd, rn, imm), + LoadPc( rd, rn) => self.isa_load_pc( rd, rn), + StoreByteImmediateOffset(rd, rn, imm) => self.isa_store_byte_immediate_offset(rd, rn, imm), + StoreByteRegisterOffset( rd, rn, rm) => self.isa_store_byte_register_offset( rd, rn, rm), + StoreHalfword( rd, rn, imm) => self.isa_store_halfword( rd, rn, imm), + StoreImmediateOffset( rd, rn, imm) => self.isa_store_immediate_offset( rd, rn, imm), + + // Move: + MoveImmediate( rd, imm) => self.isa_move_immediate( rd, imm), + MoveImmediateArithmeticShiftRight( rd, rm, rs) => self.isa_move_immediate_arithmetic_shift_right(rd, rm, rs), + MoveImmediateLogicalShiftLeftImmediate( rd, rm, rs) => self.isa_move_immediate_logical_shift_left( rd, rm, rs), + MoveImmediateLogicalShiftRightImmediate(rd, rm, rs) => self.isa_move_immediate_logical_shift_right( rd, rm, rs), + MoveNotRegister( rd, rm) => self.isa_move_not_register( rd, rm), + MoveRegister( rd, rm) => self.isa_move_register( rd, rm), + MoveRegisterArithmeticShiftRight( rd, rm, rs) => self.isa_move_register_arithmetic_shift_right( rd, rm, rs), + MoveRegisterLogicalShiftLeftImmediate( rd, rm, rs) => self.isa_move_register_logical_shift_left( rd, rm, rs), + MoveRegisterLogicalShiftRightImmediate( rd, rm, rs) => self.isa_move_register_logical_shift_right( rd, rm, rs), + MoveRegisterRotateRight( rd, rm, rs) => self.isa_move_register_rotate_right( rd, rm, rs), Undefined => {}, }; diff --git a/src/luma/cpu/compare.rs b/src/luma/cpu/compare.rs deleted file mode 100644 index 4aa3bc4..0000000 --- a/src/luma/cpu/compare.rs +++ /dev/null @@ -1,70 +0,0 @@ -/* - Copyright 2021-2023 Gabriel Jensen. - - This file is part of Luma. - - Luma is free software: you can redistribute it - and/or modify it under the terms of the GNU - Affero General Public License as published by - the Free Software Foundation, either version 3 - of the License, or (at your option) any later - version. - - Luma is distributed in the hope that it will be - useful, but WITHOUT ANY WARRANTY; without even - the implied warranty of MERCHANTABILITY or - FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Affero General Public License for more details. - - You should have received a copy of the GNU - Affero General Public License along with Luma. - If not, see <https://www.gnu.org/licenses/>. -*/ - -use crate::luma::log; -use crate::luma::cpu::Cpu; - -impl Cpu { - pub(super) fn compare_immediate(&mut self, register: u8, immediate: u32) { - log(&format!("cmp r{register}, {immediate:#X}")); - - let mut state = self.state.lock().unwrap(); - - let register_value = state.read_register(register); - - let (temporary, c) = register_value.overflowing_sub(immediate); - - let v = false; // ??? - let z = temporary == 0x0; - let n = temporary & 0b10000000000000000000000000000000 != 0x0; - - let mut cpsr = state.read_cpsr() & 0b00001111111111111111111111111111; - cpsr |= (v as u32) << 0x1C; - cpsr |= (c as u32) << 0x1D; - cpsr |= (z as u32) << 0x1E; - cpsr |= (n as u32) << 0x1F; - state.write_cpsr(cpsr); - } - - pub(super) fn compare_register(&mut self, left: u8, right: u8) { - log(&format!("cmp r{left}, r{right}")); - - let mut state = self.state.lock().unwrap(); - - let left_value = state.read_register(left); - let right_value = state.read_register(right); - - let (temporary, c) = left_value.overflowing_sub(right_value); - - let v = false; // ??? - let z = temporary == 0x0; - let n = temporary & 0b10000000000000000000000000000000 != 0x0; - - let mut cpsr = state.read_cpsr() & 0b00001111111111111111111111111111; - cpsr |= (v as u32) << 0x1C; - cpsr |= (c as u32) << 0x1D; - cpsr |= (z as u32) << 0x1E; - cpsr |= (n as u32) << 0x1F; - state.write_cpsr(cpsr); - } -} diff --git a/src/luma/cpu/decode_arm.rs b/src/luma/cpu/decode_arm.rs index 9b4377c..2e7e882 100644 --- a/src/luma/cpu/decode_arm.rs +++ b/src/luma/cpu/decode_arm.rs @@ -37,7 +37,8 @@ impl Cpu { drop(state); - log(&format!("{opcode:#034b} @ {address:#010X} - ({})", self.cycle)); + log!(); + log!("\u{1B}[1m{opcode:032b}\u{1B}[0m @ \u{1B}[1m{address:08X}\u{1B}[0m - ({})", self.cycle); return decode(address, opcode); } @@ -156,7 +157,7 @@ fn decode(address: u32, opcode: u32) -> Instruction { 0b001 => { let immediate = (opcode & 0b00000000000000000000000011111111) as u8; - let rotate = (opcode & 0b00000000000000000000111100000000).wrapping_shr(0x8) as u8; + let rotate = (opcode & 0b00000000000000000000111100000000).wrapping_shr(0x8); let destination = (opcode & 0b00000000000000001111000000000000).wrapping_shr(0xC) as u8; @@ -164,7 +165,8 @@ fn decode(address: u32, opcode: u32) -> Instruction { let _s = opcode & 0b00000000000100000000000000000000 != 0x0; - let immediate = (immediate as u32).rotate_right(rotate as u32); + let rotate = rotate << 0x1; + let immediate = (immediate as u32).rotate_right(rotate); match (opcode & 0b00000001111000000000000000000000).wrapping_shr(0x15) { 0b0000 => {}, diff --git a/src/luma/cpu/decode_thumb.rs b/src/luma/cpu/decode_thumb.rs index c8eacb8..43cbcfa 100644 --- a/src/luma/cpu/decode_thumb.rs +++ b/src/luma/cpu/decode_thumb.rs @@ -40,7 +40,8 @@ impl Cpu { drop(state); - log(&format!("{opcode:#018b} @ {address:#010X} - ({})", self.cycle)); + log!(); + log!(" \u{1B}[1m{opcode:016b}\u{1B}[0m @ \u{1B}[1m{address:08X}\u{1B}[0m - ({})", self.cycle); match (opcode & 0b1110000000000000).wrapping_shr(0xD) { 0b000 => { @@ -115,42 +116,42 @@ impl Cpu { 0b0 => { match (opcode & 0b0000010000000000).wrapping_shr(0xA) { 0b0 => { - let destination = (opcode & 0b0000000000000111) as u8; + let left = (opcode & 0b0000000000000111) as u8; - let base = (opcode & 0b0000000000111000).wrapping_shr(0x3) as u8; + let right = (opcode & 0b0000000000111000).wrapping_shr(0x3) as u8; match (opcode & 0b0000001111000000).wrapping_shr(0x6) { - 0b0000 => {}, + 0b0000 => return AndRegister(left, left, right), - 0b0001 => {}, + 0b0001 => return ExclusiveOrRegister(left, left, right), - 0b0010 => {}, + 0b0010 => return MoveRegisterLogicalShiftLeftImmediate(left, left, right), - 0b0011 => {}, + 0b0011 => return MoveRegisterLogicalShiftRightImmediate(left, left, right), - 0b0100 => {}, + 0b0100 => return MoveRegisterArithmeticShiftRight(left, left, right), - 0b0101 => {}, + 0b0101 => return AddCarryRegister(left, left, right), - 0b0110 => {}, + 0b0110 => return SubtractCarryRegister(left, left, right), - 0b0111 => {}, + 0b0111 => return MoveRegisterRotateRight(left, left, right), - 0b1000 => {}, + 0b1000 => return TestRegister(left, right), - 0b1001 => {}, + 0b1001 => return ReverseSubtractImmediate(left, right, 0x0), - 0b1010 => return CompareRegister(destination, base), + 0b1010 => return CompareRegister(left, right), - 0b1011 => {}, + 0b1011 => return CompareNegativeRegister(left, right), - 0b1100 => {}, + 0b1100 => return LogicalOrRegister(left, right, right), - 0b1101 => {}, + 0b1101 => return MultiplyRegister(left, left, right), - 0b1110 => {}, + 0b1110 => return BitClearRegister(left, left, right), - 0b1111 => {}, + 0b1111 => return MoveNotRegister(left, right), _ => unsafe { unreachable_unchecked() }, } diff --git a/src/luma/cpu/isa_arithmetic.rs b/src/luma/cpu/isa_arithmetic.rs new file mode 100644 index 0000000..3fe8830 --- /dev/null +++ b/src/luma/cpu/isa_arithmetic.rs @@ -0,0 +1,122 @@ +/* + Copyright 2021-2023 Gabriel Jensen. + + This file is part of Luma. + + Luma is free software: you can redistribute it + and/or modify it under the terms of the GNU + Affero General Public License as published by + the Free Software Foundation, either version 3 + of the License, or (at your option) any later + version. + + Luma is distributed in the hope that it will be + useful, but WITHOUT ANY WARRANTY; without even + the implied warranty of MERCHANTABILITY or + FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Affero General Public License for more details. + + You should have received a copy of the GNU + Affero General Public License along with Luma. + If not, see <https://www.gnu.org/licenses/>. +*/ + +use crate::luma::log; +use crate::luma::cpu::Cpu; + +impl Cpu { + pub(super) fn isa_add_carry_register(&mut self, rd: u8, rn: u8, rm: u8) { + log!("adc r{rd}, r{rn}, r{rm}"); + + let mut state = self.state.lock().unwrap(); + + let rn_value = state.read_register(rn); + let rm_value = state.read_register(rm); + + let (mut value, c) = rn_value.overflowing_add(rm_value); + value += c as u32; + state.write_register(rd, value); + } + + pub(super) fn isa_add_immediate(&mut self, rd: u8, rn: u8, imm: u32) { + log!("add r{rd}, r{rn}, {imm:#X}"); + + let mut state = self.state.lock().unwrap(); + + let rn_value = state.read_register(rn); + + let value = rn_value.wrapping_add(imm); + state.write_register(rd, value); + } + + pub(super) fn isa_add_register(&mut self, rd: u8, rn: u8, rm: u8) { + log!("add r{rd}, r{rn}, r{rm}"); + + let mut state = self.state.lock().unwrap(); + + let rn_value = state.read_register(rn); + let rm_value = state.read_register(rm); + + let value = rn_value.wrapping_add(rm_value); + state.write_register(rd, value); + } + + pub(super) fn isa_multiply_register(&mut self, rd: u8, rm: u8, rs: u8) { + log!("mul r{rd}, r{rm}, r{rs}"); + + let mut state = self.state.lock().unwrap(); + + let rm_value = state.read_register(rm); + let rs_value = state.read_register(rs); + + let result = rm_value.wrapping_mul(rs_value); + state.write_register(rd, result); + } + + pub(super) fn isa_reverse_subtract_immediate(&mut self, rd: u8, rm: u8, imm: u32) { + log!("rsb r{rd}, r{rm}, {imm:#X}"); + + let mut state = self.state.lock().unwrap(); + + let rm_value = state.read_register(rm); + + let result = imm.wrapping_sub(rm_value); + state.write_register(rd, result); + } + + pub(super) fn isa_subtract_carry_register(&mut self, rd: u8, rn: u8, rm: u8) { + log!("sbc r{rd}, r{rn}, r{rm}"); + + let mut state = self.state.lock().unwrap(); + + let rn_value = state.read_register(rn); + let rm_value = state.read_register(rm); + + let (mut value, c) = rn_value.overflowing_sub(rm_value); + value += (!c) as u32; + state.write_register(rd, value); + } + + pub(super) fn isa_subtract_immediate(&mut self, rd: u8, rn: u8, imm: u32) { + log!("sub r{rd}, r{rn}, {imm:#X}"); + + let mut state = self.state.lock().unwrap(); + + let rn_value = state.read_register(rn); + + let value = rn_value.wrapping_sub(imm); + state.write_register(rd, value); + } + + pub(super) fn isa_subtract_register(&mut self, rd: u8, rn: u8, rm: u8) { + log!("sub r{rd}, r{rn}, r{rm}"); + + let mut state = self.state.lock().unwrap(); + + let rn_value = state.read_register(rn); + let rm_value = state.read_register(rm); + + let value = rn_value.wrapping_sub(rm_value); + state.write_register(rd, value); + } +} diff --git a/src/luma/cpu/isa_bitwise.rs b/src/luma/cpu/isa_bitwise.rs new file mode 100644 index 0000000..f2ed822 --- /dev/null +++ b/src/luma/cpu/isa_bitwise.rs @@ -0,0 +1,75 @@ +/* + Copyright 2021-2023 Gabriel Jensen. + + This file is part of Luma. + + Luma is free software: you can redistribute it + and/or modify it under the terms of the GNU + Affero General Public License as published by + the Free Software Foundation, either version 3 + of the License, or (at your option) any later + version. + + Luma is distributed in the hope that it will be + useful, but WITHOUT ANY WARRANTY; without even + the implied warranty of MERCHANTABILITY or + FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Affero General Public License for more details. + + You should have received a copy of the GNU + Affero General Public License along with Luma. + If not, see <https://www.gnu.org/licenses/>. +*/ + +use crate::luma::log; +use crate::luma::cpu::Cpu; + +impl Cpu { + pub(super) fn isa_and_register(&mut self, rd: u8, rn: u8, rm: u8) { + log!("and r{rd}, r{rn}, r{rm}"); + + let mut state = self.state.lock().unwrap(); + + let rn_value = state.read_register(rn); + let rm_value = state.read_register(rm); + + let result = rn_value & rm_value; + state.write_register(rd, result); + } + + pub(super) fn isa_bit_clear_register(&mut self, rd: u8, rn: u8, rm: u8) { + log!("bic r{rd}, r{rn}, r{rm}"); + + let mut state = self.state.lock().unwrap(); + + let rn_value = state.read_register(rn); + let rm_value = state.read_register(rm); + + let result = rn_value & !rm_value; + state.write_register(rd, result); + } + + pub(super) fn isa_exclusive_or_register(&mut self, rd: u8, rn: u8, rm: u8) { + log!("eor r{rd}, r{rn}, r{rm}"); + + let mut state = self.state.lock().unwrap(); + + let rn_value = state.read_register(rn); + let rm_value = state.read_register(rm); + + let result = rn_value ^ rm_value; + state.write_register(rd, result); + } + + pub(super) fn isa_logical_or_register(&mut self, rd: u8, rn: u8, rm: u8) { + log!("eor r{rd}, r{rn}, r{rm}"); + + let mut state = self.state.lock().unwrap(); + + let rn_value = state.read_register(rn); + let rm_value = state.read_register(rm); + + let result = rn_value | rm_value; + state.write_register(rd, result); + } +} diff --git a/src/luma/cpu/branch.rs b/src/luma/cpu/isa_branch.rs index a29205d..a887a35 100644 --- a/src/luma/cpu/branch.rs +++ b/src/luma/cpu/isa_branch.rs @@ -25,23 +25,23 @@ use crate::luma::log; use crate::luma::cpu::{Cpu, exchange}; impl Cpu { - pub(super) fn branch(&mut self, offset: i32) { + pub(super) fn isa_branch(&mut self, imm: i32) { let mut state = self.state.lock().unwrap(); - let mut target = state.read_register(0xF).wrapping_add_signed(offset); + let mut target = state.read_register(0xF).wrapping_add_signed(imm); - log(&format!("b {target:#X}")); + log!("b {target:#X}"); target = target.wrapping_add(self.instruction_size); state.write_register(0xF, target); } - pub(super) fn branch_exchange(&mut self, source: u8) { + pub(super) fn isa_branch_exchange(&mut self, rm: u8) { let mut state = self.state.lock().unwrap(); - log(&format!("bx r{source}")); + log!("bx r{rm}"); - let mut target = state.read_register(source); + let mut target = state.read_register(rm); let t = target & 0b00000000000000000000000000000001 != 0x0; exchange!(self, t); @@ -54,32 +54,32 @@ impl Cpu { state.write_register(0xF, target); } - pub(super) fn branch_link(&mut self, offset: i32) { + pub(super) fn isa_branch_link(&mut self, imm: i32) { let mut state = self.state.lock().unwrap(); - let mut target = state.read_register(0xF).wrapping_add_signed(offset); + let mut target = state.read_register(0xF).wrapping_add_signed(imm); - log(&format!("bl {target:#X}")); + log!("bl {target:#X}"); target = target.wrapping_add(self.instruction_size); state.write_register(0xF, target); } - pub(super) fn branch_link_prefix(&mut self, offset: i32) { + pub(super) fn isa_branch_link_prefix(&mut self, imm: i32) { let mut state = self.state.lock().unwrap(); - let target = state.read_register(0xF).wrapping_add_signed(offset); + let target = state.read_register(0xF).wrapping_add_signed(imm); state.write_register(0xE, target); } - pub(super) fn branch_link_suffix(&mut self, offset: i32) { + pub(super) fn isa_branch_link_suffix(&mut self, imm: i32) { let mut state = self.state.lock().unwrap(); - let mut branch_target = state.read_register(0xE).wrapping_add_signed(offset); + let mut branch_target = state.read_register(0xE).wrapping_add_signed(imm); let link_target = state.read_register(0xF).wrapping_sub(0x2); - log(&format!("bl {branch_target:#X}")); + log!("bl {branch_target:#X}"); state.write_register(0xE, link_target); diff --git a/src/luma/cpu/isa_logic.rs b/src/luma/cpu/isa_logic.rs new file mode 100644 index 0000000..a624f57 --- /dev/null +++ b/src/luma/cpu/isa_logic.rs @@ -0,0 +1,111 @@ +/* + Copyright 2021-2023 Gabriel Jensen. + + This file is part of Luma. + + Luma is free software: you can redistribute it + and/or modify it under the terms of the GNU + Affero General Public License as published by + the Free Software Foundation, either version 3 + of the License, or (at your option) any later + version. + + Luma is distributed in the hope that it will be + useful, but WITHOUT ANY WARRANTY; without even + the implied warranty of MERCHANTABILITY or + FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Affero General Public License for more details. + + You should have received a copy of the GNU + Affero General Public License along with Luma. + If not, see <https://www.gnu.org/licenses/>. +*/ + +use crate::luma::log; +use crate::luma::cpu::Cpu; + +impl Cpu { + pub(super) fn isa_compare_immediate(&mut self, rn: u8, imm: u32) { + log!("cmp r{rn}, {imm:#X}"); + + let mut state = self.state.lock().unwrap(); + + let rn_value = state.read_register(rn); + + let (temporary, c) = rn_value.overflowing_sub(imm); + + let v = false; // ??? + let z = temporary == 0x0; + let n = temporary & 0b10000000000000000000000000000000 != 0x0; + + let mut cpsr = state.read_cpsr() & 0b00001111111111111111111111111111; + cpsr |= (v as u32) << 0x1C; + cpsr |= (c as u32) << 0x1D; + cpsr |= (z as u32) << 0x1E; + cpsr |= (n as u32) << 0x1F; + state.write_cpsr(cpsr); + } + + pub(super) fn isa_compare_negative_register(&mut self, rn: u8, rm: u8) { + log!("cmn r{rn}, r{rm}"); + + let mut state = self.state.lock().unwrap(); + + let rn_value = state.read_register(rn); + let rm_value = state.read_register(rm); + + let (temporary, c) = rn_value.overflowing_add(rm_value); + + let v = false; // ??? + let z = temporary == 0x0; + let n = temporary & 0b10000000000000000000000000000000 != 0x0; + + let mut cpsr = state.read_cpsr() & 0b00001111111111111111111111111111; + cpsr |= (v as u32) << 0x1C; + cpsr |= (c as u32) << 0x1D; + cpsr |= (z as u32) << 0x1E; + cpsr |= (n as u32) << 0x1F; + state.write_cpsr(cpsr); + } + + pub(super) fn isa_compare_register(&mut self, rn: u8, rm: u8) { + log!("cmp r{rn}, r{rm}"); + + let mut state = self.state.lock().unwrap(); + + let rn_value = state.read_register(rn); + let rm_value = state.read_register(rm); + + let (temporary, c) = rn_value.overflowing_sub(rm_value); + + let v = false; // ??? + let z = temporary == 0x0; + let n = temporary & 0b10000000000000000000000000000000 != 0x0; + + let mut cpsr = state.read_cpsr() & 0b00001111111111111111111111111111; + cpsr |= (v as u32) << 0x1C; + cpsr |= (c as u32) << 0x1D; + cpsr |= (z as u32) << 0x1E; + cpsr |= (n as u32) << 0x1F; + state.write_cpsr(cpsr); + } + + pub(super) fn isa_test_register(&mut self, rn: u8, rm: u8) { + log!("tst r{rn}, r{rm}"); + + let mut state = self.state.lock().unwrap(); + + let rn_value = state.read_register(rn); + let rm_value = state.read_register(rm); + + let temporary = rn_value & !rm_value; + + let z = temporary == 0x0; + let n = temporary & 0b10000000000000000000000000000000 != 0x0; + + let mut cpsr = state.read_cpsr() & 0b00001111111111111111111111111111; + cpsr |= (z as u32) << 0x1E; + cpsr |= (n as u32) << 0x1F; + state.write_cpsr(cpsr); + } +} diff --git a/src/luma/cpu/isa_memory.rs b/src/luma/cpu/isa_memory.rs new file mode 100644 index 0000000..8b953c0 --- /dev/null +++ b/src/luma/cpu/isa_memory.rs @@ -0,0 +1,122 @@ +/* + Copyright 2021-2023 Gabriel Jensen. + + This file is part of Luma. + + Luma is free software: you can redistribute it + and/or modify it under the terms of the GNU + Affero General Public License as published by + the Free Software Foundation, either version 3 + of the License, or (at your option) any later + version. + + Luma is distributed in the hope that it will be + useful, but WITHOUT ANY WARRANTY; without even + the implied warranty of MERCHANTABILITY or + FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Affero General Public License for more details. + + You should have received a copy of the GNU + Affero General Public License along with Luma. + If not, see <https://www.gnu.org/licenses/>. +*/ + +use crate::luma::log; +use crate::luma::cpu::Cpu; + +impl Cpu { + pub(super) fn isa_load_halfword(&mut self, rd: u8, rn: u8, imm: i16) { + let mut state = self.state.lock().unwrap(); + + log!("ldrh r{rd}, [r{rn}, {imm:#X}]"); + + let rn_value = state.read_register(rn); + + let target = rn_value.wrapping_add_signed(imm as i32); + + let result = state.read_halfword(target) as u32; + state.write_register(rd, result); + } + + pub(super) fn isa_load_immediate_offset(&mut self, rd: u8, rn: u8, imm: i16) { + let mut state = self.state.lock().unwrap(); + + log!("ldr r{rd}, [r{rn}, {imm:#X}]"); + + let rn_value = state.read_register(rn); + + let target = rn_value.wrapping_add_signed(imm as i32); + + let result = state.read_word(target); + state.write_register(rd, result); + } + + pub(super) fn isa_load_pc(&mut self, rd: u8, imm: i16) { + // Slightly different from load_immediate_offset + // due to the target being forced word-aligned. + + let mut state = self.state.lock().unwrap(); + + log!("ldr r{rd}, [pc, {imm:#X}]"); + + let rn_value = state.read_register(0xF) & 0b11111111111111111111111111111100; + + let target = rn_value.wrapping_add_signed(imm as i32); + + let result = state.read_word(target); + state.write_register(rd, result); + } + + pub(super) fn isa_store_byte_immediate_offset(&mut self, rd: u8, rn: u8, imm: i16) { + log!("strb r{rd}, [r{rn}, {imm:#X}]"); + + let mut state = self.state.lock().unwrap(); + + let rn_value = state.read_register(rn); + + let target = rn_value.wrapping_add_signed(imm as i32); + + let result = state.read_register(rd) as u8; + state.write_byte(target, result); + } + + pub(super) fn isa_store_byte_register_offset(&mut self, rd: u8, rn: u8, rm: u8) { + log!("strb r{rd}, [r{rn}, r{rm}]"); + + let mut state = self.state.lock().unwrap(); + + let rn_value = state.read_register(rn); + let rm_value = state.read_register(rm); + + let target = rn_value.wrapping_add(rm_value); + + let result = state.read_register(rd) as u8; + state.write_byte(target, result); + } + + pub(super) fn isa_store_halfword(&mut self, rd: u8, rn: u8, imm: i16) { + log!("strh r{rd}, [r{rn}, {imm:#X}]"); + + let mut state = self.state.lock().unwrap(); + + let rn_value = state.read_register(rn); + + let target = rn_value.wrapping_add_signed(imm as i32); + + let result = state.read_register(rd) as u16; + state.write_halfword(target, result); + } + + pub(super) fn isa_store_immediate_offset(&mut self, rd: u8, rn: u8, imm: i16) { + log!("str r{rd}, [r{rn}, {imm:#X}]"); + + let mut state = self.state.lock().unwrap(); + + let rn_value = state.read_register(rn); + + let target = rn_value.wrapping_add_signed(imm as i32); + + let result = state.read_register(rd); + state.write_word(target, result); + } +} diff --git a/src/luma/cpu/move.rs b/src/luma/cpu/isa_move.rs index 8389b16..a9edac8 100644 --- a/src/luma/cpu/move.rs +++ b/src/luma/cpu/isa_move.rs @@ -25,58 +25,69 @@ use crate::luma::log; use crate::luma::cpu::Cpu; impl Cpu { - pub(super) fn move_immediate(&mut self, destination: u8, immediate: u32) { - log(&format!("mov r{destination}, {immediate:#X}")); + pub(super) fn isa_move_immediate(&mut self, rd: u8, immediate: u32) { + log!("mov r{rd}, {immediate:#X}"); let mut state = self.state.lock().unwrap(); - state.write_register(destination, immediate); + state.write_register(rd, immediate); } - pub(super) fn move_immediate_arithmetic_shift_right(&mut self, destination: u8, base: u8, shift: u8) { - log(&format!("mov r{destination}, r{base}, ASR {shift:#X}")); + pub(super) fn isa_move_immediate_arithmetic_shift_right(&mut self, rd: u8, base: u8, shift: u8) { + log!("mov r{rd}, r{base}, ASR {shift:#X}"); let mut state = self.state.lock().unwrap(); let base_value = state.read_register(base); let value = (base_value as i32).wrapping_shr(shift as u32) as u32; - state.write_register(destination, value); + state.write_register(rd, value); } - pub(super) fn move_immediate_logical_shift_left(&mut self, destination: u8, base: u8, shift: u8) { - log(&format!("mov r{destination}, r{base}, LSL {shift:#X}")); + pub(super) fn isa_move_immediate_logical_shift_left(&mut self, rd: u8, base: u8, shift: u8) { + log!("mov r{rd}, r{base}, LSL {shift:#X}"); let mut state = self.state.lock().unwrap(); let base_value = state.read_register(base); let value = base_value.wrapping_shl(shift as u32); - state.write_register(destination, value); + state.write_register(rd, value); } - pub(super) fn move_immediate_logical_shift_right(&mut self, destination: u8, base: u8, shift: u8) { - log(&format!("mov r{destination}, r{base}, LSR {shift:#X}")); + pub(super) fn isa_move_immediate_logical_shift_right(&mut self, rd: u8, base: u8, shift: u8) { + log!("mov r{rd}, r{base}, LSR {shift:#X}"); let mut state = self.state.lock().unwrap(); let base_value = state.read_register(base); let value = base_value.wrapping_shr(shift as u32); - state.write_register(destination, value); + state.write_register(rd, value); } - pub(super) fn move_register(&mut self, destination: u8, source: u8) { - log(&format!("mov r{destination}, r{source}")); + pub(super) fn isa_move_not_register(&mut self, rd: u8, rm: u8) { + log!("mvn r{rd}, r{rm}"); + + let mut state = self.state.lock().unwrap(); + + let rm_value = state.read_register(rm); + + let result = !rm_value; + state.write_register(rd, result); + } + + pub(super) fn isa_move_register(&mut self, rd: u8, source: u8) { + log!("mov r{rd}, r{source}"); let mut state = self.state.lock().unwrap(); let value = state.read_register(source); - state.write_register(destination, value); + state.write_register(rd, value); } - pub(super) fn move_register_arithmetic_shift_right(&mut self, destination: u8, base: u8, shift: u8) { - log(&format!("mov r{destination}, r{base}, ASR r{shift}")); + pub(super) fn isa_move_register_arithmetic_shift_right(&mut self, rd: u8, base: u8, shift: u8) { + log!("mov r{rd}, r{base}, ASR r{shift}"); let mut state = self.state.lock().unwrap(); @@ -84,11 +95,11 @@ impl Cpu { let shift_value = state.read_register(shift); let value = (base_value.wrapping_shr(shift_value)) as u32; - state.write_register(destination, value); + state.write_register(rd, value); } - pub(super) fn move_register_logical_shift_left(&mut self, destination: u8, base: u8, shift: u8) { - log(&format!("mov r{destination}, r{base}, LSL r{shift}")); + pub(super) fn isa_move_register_logical_shift_left(&mut self, rd: u8, base: u8, shift: u8) { + log!("mov r{rd}, r{base}, LSL r{shift}"); let mut state = self.state.lock().unwrap(); @@ -96,11 +107,11 @@ impl Cpu { let shift_value = state.read_register(shift); let value = base_value.wrapping_shl(shift_value); - state.write_register(destination, value); + state.write_register(rd, value); } - pub(super) fn move_register_logical_shift_right(&mut self, destination: u8, base: u8, shift: u8) { - log(&format!("mov r{destination}, r{base}, LSR r{shift}")); + pub(super) fn isa_move_register_logical_shift_right(&mut self, rd: u8, base: u8, shift: u8) { + log!("mov r{rd}, r{base}, LSR r{shift}"); let mut state = self.state.lock().unwrap(); @@ -108,6 +119,18 @@ impl Cpu { let shift_value = state.read_register(shift); let value = base_value.wrapping_shr(shift_value); - state.write_register(destination, value); + state.write_register(rd, value); + } + + pub(super) fn isa_move_register_rotate_right(&mut self, rd: u8, base: u8, rotate: u8) { + log!("mov r{rd}, r{base}, ROR r{rotate}"); + + let mut state = self.state.lock().unwrap(); + + let base_value = state.read_register(base); + let rotate_value = state.read_register(rotate); + + let value = base_value.rotate_right(rotate_value); + state.write_register(rd, value); } } diff --git a/src/luma/cpu/load.rs b/src/luma/cpu/load.rs deleted file mode 100644 index 39a6e49..0000000 --- a/src/luma/cpu/load.rs +++ /dev/null @@ -1,69 +0,0 @@ -/* - Copyright 2021-2023 Gabriel Jensen. - - This file is part of Luma. - - Luma is free software: you can redistribute it - and/or modify it under the terms of the GNU - Affero General Public License as published by - the Free Software Foundation, either version 3 - of the License, or (at your option) any later - version. - - Luma is distributed in the hope that it will be - useful, but WITHOUT ANY WARRANTY; without even - the implied warranty of MERCHANTABILITY or - FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Affero General Public License for more details. - - You should have received a copy of the GNU - Affero General Public License along with Luma. - If not, see <https://www.gnu.org/licenses/>. -*/ - -use crate::luma::log; -use crate::luma::cpu::Cpu; - -impl Cpu { - pub(super) fn load_halfword(&mut self, destination: u8, base: u8, offset: i16) { - let mut state = self.state.lock().unwrap(); - - log(&format!("ldrh r{destination}, [r{base}, {offset:#X}]")); - - let base_value = state.read_register(base); - - let target = base_value.wrapping_add_signed(offset as i32); - - let value = state.read_halfword(target) as u32; - state.write_register(destination, value); - } - - pub(super) fn load_immediate_offset(&mut self, destination: u8, base: u8, offset: i16) { - let mut state = self.state.lock().unwrap(); - - log(&format!("ldr r{destination}, [r{base}, {offset:#X}]")); - - let base_value = state.read_register(base); - - let target = base_value.wrapping_add_signed(offset as i32); - - let value = state.read_word(target); - state.write_register(destination, value); - } - - pub(super) fn load_pc(&mut self, destination: u8, offset: i16) { - // Slightly different from load_immediate_offset - // due to the target being forced word-aligned. - - let mut state = self.state.lock().unwrap(); - - log(&format!("ldr r{destination}, [pc, {offset:#X}]")); - - let base_value = state.read_register(0xF) & 0b11111111111111111111111111111100; - - let target = base_value.wrapping_add_signed(offset as i32); - - let value = state.read_word(target); - state.write_register(destination, value); - } -} diff --git a/src/luma/cpu/store.rs b/src/luma/cpu/store.rs deleted file mode 100644 index 6f073ab..0000000 --- a/src/luma/cpu/store.rs +++ /dev/null @@ -1,80 +0,0 @@ -/* - Copyright 2021-2023 Gabriel Jensen. - - This file is part of Luma. - - Luma is free software: you can redistribute it - and/or modify it under the terms of the GNU - Affero General Public License as published by - the Free Software Foundation, either version 3 - of the License, or (at your option) any later - version. - - Luma is distributed in the hope that it will be - useful, but WITHOUT ANY WARRANTY; without even - the implied warranty of MERCHANTABILITY or - FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Affero General Public License for more details. - - You should have received a copy of the GNU - Affero General Public License along with Luma. - If not, see <https://www.gnu.org/licenses/>. -*/ - -use crate::luma::log; -use crate::luma::cpu::Cpu; - -impl Cpu { - pub(super) fn store_byte_immediate_offset(&mut self, source: u8, base: u8, offset: i16) { - log(&format!("strb r{source}, [r{base}, {offset:#X}]")); - - let mut state = self.state.lock().unwrap(); - - let base_value = state.read_register(base); - - let target = base_value.wrapping_add_signed(offset as i32); - - let value = state.read_register(source) as u8; - state.write_byte(target, value); - } - - pub(super) fn store_byte_register_offset(&mut self, source: u8, base: u8, offset: u8) { - log(&format!("strb r{source}, [r{base}, r{offset}]")); - - let mut state = self.state.lock().unwrap(); - - let base_value = state.read_register(base); - let offset_value = state.read_register(offset); - - let target = base_value.wrapping_add(offset_value); - - let value = state.read_register(source) as u8; - state.write_byte(target, value); - } - - pub(super) fn store_halfword(&mut self, source: u8, base: u8, offset: i16) { - log(&format!("strh r{source}, [r{base}, {offset:#X}]")); - - let mut state = self.state.lock().unwrap(); - - let base_value = state.read_register(base); - - let target = base_value.wrapping_add_signed(offset as i32); - - let value = state.read_register(source) as u16; - state.write_halfword(target, value); - } - - pub(super) fn store_immediate_offset(&mut self, source: u8, base: u8, offset: i16) { - log(&format!("str r{source}, [r{base}, {offset:#X}]")); - - let mut state = self.state.lock().unwrap(); - - let base_value = state.read_register(base); - - let target = base_value.wrapping_add_signed(offset as i32); - - let value = state.read_register(source); - state.write_word(target, value); - } -} diff --git a/src/luma/cpu/subtract.rs b/src/luma/cpu/subtract.rs deleted file mode 100644 index 59267ca..0000000 --- a/src/luma/cpu/subtract.rs +++ /dev/null @@ -1,50 +0,0 @@ -/* - Copyright 2021-2023 Gabriel Jensen. - - This file is part of Luma. - - Luma is free software: you can redistribute it - and/or modify it under the terms of the GNU - Affero General Public License as published by - the Free Software Foundation, either version 3 - of the License, or (at your option) any later - version. - - Luma is distributed in the hope that it will be - useful, but WITHOUT ANY WARRANTY; without even - the implied warranty of MERCHANTABILITY or - FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Affero General Public License for more details. - - You should have received a copy of the GNU - Affero General Public License along with Luma. - If not, see <https://www.gnu.org/licenses/>. -*/ - -use crate::luma::log; -use crate::luma::cpu::Cpu; - -impl Cpu { - pub(super) fn subtract_immediate(&mut self, destination: u8, base: u8, immediate: u32) { - log(&format!("sub r{destination}, r{base}, {immediate:#X}")); - - let mut state = self.state.lock().unwrap(); - - let base_value = state.read_register(base); - - let value = base_value.wrapping_sub(immediate); - state.write_register(destination, value); - } - - pub(super) fn subtract_register(&mut self, destination: u8, base: u8, subtract: u8) { - log(&format!("sub r{destination}, r{base}, r{subtract}")); - - let mut state = self.state.lock().unwrap(); - - let base_value = state.read_register(base); - let subtract_value = state.read_register(subtract); - - let value = base_value.wrapping_sub(subtract_value); - state.write_register(destination, value); - } -} diff --git a/src/luma/cpu_handle.rs b/src/luma/cpu_handle.rs index f83ddd2..ceacd03 100644 --- a/src/luma/cpu_handle.rs +++ b/src/luma/cpu_handle.rs @@ -27,15 +27,15 @@ use std::sync::{Arc, Mutex}; use std::sync::atomic::AtomicBool; use std::thread::JoinHandle; +pub mod drop; pub mod dump_video; pub mod dump_palette; -pub mod kill; pub struct CpuHandle { state: Arc<Mutex<State>>, dead: Arc<AtomicBool>, - handle: JoinHandle<()>, + handle: Option<JoinHandle<()>>, } impl CpuHandle { @@ -49,7 +49,7 @@ impl CpuHandle { state: state, dead: dead, - handle: handle, + handle: Some(handle), }; } } diff --git a/src/luma/cpu_handle/kill.rs b/src/luma/cpu_handle/drop.rs index e89affa..52c2866 100644 --- a/src/luma/cpu_handle/kill.rs +++ b/src/luma/cpu_handle/drop.rs @@ -25,16 +25,13 @@ use crate::luma::cpu_handle::CpuHandle; use std::sync::atomic::Ordering; -impl CpuHandle { - #[must_use] - pub fn kill(self) -> Result<(), String> { +impl Drop for CpuHandle { + fn drop(&mut self) { eprintln!("got kill order"); - self.dead.store(true, Ordering::Relaxed); - self.handle.join().unwrap(); - - self.dead.store(false, Ordering::Relaxed); + let handle = self.handle.take().unwrap(); - return Ok(()); + self.dead.store(true, Ordering::Relaxed); + handle.join().unwrap(); } } diff --git a/src/luma/instruction.rs b/src/luma/instruction.rs index 96aa288..9de3d32 100644 --- a/src/luma/instruction.rs +++ b/src/luma/instruction.rs @@ -23,32 +23,44 @@ #[derive(Clone, Copy)] pub enum Instruction { + AddCarryRegister( u8, u8, u8), AddImmediate( u8, u8, u32), AddRegister( u8, u8, u8), + AndRegister( u8, u8, u8), + BitClearRegister( u8, u8, u8), Branch( i32), BranchExchange( u8), BranchLink( i32), BranchLinkPrefix( i32), BranchLinkSuffix( i32), CompareImmediate( u8, u32), + CompareNegativeRegister( u8, u8), CompareRegister( u8, u8), + ExclusiveOrRegister( u8, u8, u8), LoadHalfword( u8, u8, i16), LoadImmediateOffset( u8, u8, i16), LoadPc( u8, i16), + LogicalOrRegister( u8, u8, u8), MoveImmediate( u8, u32), MoveImmediateArithmeticShiftRight( u8, u8, u8), MoveImmediateLogicalShiftLeftImmediate( u8, u8, u8), MoveImmediateLogicalShiftRightImmediate(u8, u8, u8), + MoveNotRegister( u8, u8), MoveRegister( u8, u8), MoveRegisterArithmeticShiftRight( u8, u8, u8), MoveRegisterLogicalShiftLeftImmediate( u8, u8, u8), MoveRegisterLogicalShiftRightImmediate( u8, u8, u8), + MoveRegisterRotateRight( u8, u8, u8), + MultiplyRegister( u8, u8, u8), + ReverseSubtractImmediate( u8, u8, u32), StoreByteImmediateOffset( u8, u8, i16), StoreByteRegisterOffset( u8, u8, u8), StoreHalfword( u8, u8, i16), StoreImmediateOffset( u8, u8, i16), + SubtractCarryRegister( u8, u8, u8), SubtractImmediate( u8, u8, u32), SubtractRegister( u8, u8, u8), + TestRegister( u8, u8), Undefined, } diff --git a/src/luma/state.rs b/src/luma/state.rs index 1836573..1b293ef 100644 --- a/src/luma/state.rs +++ b/src/luma/state.rs @@ -21,12 +21,13 @@ If not, see <https://www.gnu.org/licenses/>. */ -use crate::luma::{Error, log}; -use crate::luma::{BOOTLOADER_LENGTH, IMAGE_LENGTH, MEMORY_LENGTH, PALETTE_LENGTH, VIDEO_LENGTH}; +use crate::luma::{BOOTLOADER_LENGTH, IMAGE_LENGTH, PALETTE_LENGTH, VIDEO_LENGTH}; use std::slice::{from_raw_parts, from_raw_parts_mut}; pub mod new; +pub mod read; +pub mod write; pub struct State { registers: [u32; 0x10], @@ -35,105 +36,23 @@ pub struct State { memory: Vec::<u32>, } -impl State { - #[inline(always)] - #[must_use] - pub fn read_register(&self, register: u8) -> u32 { - // Limit to 0..=15. - let index = (register & 0b00001111) as usize; - - return unsafe { *self.registers.get_unchecked(index) }; - } - - #[inline(always)] - pub fn write_register(&mut self, register: u8, value: u32) { - log(&format!("* r{register} = {value:#010X}")); - - let index = (register & 0b00001111) as usize; - - unsafe { *self.registers.get_unchecked_mut(index) = value }; - } - - #[must_use] - pub fn read_word(&self, address: u32) -> u32 { - if address > MEMORY_LENGTH - 0x4 { Error::OutOfBounds( address).trap(); return 0x00000000; } - if address % 0x4 != 0x0 { Error::BadAlignment(address, 0x4).trap(); return 0x00000000; } - - unsafe { - let pointer = (self.memory.as_ptr() as *const u8).add(address as usize) as *const u32; - return *pointer; +macro_rules! address_unused { + ($address: expr) => {{ + match $address { + 0x02040000..=0x02FFFFFF => true, + 0x03008000..=0x03FFFFFF => true, + 0x04000400..=0x04FFFFFF => true, + 0x05000400..=0x05FFFFFF => true, + 0x06018000..=0x06FFFFFF => true, + 0x07000400..=0x07FFFFFF => true, + + _ => false, } - } - - pub fn write_word(&mut self, address: u32, value: u32) { - log(&format!("* {address:#010X} = {value:#010X}")); - - if address > MEMORY_LENGTH - 0x4 { Error::OutOfBounds( address).trap(); return; } - if address % 0x4 != 0x0 { Error::BadAlignment(address, 0x4).trap(); return; } - - unsafe { - let pointer = (self.memory.as_mut_ptr() as *mut u8).add(address as usize) as *mut u32; - *pointer = value; - } - } - - #[must_use] - pub fn read_halfword(&self, address: u32) -> u16 { - if address > MEMORY_LENGTH - 0x2 { Error::OutOfBounds( address).trap(); return 0x0000; } - if address % 0x2 != 0x0 { Error::BadAlignment(address, 0x2).trap(); return 0x0000; } - - unsafe { - let pointer = (self.memory.as_ptr() as *const u8).add(address as usize) as *const u16; - return *pointer; - } - } - - pub fn write_halfword(&mut self, address: u32, value: u16) { - log(&format!("* {address:#010X} = {value:#010X}")); - - if address > MEMORY_LENGTH - 0x2 { Error::OutOfBounds( address).trap(); return; } - if address % 0x2 != 0x0 { Error::BadAlignment(address, 0x2).trap(); return; } - - unsafe { - let pointer = (self.memory.as_mut_ptr() as *mut u8).add(address as usize) as *mut u16; - *pointer = value; - } - } - - #[must_use] - pub fn read_byte(&self, address: u32) -> u8 { - if address > MEMORY_LENGTH - 0x1 { Error::OutOfBounds(address).trap(); return 0x00; } - - unsafe { - let pointer = (self.memory.as_ptr() as *const u8).add(address as usize); - return *pointer; - } - } - - pub fn write_byte(&mut self, address: u32, value: u8) { - log(&format!("* {address:#010X} = {value:#010X}")); - - if address > MEMORY_LENGTH - 0x1 { Error::OutOfBounds(address).trap(); return; } - - unsafe { - let pointer = (self.memory.as_mut_ptr() as *mut u8).add(address as usize); - *pointer = value; - } - } - - #[inline(always)] - #[must_use] - pub fn read_cpsr(&self) -> u32 { - return self.cpsr; - } - - #[inline(always)] - pub fn write_cpsr(&mut self, value: u32) { - log(&format!("* cpsr = {value:#034b}")); - - self.cpsr = value; - } + }}; +} +pub(crate) use address_unused; +impl State { #[must_use] pub fn video8<'a>(&'a self) -> &'a [u8] { let slice = unsafe { diff --git a/src/luma/state/read.rs b/src/luma/state/read.rs new file mode 100644 index 0000000..f22dc86 --- /dev/null +++ b/src/luma/state/read.rs @@ -0,0 +1,74 @@ +/* + Copyright 2021-2023 Gabriel Jensen. + + This file is part of Luma. + + Luma is free software: you can redistribute it + and/or modify it under the terms of the GNU + Affero General Public License as published by + the Free Software Foundation, either version 3 + of the License, or (at your option) any later + version. + + Luma is distributed in the hope that it will be + useful, but WITHOUT ANY WARRANTY; without even + the implied warranty of MERCHANTABILITY or + FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Affero General Public License for more details. + + You should have received a copy of the GNU + Affero General Public License along with Luma. + If not, see <https://www.gnu.org/licenses/>. +*/ + +use crate::luma::{Error, MEMORY_LENGTH}; +use crate::luma::state::State; + +impl State { + #[inline(always)] + #[must_use] + pub fn read_register(&self, register: u8) -> u32 { + // Limit to 0..=15. + let index = (register & 0b00001111) as usize; + + return unsafe { *self.registers.get_unchecked(index) }; + } + + #[must_use] + pub fn read_word(&self, address: u32) -> u32 { + if address > MEMORY_LENGTH - 0x4 { Error::OutOfBounds( address).trap(); return 0x00000000; } + if address % 0x4 != 0x0 { Error::BadAlignment(address, 0x4).trap(); return 0x00000000; } + + unsafe { + let pointer = (self.memory.as_ptr() as *const u8).add(address as usize) as *const u32; + return *pointer; + } + } + + #[must_use] + pub fn read_halfword(&self, address: u32) -> u16 { + if address > MEMORY_LENGTH - 0x2 { Error::OutOfBounds( address).trap(); return 0x0000; } + if address % 0x2 != 0x0 { Error::BadAlignment(address, 0x2).trap(); return 0x0000; } + + unsafe { + let pointer = (self.memory.as_ptr() as *const u8).add(address as usize) as *const u16; + return *pointer; + } + } + + #[must_use] + pub fn read_byte(&self, address: u32) -> u8 { + if address > MEMORY_LENGTH - 0x1 { Error::OutOfBounds(address).trap(); return 0x00; } + + unsafe { + let pointer = (self.memory.as_ptr() as *const u8).add(address as usize); + return *pointer; + } + } + + #[inline(always)] + #[must_use] + pub fn read_cpsr(&self) -> u32 { + return self.cpsr; + } +} diff --git a/src/luma/state/write.rs b/src/luma/state/write.rs new file mode 100644 index 0000000..bd2b031 --- /dev/null +++ b/src/luma/state/write.rs @@ -0,0 +1,116 @@ +/* + Copyright 2021-2023 Gabriel Jensen. + + This file is part of Luma. + + Luma is free software: you can redistribute it + and/or modify it under the terms of the GNU + Affero General Public License as published by + the Free Software Foundation, either version 3 + of the License, or (at your option) any later + version. + + Luma is distributed in the hope that it will be + useful, but WITHOUT ANY WARRANTY; without even + the implied warranty of MERCHANTABILITY or + FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Affero General Public License for more details. + + You should have received a copy of the GNU + Affero General Public License along with Luma. + If not, see <https://www.gnu.org/licenses/>. +*/ + +use crate::luma::{Error, log_assignment, MEMORY_LENGTH}; +use crate::luma::state::{address_unused, State}; + +macro_rules! read_only { + ($address: expr) => {{ + match $address { + 0x00000000..=0x00003FFF => true, + 0x04000130..=0x04000131 => true, // KEYINPUT + 0x08000000..=0x0DFFFFFF => true, + + _ => false, + } + }}; +} + +impl State { + #[inline(always)] + pub fn write_register(&mut self, register: u8, value: u32) { + log_assignment!("r{register}", "{value:#010X}"); + + let index = (register & 0b00001111) as usize; + + unsafe { *self.registers.get_unchecked_mut(index) = value }; + } + + pub fn write_word(&mut self, address: u32, value: u32) { + log_assignment!("{address:#010X}", "{value:#010X}"); + + if address > MEMORY_LENGTH - 0x4 { Error::OutOfBounds( address).trap(); return; } + if address % 0x4 != 0x0 { Error::BadAlignment(address, 0x4).trap(); return; } + + if read_only!(address) || address_unused!(address) { return }; + + unsafe { + let pointer = (self.memory.as_mut_ptr() as *mut u8).add(address as usize) as *mut u32; + *pointer = value; + } + } + + pub fn write_halfword(&mut self, address: u32, value: u16) { + log_assignment!("{address:#010X}", "{value:#06X}"); + + if address > MEMORY_LENGTH - 0x2 { Error::OutOfBounds( address).trap(); return; } + if address % 0x2 != 0x0 { Error::BadAlignment(address, 0x2).trap(); return; } + + if read_only!(address) || address_unused!(address) { return }; + + unsafe { + let pointer = (self.memory.as_mut_ptr() as *mut u8).add(address as usize) as *mut u16; + *pointer = value; + } + } + + pub fn write_byte(&mut self, address: u32, value: u8) { + log_assignment!("{address:#010X}", "{value:#04X}"); + + if address > MEMORY_LENGTH - 0x1 { Error::OutOfBounds(address).trap(); return; } + + if read_only!(address) || address_unused!(address) { return }; + + let memory = self.memory.as_mut_ptr() as *mut u8; + + match address { + // Extend to halfwords: + | 0x05000000..=0x050003FF + | 0x06000000..=0x06017FFF + | 0x07000000..=0x070003FF => { + // Align to halfwords. + let address = address & 0b11111111111111111111111111111110; + + // Repeat value. + let value = value as u16 | (value as u16) << 0x8; + + unsafe { + let pointer = memory.add(address as usize) as *mut u16; + *pointer = value; + } + }, + + // Bytes are allowed: + _ => unsafe { + let pointer = memory.add(address as usize); + *pointer = value; + }, + }; + } + + pub fn write_cpsr(&mut self, value: u32) { + log_assignment!("cpsr", "{value:#034b}"); + + self.cpsr = value; + } +} @@ -111,23 +111,25 @@ start: @ - r3 is the last pixel address. ldr r0, .videoAddr ldr r1, .paletteIndex - movs r2, 0x0 + bics r2, r2 movs r3, 0x4B lsls r3, 0x9 adds r3, r0 @ Plot pixels: + @ TO-DO: Plot correctly (bytewise). .loop: strb r1, [r0] adds r0, r2 adds r2, 0x1 cmp r0, r3 - bge .stop @ Stop if we've reached the end. + bge .restart b .loop @ Repeat loop. - @ Stop: -.stop: - b .stop + @ Restart loop: +.restart: + ldr r0, .videoAddr + b .loop .endfunc .align |