From fe62f96006bfb918354ce741735f1cded1f51f36 Mon Sep 17 00:00:00 2001 From: baltdev Date: Fri, 5 Apr 2024 15:57:58 -0500 Subject: [PATCH] Back up to VCS --- .gitignore | 1 + .idea/.gitignore | 8 + .idea/apple-one.iml | 11 ++ .idea/modules.xml | 8 + .idea/vcs.xml | 6 + Cargo.lock | 271 ++++++++++++++++++++++++++++++ Cargo.toml | 10 ++ src/basic.rom | Bin 0 -> 4098 bytes src/constants.rs | 8 + src/main.rs | 391 ++++++++++++++++++++++++++++++++++++++++++++ src/wozmon.rom | Bin 0 -> 256 bytes 11 files changed, 714 insertions(+) create mode 100644 .gitignore create mode 100644 .idea/.gitignore create mode 100644 .idea/apple-one.iml create mode 100644 .idea/modules.xml create mode 100644 .idea/vcs.xml create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 src/basic.rom create mode 100644 src/constants.rs create mode 100644 src/main.rs create mode 100644 src/wozmon.rom diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/target diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..13566b8 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,8 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Editor-based HTTP Client requests +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/.idea/apple-one.iml b/.idea/apple-one.iml new file mode 100644 index 0000000..cf84ae4 --- /dev/null +++ b/.idea/apple-one.iml @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..4123969 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..35eb1dd --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..b951e08 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,271 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "apple-one" +version = "0.1.0" +dependencies = [ + "crossterm", + "r6502", +] + +[[package]] +name = "autocfg" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1fdabc7756949593fe60f30ec81974b613357de856987752631dea1e3394c80" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "crossterm" +version = "0.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f476fe445d41c9e991fd07515a6f463074b782242ccf4a5b7b1d1012e70824df" +dependencies = [ + "bitflags 2.5.0", + "crossterm_winapi", + "libc", + "mio", + "parking_lot", + "signal-hook", + "signal-hook-mio", + "winapi", +] + +[[package]] +name = "crossterm_winapi" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acdd7c62a3665c7f6830a51635d9ac9b23ed385797f70a83bb8bafe9c572ab2b" +dependencies = [ + "winapi", +] + +[[package]] +name = "libc" +version = "0.2.153" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" + +[[package]] +name = "lock_api" +version = "0.4.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c168f8615b12bc01f9c17e2eb0cc07dcae1940121185446edc3744920e8ef45" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" + +[[package]] +name = "mio" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" +dependencies = [ + "libc", + "log", + "wasi", + "windows-sys", +] + +[[package]] +name = "parking_lot" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c42a9226546d68acdd9c0a280d17ce19bfe27a46bf68784e4066115788d008e" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-targets", +] + +[[package]] +name = "r6502" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "757565cafd699d777dee11fba136fb84cbe22817302426bef5901709dbf5e1f4" +dependencies = [ + "bitflags 2.5.0", +] + +[[package]] +name = "redox_syscall" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" +dependencies = [ + "bitflags 1.3.2", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "signal-hook" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8621587d4798caf8eb44879d42e56b9a93ea5dcd315a6487c357130095b62801" +dependencies = [ + "libc", + "signal-hook-registry", +] + +[[package]] +name = "signal-hook-mio" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29ad2e15f37ec9a6cc544097b78a1ec90001e9f71b81338ca39f430adaca99af" +dependencies = [ + "libc", + "mio", + "signal-hook", +] + +[[package]] +name = "signal-hook-registry" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1" +dependencies = [ + "libc", +] + +[[package]] +name = "smallvec" +version = "1.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..a751bd5 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "apple-one" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +r6502 = "1.1.1" +crossterm = "0.27" diff --git a/src/basic.rom b/src/basic.rom new file mode 100644 index 0000000000000000000000000000000000000000..2cc071f407bc930dfd8b1722557988d1be4fd27d GIT binary patch literal 4098 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 ExitCode { + let Err(err) = inner_main() else { + disable_raw_mode().expect("failed to disable raw mode"); + execute!(io::stdout(), LeaveAlternateScreen, EnableLineWrap) + .expect("failed to clear alternate screen"); + return ExitCode::SUCCESS; + }; + disable_raw_mode().expect("failed to disable raw mode"); + execute!(io::stdout(), LeaveAlternateScreen, EnableLineWrap) + .expect("failed to clear alternate screen"); + eprintln!("io error: {err}"); + ExitCode::FAILURE +} + +struct EmulatorState { + keyboard_ready: bool, + keyboard_register: u8, + display_x: u8, + display_y: u8, + display_reg: u8, + display_buffer: [u8; 40 * 24], + old_display_buffer: Option<[u8; 40 * 24]>, + paused: bool, + step: bool, + step_counter: u16, + last_display: Instant, +} + +static CHARACTER_SET: &[u8] = br##"@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_ !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_ !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_ !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_ !"#$%&'()*+,-./0123456789:;<=>?"##; + +fn inner_main() -> io::Result<()> { + execute!( + io::stdout(), + EnterAlternateScreen, + DisableLineWrap, + EnableBlinking, + Clear(ClearType::All) + )?; + + enable_raw_mode()?; + + let emu_state = Rc::new(RefCell::new(EmulatorState { + keyboard_ready: false, + keyboard_register: 0b1000_0000, + display_x: 0, + display_y: 0, + display_reg: 0b0000_0000, + display_buffer: [b' '; 40 * 24], + old_display_buffer: None, + paused: false, + step: false, + step_counter: 0, + last_display: Instant::now() + })); + + let mut emulator = Emulator::default() + .with_program_counter(0xFF00) + .with_rom_from(include_bytes!("basic.rom"), 0xE000) + .with_rom_from(include_bytes!("wozmon.rom"), 0xFF00) + .with_write_callback(FunctionWriteCallback( + |state: &mut State, addr, mut byte| { + if emu_state.borrow_mut().step { + let _ = execute!( + io::stdout(), + Print(format!("WRITE {addr:04X} {byte:02X}")), + MoveToNextLine(1) + ); + } + match addr { + 0..=0x7FFF => state.memory[addr as usize] = byte, + constants::variables::DSP => { + let mut emu_state = emu_state.borrow_mut(); + byte &= 0b0111_1111; + emu_state.display_reg = byte | 0b1000_0000; + emu_state.last_display = Instant::now(); + match byte { + 0x00 | 0x7F => {} + b'\n' | b'\r' => { + emu_state.old_display_buffer = Some(emu_state.display_buffer); + emu_state.display_x = 0; + emu_state.display_y += 1; + } + other => { + emu_state.old_display_buffer = Some(emu_state.display_buffer); + let index = (emu_state.display_y as usize) * 40 + + (emu_state.display_x as usize); + emu_state.display_buffer[index] = other; + emu_state.display_x += 1; + } + } + if emu_state.display_x == 40 { + if emu_state.old_display_buffer.is_none() { + emu_state.old_display_buffer = Some(emu_state.display_buffer); + } + emu_state.display_x = 0; + emu_state.display_y += 1; + } + if emu_state.display_y > 23 { + if emu_state.old_display_buffer.is_none() { + emu_state.old_display_buffer = Some(emu_state.display_buffer); + } + emu_state.display_y = 23; + // Copy the screen buffer up for a new line + emu_state.display_buffer.rotate_left(40); + emu_state.display_buffer[40 * 23..].fill(b' '); + } + } + _ => {} // ROM + } + }, + )) + .with_read_callback(FunctionReadCallback(|state: &mut State, addr| { + let res = match addr { + constants::variables::KBD => { + let mut emu_state = emu_state.borrow_mut(); + emu_state.keyboard_ready = false; + emu_state.keyboard_register + } + constants::variables::KBD_CR => { + if emu_state.borrow_mut().keyboard_ready { + 0xa7 + } else { + 0x00 + } + } + constants::variables::DSP => emu_state.borrow_mut().display_reg, + constants::variables::DSP_CR => 0b1000_0000, + _ => state.memory[addr as usize], + }; + if emu_state.borrow_mut().step { + let _ = execute!( + io::stdout(), + Print(format!("READ {addr:04X} {res:02X}")), + MoveToNextLine(1) + ); + } + res + })); + + let mut last = Instant::now(); + + loop { + let mut emu_state = emu_state.borrow_mut(); + emu_state.step = false; + + if poll(Duration::ZERO)? { + let event = read()?; + if let Event::Key(KeyEvent { + code, + kind: KeyEventKind::Press, + .. + }) = event + { + match keycode_ascii(code) { + Ok(ascii) => { + emu_state.keyboard_ready = true; + emu_state.keyboard_register = ascii | 0b1000_0000; + } + Err(KeyCode::End | KeyCode::F(1)) => { + // Stop + break Ok(()); + } + Err(KeyCode::Pause | KeyCode::F(2)) => { + // Pause + emu_state.paused ^= true; + emu_state.step_counter = 0; + emu_state.old_display_buffer = Some(emu_state.display_buffer); + } + Err(KeyCode::Home | KeyCode::F(3)) => { + // Reset + let reset = u16::from_le_bytes([ + emulator.state.memory[0xFFFC], + emulator.state.memory[0xFFFD], + ]); + emulator.state.program_counter = reset; + } + Err(KeyCode::Insert | KeyCode::F(4)) => { + // Clear screen + emu_state.display_buffer = [b' '; 40 * 24]; + emu_state.display_x = 0; + emu_state.display_y = 0; + emu_state.step_counter = 0; + emu_state.old_display_buffer = Some([b'@'; 40 * 24]); + execute!(io::stdout(), Clear(ClearType::All))?; + } + Err(KeyCode::F(5)) => { + disable_raw_mode()?; + + // Load a file + execute!( + io::stdout(), + MoveTo(0, 32), + EnableLineWrap, + Print("File path?") + )?; + let mut contents = None; + while contents.is_none() { + execute!(io::stdout(), MoveTo(0, 33), Clear(ClearType::CurrentLine))?; + let mut path_str = String::new(); + io::stdin().read_line(&mut path_str)?; + if path_str.trim().is_empty() { + break; + } + let path_buf = PathBuf::from(path_str.trim()); + if path_buf.exists() { + contents = fs::read(path_buf).ok(); + } + } + let Some(contents) = contents else { + enable_raw_mode()?; + continue; + }; + execute!( + io::stdout(), + MoveTo(0, 34), + EnableLineWrap, + Print(format!( + "Read {} bytes.\nPlace in ROM (in hex):", + contents.len() + )) + )?; + let mut placed = false; + while !placed { + execute!(io::stdout(), MoveTo(0, 36), Clear(ClearType::CurrentLine))?; + let mut input = String::new(); + io::stdin().read_line(&mut input)?; + if input.trim().is_empty() { + break; + } + let Ok(place) = usize::from_str_radix(input.trim(), 16) else { + continue; + }; + if (place + contents.len()) <= 0xFFFF { + placed = true; + emulator.state.memory[place..place + contents.len()] + .copy_from_slice(&contents); + } + } + execute!( + io::stdout(), + MoveTo(0, 32), + DisableLineWrap, + Clear(ClearType::FromCursorDown) + )?; + enable_raw_mode()?; + } + Err(KeyCode::F(6)) if emu_state.paused => { + emu_state.step = true; + } + Err(_) => {} + } + } + } + + if emu_state.last_display.elapsed() > Duration::from_secs_f64(1.0 / 60.0) { + emu_state.last_display = Instant::now(); + emu_state.display_reg &= 0b0111_1111; + if let Some(old) = emu_state.old_display_buffer.take() { + // Display screen + for y in 0..24 { + for x in 0..40 { + if old[(y * 40 + x) as usize] == emu_state.display_buffer[(y * 40 + x) as usize] + { + continue; + } + let byte_char = + CHARACTER_SET[emu_state.display_buffer[(y * 40 + x) as usize] as usize]; + execute!(io::stdout(), MoveTo(x, y), Print(byte_char as char))?; + } + } + + execute!( + io::stdout(), + MoveTo(0, 30), + Clear(ClearType::CurrentLine), + Print(if emu_state.paused { + "F1/End: Quit F2/Pause: Resume F3/Home: Reset F4/Insert: Clear F5: Load File F6: Step" + } else { + "F1/End: Quit F2/Pause: Pause F3/Home: Reset F4/Insert: Clear F5: Load File" + }), + MoveTo( + u16::from(emu_state.display_x), + u16::from(emu_state.display_y) + ) + )?; + } + } + + if !emu_state.paused || emu_state.step { + if emu_state.step { + let opcode_str = + Opcode::load(&emulator.state.memory[emulator.state.program_counter as usize..]) + .unwrap_or_default() + .map_or("".into(), |op| format!("{op}")); + execute!( + io::stdout(), + MoveTo(0, 31), + Clear(ClearType::CurrentLine), + Print(&opcode_str), + MoveTo(80, emu_state.step_counter), + Print(format!( + "{:04X} {opcode_str}", + emulator.state.program_counter + )), + MoveTo(0, 32), + Clear(ClearType::CurrentLine), + Print(format!("{:04X?}", emulator.state)), + MoveTo(0, 33), + Print(format!( + "XAM: {:04x} ST: {:04x} HEX: {:04x} YSAV: {:02x} MODE: {:02x}", + u16::from_le_bytes([ + emulator.state.memory[0x24], + emulator.state.memory[0x25] + ]), + u16::from_le_bytes([ + emulator.state.memory[0x26], + emulator.state.memory[0x27] + ]), + u16::from_le_bytes([ + emulator.state.memory[0x28], + emulator.state.memory[0x29] + ]), + emulator.state.memory[0x2A], + emulator.state.memory[0x2B] + )), + MoveTo(0, 34), + Clear(ClearType::FromCursorDown) + )?; + emu_state.step_counter = emu_state.step_counter.wrapping_add(1); + } + + drop(emu_state); + + let Ok(interrupt_requested) = emulator.step() else { + // !!! INVALID OPCODE! + continue; + }; + if interrupt_requested { + // Jump to IRQ vector + let vector = u16::from_le_bytes([emulator.read(0xFFFE), emulator.read(0xFFFF)]); + emulator.state.program_counter = vector; + }; + } + std::thread::sleep(Duration::from_micros(1).saturating_sub(last.elapsed())); + last = Instant::now(); + } +} + +fn keycode_ascii(code: KeyCode) -> Result { + use KeyCode::{BackTab, Backspace, Char, Delete, Enter, Esc, Null, Tab}; + + match code { + Enter => Ok(b'\r'), + Tab | BackTab => Ok(b'\t'), + Delete => Ok(0x7f), + Backspace => Ok(b'_'), + Char(c) => Ok(c.to_ascii_uppercase() as u8), + Null => Ok(0), + Esc => Ok(0x1B), + c => Err(c), + } +} + +// 0:a9 0 aa 20 ef ff e8 8a 4c 2 0 diff --git a/src/wozmon.rom b/src/wozmon.rom new file mode 100644 index 0000000000000000000000000000000000000000..7280907226f38c9243aa42bcba245bf5824b764e GIT binary patch literal 256 zcmcZ+v7o+3=)%h7y@D5dg)f}E|3UcV><`Q*1o&6pQF#AMm0|CJ1@xLUPO>||g%+56$j$#oOHeE4v3*N4|9FMY6X(`eId(E>_& zZaB#`fi1J+WTtKjhg-cCCP4m=+A15w6IHL7ox{7vyz*3DywP$J