terminal mode! i like this better, also functional cli now

This commit is contained in:
august kline 2024-06-29 23:38:02 -04:00
parent 229b8b450d
commit 001d3e434c
13 changed files with 446 additions and 1624 deletions

1232
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -8,18 +8,8 @@ edition = "2021"
[dependencies] [dependencies]
anyhow = "1.0.81" anyhow = "1.0.81"
bdf = "0.6.0" bdf = "0.6.0"
clap = { version = "4.5.4", features = ["derive"] }
criterion = "0.4"
crossterm = "0.27.0" crossterm = "0.27.0"
minifb = "0.25.0" # minifb = "0.25.0"
ratatui = "0.26.3" # ratatui = "0.26.3"
serde = { version = "1.0.197", features = ["serde_derive", "derive"] } serde = { version = "1.0.197", features = ["serde_derive", "derive"] }
toml = "0.8.12" toml = "0.8.12"
[dev-dependencies]
criterion = {version = "0.4", features = ["html_reports"]}
[[bench]]
name = "benchmark"
path = "src/benches/benchmark.rs"
harness = false

11
run.sh
View File

@ -1,5 +1,12 @@
#!/usr/bin/env bash #!/usr/bin/env bash
vasm6502_oldstyle ./src/roms/george.asm -dotdir -wdc02 -ldots -Fbin -o ./src/roms/george.rom; if [ $# -eq 0 ]; then
cargo run; echo "Provide the name of the rom/asm file to run"
exit 1
fi
set -e
vasm6502_oldstyle ./src/roms/$1.asm -dotdir -wdc02 -ldots -Fbin -o ./src/roms/$1.rom;
cargo run rom "./src/roms/$1.rom";
# hexdump -C ./cpu_dump.bin; # hexdump -C ./cpu_dump.bin;

View File

@ -2,8 +2,8 @@ use crate::instructions::{get_instruction, Instruction};
use crate::memory::{MemHandle, MemoryReader, MemoryWriter}; use crate::memory::{MemHandle, MemoryReader, MemoryWriter};
use crate::types::{Byte, Word}; use crate::types::{Byte, Word};
use std::fmt::Display; use std::fmt::Display;
use std::sync::mpsc::{Receiver, Sender}; use std::sync::mpsc::{channel, Receiver, Sender};
use std::thread::sleep; use std::thread::{sleep, LocalKey};
use std::time::Duration; use std::time::Duration;
use anyhow::Result; use anyhow::Result;
@ -124,6 +124,13 @@ impl Cpu {
// state_tx, // state_tx,
} }
} }
pub fn new_with_control(memory: MemHandle) -> (Self, CpuController) {
let (tx, rx) = channel::<CpuControl>();
let controller = CpuController(tx);
let receiver = CpuReceiver(rx);
let cpu = Cpu::new(memory, receiver);
(cpu, controller)
}
pub fn reset(&mut self) -> Result<()> { pub fn reset(&mut self) -> Result<()> {
let reset_vector_pointer = self.read_word(0xFFFC)?; let reset_vector_pointer = self.read_word(0xFFFC)?;
self.pc = reset_vector_pointer; self.pc = reset_vector_pointer;
@ -203,7 +210,11 @@ impl Cpu {
} }
fn receive_control(&mut self) { fn receive_control(&mut self) {
let control = self.receiver.0.recv().unwrap(); let control = match self.receiver.0.try_recv() {
Ok(control) => control,
Err(_error) => return, // most of the time we won't have any impending control
// messages, just return if that's the case
};
match control { match control {
CpuControl::Nmi => self.nmi = true, CpuControl::Nmi => self.nmi = true,
CpuControl::Irq => self.irq = true, CpuControl::Irq => self.irq = true,
@ -226,13 +237,13 @@ impl Cpu {
} }
pub fn cycle(&mut self) { pub fn cycle(&mut self) {
// self.receive_control(); self.receive_control();
if self.stopped { if self.stopped {
return; return;
} }
while self.pending_cycles != 0 { while self.pending_cycles != 0 {
// roughly cycle-accurate timing // roughly cycle-accurate timing
sleep(Duration::from_nanos(100)); sleep(Duration::from_nanos(500));
self.pending_cycles -= 1; self.pending_cycles -= 1;
} }
if !self.get_flag(StatusFlag::IrqDisable) && self.irq { if !self.get_flag(StatusFlag::IrqDisable) && self.irq {
@ -272,7 +283,6 @@ impl Cpu {
}, },
} }
// self.cycle_count += 1; // self.cycle_count += 1;
sleep(Duration::from_nanos(100));
} }
pub fn stop(&mut self) { pub fn stop(&mut self) {
self.stopped = true; self.stopped = true;

View File

@ -1,4 +1,5 @@
use minifb::{InputCallback, Key}; use crossterm::event::{KeyCode, KeyEvent, KeyEventKind, KeyModifiers};
// use minifb::{InputCallback, Key};
use crate::memory::{MemHandle, MemoryWriter}; use crate::memory::{MemHandle, MemoryWriter};
@ -10,71 +11,66 @@ impl Keyboard {
pub fn new(memory: MemHandle) -> Self { pub fn new(memory: MemHandle) -> Self {
Self { memory } Self { memory }
} }
} pub fn read_keys(&self, key: KeyEvent) {
impl MemoryWriter for Keyboard {
fn write(&self, address: u16, data: u8) {
self.memory.write(address, data);
}
}
impl InputCallback for Keyboard {
fn add_char(&mut self, _uni_char: u32) {}
fn set_key_state(&mut self, key: Key, _state: bool) {
let mut row0 = 0; let mut row0 = 0;
let mut row1 = 0; let mut row1 = 0;
let mut row2 = 0; let mut row2 = 0;
let mut row3 = 0; let mut row3 = 0;
let mut row4 = 0; let mut row4 = 0;
let mut row5 = 0; let mut row5 = 0;
if key.kind == KeyEventKind::Press || key.kind == KeyEventKind::Repeat {
match key { match key.modifiers {
Key::Escape => row0 ^= 0b1000_0000, KeyModifiers::SHIFT => row2 ^= 0b1000_0000,
Key::W => row0 ^= 0b0100_0000, KeyModifiers::CONTROL => row3 ^= 0b1000_0000,
Key::E => row0 ^= 0b0010_0000, KeyModifiers::ALT => row4 ^= 0b1000_0000,
Key::R => row0 ^= 0b0001_0000, KeyModifiers::META => row5 ^= 0b1000_0000,
Key::T => row0 ^= 0b0000_1000, KeyModifiers::SUPER => row5 ^= 0b0010_0000,
Key::U => row0 ^= 0b0000_0100,
Key::O => row0 ^= 0b0000_0010,
Key::Backspace => row0 ^= 0b0000_0001,
Key::Tab => row1 ^= 0b1000_0000,
Key::Q => row1 ^= 0b0100_0000,
Key::S => row1 ^= 0b0010_0000,
Key::G => row1 ^= 0b0001_0000,
Key::Y => row1 ^= 0b0000_1000,
Key::I => row1 ^= 0b0000_0100,
Key::P => row1 ^= 0b0000_0010,
Key::Enter => row1 ^= 0b0000_0001,
Key::LeftShift | Key::RightShift => row2 ^= 0b1000_0000,
Key::D => row2 ^= 0b0100_0000,
Key::V => row2 ^= 0b0010_0000,
Key::H => row2 ^= 0b0001_0000,
Key::K => row2 ^= 0b0000_1000,
Key::Apostrophe => row2 ^= 0b0000_0100,
Key::Slash => row2 ^= 0b0000_0010,
Key::A => row2 ^= 0b0000_0001,
Key::LeftCtrl | Key::RightCtrl => row3 ^= 0b1000_0000,
Key::Z => row3 ^= 0b0100_0000,
Key::F => row3 ^= 0b0010_0000,
Key::B => row3 ^= 0b0001_0000,
Key::J => row3 ^= 0b0000_1000,
Key::L => row3 ^= 0b0000_0100,
Key::Key2 => row3 ^= 0b0000_0010,
Key::Key4 => row3 ^= 0b0000_0001,
Key::LeftAlt | Key::RightAlt => row4 ^= 0b1000_0000,
Key::X => row4 ^= 0b0100_0000,
Key::C => row4 ^= 0b0010_0000,
Key::N => row4 ^= 0b0001_0000,
Key::M => row4 ^= 0b0000_1000,
Key::Comma => row4 ^= 0b0000_0100,
Key::Key1 => row4 ^= 0b0000_0010,
Key::Key3 => row4 ^= 0b0000_0001,
Key::LeftSuper => row5 ^= 0b1000_0000,
Key::Space => row5 ^= 0b0100_0000,
Key::RightSuper => row5 ^= 0b0010_0000,
_ => {} _ => {}
}; };
match key.code {
KeyCode::Esc => row0 ^= 0b1000_0000,
KeyCode::Char('w') => row0 ^= 0b0100_0000,
KeyCode::Char('e') => row0 ^= 0b0010_0000,
KeyCode::Char('r') => row0 ^= 0b0001_0000,
KeyCode::Char('t') => row0 ^= 0b0000_1000,
KeyCode::Char('u') => row0 ^= 0b0000_0100,
KeyCode::Char('o') => row0 ^= 0b0000_0010,
KeyCode::Backspace => row0 ^= 0b0000_0001,
KeyCode::Tab => row1 ^= 0b1000_0000,
KeyCode::Char('q') => row1 ^= 0b0100_0000,
KeyCode::Char('s') => row1 ^= 0b0010_0000,
KeyCode::Char('g') => row1 ^= 0b0001_0000,
KeyCode::Char('y') => row1 ^= 0b0000_1000,
KeyCode::Char('i') => row1 ^= 0b0000_0100,
KeyCode::Char('p') => row1 ^= 0b0000_0010,
KeyCode::Enter => row1 ^= 0b0000_0001,
KeyCode::Char('d') => row2 ^= 0b0100_0000,
KeyCode::Char('v') => row2 ^= 0b0010_0000,
KeyCode::Char('h') => row2 ^= 0b0001_0000,
KeyCode::Char('k') => row2 ^= 0b0000_1000,
KeyCode::Char('\'') => row2 ^= 0b0000_0100,
KeyCode::Char('/') => row2 ^= 0b0000_0010,
KeyCode::Char('a') => row2 ^= 0b0000_0001,
KeyCode::Char('z') => row3 ^= 0b0100_0000,
KeyCode::Char('f') => row3 ^= 0b0010_0000,
KeyCode::Char('b') => row3 ^= 0b0001_0000,
KeyCode::Char('j') => row3 ^= 0b0000_1000,
KeyCode::Char('l') => row3 ^= 0b0000_0100,
KeyCode::Char('2') => row3 ^= 0b0000_0010,
KeyCode::Char('4') => row3 ^= 0b0000_0001,
KeyCode::Char('x') => row4 ^= 0b0100_0000,
KeyCode::Char('c') => row4 ^= 0b0010_0000,
KeyCode::Char('n') => row4 ^= 0b0001_0000,
KeyCode::Char('m') => row4 ^= 0b0000_1000,
KeyCode::Char(',') => row4 ^= 0b0000_0100,
KeyCode::Char('1') => row4 ^= 0b0000_0010,
KeyCode::Char('3') => row4 ^= 0b0000_0001,
KeyCode::Char(' ') => row5 ^= 0b0100_0000,
_ => {}
}
};
self.memory.write(0x4400, row0); self.memory.write(0x4400, row0);
self.memory.write(0x4401, row1); self.memory.write(0x4401, row1);
self.memory.write(0x4402, row2); self.memory.write(0x4402, row2);
@ -83,3 +79,75 @@ impl InputCallback for Keyboard {
self.memory.write(0x4405, row5); self.memory.write(0x4405, row5);
} }
} }
impl MemoryWriter for Keyboard {
fn write(&self, address: u16, data: u8) {
self.memory.write(address, data);
}
}
// impl InputCallback for Keyboard {
// fn add_char(&mut self, _uni_char: u32) {}
// fn set_key_state(&mut self, key: Key, _state: bool) {
// let mut row0 = 0;
// let mut row1 = 0;
// let mut row2 = 0;
// let mut row3 = 0;
// let mut row4 = 0;
// let mut row5 = 0;
// match key {
// Key::Escape => row0 ^= 0b1000_0000,
// Key::W => row0 ^= 0b0100_0000,
// Key::E => row0 ^= 0b0010_0000,
// Key::R => row0 ^= 0b0001_0000,
// Key::T => row0 ^= 0b0000_1000,
// Key::U => row0 ^= 0b0000_0100,
// Key::O => row0 ^= 0b0000_0010,
// Key::Backspace => row0 ^= 0b0000_0001,
// Key::Tab => row1 ^= 0b1000_0000,
// Key::Q => row1 ^= 0b0100_0000,
// Key::S => row1 ^= 0b0010_0000,
// Key::G => row1 ^= 0b0001_0000,
// Key::Y => row1 ^= 0b0000_1000,
// Key::I => row1 ^= 0b0000_0100,
// Key::P => row1 ^= 0b0000_0010,
// Key::Enter => row1 ^= 0b0000_0001,
// Key::LeftShift | Key::RightShift => row2 ^= 0b1000_0000,
// Key::D => row2 ^= 0b0100_0000,
// Key::V => row2 ^= 0b0010_0000,
// Key::H => row2 ^= 0b0001_0000,
// Key::K => row2 ^= 0b0000_1000,
// Key::Apostrophe => row2 ^= 0b0000_0100,
// Key::Slash => row2 ^= 0b0000_0010,
// Key::A => row2 ^= 0b0000_0001,
// Key::LeftCtrl | Key::RightCtrl => row3 ^= 0b1000_0000,
// Key::Z => row3 ^= 0b0100_0000,
// Key::F => row3 ^= 0b0010_0000,
// Key::B => row3 ^= 0b0001_0000,
// Key::J => row3 ^= 0b0000_1000,
// Key::L => row3 ^= 0b0000_0100,
// Key::Key2 => row3 ^= 0b0000_0010,
// Key::Key4 => row3 ^= 0b0000_0001,
// Key::LeftAlt | Key::RightAlt => row4 ^= 0b1000_0000,
// Key::X => row4 ^= 0b0100_0000,
// Key::C => row4 ^= 0b0010_0000,
// Key::N => row4 ^= 0b0001_0000,
// Key::M => row4 ^= 0b0000_1000,
// Key::Comma => row4 ^= 0b0000_0100,
// Key::Key1 => row4 ^= 0b0000_0010,
// Key::Key3 => row4 ^= 0b0000_0001,
// Key::LeftSuper => row5 ^= 0b1000_0000,
// Key::Space => row5 ^= 0b0100_0000,
// Key::RightSuper => row5 ^= 0b0010_0000,
// _ => {}
// };
// self.memory.write(0x4400, row0);
// self.memory.write(0x4401, row1);
// self.memory.write(0x4402, row2);
// self.memory.write(0x4403, row3);
// self.memory.write(0x4404, row4);
// self.memory.write(0x4405, row5);
// }
// }

View File

@ -5,7 +5,6 @@ mod error;
mod instructions; mod instructions;
mod keyboard; mod keyboard;
mod memory; mod memory;
mod tui;
mod types; mod types;
mod video; mod video;
@ -14,41 +13,31 @@ use crate::keyboard::Keyboard;
use crate::memory::Mem; use crate::memory::Mem;
use crate::video::{Screen, TerminalRenderer}; use crate::video::{Screen, TerminalRenderer};
use cpu::{CpuController, CpuReceiver};
use crossterm::cursor::Hide;
use crossterm::execute; use crossterm::execute;
use crossterm::terminal::{size, Clear, ClearType, SetSize}; use crossterm::terminal::{size, Clear, ClearType, SetSize};
// use cpu::CpuController; // use cpu::CpuController;
use memory::MemHandle; use memory::MemHandle;
// use clap::Parser;
// use minifb::{Scale, ScaleMode, Window, WindowOptions}; // use minifb::{Scale, ScaleMode, Window, WindowOptions};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::env;
use std::io::ErrorKind;
use std::process::exit;
use std::thread::{self, sleep};
use std::time::Duration;
use std::{ use std::{
fs::File, fs::File,
io::{stdout, Read, Result}, io::{stdout, Read, Result},
path::PathBuf, path::PathBuf,
str::FromStr, str::FromStr,
sync::mpsc,
thread,
}; };
use crossterm::{ use crossterm::{
cursor, cursor,
event::{self, KeyCode, KeyEventKind}, event::{self, KeyCode, KeyEventKind},
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen}, terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
// ExecutableCommand,
}; };
// use toml::Table;
//#[derive(Parser)]
//struct Cli {
// // Load a rom onto the system rom
// #[arg(short, required = true)]
// rom: Option<std::path::PathBuf>,
//}
#[derive(Serialize, Deserialize, Debug)] #[derive(Serialize, Deserialize, Debug)]
struct Config { struct Config {
char_rom: Option<String>, char_rom: Option<String>,
@ -56,21 +45,26 @@ struct Config {
} }
fn main() -> Result<()> { fn main() -> Result<()> {
let mut stdout = stdout(); let args: Vec<String> = env::args().collect();
let (cols, rows) = size()?;
execute!(stdout, SetSize(64, 29), cursor::Hide, EnterAlternateScreen)?;
enable_raw_mode()?;
let config: Config = match File::open("./config.toml") { let mut memory = Mem::new();
match args.len() {
0 => {
println!("george-emu must be run in the terminal, don't know what went wrong here!");
exit(1)
}
1 => {
let config: Config = match File::open("./george.toml") {
Ok(mut file) => { Ok(mut file) => {
let mut string = String::new(); let mut string = String::new();
file.read_to_string(&mut string).unwrap(); file.read_to_string(&mut string).unwrap();
toml::from_str(string.as_str()).unwrap() toml::from_str(string.as_str()).unwrap()
} }
Err(_) => return Ok(()), Err(_) => {
println!("couldn't find a `george.toml` in the current directory!");
exit(1);
}
}; };
let mut memory = Mem::new();
let rom = match std::fs::File::open(config.rom) { let rom = match std::fs::File::open(config.rom) {
Ok(file) => file, Ok(file) => file,
Err(error) => panic!("Couldn't open main rom! {:?}", error), Err(error) => panic!("Couldn't open main rom! {:?}", error),
@ -78,6 +72,86 @@ fn main() -> Result<()> {
if let Err(error) = memory.load_rom(rom) { if let Err(error) = memory.load_rom(rom) {
println!("{:?}", error); println!("{:?}", error);
}; };
}
2 => match &args[1] as &str {
"help" => {
println!("ʕ·ᴥ·ʔ- george-emu is an emulator for george:");
println!("https://git.augustkline.com/august/george\n");
println!("commands:");
println!(" help: print this help screen");
println!(" help <command>: print help info for any command");
println!(" rom <path>: load a rom/binary from path\n");
println!("configuration:");
println!(" george-emu searches for a `george.toml` in the current directory. in `george.toml` you can specify a path for the character rom using the key `char_rom` and the main rom/binary with the key `rom`");
exit(0);
}
_ => {
println!(
"{:?} isn't a valid command!\n\nuse `{} help` to see all valid commands",
&args[1], &args[0]
);
exit(1);
}
},
3 => match &args[1] as &str {
"help" => match &args[2] as &str {
"rom" => {
println!("{:?} rom <path>\nload a rom/binary from path", &args[0]);
}
_ => {
println!(
"{:?} isn't a valid command!\n\nuse `{} help` to see all valid commands",
&args[2], &args[0]
);
exit(1);
}
},
"rom" => {
let rom = match std::fs::File::open(&args[2]) {
Ok(file) => file,
Err(error) => {
match error.kind() {
ErrorKind::NotFound => {
println!("couldn't find the rom at {:?}", &args[2]);
}
ErrorKind::PermissionDenied => {
println!(
"didn't have sufficient permissions to open the rom at {:?}",
&args[2]
);
}
_ => {
println!("something went wrong! try again in a moment? really not sure why you're getting this error");
}
}
exit(1);
}
};
if let Err(error) = memory.load_rom(rom) {
println!("oh no! this rom couldn't be loaded: {:?}", error);
exit(1);
};
}
_ => {
println!(
"{:?} isn't a valid command!\n\nuse `{} help` to see all valid commands",
&args[1], &args[0]
);
exit(1);
}
},
_ => {
println!(
"too many arguments were provided!\n\nuse `{} help` to see all valid commands",
&args[0]
);
exit(1);
}
}
let mut stdout = stdout();
let (cols, rows) = size()?;
execute!(stdout, SetSize(64, 29), cursor::Hide, EnterAlternateScreen)?;
enable_raw_mode()?;
memory memory
.dump(PathBuf::from_str("./coredump.bin").unwrap()) .dump(PathBuf::from_str("./coredump.bin").unwrap())
@ -88,11 +162,8 @@ fn main() -> Result<()> {
let cpu_memory = shared_memory.clone(); let cpu_memory = shared_memory.clone();
let keyboard_memory = shared_memory.clone(); let keyboard_memory = shared_memory.clone();
let (cpu_tx, cpu_rx) = mpsc::channel(); // For when we want to leave the terminal again :sigh:
// let (state_tx, state_rx) = mpsc::channel(); //
let screen_cpu_tx = cpu_tx.clone();
// let (window_tx, window_rx) = mpsc::channel();
// thread::spawn(move || { // thread::spawn(move || {
// let mut screen = Crtc::new( // let mut screen = Crtc::new(
// screen_memory, // screen_memory,
@ -120,40 +191,30 @@ fn main() -> Result<()> {
// ) // )
// .unwrap(); // .unwrap();
// window.set_input_callback(Box::new(Keyboard::new(keyboard_memory))); let keyboard = Keyboard::new(keyboard_memory);
let mut cpu = Cpu::new(cpu_memory, CpuReceiver::new(cpu_rx)); let (mut cpu, cpu_controller) = Cpu::new_with_control(cpu_memory);
// let mut cpu = Cpu::new(cpu_memory); let _ = cpu.reset();
// let mut tui = tui::App::new(
// CpuController::new(cpu_tx.clone()), thread::spawn(move || loop {
// shared_memory.clone(), cpu.cycle();
// state_rx,
// );
thread::spawn(move || {
cpu.reset().unwrap();
cpu.memory.write(0x4400, 0b0000_0100);
cpu.execute()
}); });
let stdout_lock = stdout.lock(); let stdout_lock = stdout.lock();
let renderer = TerminalRenderer::new(screen_memory, stdout_lock); let renderer = TerminalRenderer::new(screen_memory, stdout_lock);
let mut screen = Screen::new(CpuController::new(cpu_tx.clone()), renderer); let mut screen = Screen::new(renderer, cpu_controller);
// thread::spawn(move || {
// screen.run();
// });
loop { loop {
sleep(Duration::from_millis(16));
screen.draw();
if event::poll(std::time::Duration::from_millis(16))? { if event::poll(std::time::Duration::from_millis(16))? {
if let event::Event::Key(key) = event::read()? { if let event::Event::Key(key) = event::read()? {
keyboard.read_keys(key);
if key.kind == KeyEventKind::Press && key.code == KeyCode::Char('q') { if key.kind == KeyEventKind::Press && key.code == KeyCode::Char('q') {
break; break;
} }
} }
} }
// let buffer = window_rx.recv().unwrap();
screen.draw()
// tui.update(&mut terminal)?;
} }
execute!( execute!(

View File

@ -1,10 +1,10 @@
use crate::types::{Byte, Word};
use anyhow::{bail, Result}; use anyhow::{bail, Result};
use std::cell::RefCell;
use std::io::{self, Write}; use std::io::{self, Write};
use std::path::PathBuf; use std::path::PathBuf;
use std::rc::Rc;
use std::str::FromStr; use std::str::FromStr;
use std::sync::{Arc, Mutex, MutexGuard}; use std::sync::{Arc, Mutex};
use std::u16;
use std::{fs::File, io::Read}; use std::{fs::File, io::Read};
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
@ -13,30 +13,25 @@ impl MemHandle {
pub fn new(memory: Mem) -> Self { pub fn new(memory: Mem) -> Self {
Self(Arc::new(Mutex::new(memory))) Self(Arc::new(Mutex::new(memory)))
} }
pub fn read(&self, address: Word) -> Byte { pub fn read(&self, address: u16) -> u8 {
let memory = self.lock().unwrap(); let memory = self.0.lock().unwrap();
memory.read(address) memory.read(address)
} }
pub fn write(&self, address: Word, data: Byte) { pub fn write(&self, address: u16, data: u8) {
let mut memory = self.lock().unwrap(); let mut memory = self.0.lock().unwrap();
memory.write(address, data); memory.write(address, data);
} }
pub fn dump(&self) { pub fn dump(&self) {
let memory = self.lock().unwrap(); let memory = self.0.lock().unwrap();
memory.dump(PathBuf::from_str("./cpu_dump.bin").unwrap()); let _ = memory.dump(PathBuf::from_str("./cpu_dump.bin").unwrap());
}
fn lock(&self) -> Result<MutexGuard<'_, Mem>> {
match self.0.lock() {
Ok(result) => Ok(result),
Err(error) => bail!("{error}"),
} }
pub fn poke(&self, address: u16) {
let memory = self.0.lock().unwrap();
memory.poke(address)
} }
} }
// TODO: impl Iterator for MemHandle so we can get references to the underlying data from other threads without cloning
pub trait MemoryReader { pub trait MemoryReader {
fn read(&self, address: u16) -> u8; fn read(&self, address: u16) -> u8;
} }
@ -45,19 +40,16 @@ pub trait MemoryWriter {
fn write(&self, address: u16, data: u8); fn write(&self, address: u16, data: u8);
} }
// pub trait MemoryAccess: MemoryReader + MemoryWriter {} #[derive(Debug, Clone, Copy)]
// impl<T> MemoryAccess for T where T: MemoryReader + MemoryWriter {} // This always feels wrong instead of 0xFFFF,
// but remember it's the number of elements,
#[derive(Debug, Clone)] // not the maximum index. 0x0000 to 0xFFFF addresses
pub struct Mem { // 0x10000 elements
pub data: Vec<u8>, pub struct Mem([u8; 0x10000]);
}
impl Default for Mem { impl Default for Mem {
fn default() -> Self { fn default() -> Self {
Self { Self([0; 0x10000])
data: vec![0; u16::MAX as usize + 1],
}
} }
} }
@ -67,23 +59,24 @@ impl Mem {
} }
pub fn dump(&self, path: PathBuf) -> io::Result<()> { pub fn dump(&self, path: PathBuf) -> io::Result<()> {
let mut outfile = File::create(path)?; let mut outfile = File::create(path)?;
outfile.write_all(&self.data)?; outfile.write_all(&self.0)?;
Ok(()) Ok(())
} }
pub fn read(&self, address: Word) -> Byte { pub fn read(&self, address: u16) -> u8 {
self.data[address as usize] self.0[address as usize]
} }
pub fn write(&mut self, address: Word, data: Byte) { pub fn write(&mut self, address: u16, data: u8) {
self.data[address as usize] = data; self.0[address as usize] = data;
} }
pub fn load_rom(&mut self, rom: File) -> Result<()> { pub fn load_rom(&mut self, rom: File) -> Result<()> {
let bytes = rom.bytes(); let bytes = rom.bytes();
for (address, byte) in bytes.enumerate() { for (address, byte) in bytes.enumerate() {
// println!("{address}");
match byte { match byte {
Ok(value) => self.write(address as Word + 0x8000, value), Ok(value) => self.write(address as u16 + 0x8000, value),
Err(_) => { Err(_) => {
bail!("Loading rom: couldn't write byte {:#04x}", address); bail!("Loading rom: couldn't write byte {:#04x}", address);
} }
@ -91,11 +84,15 @@ impl Mem {
} }
Ok(()) Ok(())
} }
pub fn poke(&self, address: u16) {
println!("{:02x}", self.read(address));
}
//pub fn read_from_bin(&mut self, f: File) -> Result<()> { //pub fn read_from_bin(&mut self, f: File) -> Result<()> {
// let bytes = f.bytes(); // let bytes = f.bytes();
// for (address, byte) in bytes.enumerate() { // for (address, byte) in bytes.enumerate() {
// match byte { // match byte {
// Ok(value) => self.write(address as Word, value), // Ok(value) => self.write(address as u16, value),
// Err(_) => { // Err(_) => {
// bail!("couldn't write byte {:#04x}", address); // bail!("couldn't write byte {:#04x}", address);
// } // }

View File

@ -37,7 +37,7 @@ cleardisplay:
; cli ; cli
main: main:
jsr keyboard jsr printtext
; key_zero: ; key_zero:
; stz keyboard_cache, x ; stz keyboard_cache, x
; dex ; dex
@ -45,164 +45,28 @@ main:
; fim: ; fim:
; cli ; cli
; bra fim ; bra fim
; jsr kitty_keys
jmp main jmp main
; ; copying @smal rn: https://github.com/smaldragon/KittyEMU/blob/main/roms/foxmon.asm printtext:
; char_timer = $10 ldx 0
; line_buffer = $0200 loop:
; char_cur = $11 lda text, x
; line_buffer_i = $12 beq end
; line_buffer_l = $13 sta $6000, x
inx
bra loop
end:
rts
; keyboard_cache = $14 text:
; line_cur = $20 .asciiz "hi <3"
; irq:
; lda line_buffer_i
; lda $e0
; lda $6f
; sei
; stz char_cur
; lda line_buffer_i
; sta line_buffer_l
; ldx #4
; kitty_keys: ; reads pressed key and writes keymap value to char_cur
; phx
; txa
; asl
; asl
; asl
; asl
; asl ; i think this is supposed to be once for every keyboard row
; tax
; lda kb_row, x
; plx
; pha
; cmp keyboard_cache, x
; bne change
; jmp nochange
; change:
; bit7:
; asl keyboard_cache, x
; bcs bit6
; bit #0b10000000
; beq bit6
; pha
; lda keymap_7, x
; sta char_cur
; pla
; bit6:
; asl keyboard_cache, x
; bcs bit5
; bit #0b01000000
; beq bit5
; pha
; lda keymap_6, x
; sta char_cur
; pla
; bit5:
; asl keyboard_cache, x
; bcs bit4
; bit #0b00100000
; beq bit4
; pha
; lda keymap_5, x
; sta char_cur
; pla
; bit4:
; asl keyboard_cache, x
; bcs bit3
; bit #0b00010000
; beq bit3
; pha
; lda keymap_4, x
; sta char_cur
; pla
; bit3:
; asl keyboard_cache, x
; bcs bit2
; bit #0b00001000
; beq bit2
; pha
; lda keymap_3, x
; sta char_cur
; pla
; bit2:
; asl keyboard_cache, x
; bcs bit1
; bit #0b00000100
; beq bit1
; pha
; lda keymap_2, x
; sta char_cur
; pla
; bit1:
; asl keyboard_cache, x
; bcs bit0
; bit #0b00000010
; beq bit0
; pha
; lda keymap_1, x
; sta char_cur
; pla
; bit0:
; asl keyboard_cache, x
; bcs bitend
; bit #0b00000001
; beq bitend
; pha
; lda keymap_0, x
; sta char_cur
; pla
; bitend:
; nochange:
; pla
; sta keyboard_cache, x
; dex
; bmi keyend
; jmp kitty_keys
; keyend:
; write:
; lda char_cur
; ldy cursor
; sta $6000, y
; inc cursor
; rts
; ; col = keyboard row, row = keyboard bit placement inverted
; keymap_0:
; .byte "??????"
; keymap_1:
; .byte "wqdzx "
; keymap_2:
; .byte "esvfc "
; keymap_3:
; .byte "rghbn?"
; keymap_4:
; .byte "tykjm?"
; keymap_5:
; .byte "ui?l??"
; keymap_6:
; .byte "op?21?"
; keymap_7:
; .byte "??a43?"
;
;
keyboard: keyboard:
ldy #0
x
not_keyboard:
ldy #0 ldy #0
.check_row: ; loop through each row .check_row: ; loop through each row
lda kb_row, y lda kb_row, y
beq .skip_row ; if row has no key pressed, skip checking which key beq .skip_row ; if row has no key pressed, skip checking which key
; jmp key_down
sta kb_row_cache, y ; if key pressed, cache it sta kb_row_cache, y ; if key pressed, cache it
lda kb_row, y lda kb_row, y
cmp kb_row_cache, y ; has key changed? cmp kb_row_cache, y ; has key changed?

Binary file not shown.

View File

@ -1,26 +1,16 @@
; .setcpu "65C02" ; .setcpu "65C02"
.include "./macro.inc" .include "./macro.inc"
; okay so rn i wanna set up a very basic system init, and write a few subroutines to draw characters at x,y coordinates
n = $01 ; temporary storage for data stack operations
key_row = $200 ; used for character lookup when key pressed
key_col = $201
cursor = $202
char_buffer = $300 ; 256 byte character buffer
kb_row = $4400 ; keyboard hardware register, there are 5 more but i can just increment from here
kb_row_cache = $203 ; cache
.org $8000 .org $8000
n = $01 ; temporary storage for data stack operations
reset: reset:
sei sei
ldx #0; initialize data stack pointer ldx #0; initialize data stack pointer
initdisplay: initdisplay:
lda #$20 lda #0
ldy #0 ldy #0
cleardisplay: cleardisplay:
@ -37,56 +27,13 @@ cleardisplay:
cli cli
main: main:
jsr draw ; jmp draw
jmp cleardisplay
jmp main jmp main
keyboard:
ldy #5
.loop: ; loop through each row
lda kb_row, y
bne key_down; if row has key pressed, go to key_down subroutine
dey
bne .loop
rts
key_down: ; a is loaded with the row byte
sty key_row ; store character row
inc cursor
pha
phy
ldy #0
.find_col: ; test each row bit, store column if key pressed
lsr ; test bit 7
bcs store_col ; if unset, don't go store character columnb
.skip:
iny
cpy #8
bne .find_col ; loop until we've checked each bit
do_something_w_key: ; we've stored the character position, now let's
lda keymap, y
ldy cursor
sta $6000, y
ply
pla
rts
keymap_0:
.byte "?outrew?"
keymap_1:
.byte "?piygsq?"
keymap_2:
.byte "a??khvd?"
keymap_3:
.byte "42ljbfz?"
keymap_4:
.byte "31?mncx?"
keymap_5:
.byte "????? m"
draw: draw:
push_coords #0, #0 push_coords #0, #0
push_char #$00 push_char #$af
jsr draw_char jsr draw_char
rts rts
@ -125,11 +72,16 @@ get_char_address: ; gets vram address for a character at (x, y),
fill: ; fills an area from (x1, y1) to (x2, y2) will character c, (n1: c n2: x1 n3: y1 n4: x2 n5: y2 -- ) fill: ; fills an area from (x1, y1) to (x2, y2) will character c, (n1: c n2: x1 n3: y1 n4: x2 n5: y2 -- )
jsr get_char_address jsr get_char_address
irq:
keyboard:
rts
isr: ; interrupt service routine isr: ; interrupt service routine
pha pha
phx phx
phy phy
jsr keyboard jsr irq
ply ply
plx plx
pla pla

88
src/roms/template.asm Normal file
View File

@ -0,0 +1,88 @@
; .setcpu "65C02"
.include "./macro.inc"
.org $8000
n = $01 ; temporary storage for data stack operations
reset:
sei
ldx #0; initialize data stack pointer
initdisplay:
lda #0
ldy #0
cleardisplay:
sta $6000,y
sta $6100,y
sta $6200,y
sta $6300,y
sta $6400,y
sta $6500,y
sta $6600,y
sta $6700,y ; this goes slightly over but it's fine
iny
bne cleardisplay
cli
main:
jmp draw
jmp main
draw:
push_coords #0, #0
push_char #$af
jsr draw_char
rts
draw_char: ; draw a character c at (x, y) (n1: x n2: y n3: c -- )
lda 0, x ; load a with character to draw
pop ; and pop it off the stack
jsr get_char_address ; calculate where to put the character in memory
sta (0, x) ; store a at the address pointed to on the stack
rts
get_char_address: ; gets vram address for a character at (x, y),
; (n1: x n2: y -- n: $6000 + x + (64 * y))
;jsr push_lit ; push 64 onto stack, low byte first
;.byte 64
;.byte 0
pha
lda #64
push ; doing this instead until `push_lit` is fixed
sta 0, x
stz 1, x
jsr mult ; multiply 64 with y (n2)
jsr plus ; add result with x (n1)
;jsr push_lit ; push vram address onto the stack
;.byte $00
;.byte $60
lda #$60
push
sta 1, x
stz 0, x
jsr plus ; add vram start address to result
pla
rts
fill: ; fills an area from (x1, y1) to (x2, y2) will character c, (n1: c n2: x1 n3: y1 n4: x2 n5: y2 -- )
jsr get_char_address
isr: ; interrupt service routine
pha
phx
phy
; jsr irq
ply
plx
pla
rti
.include "math.inc"
.org $fffc
.word reset
.word isr

View File

@ -1,21 +1,21 @@
use crossterm::{ use crossterm::{
cursor::{MoveTo, SavePosition}, cursor::{MoveTo, RestorePosition, SavePosition},
execute, queue, execute, queue,
style::{Color, PrintStyledContent, Stylize}, style::{Color, PrintStyledContent, Stylize},
}; };
use crate::{ use crate::{
cpu::CpuController, cpu::CpuController,
memory::{self, MemHandle, MemoryReader}, memory::{MemHandle, MemoryReader},
types::{Byte, Word}, types::{Byte, Word},
}; };
use std::{ use std::{
fs::File, fs::File,
io::{Read, Stdout, StdoutLock, Write}, io::{Read, StdoutLock, Write},
path::Path, path::Path,
process::exit,
sync::mpsc::Sender, sync::mpsc::Sender,
thread::sleep, time::Instant,
time::Duration,
}; };
const FG_COLOR: u32 = 0xFFCC00; const FG_COLOR: u32 = 0xFFCC00;
@ -73,7 +73,7 @@ impl Renderer for WindowRenderer {
let mut buffer = vec![0; 512 * 380]; let mut buffer = vec![0; 512 * 380];
for char_row in 0..29 { for char_row in 0..29 {
for char_col in 0..64 { for char_col in 0..64 {
let ascii = self.memory.read(0x6000 + i); let ascii = self.read(0x6000 + i);
i += 1; i += 1;
for row in 0..13 { for row in 0..13 {
let byte = self.char_rom[ascii as usize + (row * 0x100)]; let byte = self.char_rom[ascii as usize + (row * 0x100)];
@ -118,7 +118,7 @@ impl MemoryReader for TerminalRenderer<'_> {
} }
} }
static ASCII_LOOPUP: [&str; 256] = [ const ASCII_LOOPUP: [&str; 256] = [
" ", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", " ", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "奔", "", "", "", "", "", "", " ", "!", "\"", "#", "", "", "", "", "", "", "", "奔", "", "", "", "", "", "", " ", "!", "\"", "#",
"$", "%", "&", "\'", "(", ")", "*", "+", ",", "-", ".", "/", "0", "1", "2", "3", "4", "5", "6", "$", "%", "&", "\'", "(", ")", "*", "+", ",", "-", ".", "/", "0", "1", "2", "3", "4", "5", "6",
@ -137,11 +137,12 @@ static ASCII_LOOPUP: [&str; 256] = [
impl Renderer for TerminalRenderer<'_> { impl Renderer for TerminalRenderer<'_> {
fn render(&mut self) { fn render(&mut self) {
// let now = Instant::now();
let _ = execute!(self.stdout, SavePosition); let _ = execute!(self.stdout, SavePosition);
let mut i = 0; let mut i = 0;
for char_row in 0..29 { for char_row in 0..29 {
for char_col in 0..64 { for char_col in 0..64 {
let ascii = self.memory.read(0x6000 + i); let ascii = self.read(0x6000 + i);
i += 1; i += 1;
let char = ASCII_LOOPUP[ascii as usize]; let char = ASCII_LOOPUP[ascii as usize];
let _ = queue!( let _ = queue!(
@ -165,32 +166,36 @@ impl Renderer for TerminalRenderer<'_> {
} }
} }
let _ = self.stdout.flush(); let _ = self.stdout.flush();
let _ = execute!(self.stdout, RestorePosition);
// let elapsed = now.elapsed();
// println!("{elapsed:?}");
// exit(0);
} }
} }
pub struct Screen<'a> { pub struct Screen<'a> {
cpu_controller: CpuController,
// renderer: Box<dyn Renderer>, // renderer: Box<dyn Renderer>,
renderer: TerminalRenderer<'a>, renderer: TerminalRenderer<'a>,
controller: CpuController,
} }
impl<'a> Screen<'a> { impl<'a> Screen<'a> {
// pub fn new(cpu_controller: CpuController, renderer: Box<dyn Renderer>) -> Self { // pub fn new(cpu_controller: CpuController, renderer: Box<dyn Renderer>) -> Self {
pub fn new(cpu_controller: CpuController, renderer: TerminalRenderer<'a>) -> Self { pub fn new(renderer: TerminalRenderer<'a>, controller: CpuController) -> Self {
Self { Self {
cpu_controller,
renderer, renderer,
controller,
} }
} }
pub fn draw(&mut self) { pub fn draw(&mut self) {
self.controller.irq();
self.renderer.render(); self.renderer.render();
} }
pub fn run(&mut self) { // pub fn run(&mut self) {
loop { // loop {
self.cpu_controller.irq(); // // sleep(Duration::from_millis(16));
sleep(Duration::from_millis(16)); // self.draw();
self.draw(); // }
} // }
}
} }