From 08bfb19b97d1f3267bd7ec3a66476815de4e4752 Mon Sep 17 00:00:00 2001 From: Gabriel Bjørnager Jensen Date: Fri, 17 Nov 2023 20:25:22 +0100 Subject: Add logo; Implement exceptions (including banked registers); Implement software interrupts; Add custom bootloader; Update readme; Support condition-setting instructions; Implement all data-processing instructions; Support conditional execution (predicates); Rework shifter instructions; Rework pipeline; Rework file structure; Update gitignore; Update naming convention; --- .gitignore | 3 + CHANGELOG.md | 16 ++ Cargo.toml | 4 +- README.md | 2 +- bootloader.ld | 12 ++ bootloader.s | 80 +++++++++ luma.svg | 27 ++++ make_test.sh | 12 +- src/agb.rs | 25 +++ src/app.rs | 47 ++++++ src/app/check_events.rs | 52 ++++++ src/app/draw_video.rs | 71 ++++++++ src/app/init.rs | 88 ++++++++++ src/app/load.rs | 96 +++++++++++ src/app/main.rs | 69 ++++++++ src/app/run.rs | 58 +++++++ src/app/sync_video.rs | 44 +++++ src/configuration.rs | 36 +++++ src/configuration/load.rs | 100 ++++++++++++ src/configuration/validate.rs | 32 ++++ src/cpu.rs | 104 ++++++++++++ src/cpu/alu_exit_exception.rs | 38 +++++ src/cpu/boot.rs | 70 ++++++++ src/cpu/continue.rs | 33 ++++ src/cpu/enter_exception.rs | 59 +++++++ src/cpu/exchange.rs | 45 ++++++ src/cpu/execute.rs | 69 ++++++++ src/cpu/exit_exception.rs | 42 +++++ src/cpu/fetch_arm.rs | 55 +++++++ src/cpu/fetch_thumb.rs | 54 +++++++ src/cpu/isa_adc.rs | 58 +++++++ src/cpu/isa_add.rs | 55 +++++++ src/cpu/isa_and.rs | 53 ++++++ src/cpu/isa_b.rs | 38 +++++ src/cpu/isa_bic.rs | 53 ++++++ src/cpu/isa_bl.rs | 62 +++++++ src/cpu/isa_bx.rs | 45 ++++++ src/cpu/isa_cmn.rs | 47 ++++++ src/cpu/isa_cmp.rs | 47 ++++++ src/cpu/isa_eor.rs | 53 ++++++ src/cpu/isa_memory.rs | 122 ++++++++++++++ src/cpu/isa_mov.rs | 53 ++++++ src/cpu/isa_mul.rs | 52 ++++++ src/cpu/isa_mvn.rs | 52 ++++++ src/cpu/isa_orr.rs | 53 ++++++ src/cpu/isa_rsb.rs | 55 +++++++ src/cpu/isa_rsc.rs | 58 +++++++ src/cpu/isa_sbc.rs | 58 +++++++ src/cpu/isa_sub.rs | 55 +++++++ src/cpu/isa_swi.rs | 34 ++++ src/cpu/isa_teq.rs | 45 ++++++ src/cpu/isa_tst.rs | 45 ++++++ src/cpu/sync_cycle.rs | 43 +++++ src/cpu/take_state.rs | 36 +++++ src/cpu_handle.rs | 55 +++++++ src/cpu_handle/drop.rs | 37 +++++ src/cpu_handle/dump_palette.rs | 36 +++++ src/cpu_handle/dump_video.rs | 34 ++++ src/cpu_mode.rs | 66 ++++++++ src/exception.rs | 64 ++++++++ src/instruction.rs | 65 ++++++++ src/instruction/decode_arm.rs | 201 +++++++++++++++++++++++ src/instruction/decode_thumb.rs | 267 ++++++++++++++++++++++++++++++ src/luma.rs | 30 +++- src/luma/agb.rs | 25 --- src/luma/app.rs | 46 ------ src/luma/app/check_events.rs | 52 ------ src/luma/app/draw_video.rs | 71 -------- src/luma/app/init.rs | 88 ---------- src/luma/app/load.rs | 96 ----------- src/luma/app/run.rs | 58 ------- src/luma/app/sync_video.rs | 44 ----- src/luma/configuration.rs | 36 ----- src/luma/configuration/load.rs | 100 ------------ src/luma/configuration/validate.rs | 32 ---- src/luma/cpu.rs | 78 --------- src/luma/cpu/boot.rs | 131 --------------- src/luma/cpu/continue.rs | 33 ---- src/luma/cpu/decode_arm.rs | 258 ----------------------------- src/luma/cpu/decode_thumb.rs | 312 ------------------------------------ src/luma/cpu/exchange.rs | 39 ----- src/luma/cpu/isa_arithmetic.rs | 122 -------------- src/luma/cpu/isa_bitwise.rs | 75 --------- src/luma/cpu/isa_branch.rs | 89 ---------- src/luma/cpu/isa_logic.rs | 111 ------------- src/luma/cpu/isa_memory.rs | 122 -------------- src/luma/cpu/isa_move.rs | 136 ---------------- src/luma/cpu/test_predicate.rs | 74 --------- src/luma/cpu_handle.rs | 55 ------- src/luma/cpu_handle/drop.rs | 37 ----- src/luma/cpu_handle/dump_palette.rs | 36 ----- src/luma/cpu_handle/dump_video.rs | 34 ---- src/luma/instruction.rs | 66 -------- src/luma/state.rs | 103 ------------ src/luma/state/new.rs | 60 ------- src/luma/state/read.rs | 74 --------- src/luma/state/write.rs | 116 -------------- src/main.rs | 74 --------- src/predicate.rs | 101 ++++++++++++ src/shifter.rs | 65 ++++++++ src/shifter/extract.rs | 92 +++++++++++ src/state.rs | 126 +++++++++++++++ src/state/bank.rs | 89 ++++++++++ src/state/new.rs | 75 +++++++++ src/state/read.rs | 85 ++++++++++ src/state/shifter_value.rs | 130 +++++++++++++++ src/state/write.rs | 125 +++++++++++++++ test.s | 111 +++++++------ 108 files changed, 4505 insertions(+), 2947 deletions(-) create mode 100644 bootloader.ld create mode 100644 bootloader.s create mode 100644 luma.svg create mode 100644 src/agb.rs create mode 100644 src/app.rs create mode 100644 src/app/check_events.rs create mode 100644 src/app/draw_video.rs create mode 100644 src/app/init.rs create mode 100644 src/app/load.rs create mode 100644 src/app/main.rs create mode 100644 src/app/run.rs create mode 100644 src/app/sync_video.rs create mode 100644 src/configuration.rs create mode 100644 src/configuration/load.rs create mode 100644 src/configuration/validate.rs create mode 100644 src/cpu.rs create mode 100644 src/cpu/alu_exit_exception.rs create mode 100644 src/cpu/boot.rs create mode 100644 src/cpu/continue.rs create mode 100644 src/cpu/enter_exception.rs create mode 100644 src/cpu/exchange.rs create mode 100644 src/cpu/execute.rs create mode 100644 src/cpu/exit_exception.rs create mode 100644 src/cpu/fetch_arm.rs create mode 100644 src/cpu/fetch_thumb.rs create mode 100644 src/cpu/isa_adc.rs create mode 100644 src/cpu/isa_add.rs create mode 100644 src/cpu/isa_and.rs create mode 100644 src/cpu/isa_b.rs create mode 100644 src/cpu/isa_bic.rs create mode 100644 src/cpu/isa_bl.rs create mode 100644 src/cpu/isa_bx.rs create mode 100644 src/cpu/isa_cmn.rs create mode 100644 src/cpu/isa_cmp.rs create mode 100644 src/cpu/isa_eor.rs create mode 100644 src/cpu/isa_memory.rs create mode 100644 src/cpu/isa_mov.rs create mode 100644 src/cpu/isa_mul.rs create mode 100644 src/cpu/isa_mvn.rs create mode 100644 src/cpu/isa_orr.rs create mode 100644 src/cpu/isa_rsb.rs create mode 100644 src/cpu/isa_rsc.rs create mode 100644 src/cpu/isa_sbc.rs create mode 100644 src/cpu/isa_sub.rs create mode 100644 src/cpu/isa_swi.rs create mode 100644 src/cpu/isa_teq.rs create mode 100644 src/cpu/isa_tst.rs create mode 100644 src/cpu/sync_cycle.rs create mode 100644 src/cpu/take_state.rs create mode 100644 src/cpu_handle.rs create mode 100644 src/cpu_handle/drop.rs create mode 100644 src/cpu_handle/dump_palette.rs create mode 100644 src/cpu_handle/dump_video.rs create mode 100644 src/cpu_mode.rs create mode 100644 src/exception.rs create mode 100644 src/instruction.rs create mode 100644 src/instruction/decode_arm.rs create mode 100644 src/instruction/decode_thumb.rs delete mode 100644 src/luma/agb.rs delete mode 100644 src/luma/app.rs delete mode 100644 src/luma/app/check_events.rs delete mode 100644 src/luma/app/draw_video.rs delete mode 100644 src/luma/app/init.rs delete mode 100644 src/luma/app/load.rs delete mode 100644 src/luma/app/run.rs delete mode 100644 src/luma/app/sync_video.rs delete mode 100644 src/luma/configuration.rs delete mode 100644 src/luma/configuration/load.rs delete mode 100644 src/luma/configuration/validate.rs delete mode 100644 src/luma/cpu.rs delete mode 100644 src/luma/cpu/boot.rs delete mode 100644 src/luma/cpu/continue.rs delete mode 100644 src/luma/cpu/decode_arm.rs delete mode 100644 src/luma/cpu/decode_thumb.rs delete mode 100644 src/luma/cpu/exchange.rs delete mode 100644 src/luma/cpu/isa_arithmetic.rs delete mode 100644 src/luma/cpu/isa_bitwise.rs delete mode 100644 src/luma/cpu/isa_branch.rs delete mode 100644 src/luma/cpu/isa_logic.rs delete mode 100644 src/luma/cpu/isa_memory.rs delete mode 100644 src/luma/cpu/isa_move.rs delete mode 100644 src/luma/cpu/test_predicate.rs delete mode 100644 src/luma/cpu_handle.rs delete mode 100644 src/luma/cpu_handle/drop.rs delete mode 100644 src/luma/cpu_handle/dump_palette.rs delete mode 100644 src/luma/cpu_handle/dump_video.rs delete mode 100644 src/luma/instruction.rs delete mode 100644 src/luma/state.rs delete mode 100644 src/luma/state/new.rs delete mode 100644 src/luma/state/read.rs delete mode 100644 src/luma/state/write.rs delete mode 100644 src/main.rs create mode 100644 src/predicate.rs create mode 100644 src/shifter.rs create mode 100644 src/shifter/extract.rs create mode 100644 src/state.rs create mode 100644 src/state/bank.rs create mode 100644 src/state/new.rs create mode 100644 src/state/read.rs create mode 100644 src/state/shifter_value.rs create mode 100644 src/state/write.rs diff --git a/.gitignore b/.gitignore index 61bf874..d11d9bf 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,8 @@ *.agb +*.bin +*.dmg *.elf +*.gba *.o *.sav vgcore.* diff --git a/CHANGELOG.md b/CHANGELOG.md index f361f14..10ab25c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,19 @@ +# 0.2F + +* Add logo +* Implement exceptions (including banked registers) +* Implement software interrupts +* Add custom bootloader +* Update readme +* Support condition-setting instructions +* Implement all data-processing instructions +* Support conditional execution (predicates) +* Rework shifter instructions +* Rework pipeline +* Rework file structure +* Update gitignore +* Update naming convention + # 0.2E * Update readme diff --git a/Cargo.toml b/Cargo.toml index 5ae95fa..cb185d6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "luma" -version = "0.45.0" +version = "0.47.0" authors = ["Gabriel Jensen"] edition = "2021" description = "AGB emulator." @@ -11,7 +11,7 @@ categories = ["emulators"] [[bin]] name = "luma" -path = "src/main.rs" +path = "src/luma.rs" [profile.release] codegen-units = 1 diff --git a/README.md b/README.md index e7c525d..4c42939 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Luma -luma is an emulator for the AGB—Game Boy Advance platform. +Luma is an emulator for the AGB—Game Boy Advance platform. # Usage diff --git a/bootloader.ld b/bootloader.ld new file mode 100644 index 0000000..c9e5ffe --- /dev/null +++ b/bootloader.ld @@ -0,0 +1,12 @@ +OUTPUT_ARCH(arm); + +MEMORY { + bios : ORIGIN = 0x00000000, LENGTH = 0x00004000 +}; + +SECTIONS { + .bss : {*(.bss*)} > bios + .data : {*(.data*)} > bios + .text : {*(.text*)} > bios + .rodata : {*(.rodata*)} > bios +}; diff --git a/bootloader.s b/bootloader.s new file mode 100644 index 0000000..e92e916 --- /dev/null +++ b/bootloader.s @@ -0,0 +1,80 @@ +.cpu arm7tdmi + +.arm +b reset + +.arm +b undefined_instruction + +.arm +b software_interrupt + +.arm +b prefetch_abort + +.arm +b data_abort + +.arm +b interrupt_request + +.arm +b fast_interrupt_request + +.func +reset: + ldr lr, .image_entry_point + bx lr + +.endfunc + +.align +.image_entry_point: + .word 0x08000000 + +.func +undefined_instruction: + b undefined_instruction +.endfunc + +.func +software_interrupt: + ldr r0, =.software_interrupt_impl + mov r1, pc + bx r0 + movs pc, lr +.endfunc + +.thumb + +.func +.thumb_func +.software_interrupt_impl: + mov r0, lr + sub r0, #0x4 + ldr r0, [r0] + bx r1 + +.endfunc + +.arm + +.func +prefetch_abort: + b prefetch_abort +.endfunc + +.func +data_abort: + b data_abort +.endfunc + +.func +interrupt_request: + b interrupt_request +.endfunc + +.func +fast_interrupt_request: + b fast_interrupt_request +.endfunc diff --git a/luma.svg b/luma.svg new file mode 100644 index 0000000..5b733ec --- /dev/null +++ b/luma.svg @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/make_test.sh b/make_test.sh index e92a652..7403f3c 100755 --- a/make_test.sh +++ b/make_test.sh @@ -1,14 +1,18 @@ #!/usr/bin/env sh -echo Making object file... +echo "Making object files..." +arm-none-eabi-as -obootloader.o bootloader.s arm-none-eabi-as -otest.o test.s -echo Making binary... +echo "Making binaries..." arm-none-eabi-ld -Ttest.ld -otest.elf test.o +arm-none-eabi-ld -Tbootloader.ld -obootloader.elf bootloader.o -echo Stripping binary... +echo "Stripping binary..." +arm-none-eabi-strip --strip-debug --strip-unneeded bootloader.elf arm-none-eabi-strip --strip-debug --strip-unneeded test.elf +arm-none-eabi-objcopy -Obinary bootloader.elf bootloader.bin arm-none-eabi-objcopy -Obinary test.elf test.agb -echo Patching header... +echo "Patching header (test)..." agbsum -psitest.agb diff --git a/src/agb.rs b/src/agb.rs new file mode 100644 index 0000000..01a77b8 --- /dev/null +++ b/src/agb.rs @@ -0,0 +1,25 @@ +/* + 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 . +*/ + +pub mod arm; +pub mod thumb; diff --git a/src/app.rs b/src/app.rs new file mode 100644 index 0000000..45b6e90 --- /dev/null +++ b/src/app.rs @@ -0,0 +1,47 @@ +/* + 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 . +*/ + +use sdl2::Sdl; +use sdl2::render::WindowCanvas; +use std::sync::Arc; +use std::sync::atomic::AtomicBool; + +pub mod check_events; +pub mod draw_video; +pub mod init; +pub mod load; +pub mod main; +pub mod run; +pub mod sync_video; + +pub struct App { + bootloader: String, + image: String, + + scale: u32, + + got_terminate: Arc::, + + sdl: Sdl, + canvas: WindowCanvas, +} diff --git a/src/app/check_events.rs b/src/app/check_events.rs new file mode 100644 index 0000000..1dd9551 --- /dev/null +++ b/src/app/check_events.rs @@ -0,0 +1,52 @@ +/* + 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 . +*/ + +use crate::app::App; + +use sdl2::event::Event; +use std::sync::atomic::Ordering; + +impl App { + pub fn check_events(&mut self) -> Result { + // Return true if we should quit. + + let mut event_pump = match self.sdl.event_pump() { + Ok(pump) => pump, + _ => return Err("unable to get event pump".to_string()), + }; + + if self.got_terminate.load(Ordering::Relaxed) { + eprintln!("got terminate"); + return Ok(true) + }; + + for event in event_pump.poll_iter() { + match event { + Event::Quit {..} => return Ok(true), + _ => {}, + }; + } + + return Ok(false); + } +} diff --git a/src/app/draw_video.rs b/src/app/draw_video.rs new file mode 100644 index 0000000..a0d2016 --- /dev/null +++ b/src/app/draw_video.rs @@ -0,0 +1,71 @@ +/* + 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 . +*/ + +use crate::SCREEN_SIZE; +use crate::app::App; + +use sdl2::pixels::Color; +use sdl2::rect::Rect; + +impl App { + pub fn draw_video(&mut self, video: &[u8], agb_palette: &[u16]) { + // TO-DO: Honour video mode. + + let mut palette: [Color; 0x100] = [Color::RGB(0x00, 0x00, 0x00); 0x100]; + + for (index, element) in palette.iter_mut().enumerate() { + let value = unsafe { *agb_palette.get_unchecked(index) }; + + let colour = decode_colour(value); + *element = colour; + } + + for pixel_y in 0x0..SCREEN_SIZE.1 { + for pixel_x in 0x0..SCREEN_SIZE.0 { + let pixel = pixel_y as usize * SCREEN_SIZE.0 as usize + pixel_x as usize; + + let value = video[pixel]; + let colour = palette[value as usize]; + self.canvas.set_draw_color(colour); + + let square = Rect::new( + (pixel_x as u32 * self.scale) as i32, + (pixel_y as u32 * self.scale) as i32, + self.scale, + self.scale, + ); + self.canvas.fill_rect(square).unwrap(); + } + } + + 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/app/init.rs b/src/app/init.rs new file mode 100644 index 0000000..c7a8653 --- /dev/null +++ b/src/app/init.rs @@ -0,0 +1,88 @@ +/* + 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 . +*/ + +use crate::{SCREEN_SIZE, VERSION}; +use crate::app::App; +use crate::configuration::Configuration; + +use sdl2::pixels::Color; +use sdl2::render::BlendMode; +use std::sync::Arc; +use std::sync::atomic::{AtomicBool, Ordering}; + +impl App { + pub fn init(configuration: Configuration) -> Result { + let got_terminate = Arc::new(AtomicBool::new(false)); + + match ctrlc::set_handler({ + let got_terminate = got_terminate.clone(); + move || got_terminate.store(true, Ordering::Relaxed) + }) { + Err(..) => return Err("unable to set signal handler".to_string()), + _ => {}, + }; + + let sdl = match sdl2::init() { + Ok( sdl) => sdl, + Err(..) => return Err("unable to initialise sdl2".to_string()), + }; + + let sdl_video = match sdl.video() { + Ok( video) => video, + Err(..) => return Err("unable to initialise video".to_string()), + }; + + let window_title = format!("Luma {:X}.{:X}", VERSION.0, VERSION.1); + + let mut window_builder = sdl_video.window(&window_title, SCREEN_SIZE.0 as u32 * configuration.scale, SCREEN_SIZE.1 as u32 * configuration.scale); + window_builder.position_centered(); + + let window = match window_builder.build() { + Ok( window) => window, + Err(..) => return Err("unable to open window".to_string()), + }; + + let mut canvas = match window.into_canvas().build() { + Ok( canvas) => canvas, + Err(..) => return Err("unable to build canvas".to_string()), + }; + + canvas.set_blend_mode(BlendMode::Blend); + + let clear_colour = Color::RGB(0x00, 0x00, 0x00); + canvas.set_draw_color(clear_colour); + canvas.clear(); + canvas.present(); + + return Ok(App { + bootloader: configuration.bootloader, + image: configuration.image, + + scale: configuration.scale, + + got_terminate: got_terminate, + + sdl: sdl, + canvas: canvas, + }); + } +} diff --git a/src/app/load.rs b/src/app/load.rs new file mode 100644 index 0000000..24a2385 --- /dev/null +++ b/src/app/load.rs @@ -0,0 +1,96 @@ +/* + 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 . +*/ + +use crate::VERSION; +use crate::app::App; +use crate::state::State; + +use std::fs::File; +use std::io::Read; + +impl App { + pub fn load(&mut self, state: &mut State) -> Result<(), String> { + eprintln!("loading booatloader \"{}\"", self.bootloader); + + let mut bootloader = match File::open(&self.bootloader) { + Ok(file) => file, + _ => return Err("unable to open bootloader".to_string()), + }; + + if let Err(..) = bootloader.read(state.bootloader_buffer()) { return Err("unable to read bootloader".to_string()) }; + + eprintln!("loading image \"{}\"", self.image); + + let mut image = match File::open(&self.image) { + Ok(file) => file, + _ => return Err("unable to open image".to_string()), + }; + + match image.read(state.image_buffer()) { + Err(..) => return Err("unable to read image".to_string()), + _ => {}, + }; + + let title = get_title(&state.image_buffer()[0xA0..0xAC]); + let id = get_id(&state.image_buffer()[0xAC..0xB0]); + let version = state.image_buffer()[0xBC]; + + eprintln!("loaded image \"{title}\" ({id}) v.{version}"); + + self.canvas.window_mut().set_title(&format!("Luma {:X}.{:X} - {title}", VERSION.0, VERSION.1)).unwrap(); + + return Ok(()); + } +} + +fn get_title(data: &[u8]) -> String { + let mut title = String::with_capacity(0xC); + + for raw in data { + let character = match char::from_u32(*raw as u32) { + Some('\u{0000}') => break, + Some(character) => character, + None => '?', + }; + + title.push(character); + } + + return title; +} + +fn get_id(data: &[u8]) -> String { + let mut id = String::with_capacity(0xC); + + for raw in data { + let character = match char::from_u32(*raw as u32) { + Some('\u{0000}') => break, + Some(character) => character, + None => '?', + }; + + id.push(character); + } + + return id; +} diff --git a/src/app/main.rs b/src/app/main.rs new file mode 100644 index 0000000..6391716 --- /dev/null +++ b/src/app/main.rs @@ -0,0 +1,69 @@ +/* + 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 . +*/ + +use crate::VERSION; +use crate::app::App; +use crate::configuration::Configuration; + +use std::env::{args, var}; +use std::process::exit; + +impl App { + pub fn main() { + println!("\u{1B}[1mluma\u{1B}[0m {:X}.{:X}", VERSION.0, VERSION.1); + println!("Copyright 2021-2023 \u{1B}[1mGabriel Bj\u{F8}rnager Jensen\u{1B}[0m."); + println!(); + + let path = if let Some(path) = args().nth(0x1) { path } + else { default_configuration_path() }; + + let configuration = match Configuration::load(&path) { + Ok( configuration) => configuration, + Err(message) => panic!("unable to load configuration: {message}"), + }; + + let app = match App::init(configuration) { + Ok( app) => app, + Err(message) => panic!("unable to initialise application: {message}"), + }; + + let result = app.run(); + + if let Err(ref message) = result { eprintln!("\u{1B}[1m\u{1B}[91merror\u{1B}[0m: {message}") }; + + exit(match result { + Ok( ..) => 0x0, + Err(..) => 0x1, + }); + } +} + +fn default_configuration_path() -> String { + let home = match var("HOME") { + Ok( path) => path, + Err(..) => "/".to_string(), + }; + + let path = home + "/.luma.toml"; + return path; +} diff --git a/src/app/run.rs b/src/app/run.rs new file mode 100644 index 0000000..5bc8dc7 --- /dev/null +++ b/src/app/run.rs @@ -0,0 +1,58 @@ +/* + 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 . +*/ + +use crate::{PALETTE_LENGTH, VIDEO_LENGTH}; +use crate::app::App; +use crate::cpu::Cpu; +use crate::state::State; + +use std::time::Instant; + +impl App { + pub fn run(mut self) -> Result<(), String> { + let mut state = State::new(); + + self.load(&mut state)?; + + let cpu = Cpu::new(state); + + let mut cpu = cpu.boot(); + + let mut video_buffer: Vec:: = vec![0x0; VIDEO_LENGTH as usize]; + let mut palette_buffer: Vec:: = vec![0x0; (PALETTE_LENGTH / 0x2) as usize]; + + 'main_loop: loop { + let frame_start = Instant::now(); + + if self.check_events()? { break 'main_loop }; + + cpu.dump_video( &mut video_buffer[..]); + cpu.dump_palette(&mut palette_buffer[..]); + self.draw_video(&video_buffer[..], &palette_buffer[..]); + + self.sync_video(frame_start); + } + + return Ok(()); + } +} diff --git a/src/app/sync_video.rs b/src/app/sync_video.rs new file mode 100644 index 0000000..bb863d4 --- /dev/null +++ b/src/app/sync_video.rs @@ -0,0 +1,44 @@ +/* + 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 . +*/ + +use crate::app::App; + +use std::thread::sleep; +use std::time::{Duration, Instant}; + +impl App { + pub fn sync_video(&self, frame_start: Instant) { + // Courtesy of TASVideos: + // 59.7275005696058 Hz + + const FRAME_DURATION: u64 = 0xFF7932; + let frame_duration = Duration::from_nanos(FRAME_DURATION); + + let remaining = match frame_duration.checked_sub(frame_start.elapsed()) { + Some(value) => value, + None => Duration::from_secs(0x0), + }; + + sleep(remaining); + } +} diff --git a/src/configuration.rs b/src/configuration.rs new file mode 100644 index 0000000..183901a --- /dev/null +++ b/src/configuration.rs @@ -0,0 +1,36 @@ +/* + 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 . +*/ + +mod load; +mod validate; + +pub struct Configuration { + pub bootloader: String, + pub image: String, + + pub scale: u32, +} + +impl Configuration { + pub const VERSION: u32 = 0x0; +} diff --git a/src/configuration/load.rs b/src/configuration/load.rs new file mode 100644 index 0000000..a0065fc --- /dev/null +++ b/src/configuration/load.rs @@ -0,0 +1,100 @@ +/* + 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 . +*/ + +use crate::configuration::Configuration; + +extern crate toml; + +use std::fs::read_to_string; +use std::str::FromStr; +use toml::{Table, Value}; + +impl Configuration { + pub fn load(path: &str) -> Result { + eprintln!("loading configuration at \"{path}\""); + + let configuration_text = match read_to_string(path) { + Ok( content) => content, + _ => return Err("unable to read file".to_string()), + }; + + let base_table = match Table::from_str(&configuration_text) { + Ok( table) => table, + _ => return Err("unable to parse configuration".to_string()), + }; + + let luma_table = get_table(&base_table, "luma")?; + let device_table = get_table(&base_table, "device")?; + let video_table = get_table(&base_table, "video")?; + + let version = get_integer(&luma_table, "version")?; + + if version < Self::VERSION { return Err(format!("ancient version - got {}, expected {}", version, Self::VERSION)) } + if version > Self::VERSION { return Err(format!("out-of-date version - got {}, expected {}", version, Self::VERSION)) } + + let bootloader = get_string(&device_table, "bootloader")?; + let image = get_string(&device_table, "image")?; + + let scale = get_integer(&video_table, "scale")?; + + let configuration = Configuration { + bootloader: bootloader.clone(), + image: image.clone(), + + scale: scale, + }; + + configuration.validate()?; + return Ok(configuration); + } +} + +fn get_value<'a>(table: &'a Table, name: &str) -> Option<&'a Value> { + if !table.contains_key(name) { return None }; + + return Some(&table[name]); +} + +fn get_table<'a>(table: &'a Table, name: &str) -> Result<&'a Table, String> { + return match get_value(table, name) { + Some(Value::Table(table)) => Ok(table), + Some(_) => Err(format!("\"{name}\" should be a section")), + _ => Err("section \"{name}\" is required".to_string()), + }; +} + +fn get_integer(table: &Table, name: &str) -> Result { + return match get_value(table, name) { + Some(Value::Integer(value)) => Ok(*value as u32), + Some(_) => Err(format!("\"{name}\" should be an integer")), + _ => Err("missing integer \"{name}\"".to_string()), + }; +} + +fn get_string<'a>(table: &'a Table, name: &str) -> Result<&'a String, String> { + return match get_value(table, name) { + Some(Value::String(string)) => Ok(string), + Some(_) => Err(format!("\"{name}\" should be a string")), + _ => Err("missing string \"{name}\"".to_string()), + }; +} diff --git a/src/configuration/validate.rs b/src/configuration/validate.rs new file mode 100644 index 0000000..26aee61 --- /dev/null +++ b/src/configuration/validate.rs @@ -0,0 +1,32 @@ +/* + 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 . +*/ + +use crate::configuration::Configuration; + +impl Configuration { + pub(super) fn validate(&self) -> Result<(), String> { + if self.scale < 0x1 { return Err("scale must be at least 1".to_string()) }; + + return Ok(()); + } +} diff --git a/src/cpu.rs b/src/cpu.rs new file mode 100644 index 0000000..8b31d59 --- /dev/null +++ b/src/cpu.rs @@ -0,0 +1,104 @@ +/* + 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 . +*/ + +use crate::instruction::Instruction; +use crate::predicate::Predicate; +use crate::state::State; + +use std::sync::{Arc, Mutex}; +use std::sync::atomic::AtomicBool; + +mod alu_exit_exception; +mod boot; +mod r#continue; +mod enter_exception; +mod execute; +mod exit_exception; +mod fetch_arm; +mod fetch_thumb; + +mod isa_adc; +mod isa_add; +mod isa_and; +mod isa_bic; +mod isa_bl; +mod isa_b; +mod isa_bx; +mod isa_cmn; +mod isa_cmp; +mod isa_eor; +mod isa_mov; +mod isa_mul; +mod isa_mvn; +mod isa_orr; +mod isa_rsb; +mod isa_rsc; +mod isa_sbc; +mod isa_sub; +mod isa_swi; +mod isa_teq; +mod isa_tst; + +mod isa_memory; + +mod exchange; +mod take_state; + +// + +#[allow(unused_imports)] +pub use alu_exit_exception::*; + +#[allow(unused_imports)] +pub use exchange::*; + +#[allow(unused_imports)] +pub use take_state::*; + +pub type Fetcher = fn(&mut Cpu) -> (Instruction, Predicate, u32); + +pub struct Cpu { + state: Arc>, + cycle: u64, + dead: Arc, + + instruction_size: u32, + fetcher: Fetcher, +} + +impl Cpu { + pub fn new(state: State) -> Self { + return Self { + state: Arc::new(Mutex::new(state)), + cycle: 0x0, + dead: Arc::new(AtomicBool::new(false)), + + instruction_size: 0x4, + fetcher: Self::fetch_arm, + }; + } + + #[inline(always)] + #[must_use] + fn fetch(&mut self) -> (Instruction, Predicate, u32) { (self.fetcher)(self) } +} diff --git a/src/cpu/alu_exit_exception.rs b/src/cpu/alu_exit_exception.rs new file mode 100644 index 0000000..de9f238 --- /dev/null +++ b/src/cpu/alu_exit_exception.rs @@ -0,0 +1,38 @@ +/* + 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 . +*/ + +macro_rules! alu_exit_exception { + ($self: ident, $state: ident, $rd: ident, $block: block) => {{ + match $rd { + 0b1111 => { + drop($state); + $self.exit_exception(); + }, + + _ => { + $block; + }, + }; + }}; +} +pub(crate) use alu_exit_exception; diff --git a/src/cpu/boot.rs b/src/cpu/boot.rs new file mode 100644 index 0000000..5c38427 --- /dev/null +++ b/src/cpu/boot.rs @@ -0,0 +1,70 @@ +/* + 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 . +*/ + +use crate::{log, log_status}; +use crate::cpu::Cpu; +use crate::cpu_handle::CpuHandle; + +use std::sync::atomic::Ordering; +use std::thread::{sleep, spawn}; +use std::time::{Duration, Instant}; + +impl Cpu { + pub fn boot(self) -> CpuHandle { + eprintln!("starting emulation at {:#010X}", self.state.lock().unwrap().read_register(0xF).wrapping_sub(0x8)); + + let state = self.state.clone(); + let dead = self.dead.clone(); + + let handle = spawn(move || { self.run() }); + + return CpuHandle::new( + state, + dead, + + handle, + ); + } + + fn run(mut self) { + let run_timer = Instant::now(); + + 'main_loop: loop { + if self.dead.load(Ordering::Relaxed) { break 'main_loop }; + + let (instruction, predicate, cpsr) = self.fetch(); + match predicate.test(cpsr) { + false => log_status!("skipping due to predicate ({})", predicate.code()), + true => self.execute(instruction), + }; + self.r#continue(); + + if cfg!(debug_assertions) { sleep(Duration::from_millis(125)) }; + + self.cycle += 0x1; + } + + let frequency = self.cycle as f64 / run_timer.elapsed().as_micros() as f64; + eprintln!("emulated {} cycle(s) ({frequency:.9} MHz)", self.cycle); + } +} diff --git a/src/cpu/continue.rs b/src/cpu/continue.rs new file mode 100644 index 0000000..7b96713 --- /dev/null +++ b/src/cpu/continue.rs @@ -0,0 +1,33 @@ +/* + 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 . +*/ + +use crate::cpu::{Cpu, take_state_mut}; + +impl Cpu { + pub(super) fn r#continue(&mut self) { + take_state_mut!(state, self); + + let pc = state.read_register(0xF).wrapping_add(self.instruction_size); + state.write_register(0xF, pc); + } +} diff --git a/src/cpu/enter_exception.rs b/src/cpu/enter_exception.rs new file mode 100644 index 0000000..193b8cf --- /dev/null +++ b/src/cpu/enter_exception.rs @@ -0,0 +1,59 @@ +/* + 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 . +*/ + +use crate::cpu::{Cpu, exchange, take_state_mut}; +use crate::exception::Exception; + +impl Cpu { + pub(super) fn enter_exception(&mut self, exception: Exception) { + take_state_mut!(state, self); + + let mode = exception.mode(); + + state.bank(mode); + + let lr = state.read_register(0xF); + state.write_register(0xE, lr); + + let mut cpsr = state.read_cpsr(); + state.write_spsr(mode, cpsr); + + cpsr &= 0b11111111111111111111111101000000; + cpsr |= 0b00000000000000000000000010000000; + cpsr |= mode as u32; + + cpsr &= match exception { + | Exception::FastInterruptRequest + | Exception::Reset => 0b11111111111111111111111110111111, + + _ => 0b11111111111111111111111111111111, + }; + + state.write_cpsr(cpsr); + + exchange!(self, false); + + let pc = exception.vector_address().wrapping_add(0x4); + state.write_register(0xF, pc); + } +} diff --git a/src/cpu/exchange.rs b/src/cpu/exchange.rs new file mode 100644 index 0000000..1e74a75 --- /dev/null +++ b/src/cpu/exchange.rs @@ -0,0 +1,45 @@ +/* + 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 . +*/ + +macro_rules! exchange { + ($cpu: ident, $t: expr) => {{ + use crate::log_status; + use crate::cpu::Fetcher; + + log_status!("exchanging to {}", match $t { + false => "ARM", + true => "Thumb", + }); + + const DATA: [(u32, Fetcher); 0x2] = [ + (0x4, Cpu::fetch_arm), + (0x2, Cpu::fetch_thumb), + ]; + + let index = $t as usize & 0b1; + + $cpu.instruction_size = unsafe { DATA.get_unchecked(index).0 }; + $cpu.fetcher = unsafe { DATA.get_unchecked(index).1 }; + }}; +} +pub(crate) use exchange; diff --git a/src/cpu/execute.rs b/src/cpu/execute.rs new file mode 100644 index 0000000..7987f92 --- /dev/null +++ b/src/cpu/execute.rs @@ -0,0 +1,69 @@ +/* + 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 . +*/ + +use crate::cpu::Cpu; +use crate::instruction::Instruction; + +impl Cpu { + #[inline(always)] + pub(super) fn execute(&mut self, instruction: Instruction) { + use Instruction::*; + + match instruction { + Adc(rd, rn, shifter, s) => self.isa_adc(rd, rn, shifter, s), + Add(rd, rn, shifter, s) => self.isa_add(rd, rn, shifter, s), + And(rd, rn, shifter, s) => self.isa_and(rd, rn, shifter, s), + B( imm) => self.isa_b( imm), + Bic(rd, rn, shifter, s) => self.isa_bic(rd, rn, shifter, s), + Bl( imm) => self.isa_bl( imm), + Bl0(imm) => self.isa_bl0(imm), + Bl1(imm) => self.isa_bl1(imm), + Bx( rm) => self.isa_bx( rm), + Cmn(rn, shifter) => self.isa_cmn(rn, shifter), + Cmp(rn, shifter) => self.isa_cmp(rn, shifter), + Eor(rd, rn, shifter, s) => self.isa_eor(rd, rn, shifter, s), + Mov(rd, shifter, s) => self.isa_mov(rd, shifter, s), + Mul(rd, rn, shifter, s) => self.isa_mul(rd, rn, shifter, s), + Mvn(rd, shifter, s) => self.isa_mvn(rd, shifter, s), + Orr(rd, rn, shifter, s) => self.isa_orr(rd, rn, shifter, s), + Rsb(rd, rn, shifter, s) => self.isa_rsb(rd, rn, shifter, s), + Rsc(rd, rn, shifter, s) => self.isa_rsc(rd, rn, shifter, s), + Sbc(rd, rn, shifter, s) => self.isa_sbc(rd, rn, shifter, s), + Sub(rd, rn, shifter, s) => self.isa_sub(rd, rn, shifter, s), + Swi(imm) => self.isa_swi(imm), + Teq(rn, shifter) => self.isa_teq(rn, shifter), + Tst(rn, shifter) => self.isa_tst(rn, shifter), + + // Legacy: + 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, imm) => self.isa_load_pc( rd, imm), + 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), + + Undefined => {}, + }; + } +} diff --git a/src/cpu/exit_exception.rs b/src/cpu/exit_exception.rs new file mode 100644 index 0000000..fa47fad --- /dev/null +++ b/src/cpu/exit_exception.rs @@ -0,0 +1,42 @@ +/* + 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 . +*/ + +use crate::cpu::{Cpu, exchange, take_state_mut}; +use crate::cpu_mode::CpuMode; + +impl Cpu { + pub(super) fn exit_exception(&mut self) { + take_state_mut!(state, self); + + let cpsr = state.read_cpsr(); + let old_mode = CpuMode::from_raw((cpsr & 0b00000000000000000000000000011111) as u8); + + let spsr = state.read_spsr(old_mode); + let new_mode = CpuMode::from_raw((spsr & 0b00000000000000000000000000011111) as u8); + + let t = spsr & 0b00000000000000000000000000100000 != 0x0; + state.write_cpsr(spsr); + state.bank(new_mode); + exchange!(self, t); + } +} diff --git a/src/cpu/fetch_arm.rs b/src/cpu/fetch_arm.rs new file mode 100644 index 0000000..453dbe2 --- /dev/null +++ b/src/cpu/fetch_arm.rs @@ -0,0 +1,55 @@ +/* + 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 . +*/ + +use crate::{Error, log}; +use crate::cpu::{Cpu, take_state}; +use crate::instruction::Instruction; +use crate::predicate::Predicate; + +impl Cpu { + #[must_use] + pub(super) fn fetch_arm(&mut self) -> (Instruction, Predicate, u32) { + take_state!(state, self); + + let address = state.read_register(0xF).wrapping_sub(0x8); + let opcode = state.read_word(address); + let cpsr = state.read_cpsr(); + + drop(state); + + log!(); + log!("\u{1B}[1m{opcode:032b}\u{1B}[0m @ \u{1B}[1m{address:08X}\u{1B}[0m - ({})", self.cycle); + + let (instruction, predicate) = Instruction::decode_arm(opcode); + + match (instruction, predicate) { + | (Instruction::Undefined, _) + | (_, Predicate::Nv) + => Error::InvalidArmOpcode(address, opcode).trap(), + + _ => {}, + }; + + return (instruction, predicate, cpsr); + } +} diff --git a/src/cpu/fetch_thumb.rs b/src/cpu/fetch_thumb.rs new file mode 100644 index 0000000..cee6ba7 --- /dev/null +++ b/src/cpu/fetch_thumb.rs @@ -0,0 +1,54 @@ +/* + 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 . +*/ + +use crate::{Error, log}; +use crate::cpu::{Cpu, take_state}; +use crate::instruction::Instruction; +use crate::predicate::Predicate; + +impl Cpu { + pub(super) fn fetch_thumb(&mut self) -> (Instruction, Predicate, u32) { + take_state!(state, self); + + let address = state.read_register(0xF).wrapping_sub(0x4); + let opcode = state.read_halfword(address); + let cpsr = state.read_cpsr(); + + drop(state); + + log!(); + log!(" \u{1B}[1m{opcode:016b}\u{1B}[0m @ \u{1B}[1m{address:08X}\u{1B}[0m - ({})", self.cycle); + + let (instruction, predicate) = Instruction::decode_thumb(opcode); + + match (instruction, predicate) { + | (Instruction::Undefined, _) + | (_, Predicate::Nv) + => Error::InvalidThumbOpcode(address, opcode).trap(), + + _ => {}, + }; + + return (instruction, predicate, cpsr); + } +} diff --git a/src/cpu/isa_adc.rs b/src/cpu/isa_adc.rs new file mode 100644 index 0000000..214d289 --- /dev/null +++ b/src/cpu/isa_adc.rs @@ -0,0 +1,58 @@ +/* + 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 . +*/ + +use crate::log; +use crate::cpu::{alu_exit_exception, Cpu, take_state_mut}; +use crate::shifter::Shifter; + +impl Cpu { + pub(super) fn isa_adc(&mut self, rd: u8, rn: u8, shifter: Shifter, s: bool) { + log!("{} r{rd}, r{rn}, {shifter}", match s { + false => "adc", + true => "adcs", + }); + + take_state_mut!(state, self); + + let rn_value = state.read_register(rn); + let c = (state.read_cpsr() & 0b00100000000000000000000000000000) >> 0x1D; + + let (mut addend, _) = state.shifter_value(shifter); + addend = addend.wrapping_add(c); + + let (result, c) = rn_value.overflowing_add(addend); + let (_, v) = (rn_value as i32).overflowing_add_unsigned(addend); + state.write_register(rd, result); + + if s { + alu_exit_exception!(self, state, rd, { + let mut cpsr = state.read_cpsr() & 0b00001111111111111111111111111111; + cpsr |= (v as u32) << 0x1C; + cpsr |= (c as u32) << 0x1D; + cpsr |= ((result == 0x0) as u32) << 0x1E; + cpsr |= result & 0b10000000000000000000000000000000; + state.write_cpsr(cpsr); + }); + } + } +} diff --git a/src/cpu/isa_add.rs b/src/cpu/isa_add.rs new file mode 100644 index 0000000..c099c97 --- /dev/null +++ b/src/cpu/isa_add.rs @@ -0,0 +1,55 @@ +/* + 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 . +*/ + +use crate::log; +use crate::cpu::{alu_exit_exception, Cpu, take_state_mut}; +use crate::shifter::Shifter; + +impl Cpu { + pub(super) fn isa_add(&mut self, rd: u8, rn: u8, shifter: Shifter, s: bool) { + log!("{} r{rd}, r{rn}, {shifter}", match s { + false => "add", + true => "adds", + }); + + take_state_mut!(state, self); + + let rn_value = state.read_register(rn); + let (addend, _) = state.shifter_value(shifter); + + let (result, c) = rn_value.overflowing_add(addend); + let (_, v) = (rn_value as i32).overflowing_add_unsigned(addend); + state.write_register(rd, result); + + if s { + alu_exit_exception!(self, state, rd, { + let mut cpsr = state.read_cpsr() & 0b00001111111111111111111111111111; + cpsr |= (v as u32) << 0x1C; + cpsr |= (c as u32) << 0x1D; + cpsr |= ((result == 0x0) as u32) << 0x1E; + cpsr |= result & 0b10000000000000000000000000000000; + state.write_cpsr(cpsr); + }); + } + } +} diff --git a/src/cpu/isa_and.rs b/src/cpu/isa_and.rs new file mode 100644 index 0000000..1148953 --- /dev/null +++ b/src/cpu/isa_and.rs @@ -0,0 +1,53 @@ +/* + 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 . +*/ + +use crate::log; +use crate::cpu::{alu_exit_exception, Cpu, take_state_mut}; +use crate::shifter::Shifter; + +impl Cpu { + pub(super) fn isa_and(&mut self, rd: u8, rn: u8, shifter: Shifter, s: bool) { + log!("{} r{rd}, r{rn}, {shifter}", match s { + false => "and", + true => "ands", + }); + + take_state_mut!(state, self); + + let rn_value = state.read_register(rn); + let (mask, c) = state.shifter_value(shifter); + + let result = rn_value & mask; + state.write_register(rd, result); + + if s { + alu_exit_exception!(self, state, rd, { + let mut cpsr = state.read_cpsr() & 0b00011111111111111111111111111111; + cpsr |= (c as u32) << 0x1D; + cpsr |= ((result == 0x0) as u32) << 0x1E; + cpsr |= result & 0b10000000000000000000000000000000; + state.write_cpsr(cpsr); + }); + } + } +} diff --git a/src/cpu/isa_b.rs b/src/cpu/isa_b.rs new file mode 100644 index 0000000..e0ebdca --- /dev/null +++ b/src/cpu/isa_b.rs @@ -0,0 +1,38 @@ +/* + 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 . +*/ + +use crate::log; +use crate::cpu::{Cpu, take_state_mut}; + +impl Cpu { + pub(super) fn isa_b(&mut self, imm: i32) { + take_state_mut!(state, self); + + let mut target = state.read_register(0xF).wrapping_add_signed(imm); + + log!("b {target:#X}"); + + target = target.wrapping_add(self.instruction_size); + state.write_register(0xF, target); + } +} diff --git a/src/cpu/isa_bic.rs b/src/cpu/isa_bic.rs new file mode 100644 index 0000000..6647e0c --- /dev/null +++ b/src/cpu/isa_bic.rs @@ -0,0 +1,53 @@ +/* + 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 . +*/ + +use crate::log; +use crate::cpu::{alu_exit_exception, Cpu, take_state_mut}; +use crate::shifter::Shifter; + +impl Cpu { + pub(super) fn isa_bic(&mut self, rd: u8, rn: u8, shifter: Shifter, s: bool) { + log!("{} r{rd}, r{rn}, {shifter}", match s { + false => "bic", + true => "bics", + }); + + take_state_mut!(state, self); + + let rn_value = state.read_register(rn); + let (mask, c) = state.shifter_value(shifter); + + let result = rn_value & !mask; + state.write_register(rd, result); + + if s { + alu_exit_exception!(self, state, rd, { + let mut cpsr = state.read_cpsr() & 0b00011111111111111111111111111111; + cpsr |= (c as u32) << 0x1D; + cpsr |= ((result == 0x0) as u32) << 0x1E; + cpsr |= result & 0b10000000000000000000000000000000; + state.write_cpsr(cpsr); + }); + } + } +} diff --git a/src/cpu/isa_bl.rs b/src/cpu/isa_bl.rs new file mode 100644 index 0000000..416ce4a --- /dev/null +++ b/src/cpu/isa_bl.rs @@ -0,0 +1,62 @@ +/* + 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 . +*/ + +use crate::log; +use crate::cpu::{Cpu, take_state_mut}; + +impl Cpu { + pub(super) fn isa_bl(&mut self, imm: i32) { + take_state_mut!(state, self); + + let link_target = state.read_register(0xF).wrapping_sub(self.instruction_size); + let mut branch_target = state.read_register(0xF).wrapping_add_signed(imm); + + log!("bl {branch_target:#X}"); + + branch_target = branch_target.wrapping_add(self.instruction_size); + state.write_register(0xE, link_target); + state.write_register(0xF, branch_target); + } + + pub(super) fn isa_bl0(&mut self, imm: i32) { + take_state_mut!(state, self); + + let target = state.read_register(0xF).wrapping_add_signed(imm); + + state.write_register(0xE, target); + } + + pub(super) fn isa_bl1(&mut self, imm: i32) { + take_state_mut!(state, self); + + let mut branch_target = state.read_register(0xE).wrapping_add_signed(imm); + let link_target = state.read_register(0xF).wrapping_sub(0x2); + + log!("bl {branch_target:#X}"); + + state.write_register(0xE, link_target); + + branch_target = branch_target.wrapping_add(0x2); + state.write_register(0xF, branch_target); + } +} diff --git a/src/cpu/isa_bx.rs b/src/cpu/isa_bx.rs new file mode 100644 index 0000000..3307dbf --- /dev/null +++ b/src/cpu/isa_bx.rs @@ -0,0 +1,45 @@ +/* + 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 . +*/ + +use crate::log; +use crate::cpu::{Cpu, exchange, take_state_mut}; + +impl Cpu { + pub(super) fn isa_bx(&mut self, rm: u8) { + take_state_mut!(state, self); + + log!("bx r{rm}"); + + let mut target = state.read_register(rm); + + let t = target & 0b00000000000000000000000000000001 != 0x0; + exchange!(self, t); + + let cpsr = state.read_cpsr() & 0b11111111111111111111111111011111 | (t as u32) << 0x5; + state.write_cpsr(cpsr); + + target &= 0b11111111111111111111111111111110; + target = target.wrapping_add(self.instruction_size); + state.write_register(0xF, target); + } +} diff --git a/src/cpu/isa_cmn.rs b/src/cpu/isa_cmn.rs new file mode 100644 index 0000000..f69acef --- /dev/null +++ b/src/cpu/isa_cmn.rs @@ -0,0 +1,47 @@ +/* + 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 . +*/ + +use crate::log; +use crate::cpu::{Cpu, take_state_mut}; +use crate::shifter::Shifter; + +impl Cpu { + pub(super) fn isa_cmn(&mut self, rn: u8, shifter: Shifter) { + log!("cmn r{rn}, {shifter}"); + + take_state_mut!(state, self); + + let rn_value = state.read_register(rn); + let (subtrahend, _) = state.shifter_value(shifter); + + let (temporary, c) = rn_value.overflowing_add(subtrahend); + let (_, v) = (rn_value as i32).overflowing_add_unsigned(subtrahend); + + let mut cpsr = state.read_cpsr() & 0b00001111111111111111111111111111; + cpsr |= (v as u32) << 0x1C; + cpsr |= (!c as u32) << 0x1D; + cpsr |= ((temporary == 0x0) as u32) << 0x1E; + cpsr |= temporary & 0b10000000000000000000000000000000; + state.write_cpsr(cpsr); + } +} diff --git a/src/cpu/isa_cmp.rs b/src/cpu/isa_cmp.rs new file mode 100644 index 0000000..c5719fb --- /dev/null +++ b/src/cpu/isa_cmp.rs @@ -0,0 +1,47 @@ +/* + 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 . +*/ + +use crate::log; +use crate::cpu::{Cpu, take_state_mut}; +use crate::shifter::Shifter; + +impl Cpu { + pub(super) fn isa_cmp(&mut self, rn: u8, shifter: Shifter) { + log!("cmp r{rn}, {shifter}"); + + take_state_mut!(state, self); + + let rn_value = state.read_register(rn); + let (subtrahend, _) = state.shifter_value(shifter); + + let (temporary, c) = rn_value.overflowing_sub(subtrahend); + let (_, v) = (rn_value as i32).overflowing_sub_unsigned(subtrahend); + + let mut cpsr = state.read_cpsr() & 0b00001111111111111111111111111111; + cpsr |= (v as u32) << 0x1C; + cpsr |= (!c as u32) << 0x1D; + cpsr |= ((temporary == 0x0) as u32) << 0x1E; + cpsr |= temporary & 0b10000000000000000000000000000000; + state.write_cpsr(cpsr); + } +} diff --git a/src/cpu/isa_eor.rs b/src/cpu/isa_eor.rs new file mode 100644 index 0000000..22d3842 --- /dev/null +++ b/src/cpu/isa_eor.rs @@ -0,0 +1,53 @@ +/* + 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 . +*/ + +use crate::log; +use crate::cpu::{alu_exit_exception, Cpu, take_state_mut}; +use crate::shifter::Shifter; + +impl Cpu { + pub(super) fn isa_eor(&mut self, rd: u8, rn: u8, shifter: Shifter, s: bool) { + log!("{} r{rd}, r{rn}, {shifter}", match s { + false => "eor", + true => "eors", + }); + + take_state_mut!(state, self); + + let rn_value = state.read_register(rn); + let (mask, c) = state.shifter_value(shifter); + + let result = rn_value ^ mask; + state.write_register(rd, result); + + if s { + alu_exit_exception!(self, state, rd, { + let mut cpsr = state.read_cpsr() & 0b00011111111111111111111111111111; + cpsr |= (c as u32) << 0x1D; + cpsr |= ((result == 0x0) as u32) << 0x1E; + cpsr |= result & 0b10000000000000000000000000000000; + state.write_cpsr(cpsr); + }); + } + } +} diff --git a/src/cpu/isa_memory.rs b/src/cpu/isa_memory.rs new file mode 100644 index 0000000..f4abecb --- /dev/null +++ b/src/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 . +*/ + +use crate::log; +use crate::cpu::{Cpu, take_state_mut}; + +impl Cpu { + pub(super) fn isa_load_halfword(&mut self, rd: u8, rn: u8, imm: i16) { + take_state_mut!(state, self); + + 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) { + take_state_mut!(state, self); + + let rn_value = state.read_register(rn); + + let target = rn_value.wrapping_add_signed(imm as i32); + + log!("ldr r{rd}, [r{rn}, #{imm:#X}] @ {target:#010X}"); + + let result = state.read_word(target); + state.write_register(rd, result); + } + + pub(super) fn isa_load_pc(&mut self, rd: u8, imm: u16) { + // Slightly different from load_immediate_offset + // due to the target being forced word-aligned. + + take_state_mut!(state, self); + + let rn_value = state.read_register(0xF) & 0b11111111111111111111111111111100; + + let target = rn_value.wrapping_add(imm as u32); + + log!("ldr r{rd}, [pc, #{imm:#X}] @ {target:#010X}"); + + 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}]"); + + take_state_mut!(state, self); + + 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}]"); + + take_state_mut!(state, self); + + 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}]"); + + take_state_mut!(state, self); + + 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}]"); + + take_state_mut!(state, self); + + 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/cpu/isa_mov.rs b/src/cpu/isa_mov.rs new file mode 100644 index 0000000..302128e --- /dev/null +++ b/src/cpu/isa_mov.rs @@ -0,0 +1,53 @@ +/* + 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 . +*/ + +use crate::log; +use crate::cpu::{alu_exit_exception, Cpu, take_state_mut}; +use crate::shifter::Shifter; + +impl Cpu { + pub(super) fn isa_mov(&mut self, rd: u8, shifter: Shifter, s: bool) { + log!("{} r{rd}, {shifter}", match s { + false => "mov", + true => "movs", + }); + + take_state_mut!(state, self); + + let (mut value, c) = state.shifter_value(shifter); + // Adjust value for branch. + if value == 0b1111 { value = value.wrapping_add(self.instruction_size.wrapping_shl(0x1)) }; + + state.write_register(rd, value); + + if s { + alu_exit_exception!(self, state, rd, { + let mut cpsr = state.read_cpsr() & 0b00011111111111111111111111111111; + cpsr |= (c as u32) << 0x1D; + cpsr |= ((value == 0x0) as u32) << 0x1E; + cpsr |= value & 0b10000000000000000000000000000000; + state.write_cpsr(cpsr); + }); + } + } +} diff --git a/src/cpu/isa_mul.rs b/src/cpu/isa_mul.rs new file mode 100644 index 0000000..d4212f0 --- /dev/null +++ b/src/cpu/isa_mul.rs @@ -0,0 +1,52 @@ +/* + 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 . +*/ + +use crate::log; +use crate::cpu::{alu_exit_exception, Cpu, take_state_mut}; +use crate::shifter::Shifter; + +impl Cpu { + pub(super) fn isa_mul(&mut self, rd: u8, rn: u8, shifter: Shifter, s: bool) { + log!("{} r{rd}, r{rn}, {shifter}", match s { + false => "mul", + true => "muls", + }); + + take_state_mut!(state, self); + + let rn_value = state.read_register(rn); + let (multiplicand, _) = state.shifter_value(shifter); + + let result = rn_value.wrapping_mul(multiplicand); + state.write_register(rd, result); + + if s { + alu_exit_exception!(self, state, rd, { + let mut cpsr = state.read_cpsr() & 0b00111111111111111111111111111111; + cpsr |= ((result == 0x0) as u32) << 0x1E; + cpsr |= result & 0b10000000000000000000000000000000; + state.write_cpsr(cpsr); + }); + } + } +} diff --git a/src/cpu/isa_mvn.rs b/src/cpu/isa_mvn.rs new file mode 100644 index 0000000..a3847e4 --- /dev/null +++ b/src/cpu/isa_mvn.rs @@ -0,0 +1,52 @@ +/* + 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 . +*/ + +use crate::log; +use crate::cpu::{alu_exit_exception, Cpu, take_state_mut}; +use crate::shifter::Shifter; + +impl Cpu { + pub(super) fn isa_mvn(&mut self, rd: u8, shifter: Shifter, s: bool) { + log!("{} r{rd}, {shifter}", match s { + false => "mvn", + true => "mvns", + }); + + take_state_mut!(state, self); + + let (mut result, c) = state.shifter_value(shifter); + result = !result; + + state.write_register(rd, result); + + if s { + alu_exit_exception!(self, state, rd, { + let mut cpsr = state.read_cpsr() & 0b00011111111111111111111111111111; + cpsr |= (c as u32) << 0x1D; + cpsr |= ((result == 0x0) as u32) << 0x1E; + cpsr |= result & 0b10000000000000000000000000000000; + state.write_cpsr(cpsr); + }); + } + } +} diff --git a/src/cpu/isa_orr.rs b/src/cpu/isa_orr.rs new file mode 100644 index 0000000..030695a --- /dev/null +++ b/src/cpu/isa_orr.rs @@ -0,0 +1,53 @@ +/* + 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 . +*/ + +use crate::log; +use crate::cpu::{alu_exit_exception, Cpu, take_state_mut}; +use crate::shifter::Shifter; + +impl Cpu { + pub(super) fn isa_orr(&mut self, rd: u8, rn: u8, shifter: Shifter, s: bool) { + log!("{} r{rd}, r{rn}, {shifter}", match s { + false => "orr", + true => "orrs", + }); + + take_state_mut!(state, self); + + let rn_value = state.read_register(rn); + let (mask, c) = state.shifter_value(shifter); + + let result = rn_value | mask; + state.write_register(rd, result); + + if s { + alu_exit_exception!(self, state, rd, { + let mut cpsr = state.read_cpsr() & 0b00011111111111111111111111111111; + cpsr |= (c as u32) << 0x1D; + cpsr |= ((result == 0x0) as u32) << 0x1E; + cpsr |= result & 0b10000000000000000000000000000000; + state.write_cpsr(cpsr); + }); + } + } +} diff --git a/src/cpu/isa_rsb.rs b/src/cpu/isa_rsb.rs new file mode 100644 index 0000000..d4daf06 --- /dev/null +++ b/src/cpu/isa_rsb.rs @@ -0,0 +1,55 @@ +/* + 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 . +*/ + +use crate::log; +use crate::cpu::{alu_exit_exception, Cpu, take_state_mut}; +use crate::shifter::Shifter; + +impl Cpu { + pub(super) fn isa_rsb(&mut self, rd: u8, rn: u8, shifter: Shifter, s: bool) { + log!("{} r{rd}, r{rn}, {shifter}", match s { + false => "rsb", + true => "rsbs", + }); + + take_state_mut!(state, self); + + let rn_value = state.read_register(rn); + let (minuend, _) = state.shifter_value(shifter); + + let (result, c) = minuend.overflowing_sub(rn_value); + let (_, v) = (minuend as i32).overflowing_sub_unsigned(rn_value); + state.write_register(rd, result); + + if s { + alu_exit_exception!(self, state, rd, { + let mut cpsr = state.read_cpsr() & 0b00001111111111111111111111111111; + cpsr |= (v as u32) << 0x1C; + cpsr |= (!c as u32) << 0x1D; + cpsr |= ((result == 0x0) as u32) << 0x1E; + cpsr |= result & 0b10000000000000000000000000000000; + state.write_cpsr(cpsr); + }); + } + } +} diff --git a/src/cpu/isa_rsc.rs b/src/cpu/isa_rsc.rs new file mode 100644 index 0000000..414df34 --- /dev/null +++ b/src/cpu/isa_rsc.rs @@ -0,0 +1,58 @@ +/* + 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 . +*/ + +use crate::log; +use crate::cpu::{alu_exit_exception, Cpu, take_state_mut}; +use crate::shifter::Shifter; + +impl Cpu { + pub(super) fn isa_rsc(&mut self, rd: u8, rn: u8, shifter: Shifter, s: bool) { + log!("{} r{rd}, r{rn}, {shifter}", match s { + false => "rsc", + true => "rscs", + }); + + take_state_mut!(state, self); + + let rn_value = state.read_register(rn); + let (minuend, _) = state.shifter_value(shifter); + + let c = (state.read_cpsr() & 0b00100000000000000000000000000000) >> 0x1D != 0x0; + let subtrahend = rn_value.wrapping_sub(c as u32); + + let (result, c) = minuend.overflowing_sub(subtrahend); + let (_, v) = (minuend as i32).overflowing_sub_unsigned(subtrahend); + state.write_register(rd, result); + + if s { + alu_exit_exception!(self, state, rd, { + let mut cpsr = state.read_cpsr() & 0b00001111111111111111111111111111; + cpsr |= (v as u32) << 0x1C; + cpsr |= (!c as u32) << 0x1D; + cpsr |= ((result == 0x0) as u32) << 0x1E; + cpsr |= result & 0b10000000000000000000000000000000; + state.write_cpsr(cpsr); + }); + } + } +} diff --git a/src/cpu/isa_sbc.rs b/src/cpu/isa_sbc.rs new file mode 100644 index 0000000..28a7c0d --- /dev/null +++ b/src/cpu/isa_sbc.rs @@ -0,0 +1,58 @@ +/* + 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 . +*/ + +use crate::log; +use crate::cpu::{alu_exit_exception, Cpu, take_state_mut}; +use crate::shifter::Shifter; + +impl Cpu { + pub(super) fn isa_sbc(&mut self, rd: u8, rn: u8, shifter: Shifter, s: bool) { + log!("{} r{rd}, r{rn}, {shifter}", match s { + false => "sbc", + true => "sbcs", + }); + + take_state_mut!(state, self); + + let rn_value = state.read_register(rn); + let c = (state.read_cpsr() & 0b00100000000000000000000000000000) >> 0x1D; + + let (mut subtrahend, _) = state.shifter_value(shifter); + subtrahend = subtrahend.wrapping_sub(c); + + let (result, c) = rn_value.overflowing_sub(subtrahend); + let (_, v) = (rn_value as i32).overflowing_sub_unsigned(subtrahend); + state.write_register(rd, result); + + if s { + alu_exit_exception!(self, state, rd, { + let mut cpsr = state.read_cpsr() & 0b00001111111111111111111111111111; + cpsr |= (v as u32) << 0x1C; + cpsr |= (!c as u32) << 0x1D; + cpsr |= ((result == 0x0) as u32) << 0x1E; + cpsr |= result & 0b10000000000000000000000000000000; + state.write_cpsr(cpsr); + }); + } + } +} diff --git a/src/cpu/isa_sub.rs b/src/cpu/isa_sub.rs new file mode 100644 index 0000000..f8290c9 --- /dev/null +++ b/src/cpu/isa_sub.rs @@ -0,0 +1,55 @@ +/* + 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 . +*/ + +use crate::log; +use crate::cpu::{alu_exit_exception, Cpu, take_state_mut}; +use crate::shifter::Shifter; + +impl Cpu { + pub(super) fn isa_sub(&mut self, rd: u8, rn: u8, shifter: Shifter, s: bool) { + log!("{} r{rd}, r{rn}, {shifter}", match s { + false => "sub", + true => "subs", + }); + + take_state_mut!(state, self); + + let rn_value = state.read_register(rn); + let (subtrahend, _) = state.shifter_value(shifter); + + let (result, c) = rn_value.overflowing_sub(subtrahend); + let (_, v) = (rn_value as i32).overflowing_sub_unsigned(subtrahend); + state.write_register(rd, result); + + if s { + alu_exit_exception!(self, state, rd, { + let mut cpsr = state.read_cpsr() & 0b00001111111111111111111111111111; + cpsr |= (v as u32) << 0x1C; + cpsr |= (!c as u32) << 0x1D; + cpsr |= ((result == 0x0) as u32) << 0x1E; + cpsr |= result & 0b10000000000000000000000000000000; + state.write_cpsr(cpsr); + }); + } + } +} diff --git a/src/cpu/isa_swi.rs b/src/cpu/isa_swi.rs new file mode 100644 index 0000000..0575740 --- /dev/null +++ b/src/cpu/isa_swi.rs @@ -0,0 +1,34 @@ +/* + 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 . +*/ + +use crate::log; +use crate::cpu::{Cpu}; +use crate::exception::Exception; + +impl Cpu { + pub(super) fn isa_swi(&mut self, imm: u32) { + log!("swi #{imm:#X}"); + + self.enter_exception(Exception::SoftwareInterrupt); + } +} diff --git a/src/cpu/isa_teq.rs b/src/cpu/isa_teq.rs new file mode 100644 index 0000000..2bc162a --- /dev/null +++ b/src/cpu/isa_teq.rs @@ -0,0 +1,45 @@ +/* + 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 . +*/ + +use crate::log; +use crate::cpu::{Cpu, take_state_mut}; +use crate::shifter::Shifter; + +impl Cpu { + pub(super) fn isa_teq(&mut self, rn: u8, shifter: Shifter) { + log!("teq r{rn}, {shifter}"); + + take_state_mut!(state, self); + + let rn_value = state.read_register(rn); + let (mask, c) = state.shifter_value(shifter); + + let temporary = rn_value ^ mask; + + let mut cpsr = state.read_cpsr() & 0b00011111111111111111111111111111; + cpsr |= (c as u32) << 0x1D; + cpsr |= ((temporary == 0x0) as u32) << 0x1E; + cpsr |= temporary & 0b10000000000000000000000000000000; + state.write_cpsr(cpsr); + } +} diff --git a/src/cpu/isa_tst.rs b/src/cpu/isa_tst.rs new file mode 100644 index 0000000..0c4d290 --- /dev/null +++ b/src/cpu/isa_tst.rs @@ -0,0 +1,45 @@ +/* + 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 . +*/ + +use crate::log; +use crate::cpu::{Cpu, take_state_mut}; +use crate::shifter::Shifter; + +impl Cpu { + pub(super) fn isa_tst(&mut self, rn: u8, shifter: Shifter) { + log!("tst r{rn}, {shifter}"); + + take_state_mut!(state, self); + + let rn_value = state.read_register(rn); + let (mask, c) = state.shifter_value(shifter); + + let temporary = rn_value & mask; + + let mut cpsr = state.read_cpsr() & 0b00011111111111111111111111111111; + cpsr |= (c as u32) << 0x1D; + cpsr |= ((temporary == 0x0) as u32) << 0x1E; + cpsr |= temporary & 0b10000000000000000000000000000000; + state.write_cpsr(cpsr); + } +} diff --git a/src/cpu/sync_cycle.rs b/src/cpu/sync_cycle.rs new file mode 100644 index 0000000..b343c32 --- /dev/null +++ b/src/cpu/sync_cycle.rs @@ -0,0 +1,43 @@ +/* + 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 . +*/ + +use crate::cpu::Cpu; + +use std::thread::sleep; +use std::time::{Duration, Instant}; + +impl Cpu { + pub fn sync_cycle(&self, cycle_start: Instant) { + // 16*1024*1024 Hz, 59.604644775 ns + + const CYCLE_DURATION: f64 = 0.000000059604644775; + let cycle_duration = Duration::from_secs_f64(CYCLE_DURATION); + + let remaining = match cycle_duration.checked_sub(cycle_start.elapsed()) { + Some(value) => value, + None => Duration::from_secs(0x0), + }; + + sleep(remaining); + } +} diff --git a/src/cpu/take_state.rs b/src/cpu/take_state.rs new file mode 100644 index 0000000..5b90a1f --- /dev/null +++ b/src/cpu/take_state.rs @@ -0,0 +1,36 @@ +/* + 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 . +*/ + +macro_rules! take_state { + ($name: ident, $cpu: ident) => { + let $name = $cpu.state.lock().unwrap(); + }; +} +pub(crate) use take_state; + +macro_rules! take_state_mut { + ($name: ident, $cpu: ident) => { + let mut $name = $cpu.state.lock().unwrap(); + }; +} +pub(crate) use take_state_mut; diff --git a/src/cpu_handle.rs b/src/cpu_handle.rs new file mode 100644 index 0000000..c4c1513 --- /dev/null +++ b/src/cpu_handle.rs @@ -0,0 +1,55 @@ +/* + 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 . +*/ + +use crate::state::State; + +use std::sync::{Arc, Mutex}; +use std::sync::atomic::AtomicBool; +use std::thread::JoinHandle; + +mod drop; +mod dump_video; +mod dump_palette; + +pub struct CpuHandle { + state: Arc>, + dead: Arc, + + handle: Option>, +} + +impl CpuHandle { + pub fn new( + state: Arc>, + dead: Arc, + + handle: JoinHandle<()>, + ) -> Self { + return Self { + state: state, + dead: dead, + + handle: Some(handle), + }; + } +} diff --git a/src/cpu_handle/drop.rs b/src/cpu_handle/drop.rs new file mode 100644 index 0000000..a2a4853 --- /dev/null +++ b/src/cpu_handle/drop.rs @@ -0,0 +1,37 @@ +/* + 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 . +*/ + +use crate::cpu_handle::CpuHandle; + +use std::sync::atomic::Ordering; + +impl Drop for CpuHandle { + fn drop(&mut self) { + eprintln!("got kill order"); + + let handle = self.handle.take().unwrap(); + + self.dead.store(true, Ordering::Relaxed); + handle.join().unwrap(); + } +} diff --git a/src/cpu_handle/dump_palette.rs b/src/cpu_handle/dump_palette.rs new file mode 100644 index 0000000..826c96b --- /dev/null +++ b/src/cpu_handle/dump_palette.rs @@ -0,0 +1,36 @@ +/* + 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 . +*/ + +use crate::PALETTE_LENGTH; +use crate::cpu_handle::CpuHandle; + +use std::ptr::copy_nonoverlapping; + +impl CpuHandle { + pub fn dump_palette(&mut self, buffer: &mut [u16]) { + assert_eq!(buffer.len(), PALETTE_LENGTH as usize >> 0x1); + + let state = self.state.lock().unwrap(); + unsafe { copy_nonoverlapping(state.palette().as_ptr(), buffer.as_mut_ptr(), buffer.len()) }; + } +} diff --git a/src/cpu_handle/dump_video.rs b/src/cpu_handle/dump_video.rs new file mode 100644 index 0000000..6f7eefb --- /dev/null +++ b/src/cpu_handle/dump_video.rs @@ -0,0 +1,34 @@ +/* + 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 . +*/ + +use crate::VIDEO_LENGTH; +use crate::cpu_handle::CpuHandle; + +impl CpuHandle { + pub fn dump_video(&mut self, buffer: &mut [u8]) { + assert_eq!(buffer.len(), VIDEO_LENGTH as usize); + + let state = self.state.lock().unwrap(); + buffer.copy_from_slice(state.video8()); + } +} diff --git a/src/cpu_mode.rs b/src/cpu_mode.rs new file mode 100644 index 0000000..9afa665 --- /dev/null +++ b/src/cpu_mode.rs @@ -0,0 +1,66 @@ +/* + 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 . +*/ + +#[repr(u8)] +#[derive(Clone, Copy)] +pub enum CpuMode { + User = 0b10000, + FastInterruptRequest = 0b10001, + InterruptRequest = 0b10010, + Supervisor = 0b10011, + Abort = 0b10111, + Undefined = 0b11011, + System = 0b11111, +} + +impl CpuMode { + pub fn from_raw(raw: u8) -> Self { + use CpuMode::*; + + return match raw { + 0b10000 => User, + 0b10001 => FastInterruptRequest, + 0b10010 => InterruptRequest, + 0b10011 => Supervisor, + 0b10111 => Abort, + 0b11011 => Undefined, + 0b11111 => System, + + _ => panic!("invalid cpu mode {raw:#010b}"), + }; + } + + pub fn name(self) -> &'static str { + use CpuMode::*; + + return match self { + Abort => "abt", + FastInterruptRequest => "fiq", + InterruptRequest => "irq", + Supervisor => "svc", + System => "sys", + Undefined => "und", + User => "usr", + }; + } +} diff --git a/src/exception.rs b/src/exception.rs new file mode 100644 index 0000000..301b84c --- /dev/null +++ b/src/exception.rs @@ -0,0 +1,64 @@ +/* + 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 . +*/ + +use crate::cpu_mode::CpuMode; + +#[repr(u8)] +#[derive(Clone, Copy)] +pub enum Exception { + Reset, + UndefinedInstruction, + SoftwareInterrupt, + PrefetchAbort, + DataAbort, + InterruptRequest, + FastInterruptRequest, +} + +impl Exception { + pub fn mode(self) -> CpuMode { + return match self { + Self::Reset => CpuMode::Supervisor, + Self::UndefinedInstruction => CpuMode::Undefined, + Self::SoftwareInterrupt => CpuMode::Supervisor, + Self::PrefetchAbort => CpuMode::Abort, + Self::DataAbort => CpuMode::Abort, + Self::InterruptRequest => CpuMode::InterruptRequest, + Self::FastInterruptRequest => CpuMode::FastInterruptRequest, + }; + } + + pub fn vector_address(self) -> u32 { + use Exception::*; + + return match self { + Reset => 0x00000000, + UndefinedInstruction => 0x00000004, + SoftwareInterrupt => 0x00000008, + PrefetchAbort => 0x0000000C, + DataAbort => 0x00000010, + InterruptRequest => 0x00000018, + FastInterruptRequest => 0x0000001C, + }; + } +} diff --git a/src/instruction.rs b/src/instruction.rs new file mode 100644 index 0000000..28338c4 --- /dev/null +++ b/src/instruction.rs @@ -0,0 +1,65 @@ +/* + 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 . +*/ + +use crate::shifter::Shifter; + +mod decode_arm; +mod decode_thumb; + +#[derive(Clone, Copy)] +pub enum Instruction { + Adc(u8, u8, Shifter, bool), + Add(u8, u8, Shifter, bool), + And(u8, u8, Shifter, bool), + B( i32), + Bic(u8, u8, Shifter, bool), + Bl( i32), + Bl0(i32), + Bl1(i32), + Bx( u8), + Cmn(u8 ,Shifter), + Cmp(u8, Shifter), + Eor(u8, u8, Shifter, bool), + Mov(u8, Shifter, bool), + Mul(u8, u8, Shifter, bool), + Mvn(u8, Shifter, bool), + Orr(u8, u8, Shifter, bool), + Rsb(u8, u8, Shifter, bool), + Rsc(u8, u8, Shifter, bool), + Sbc(u8, u8, Shifter, bool), + Sub(u8, u8, Shifter, bool), + Swi(u32), + Teq(u8, Shifter), + Tst(u8, Shifter), + + // Rework in next release: + LoadHalfword( u8, u8, i16), + LoadImmediateOffset( u8, u8, i16), + LoadPc( u8, u16), + StoreByteImmediateOffset(u8, u8, i16), + StoreByteRegisterOffset( u8, u8, u8), + StoreHalfword( u8, u8, i16), + StoreImmediateOffset( u8, u8, i16), + + Undefined, +} diff --git a/src/instruction/decode_arm.rs b/src/instruction/decode_arm.rs new file mode 100644 index 0000000..9efe859 --- /dev/null +++ b/src/instruction/decode_arm.rs @@ -0,0 +1,201 @@ +/* + 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 . +*/ + +use crate::instruction::Instruction; +use crate::predicate::Predicate; +use crate::shifter::Shifter; + +use std::hint::unreachable_unchecked; + +impl Instruction { + #[must_use] + pub fn decode_arm(opcode: u32) -> (Self, Predicate) { + use Instruction::*; + use Predicate::*; + + let predicate = (opcode & 0b11110000000000000000000000000000).wrapping_shr(0x1C) as u8; + let predicate = Predicate::from_raw(predicate); + + return match (opcode & 0b00001110000000000000000000000000).wrapping_shr(0x19) { + 0b000 => { + match (opcode & 0b00000000000000000000000000010000).wrapping_shr(0x4) { + 0b0 => { + let rd = (opcode & 0b00000000000000001111000000000000).wrapping_shr(0xC) as u8; + let rn = (opcode & 0b00000000000011110000000000000000).wrapping_shr(0x10) as u8; + let s = opcode & 0b00000000000100000000000000000000 != 0x0; + + let shifter = Shifter::extract(opcode); + + match (opcode & 0b00000001111000000000000000000000).wrapping_shr(0x15) { + 0b0000 => (And(rd, rn, shifter, s), predicate), + 0b0001 => (Eor(rd, rn, shifter, s), predicate), + 0b0010 => (Sub(rd, rn, shifter,s), predicate), + 0b0011 => (Rsb(rd, rn, shifter, s), predicate), + 0b0100 => (Add(rd, rn, shifter, s), predicate), + 0b0101 => (Adc(rd, rn, shifter, s), predicate), + 0b0110 => (Sbc(rd, rn, shifter, s), predicate), + 0b0111 => (Rsc(rd, rn, shifter, s), predicate), + 0b1000 => (Tst(rd, shifter), predicate), + 0b1001 => (Teq(rd, shifter), predicate), + 0b1010 => (Cmp(rd, shifter), predicate), + 0b1011 => (Cmn(rd, shifter), predicate), + 0b1100 => (Orr(rd, rn, shifter, s), predicate), + 0b1101 => (Mov(rd, shifter, s), predicate), + 0b1110 => (Bic(rd, rn, shifter, s), predicate), + 0b1111 => (Mvn(rd, shifter, s), predicate), + + _ => unsafe { unreachable_unchecked() }, + } + }, + + 0b1 => { + match (opcode & 0b00000000000000000000000010000000).wrapping_shr(0x7) { + 0b0 => { + match opcode & 0b00000001100100000000000000000000 { + 0b00000001000000000000000000000000 => { + let rm = (opcode & 0b00000000000000000000000000001111) as u8; + + match (opcode & 0b00000000011000000000000000000000).wrapping_shr(0x13) | (opcode & 0b00000000000000000000000001100000).wrapping_shr(0x5) { + 0b0000 => (Undefined, Al), + 0b0001 => (Undefined, Al), + 0b0010 => (Undefined, Al), + 0b0011 => (Undefined, Al), + 0b0100 => (Bx(rm), predicate), + 0b0101 => (Undefined, Al), + 0b0110 => (Undefined, Al), + 0b0111 => (Undefined, Al), + 0b1000 => (Undefined, Al), + 0b1001 => (Undefined, Al), + 0b1010 => (Undefined, Al), + 0b1011 => (Undefined, Al), + 0b1100 => (Undefined, Al), + 0b1101 => (Undefined, Al), + 0b1110 => (Undefined, Al), + 0b1111 => (Undefined, Al), + + _ => unsafe { unreachable_unchecked() }, + } + }, + + _ => (Undefined, Al), + } + }, + + 0b1 => (Undefined, Al), + + _ => unsafe { unreachable_unchecked() }, + } + }, + + _ => unsafe { unreachable_unchecked() }, + } + }, + + 0b001 => { + let rd = (opcode & 0b00000000000000001111000000000000).wrapping_shr(0xC) as u8; + let rn = (opcode & 0b00000000000011110000000000000000).wrapping_shr(0x10) as u8; + let s = opcode & 0b00000000000100000000000000000000 != 0x0; + + let shifter = Shifter::extract(opcode); + + match (opcode & 0b00000001111000000000000000000000).wrapping_shr(0x15) { + 0b0000 => (And(rd, rn, shifter, s), predicate), + 0b0001 => (Eor(rd, rn, shifter, s), predicate), + 0b0010 => (Sub(rd, rn, shifter, s), predicate), + 0b0011 => (Rsb(rd, rn, shifter, s), predicate), + 0b0100 => (Add(rd, rn, shifter, s), predicate), + 0b0101 => (Adc(rd, rn, shifter, s), predicate), + 0b0110 => (Sbc(rd, rn, shifter, s), predicate), + 0b0111 => (Rsc(rd, rn, shifter, s), predicate), + 0b1000 => (Tst(rn, shifter), predicate), + 0b1001 => (Teq(rn, shifter), predicate), + 0b1010 => (Cmp(rn, shifter), predicate), + 0b1011 => (Cmn(rn, shifter), predicate), + 0b1100 => (Orr(rd, rn, shifter, s), predicate), + 0b1101 => (Mov(rd, shifter, s), predicate), + 0b1110 => (Bic(rd, rn, shifter, s), predicate), + 0b1111 => (Mvn(rd, shifter, s), predicate), + + _ => unsafe { unreachable_unchecked() }, + } + }, + + 0b010 => { + let imm = (opcode & 0b00000000000000000000111111111111) as u16; + + let register = (opcode & 0b00000000000000001111000000000000).wrapping_shr(0xC) as u8; + + let base = (opcode & 0b00000000000011110000000000000000).wrapping_shr(0x10) as u8; + + let l = opcode & 0b00000000000100000000000000000000 != 0x0; + let _w = opcode & 0b00000000001000000000000000000000 != 0x0; + let _b = opcode & 0b00000000010000000000000000000000 != 0x0; + let u = opcode & 0b00000000100000000000000000000000 != 0x0; + let _p = opcode & 0b00000001000000000000000000000000 != 0x0; + + let imm = match u { + false => 0x0 - imm as i16, + true => imm as i16, + }; + + match l { + false => (StoreImmediateOffset(register, base, imm), predicate), + true => (LoadImmediateOffset( register, base, imm), predicate), + } + }, + + 0b011 => (Undefined, Al), + + 0b100 => (Undefined, Al), + + 0b101 => { + let offset = opcode & 0b00000000111111111111111111111111; + let offset = (offset << 0x8) as i32 >> 0x6; + + let l = opcode & 0b00000001000000000000000000000000 != 0x0; + + match l { + false => (B(offset), predicate), + true => (Bl(offset), predicate), + } + }, + + 0b110 => (Undefined, Al), + + 0b111 => { + match (opcode & 0b00000001000000000000000000000000).wrapping_shr(0x18) { + 0b0 => (Undefined, Al), + + 0b1 => { + let imm = opcode & 0b00000000111111111111111111111111; + (Swi(imm), predicate) + }, + + _ => unsafe { unreachable_unchecked() }, + } + }, + + _ => unsafe { unreachable_unchecked() }, + }; + } +} diff --git a/src/instruction/decode_thumb.rs b/src/instruction/decode_thumb.rs new file mode 100644 index 0000000..5735367 --- /dev/null +++ b/src/instruction/decode_thumb.rs @@ -0,0 +1,267 @@ +/* + 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 . +*/ + +use crate::instruction::Instruction; +use crate::predicate::Predicate; +use crate::shifter::Shifter; + +use std::hint::unreachable_unchecked; + +impl Instruction { + pub fn decode_thumb(opcode: u16) -> (Self, Predicate) { + use Instruction::*; + use Predicate::*; + use Shifter::*; + + return match (opcode & 0b1110000000000000).wrapping_shr(0xD) { + 0b000 => { + match (opcode & 0b0001100000000000).wrapping_shr(0xB) { + 0b11 => { + match (opcode & 0b0000010000000000).wrapping_shr(0xA) { + 0b0 => { + let rd = (opcode & 0b0000000000000111) as u8; + let rn = ((opcode & 0b0000000000111000).wrapping_shr(0x3)) as u8; + let rm = ((opcode & 0b0000000111000000).wrapping_shr(0x6)) as u8; + + match (opcode & 0b0000001000000000).wrapping_shr(0x9) { + 0b0 => (Add(rd, rn, Shifter::from_register(rm), true), Al), + 0b1 => (Sub(rd, rn, Shifter::from_register(rm), true), Al), + + _ => unsafe { unreachable_unchecked() }, + } + }, + + 0b1 => (Undefined, Al), + + _ => unsafe { unreachable_unchecked() }, + } + }, + + pattern => { + let rd = (opcode & 0b0000000000000111) as u8; + let rm = ((opcode & 0b0000000000111000).wrapping_shr(0x3)) as u8; + let imm = ((opcode & 0b0000011111000000).wrapping_shr(0x6)) as u8; + + match pattern { + 0b00 => (Mov(rd, LogicalShiftLeftImmediate(rm, imm), true), Al), + 0b01 => (Mov(rd, LogicalShiftRightImmediate(rm, imm), true), Al), + 0b10 => (Mov(rd, ArithmeticShiftRightImmediate(rm, imm), true), Al), + + _ => unsafe { unreachable_unchecked() }, + } + }, + } + }, + + 0b001 => { + let imm = (opcode & 0b0000000011111111) as u8; + let rd = ((opcode & 0b0000111100000000).wrapping_shr(0x8)) as u8; + + match (opcode & 0b0001100000000000).wrapping_shr(0xB) { + 0b00 => (Mov(rd, Immediate(imm, 0x0), true), Al), + 0b01 => (Cmp(rd, Immediate(imm, 0x0)), Al), + 0b10 => (Add(rd, rd, Immediate(imm, 0x0), true), Al), + 0b11 => (Sub(rd, rd, Immediate(imm, 0x0), true), Al), + + _ => unsafe { unreachable_unchecked() }, + } + }, + + 0b010 => { + match (opcode & 0b0001000000000000).wrapping_shr(0xC) { + 0b0 => { + match (opcode & 0b0000100000000000).wrapping_shr(0xB) { + 0b0 => { + match (opcode & 0b0000010000000000).wrapping_shr(0xA) { + 0b0 => { + let rd = (opcode & 0b0000000000000111) as u8; + let rm = (opcode & 0b0000000000111000).wrapping_shr(0x3) as u8; + + match (opcode & 0b0000001111000000).wrapping_shr(0x6) { + 0b0000 => (And(rd, rd, Shifter::from_register(rm), true), Al), + 0b0001 => (Eor(rd, rd, Shifter::from_register(rm), true), Al), + 0b0010 => (Mov(rd, LogicalShiftLeftRegister(rd, rm), true), Al), + 0b0011 => (Mov(rd, LogicalShiftRightRegister(rd, rm), true), Al), + 0b0100 => (Mov(rd, ArithmeticShiftRightRegister(rd, rm), true), Al), + 0b0101 => (Adc(rd, rd, Shifter::from_register(rm), true), Al), + 0b0110 => (Sbc(rd, rd, Shifter::from_register(rm), true), Al), + 0b0111 => (Mov(rd, RotateRightRegister(rd, rm), true), Al), + 0b1000 => (Tst(rd, Shifter::from_register(rm)), Al), + 0b1001 => (Rsb(rd, rm, Immediate(0x0, 0x0), true), Al), + 0b1010 => (Cmp(rd, Shifter::from_register(rm)), Al), + 0b1011 => (Cmn(rd, Shifter::from_register(rm)), Al), + 0b1100 => (Orr(rd, rd, Shifter::from_register(rm), true), Al), + 0b1101 => (Mul(rd, rd, Shifter::from_register(rm), true), Al), + 0b1110 => (Bic(rd, rd, Shifter::from_register(rm), true), Al), + 0b1111 => (Mvn(rd, Shifter::from_register(rm), true), Al), + + _ => unsafe { unreachable_unchecked() }, + } + }, + + 0b1 => { + let h0 = opcode & 0b0000000010000000 != 0x0; + let h1 = opcode & 0b0000000001000000 != 0x0; + + let rd = (opcode & 0b0000000000000111) as u8 | (h0 as u8) << 0x3; + let rm = ((opcode & 0b0000000000111000).wrapping_shr(0x3)) as u8 | (h1 as u8) << 0x3; + + match (opcode & 0b0000001100000000).wrapping_shr(0x8) { + 0b00 => (Undefined, Al), + + // Unpredictable if rd is pc or if both rd and rm + // are low registers. + 0b01 => (Cmp(rd, Shifter::from_register(rm)), Al), + 0b10 => (Mov(rd, Shifter::from_register(rm), true), Al), + // Unpredictable if h0 is true or if rd is non- + // zero. + 0b11 => (Bx(rm), Al), + + _ => unsafe { unreachable_unchecked() }, + } + }, + + _ => unsafe { unreachable_unchecked() }, + } + }, + + 0b1 => { + let imm = (opcode & 0b0000000011111111) as u8; + let rd = ((opcode & 0b0000011100000000).wrapping_shr(0x8)) as u8; + + let imm = (imm as u16).wrapping_mul(0x4); + + (LoadPc(rd, imm), Al) + }, + + _ => unsafe { unreachable_unchecked() }, + } + }, + + 0b1 => (Undefined, Al), + + _ => unsafe { unreachable_unchecked() }, + } + }, + + 0b011 => { + let rd = (opcode & 0b0000000000000111) as u8; + let rn = ((opcode & 0b0000000000111000).wrapping_shr(0x3)) as u8; + let imm = (opcode & 0b0000011111000000).wrapping_shr(0x6); + let l = opcode & 0b0000100000000000 != 0x0; + let b = opcode & 0b0001000000000000 != 0x0; + + let imm = imm.wrapping_shl(0x2) as i16; + + match l { + false => match b { + false => (StoreImmediateOffset( rd, rn, imm), Al), + true => (StoreByteImmediateOffset(rd, rn, imm), Al), + }, + + true => match b { + false => (LoadImmediateOffset( rd, rn, imm), Al), + true => (StoreByteImmediateOffset(rd, rn, imm), Al), // TODO + }, + } + }, + + 0b100 => { + match (opcode & 0b0001000000000000).wrapping_shr(0xC) { + 0b0 => { + let rd = (opcode & 0b0000000000000111) as u8; + let rn = ((opcode & 0b0000000000111000).wrapping_shr(0x3)) as u8; + let imm = (opcode & 0b0000011111000000).wrapping_shr(0x6) as u8; + let l = opcode & 0b0000010000000000 != 0x0; + + let imm = imm.wrapping_shl(0x1) as i16; + + match l { + false => (StoreHalfword(rd, rn, imm), Al), + true => (LoadHalfword( rd, rn, imm), Al), + } + }, + + 0b1 => (Undefined, Al), + + _ => unsafe { unreachable_unchecked() }, + } + }, + + 0b101 => (Undefined, Al), + + 0b110 => { + match (opcode & 0b0001000000000000).wrapping_shr(0xC) { + 0b0 => (Undefined, Al), + + 0b1 => { + let predicate = ((opcode & 0b0000111100000000).wrapping_shr(0x8)) as u8; + + let imm = opcode & 0b0000000011111111; + + match predicate { + 0b1111 => (Swi(imm as u32), Al), + + _ => { + let predicate = Predicate::from_raw(predicate); + let imm = ((imm as u32) << 0x18) as i32 >> 0x17; + + (B(imm), predicate) + }, + } + }, + + _ => unsafe { unreachable_unchecked() }, + } + }, + + 0b111 => { + let imm = (opcode & 0b0000011111111111) as u32; + + match (opcode & 0b0001100000000000).wrapping_shr(0xB) { + 0b00 => { + let imm = imm.wrapping_shl(0x15) as i32 >> 0x14; + (B(imm), Al) + }, + + // Undefined in ARMv4 (later blx suffix). + 0b01 => (Undefined, Al), + + 0b10 => { + let imm = imm.wrapping_shl(0x15) as i32 >> 0x9; + (Bl0(imm), Al) + }, + + 0b11 => { + let imm = imm.wrapping_shl(0x1) as i32; + (Bl1(imm), Al) + }, + + _ => unsafe { unreachable_unchecked() }, + } + }, + + _ => unsafe { unreachable_unchecked() }, + }; + } +} diff --git a/src/luma.rs b/src/luma.rs index 9fd490a..d0887fd 100644 --- a/src/luma.rs +++ b/src/luma.rs @@ -21,18 +21,27 @@ If not, see . */ +extern crate ctrlc; +extern crate sdl2; +extern crate toml; + pub mod app; pub mod configuration; pub mod cpu; pub mod cpu_handle; +pub mod cpu_mode; +pub mod exception; pub mod instruction; +pub mod predicate; +pub mod shifter; pub mod state; pub const VERSION: (u32, u32) = ( 0x0, // major - 0x2E, // minor + 0x2F, // minor ); +#[derive(Clone, Copy)] pub enum Error { BadAlignment( u32, u32), InvalidArmOpcode( u32, u32), @@ -67,7 +76,7 @@ pub const SCREEN_SIZE: (u8, u8) = ( macro_rules! log { () => { - eprintln!(); + if cfg!(debug_assertions) { eprintln!() }; }; ($($message: tt)*) => {{ @@ -80,11 +89,20 @@ pub(crate) use log; macro_rules! log_assignment { ($name: expr, $value: expr) => {{ - use crate::luma::log; + use crate::log; - if cfg!(debug_assertions) { - log!("\u{1B}[3m\u{B7} \u{1B}[1m{}\u{1B}[22m = {}\u{1B}[0m", format!($name), format!($value)); - } + log!("\u{1B}[3m\u{B7} \u{1B}[1m{}\u{1B}[22m = {}\u{1B}[0m", $name, $value); }}; } pub(crate) use log_assignment; + +macro_rules! log_status { + ($($message: tt)*) => {{ + use crate::log; + + log!("({})", format!($($message)?)); + }}; +} +pub(crate) use log_status; + +fn main() { app::App::main() } diff --git a/src/luma/agb.rs b/src/luma/agb.rs deleted file mode 100644 index 01a77b8..0000000 --- a/src/luma/agb.rs +++ /dev/null @@ -1,25 +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 . -*/ - -pub mod arm; -pub mod thumb; diff --git a/src/luma/app.rs b/src/luma/app.rs deleted file mode 100644 index c8e0134..0000000 --- a/src/luma/app.rs +++ /dev/null @@ -1,46 +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 . -*/ - -use sdl2::Sdl; -use sdl2::render::WindowCanvas; -use std::sync::Arc; -use std::sync::atomic::AtomicBool; - -pub mod check_events; -pub mod draw_video; -pub mod init; -pub mod load; -pub mod run; -pub mod sync_video; - -pub struct App { - bootloader: String, - image: String, - - scale: u32, - - got_terminate: Arc::, - - sdl: Sdl, - canvas: WindowCanvas, -} diff --git a/src/luma/app/check_events.rs b/src/luma/app/check_events.rs deleted file mode 100644 index 288a093..0000000 --- a/src/luma/app/check_events.rs +++ /dev/null @@ -1,52 +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 . -*/ - -use crate::luma::app::App; - -use sdl2::event::Event; -use std::sync::atomic::Ordering; - -impl App { - pub fn check_events(&mut self) -> Result { - // Return true if we should quit. - - let mut event_pump = match self.sdl.event_pump() { - Ok(pump) => pump, - _ => return Err("unable to get event pump".to_string()), - }; - - if self.got_terminate.load(Ordering::Relaxed) { - eprintln!("got terminate"); - return Ok(true) - }; - - for event in event_pump.poll_iter() { - match event { - Event::Quit {..} => return Ok(true), - _ => {}, - }; - } - - return Ok(false); - } -} diff --git a/src/luma/app/draw_video.rs b/src/luma/app/draw_video.rs deleted file mode 100644 index 4acb012..0000000 --- a/src/luma/app/draw_video.rs +++ /dev/null @@ -1,71 +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 . -*/ - -use crate::luma::SCREEN_SIZE; -use crate::luma::app::App; - -use sdl2::pixels::Color; -use sdl2::rect::Rect; - -impl App { - pub fn draw_video(&mut self, video: &[u8], agb_palette: &[u16]) { - // TO-DO: Honour video mode. - - let mut palette: [Color; 0x100] = [Color::RGB(0x00, 0x00, 0x00); 0x100]; - - for (index, element) in palette.iter_mut().enumerate() { - let value = unsafe { *agb_palette.get_unchecked(index) }; - - let colour = decode_colour(value); - *element = colour; - } - - for pixel_y in 0x0..SCREEN_SIZE.1 { - for pixel_x in 0x0..SCREEN_SIZE.0 { - let pixel = pixel_y as usize * SCREEN_SIZE.0 as usize + pixel_x as usize; - - let value = video[pixel]; - let colour = palette[value as usize]; - self.canvas.set_draw_color(colour); - - let square = Rect::new( - (pixel_x as u32 * self.scale) as i32, - (pixel_y as u32 * self.scale) as i32, - self.scale, - self.scale, - ); - self.canvas.fill_rect(square).unwrap(); - } - } - - 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/init.rs b/src/luma/app/init.rs deleted file mode 100644 index 2d826aa..0000000 --- a/src/luma/app/init.rs +++ /dev/null @@ -1,88 +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 . -*/ - -use crate::luma::{SCREEN_SIZE, VERSION}; -use crate::luma::app::App; -use crate::luma::configuration::Configuration; - -use sdl2::pixels::Color; -use sdl2::render::BlendMode; -use std::sync::Arc; -use std::sync::atomic::{AtomicBool, Ordering}; - -impl App { - pub fn init(configuration: Configuration) -> Result { - let got_terminate = Arc::new(AtomicBool::new(false)); - - match ctrlc::set_handler({ - let got_terminate = got_terminate.clone(); - move || got_terminate.store(true, Ordering::Relaxed) - }) { - Err(..) => return Err("unable to set signal handler".to_string()), - _ => {}, - }; - - let sdl = match sdl2::init() { - Ok( sdl) => sdl, - Err(..) => return Err("unable to initialise sdl2".to_string()), - }; - - let sdl_video = match sdl.video() { - Ok( video) => video, - Err(..) => return Err("unable to initialise video".to_string()), - }; - - let window_title = format!("Luma {:X}.{:X}", VERSION.0, VERSION.1); - - let mut window_builder = sdl_video.window(&window_title, SCREEN_SIZE.0 as u32 * configuration.scale, SCREEN_SIZE.1 as u32 * configuration.scale); - window_builder.position_centered(); - - let window = match window_builder.build() { - Ok( window) => window, - Err(..) => return Err("unable to open window".to_string()), - }; - - let mut canvas = match window.into_canvas().build() { - Ok( canvas) => canvas, - Err(..) => return Err("unable to build canvas".to_string()), - }; - - canvas.set_blend_mode(BlendMode::Blend); - - let clear_colour = Color::RGB(0x00, 0x00, 0x00); - canvas.set_draw_color(clear_colour); - canvas.clear(); - canvas.present(); - - return Ok(App { - bootloader: configuration.bootloader, - image: configuration.image, - - scale: configuration.scale, - - got_terminate: got_terminate, - - sdl: sdl, - canvas: canvas, - }); - } -} diff --git a/src/luma/app/load.rs b/src/luma/app/load.rs deleted file mode 100644 index 5c67daf..0000000 --- a/src/luma/app/load.rs +++ /dev/null @@ -1,96 +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 . -*/ - -use crate::luma::VERSION; -use crate::luma::app::App; -use crate::luma::state::State; - -use std::fs::File; -use std::io::Read; - -impl App { - pub fn load(&mut self, state: &mut State) -> Result<(), String> { - eprintln!("loading booatloader \"{}\"", self.bootloader); - - let mut bootloader = match File::open(&self.bootloader) { - Ok(file) => file, - _ => return Err("unable to open bootloader".to_string()), - }; - - if let Err(..) = bootloader.read(state.bootloader_buffer()) { return Err("unable to read bootloader".to_string()) }; - - eprintln!("loading image \"{}\"", self.image); - - let mut image = match File::open(&self.image) { - Ok(file) => file, - _ => return Err("unable to open image".to_string()), - }; - - match image.read(state.image_buffer()) { - Err(..) => return Err("unable to read image".to_string()), - _ => {}, - }; - - let title = get_title(&state.image_buffer()[0xA0..0xAC]); - let id = get_id(&state.image_buffer()[0xAC..0xB0]); - let version = state.image_buffer()[0xBC]; - - eprintln!("loaded image \"{title}\" ({id}) v.{version}"); - - self.canvas.window_mut().set_title(&format!("Luma {:X}.{:X} - {title}", VERSION.0, VERSION.1)).unwrap(); - - return Ok(()); - } -} - -fn get_title(data: &[u8]) -> String { - let mut title = String::with_capacity(0xC); - - for raw in data { - let character = match char::from_u32(*raw as u32) { - Some('\u{0000}') => break, - Some(character) => character, - None => '?', - }; - - title.push(character); - } - - return title; -} - -fn get_id(data: &[u8]) -> String { - let mut id = String::with_capacity(0xC); - - for raw in data { - let character = match char::from_u32(*raw as u32) { - Some('\u{0000}') => break, - Some(character) => character, - None => '?', - }; - - id.push(character); - } - - return id; -} diff --git a/src/luma/app/run.rs b/src/luma/app/run.rs deleted file mode 100644 index 6c00153..0000000 --- a/src/luma/app/run.rs +++ /dev/null @@ -1,58 +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 . -*/ - -use crate::luma::{PALETTE_LENGTH, VIDEO_LENGTH}; -use crate::luma::app::App; -use crate::luma::cpu::Cpu; -use crate::luma::state::State; - -use std::time::Instant; - -impl App { - pub fn run(mut self) -> Result<(), String> { - let mut state = State::new(); - - self.load(&mut state)?; - - let cpu = Cpu::new(state); - - let mut cpu = cpu.boot(); - - let mut video_buffer: Vec:: = vec![0x0; VIDEO_LENGTH as usize]; - let mut palette_buffer: Vec:: = vec![0x0; (PALETTE_LENGTH / 0x2) as usize]; - - 'main_loop: loop { - let frame_start = Instant::now(); - - if self.check_events()? { break 'main_loop }; - - cpu.dump_video( &mut video_buffer[..]); - cpu.dump_palette(&mut palette_buffer[..]); - self.draw_video(&video_buffer[..], &palette_buffer[..]); - - self.sync_video(frame_start); - } - - return Ok(()); - } -} diff --git a/src/luma/app/sync_video.rs b/src/luma/app/sync_video.rs deleted file mode 100644 index 10c6abc..0000000 --- a/src/luma/app/sync_video.rs +++ /dev/null @@ -1,44 +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 . -*/ - -use crate::luma::app::App; - -use std::thread::sleep; -use std::time::{Duration, Instant}; - -impl App { - pub fn sync_video(&self, frame_start: Instant) { - // Courtesy of TASVideos: - // 59.7275005696058 Hz - - const FRAME_DURATION: u64 = 0xFF7932; - let frame_duration = Duration::from_nanos(FRAME_DURATION); - - let remaining = match frame_duration.checked_sub(frame_start.elapsed()) { - Some(value) => value, - None => Duration::from_secs(0x0), - }; - - sleep(remaining); - } -} diff --git a/src/luma/configuration.rs b/src/luma/configuration.rs deleted file mode 100644 index facbc69..0000000 --- a/src/luma/configuration.rs +++ /dev/null @@ -1,36 +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 . -*/ - -pub mod load; -pub mod validate; - -pub struct Configuration { - pub bootloader: String, - pub image: String, - - pub scale: u32, -} - -impl Configuration { - pub const VERSION: u32 = 0x0; -} diff --git a/src/luma/configuration/load.rs b/src/luma/configuration/load.rs deleted file mode 100644 index bfd91b0..0000000 --- a/src/luma/configuration/load.rs +++ /dev/null @@ -1,100 +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 . -*/ - -use crate::luma::configuration::Configuration; - -extern crate toml; - -use std::fs::read_to_string; -use std::str::FromStr; -use toml::{Table, Value}; - -impl Configuration { - pub fn load(path: &str) -> Result { - eprintln!("loading configuration at \"{path}\""); - - let configuration_text = match read_to_string(path) { - Ok( content) => content, - _ => return Err("unable to read file".to_string()), - }; - - let base_table = match Table::from_str(&configuration_text) { - Ok( table) => table, - _ => return Err("unable to parse configuration".to_string()), - }; - - let luma_table = get_table(&base_table, "luma")?; - let device_table = get_table(&base_table, "device")?; - let video_table = get_table(&base_table, "video")?; - - let version = get_integer(&luma_table, "version")?; - - if version < Self::VERSION { return Err(format!("ancient version - got {}, expected {}", version, Self::VERSION)) } - if version > Self::VERSION { return Err(format!("out-of-date version - got {}, expected {}", version, Self::VERSION)) } - - let bootloader = get_string(&device_table, "bootloader")?; - let image = get_string(&device_table, "image")?; - - let scale = get_integer(&video_table, "scale")?; - - let configuration = Configuration { - bootloader: bootloader.clone(), - image: image.clone(), - - scale: scale, - }; - - configuration.validate()?; - return Ok(configuration); - } -} - -fn get_value<'a>(table: &'a Table, name: &str) -> Option<&'a Value> { - if !table.contains_key(name) { return None }; - - return Some(&table[name]); -} - -fn get_table<'a>(table: &'a Table, name: &str) -> Result<&'a Table, String> { - return match get_value(table, name) { - Some(Value::Table(table)) => Ok(table), - Some(_) => Err(format!("\"{name}\" should be a section")), - _ => Err("section \"{name}\" is required".to_string()), - }; -} - -fn get_integer(table: &Table, name: &str) -> Result { - return match get_value(table, name) { - Some(Value::Integer(value)) => Ok(*value as u32), - Some(_) => Err(format!("\"{name}\" should be an integer")), - _ => Err("missing integer \"{name}\"".to_string()), - }; -} - -fn get_string<'a>(table: &'a Table, name: &str) -> Result<&'a String, String> { - return match get_value(table, name) { - Some(Value::String(string)) => Ok(string), - Some(_) => Err(format!("\"{name}\" should be a string")), - _ => Err("missing string \"{name}\"".to_string()), - }; -} diff --git a/src/luma/configuration/validate.rs b/src/luma/configuration/validate.rs deleted file mode 100644 index 9cdd135..0000000 --- a/src/luma/configuration/validate.rs +++ /dev/null @@ -1,32 +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 . -*/ - -use crate::luma::configuration::Configuration; - -impl Configuration { - pub(super) fn validate(&self) -> Result<(), String> { - if self.scale < 0x1 { return Err("scale must be at least 1".to_string()) }; - - return Ok(()); - } -} diff --git a/src/luma/cpu.rs b/src/luma/cpu.rs deleted file mode 100644 index 28f6844..0000000 --- a/src/luma/cpu.rs +++ /dev/null @@ -1,78 +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 . -*/ - -use crate::luma::instruction::Instruction; -use crate::luma::state::State; - -use std::sync::{Arc, Mutex}; -use std::sync::atomic::AtomicBool; - -pub mod boot; -pub mod decode_arm; -pub mod decode_thumb; -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; - -// https://github.com/rust-lang/rust/issues/115966 - -#[allow(unused_imports)] -pub use exchange::*; - -#[allow(unused_imports)] -pub use test_predicate::*; - -pub type Decoder = fn(&mut Cpu) -> Instruction; - -pub struct Cpu { - state: Arc>, - cycle: u64, - dead: Arc, - - instruction_size: u32, - decoder: Decoder, -} - -impl Cpu { - pub fn new(state: State) -> Self { - return Self { - state: Arc::new(Mutex::new(state)), - cycle: 0x0, - dead: Arc::new(AtomicBool::new(false)), - - instruction_size: 0x4, - decoder: Self::decode_arm, - }; - } - - #[inline(always)] - #[must_use] - fn decode(&mut self) -> Instruction { (self.decoder)(self) } -} diff --git a/src/luma/cpu/boot.rs b/src/luma/cpu/boot.rs deleted file mode 100644 index 4a089eb..0000000 --- a/src/luma/cpu/boot.rs +++ /dev/null @@ -1,131 +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 . -*/ - -use crate::luma::cpu::Cpu; -use crate::luma::cpu_handle::CpuHandle; -use crate::luma::instruction::Instruction; - -use std::sync::atomic::Ordering; -use std::thread::{sleep, spawn}; -use std::time::{Duration, Instant}; - -impl Cpu { - pub fn boot(self) -> CpuHandle { - let state = self.state.lock().unwrap(); - - eprintln!("starting emulation at {:#010X}", state.read_register(0xF).wrapping_sub(0x8)); - - drop(state); - - let state = self.state.clone(); - let dead = self.dead.clone(); - - let handle = spawn(move || { self.run() }); - - return CpuHandle::new( - state, - dead, - - handle, - ); - } - - fn run(mut self) { - let run_timer = Instant::now(); - - 'main_loop: loop { - if self.dead.load(Ordering::Relaxed) { break 'main_loop }; - - let instruction = self.decode(); - self.execute(instruction); - - if cfg!(debug_assertions) { sleep(Duration::from_millis(125)) }; - - self.cycle += 0x1; - } - - let frequency = self.cycle as f64 / run_timer.elapsed().as_micros() as f64; - eprintln!("emulated {} cycle(s) ({frequency:.9} MHz)", self.cycle); - } - - #[inline(always)] - fn execute(&mut self, instruction: Instruction) { - use Instruction::*; - - match instruction { - // 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 => {}, - }; - - self.r#continue(); - } -} diff --git a/src/luma/cpu/continue.rs b/src/luma/cpu/continue.rs deleted file mode 100644 index 6d10bc3..0000000 --- a/src/luma/cpu/continue.rs +++ /dev/null @@ -1,33 +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 . -*/ - -use crate::luma::cpu::Cpu; - -impl Cpu { - pub(super) fn r#continue(&mut self) { - let mut state = self.state.lock().unwrap(); - - let pc = state.read_register(0xF).wrapping_add(self.instruction_size); - state.write_register(0xF, pc); - } -} diff --git a/src/luma/cpu/decode_arm.rs b/src/luma/cpu/decode_arm.rs deleted file mode 100644 index 2e7e882..0000000 --- a/src/luma/cpu/decode_arm.rs +++ /dev/null @@ -1,258 +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 . -*/ - -use crate::luma::{Error, log}; -use crate::luma::cpu::Cpu; -use crate::luma::instruction::Instruction; - -use std::hint::unreachable_unchecked; - -impl Cpu { - #[must_use] - pub(super) fn decode_arm(&mut self) -> Instruction { - let state = self.state.lock().unwrap(); - - let address = state.read_register(0xF).wrapping_sub(0x8); - let opcode = state.read_word(address); - - drop(state); - - log!(); - log!("\u{1B}[1m{opcode:032b}\u{1B}[0m @ \u{1B}[1m{address:08X}\u{1B}[0m - ({})", self.cycle); - - return decode(address, opcode); - } -} - -#[must_use] -fn decode(address: u32, opcode: u32) -> Instruction { - use Instruction::*; - - match (opcode & 0b00001110000000000000000000000000).wrapping_shr(0x19) { - 0b000 => { - match (opcode & 0b00000000000000000000000000010000).wrapping_shr(0x4) { - 0b0 => { - let _source = (opcode & 0b00000000000000000000000000001111) as u8; - - let _destination = (opcode & 0b00000000000000001111000000000000).wrapping_shr(0xC) as u8; - - let _base = (opcode & 0b00000000000011110000000000000000).wrapping_shr(0x10) as u8; - - let _s = opcode & 0b00000000000100000000000000000000 != 0x0; - - match (opcode & 0b00000001111000000000000000000000).wrapping_shr(0x15) { - 0b0000 => {}, - - 0b0001 => {}, - - 0b0010 => {}, - - 0b0011 => {}, - - 0b0100 => {}, - - 0b0101 => {}, - - 0b0110 => {}, - - 0b0111 => {}, - - 0b1000 => {}, - - 0b1001 => {}, - - 0b1010 => {}, - - 0b1011 => {}, - - 0b1100 => {}, - - 0b1101 => {}, - - 0b1110 => {}, - - 0b1111 => {}, - - _ => unsafe { unreachable_unchecked() }, - } - }, - - 0b1 => { - let source = (opcode & 0b00000000000000000000000000001111) as u8; - - let _shift = (opcode & 0b00000000000000000000000011110000) as u8; - - let _destination = (opcode & 0b00000000000000001111000000000000).wrapping_shr(0xC) as u8; - - let _base = (opcode & 0b00000000000011110000000000000000).wrapping_shr(0x10) as u8; - - let _s = opcode & 0b00000000000100000000000000000000 != 0x0; - - match (opcode & 0b00000001111000000000000000000000).wrapping_shr(0x15) { - 0b0000 => {}, - - 0b0001 => {}, - - 0b0010 => {}, - - 0b0011 => {}, - - 0b0100 => {}, - - 0b0101 => {}, - - 0b0110 => {}, - - 0b0111 => {}, - - 0b1000 => {}, - - 0b1001 => { - // Unpredictable if any of shift, destination, or - // base is non-zero. Unpredictable if s is true. - - return BranchExchange(source); - }, - - 0b1010 => {}, - - 0b1011 => {}, - - 0b1100 => {}, - - 0b1101 => {}, - - 0b1110 => {}, - - 0b1111 => {}, - - _ => unsafe { unreachable_unchecked() }, - } - }, - - _ => unsafe { unreachable_unchecked() }, - } - }, - - 0b001 => { - let immediate = (opcode & 0b00000000000000000000000011111111) as u8; - - let rotate = (opcode & 0b00000000000000000000111100000000).wrapping_shr(0x8); - - let destination = (opcode & 0b00000000000000001111000000000000).wrapping_shr(0xC) as u8; - - let base = (opcode & 0b00000000000011110000000000000000).wrapping_shr(0x10) as u8; - - let _s = opcode & 0b00000000000100000000000000000000 != 0x0; - - let rotate = rotate << 0x1; - let immediate = (immediate as u32).rotate_right(rotate); - - match (opcode & 0b00000001111000000000000000000000).wrapping_shr(0x15) { - 0b0000 => {}, - - 0b0001 => {}, - - 0b0010 => {}, - - 0b0011 => {}, - - 0b0100 => return AddImmediate(destination, base, immediate), - - 0b0101 => {}, - - 0b0110 => {}, - - 0b0111 => {}, - - 0b1000 => {}, - - 0b1001 => {}, - - 0b1010 => {}, - - 0b1011 => {}, - - 0b1100 => {}, - - // Unpredictable if base is non-zero. - 0b1101 => return MoveImmediate(destination, immediate), - - 0b1110 => {}, - - 0b1111 => {}, - - _ => unsafe { unreachable_unchecked() }, - } - }, - - 0b010 => { - let immediate = (opcode & 0b00000000000000000000111111111111) as u16; - - let register = (opcode & 0b00000000000000001111000000000000).wrapping_shr(0xC) as u8; - - let base = (opcode & 0b00000000000011110000000000000000).wrapping_shr(0x10) as u8; - - let l = opcode & 0b00000000000100000000000000000000 != 0x0; - let _w = opcode & 0b00000000001000000000000000000000 != 0x0; - let _b = opcode & 0b00000000010000000000000000000000 != 0x0; - let u = opcode & 0b00000000100000000000000000000000 != 0x0; - let _p = opcode & 0b00000001000000000000000000000000 != 0x0; - - let offset = match u { - false => 0x0 - immediate as i16, - true => 0x0 + immediate as i16, - }; - - return match l { - false => StoreImmediateOffset(register, base, offset), - true => LoadImmediateOffset( register, base, offset), - }; - }, - - 0b011 => {}, - - 0b100 => {}, - - 0b101 => { - let offset = opcode & 0b00000000111111111111111111111111; - let offset = (offset << 0x8) as i32 >> 0x6; - - let l = opcode & 0b00000001000000000000000000000000 != 0x0; - - return match l { - false => Branch( offset), - true => BranchLink(offset), - }; - }, - - 0b110 => {}, - - 0b111 => {}, - - _ => unsafe { unreachable_unchecked() }, - } - - Error::InvalidArmOpcode(address, opcode).trap(); - return Undefined; -} diff --git a/src/luma/cpu/decode_thumb.rs b/src/luma/cpu/decode_thumb.rs deleted file mode 100644 index 43cbcfa..0000000 --- a/src/luma/cpu/decode_thumb.rs +++ /dev/null @@ -1,312 +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 . -*/ - -use crate::luma::{Error, log}; -use crate::luma::cpu::{Cpu, test_predicate}; -use crate::luma::instruction::Instruction; - -use std::hint::unreachable_unchecked; - -impl Cpu { - pub(super) fn decode_thumb(&mut self) -> Instruction { - use Instruction::*; - - let state = self.state.lock().unwrap(); - - let address = state.read_register(0xF).wrapping_sub(0x4); - let opcode = state.read_halfword(address); - - let cpsr = state.read_cpsr(); - - drop(state); - - 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 => { - match (opcode & 0b0001100000000000).wrapping_shr(0xB) { - 0b11 => { - match (opcode & 0b0000010000000000).wrapping_shr(0xA) { - 0b0 => { - let destination = (opcode & 0b0000000000000111) as u8; - - let base = ((opcode & 0b0000000000111000).wrapping_shr(0x3)) as u8; - - let register = ((opcode & 0b0000000111000000).wrapping_shr(0x6)) as u8; - - match (opcode & 0b0000001000000000).wrapping_shr(0x9) { - 0b0 => return AddRegister(destination, base, register), - - 0b1 => return SubtractRegister(destination, base, register), - - _ => unsafe { unreachable_unchecked() }, - } - }, - - 0b1 => { - }, - - _ => unsafe { unreachable_unchecked() }, - } - }, - - pattern => { - let destination = (opcode & 0b0000000000000111) as u8; - - let base = ((opcode & 0b0000000000111000).wrapping_shr(0x3)) as u8; - - let immediate = ((opcode & 0b0000011111000000).wrapping_shr(0x6)) as u8; - - match pattern { - 0b00 => return MoveImmediateLogicalShiftLeftImmediate(destination, base, immediate), - - 0b01 => return MoveImmediateLogicalShiftRightImmediate(destination, base, immediate), - - 0b10 => return MoveImmediateArithmeticShiftRight(destination, base, immediate), - - _ => unsafe { unreachable_unchecked() }, - } - }, - } - }, - - 0b001 => { - let immediate = (opcode & 0b0000000011111111) as u32; - - let destination = ((opcode & 0b0000111100000000).wrapping_shr(0x8)) as u8; - - match (opcode & 0b0001100000000000).wrapping_shr(0xB) { - 0b00 => return MoveImmediate(destination, immediate), - - 0b01 => return CompareImmediate(destination, immediate), - - 0b10 => return AddImmediate(destination, destination, immediate), - - 0b11 => return SubtractImmediate(destination, destination, immediate), - - _ => unsafe { unreachable_unchecked() }, - } - }, - - 0b010 => { - match (opcode & 0b0001000000000000).wrapping_shr(0xC) { - 0b0 => { - match (opcode & 0b0000100000000000).wrapping_shr(0xB) { - 0b0 => { - match (opcode & 0b0000010000000000).wrapping_shr(0xA) { - 0b0 => { - let left = (opcode & 0b0000000000000111) as u8; - - let right = (opcode & 0b0000000000111000).wrapping_shr(0x3) as u8; - - match (opcode & 0b0000001111000000).wrapping_shr(0x6) { - 0b0000 => return AndRegister(left, left, right), - - 0b0001 => return ExclusiveOrRegister(left, left, right), - - 0b0010 => return MoveRegisterLogicalShiftLeftImmediate(left, left, right), - - 0b0011 => return MoveRegisterLogicalShiftRightImmediate(left, left, right), - - 0b0100 => return MoveRegisterArithmeticShiftRight(left, left, right), - - 0b0101 => return AddCarryRegister(left, left, right), - - 0b0110 => return SubtractCarryRegister(left, left, right), - - 0b0111 => return MoveRegisterRotateRight(left, left, right), - - 0b1000 => return TestRegister(left, right), - - 0b1001 => return ReverseSubtractImmediate(left, right, 0x0), - - 0b1010 => return CompareRegister(left, right), - - 0b1011 => return CompareNegativeRegister(left, right), - - 0b1100 => return LogicalOrRegister(left, right, right), - - 0b1101 => return MultiplyRegister(left, left, right), - - 0b1110 => return BitClearRegister(left, left, right), - - 0b1111 => return MoveNotRegister(left, right), - - _ => unsafe { unreachable_unchecked() }, - } - }, - - 0b1 => { - let h0 = opcode & 0b0000000010000000 != 0x0; - let h1 = opcode & 0b0000000001000000 != 0x0; - - let destination = (opcode & 0b0000000000000111) as u8; - let destination = destination | (h0 as u8) << 0x3; - - let source = ((opcode & 0b0000000000111000).wrapping_shr(0x3)) as u8; - let source = source | (h1 as u8) << 0x3; - - match (opcode & 0b0000001100000000).wrapping_shr(0x8) { - 0b00 => {}, - - // Unpredictable if destination is pc or if both it - // and source are low registers. - 0b01 => return CompareRegister(destination, source), - - 0b10 => return MoveRegister(destination, source), - - // Unpredictable if h0 is true or if destination is - // non-zero. - 0b11 => return BranchExchange(source), - - _ => unsafe { unreachable_unchecked() }, - } - }, - - _ => unsafe { unreachable_unchecked() }, - } - }, - - 0b1 => { - let immediate = opcode & 0b0000000011111111; - - let destination = ((opcode & 0b0000011100000000).wrapping_shr(0x8)) as u8; - - let offset = (immediate as i16).wrapping_mul(0x4); - - return LoadPc(destination, offset); - }, - - _ => unsafe { unreachable_unchecked() }, - } - }, - - 0b1 => {}, - - _ => unsafe { unreachable_unchecked() }, - } - }, - - 0b011 => { - let register = (opcode & 0b0000000000000111) as u8; - - let base = ((opcode & 0b0000000000111000).wrapping_shr(0x3)) as u8; - - let immediate = (opcode & 0b0000011111000000).wrapping_shr(0x6); - - let _l = opcode & 0b0000100000000000 != 0x0; - let b = opcode & 0b0001000000000000 != 0x0; - - let offset = immediate.wrapping_shl(0x2) as i16; - - return match b { - false => StoreImmediateOffset( register, base, offset), - true => StoreByteImmediateOffset(register, base, offset), - }; - }, - - 0b100 => { - match (opcode & 0b0001000000000000).wrapping_shr(0xC) { - 0b0 => { - let register = (opcode & 0b0000000000000111) as u8; - - let base = ((opcode & 0b0000000000111000).wrapping_shr(0x3)) as u8; - - let immediate = (opcode & 0b0000011111000000).wrapping_shr(0x6); - - let l = opcode & 0b0000010000000000 != 0x0; - - let offset = immediate.wrapping_shl(0x1) as i16; - - return match l { - false => StoreHalfword(register, base, offset), - true => LoadHalfword( register, base, offset), - }; - }, - - 0b1 => {}, - - _ => unsafe { unreachable_unchecked() }, - } - }, - - 0b101 => { - }, - - 0b110 => { - match (opcode & 0b0001000000000000).wrapping_shr(0xC) { - 0b0 => {}, - - 0b1 => { - let predicate = ((opcode & 0b0000111100000000).wrapping_shr(0x8)) as u8; - - if !test_predicate!(cpsr, predicate) { return Undefined }; - - let immediate = opcode & 0b0000000011111111; - - let offset = ((immediate as u32) << 0x18) as i32 >> 0x17; - - return Branch(offset); - }, - - _ => unsafe { unreachable_unchecked() }, - } - }, - - 0b111 => { - let immediate = (opcode & 0b0000011111111111) as u32; - - match (opcode & 0b0001100000000000).wrapping_shr(0xB) { - 0b00 => { - let offset = immediate.wrapping_shl(0x15) as i32 >> 0x14; - - return Branch(offset); - }, - - // Undefined in ARMv4 (later blx suffix). - 0b01 => {}, - - 0b10 => { - let offset = immediate.wrapping_shl(0x15) as i32 >> 0x9; - - return BranchLinkPrefix(offset); - }, - - 0b11 => { - let offset = immediate.wrapping_shl(0x1) as i32; - - return BranchLinkSuffix(offset); - }, - - _ => unsafe { unreachable_unchecked() }, - } - }, - - _ => unsafe { unreachable_unchecked() }, - } - - Error::InvalidThumbOpcode(address, opcode).trap(); - return Undefined; - } -} diff --git a/src/luma/cpu/exchange.rs b/src/luma/cpu/exchange.rs deleted file mode 100644 index 2414831..0000000 --- a/src/luma/cpu/exchange.rs +++ /dev/null @@ -1,39 +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 . -*/ - -macro_rules! exchange { - ($cpu: expr, $t: expr) => {{ - use crate::luma::cpu::Decoder; - - const DATA: [(u32, Decoder); 0x2] = [ - (0x4, Cpu::decode_arm), - (0x2, Cpu::decode_thumb), - ]; - - let index = $t as usize & 0b1; - - $cpu.instruction_size = unsafe { DATA.get_unchecked(index).0 }; - $cpu.decoder = unsafe { DATA.get_unchecked(index).1 }; - }}; -} -pub(crate) use exchange; diff --git a/src/luma/cpu/isa_arithmetic.rs b/src/luma/cpu/isa_arithmetic.rs deleted file mode 100644 index 3fe8830..0000000 --- a/src/luma/cpu/isa_arithmetic.rs +++ /dev/null @@ -1,122 +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 . -*/ - -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 deleted file mode 100644 index f2ed822..0000000 --- a/src/luma/cpu/isa_bitwise.rs +++ /dev/null @@ -1,75 +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 . -*/ - -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/isa_branch.rs b/src/luma/cpu/isa_branch.rs deleted file mode 100644 index a887a35..0000000 --- a/src/luma/cpu/isa_branch.rs +++ /dev/null @@ -1,89 +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 . -*/ - -use crate::luma::log; -use crate::luma::cpu::{Cpu, exchange}; - -impl Cpu { - 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(imm); - - log!("b {target:#X}"); - - target = target.wrapping_add(self.instruction_size); - state.write_register(0xF, target); - } - - pub(super) fn isa_branch_exchange(&mut self, rm: u8) { - let mut state = self.state.lock().unwrap(); - - log!("bx r{rm}"); - - let mut target = state.read_register(rm); - - let t = target & 0b00000000000000000000000000000001 != 0x0; - exchange!(self, t); - - let cpsr = state.read_cpsr() & 0b11111111111111111111111111011111 | (t as u32) << 0x5; - state.write_cpsr(cpsr); - - target &= 0b11111111111111111111111111111110; - target = target.wrapping_add(self.instruction_size); - state.write_register(0xF, target); - } - - 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(imm); - - log!("bl {target:#X}"); - - target = target.wrapping_add(self.instruction_size); - state.write_register(0xF, target); - } - - 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(imm); - - state.write_register(0xE, target); - } - - 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(imm); - let link_target = state.read_register(0xF).wrapping_sub(0x2); - - log!("bl {branch_target:#X}"); - - state.write_register(0xE, link_target); - - branch_target = branch_target.wrapping_add(0x2); - state.write_register(0xF, branch_target); - } -} diff --git a/src/luma/cpu/isa_logic.rs b/src/luma/cpu/isa_logic.rs deleted file mode 100644 index a624f57..0000000 --- a/src/luma/cpu/isa_logic.rs +++ /dev/null @@ -1,111 +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 . -*/ - -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 deleted file mode 100644 index 8b953c0..0000000 --- a/src/luma/cpu/isa_memory.rs +++ /dev/null @@ -1,122 +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 . -*/ - -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/isa_move.rs b/src/luma/cpu/isa_move.rs deleted file mode 100644 index a9edac8..0000000 --- a/src/luma/cpu/isa_move.rs +++ /dev/null @@ -1,136 +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 . -*/ - -use crate::luma::log; -use crate::luma::cpu::Cpu; - -impl Cpu { - 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(rd, immediate); - } - - 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(rd, value); - } - - 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(rd, value); - } - - 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(rd, value); - } - - 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(rd, value); - } - - 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(); - - let base_value = state.read_register(base) as i32; - let shift_value = state.read_register(shift); - - let value = (base_value.wrapping_shr(shift_value)) as u32; - state.write_register(rd, value); - } - - 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(); - - let base_value = state.read_register(base); - let shift_value = state.read_register(shift); - - let value = base_value.wrapping_shl(shift_value); - state.write_register(rd, value); - } - - 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(); - - let base_value = state.read_register(base); - let shift_value = state.read_register(shift); - - let value = base_value.wrapping_shr(shift_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/test_predicate.rs b/src/luma/cpu/test_predicate.rs deleted file mode 100644 index b6ed46a..0000000 --- a/src/luma/cpu/test_predicate.rs +++ /dev/null @@ -1,74 +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 . -*/ - -macro_rules! test_predicate { - ($cpsr: expr, $predicate: expr) => {{ - // True if predicate applies. - - // Code Id. Predicates - // 0 eq Z==1 - // 1 ne Z==0 - // 2 cs, hs C==1 - // 3 cc, lo C==0 - // 4 mi N==1 - // 5 pl N==0 - // 6 vs V==1 - // 7 vc V==0 - // 8 hi C==1 && Z==0 - // 9 ls C==0 && Z==1 - // A ge N==V - // B lt N!=V - // C gt Z==0 && N==V - // D le Z==1 && N!=V - // E al true - // F nv false - // - // Note that nv is always invalid on ARMv4. - - let v = $cpsr & 0b00010000000000000000000000000000 != 0x0; - let c = $cpsr & 0b00100000000000000000000000000000 != 0x0; - let z = $cpsr & 0b01000000000000000000000000000000 != 0x0; - let n = $cpsr & 0b10000000000000000000000000000000 != 0x0; - - match $predicate { - 0x0 => z, - 0x1 => !z, - 0x2 => c, - 0x3 => !c, - 0x4 => n, - 0x5 => !n, - 0x6 => v, - 0x7 => !v, - 0x8 => c && !z, - 0x9 => !c && z, - 0xA => n == v, - 0xB => n != v, - 0xC => !z && n == v, - 0xD => z && n != v, - 0xE => false, - 0xF => true, - _ => unreachable!(), - } - }} -} -pub(crate) use test_predicate; diff --git a/src/luma/cpu_handle.rs b/src/luma/cpu_handle.rs deleted file mode 100644 index ceacd03..0000000 --- a/src/luma/cpu_handle.rs +++ /dev/null @@ -1,55 +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 . -*/ - -use crate::luma::state::State; - -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 struct CpuHandle { - state: Arc>, - dead: Arc, - - handle: Option>, -} - -impl CpuHandle { - pub fn new( - state: Arc>, - dead: Arc, - - handle: JoinHandle<()>, - ) -> Self { - return Self { - state: state, - dead: dead, - - handle: Some(handle), - }; - } -} diff --git a/src/luma/cpu_handle/drop.rs b/src/luma/cpu_handle/drop.rs deleted file mode 100644 index 52c2866..0000000 --- a/src/luma/cpu_handle/drop.rs +++ /dev/null @@ -1,37 +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 . -*/ - -use crate::luma::cpu_handle::CpuHandle; - -use std::sync::atomic::Ordering; - -impl Drop for CpuHandle { - fn drop(&mut self) { - eprintln!("got kill order"); - - let handle = self.handle.take().unwrap(); - - self.dead.store(true, Ordering::Relaxed); - handle.join().unwrap(); - } -} diff --git a/src/luma/cpu_handle/dump_palette.rs b/src/luma/cpu_handle/dump_palette.rs deleted file mode 100644 index c59a132..0000000 --- a/src/luma/cpu_handle/dump_palette.rs +++ /dev/null @@ -1,36 +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 . -*/ - -use crate::luma::PALETTE_LENGTH; -use crate::luma::cpu_handle::CpuHandle; - -use std::ptr::copy_nonoverlapping; - -impl CpuHandle { - pub fn dump_palette(&mut self, buffer: &mut [u16]) { - assert_eq!(buffer.len(), PALETTE_LENGTH as usize >> 0x1); - - let state = self.state.lock().unwrap(); - unsafe { copy_nonoverlapping(state.palette().as_ptr(), buffer.as_mut_ptr(), buffer.len()) }; - } -} diff --git a/src/luma/cpu_handle/dump_video.rs b/src/luma/cpu_handle/dump_video.rs deleted file mode 100644 index c59c467..0000000 --- a/src/luma/cpu_handle/dump_video.rs +++ /dev/null @@ -1,34 +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 . -*/ - -use crate::luma::VIDEO_LENGTH; -use crate::luma::cpu_handle::CpuHandle; - -impl CpuHandle { - pub fn dump_video(&mut self, buffer: &mut [u8]) { - assert_eq!(buffer.len(), VIDEO_LENGTH as usize); - - let state = self.state.lock().unwrap(); - buffer.copy_from_slice(state.video8()); - } -} diff --git a/src/luma/instruction.rs b/src/luma/instruction.rs deleted file mode 100644 index 9de3d32..0000000 --- a/src/luma/instruction.rs +++ /dev/null @@ -1,66 +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 . -*/ - -#[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 deleted file mode 100644 index 1b293ef..0000000 --- a/src/luma/state.rs +++ /dev/null @@ -1,103 +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 . -*/ - -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], - cpsr: u32, - - memory: Vec::, -} - -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(crate) use address_unused; - -impl State { - #[must_use] - pub fn video8<'a>(&'a self) -> &'a [u8] { - let slice = unsafe { - let pointer = (self.memory.as_ptr() as *const u8).add(0x06000000); - let slice = from_raw_parts(pointer, VIDEO_LENGTH as usize); - - slice - }; - - return slice; - } - - #[must_use] - pub fn palette<'a>(&'a self) -> &'a [u16] { - let slice = unsafe { - let pointer = (self.memory.as_ptr() as *const u8).add(0x05000000) as *const u16; - let slice = from_raw_parts(pointer, PALETTE_LENGTH as usize); - - slice - }; - - return slice; - } - - #[must_use] - pub fn bootloader_buffer<'a>(&'a mut self) -> &'a mut [u8] { - let slice = unsafe { - let pointer = (self.memory.as_mut_ptr() as *mut u8).add(0x00000000); - let slice = from_raw_parts_mut(pointer, BOOTLOADER_LENGTH as usize); - - slice - }; - - return slice; - } - - #[must_use] - pub fn image_buffer<'a>(&'a mut self) -> &'a mut [u8] { - let slice = unsafe { - let pointer = (self.memory.as_mut_ptr() as *mut u8).add(0x08000000); - let slice = from_raw_parts_mut(pointer, IMAGE_LENGTH as usize); - - slice - }; - - return slice; - } -} diff --git a/src/luma/state/new.rs b/src/luma/state/new.rs deleted file mode 100644 index 39caaf6..0000000 --- a/src/luma/state/new.rs +++ /dev/null @@ -1,60 +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 register 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 register Public License for more details. - - You should have received a copy of the GNU - Affero register Public License along with Luma. - If not, see . -*/ - -use crate::luma::MEMORY_LENGTH; -use crate::luma::state::State; - -impl State { - #[must_use] - pub fn new() -> Self { - let registers: [u32; 0x10] = [ - 0x00000000, - 0x00000000, - 0x00000000, - 0x00000000, - 0x00000000, - 0x00000000, - 0x00000000, - 0x00000000, - 0x00000000, - 0x00000000, - 0x00000000, - 0x00000000, - 0x00000000, - 0x00000000, - 0x00000000, - 0x08000008, - ]; - - let cpsr = 0b00000000000000000000000000001111; - - let memory: Vec:: = vec![0x0; MEMORY_LENGTH as usize / 0x4]; - - return Self { - registers: registers, - cpsr: cpsr, - - memory: memory, - }; - } -} diff --git a/src/luma/state/read.rs b/src/luma/state/read.rs deleted file mode 100644 index f22dc86..0000000 --- a/src/luma/state/read.rs +++ /dev/null @@ -1,74 +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 . -*/ - -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 deleted file mode 100644 index bd2b031..0000000 --- a/src/luma/state/write.rs +++ /dev/null @@ -1,116 +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 . -*/ - -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; - } -} diff --git a/src/main.rs b/src/main.rs deleted file mode 100644 index 4478a81..0000000 --- a/src/main.rs +++ /dev/null @@ -1,74 +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 . -*/ - -extern crate ctrlc; -extern crate sdl2; -extern crate toml; - -mod luma; - -use crate::luma::VERSION; -use crate::luma::app::App; -use crate::luma::configuration::Configuration; - -use std::env::{args, var}; -use std::process::exit; - -fn main() { - eprintln!(); - eprintln!("\u{1B}[1mluma\u{1B}[0m {:X}.{:X}", VERSION.0, VERSION.1); - eprintln!("Copyright 2021-2023 \u{1B}[1mGabriel Bj\u{F8}rnager Jensen\u{1B}[0m."); - eprintln!(); - - let path = if let Some(path) = args().nth(0x1) { path } - else { default_configuration_path() }; - - let configuration = match Configuration::load(&path) { - Ok( configuration) => configuration, - Err(message) => panic!("unable to load configuration: {message}"), - }; - - let app = match App::init(configuration) { - Ok( app) => app, - Err(message) => panic!("unable to initialise application: {message}"), - }; - - let result = app.run(); - - if let Err(ref message) = result { eprintln!("\u{1B}[1m\u{1B}[91merror\u{1B}[0m: {message}") }; - - exit(match result { - Ok( ..) => 0x0, - Err(..) => 0x1, - }); -} - -fn default_configuration_path() -> String { - let home = match var("HOME") { - Ok( path) => path, - Err(..) => "/".to_string(), - }; - - let path = home + "/.luma.toml"; - return path; -} diff --git a/src/predicate.rs b/src/predicate.rs new file mode 100644 index 0000000..737ff7f --- /dev/null +++ b/src/predicate.rs @@ -0,0 +1,101 @@ +/* + 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 . +*/ + +use std::mem::transmute; + +#[derive(Clone, Copy)] +#[repr(u8)] +pub enum Predicate { + Eq = 0b0000, + Ne = 0b0001, + Cs = 0b0010, // Hs + Cc = 0b0011, // Lo + Mi = 0b0100, + Pl = 0b0101, + Vs = 0b0110, + Vc = 0b0111, + Hi = 0b1000, + Ls = 0b1001, + Ge = 0b1010, + Lt = 0b1011, + Gt = 0b1100, + Le = 0b1101, + Al = 0b1110, + Nv = 0b1111, +} + +impl Predicate { + pub fn from_raw(mut raw: u8) -> Self { + raw &= 0b00001111; + return unsafe { transmute(raw) }; + } + + pub fn test(self, cpsr: u32) -> bool { + let v = cpsr & 0b00010000000000000000000000000000 != 0x0; + let c = cpsr & 0b00100000000000000000000000000000 != 0x0; + let z = cpsr & 0b01000000000000000000000000000000 != 0x0; + let n = cpsr & 0b10000000000000000000000000000000 != 0x0; + + use Predicate::*; + return match self { + Eq => z, + Ne => !z, + Cs => c, + Cc => !c, + Mi => n, + Pl => !n, + Vs => v, + Vc => !v, + Hi => c && !z, + Ls => !c && z, + Ge => n == v, + Lt => n != v, + Gt => !z && n == v, + Le => z && n != v, + Al => true, + Nv => false, // Unpredictable in ARMv4. + }; + } + + pub fn code(self) -> &'static str { + use Predicate::*; + return match self { + Eq => "eq", + Ne => "ne", + Cs => "cs", + Cc => "cc", + Mi => "mi", + Pl => "pl", + Vs => "vs", + Vc => "vc", + Hi => "hi", + Ls => "ls", + Ge => "ge", + Lt => "lt", + Gt => "gt", + Le => "le", + Al => "al", + Nv => "nv", + }; + } +} diff --git a/src/shifter.rs b/src/shifter.rs new file mode 100644 index 0000000..b8db2ce --- /dev/null +++ b/src/shifter.rs @@ -0,0 +1,65 @@ +/* + 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 . +*/ + +use std::fmt::{Display, Formatter}; + +mod extract; + +#[derive(Clone, Copy)] +pub enum Shifter { + Immediate( u8, u8), + ArithmeticShiftRightImmediate(u8, u8), + ArithmeticShiftRightRegister( u8, u8), + LogicalShiftLeftImmediate( u8, u8), + LogicalShiftLeftRegister( u8, u8), + LogicalShiftRightImmediate( u8, u8), + LogicalShiftRightRegister( u8, u8), + RotateRightImmediate( u8, u8), + RotateRightRegister( u8, u8), + RotateRightExtend( u8), +} + +impl Shifter { + #[inline(always)] + pub const fn from_register(register: u8) -> Shifter { Shifter::LogicalShiftLeftImmediate(register, 0x0) } +} + +impl Display for Shifter { + #[must_use] + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + use Shifter::*; + + return match *self { + Immediate( imm, rot) => write!(f, "#{:#X}", (imm as u32).rotate_right(rot as u32)), + ArithmeticShiftRightImmediate(rm, imm) => write!(f, "r{rm}, ASR #{imm:#X}"), + ArithmeticShiftRightRegister( rm, rs) => write!(f, "r{rm}, ASR r{rs}"), + LogicalShiftLeftImmediate( rm, imm) => write!(f, "r{rm}, LSL #{imm:#X}"), + LogicalShiftLeftRegister( rm, rs) => write!(f, "r{rm}, LSL r{rs}"), + LogicalShiftRightImmediate( rm, imm) => write!(f, "r{rm}, LSR #{imm:#X}"), + LogicalShiftRightRegister( rm, rs) => write!(f, "r{rm}, LSR r{rs}"), + RotateRightImmediate( rm, imm) => write!(f, "r{rm}, ROR #{imm:#X}"), + RotateRightRegister( rm, rs) => write!(f, "r{rm}, ROR r{rs}"), + RotateRightExtend( rm) => write!(f, "RXX"), + }; + } +} diff --git a/src/shifter/extract.rs b/src/shifter/extract.rs new file mode 100644 index 0000000..d4ff6e0 --- /dev/null +++ b/src/shifter/extract.rs @@ -0,0 +1,92 @@ +/* + 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 . +*/ + +use crate::shifter::Shifter; + +use std::hint::unreachable_unchecked; + +impl Shifter { + pub fn extract(opcode: u32) -> Self { + use Shifter::*; + + match opcode & 0b00000010000000000000000000000000 != 0x0 { + false => { + let rm = (opcode & 0b00000000000000000000000000001111) as u8; + + let shift = (opcode & 0b00000000000000000000000001100000).wrapping_shr(0x5) as u8; + let register_shift = opcode & 0b00000000000000000000000000010000 != 0x0; + + match register_shift { + false => { + let imm = (opcode & 0b00000000000000000000111110000000).wrapping_shr(0x7) as u8; + + return match shift { + 0b11 => match imm { + 0x0 => RotateRightExtend( rm), + imm => RotateRightImmediate(rm, imm), + }, + + shift => { + match shift { + 0b00 => LogicalShiftLeftImmediate(rm, imm), + + 0b01 => LogicalShiftRightImmediate(rm, match imm { + 0x0 => 0x20, + imm => imm, + }), + + 0b10 => ArithmeticShiftRightImmediate(rm, match imm { + 0x0 => 0x20, + imm => imm, + }), + + _ => unsafe { unreachable_unchecked() }, + } + }, + }; + }, + + true => { + let rs = (opcode & 0b0000000000000000000111100000000).wrapping_shr(0x8) as u8; + + return match shift { + 0b00 => LogicalShiftLeftRegister( rm, rs), + 0b01 => LogicalShiftRightRegister( rm, rs), + 0b10 => ArithmeticShiftRightRegister(rm, rs), + 0b11 => RotateRightRegister( rm, rs), + + _ => unsafe { unreachable_unchecked() }, + }; + }, + }; + }, + + true => { + let imm = (opcode & 0b00000000000000000000000011111111) as u8; + let rot = (opcode & 0b00000000000000000000111100000000).wrapping_shr(0x7) as u8; + + return Immediate(imm, rot); + }, + }; + } +} diff --git a/src/state.rs b/src/state.rs new file mode 100644 index 0000000..6697059 --- /dev/null +++ b/src/state.rs @@ -0,0 +1,126 @@ +/* + 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 . +*/ + +use crate::{BOOTLOADER_LENGTH, IMAGE_LENGTH, PALETTE_LENGTH, VIDEO_LENGTH}; +use crate::cpu_mode::CpuMode; + +use std::slice::{from_raw_parts, from_raw_parts_mut}; + +pub mod bank; +pub mod new; +pub mod read; +pub mod shifter_value; +pub mod write; + +pub struct State { + registers: [*mut u32; 0x10], + banks: Box<[[u32; 0x10]; 0x6]>, + + cpsr: u32, + spsr: [u32; 0x6], + + memory: Vec::, +} + +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(crate) use address_unused; + +impl State { + #[must_use] + pub fn video8<'a>(&'a self) -> &'a [u8] { + let slice = unsafe { + let pointer = (self.memory.as_ptr() as *const u8).add(0x06000000); + let slice = from_raw_parts(pointer, VIDEO_LENGTH as usize); + + slice + }; + + return slice; + } + + #[must_use] + pub fn palette<'a>(&'a self) -> &'a [u16] { + let slice = unsafe { + let pointer = (self.memory.as_ptr() as *const u8).add(0x05000000) as *const u16; + let slice = from_raw_parts(pointer, PALETTE_LENGTH as usize); + + slice + }; + + return slice; + } + + #[must_use] + pub fn bootloader_buffer<'a>(&'a mut self) -> &'a mut [u8] { + let slice = unsafe { + let pointer = (self.memory.as_mut_ptr() as *mut u8).add(0x00000000); + let slice = from_raw_parts_mut(pointer, BOOTLOADER_LENGTH as usize); + + slice + }; + + return slice; + } + + #[must_use] + pub fn image_buffer<'a>(&'a mut self) -> &'a mut [u8] { + let slice = unsafe { + let pointer = (self.memory.as_mut_ptr() as *mut u8).add(0x08000000); + let slice = from_raw_parts_mut(pointer, IMAGE_LENGTH as usize); + + slice + }; + + return slice; + } + + #[must_use] + fn spsr_index(mode: CpuMode) -> usize { + use CpuMode::*; + + return match mode { + User => 0x0, + System => 0x0, + FastInterruptRequest => 0x1, + InterruptRequest => 0x2, + Supervisor => 0x3, + Abort => 0x4, + Undefined => 0x5, + }; + } +} + +unsafe impl Send for State { } diff --git a/src/state/bank.rs b/src/state/bank.rs new file mode 100644 index 0000000..7cab594 --- /dev/null +++ b/src/state/bank.rs @@ -0,0 +1,89 @@ +/* + 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 register 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 register Public License for more details. + + You should have received a copy of the GNU + Affero register Public License along with Luma. + If not, see . +*/ + +use crate::{log, log_status}; +use crate::cpu_mode::CpuMode; +use crate::state::State; + +impl State { + pub fn bank(&mut self, mode: CpuMode) { + log_status!("banking to {}", mode.name()); + + let bank_index = bank_index(mode); + + self.registers[0x0] = &mut self.banks[bank_index.0][ 0x0] as *mut u32; + self.registers[0x1] = &mut self.banks[bank_index.1][ 0x1] as *mut u32; + self.registers[0x2] = &mut self.banks[bank_index.2][ 0x2] as *mut u32; + self.registers[0x3] = &mut self.banks[bank_index.3][ 0x3] as *mut u32; + self.registers[0x4] = &mut self.banks[bank_index.4][ 0x4] as *mut u32; + self.registers[0x5] = &mut self.banks[bank_index.5][ 0x5] as *mut u32; + self.registers[0x6] = &mut self.banks[bank_index.6][ 0x6] as *mut u32; + self.registers[0x7] = &mut self.banks[bank_index.7][ 0x7] as *mut u32; + self.registers[0x8] = &mut self.banks[bank_index.8][ 0x8] as *mut u32; + self.registers[0x9] = &mut self.banks[bank_index.9][ 0x9] as *mut u32; + self.registers[0xA] = &mut self.banks[bank_index.10][0xA] as *mut u32; + self.registers[0xB] = &mut self.banks[bank_index.11][0xB] as *mut u32; + self.registers[0xC] = &mut self.banks[bank_index.12][0xC] as *mut u32; + self.registers[0xD] = &mut self.banks[bank_index.13][0xD] as *mut u32; + self.registers[0xE] = &mut self.banks[bank_index.14][0xE] as *mut u32; + self.registers[0xF] = &mut self.banks[bank_index.15][0xF] as *mut u32; + + log!("new register layout:"); + log!(" r0: {:#010X} r4: {:#010X} r8: {:#010X} r12: {:#010X}", self.read_register(0x0), self.read_register(0x4), self.read_register(0x8), self.read_register(0xC)); + log!(" r1: {:#010X} r5: {:#010X} r9: {:#010X} sp: {:#010X}", self.read_register(0x1), self.read_register(0x5), self.read_register(0x9), self.read_register(0xD)); + log!(" r2: {:#010X} r6: {:#010X} r10: {:#010X} lr: {:#010X}", self.read_register(0x2), self.read_register(0x6), self.read_register(0xA), self.read_register(0xE)); + log!(" r3: {:#010X} r7: {:#010X} r11: {:#010X} pc: {:#010X}", self.read_register(0x3), self.read_register(0x7), self.read_register(0xB), self.read_register(0xF)); + } +} + +#[must_use] +fn bank_index(mode: CpuMode) -> ( + usize, // r0 + usize, // r1 + usize, // r2 + usize, // r3 + usize, // r4 + usize, // r5 + usize, // r6 + usize, // r7 + usize, // r8 + usize, // r9 + usize, // r10 + usize, // r11 + usize, // r12 + usize, // sp + usize, // lr + usize, // pc +) { + use CpuMode::*; + + return match mode { + User => (0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0), + System => (0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0), + FastInterruptRequest => (0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x0), + InterruptRequest => (0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0x2, 0x0), + Supervisor => (0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x3, 0x3, 0x0), + Abort => (0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x4, 0x4, 0x0), + Undefined => (0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x5, 0x5, 0x0), + }; +} diff --git a/src/state/new.rs b/src/state/new.rs new file mode 100644 index 0000000..bb6f87e --- /dev/null +++ b/src/state/new.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 register 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 register Public License for more details. + + You should have received a copy of the GNU + Affero register Public License along with Luma. + If not, see . +*/ + +use crate::log; +use crate::MEMORY_LENGTH; +use crate::cpu_mode::CpuMode; +use crate::state::State; + +use std::ptr::null_mut; + +impl State { + #[must_use] + pub fn new() -> Self { + log!("initialising new state"); + + let banks = Box::new([DEFAULT_REGISTER_VALUES; 0x6]); + + let cpsr = 0b00000000000000000000000000011111; + + let memory: Vec:: = vec![0x0; MEMORY_LENGTH as usize / 0x4]; + + let mut state = Self { + registers: [null_mut(); 0x10], + banks: banks, + + cpsr: cpsr, + spsr: [0b00000000000000000000000000000000; 0x6], + + memory: memory, + }; + + state.bank(CpuMode::System); + + return state; + } +} + +const DEFAULT_REGISTER_VALUES: [u32; 0x10] = [ + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000008, +]; diff --git a/src/state/read.rs b/src/state/read.rs new file mode 100644 index 0000000..927d528 --- /dev/null +++ b/src/state/read.rs @@ -0,0 +1,85 @@ +/* + 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 . +*/ + +use crate::{Error, MEMORY_LENGTH}; +use crate::cpu_mode::CpuMode; +use crate::state::State; + +const MAX_BYTE_ADDRESS: u32 = MEMORY_LENGTH; +const MAX_HALFWORD_ADDRESS: u32 = MEMORY_LENGTH - 0x1; +const MAX_WORD_ADDRESS: u32 = MEMORY_LENGTH - 0x3; + +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 > MAX_WORD_ADDRESS { Error::OutOfBounds( address).trap(); return 0xFFFFFFFF; } + if address % 0x4 != 0x0 { Error::BadAlignment(address, 0x4).trap(); return 0xFFFFFFFF; } + + 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 > MAX_HALFWORD_ADDRESS { Error::OutOfBounds( address).trap(); return 0xFFFF; } + if address % 0x2 != 0x0 { Error::BadAlignment(address, 0x2).trap(); return 0xFFFF; } + + 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 > MAX_BYTE_ADDRESS { Error::OutOfBounds(address).trap(); return 0xFF; } + + 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; + } + + #[inline(always)] + #[must_use] + pub fn read_spsr(&self, mode: CpuMode) -> u32 { + return unsafe { *self.spsr.get_unchecked(Self::spsr_index(mode)) }; + } +} diff --git a/src/state/shifter_value.rs b/src/state/shifter_value.rs new file mode 100644 index 0000000..58821fe --- /dev/null +++ b/src/state/shifter_value.rs @@ -0,0 +1,130 @@ +/* + 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 . +*/ + +use crate::shifter::Shifter; +use crate::state::State; + +impl State { + #[must_use] + pub fn shifter_value(&self, shifter: Shifter) -> (u32, bool) { + use Shifter::*; + + let c = self.read_cpsr() & 0b00100000000000000000000000000000 != 0x0; + + let (value, carry) = match shifter { + Immediate(imm, rot) => { + let result = (imm as u32).rotate_right(rot as u32); + let c = if rot == 0x0 { c } else { result & 0b10000000000000000000000000000000 != 0x0 }; + + (result, c) + }, + + ArithmeticShiftRightImmediate(rm, imm) => { + let rm_value = self.read_register(rm); + + let result = (rm_value as i32).wrapping_shr(imm as u32) as u32; + let c = false; // TODO + + (result, c) + }, + + ArithmeticShiftRightRegister(rm, rs) => { + let rm_value = self.read_register(rm); + let rs_value = self.read_register(rs); + + let result = (rm_value as i32).wrapping_shr(rs_value) as u32; + let c = false; // TODO + + (result, c) + }, + + LogicalShiftLeftImmediate(rm, imm) => { + let rm_value = self.read_register(rm); + + let result = rm_value.wrapping_shl(imm as u32); + let c = false; // TODO + + (result, c) + }, + + LogicalShiftLeftRegister(rm, rs) => { + let rm_value = self.read_register(rm); + let rs_value = self.read_register(rs); + + let result = rm_value.wrapping_shl(rs_value); + let c = false; // TODO + + (result, c) + }, + + LogicalShiftRightImmediate(rm, imm) => { + let rm_value = self.read_register(rm); + + let result = rm_value.wrapping_shr(imm as u32); + let c = false; // TODO + + (result, c) + }, + + LogicalShiftRightRegister(rm, rs) => { + let rm_value = self.read_register(rm); + let rs_value = self.read_register(rs); + + let result = rm_value.wrapping_shr(rs_value); + let c = false; // TODO + + (result, c) + }, + + RotateRightImmediate(rm, imm) => { + let rm_value = self.read_register(rm); + + let result = rm_value.rotate_right(imm as u32); + let c = false; // TODO + + (result, c) + }, + + RotateRightRegister(rm, rs) => { + let rm_value = self.read_register(rm); + let rs_value = self.read_register(rs); + + let result = rm_value.rotate_right(rs_value); + let c = false; // TODO + + (result, c) + }, + + RotateRightExtend(rm) => { + let rm_value = self.read_register(rm); + + let result = 0x0_u32; // TODO + let c = rm_value & 0b00000000000000000000000000000001 != 0x0; + + (result, c) + }, + }; + + return (value, carry); + } +} diff --git a/src/state/write.rs b/src/state/write.rs new file mode 100644 index 0000000..10955e2 --- /dev/null +++ b/src/state/write.rs @@ -0,0 +1,125 @@ +/* + 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 . +*/ + +use crate::{Error, log_assignment, MEMORY_LENGTH}; +use crate::cpu_mode::CpuMode; +use crate::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!(format!("r{register}"), format!("{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!(format!("{address:#010X}"), format!("{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!(format!("{address:#010X}"), format!("{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!(format!("{address:#010X}"), format!("{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; + }, + }; + } + + #[inline(always)] + pub fn write_cpsr(&mut self, value: u32) { + log_assignment!("cpsr", format!("{value:#034b}")); + + self.cpsr = value; + } + + #[inline(always)] + pub fn write_spsr(&mut self, mode: CpuMode, value: u32) { + log_assignment!("spsr", format!("{value:#034b}")); + + unsafe { *self.spsr.get_unchecked_mut(Self::spsr_index(mode)) = value }; + } +} diff --git a/test.s b/test.s index 8497dab..691c4b5 100644 --- a/test.s +++ b/test.s @@ -1,49 +1,47 @@ -.syntax unified - .cpu arm7tdmi .arm b _start -.byte 0x24,0xFF,0xAE,0x51 -.byte 0x69,0x9A,0xA2,0x21 -.byte 0x3D,0x84,0x82,0x0A -.byte 0x84,0xE4,0x09,0xAD -.byte 0x11,0x24,0x8B,0x98 -.byte 0xC0,0x81,0x7F,0x21 -.byte 0xA3,0x52,0xBE,0x19 -.byte 0x93,0x09,0xCE,0x20 -.byte 0x10,0x46,0x4A,0x4A -.byte 0xF8,0x27,0x31,0xEC -.byte 0x58,0xC7,0xE8,0x33 -.byte 0x82,0xE3,0xCE,0xBF -.byte 0x85,0xF4,0xDF,0x94 -.byte 0xCE,0x4B,0x09,0xC1 -.byte 0x94,0x56,0x8A,0xC0 -.byte 0x13,0x72,0xA7,0xFC -.byte 0x9F,0x84,0x4D,0x73 -.byte 0xA3,0xCA,0x9A,0x61 -.byte 0x58,0x97,0xA3,0x27 -.byte 0xFC,0x03,0x98,0x76 -.byte 0x23,0x1D,0xC7,0x61 -.byte 0x03,0x04,0xAE,0x56 -.byte 0xBF,0x38,0x84,0x00 -.byte 0x40,0xA7,0x0E,0xFD -.byte 0xFF,0x52,0xFE,0x03 -.byte 0x6F,0x95,0x30,0xF1 -.byte 0x97,0xFB,0xC0,0x85 -.byte 0x60,0xD6,0x80,0x25 -.byte 0xA9,0x63,0xBE,0x03 -.byte 0x01,0x4E,0x38,0xE2 -.byte 0xF9,0xA2,0x34,0xFF -.byte 0xBB,0x3E,0x03,0x44 -.byte 0x78,0x00,0x90,0xCB -.byte 0x88,0x11,0x3A,0x94 -.byte 0x65,0xC0,0x7C,0x63 -.byte 0x87,0xF0,0x3C,0xAF -.byte 0xD6,0x25,0xE4,0x8B -.byte 0x38,0x0A,0xAC,0x72 -.byte 0x21,0xD4,0xF8,0x07 +.byte 0x24, 0xFF, 0xAE, 0x51 +.byte 0x69, 0x9A, 0xA2, 0x21 +.byte 0x3D, 0x84, 0x82, 0x0A +.byte 0x84, 0xE4, 0x09, 0xAD +.byte 0x11, 0x24, 0x8B, 0x98 +.byte 0xC0, 0x81, 0x7F, 0x21 +.byte 0xA3, 0x52, 0xBE, 0x19 +.byte 0x93, 0x09, 0xCE, 0x20 +.byte 0x10, 0x46, 0x4A, 0x4A +.byte 0xF8, 0x27, 0x31, 0xEC +.byte 0x58, 0xC7, 0xE8, 0x33 +.byte 0x82, 0xE3, 0xCE, 0xBF +.byte 0x85, 0xF4, 0xDF, 0x94 +.byte 0xCE, 0x4B, 0x09, 0xC1 +.byte 0x94, 0x56, 0x8A, 0xC0 +.byte 0x13, 0x72, 0xA7, 0xFC +.byte 0x9F, 0x84, 0x4D, 0x73 +.byte 0xA3, 0xCA, 0x9A, 0x61 +.byte 0x58, 0x97, 0xA3, 0x27 +.byte 0xFC, 0x03, 0x98, 0x76 +.byte 0x23, 0x1D, 0xC7, 0x61 +.byte 0x03, 0x04, 0xAE, 0x56 +.byte 0xBF, 0x38, 0x84, 0x00 +.byte 0x40, 0xA7, 0x0E, 0xFD +.byte 0xFF, 0x52, 0xFE, 0x03 +.byte 0x6F, 0x95, 0x30, 0xF1 +.byte 0x97, 0xFB, 0xC0, 0x85 +.byte 0x60, 0xD6, 0x80, 0x25 +.byte 0xA9, 0x63, 0xBE, 0x03 +.byte 0x01, 0x4E, 0x38, 0xE2 +.byte 0xF9, 0xA2, 0x34, 0xFF +.byte 0xBB, 0x3E, 0x03, 0x44 +.byte 0x78, 0x00, 0x90, 0xCB +.byte 0x88, 0x11, 0x3A, 0x94 +.byte 0x65, 0xC0, 0x7C, 0x63 +.byte 0x87, 0xF0, 0x3C, 0xAF +.byte 0xD6, 0x25, 0xE4, 0x8B +.byte 0x38, 0x0A, 0xAC, 0x72 +.byte 0x21, 0xD4, 0xF8, 0x07 .ascii "LUMA\x0\x0\x0\x0\x0\x0\x0\x0" @@ -80,8 +78,15 @@ nop .arm .func _start: - ldr lr, =start - bx lr + mov r9, #0x1 + rsbs r9, #0x0 + adds r9, #0x2 + mov r9, r9, LSL #0x1F + subs r9, #0x1 + mov sp, #0x450 + swi #0x3 @ Comment on other emulators. + ldr lr, =start + bx lr .endfunc .thumb @@ -99,8 +104,8 @@ start: ldr r1, .backgroundColour strh r1, [r0] ldr r1, .paletteIndex - lsls r1, 0x1 @ Multiply by two. - adds r0, r1 @ Apply index. + lsl r1, #0x1 @ Multiply by two. + add r0, r1 @ Apply index. ldr r1, .foregroundColour strh r1, [r0] @@ -111,25 +116,29 @@ start: @ - r3 is the last pixel address. ldr r0, .videoAddr ldr r1, .paletteIndex - bics r2, r2 - movs r3, 0x4B - lsls r3, 0x9 - adds r3, r0 + bic r2, r2 + mov r3, #0x4B + lsl r3, #0x9 + add r3, r0 @ Plot pixels: @ TO-DO: Plot correctly (bytewise). .loop: strb r1, [r0] - adds r0, r2 - adds r2, 0x1 + add r0, r2 + add r2, #0x1 cmp r0, r3 bge .restart + @bge .stop b .loop @ Repeat loop. @ Restart loop: .restart: ldr r0, .videoAddr b .loop + +.stop: + b .stop .endfunc .align -- cgit v1.2.3