terminal mode! i like this better, also functional cli now
This commit is contained in:
parent
229b8b450d
commit
001d3e434c
File diff suppressed because it is too large
Load Diff
14
Cargo.toml
14
Cargo.toml
|
@ -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
11
run.sh
|
@ -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;
|
||||||
|
|
22
src/cpu.rs
22
src/cpu.rs
|
@ -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;
|
||||||
|
|
182
src/keyboard.rs
182
src/keyboard.rs
|
@ -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,69 +11,64 @@ 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.modifiers {
|
||||||
|
KeyModifiers::SHIFT => row2 ^= 0b1000_0000,
|
||||||
|
KeyModifiers::CONTROL => row3 ^= 0b1000_0000,
|
||||||
|
KeyModifiers::ALT => row4 ^= 0b1000_0000,
|
||||||
|
KeyModifiers::META => row5 ^= 0b1000_0000,
|
||||||
|
KeyModifiers::SUPER => row5 ^= 0b0010_0000,
|
||||||
|
_ => {}
|
||||||
|
};
|
||||||
|
|
||||||
match key {
|
match key.code {
|
||||||
Key::Escape => row0 ^= 0b1000_0000,
|
KeyCode::Esc => row0 ^= 0b1000_0000,
|
||||||
Key::W => row0 ^= 0b0100_0000,
|
KeyCode::Char('w') => row0 ^= 0b0100_0000,
|
||||||
Key::E => row0 ^= 0b0010_0000,
|
KeyCode::Char('e') => row0 ^= 0b0010_0000,
|
||||||
Key::R => row0 ^= 0b0001_0000,
|
KeyCode::Char('r') => row0 ^= 0b0001_0000,
|
||||||
Key::T => row0 ^= 0b0000_1000,
|
KeyCode::Char('t') => row0 ^= 0b0000_1000,
|
||||||
Key::U => row0 ^= 0b0000_0100,
|
KeyCode::Char('u') => row0 ^= 0b0000_0100,
|
||||||
Key::O => row0 ^= 0b0000_0010,
|
KeyCode::Char('o') => row0 ^= 0b0000_0010,
|
||||||
Key::Backspace => row0 ^= 0b0000_0001,
|
KeyCode::Backspace => row0 ^= 0b0000_0001,
|
||||||
Key::Tab => row1 ^= 0b1000_0000,
|
KeyCode::Tab => row1 ^= 0b1000_0000,
|
||||||
Key::Q => row1 ^= 0b0100_0000,
|
KeyCode::Char('q') => row1 ^= 0b0100_0000,
|
||||||
Key::S => row1 ^= 0b0010_0000,
|
KeyCode::Char('s') => row1 ^= 0b0010_0000,
|
||||||
Key::G => row1 ^= 0b0001_0000,
|
KeyCode::Char('g') => row1 ^= 0b0001_0000,
|
||||||
Key::Y => row1 ^= 0b0000_1000,
|
KeyCode::Char('y') => row1 ^= 0b0000_1000,
|
||||||
Key::I => row1 ^= 0b0000_0100,
|
KeyCode::Char('i') => row1 ^= 0b0000_0100,
|
||||||
Key::P => row1 ^= 0b0000_0010,
|
KeyCode::Char('p') => row1 ^= 0b0000_0010,
|
||||||
Key::Enter => row1 ^= 0b0000_0001,
|
KeyCode::Enter => row1 ^= 0b0000_0001,
|
||||||
Key::LeftShift | Key::RightShift => row2 ^= 0b1000_0000,
|
KeyCode::Char('d') => row2 ^= 0b0100_0000,
|
||||||
Key::D => row2 ^= 0b0100_0000,
|
KeyCode::Char('v') => row2 ^= 0b0010_0000,
|
||||||
Key::V => row2 ^= 0b0010_0000,
|
KeyCode::Char('h') => row2 ^= 0b0001_0000,
|
||||||
Key::H => row2 ^= 0b0001_0000,
|
KeyCode::Char('k') => row2 ^= 0b0000_1000,
|
||||||
Key::K => row2 ^= 0b0000_1000,
|
KeyCode::Char('\'') => row2 ^= 0b0000_0100,
|
||||||
Key::Apostrophe => row2 ^= 0b0000_0100,
|
KeyCode::Char('/') => row2 ^= 0b0000_0010,
|
||||||
Key::Slash => row2 ^= 0b0000_0010,
|
KeyCode::Char('a') => row2 ^= 0b0000_0001,
|
||||||
Key::A => row2 ^= 0b0000_0001,
|
KeyCode::Char('z') => row3 ^= 0b0100_0000,
|
||||||
Key::LeftCtrl | Key::RightCtrl => row3 ^= 0b1000_0000,
|
KeyCode::Char('f') => row3 ^= 0b0010_0000,
|
||||||
Key::Z => row3 ^= 0b0100_0000,
|
KeyCode::Char('b') => row3 ^= 0b0001_0000,
|
||||||
Key::F => row3 ^= 0b0010_0000,
|
KeyCode::Char('j') => row3 ^= 0b0000_1000,
|
||||||
Key::B => row3 ^= 0b0001_0000,
|
KeyCode::Char('l') => row3 ^= 0b0000_0100,
|
||||||
Key::J => row3 ^= 0b0000_1000,
|
KeyCode::Char('2') => row3 ^= 0b0000_0010,
|
||||||
Key::L => row3 ^= 0b0000_0100,
|
KeyCode::Char('4') => row3 ^= 0b0000_0001,
|
||||||
Key::Key2 => row3 ^= 0b0000_0010,
|
KeyCode::Char('x') => row4 ^= 0b0100_0000,
|
||||||
Key::Key4 => row3 ^= 0b0000_0001,
|
KeyCode::Char('c') => row4 ^= 0b0010_0000,
|
||||||
Key::LeftAlt | Key::RightAlt => row4 ^= 0b1000_0000,
|
KeyCode::Char('n') => row4 ^= 0b0001_0000,
|
||||||
Key::X => row4 ^= 0b0100_0000,
|
KeyCode::Char('m') => row4 ^= 0b0000_1000,
|
||||||
Key::C => row4 ^= 0b0010_0000,
|
KeyCode::Char(',') => row4 ^= 0b0000_0100,
|
||||||
Key::N => row4 ^= 0b0001_0000,
|
KeyCode::Char('1') => row4 ^= 0b0000_0010,
|
||||||
Key::M => row4 ^= 0b0000_1000,
|
KeyCode::Char('3') => row4 ^= 0b0000_0001,
|
||||||
Key::Comma => row4 ^= 0b0000_0100,
|
KeyCode::Char(' ') => row5 ^= 0b0100_0000,
|
||||||
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(0x4400, row0);
|
||||||
|
@ -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);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
179
src/main.rs
179
src/main.rs
|
@ -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,29 +45,114 @@ struct Config {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main() -> Result<()> {
|
fn main() -> Result<()> {
|
||||||
|
let args: Vec<String> = env::args().collect();
|
||||||
|
|
||||||
|
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) => {
|
||||||
|
let mut string = String::new();
|
||||||
|
file.read_to_string(&mut string).unwrap();
|
||||||
|
toml::from_str(string.as_str()).unwrap()
|
||||||
|
}
|
||||||
|
Err(_) => {
|
||||||
|
println!("couldn't find a `george.toml` in the current directory!");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let rom = match std::fs::File::open(config.rom) {
|
||||||
|
Ok(file) => file,
|
||||||
|
Err(error) => panic!("Couldn't open main rom! {:?}", error),
|
||||||
|
};
|
||||||
|
if let Err(error) = memory.load_rom(rom) {
|
||||||
|
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 mut stdout = stdout();
|
||||||
let (cols, rows) = size()?;
|
let (cols, rows) = size()?;
|
||||||
execute!(stdout, SetSize(64, 29), cursor::Hide, EnterAlternateScreen)?;
|
execute!(stdout, SetSize(64, 29), cursor::Hide, EnterAlternateScreen)?;
|
||||||
enable_raw_mode()?;
|
enable_raw_mode()?;
|
||||||
|
|
||||||
let config: Config = match File::open("./config.toml") {
|
|
||||||
Ok(mut file) => {
|
|
||||||
let mut string = String::new();
|
|
||||||
file.read_to_string(&mut string).unwrap();
|
|
||||||
toml::from_str(string.as_str()).unwrap()
|
|
||||||
}
|
|
||||||
Err(_) => return Ok(()),
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut memory = Mem::new();
|
|
||||||
let rom = match std::fs::File::open(config.rom) {
|
|
||||||
Ok(file) => file,
|
|
||||||
Err(error) => panic!("Couldn't open main rom! {:?}", error),
|
|
||||||
};
|
|
||||||
if let Err(error) = memory.load_rom(rom) {
|
|
||||||
println!("{:?}", error);
|
|
||||||
};
|
|
||||||
|
|
||||||
memory
|
memory
|
||||||
.dump(PathBuf::from_str("./coredump.bin").unwrap())
|
.dump(PathBuf::from_str("./coredump.bin").unwrap())
|
||||||
.unwrap();
|
.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!(
|
||||||
|
|
|
@ -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());
|
||||||
}
|
}
|
||||||
|
pub fn poke(&self, address: u16) {
|
||||||
fn lock(&self) -> Result<MutexGuard<'_, Mem>> {
|
let memory = self.0.lock().unwrap();
|
||||||
match self.0.lock() {
|
memory.poke(address)
|
||||||
Ok(result) => Ok(result),
|
|
||||||
Err(error) => bail!("{error}"),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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);
|
||||||
// }
|
// }
|
||||||
|
|
|
@ -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.
|
@ -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
|
||||||
|
|
|
@ -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
|
41
src/video.rs
41
src/video.rs
|
@ -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();
|
// }
|
||||||
}
|
// }
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue