summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore3
-rw-r--r--CHANGELOG.md (renamed from CHANGELOG.txt)16
-rw-r--r--Cargo.toml10
-rw-r--r--README.md45
-rw-r--r--README.txt85
-rwxr-xr-xmake_test.sh14
-rw-r--r--src/luma.rs72
-rw-r--r--src/luma/agb.rs (renamed from src/luma/application/drop.rs)9
-rw-r--r--src/luma/app.rs (renamed from src/luma/device/exchange.rs)30
-rw-r--r--src/luma/app/check_events.rs (renamed from src/luma/device/continue.rs)34
-rw-r--r--src/luma/app/draw_video.rs62
-rw-r--r--src/luma/app/init.rs88
-rw-r--r--src/luma/app/load.rs96
-rw-r--r--src/luma/app/run.rs60
-rw-r--r--src/luma/app/sync_video.rs (renamed from src/luma/configuration/overwrite.rs)25
-rw-r--r--src/luma/application/initialise.rs68
-rw-r--r--src/luma/application/load.rs47
-rw-r--r--src/luma/application/run.rs83
-rw-r--r--src/luma/configuration.rs13
-rw-r--r--src/luma/configuration/create.rs52
-rw-r--r--src/luma/configuration/load.rs121
-rw-r--r--src/luma/configuration/validate.rs (renamed from src/luma/configuration/path.rs)11
-rw-r--r--src/luma/cpu.rs79
-rw-r--r--src/luma/cpu/add.rs50
-rw-r--r--src/luma/cpu/boot.rs108
-rw-r--r--src/luma/cpu/branch.rs89
-rw-r--r--src/luma/cpu/compare.rs70
-rw-r--r--src/luma/cpu/continue.rs (renamed from src/luma/device/thumb.rs)11
-rw-r--r--src/luma/cpu/decode_arm.rs256
-rw-r--r--src/luma/cpu/decode_thumb.rs311
-rw-r--r--src/luma/cpu/exchange.rs39
-rw-r--r--src/luma/cpu/load.rs69
-rw-r--r--src/luma/cpu/move.rs113
-rw-r--r--src/luma/cpu/store.rs80
-rw-r--r--src/luma/cpu/subtract.rs50
-rw-r--r--src/luma/cpu/test_predicate.rs74
-rw-r--r--src/luma/cpu_handle.rs (renamed from src/luma/application.rs)46
-rw-r--r--src/luma/cpu_handle/dump_palette.rs (renamed from src/luma/configuration/new.rs)19
-rw-r--r--src/luma/cpu_handle/dump_video.rs (renamed from src/luma/device/drop.rs)14
-rw-r--r--src/luma/cpu_handle/kill.rs (renamed from src/luma/device/bootloader.rs)19
-rw-r--r--src/luma/device.rs84
-rw-r--r--src/luma/device/branch.rs109
-rw-r--r--src/luma/device/check_condition.rs66
-rw-r--r--src/luma/device/decode_arm.rs107
-rw-r--r--src/luma/device/decode_thumb.rs223
-rw-r--r--src/luma/device/image.rs33
-rw-r--r--src/luma/device/interrupt.rs44
-rw-r--r--src/luma/device/link.rs44
-rw-r--r--src/luma/device/load.rs87
-rw-r--r--src/luma/device/log.rs46
-rw-r--r--src/luma/device/memory.rs34
-rw-r--r--src/luma/device/move.rs83
-rw-r--r--src/luma/device/new.rs67
-rw-r--r--src/luma/device/palette.rs34
-rw-r--r--src/luma/device/pop.rs58
-rw-r--r--src/luma/device/push.rs58
-rw-r--r--src/luma/device/read.rs47
-rw-r--r--src/luma/device/shift.rs50
-rw-r--r--src/luma/device/store.rs74
-rw-r--r--src/luma/device/trap.rs66
-rw-r--r--src/luma/device/video.rs34
-rw-r--r--src/luma/device/write.rs47
-rw-r--r--src/luma/instruction.rs54
-rw-r--r--src/luma/state.rs184
-rw-r--r--src/luma/state/new.rs60
-rw-r--r--src/main.rs48
-rw-r--r--test.ld20
-rw-r--r--test.s159
68 files changed, 2536 insertions, 2025 deletions
diff --git a/.gitignore b/.gitignore
index 1369b23..61bf874 100644
--- a/.gitignore
+++ b/.gitignore
@@ -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;
diff --git a/Cargo.toml b/Cargo.toml
index d763c67..5ae95fa 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -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;
}
diff --git a/test.ld b/test.ld
new file mode 100644
index 0000000..a999c7d
--- /dev/null
+++ b/test.ld
@@ -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
+};
diff --git a/test.s b/test.s
new file mode 100644
index 0000000..098647e
--- /dev/null
+++ b/test.s
@@ -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