diff options
68 files changed, 2536 insertions, 2025 deletions
@@ -1,4 +1,7 @@ *.agb +*.elf +*.o +*.sav vgcore.* /target /old diff --git a/CHANGELOG.txt b/CHANGELOG.md index c27d5d0..57d7825 100644 --- a/CHANGELOG.txt +++ b/CHANGELOG.md @@ -1,3 +1,19 @@ +# 0.2D + +* Reformat changelog in Markdown +* Add test program (including build script) +* Update readme (make Markdown) +* Rewrite and restructure project +* Update optimisation flags +* Depend on ctrlc, sdl2, and toml +* Set window title according to image +* Draw video memory +* Update naming convention +* Update gitignore +* Never hang on trap +* Run CPU on seperate thread +* Rework logs + # 0.2C * Fill window according to first palette entry; @@ -1,6 +1,6 @@ [package] name = "luma" -version = "0.44.0" +version = "0.45.0" authors = ["Gabriel Jensen"] edition = "2021" description = "AGB emulator." @@ -14,10 +14,10 @@ name = "luma" path = "src/main.rs" [profile.release] -lto = true +codegen-units = 1 +lto = "fat" [dependencies] -toml = "0.7.5" -libc = "0.2.147" +ctrlc = "3.4.1" sdl2 = "0.35.2" -serde = { version = "1.0.166", features = ["derive"] } +toml = "0.8.4" diff --git a/README.md b/README.md new file mode 100644 index 0000000..ee88b42 --- /dev/null +++ b/README.md @@ -0,0 +1,45 @@ +# Luma + +luma is an emulator for the AGB—Game Boy Advance platform. + +# Usage + +``` +luma [configuration] +``` + +Invoke the emulator via the `luma` command. + +## Configuration + +The emulator tries to read the configuration file at `${HOME}/.luma.toml`. If successful, the following fields are read (all must be present): + +`luma`: + * `version`: The configuration format (currently 0) + +`device`: + * `bootloader`: The path to the bootloader file + * `image`: The path to the image file + +`video`: + * `scale`: The scale modifier applied to the screen (1-4294967295) + +If a path is parsed as a terminal parameter, the configuration at that location is read instead. + +# Compatibility + +Currently, the emulator has limited support for the Arm instruction set. All of the instructions used in the provided test program are – however – implemented. + +The entire memory space (`0x00000000` to `0x0E00FFFF`) is available, however, no I/O-mapped addresses are currently functional. + +Improved support is, of course, planned. + +# Copyright & License + +Copyright 2021-2023 Gabriel Bjørnager Jensen. + +This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. + +This program 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 this program. If not, see <https://www.gnu.org/licenses/>. diff --git a/README.txt b/README.txt deleted file mode 100644 index 941d32b..0000000 --- a/README.txt +++ /dev/null @@ -1,85 +0,0 @@ -- LUMA - -Copyright 2021-2023 Gabriel Jensen. - -This program is free software: you can redistribute it and/or modify it under -the terms of the GNU Affero General Public License as published by the Free -Software Foundation, either version 3 of the License, or (at your option) any -later version. - -This program 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 this program. If not, see <https://www.gnu.org/licenses/>. - -- ABOUT - -luma is an emulator for the AGB - Game Boy Advance - platform. - -- USAGE - -luma [image] [bootloader] - -Invoke the emulator via the 'luma' command. - -- CONFIGURATION - -The emulator tries to read the configuration file at '${HOME}/.luma.toml'. If -this file is found, the following fields are read (if present): - -luma: - * version: (Integer) The version of the configuration file (0) - -device: - * bootloader: (String) The path to the bootloader file (home-relative) - * image: (String) The path to the image file (home-relative) - -video: - * scale: (Integer) The scale modifier applied to the screen (min 1; max (2^32-1)) - -These settings are overwritten by terminal parameters (see USAGE). - -- COMPATIBILITY - -Currently, the emulator supports the following ARM instructions only. Others -will be skipped. - - * b{cond}{l} Immediate24 - * bx{cond} Rm - * ldr{cond} Rd, [Rn, Immediate12] - * mov{cond} Rd, Rn - * mov{cond} Rd, #Immediate8 - * mov{cons}s r15, Rn - * str{cond} Rd, [Rn, Immediate12] - -Moreover, the following Thumb instructions are supported: - - * b Immediate11 - * b{cond} Immediate8 - * bl Immediate24 - * bx Rm - * ldr Rd, [Rn, Immediate5] - * ldr Rd, [Rn, Rm] - * ldr Rd, [r13, Immediate8] - * ldr Rd, [r15, Immediate8] - * lsls Rd, Rm, Immediate5 - * lsrs Rd, Rm, Immediate5 - * mov Rd, Rn - * movs Rd, Immediate8 - * movs Rd, Rn - * pop Registers - * push Registers - * strh Rd, [Rn, Immediate5] - * svc Immediate8 - -When the virtual processor boots, the default mode is the sys mode. This can be -changed using the 'svc Immediate8' (Thumb) or 'svc Immediate24' (ARM) -instructions, which changes this to the svc mode. - -The entire memory space (0x00000000 to 0x0E010000, exclusive) is available, -however, no I/O-mapped addresses are currently functional. - -Improved support is, of course, planned. diff --git a/make_test.sh b/make_test.sh new file mode 100755 index 0000000..e92a652 --- /dev/null +++ b/make_test.sh @@ -0,0 +1,14 @@ +#!/usr/bin/env sh + +echo Making object file... +arm-none-eabi-as -otest.o test.s + +echo Making binary... +arm-none-eabi-ld -Ttest.ld -otest.elf test.o + +echo Stripping binary... +arm-none-eabi-strip --strip-debug --strip-unneeded test.elf +arm-none-eabi-objcopy -Obinary test.elf test.agb + +echo Patching header... +agbsum -psitest.agb diff --git a/src/luma.rs b/src/luma.rs index 6587db4..26c0014 100644 --- a/src/luma.rs +++ b/src/luma.rs @@ -21,35 +21,63 @@ If not, see <https://www.gnu.org/licenses/>. */ -pub mod application; +use sdl2::pixels::Color; + +pub mod app; pub mod configuration; -pub mod device; +pub mod cpu; +pub mod cpu_handle; +pub mod instruction; +pub mod state; + +pub const VERSION: (u32, u32) = ( + 0x0, // major + 0x2D, // minor +); -pub struct VersionType<T> { - major: T, - minor: T, +pub enum Error { + BadAlignment( u32, u32), + InvalidArmOpcode( u32, u32), + InvalidThumbOpcode(u32, u16), + OutOfBounds( u32), } -pub const VERSION: VersionType::<u32> = VersionType::<u32> { - major: 0x0, - minor: 0x2C, -}; +impl Error { + pub fn trap(&self) { + let message = match self { + Error::BadAlignment( address, alignment) => format!("bad alignment of address {address:#010X} (should be {alignment}-byte aligned)"), + Error::InvalidArmOpcode( address, opcode) => format!("invalid opcode {opcode:#034b} at {address:#010X}"), + Error::InvalidThumbOpcode( address, opcode) => format!("invalid opcode {opcode:#018b} at {address:#010X}"), + Error::OutOfBounds( address) => format!("out-of-bounds address {address:#010X} (limit is {:#010X})", MEMORY_LENGTH), + }; -pub struct WidthHeight<T> { - width: T, - height: T, + eprintln!("trap: {message}"); + } } -pub const CONFIGURATION_VERSION: u32 = 0x0; +pub const MEMORY_LENGTH: u32 = 0x0E010000; + +pub const BOOTLOADER_LENGTH: u32 = 0x00004000; +pub const IMAGE_LENGTH: u32 = 0x02000000; +pub const VIDEO_LENGTH: u32 = 0x00018000; +pub const PALETTE_LENGTH: u32 = 0x00000400; -pub const MEMORY_SIZE: usize = 0x0E010000; +pub const SCREEN_SIZE: (u8, u8) = ( + 0xF0, // width + 0xA0, // height +); -pub const BOOTLOADER_SIZE: usize = 0x00004000; -pub const IMAGE_SIZE: usize = 0x02000000; -pub const VIDEO_SIZE: usize = 0x00018000; -pub const PALETTE_SIZE: usize = 0x00000400; +pub const fn decode_colour(colour: u16) -> Color { + let red = ((colour & 0b0000000000011111) << 0x3) as u8; + let green = ((colour & 0b0000001111100000) >> 0x2) as u8; + let blue = ((colour & 0b0111110000000000) >> 0x7) as u8; -pub const SCREEN_SIZE: WidthHeight::<u8> = WidthHeight::<u8> { - width: 0xF0, - height: 0xA0, -}; + return Color::RGB(red, green, blue); +} + +pub fn log(message: &str) { + // This optimises the function away. + if cfg!(debug_assertions) { + eprintln!("{message}"); + } +} diff --git a/src/luma/application/drop.rs b/src/luma/agb.rs index a5ad544..01a77b8 100644 --- a/src/luma/application/drop.rs +++ b/src/luma/agb.rs @@ -21,10 +21,5 @@ If not, see <https://www.gnu.org/licenses/>. */ -use crate::luma::application::Application; - -impl Drop for Application { - fn drop(&mut self) { - eprintln!("ending"); - } -} +pub mod arm; +pub mod thumb; diff --git a/src/luma/device/exchange.rs b/src/luma/app.rs index 7248792..c8e0134 100644 --- a/src/luma/device/exchange.rs +++ b/src/luma/app.rs @@ -21,20 +21,26 @@ If not, see <https://www.gnu.org/licenses/>. */ -use crate::luma::device::{Device, Log}; +use sdl2::Sdl; +use sdl2::render::WindowCanvas; +use std::sync::Arc; +use std::sync::atomic::AtomicBool; -impl Device { - pub fn exchange(&mut self, thumb: bool) { - // Conditionally exchanges the instruction set. - // cpsr is set by the caller. +pub mod check_events; +pub mod draw_video; +pub mod init; +pub mod load; +pub mod run; +pub mod sync_video; - self.log(Log::Exchange, format!("T => {thumb}")); +pub struct App { + bootloader: String, + image: String, - let decoders = [ - Device::decode_arm, - Device::decode_thumb, - ]; + scale: u32, - self.decode = decoders[thumb as usize]; - } + got_terminate: Arc::<AtomicBool>, + + sdl: Sdl, + canvas: WindowCanvas, } diff --git a/src/luma/device/continue.rs b/src/luma/app/check_events.rs index 9fe39b2..288a093 100644 --- a/src/luma/device/continue.rs +++ b/src/luma/app/check_events.rs @@ -21,24 +21,32 @@ If not, see <https://www.gnu.org/licenses/>. */ -use crate::luma::device::{Device, Log}; +use crate::luma::app::App; -impl Device { - pub fn arm_continue(&mut self) { - // Increment the program counter by one - // instruction. +use sdl2::event::Event; +use std::sync::atomic::Ordering; - (self.registers[0xF], _) = self.registers[0xF].overflowing_add(0x4); +impl App { + pub fn check_events(&mut self) -> Result<bool, String> { + // Return true if we should quit. - self.log(Log::Continue, format!("pc => pc+4 ({:#010X})", self.registers[0xF])); - } + let mut event_pump = match self.sdl.event_pump() { + Ok(pump) => pump, + _ => return Err("unable to get event pump".to_string()), + }; - pub fn thumb_continue(&mut self) { - // Increment the program counter by one - // instruction. + if self.got_terminate.load(Ordering::Relaxed) { + eprintln!("got terminate"); + return Ok(true) + }; - (self.registers[0xF], _) = self.registers[0xF].overflowing_add(0x2); + for event in event_pump.poll_iter() { + match event { + Event::Quit {..} => return Ok(true), + _ => {}, + }; + } - self.log(Log::Continue, format!("pc => pc+2 ({:#010X})", self.registers[0xF])); + return Ok(false); } } diff --git a/src/luma/app/draw_video.rs b/src/luma/app/draw_video.rs new file mode 100644 index 0000000..5df407e --- /dev/null +++ b/src/luma/app/draw_video.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 <https://www.gnu.org/licenses/>. +*/ + +use crate::luma::{decode_colour, 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, value) in (&agb_palette[0x0..0x100]).into_iter().enumerate() { + let colour = decode_colour(*value); + + palette[index] = 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(); + } +} diff --git a/src/luma/app/init.rs b/src/luma/app/init.rs new file mode 100644 index 0000000..2d826aa --- /dev/null +++ b/src/luma/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 <https://www.gnu.org/licenses/>. +*/ + +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<Self, String> { + 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 new file mode 100644 index 0000000..5c67daf --- /dev/null +++ b/src/luma/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 <https://www.gnu.org/licenses/>. +*/ + +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 new file mode 100644 index 0000000..959bc71 --- /dev/null +++ b/src/luma/app/run.rs @@ -0,0 +1,60 @@ +/* + Copyright 2021-2023 Gabriel Jensen. + + This file is part of Luma. + + Luma is free software: you can redistribute it + and/or modify it under the terms of the GNU + Affero General Public License as published by + the Free Software Foundation, either version 3 + of the License, or (at your option) any later + version. + + Luma is distributed in the hope that it will be + useful, but WITHOUT ANY WARRANTY; without even + the implied warranty of MERCHANTABILITY or + FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Affero General Public License for more details. + + You should have received a copy of the GNU + Affero General Public License along with Luma. + If not, see <https://www.gnu.org/licenses/>. +*/ + +use crate::luma::{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::<u8> = vec![0x0; VIDEO_LENGTH as usize]; + let mut palette_buffer: Vec::<u16> = 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); + } + + cpu.kill()?; + + return Ok(()); + } +} diff --git a/src/luma/configuration/overwrite.rs b/src/luma/app/sync_video.rs index 2d37c55..10c6abc 100644 --- a/src/luma/configuration/overwrite.rs +++ b/src/luma/app/sync_video.rs @@ -21,21 +21,24 @@ If not, see <https://www.gnu.org/licenses/>. */ -use crate::luma::configuration::Configuration; +use crate::luma::app::App; -use std::env::args; +use std::thread::sleep; +use std::time::{Duration, Instant}; -impl Configuration { - pub(super) fn overwrite(&mut self) { - eprintln!("overwritting settings"); +impl App { + pub fn sync_video(&self, frame_start: Instant) { + // Courtesy of TASVideos: <https://tasvideos.org/PlatformFramerates> + // 59.7275005696058 Hz - let parameters: Vec<String> = args().collect(); - let number = parameters.len(); + const FRAME_DURATION: u64 = 0xFF7932; + let frame_duration = Duration::from_nanos(FRAME_DURATION); - if number >= 0x2 { self.image = parameters[0x1].clone() } + let remaining = match frame_duration.checked_sub(frame_start.elapsed()) { + Some(value) => value, + None => Duration::from_secs(0x0), + }; - if number >= 0x3 { self.bootloader = parameters[0x2].clone() } - - if number > 0x3 { panic!("invalid number of parameters ({number})") } + sleep(remaining); } } diff --git a/src/luma/application/initialise.rs b/src/luma/application/initialise.rs deleted file mode 100644 index c813e23..0000000 --- a/src/luma/application/initialise.rs +++ /dev/null @@ -1,68 +0,0 @@ -/* - Copyright 2021-2023 Gabriel Jensen. - - This file is part of Luma. - - Luma is free software: you can redistribute it - and/or modify it under the terms of the GNU - Affero General Public License as published by - the Free Software Foundation, either version 3 - of the License, or (at your option) any later - version. - - Luma is distributed in the hope that it will be - useful, but WITHOUT ANY WARRANTY; without even - the implied warranty of MERCHANTABILITY or - FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Affero General Public License for more details. - - You should have received a copy of the GNU - Affero General Public License along with Luma. - If not, see <https://www.gnu.org/licenses/>. -*/ - -use crate::luma::{SCREEN_SIZE}; -use crate::luma::application::{Application, GOT_SIGNAL}; -use crate::luma::configuration::Configuration; -use crate::luma::device::Device; - -extern crate libc; -extern crate sdl2; - -use libc::{c_int, sighandler_t, SIGINT, signal, SIGTERM}; -use std::mem::transmute; -use std::sync::atomic::Ordering; - -fn signal_handler(sig: c_int) { - unsafe { - signal(sig, transmute::<fn(c_int), sighandler_t>(signal_handler)); - - GOT_SIGNAL.store(true, Ordering::Relaxed); - } -} - -impl Application { - pub fn initialise(configuration: &Configuration) -> Application { - eprintln!("initialising"); - - unsafe { - signal(SIGINT, transmute::<fn(c_int), sighandler_t>(signal_handler)); - signal(SIGTERM, transmute::<fn(c_int), sighandler_t>(signal_handler)); - } - - let sdl = sdl2::init().expect("unable to initialise sdl2"); - let sdl_video = sdl.video().expect("unable to initialise sdl2"); - - let window = sdl_video.window("luma", SCREEN_SIZE.width as u32 * configuration.scale, SCREEN_SIZE.height as u32 * configuration.scale).position_centered().build().unwrap(); - - let canvas = window.into_canvas().build().unwrap(); - - return Application { - configuration: configuration.clone(), - sdl: sdl, - sdl_video: sdl_video, - canvas: canvas, - device: Device::new(), - }; - } -} diff --git a/src/luma/application/load.rs b/src/luma/application/load.rs deleted file mode 100644 index 338f120..0000000 --- a/src/luma/application/load.rs +++ /dev/null @@ -1,47 +0,0 @@ -/* - Copyright 2021-2023 Gabriel Jensen. - - This file is part of Luma. - - Luma is free software: you can redistribute it - and/or modify it under the terms of the GNU - Affero General Public License as published by - the Free Software Foundation, either version 3 - of the License, or (at your option) any later - version. - - Luma is distributed in the hope that it will be - useful, but WITHOUT ANY WARRANTY; without even - the implied warranty of MERCHANTABILITY or - FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Affero General Public License for more details. - - You should have received a copy of the GNU - Affero General Public License along with Luma. - If not, see <https://www.gnu.org/licenses/>. -*/ - -use crate::luma::application::Application; - -use std::fs::File; -use std::io::Read; - -impl Application { - pub fn load(&mut self) { - eprintln!("loading booatloader \"{}\"", self.configuration.bootloader); - - // Open bootloader: - let mut bootloader = File::open(self.configuration.bootloader.clone()).expect("unable to open bootloader"); - - // Read bootloader: - bootloader.read(self.device.bootloader()).expect("unable to read bootloader"); - - eprintln!("loading image \"{}\"", self.configuration.image); - - // Open image: - let mut image = File::open(self.configuration.image.clone()).expect("unable to open image"); - - // Read image: - image.read(self.device.image()).expect("unable to read image"); - } -} diff --git a/src/luma/application/run.rs b/src/luma/application/run.rs deleted file mode 100644 index 9fc033b..0000000 --- a/src/luma/application/run.rs +++ /dev/null @@ -1,83 +0,0 @@ -/* - Copyright 2021-2023 Gabriel Jensen. - - This file is part of Luma. - - Luma is free software: you can redistribute it - and/or modify it under the terms of the GNU - Affero General Public License as published by - the Free Software Foundation, either version 3 - of the License, or (at your option) any later - version. - - Luma is distributed in the hope that it will be - useful, but WITHOUT ANY WARRANTY; without even - the implied warranty of MERCHANTABILITY or - FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Affero General Public License for more details. - - You should have received a copy of the GNU - Affero General Public License along with Luma. - If not, see <https://www.gnu.org/licenses/>. -*/ - -use crate::luma::VERSION; -use crate::luma::application::{Application, GOT_SIGNAL}; - -use sdl2::event::Event; -use sdl2::pixels::Color; -use std::sync::atomic::Ordering; -use std::thread::sleep; -use std::time::Duration; - -impl Application { - pub fn run(&mut self) { - eprintln!(); - eprintln!("luma {:X}.{:X}", VERSION.major, VERSION.minor); - eprintln!("Copyright 2021-2023 Gabriel Jensen."); - eprintln!(); - - self.load(); - - let mut event_pump = self.sdl.event_pump().expect("unable to get event pump"); - - 'main_loop: for cycle in 0x0..u64::MAX { - // Check if we have recieved a signal: - if unsafe { GOT_SIGNAL.load(Ordering::Relaxed) } { - eprintln!("got interrupt"); - break; - } - - // Iterate over events: - for event in event_pump.poll_iter() { - match event { - Event::Quit {..} => break 'main_loop, - _ => {}, - } - } - - if cfg!(debug_assertions) { eprintln!("({cycle})"); } - - (self.device.decode)(&mut self.device); - - let raw_colour = self.device.palette()[0x0]; - - let colour = { - let red = ((raw_colour & 0b0000000000011111) << 0x3) as u8; - - let green = ((raw_colour & 0b0000001111100000) >> 0x2) as u8; - - let blue = ((raw_colour & 0b0111110000000000) >> 0x7) as u8; - - Color::RGB(red, green, blue) - }; - - self.canvas.set_draw_color(colour); - self.canvas.clear(); - - self.canvas.present(); - - sleep(Duration::from_millis(250)); - } - } -} diff --git a/src/luma/configuration.rs b/src/luma/configuration.rs index 17e7221..facbc69 100644 --- a/src/luma/configuration.rs +++ b/src/luma/configuration.rs @@ -21,15 +21,16 @@ If not, see <https://www.gnu.org/licenses/>. */ -pub mod create; pub mod load; -pub mod new; -pub mod overwrite; -pub mod path; +pub mod validate; -#[derive(Clone)] pub struct Configuration { pub bootloader: String, pub image: String, - pub scale: u32, + + pub scale: u32, +} + +impl Configuration { + pub const VERSION: u32 = 0x0; } diff --git a/src/luma/configuration/create.rs b/src/luma/configuration/create.rs deleted file mode 100644 index 143fbac..0000000 --- a/src/luma/configuration/create.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 <https://www.gnu.org/licenses/>. -*/ - -use crate::luma::CONFIGURATION_VERSION; -use crate::luma::configuration::Configuration; - -use std::fs::write; - -impl Configuration { - pub(super) fn create(&mut self) { - let configuration_path = Configuration::path(); - - eprintln!("creating configuration at {configuration_path}"); - - let default_configuration = format!( - "# This is the default configuration for the\n\ - # Luma emulator.\n\ - \n\ - [luma]\n\ - version = {CONFIGURATION_VERSION}\n\ - \n\ - [device]\n\ - #bootloader = \"\"\n\ - #image = \"\"\n\ - \n\ - [video]\n\ - scale = 1\n" - ); - - write(configuration_path, default_configuration).unwrap(); - } -} diff --git a/src/luma/configuration/load.rs b/src/luma/configuration/load.rs index d08bcd5..bfd91b0 100644 --- a/src/luma/configuration/load.rs +++ b/src/luma/configuration/load.rs @@ -21,81 +21,80 @@ If not, see <https://www.gnu.org/licenses/>. */ -use crate::luma::CONFIGURATION_VERSION; use crate::luma::configuration::Configuration; -extern crate serde; extern crate toml; -use std::env::var; use std::fs::read_to_string; -use serde::Deserialize; +use std::str::FromStr; +use toml::{Table, Value}; -#[derive(Deserialize)] -struct Container { - luma: Luma, - device: Device, - video: Video, -} +impl Configuration { + pub fn load(path: &str) -> Result<Self, String> { + eprintln!("loading configuration at \"{path}\""); -#[derive(Deserialize)] -struct Luma { - version: Option<u32>, -} + let configuration_text = match read_to_string(path) { + Ok( content) => content, + _ => return Err("unable to read file".to_string()), + }; -#[derive(Deserialize)] -struct Device { - bootloader: Option<String>, - image: Option<String>, -} + let base_table = match Table::from_str(&configuration_text) { + Ok( table) => table, + _ => return Err("unable to parse configuration".to_string()), + }; -#[derive(Deserialize)] -struct Video { - scale: Option<u32>, -} + let luma_table = get_table(&base_table, "luma")?; + let device_table = get_table(&base_table, "device")?; + let video_table = get_table(&base_table, "video")?; -impl Configuration { - pub(super) fn load(&mut self) { - let configuration_path = Configuration::path(); + let version = get_integer(&luma_table, "version")?; - eprintln!("loading configuration \"{configuration_path}\""); + 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 contents = match read_to_string(configuration_path) { - Ok( contents) => contents, - Err(_) => { - eprintln!("unable to read configuration file"); - return self.create(); - }, - }; + let bootloader = get_string(&device_table, "bootloader")?; + let image = get_string(&device_table, "image")?; - let configuration: Container = toml::from_str(contents.as_str()).expect("unable to parse configuration file"); - - let version = configuration.luma.version.expect("missing value 'version' under 'luma'"); - if version < CONFIGURATION_VERSION { panic!("ancient version: downgrade configuration") } - if version > CONFIGURATION_VERSION { panic!("out-of-date version: upgrade configuration") } - - let get_path = |output_path: &mut String, input_path: Option<String>| { - match input_path { - Some(path) => { - *output_path = if path.chars().nth(0x0).unwrap() != '/' { - let home_directory = match var("HOME") { - Ok( path) => path, - Err(_) => { eprintln!("unable to get home directory"); "".to_string() }, - }; - - home_directory + "/" + &path - } else { path } - }, - None => {}, - }; - }; + let scale = get_integer(&video_table, "scale")?; - get_path(&mut self.bootloader, configuration.device.bootloader); - get_path(&mut self.image, configuration.device.image); + let configuration = Configuration { + bootloader: bootloader.clone(), + image: image.clone(), - if configuration.video.scale.is_some() { - self.scale = configuration.video.scale.unwrap(); - assert!(self.scale >= 0x1); - } + 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<u32, String> { + 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/path.rs b/src/luma/configuration/validate.rs index 19d8145..9cdd135 100644 --- a/src/luma/configuration/path.rs +++ b/src/luma/configuration/validate.rs @@ -23,13 +23,10 @@ use crate::luma::configuration::Configuration; -use std::env::var; - impl Configuration { - pub(super) fn path() -> String { - return match var("HOME") { - Ok( path) => path, - Err(_) => panic!("unable to get home directory"), - } + "/.luma.toml"; + 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 new file mode 100644 index 0000000..a31c47d --- /dev/null +++ b/src/luma/cpu.rs @@ -0,0 +1,79 @@ +/* + Copyright 2021-2023 Gabriel Jensen. + + This file is part of Luma. + + Luma is free software: you can redistribute it + and/or modify it under the terms of the GNU + Affero General Public License as published by + the Free Software Foundation, either version 3 + of the License, or (at your option) any later + version. + + Luma is distributed in the hope that it will be + useful, but WITHOUT ANY WARRANTY; without even + the implied warranty of MERCHANTABILITY or + FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Affero General Public License for more details. + + You should have received a copy of the GNU + Affero General Public License along with Luma. + If not, see <https://www.gnu.org/licenses/>. +*/ + +use crate::luma::instruction::Instruction; +use crate::luma::state::State; + +use std::sync::{Arc, Mutex}; +use std::sync::atomic::AtomicBool; + +pub mod add; +pub mod boot; +pub mod branch; +pub mod compare; +pub mod r#continue; +pub mod decode_arm; +pub mod decode_thumb; +pub mod load; +pub mod r#move; +pub mod store; +pub mod subtract; + +pub type Decoder = fn(&mut Cpu) -> Instruction; + +pub struct Cpu { + state: Arc<Mutex<State>>, + cycle: u64, + dead: Arc<AtomicBool>, + + instruction_size: u32, + decoder: Decoder, +} + +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::*; + +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/add.rs b/src/luma/cpu/add.rs new file mode 100644 index 0000000..c2594a3 --- /dev/null +++ b/src/luma/cpu/add.rs @@ -0,0 +1,50 @@ +/* + Copyright 2021-2023 Gabriel Jensen. + + This file is part of Luma. + + Luma is free software: you can redistribute it + and/or modify it under the terms of the GNU + Affero General Public License as published by + the Free Software Foundation, either version 3 + of the License, or (at your option) any later + version. + + Luma is distributed in the hope that it will be + useful, but WITHOUT ANY WARRANTY; without even + the implied warranty of MERCHANTABILITY or + FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Affero General Public License for more details. + + You should have received a copy of the GNU + Affero General Public License along with Luma. + If not, see <https://www.gnu.org/licenses/>. +*/ + +use crate::luma::log; +use crate::luma::cpu::Cpu; + +impl Cpu { + pub(super) fn add_immediate(&mut self, destination: u8, base: u8, immediate: u32) { + log(&format!("add r{destination}, r{base}, {immediate:#X}")); + + let mut state = self.state.lock().unwrap(); + + let base_value = state.read_register(base); + + let value = base_value.wrapping_add(immediate); + state.write_register(destination, value); + } + + pub(super) fn add_register(&mut self, destination: u8, base: u8, add: u8) { + log(&format!("add r{destination}, r{base}, r{add}")); + + let mut state = self.state.lock().unwrap(); + + let base_value = state.read_register(base); + let add_value = state.read_register(add); + + let value = base_value.wrapping_add(add_value); + state.write_register(destination, value); + } +} diff --git a/src/luma/cpu/boot.rs b/src/luma/cpu/boot.rs new file mode 100644 index 0000000..f92176b --- /dev/null +++ b/src/luma/cpu/boot.rs @@ -0,0 +1,108 @@ +/* + Copyright 2021-2023 Gabriel Jensen. + + This file is part of Luma. + + Luma is free software: you can redistribute it + and/or modify it under the terms of the GNU + Affero General Public License as published by + the Free Software Foundation, either version 3 + of the License, or (at your option) any later + version. + + Luma is distributed in the hope that it will be + useful, but WITHOUT ANY WARRANTY; without even + the implied warranty of MERCHANTABILITY or + FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Affero General Public License for more details. + + You should have received a copy of the GNU + Affero General Public License along with Luma. + If not, see <https://www.gnu.org/licenses/>. +*/ + +use crate::luma::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 { + AddImmediate( destination, base, immediate) => self.add_immediate( destination, base, immediate), + AddRegister( destination, base, immediate) => self.add_register( destination, base, immediate), + Branch( offset) => self.branch( offset), + BranchExchange( source) => self.branch_exchange( source), + BranchLink( offset) => self.branch_link( offset), + BranchLinkPrefix( offset) => self.branch_link_prefix( offset), + BranchLinkSuffix( offset) => self.branch_link_suffix( offset), + CompareImmediate( register, immediate) => self.compare_immediate( register, immediate), + CompareRegister( left, right) => self.compare_register( left, right), + LoadHalfword( destination, base, offset) => self.load_halfword( destination, base, offset), + LoadImmediateOffset( destination, base, offset) => self.load_immediate_offset( destination, base, offset), + LoadPc( destination, offset) => self.load_pc( destination, offset), + MoveImmediate( destination, immediate) => self.move_immediate( destination, immediate), + MoveImmediateArithmeticShiftRight( destination, base, shift) => self.move_immediate_arithmetic_shift_right(destination, base, shift), + MoveImmediateLogicalShiftLeftImmediate( destination, base, shift) => self.move_immediate_logical_shift_left( destination, base, shift), + MoveImmediateLogicalShiftRightImmediate(destination, base, shift) => self.move_immediate_logical_shift_right( destination, base, shift), + MoveRegister( destination, source) => self.move_register( destination, source), + MoveRegisterArithmeticShiftRight( destination, base, shift) => self.move_register_arithmetic_shift_right( destination, base, shift), + MoveRegisterLogicalShiftLeftImmediate( destination, base, shift) => self.move_register_logical_shift_left( destination, base, shift), + MoveRegisterLogicalShiftRightImmediate( destination, base, shift) => self.move_register_logical_shift_right( destination, base, shift), + StoreByteImmediateOffset( source, base, offset) => self.store_byte_immediate_offset( source, base, offset), + StoreByteRegisterOffset( source, base, offset) => self.store_byte_register_offset( source, base, offset), + StoreHalfword( source, base, offset) => self.store_halfword( source, base, offset), + StoreImmediateOffset( source, base, offset) => self.store_immediate_offset( source, base, offset), + SubtractImmediate( destination, base, immediate) => self.subtract_immediate( destination, base, immediate), + SubtractRegister( destination, base, immediate) => self.subtract_register( destination, base, immediate), + + Undefined => {}, + }; + + self.r#continue(); + } +} diff --git a/src/luma/cpu/branch.rs b/src/luma/cpu/branch.rs new file mode 100644 index 0000000..a29205d --- /dev/null +++ b/src/luma/cpu/branch.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 General Public License as published by + the Free Software Foundation, either version 3 + of the License, or (at your option) any later + version. + + Luma is distributed in the hope that it will be + useful, but WITHOUT ANY WARRANTY; without even + the implied warranty of MERCHANTABILITY or + FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Affero General Public License for more details. + + You should have received a copy of the GNU + Affero General Public License along with Luma. + If not, see <https://www.gnu.org/licenses/>. +*/ + +use crate::luma::log; +use crate::luma::cpu::{Cpu, exchange}; + +impl Cpu { + pub(super) fn branch(&mut self, offset: i32) { + let mut state = self.state.lock().unwrap(); + + let mut target = state.read_register(0xF).wrapping_add_signed(offset); + + log(&format!("b {target:#X}")); + + target = target.wrapping_add(self.instruction_size); + state.write_register(0xF, target); + } + + pub(super) fn branch_exchange(&mut self, source: u8) { + let mut state = self.state.lock().unwrap(); + + log(&format!("bx r{source}")); + + let mut target = state.read_register(source); + + 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 branch_link(&mut self, offset: i32) { + let mut state = self.state.lock().unwrap(); + + let mut target = state.read_register(0xF).wrapping_add_signed(offset); + + log(&format!("bl {target:#X}")); + + target = target.wrapping_add(self.instruction_size); + state.write_register(0xF, target); + } + + pub(super) fn branch_link_prefix(&mut self, offset: i32) { + let mut state = self.state.lock().unwrap(); + + let target = state.read_register(0xF).wrapping_add_signed(offset); + + state.write_register(0xE, target); + } + + pub(super) fn branch_link_suffix(&mut self, offset: i32) { + let mut state = self.state.lock().unwrap(); + + let mut branch_target = state.read_register(0xE).wrapping_add_signed(offset); + let link_target = state.read_register(0xF).wrapping_sub(0x2); + + log(&format!("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/compare.rs b/src/luma/cpu/compare.rs new file mode 100644 index 0000000..4aa3bc4 --- /dev/null +++ b/src/luma/cpu/compare.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 <https://www.gnu.org/licenses/>. +*/ + +use crate::luma::log; +use crate::luma::cpu::Cpu; + +impl Cpu { + pub(super) fn compare_immediate(&mut self, register: u8, immediate: u32) { + log(&format!("cmp r{register}, {immediate:#X}")); + + let mut state = self.state.lock().unwrap(); + + let register_value = state.read_register(register); + + let (temporary, c) = register_value.overflowing_sub(immediate); + + let v = false; // ??? + let z = temporary == 0x0; + let n = temporary & 0b10000000000000000000000000000000 != 0x0; + + let mut cpsr = state.read_cpsr() & 0b00001111111111111111111111111111; + cpsr |= (v as u32) << 0x1C; + cpsr |= (c as u32) << 0x1D; + cpsr |= (z as u32) << 0x1E; + cpsr |= (n as u32) << 0x1F; + state.write_cpsr(cpsr); + } + + pub(super) fn compare_register(&mut self, left: u8, right: u8) { + log(&format!("cmp r{left}, r{right}")); + + let mut state = self.state.lock().unwrap(); + + let left_value = state.read_register(left); + let right_value = state.read_register(right); + + let (temporary, c) = left_value.overflowing_sub(right_value); + + let v = false; // ??? + let z = temporary == 0x0; + let n = temporary & 0b10000000000000000000000000000000 != 0x0; + + let mut cpsr = state.read_cpsr() & 0b00001111111111111111111111111111; + cpsr |= (v as u32) << 0x1C; + cpsr |= (c as u32) << 0x1D; + cpsr |= (z as u32) << 0x1E; + cpsr |= (n as u32) << 0x1F; + state.write_cpsr(cpsr); + } +} diff --git a/src/luma/device/thumb.rs b/src/luma/cpu/continue.rs index aeb7059..6d10bc3 100644 --- a/src/luma/device/thumb.rs +++ b/src/luma/cpu/continue.rs @@ -21,10 +21,13 @@ If not, see <https://www.gnu.org/licenses/>. */ -use crate::luma::device::Device; +use crate::luma::cpu::Cpu; -impl Device { - pub fn thumb(&self) -> bool { - return self.cpsr & 0b00000000000000000000000000100000 != 0x0; +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 new file mode 100644 index 0000000..9b4377c --- /dev/null +++ b/src/luma/cpu/decode_arm.rs @@ -0,0 +1,256 @@ +/* + Copyright 2021-2023 Gabriel Jensen. + + This file is part of Luma. + + Luma is free software: you can redistribute it + and/or modify it under the terms of the GNU + Affero General Public License as published by + the Free Software Foundation, either version 3 + of the License, or (at your option) any later + version. + + Luma is distributed in the hope that it will be + useful, but WITHOUT ANY WARRANTY; without even + the implied warranty of MERCHANTABILITY or + FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Affero General Public License for more details. + + You should have received a copy of the GNU + Affero General Public License along with Luma. + If not, see <https://www.gnu.org/licenses/>. +*/ + +use crate::luma::{Error, log}; +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(&format!("{opcode:#034b} @ {address:#010X} - ({})", 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) 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; + + let immediate = (immediate as u32).rotate_right(rotate as u32); + + 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 new file mode 100644 index 0000000..c8eacb8 --- /dev/null +++ b/src/luma/cpu/decode_thumb.rs @@ -0,0 +1,311 @@ +/* + Copyright 2021-2023 Gabriel Jensen. + + This file is part of Luma. + + Luma is free software: you can redistribute it + and/or modify it under the terms of the GNU + Affero General Public License as published by + the Free Software Foundation, either version 3 + of the License, or (at your option) any later + version. + + Luma is distributed in the hope that it will be + useful, but WITHOUT ANY WARRANTY; without even + the implied warranty of MERCHANTABILITY or + FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Affero General Public License for more details. + + You should have received a copy of the GNU + Affero General Public License along with Luma. + If not, see <https://www.gnu.org/licenses/>. +*/ + +use crate::luma::{Error, log}; +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(&format!("{opcode:#018b} @ {address:#010X} - ({})", 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 destination = (opcode & 0b0000000000000111) as u8; + + let base = (opcode & 0b0000000000111000).wrapping_shr(0x3) as u8; + + match (opcode & 0b0000001111000000).wrapping_shr(0x6) { + 0b0000 => {}, + + 0b0001 => {}, + + 0b0010 => {}, + + 0b0011 => {}, + + 0b0100 => {}, + + 0b0101 => {}, + + 0b0110 => {}, + + 0b0111 => {}, + + 0b1000 => {}, + + 0b1001 => {}, + + 0b1010 => return CompareRegister(destination, base), + + 0b1011 => {}, + + 0b1100 => {}, + + 0b1101 => {}, + + 0b1110 => {}, + + 0b1111 => {}, + + _ => 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 new file mode 100644 index 0000000..2414831 --- /dev/null +++ b/src/luma/cpu/exchange.rs @@ -0,0 +1,39 @@ +/* + Copyright 2021-2023 Gabriel Jensen. + + This file is part of Luma. + + Luma is free software: you can redistribute it + and/or modify it under the terms of the GNU + Affero General Public License as published by + the Free Software Foundation, either version 3 + of the License, or (at your option) any later + version. + + Luma is distributed in the hope that it will be + useful, but WITHOUT ANY WARRANTY; without even + the implied warranty of MERCHANTABILITY or + FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Affero General Public License for more details. + + You should have received a copy of the GNU + Affero General Public License along with Luma. + If not, see <https://www.gnu.org/licenses/>. +*/ + +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/load.rs b/src/luma/cpu/load.rs new file mode 100644 index 0000000..39a6e49 --- /dev/null +++ b/src/luma/cpu/load.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 <https://www.gnu.org/licenses/>. +*/ + +use crate::luma::log; +use crate::luma::cpu::Cpu; + +impl Cpu { + pub(super) fn load_halfword(&mut self, destination: u8, base: u8, offset: i16) { + let mut state = self.state.lock().unwrap(); + + log(&format!("ldrh r{destination}, [r{base}, {offset:#X}]")); + + let base_value = state.read_register(base); + + let target = base_value.wrapping_add_signed(offset as i32); + + let value = state.read_halfword(target) as u32; + state.write_register(destination, value); + } + + pub(super) fn load_immediate_offset(&mut self, destination: u8, base: u8, offset: i16) { + let mut state = self.state.lock().unwrap(); + + log(&format!("ldr r{destination}, [r{base}, {offset:#X}]")); + + let base_value = state.read_register(base); + + let target = base_value.wrapping_add_signed(offset as i32); + + let value = state.read_word(target); + state.write_register(destination, value); + } + + pub(super) fn load_pc(&mut self, destination: u8, offset: i16) { + // Slightly different from load_immediate_offset + // due to the target being forced word-aligned. + + let mut state = self.state.lock().unwrap(); + + log(&format!("ldr r{destination}, [pc, {offset:#X}]")); + + let base_value = state.read_register(0xF) & 0b11111111111111111111111111111100; + + let target = base_value.wrapping_add_signed(offset as i32); + + let value = state.read_word(target); + state.write_register(destination, value); + } +} diff --git a/src/luma/cpu/move.rs b/src/luma/cpu/move.rs new file mode 100644 index 0000000..8389b16 --- /dev/null +++ b/src/luma/cpu/move.rs @@ -0,0 +1,113 @@ +/* + Copyright 2021-2023 Gabriel Jensen. + + This file is part of Luma. + + Luma is free software: you can redistribute it + and/or modify it under the terms of the GNU + Affero General Public License as published by + the Free Software Foundation, either version 3 + of the License, or (at your option) any later + version. + + Luma is distributed in the hope that it will be + useful, but WITHOUT ANY WARRANTY; without even + the implied warranty of MERCHANTABILITY or + FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Affero General Public License for more details. + + You should have received a copy of the GNU + Affero General Public License along with Luma. + If not, see <https://www.gnu.org/licenses/>. +*/ + +use crate::luma::log; +use crate::luma::cpu::Cpu; + +impl Cpu { + pub(super) fn move_immediate(&mut self, destination: u8, immediate: u32) { + log(&format!("mov r{destination}, {immediate:#X}")); + + let mut state = self.state.lock().unwrap(); + + state.write_register(destination, immediate); + } + + pub(super) fn move_immediate_arithmetic_shift_right(&mut self, destination: u8, base: u8, shift: u8) { + log(&format!("mov r{destination}, r{base}, ASR {shift:#X}")); + + let mut state = self.state.lock().unwrap(); + + let base_value = state.read_register(base); + + let value = (base_value as i32).wrapping_shr(shift as u32) as u32; + state.write_register(destination, value); + } + + pub(super) fn move_immediate_logical_shift_left(&mut self, destination: u8, base: u8, shift: u8) { + log(&format!("mov r{destination}, r{base}, LSL {shift:#X}")); + + let mut state = self.state.lock().unwrap(); + + let base_value = state.read_register(base); + + let value = base_value.wrapping_shl(shift as u32); + state.write_register(destination, value); + } + + pub(super) fn move_immediate_logical_shift_right(&mut self, destination: u8, base: u8, shift: u8) { + log(&format!("mov r{destination}, r{base}, LSR {shift:#X}")); + + let mut state = self.state.lock().unwrap(); + + let base_value = state.read_register(base); + + let value = base_value.wrapping_shr(shift as u32); + state.write_register(destination, value); + } + + pub(super) fn move_register(&mut self, destination: u8, source: u8) { + log(&format!("mov r{destination}, r{source}")); + + let mut state = self.state.lock().unwrap(); + + let value = state.read_register(source); + state.write_register(destination, value); + } + + pub(super) fn move_register_arithmetic_shift_right(&mut self, destination: u8, base: u8, shift: u8) { + log(&format!("mov r{destination}, r{base}, ASR r{shift}")); + + 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(destination, value); + } + + pub(super) fn move_register_logical_shift_left(&mut self, destination: u8, base: u8, shift: u8) { + log(&format!("mov r{destination}, r{base}, LSL r{shift}")); + + 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(destination, value); + } + + pub(super) fn move_register_logical_shift_right(&mut self, destination: u8, base: u8, shift: u8) { + log(&format!("mov r{destination}, r{base}, LSR r{shift}")); + + 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(destination, value); + } +} diff --git a/src/luma/cpu/store.rs b/src/luma/cpu/store.rs new file mode 100644 index 0000000..6f073ab --- /dev/null +++ b/src/luma/cpu/store.rs @@ -0,0 +1,80 @@ +/* + Copyright 2021-2023 Gabriel Jensen. + + This file is part of Luma. + + Luma is free software: you can redistribute it + and/or modify it under the terms of the GNU + Affero General Public License as published by + the Free Software Foundation, either version 3 + of the License, or (at your option) any later + version. + + Luma is distributed in the hope that it will be + useful, but WITHOUT ANY WARRANTY; without even + the implied warranty of MERCHANTABILITY or + FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Affero General Public License for more details. + + You should have received a copy of the GNU + Affero General Public License along with Luma. + If not, see <https://www.gnu.org/licenses/>. +*/ + +use crate::luma::log; +use crate::luma::cpu::Cpu; + +impl Cpu { + pub(super) fn store_byte_immediate_offset(&mut self, source: u8, base: u8, offset: i16) { + log(&format!("strb r{source}, [r{base}, {offset:#X}]")); + + let mut state = self.state.lock().unwrap(); + + let base_value = state.read_register(base); + + let target = base_value.wrapping_add_signed(offset as i32); + + let value = state.read_register(source) as u8; + state.write_byte(target, value); + } + + pub(super) fn store_byte_register_offset(&mut self, source: u8, base: u8, offset: u8) { + log(&format!("strb r{source}, [r{base}, r{offset}]")); + + let mut state = self.state.lock().unwrap(); + + let base_value = state.read_register(base); + let offset_value = state.read_register(offset); + + let target = base_value.wrapping_add(offset_value); + + let value = state.read_register(source) as u8; + state.write_byte(target, value); + } + + pub(super) fn store_halfword(&mut self, source: u8, base: u8, offset: i16) { + log(&format!("strh r{source}, [r{base}, {offset:#X}]")); + + let mut state = self.state.lock().unwrap(); + + let base_value = state.read_register(base); + + let target = base_value.wrapping_add_signed(offset as i32); + + let value = state.read_register(source) as u16; + state.write_halfword(target, value); + } + + pub(super) fn store_immediate_offset(&mut self, source: u8, base: u8, offset: i16) { + log(&format!("str r{source}, [r{base}, {offset:#X}]")); + + let mut state = self.state.lock().unwrap(); + + let base_value = state.read_register(base); + + let target = base_value.wrapping_add_signed(offset as i32); + + let value = state.read_register(source); + state.write_word(target, value); + } +} diff --git a/src/luma/cpu/subtract.rs b/src/luma/cpu/subtract.rs new file mode 100644 index 0000000..59267ca --- /dev/null +++ b/src/luma/cpu/subtract.rs @@ -0,0 +1,50 @@ +/* + Copyright 2021-2023 Gabriel Jensen. + + This file is part of Luma. + + Luma is free software: you can redistribute it + and/or modify it under the terms of the GNU + Affero General Public License as published by + the Free Software Foundation, either version 3 + of the License, or (at your option) any later + version. + + Luma is distributed in the hope that it will be + useful, but WITHOUT ANY WARRANTY; without even + the implied warranty of MERCHANTABILITY or + FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Affero General Public License for more details. + + You should have received a copy of the GNU + Affero General Public License along with Luma. + If not, see <https://www.gnu.org/licenses/>. +*/ + +use crate::luma::log; +use crate::luma::cpu::Cpu; + +impl Cpu { + pub(super) fn subtract_immediate(&mut self, destination: u8, base: u8, immediate: u32) { + log(&format!("sub r{destination}, r{base}, {immediate:#X}")); + + let mut state = self.state.lock().unwrap(); + + let base_value = state.read_register(base); + + let value = base_value.wrapping_sub(immediate); + state.write_register(destination, value); + } + + pub(super) fn subtract_register(&mut self, destination: u8, base: u8, subtract: u8) { + log(&format!("sub r{destination}, r{base}, r{subtract}")); + + let mut state = self.state.lock().unwrap(); + + let base_value = state.read_register(base); + let subtract_value = state.read_register(subtract); + + let value = base_value.wrapping_sub(subtract_value); + state.write_register(destination, value); + } +} diff --git a/src/luma/cpu/test_predicate.rs b/src/luma/cpu/test_predicate.rs new file mode 100644 index 0000000..b6ed46a --- /dev/null +++ b/src/luma/cpu/test_predicate.rs @@ -0,0 +1,74 @@ +/* + Copyright 2021-2023 Gabriel Jensen. + + This file is part of Luma. + + Luma is free software: you can redistribute it + and/or modify it under the terms of the GNU + Affero General Public License as published by + the Free Software Foundation, either version 3 + of the License, or (at your option) any later + version. + + Luma is distributed in the hope that it will be + useful, but WITHOUT ANY WARRANTY; without even + the implied warranty of MERCHANTABILITY or + FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Affero General Public License for more details. + + You should have received a copy of the GNU + Affero General Public License along with Luma. + If not, see <https://www.gnu.org/licenses/>. +*/ + +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/application.rs b/src/luma/cpu_handle.rs index 1b4c879..f83ddd2 100644 --- a/src/luma/application.rs +++ b/src/luma/cpu_handle.rs @@ -21,27 +21,35 @@ If not, see <https://www.gnu.org/licenses/>. */ -use crate::luma::configuration::Configuration; -use crate::luma::device::Device; +use crate::luma::state::State; -extern crate sdl2; - -use sdl2::{Sdl, VideoSubsystem}; -use sdl2::render::WindowCanvas; -use sdl2::video::Window; +use std::sync::{Arc, Mutex}; use std::sync::atomic::AtomicBool; +use std::thread::JoinHandle; + +pub mod dump_video; +pub mod dump_palette; +pub mod kill; + +pub struct CpuHandle { + state: Arc<Mutex<State>>, + dead: Arc<AtomicBool>, -pub mod drop; -pub mod initialise; -pub mod load; -pub mod run; - -pub struct Application { - configuration: Configuration, - sdl: Sdl, - sdl_video: VideoSubsystem, - canvas: WindowCanvas, - device: Device, + handle: JoinHandle<()>, } -pub static mut GOT_SIGNAL: AtomicBool = AtomicBool::new(false); +impl CpuHandle { + pub fn new( + state: Arc<Mutex<State>>, + dead: Arc<AtomicBool>, + + handle: JoinHandle<()>, + ) -> Self { + return Self { + state: state, + dead: dead, + + handle: handle, + }; + } +} diff --git a/src/luma/configuration/new.rs b/src/luma/cpu_handle/dump_palette.rs index 7875d64..c59a132 100644 --- a/src/luma/configuration/new.rs +++ b/src/luma/cpu_handle/dump_palette.rs @@ -21,19 +21,16 @@ If not, see <https://www.gnu.org/licenses/>. */ -use crate::luma::configuration::Configuration; +use crate::luma::PALETTE_LENGTH; +use crate::luma::cpu_handle::CpuHandle; -impl Configuration { - pub fn new() -> Configuration { - let mut configuration = Configuration { - bootloader: "bootloader.bin".to_string(), - image: "image.agb".to_string(), - scale: 0x1, - }; +use std::ptr::copy_nonoverlapping; - configuration.load(); - configuration.overwrite(); +impl CpuHandle { + pub fn dump_palette(&mut self, buffer: &mut [u16]) { + assert_eq!(buffer.len(), PALETTE_LENGTH as usize >> 0x1); - return configuration; + let state = self.state.lock().unwrap(); + unsafe { copy_nonoverlapping(state.palette().as_ptr(), buffer.as_mut_ptr(), buffer.len()) }; } } diff --git a/src/luma/device/drop.rs b/src/luma/cpu_handle/dump_video.rs index da5c727..c59c467 100644 --- a/src/luma/device/drop.rs +++ b/src/luma/cpu_handle/dump_video.rs @@ -21,14 +21,14 @@ If not, see <https://www.gnu.org/licenses/>. */ -use crate::luma::device::Device; -use crate::luma::MEMORY_SIZE; +use crate::luma::VIDEO_LENGTH; +use crate::luma::cpu_handle::CpuHandle; -use std::alloc::{dealloc, Layout}; -use std::mem::size_of; +impl CpuHandle { + pub fn dump_video(&mut self, buffer: &mut [u8]) { + assert_eq!(buffer.len(), VIDEO_LENGTH as usize); -impl Drop for Device { - fn drop(&mut self) { - unsafe { dealloc(self.memory, Layout::new::<[u32; MEMORY_SIZE / size_of::<u32>()]>()) }; + let state = self.state.lock().unwrap(); + buffer.copy_from_slice(state.video8()); } } diff --git a/src/luma/device/bootloader.rs b/src/luma/cpu_handle/kill.rs index 2f84f70..e89affa 100644 --- a/src/luma/device/bootloader.rs +++ b/src/luma/cpu_handle/kill.rs @@ -21,13 +21,20 @@ If not, see <https://www.gnu.org/licenses/>. */ -use crate::luma::device::Device; -use crate::luma::BOOTLOADER_SIZE; +use crate::luma::cpu_handle::CpuHandle; -use std::slice; +use std::sync::atomic::Ordering; -impl Device { - pub fn bootloader<'a>(&mut self) -> &'a mut [u8] { - return unsafe { slice::from_raw_parts_mut(self.memory.offset(0x00000000), BOOTLOADER_SIZE) }; +impl CpuHandle { + #[must_use] + pub fn kill(self) -> Result<(), String> { + eprintln!("got kill order"); + + self.dead.store(true, Ordering::Relaxed); + self.handle.join().unwrap(); + + self.dead.store(false, Ordering::Relaxed); + + return Ok(()); } } diff --git a/src/luma/device.rs b/src/luma/device.rs deleted file mode 100644 index 240535d..0000000 --- a/src/luma/device.rs +++ /dev/null @@ -1,84 +0,0 @@ -/* - Copyright 2021-2023 Gabriel Jensen. - - This file is part of Luma. - - Luma is free software: you can redistribute it - and/or modify it under the terms of the GNU - Affero General Public License as published by - the Free Software Foundation, either version 3 - of the License, or (at your option) any later - version. - - Luma is distributed in the hope that it will be - useful, but WITHOUT ANY WARRANTY; without even - the implied warranty of MERCHANTABILITY or - FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Affero General Public License for more details. - - You should have received a copy of the GNU - Affero General Public License along with Luma. - If not, see <https://www.gnu.org/licenses/>. -*/ - -pub mod bootloader; -pub mod branch; -pub mod check_condition; -pub mod r#continue; -pub mod decode_arm; -pub mod decode_thumb; -pub mod drop; -pub mod exchange; -pub mod image; -pub mod interrupt; -pub mod link; -pub mod load; -pub mod log; -pub mod memory; -pub mod r#move; -pub mod new; -pub mod palette; -pub mod pop; -pub mod push; -pub mod read; -pub mod shift; -pub mod store; -pub mod thumb; -pub mod trap; -pub mod video; -pub mod write; - -pub enum Trap { - BadAlignment( u32, u32), - InvalidArmOpcode( u32, u32), - InvalidThumbOpcode(u32, u16), - OutOfBounds( u32), -} - -pub enum Log { - Branch, - Continue, - Exchange, - Interrupt, - Link, - Load, - Move, - Pop, - Push, - Shift, - Store, -} - -pub enum Move { - Immediate(u8), - Register( u8), -} - -pub struct Device { - pub decode: fn(&mut Device), - - memory: *mut u8, - registers: [u32; 0x10], - cpsr: u32, - spsr: [u32; 0x10], // We don't actually use all sixteen, we just have this many to enable us to directly use the mode number as the offset. -} diff --git a/src/luma/device/branch.rs b/src/luma/device/branch.rs deleted file mode 100644 index 0bed178..0000000 --- a/src/luma/device/branch.rs +++ /dev/null @@ -1,109 +0,0 @@ -/* - Copyright 2021-2023 Gabriel Jensen. - - This file is part of Luma. - - Luma is free software: you can redistribute it - and/or modify it under the terms of the GNU - Affero General Public License as published by - the Free Software Foundation, either version 3 - of the License, or (at your option) any later - version. - - Luma is distributed in the hope that it will be - useful, but WITHOUT ANY WARRANTY; without even - the implied warranty of MERCHANTABILITY or - FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Affero General Public License for more details. - - You should have received a copy of the GNU - Affero General Public License along with Luma. - If not, see <https://www.gnu.org/licenses/>. -*/ - -use crate::luma::device::{Device, Log}; - -impl Device { - pub fn arm_branch(&mut self, offset: i32, l: bool) { - // Add the offset to r15 (pc). Conditionally link. - - if l { self.arm_link() } - - let (address, _) = self.registers[0xF].overflowing_add_signed(offset); - - // Add extra offset to move to the new fetch - // instruction. - self.registers[0xF] = address + 0x8; - - self.log(Log::Branch, format!("pc => pc{offset:+}+8 ({:#010X})", self.registers[0xF])); - } - - pub fn arm_branch_exchange(&mut self, register: u8) { - // Use the address stored in 'register' as the new - // value in r15 (pc). - - let value = self.registers[register as usize]; - - let t = value & 0b00000000000000000000000000000001 != 0x0; - - self.cpsr = self.cpsr & 0b11111111111111111111111111011111 | (t as u32) << 0x5; - self.exchange(t); - - // Add extra offset to move to the new fetch - // instruction. - let pc_offset: u32 = match t { - false => 0x8, - true => 0x4, - }; - - self.registers[0xF] = (value & 0b11111111111111111111111111111110) + pc_offset; - - self.log(Log::Branch, format!("pc => r{register}{pc_offset:+} ({:#010X})", self.registers[0xF])); - } - - pub fn thumb_branch(&mut self, offset: i32) { - let (address, _) = self.registers[0xF].overflowing_add_signed(offset); - - self.registers[0xF] = address + 0x4; - - self.log(Log::Branch, format!("pc => pc{offset:+}+4 ({:#010X})", self.registers[0xF])); - } - - pub fn thumb_branch_exchange(&mut self, register: u8) { - let value = self.registers[register as usize]; - - let t = value & 0b00000000000000000000000000000001 != 0x0; - - self.cpsr = self.cpsr & 0b11111111111111111111111111011111 | (t as u32) << 0x5; - self.exchange(t); - - // Add extra offset to move to the new fetch - // instruction. - let pc_offset: u32 = match t { - false => 0x8, - true => 0x4, - }; - - self.registers[0xF] = (value & 0b11111111111111111111111111111110) + pc_offset; - - self.log(Log::Branch, format!("pc => r{register}{pc_offset:+} ({:#010X})", self.registers[0xF])); - } - - pub fn thumb_branch_link0(&mut self, offset: i32) { - let (address, _) = self.registers[0xF].overflowing_add_signed(offset); - self.registers[0xE] = address; - - self.log(Log::Branch, format!("lr => pc{offset:+}+4 ({:#010X})", self.registers[0xF])); - } - - pub fn thumb_branch_link1(&mut self, offset: i32) { - let (address, _) = self.registers[0xE].overflowing_add_signed(offset); - - self.thumb_link(); - - (self.registers[0xF], _) = address.overflowing_add(0x4); - - self.log(Log::Branch, format!("pc => pc{offset:+}+4 ({:#010X})", self.registers[0xF])); - } - -} diff --git a/src/luma/device/check_condition.rs b/src/luma/device/check_condition.rs deleted file mode 100644 index 7b84d8a..0000000 --- a/src/luma/device/check_condition.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 <https://www.gnu.org/licenses/>. -*/ - -use crate::luma::device::Device; - -impl Device { - pub fn check_condition(&self, condition: u8) -> bool { - // 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 - - return match condition { - 0x0 => self.cpsr & 0b01000000000000000000000000000000 != 0x00, - 0x1 => self.cpsr & 0b01000000000000000000000000000000 == 0x00, - 0x2 => self.cpsr & 0b00100000000000000000000000000000 != 0x00, - 0x3 => self.cpsr & 0b00100000000000000000000000000000 == 0x00, - 0x4 => self.cpsr & 0b10000000000000000000000000000000 != 0x00, - 0x5 => self.cpsr & 0b10000000000000000000000000000000 == 0x00, - 0x6 => self.cpsr & 0b00010000000000000000000000000000 != 0x00, - 0x7 => self.cpsr & 0b00010000000000000000000000000000 == 0x00, - 0x8 => self.cpsr & 0b00100000000000000000000000000000 != 0x00 && self.cpsr & 0b01000000000000000000000000000000 == 0x00, - 0x9 => self.cpsr & 0b00100000000000000000000000000000 == 0x00 && self.cpsr & 0b01000000000000000000000000000000 != 0x00, - 0xA => self.cpsr & 0b00010000000000000000000000000000 >> 0x1C == self.cpsr & 0b10000000000000000000000000000000 >> 0x1F, - 0xB => self.cpsr & 0b00010000000000000000000000000000 >> 0x1C != self.cpsr & 0b10000000000000000000000000000000 >> 0x1F, - 0xC => self.cpsr & 0b01000000000000000000000000000000 == 0x00 && self.cpsr & 0b00010000000000000000000000000000 >> 0x1C == self.cpsr & 0b10000000000000000000000000000000 >> 0x1F, - 0xD => self.cpsr & 0b01000000000000000000000000000000 != 0x00 || self.cpsr & 0b00010000000000000000000000000000 >> 0x1C != self.cpsr & 0b10000000000000000000000000000000 >> 0x1F, - 0xE => true, - 0xF => false, - _ => unreachable!(), - } - } -} diff --git a/src/luma/device/decode_arm.rs b/src/luma/device/decode_arm.rs deleted file mode 100644 index 3cd0ed9..0000000 --- a/src/luma/device/decode_arm.rs +++ /dev/null @@ -1,107 +0,0 @@ -/* - Copyright 2021-2023 Gabriel Jensen. - - This file is part of Luma. - - Luma is free software: you can redistribute it - and/or modify it under the terms of the GNU - Affero General Public License as published by - the Free Software Foundation, either version 3 - of the License, or (at your option) any later - version. - - Luma is distributed in the hope that it will be - useful, but WITHOUT ANY WARRANTY; without even - the implied warranty of MERCHANTABILITY or - FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Affero General Public License for more details. - - You should have received a copy of the GNU - Affero General Public License along with Luma. - If not, see <https://www.gnu.org/licenses/>. -*/ - -use crate::luma::device::{Device, Move, Trap}; - -impl Device { - pub fn decode_arm(&mut self) { - debug_assert!(!self.thumb()); - - let (address, _) = self.registers[0xF].overflowing_sub(0x8); - - let opcode = self.read_word(address); - if cfg!(debug_assertions) { eprintln!("{opcode:#034b} @ {address:#010X}") } - - // b{cond}{l} Offset24 - if opcode & 0b00001110000000000000000000000000 == 0b00001010000000000000000000000000 { - let condition = ((opcode & 0b11110000000000000000000000000000) >> 0x1C) as u8; - if !self.check_condition(condition) { return self.arm_continue() } - - let immediate = opcode & 0b00000000111111111111111111111111; - - let l = opcode & 0b00000001000000000000000000000000 != 0x0; - - let offset = ((immediate << 0x8) as i32) >> 0x6; - - return self.arm_branch(offset, l); - } - - // bx{cond} Rm - if opcode & 0b00001111111111111111111111110000 == 0b00000001001011111111111100010000 { - let condition = ((opcode & 0b11110000000000000000000000000000) >> 0x1C) as u8; - if !self.check_condition(condition) { return self.arm_continue() } - - let register = (opcode & 0b00000000000000000000000000001111) as u8; - - return self.arm_branch_exchange(register); - } - - // ldr|str{cond}{b} Rd, [Rn, Offset12] - if opcode & 0b00001111001000000000000000000000 == 0b00000101000000000000000000000000 { - let condition = ((opcode & 0b11110000000000000000000000000000) >> 0x1C) as u8; - if !self.check_condition(condition) { return self.arm_continue() } - - let immediate = (opcode & 0b00000000000000000000111111111111) as u16; - - let register = ((opcode & 0b00000000000000001111000000000000) >> 0xC) as u8; - - let base = ((opcode & 0b00000000000011110000000000000000) >> 0x10) as u8; - - let l = opcode & 0b00000000000100000000000000000000 != 0x0; - let b = opcode & 0b00000000010000000000000000000000 != 0x0; - let u = opcode & 0b00000000100000000000000000000000 != 0x0; - - self.arm_store(register, base, immediate, u, b, l); - return self.arm_continue(); - } - - // mov{cond}{s} Rd, Rn - if opcode & 0b00001101111111100000111111110000 == 0b00000001101000000000000000000000 { - let condition = ((opcode & 0b11110000000000000000000000000000) >> 0x1C) as u8; - if !self.check_condition(condition) { return self.arm_continue() } - - let source = (opcode & 0b00000000000000000000000000001111) as u8; - - let destination = ((opcode & 0b00000000000000001111000000000000) >> 0xC) as u8; - - let s = opcode & 0b00000000000100000000000000000000 != 0x0; - - self.arm_move(destination, Move::Register(source), s); - return self.arm_continue(); - } - - // svc{cond} Immediate24 - if opcode & 0b00001111000000000000000000000000 == 0b00001111000000000000000000000000 { - let condition = ((opcode & 0b11110000000000000000000000000000) >> 0x1C) as u8; - if !self.check_condition(condition) { return self.arm_continue() } - - let immediate = opcode & 0b00000000111111111111111111111111; - - return self.interrupt(immediate); - } - - self.trap(Trap::InvalidArmOpcode(self.registers[0xF] - 0x8, opcode)); - - self.arm_continue(); - } -} diff --git a/src/luma/device/decode_thumb.rs b/src/luma/device/decode_thumb.rs deleted file mode 100644 index c6becb6..0000000 --- a/src/luma/device/decode_thumb.rs +++ /dev/null @@ -1,223 +0,0 @@ -/* - Copyright 2021-2023 Gabriel Jensen. - - This file is part of Luma. - - Luma is free software: you can redistribute it - and/or modify it under the terms of the GNU - Affero General Public License as published by - the Free Software Foundation, either version 3 - of the License, or (at your option) any later - version. - - Luma is distributed in the hope that it will be - useful, but WITHOUT ANY WARRANTY; without even - the implied warranty of MERCHANTABILITY or - FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Affero General Public License for more details. - - You should have received a copy of the GNU - Affero General Public License along with Luma. - If not, see <https://www.gnu.org/licenses/>. -*/ - -use crate::luma::device::{Device, Trap}; - -impl Device { - pub fn decode_thumb(&mut self) { - debug_assert!(self.thumb()); - - let (address, _) = self.registers[0xF].overflowing_sub(0x4); - - let opcode = self.read_halfword(address); - if cfg!(debug_assertions) { eprintln!("{opcode:#018b} @ {address:#010X}") } - - // b - if opcode & 0b1111100000000000 == 0b1110000000000000 { - let immediate = opcode & 0b0000011111111111; - - let offset = (((immediate as u32) << 0x15) as i32) >> 0x14; - - return self.thumb_branch(offset); - } - - // b{cond}, svc Immediate8 - if opcode & 0b1111000000000000 == 0b1101000000000000 { - let condition = ((opcode & 0b0000111100000000) >> 0x8) as u8; - - if condition == 0xF { - let immediate = (opcode & 0b0000000011111111) as u8; - - return self.interrupt(immediate as u32); - } - - if !self.check_condition(condition) { return self.thumb_continue() } - - let immediate = opcode & 0b0000000011111111; - - let offset = (((immediate as u32) << 0x18) as i32) >> 0x17; - - return self.thumb_branch(offset); - } - - // bl Offset11 - if opcode & 0b1111100000000000 == 0b1111000000000000 { - let immediate = opcode & 0b0000011111111111; - - let offset = (((immediate as u32) << 0x15) as i32) >> 0x9; - - self.thumb_branch_link0(offset); - return self.thumb_continue(); - } - - // bl Offset11 - if opcode & 0b1111100000000000 == 0b1111100000000000 { - let immediate = opcode & 0b0000011111111111; - - let offset = ((immediate as u32) << 0x1) as i32; - - return self.thumb_branch_link1(offset); - } - - // bx Rm - if opcode & 0b1111111110000111 == 0b0100011100000000 { - let register = ((opcode & 0b0000000001111000) >> 0x3) as u8; - - return self.thumb_branch_exchange(register); - } - - // ldr Rd, [Rn, Immediate5] - if opcode & 0b1111100000000000 == 0b0110100000000000 { - let destination = (opcode & 0b0000000000000111) as u8; - - let base = ((opcode & 0b0000000000111000) >> 0x3) as u8; - - let immediate = ((opcode & 0b0000011111000000) >> 0x5) as u8; - - self.thumb_load_immediate(destination, base, immediate); - return self.thumb_continue(); - } - - // ldr Rd, [Rn, Rm] - if opcode & 0b1111111000000000 == 0b0101100000000000 { - let destination = (opcode & 0b0000000000000111) as u8; - - let base = ((opcode & 0b0000000000111000) >> 0x3) as u8; - - let offset = ((opcode & 0b0000000111000000) >> 0x5) as u8; - - self.thumb_load_register(destination, base, offset); - return self.thumb_continue(); - } - - // ldr Rd, [r13, Immediate8] - if opcode & 0b1111100000000000 == 0b1001100000000000 { - let destination = ((opcode & 0b0000011100000000) >> 0x8) as u8; - - let immediate = (opcode & 0b0000000011111111) as u8; - - self.thumb_load_sp(destination, immediate); - return self.thumb_continue(); - } - - // ldr Rd, [r15, Immediate8] - if opcode & 0b1111100000000000 == 0b0100100000000000 { - let destination = ((opcode & 0b0000011100000000) >> 0x8) as u8; - - let immediate = (opcode & 0b0000000011111111) as u8; - - self.thumb_load_pc(destination, immediate); - return self.thumb_continue(); - } - - // lsl Rd, Rm, Immediate5 - if opcode & 0b1111100000000000 == 0b0000000000000000 { - let destination = (opcode & 0b0000000000000111) as u8; - - let source = ((opcode & 0b0000000000111000) >> 0x3) as u8; - - let immediate = ((opcode & 0b0000011111000000) >> 0x6) as u8; - - self.thumb_shift_left(destination, source, immediate); - return self.thumb_continue(); - } - - // lsr Rd, Rm, Immediate5 - if opcode & 0b1111100000000000 == 0b0000100000000000 { - let destination = (opcode & 0b0000000000000111) as u8; - - let source = ((opcode & 0b0000000000111000) >> 0x3) as u8; - - let immediate = ((opcode & 0b0000011111000000) >> 0x6) as u8; - - self.thumb_shift_right(destination, source, immediate); - return self.thumb_continue(); - } - - // mov Rd, Rm - if opcode & 0b1111111100000000 == 0b0100011000000000 { - let destination = ((opcode & 0b0000000000000111) | (opcode & 0b0000000010000000) >> 0x4) as u8; - - let source = ((opcode & 0b0000000001111000) >> 0x3) as u8; - - self.thumb_move_high(destination, source); - return self.thumb_continue(); - } - - // movs Rd, Immediate8 - if opcode & 0b1111100000000000 == 0b0010000000000000 { - let destination = ((opcode & 0b0000011100000000) >> 0x8) as u8; - - let immediate = (opcode & 0b0000000011111111) as u8; - - self.thumb_move_immediate(destination, immediate); - return self.thumb_continue(); - } - - // movs Rd, Rn - if opcode & 0b1111111111000000 == 0b0001110000000000 { - let destination = ((opcode & 0b0000000000000111) >> 0x3) as u8; - - let source = ((opcode & 0b0000000000111000) >> 0x3) as u8; - - self.thumb_move(destination, source); - return self.thumb_continue(); - } - - // pop Registers - if opcode & 0b1111111000000000 == 0b1011110000000000 { - let list = (opcode & 0b0000000011111111) as u8; - - let r = opcode & 0b0000000100000000 != 0x0; - - self.thumb_pop(list, r); - if !r { return self.thumb_continue() } - } - - // push Registers - if opcode & 0b1111111000000000 == 0b1011010000000000 { - let list = (opcode & 0b0000000011111111) as u8; - - let r = opcode & 0b0000000100000000 != 0x0; - - self.thumb_push(list, r); - return self.thumb_continue(); - } - - // strh Rd, [Rn, Immediate5] - if opcode & 0b1111100000000000 == 0b1000000000000000 { - let source = (opcode & 0b0000000000000111) as u8; - - let base = ((opcode & 0b0000000000111000) >> 0x3) as u8; - - let immediate = ((opcode & 0b0000011111000000) >> 0x6) as u8; - - self.thumb_store_halfword_immediate(source, base, immediate); - return self.thumb_continue(); - } - - self.trap(Trap::InvalidThumbOpcode(address, opcode)); - - self.thumb_continue(); - } -} diff --git a/src/luma/device/image.rs b/src/luma/device/image.rs deleted file mode 100644 index 2bb2230..0000000 --- a/src/luma/device/image.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 <https://www.gnu.org/licenses/>. -*/ - -use crate::luma::device::Device; -use crate::luma::IMAGE_SIZE; - -use std::slice; - -impl Device { - pub fn image<'a>(&mut self) -> &'a mut [u8] { - return unsafe { slice::from_raw_parts_mut(self.memory.offset(0x08000000), IMAGE_SIZE) }; - } -}
\ No newline at end of file diff --git a/src/luma/device/interrupt.rs b/src/luma/device/interrupt.rs deleted file mode 100644 index 61fd7bf..0000000 --- a/src/luma/device/interrupt.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 <https://www.gnu.org/licenses/>. -*/ - -use crate::luma::device::{Device, Log}; - -impl Device { - pub fn interrupt(&mut self, immediate: u32) { - self.log(Log::Interrupt, format!("{immediate:#010X}")); - - self.spsr[0b0011] = self.cpsr; - self.log(Log::Interrupt, format!("spsr_svc => cpsr ({:#034b})", self.spsr[0b0011])); - - // Enter svc mode. - // Enter ARM state. - // Disable IRQ exceptions. - self.cpsr = self.cpsr & 0b11111111111111111111111101000000 | 0b00000000000000000000000010010011; - self.log(Log::Interrupt, format!("cpsr => {:#034b}", self.cpsr)); - - self.exchange(false); - - self.registers[0xF] = 0x00000008; - self.log(Log::Interrupt, format!("pc => {:#010X}", self.registers[0xF])); - } -} diff --git a/src/luma/device/link.rs b/src/luma/device/link.rs deleted file mode 100644 index 24975d4..0000000 --- a/src/luma/device/link.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 <https://www.gnu.org/licenses/>. -*/ - -use crate::luma::device::{Device, Log}; - -impl Device { - pub fn arm_link(&mut self) { - // Store the address of the following instruction - // in the link register. - - (self.registers[0xE], _) = self.registers[0xF].overflowing_sub(0x4); - - self.log(Log::Link, format!("lr => pc-4 ({:#010X})", self.registers[0xE])); - } - - pub fn thumb_link(&mut self) { - // Store the address of the following instruction - // in the link register. - - (self.registers[0xE], _) = self.registers[0xF].overflowing_sub(0x2); - - self.log(Log::Link, format!("lr => pc ({:#010X})", self.registers[0xE])); - } -} diff --git a/src/luma/device/load.rs b/src/luma/device/load.rs deleted file mode 100644 index 7769300..0000000 --- a/src/luma/device/load.rs +++ /dev/null @@ -1,87 +0,0 @@ -/* - Copyright 2021-2023 Gabriel Jensen. - - This file is part of Luma. - - Luma is free software: you can redistribute it - and/or modify it under the terms of the GNU - Affero General Public License as published by - the Free Software Foundation, either version 3 - of the License, or (at your option) any later - version. - - Luma is distributed in the hope that it will be - useful, but WITHOUT ANY WARRANTY; without even - the implied warranty of MERCHANTABILITY or - FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Affero General Public License for more details. - - You should have received a copy of the GNU - Affero General Public License along with Luma. - If not, see <https://www.gnu.org/licenses/>. -*/ - -use crate::luma::device::{Device, Log}; - -impl Device { - pub fn thumb_load_immediate(&mut self, destination: u8, base: u8, immediate: u8) { - // Load word from memory using immediate offset. - - let base_value = self.registers[base as usize]; - - let offset = (immediate as u32) << 0x2; - - let address = base_value + offset; - - let value = self.read_word(address); - self.registers[destination as usize] = value; - - self.log(Log::Load, format!("r{destination} => r{base}{offset:+}={address:#010X} ({value:#010X})")); - } - - pub fn thumb_load_register(&mut self, destination: u8, base: u8, offset: u8) { - // Load word from memory using register offset. - - let base_value = self.registers[base as usize]; - let offset_value = self.registers[offset as usize] as i32; - - let (address, _) = base_value.overflowing_add_signed(offset_value); - - let value = self.read_word(address); - self.registers[destination as usize] = value; - - self.log(Log::Load, format!("r{destination} => r{base}+r{offset}={address:#010X} ({value:#010X})")); - } - - pub fn thumb_load_pc(&mut self, destination: u8, immediate: u8) { - // Load word from memory using offset relative to - // the program counter. - - let offset = (immediate as u32) << 0x2; - - let base = self.registers[0xF] & 0b11111111111111111111111111111100; - - let address = base + offset; - - let value = self.read_word(address); - self.registers[destination as usize] = value; - - self.log(Log::Load, format!("r{destination} => pc{offset:+}={address:#010X} ({value:#010X})")); - } - - pub fn thumb_load_sp(&mut self, destination: u8, immediate: u8) { - // Load word from memory using offset relative to - // the stack pointer. - - let offset = (immediate as u32) << 0x2; - - let base = self.registers[0xD]; - - let address = base + offset; - - let value = self.read_word(address); - self.registers[destination as usize] = value; - - self.log(Log::Load, format!("r{destination} => sp{offset:+}={address:#010X} ({value:#010X})")); - } -} diff --git a/src/luma/device/log.rs b/src/luma/device/log.rs deleted file mode 100644 index bc7369c..0000000 --- a/src/luma/device/log.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 <https://www.gnu.org/licenses/>. -*/ - -use crate::luma::device::{Device, Log}; - -impl Device { - pub fn log(&mut self, kind: Log, message: String) { - if cfg!(debug_assertions) { // This optimises the function away. - let keyword = match kind { - Log::Branch => "branch ", - Log::Continue => "continue ", - Log::Exchange => "exchange ", - Log::Interrupt => "interrupt", - Log::Link => "link ", - Log::Load => "load ", - Log::Move => "move ", - Log::Pop => "pop ", - Log::Push => "push ", - Log::Shift => "shift ", - Log::Store => "store ", - }; - - eprintln!("{keyword} : {message}"); - } - } -} diff --git a/src/luma/device/memory.rs b/src/luma/device/memory.rs deleted file mode 100644 index 0398a41..0000000 --- a/src/luma/device/memory.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 <https://www.gnu.org/licenses/>. -*/ - -use crate::luma::MEMORY_SIZE; -use crate::luma::device::Device; - -use std::slice; - -impl Device { - #[allow(dead_code)] - pub fn memory<'a>(&mut self) -> &'a mut [u8] { - return unsafe { slice::from_raw_parts_mut(self.memory.offset(0x00000000), MEMORY_SIZE) }; - } -}
\ No newline at end of file diff --git a/src/luma/device/move.rs b/src/luma/device/move.rs deleted file mode 100644 index 2952266..0000000 --- a/src/luma/device/move.rs +++ /dev/null @@ -1,83 +0,0 @@ -/* - Copyright 2021-2023 Gabriel Jensen. - - This file is part of Luma. - - Luma is free software: you can redistribute it - and/or modify it under the terms of the GNU - Affero General Public License as published by - the Free Software Foundation, either version 3 - of the License, or (at your option) any later - version. - - Luma is distributed in the hope that it will be - useful, but WITHOUT ANY WARRANTY; without even - the implied warranty of MERCHANTABILITY or - FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Affero General Public License for more details. - - You should have received a copy of the GNU - Affero General Public License along with Luma. - If not, see <https://www.gnu.org/licenses/>. -*/ - -use crate::luma::device::{Device, Log, Move}; - -impl Device { - pub fn arm_move(&mut self, destination: u8, kind: Move, s: bool) { - let value = match kind { - Move::Immediate(immediate) => immediate as u32, - Move::Register( source) => self.registers[source as usize], - }; - - self.registers[destination as usize] = value; - - if s { // Check the s flag. - if destination == 0xF { - self.cpsr = self.spsr[(self.cpsr & 0b00000000000000000000000000001111) as usize]; // We ignore the fifth bit, as this is always set. - } else { - // TO-DO - todo!(); - } - } - - self.log(Log::Move, match kind { - Move::Immediate(..) => format!("r{destination} => {value:#04X}"), - Move::Register( source) => format!("r{destination} => r{source} ({value:#010X})"), - }); - } - - pub fn thumb_move(&mut self, destination: u8, source: u8) { - // Move between high and low registers. Condition - // flags are set. - - let value = self.registers[source as usize]; - self.registers[destination as usize] = value; - - // TO-DO: Conditions. - - self.log(Log::Move, format!("r{destination} => r{source} ({value:#010X})")); - } - - pub fn thumb_move_high(&mut self, destination: u8, source: u8) { - // Move between registers. One or both registers - // are a high register. Condition flags are not - // set. - - let value = self.registers[source as usize]; - self.registers[destination as usize] = value; - - self.log(Log::Move, format!("r{destination} => r{source} ({value:#010X})")); - } - - pub fn thumb_move_immediate(&mut self, destination: u8, immediate: u8) { - // Move immediate to low register. Condition flags - // are set. - - self.registers[destination as usize] = immediate as u32; - - // TO-DO: Conditions. - - self.log(Log::Move, format!("r{destination} => {immediate:#04X}")); - } -}
\ No newline at end of file diff --git a/src/luma/device/new.rs b/src/luma/device/new.rs deleted file mode 100644 index d11e0f8..0000000 --- a/src/luma/device/new.rs +++ /dev/null @@ -1,67 +0,0 @@ -/* - Copyright 2021-2023 Gabriel Jensen. - - This file is part of Luma. - - Luma is free software: you can redistribute it - and/or modify it under the terms of the GNU - Affero General Public License as published by - the Free Software Foundation, either version 3 - of the License, or (at your option) any later - version. - - Luma is distributed in the hope that it will be - useful, but WITHOUT ANY WARRANTY; without even - the implied warranty of MERCHANTABILITY or - FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Affero General Public License for more details. - - You should have received a copy of the GNU - Affero General Public License along with Luma. - If not, see <https://www.gnu.org/licenses/>. -*/ - -use crate::luma::MEMORY_SIZE; -use crate::luma::device::Device; - -use std::alloc::{alloc_zeroed, Layout}; -use std::mem::size_of; - -impl Device { - pub fn new() -> Device { - eprintln!("creating new device"); - - let memory = unsafe { alloc_zeroed(Layout::new::<[u32; MEMORY_SIZE / size_of::<u32>()]>()) }; - if memory.is_null() { panic!("unable to allocate memory buffer") } - - eprintln!("allocated memory buffer at {:#0X}", memory as usize); - - let start = 0x08000008; - eprintln!("starting emulation at {start:#08X}"); - - return Device { - decode: Device::decode_arm, - memory: memory, - registers: [ - 0x00000000, - 0x00000000, - 0x00000000, - 0x00000000, - 0x00000000, - 0x00000000, - 0x00000000, - 0x00000000, - 0x00000000, - 0x00000000, - 0x00000000, - 0x00000000, - 0x00000000, - 0x00000000, - 0x03007F00, - start, - ], - cpsr: 0b00000000000000000000000000001111, - spsr: [0b00000000000000000000000000000000; 0x10], - }; - } -} diff --git a/src/luma/device/palette.rs b/src/luma/device/palette.rs deleted file mode 100644 index f7c17fb..0000000 --- a/src/luma/device/palette.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 <https://www.gnu.org/licenses/>. -*/ - -use crate::luma::PALETTE_SIZE; -use crate::luma::device::Device; - -use std::slice; - -impl Device { - #[allow(dead_code)] - pub fn palette<'a>(&mut self) -> &'a mut [u16] { - return unsafe { slice::from_raw_parts_mut(self.memory.offset(0x05000000) as *mut u16, PALETTE_SIZE) }; - } -}
\ No newline at end of file diff --git a/src/luma/device/pop.rs b/src/luma/device/pop.rs deleted file mode 100644 index cdf08ce..0000000 --- a/src/luma/device/pop.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 <https://www.gnu.org/licenses/>. -*/ - -use crate::luma::device::{Device, Log}; - -impl Device { - pub fn thumb_pop(&mut self, list: u8, r: bool) { - // Return true if branching. - - let ammount = list.count_ones() as u8 + r as u8; - - let mut address = self.registers[0xE]; - - for index in 0x0..0x7 { - let pop = (list >> index) & 0b00000001 != 0x0; - - if pop { - let value = self.read_word(address); - - self.registers[index as usize] = value; - self.log(Log::Pop, format!("r{index} => {address:#010X} ({value:#010X})")); - - address += 0x4; - } - } - - if r { - let value = self.read_word(address); - - // We ignore the T flag. - (self.registers[0xF], _) = (value & 0b11111111111111111111111111111110).overflowing_add(0x4); - self.log(Log::Pop, format!("pc => {address:#010X}+4 ({value:#010X})")); - } - - self.registers[0xE] = address - 0x4; - self.log(Log::Pop, format!("sp => sp{:+} ({:#010X})", ammount * 0x4, self.registers[0xE])); - } -} diff --git a/src/luma/device/push.rs b/src/luma/device/push.rs deleted file mode 100644 index 1c33302..0000000 --- a/src/luma/device/push.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 <https://www.gnu.org/licenses/>. -*/ - -use crate::luma::device::{Device, Log}; - -impl Device { - pub fn thumb_push(&mut self, list: u8, r: bool) { - let ammount = list.count_ones() as u8 + r as u8; - - let (start, _) = self.registers[0xE].overflowing_sub(ammount as u32 * 0x4); - - let mut address = start; - - for index in 0x0..0x7 { - let push = (list >> index) & 0b00000001 != 0x0; - - if push { - let value = self.registers[index as usize]; - - self.write_word(address, value); - self.log(Log::Push, format!("{address:#010X} => r{index} ({value:#010X})")); - - address += 0x4; - } - } - - if r { - let value = self.registers[0xD]; - - self.write_word(address, value); - self.log(Log::Push, format!("{address:#010X} => lr ({value:#010X})")); - } - - self.registers[0xE] = start; - - self.log(Log::Push, format!("sp => sp-{} ({start:#010X})", ammount * 0x4)); - } -} diff --git a/src/luma/device/read.rs b/src/luma/device/read.rs deleted file mode 100644 index f0ea176..0000000 --- a/src/luma/device/read.rs +++ /dev/null @@ -1,47 +0,0 @@ -/* - Copyright 2021-2023 Gabriel Jensen. - - This file is part of Luma. - - Luma is free software: you can redistribute it - and/or modify it under the terms of the GNU - Affero General Public License as published by - the Free Software Foundation, either version 3 - of the License, or (at your option) any later - version. - - Luma is distributed in the hope that it will be - useful, but WITHOUT ANY WARRANTY; without even - the implied warranty of MERCHANTABILITY or - FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Affero General Public License for more details. - - You should have received a copy of the GNU - Affero General Public License along with Luma. - If not, see <https://www.gnu.org/licenses/>. -*/ - -use crate::luma::device::{Device, Trap}; -use crate::luma::MEMORY_SIZE; - -impl Device { - pub fn read_byte(&mut self, address: u32) -> u8 { - if address >= MEMORY_SIZE as u32 { self.trap(Trap::OutOfBounds(address)) } - - return unsafe { *(self.memory.offset(address as isize) as *mut u8) }; - } - - pub fn read_halfword(&mut self, address: u32) -> u16 { - if address >= MEMORY_SIZE as u32 { self.trap(Trap::OutOfBounds(address)) } - if address % 0x2 != 0x0 { self.trap(Trap::BadAlignment(address, 0x2)) } - - return unsafe { *(self.memory.offset(address as isize) as *mut u16) }; - } - - pub fn read_word(&mut self, address: u32) -> u32 { - if address >= MEMORY_SIZE as u32 { self.trap(Trap::OutOfBounds(address)) } - if address % 0x4 != 0x0 { self.trap(Trap::BadAlignment(address, 0x4)) } - - return unsafe { *(self.memory.offset(address as isize) as *mut u32) }; - } -} diff --git a/src/luma/device/shift.rs b/src/luma/device/shift.rs deleted file mode 100644 index 5b19a92..0000000 --- a/src/luma/device/shift.rs +++ /dev/null @@ -1,50 +0,0 @@ -/* - Copyright 2021-2023 Gabriel Jensen. - - This file is part of Luma. - - Luma is free software: you can redistribute it - and/or modify it under the terms of the GNU - Affero General Public License as published by - the Free Software Foundation, either version 3 - of the License, or (at your option) any later - version. - - Luma is distributed in the hope that it will be - useful, but WITHOUT ANY WARRANTY; without even - the implied warranty of MERCHANTABILITY or - FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Affero General Public License for more details. - - You should have received a copy of the GNU - Affero General Public License along with Luma. - If not, see <https://www.gnu.org/licenses/>. -*/ - -use crate::luma::device::{Device, Log}; - -impl Device { - pub fn thumb_shift_left(&mut self, destination: u8, source: u8, immediate: u8) { - let source_value = self.registers[source as usize]; - - let (value, _) = source_value.overflowing_shl(immediate as u32); - - self.registers[destination as usize] = value; - - // TO-DO: Set condition flags. - - self.log(Log::Shift, format!("r{destination} => r{source} << {immediate} ({value:#010X})")); - } - - pub fn thumb_shift_right(&mut self, destination: u8, source: u8, immediate: u8) { - let source_value = self.registers[source as usize]; - - let (value, _) = source_value.overflowing_shr(immediate as u32); - - self.registers[destination as usize] = value; - - // TO-DO: Set condition flags. - - self.log(Log::Shift, format!("r{destination} => r{source} << {immediate} ({value:#010X})")); - } -} diff --git a/src/luma/device/store.rs b/src/luma/device/store.rs deleted file mode 100644 index 14bc154..0000000 --- a/src/luma/device/store.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 <https://www.gnu.org/licenses/>. -*/ - -use crate::luma::device::{Device, Log}; - -impl Device { - pub fn arm_store(&mut self, destination: u8, base: u8, immediate: u16, u: bool, _b: bool, l: bool) { - // TO-DO: Byte loads/stores. - - // The U flag determins the sign of the offset - // (set = unsigned). - let offset = if u { - 0x0 + immediate as i32 - } else { - 0x0 - immediate as i32 - }; - - let (address, _) = self.registers[base as usize].overflowing_add_signed(offset); - - if l { // Check the L flag. - // If the L flag is set, we perform a memory-to- - // destination load instead. - - let value = self.read_word(address); - self.registers[destination as usize] = value; - - self.log(Log::Load, format!("r{destination} => r{base}{offset:+}={address:#010X} ({value:#010X})")); - } else { - // Otherwise, we perform a destination-to-memory - // store. - - let value = self.registers[destination as usize]; - self.write_word(address, value); - - self.log(Log::Store, format!("r{base}{offset:+}={address:#010X} => r{destination} ({value:#010X})")); - } - } - - pub fn thumb_store_halfword_immediate(&mut self, source: u8, base: u8, immediate: u8) { - // Load halfword from memory using immediate offset. - - let base_value = self.registers[base as usize]; - - let offset = (immediate as u32) << 0x1; - - let (address, _) = base_value.overflowing_add(offset); - - let value = (self.registers[source as usize] & 0b00000000000000001111111111111111) as u16; - self.write_halfword(address, value); - - self.log(Log::Store, format!("r{source} => r{base}{immediate:+}={address:#010X} ({value:#06X})")); - } - -} diff --git a/src/luma/device/trap.rs b/src/luma/device/trap.rs deleted file mode 100644 index 9e5f003..0000000 --- a/src/luma/device/trap.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 <https://www.gnu.org/licenses/>. -*/ - -use crate::luma::MEMORY_SIZE; -use crate::luma::device::{Device, Trap}; - -impl Device { - pub fn trap(&mut self, kind: Trap) { - let message = match kind { - Trap::BadAlignment( address, alignment) => format!("bad alignment of address {address:#010X} (should be {alignment}-byte aligned)"), - Trap::InvalidArmOpcode( address, opcode) => format!("invalid opcode {opcode:#034b} at {address:#010X}"), - Trap::InvalidThumbOpcode(address, opcode) => format!("invalid opcode {opcode:#018b} at {address:#010X}"), - Trap::OutOfBounds( address) => format!("out-of-bounds address {address:#010X} (limit is {MEMORY_SIZE:#010X})"), - }; - - eprintln!("{message}"); - eprintln!(" r0: {:#010X}", self.registers[0x0]); - eprintln!(" r1: {:#010X}", self.registers[0x1]); - eprintln!(" r2: {:#010X}", self.registers[0x2]); - eprintln!(" r3: {:#010X}", self.registers[0x3]); - eprintln!(" r4: {:#010X}", self.registers[0x4]); - eprintln!(" r5: {:#010X}", self.registers[0x5]); - eprintln!(" r6: {:#010X}", self.registers[0x6]); - eprintln!(" r7: {:#010X}", self.registers[0x7]); - eprintln!(" r8: {:#010X}", self.registers[0x8]); - eprintln!(" r9: {:#010X}", self.registers[0x9]); - eprintln!(" r10: {:#010X}", self.registers[0xA]); - eprintln!(" r11: {:#010X}", self.registers[0xB]); - eprintln!(" r12: {:#010X}", self.registers[0xC]); - eprintln!(" r13: {:#010X}", self.registers[0xD]); - eprintln!(" r14: {:#010X}", self.registers[0xE]); - eprintln!(" r15: {:#010X}", self.registers[0xF]); - eprintln!(" cpsr: {:#034b}", self.cpsr); - eprintln!(" spsr_fiq: {:#034b}", self.spsr[0x1]); - eprintln!(" spsr_irq: {:#034b}", self.spsr[0x2]); - eprintln!(" spsr_svc: {:#034b}", self.spsr[0x3]); - eprintln!(" spsr_abt: {:#034b}", self.spsr[0x7]); - eprintln!(" spsr_und: {:#034b}", self.spsr[0xB]); - - match kind { - Trap::BadAlignment(..) => panic!("bad alignment of address"), - Trap::OutOfBounds( ..) => panic!("out-of-bounds address"), - _ => {}, - } - } -} diff --git a/src/luma/device/video.rs b/src/luma/device/video.rs deleted file mode 100644 index 9c97806..0000000 --- a/src/luma/device/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 <https://www.gnu.org/licenses/>. -*/ - -use crate::luma::VIDEO_SIZE; -use crate::luma::device::Device; - -use std::slice; - -impl Device { - #[allow(dead_code)] - pub fn video<'a>(&mut self) -> &'a mut [u8] { - return unsafe { slice::from_raw_parts_mut(self.memory.offset(0x06000000), VIDEO_SIZE) }; - } -}
\ No newline at end of file diff --git a/src/luma/device/write.rs b/src/luma/device/write.rs deleted file mode 100644 index 05a8901..0000000 --- a/src/luma/device/write.rs +++ /dev/null @@ -1,47 +0,0 @@ -/* - Copyright 2021-2023 Gabriel Jensen. - - This file is part of Luma. - - Luma is free software: you can redistribute it - and/or modify it under the terms of the GNU - Affero General Public License as published by - the Free Software Foundation, either version 3 - of the License, or (at your option) any later - version. - - Luma is distributed in the hope that it will be - useful, but WITHOUT ANY WARRANTY; without even - the implied warranty of MERCHANTABILITY or - FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Affero General Public License for more details. - - You should have received a copy of the GNU - Affero General Public License along with Luma. - If not, see <https://www.gnu.org/licenses/>. -*/ - -use crate::luma::device::{Device, Trap}; -use crate::luma::MEMORY_SIZE; - -impl Device { - pub fn write_byte(&mut self, address: u32, value: u8) { - if address >= MEMORY_SIZE as u32 { self.trap(Trap::OutOfBounds(address)) } - - return unsafe { *(self.memory.offset(address as isize) as *mut u8) = value }; - } - - pub fn write_halfword(&mut self, address: u32, value: u16) { - if address >= MEMORY_SIZE as u32 { self.trap(Trap::OutOfBounds(address)) } - if address % 0x2 != 0x0 { self.trap(Trap::BadAlignment(address, 0x2)) } - - return unsafe { *(self.memory.offset(address as isize) as *mut u16) = value }; - } - - pub fn write_word(&mut self, address: u32, value: u32) { - if address >= MEMORY_SIZE as u32 { self.trap(Trap::OutOfBounds(address)) } - if address % 0x4 != 0x0 { self.trap(Trap::BadAlignment(address, 0x4)) } - - return unsafe { *(self.memory.offset(address as isize) as *mut u32) = value }; - } -} diff --git a/src/luma/instruction.rs b/src/luma/instruction.rs new file mode 100644 index 0000000..96aa288 --- /dev/null +++ b/src/luma/instruction.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 <https://www.gnu.org/licenses/>. +*/ + +#[derive(Clone, Copy)] +pub enum Instruction { + AddImmediate( u8, u8, u32), + AddRegister( u8, u8, u8), + Branch( i32), + BranchExchange( u8), + BranchLink( i32), + BranchLinkPrefix( i32), + BranchLinkSuffix( i32), + CompareImmediate( u8, u32), + CompareRegister( u8, u8), + LoadHalfword( u8, u8, i16), + LoadImmediateOffset( u8, u8, i16), + LoadPc( u8, i16), + MoveImmediate( u8, u32), + MoveImmediateArithmeticShiftRight( u8, u8, u8), + MoveImmediateLogicalShiftLeftImmediate( u8, u8, u8), + MoveImmediateLogicalShiftRightImmediate(u8, u8, u8), + MoveRegister( u8, u8), + MoveRegisterArithmeticShiftRight( u8, u8, u8), + MoveRegisterLogicalShiftLeftImmediate( u8, u8, u8), + MoveRegisterLogicalShiftRightImmediate( u8, u8, u8), + StoreByteImmediateOffset( u8, u8, i16), + StoreByteRegisterOffset( u8, u8, u8), + StoreHalfword( u8, u8, i16), + StoreImmediateOffset( u8, u8, i16), + SubtractImmediate( u8, u8, u32), + SubtractRegister( u8, u8, u8), + + Undefined, +} diff --git a/src/luma/state.rs b/src/luma/state.rs new file mode 100644 index 0000000..1836573 --- /dev/null +++ b/src/luma/state.rs @@ -0,0 +1,184 @@ +/* + Copyright 2021-2023 Gabriel Jensen. + + This file is part of Luma. + + Luma is free software: you can redistribute it + and/or modify it under the terms of the GNU + Affero General Public License as published by + the Free Software Foundation, either version 3 + of the License, or (at your option) any later + version. + + Luma is distributed in the hope that it will be + useful, but WITHOUT ANY WARRANTY; without even + the implied warranty of MERCHANTABILITY or + FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Affero General Public License for more details. + + You should have received a copy of the GNU + Affero General Public License along with Luma. + If not, see <https://www.gnu.org/licenses/>. +*/ + +use crate::luma::{Error, log}; +use crate::luma::{BOOTLOADER_LENGTH, IMAGE_LENGTH, MEMORY_LENGTH, PALETTE_LENGTH, VIDEO_LENGTH}; + +use std::slice::{from_raw_parts, from_raw_parts_mut}; + +pub mod new; + +pub struct State { + registers: [u32; 0x10], + cpsr: u32, + + memory: Vec::<u32>, +} + +impl State { + #[inline(always)] + #[must_use] + pub fn read_register(&self, register: u8) -> u32 { + // Limit to 0..=15. + let index = (register & 0b00001111) as usize; + + return unsafe { *self.registers.get_unchecked(index) }; + } + + #[inline(always)] + pub fn write_register(&mut self, register: u8, value: u32) { + log(&format!("* r{register} = {value:#010X}")); + + let index = (register & 0b00001111) as usize; + + unsafe { *self.registers.get_unchecked_mut(index) = value }; + } + + #[must_use] + pub fn read_word(&self, address: u32) -> u32 { + if address > MEMORY_LENGTH - 0x4 { Error::OutOfBounds( address).trap(); return 0x00000000; } + if address % 0x4 != 0x0 { Error::BadAlignment(address, 0x4).trap(); return 0x00000000; } + + unsafe { + let pointer = (self.memory.as_ptr() as *const u8).add(address as usize) as *const u32; + return *pointer; + } + } + + pub fn write_word(&mut self, address: u32, value: u32) { + log(&format!("* {address:#010X} = {value:#010X}")); + + if address > MEMORY_LENGTH - 0x4 { Error::OutOfBounds( address).trap(); return; } + if address % 0x4 != 0x0 { Error::BadAlignment(address, 0x4).trap(); return; } + + unsafe { + let pointer = (self.memory.as_mut_ptr() as *mut u8).add(address as usize) as *mut u32; + *pointer = value; + } + } + + #[must_use] + pub fn read_halfword(&self, address: u32) -> u16 { + if address > MEMORY_LENGTH - 0x2 { Error::OutOfBounds( address).trap(); return 0x0000; } + if address % 0x2 != 0x0 { Error::BadAlignment(address, 0x2).trap(); return 0x0000; } + + unsafe { + let pointer = (self.memory.as_ptr() as *const u8).add(address as usize) as *const u16; + return *pointer; + } + } + + pub fn write_halfword(&mut self, address: u32, value: u16) { + log(&format!("* {address:#010X} = {value:#010X}")); + + if address > MEMORY_LENGTH - 0x2 { Error::OutOfBounds( address).trap(); return; } + if address % 0x2 != 0x0 { Error::BadAlignment(address, 0x2).trap(); return; } + + unsafe { + let pointer = (self.memory.as_mut_ptr() as *mut u8).add(address as usize) as *mut u16; + *pointer = value; + } + } + + #[must_use] + pub fn read_byte(&self, address: u32) -> u8 { + if address > MEMORY_LENGTH - 0x1 { Error::OutOfBounds(address).trap(); return 0x00; } + + unsafe { + let pointer = (self.memory.as_ptr() as *const u8).add(address as usize); + return *pointer; + } + } + + pub fn write_byte(&mut self, address: u32, value: u8) { + log(&format!("* {address:#010X} = {value:#010X}")); + + if address > MEMORY_LENGTH - 0x1 { Error::OutOfBounds(address).trap(); return; } + + unsafe { + let pointer = (self.memory.as_mut_ptr() as *mut u8).add(address as usize); + *pointer = value; + } + } + + #[inline(always)] + #[must_use] + pub fn read_cpsr(&self) -> u32 { + return self.cpsr; + } + + #[inline(always)] + pub fn write_cpsr(&mut self, value: u32) { + log(&format!("* cpsr = {value:#034b}")); + + self.cpsr = value; + } + + #[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 new file mode 100644 index 0000000..39caaf6 --- /dev/null +++ b/src/luma/state/new.rs @@ -0,0 +1,60 @@ +/* + 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 <https://www.gnu.org/licenses/>. +*/ + +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::<u32> = vec![0x0; MEMORY_LENGTH as usize / 0x4]; + + return Self { + registers: registers, + cpsr: cpsr, + + memory: memory, + }; + } +} diff --git a/src/main.rs b/src/main.rs index 564e193..4478a81 100644 --- a/src/main.rs +++ b/src/main.rs @@ -21,14 +21,54 @@ If not, see <https://www.gnu.org/licenses/>. */ +extern crate ctrlc; +extern crate sdl2; +extern crate toml; + mod luma; -use crate::luma::application::Application; +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() { - let configuration = Configuration::new(); + 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 mut application = Application::initialise(&configuration); - application.run(); + let path = home + "/.luma.toml"; + return path; } @@ -0,0 +1,20 @@ +OUTPUT_ARCH(arm); + +MEMORY { + bios : ORIGIN = 0x00000000, LENGTH = 0x00004000 + ewram : ORIGIN = 0x02000000, LENGTH = 0x00040000 + iwram : ORIGIN = 0x03000000, LENGTH = 0x00008000 + reg : ORIGIN = 0x04000000, LENGTH = 0x000003FF + pal : ORIGIN = 0x05000000, LENGTH = 0x00000400 + vram : ORIGIN = 0x06000000, LENGTH = 0x00018000 + oam : ORIGIN = 0x07000000, LENGTH = 0x00000400 + rom : ORIGIN = 0x08000000, LENGTH = 0x02000000 + sram : ORIGIN = 0x0E000000, LENGTH = 0x00010000 +}; + +SECTIONS { + .bss : {*(.bss*)} > ewram + .data : {*(.data*)} > ewram + .text : {*(.text*)} > rom + .rodata : {*(.rodata*)} > rom +}; @@ -0,0 +1,159 @@ +.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 + +.ascii "LUMA\x0\x0\x0\x0\x0\x0\x0\x0" + +.ascii "AMUL" + +.ascii "00" + +.byte 0x96 + +.byte 0x0 + +.byte 0x0 + +.fill 0x7,0x1,0x0 + +.byte 0x45 + +.byte 0x0 + +.fill 0x2,0x1,0x0 + +.arm +nop + +.byte 0x0 + +.byte 0x0 + +.fill 0x1A,0x1,0x0 + +.arm +nop + +.arm +.func +_start: + ldr lr, =start + bx lr +.endfunc + +.thumb +.func +.thumb_func +start: + @ Set up video mode: + ldr r0, .ioAddr + @ Already at DISPCNT. + ldr r1, .displayControl + strh r1, [r0] + + @ Set up palette: + ldr r0, .paletteAddr + ldr r1, .backgroundColour + strh r1, [r0] + ldr r1, .paletteIndex + lsls r1, 0x1 @ Multiply by two. + adds r0, r1 @ Apply index. + ldr r1, .foregroundColour + strh r1, [r0] + + @ Set up loop: + @ - r0 is the current pixel address. + @ - r1 is the palette value/index. + @ - r2 is the addend. + @ - r3 is the last pixel address. + ldr r0, .videoAddr + ldr r1, .paletteIndex + movs r2, 0x0 + movs r3, 0x4B + lsls r3, 0x9 + adds r3, r0 + + @ Plot pixels: +.loop: + strb r1, [r0] + adds r0, r2 + adds r2, 0x1 + cmp r0, r3 + bge .stop @ Stop if we've reached the end. + b .loop @ Repeat loop. + + @ Stop: +.stop: + b .stop +.endfunc + +.align +.ioAddr: + .word 0x04000000 + +.align +.displayControl: + .word 0x0404 + +.align +.paletteAddr: + .word 0x05000000 + +.align +.paletteIndex: + .word 0xFF + +.align +.backgroundColour: + .word 0b0000100001000010 + +.align +.foregroundColour: + .word 0b0001001010011110 + +.align +.videoAddr: + .word 0x06000000 |