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]
anyhow = "1.0.81"
bdf = "0.6.0"
clap = { version = "4.5.4", features = ["derive"] }
criterion = "0.4"
crossterm = "0.27.0"
minifb = "0.25.0"
ratatui = "0.26.3"
# minifb = "0.25.0"
# ratatui = "0.26.3"
serde = { version = "1.0.197", features = ["serde_derive", "derive"] }
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
vasm6502_oldstyle ./src/roms/george.asm -dotdir -wdc02 -ldots -Fbin -o ./src/roms/george.rom;
cargo run;
if [ $# -eq 0 ]; then
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;

View File

@ -2,8 +2,8 @@ use crate::instructions::{get_instruction, Instruction};
use crate::memory::{MemHandle, MemoryReader, MemoryWriter};
use crate::types::{Byte, Word};
use std::fmt::Display;
use std::sync::mpsc::{Receiver, Sender};
use std::thread::sleep;
use std::sync::mpsc::{channel, Receiver, Sender};
use std::thread::{sleep, LocalKey};
use std::time::Duration;
use anyhow::Result;
@ -124,6 +124,13 @@ impl Cpu {
// 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<()> {
let reset_vector_pointer = self.read_word(0xFFFC)?;
self.pc = reset_vector_pointer;
@ -203,7 +210,11 @@ impl Cpu {
}
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 {
CpuControl::Nmi => self.nmi = true,
CpuControl::Irq => self.irq = true,
@ -226,13 +237,13 @@ impl Cpu {
}
pub fn cycle(&mut self) {
// self.receive_control();
self.receive_control();
if self.stopped {
return;
}
while self.pending_cycles != 0 {
// roughly cycle-accurate timing
sleep(Duration::from_nanos(100));
sleep(Duration::from_nanos(500));
self.pending_cycles -= 1;
}
if !self.get_flag(StatusFlag::IrqDisable) && self.irq {
@ -272,7 +283,6 @@ impl Cpu {
},
}
// self.cycle_count += 1;
sleep(Duration::from_nanos(100));
}
pub fn stop(&mut self) {
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};
@ -10,69 +11,64 @@ impl Keyboard {
pub fn new(memory: MemHandle) -> Self {
Self { memory }
}
}
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) {
pub fn read_keys(&self, key: KeyEvent) {
let mut row0 = 0;
let mut row1 = 0;
let mut row2 = 0;
let mut row3 = 0;
let mut row4 = 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 {
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,
_ => {}
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);
@ -83,3 +79,75 @@ impl InputCallback for Keyboard {
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 keyboard;
mod memory;
mod tui;
mod types;
mod video;
@ -14,41 +13,31 @@ use crate::keyboard::Keyboard;
use crate::memory::Mem;
use crate::video::{Screen, TerminalRenderer};
use cpu::{CpuController, CpuReceiver};
use crossterm::cursor::Hide;
use crossterm::execute;
use crossterm::terminal::{size, Clear, ClearType, SetSize};
// use cpu::CpuController;
use memory::MemHandle;
// use clap::Parser;
// use minifb::{Scale, ScaleMode, Window, WindowOptions};
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::{
fs::File,
io::{stdout, Read, Result},
path::PathBuf,
str::FromStr,
sync::mpsc,
thread,
};
use crossterm::{
cursor,
event::{self, KeyCode, KeyEventKind},
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)]
struct Config {
char_rom: Option<String>,
@ -56,29 +45,114 @@ struct Config {
}
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 (cols, rows) = size()?;
execute!(stdout, SetSize(64, 29), cursor::Hide, EnterAlternateScreen)?;
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
.dump(PathBuf::from_str("./coredump.bin").unwrap())
.unwrap();
@ -88,11 +162,8 @@ fn main() -> Result<()> {
let cpu_memory = shared_memory.clone();
let keyboard_memory = shared_memory.clone();
let (cpu_tx, cpu_rx) = mpsc::channel();
// let (state_tx, state_rx) = mpsc::channel();
let screen_cpu_tx = cpu_tx.clone();
// let (window_tx, window_rx) = mpsc::channel();
// For when we want to leave the terminal again :sigh:
//
// thread::spawn(move || {
// let mut screen = Crtc::new(
// screen_memory,
@ -120,40 +191,30 @@ fn main() -> Result<()> {
// )
// .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::new(cpu_memory);
// let mut tui = tui::App::new(
// CpuController::new(cpu_tx.clone()),
// shared_memory.clone(),
// state_rx,
// );
thread::spawn(move || {
cpu.reset().unwrap();
cpu.memory.write(0x4400, 0b0000_0100);
cpu.execute()
let (mut cpu, cpu_controller) = Cpu::new_with_control(cpu_memory);
let _ = cpu.reset();
thread::spawn(move || loop {
cpu.cycle();
});
let stdout_lock = stdout.lock();
let renderer = TerminalRenderer::new(screen_memory, stdout_lock);
let mut screen = Screen::new(CpuController::new(cpu_tx.clone()), renderer);
// thread::spawn(move || {
// screen.run();
// });
let mut screen = Screen::new(renderer, cpu_controller);
loop {
sleep(Duration::from_millis(16));
screen.draw();
if event::poll(std::time::Duration::from_millis(16))? {
if let event::Event::Key(key) = event::read()? {
keyboard.read_keys(key);
if key.kind == KeyEventKind::Press && key.code == KeyCode::Char('q') {
break;
}
}
}
// let buffer = window_rx.recv().unwrap();
screen.draw()
// tui.update(&mut terminal)?;
}
execute!(

View File

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

View File

@ -37,7 +37,7 @@ cleardisplay:
; cli
main:
jsr keyboard
jsr printtext
; key_zero:
; stz keyboard_cache, x
; dex
@ -45,164 +45,28 @@ main:
; fim:
; cli
; bra fim
; jsr kitty_keys
jmp main
; ; copying @smal rn: https://github.com/smaldragon/KittyEMU/blob/main/roms/foxmon.asm
; char_timer = $10
; line_buffer = $0200
; char_cur = $11
; line_buffer_i = $12
; line_buffer_l = $13
printtext:
ldx 0
loop:
lda text, x
beq end
sta $6000, x
inx
bra loop
end:
rts
; keyboard_cache = $14
; line_cur = $20
text:
.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:
ldy #0
x
not_keyboard:
ldy #0
.check_row: ; loop through each row
lda kb_row, y
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
lda kb_row, y
cmp kb_row_cache, y ; has key changed?

Binary file not shown.

View File

@ -1,26 +1,16 @@
; .setcpu "65C02"
.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
n = $01 ; temporary storage for data stack operations
reset:
sei
ldx #0; initialize data stack pointer
initdisplay:
lda #$20
lda #0
ldy #0
cleardisplay:
@ -37,56 +27,13 @@ cleardisplay:
cli
main:
jsr draw
; jmp draw
jmp cleardisplay
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:
push_coords #0, #0
push_char #$00
push_char #$af
jsr draw_char
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 -- )
jsr get_char_address
irq:
keyboard:
rts
isr: ; interrupt service routine
pha
phx
phy
jsr keyboard
jsr irq
ply
plx
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::{
cursor::{MoveTo, SavePosition},
cursor::{MoveTo, RestorePosition, SavePosition},
execute, queue,
style::{Color, PrintStyledContent, Stylize},
};
use crate::{
cpu::CpuController,
memory::{self, MemHandle, MemoryReader},
memory::{MemHandle, MemoryReader},
types::{Byte, Word},
};
use std::{
fs::File,
io::{Read, Stdout, StdoutLock, Write},
io::{Read, StdoutLock, Write},
path::Path,
process::exit,
sync::mpsc::Sender,
thread::sleep,
time::Duration,
time::Instant,
};
const FG_COLOR: u32 = 0xFFCC00;
@ -73,7 +73,7 @@ impl Renderer for WindowRenderer {
let mut buffer = vec![0; 512 * 380];
for char_row in 0..29 {
for char_col in 0..64 {
let ascii = self.memory.read(0x6000 + i);
let ascii = self.read(0x6000 + i);
i += 1;
for row in 0..13 {
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",
@ -137,11 +137,12 @@ static ASCII_LOOPUP: [&str; 256] = [
impl Renderer for TerminalRenderer<'_> {
fn render(&mut self) {
// let now = Instant::now();
let _ = execute!(self.stdout, SavePosition);
let mut i = 0;
for char_row in 0..29 {
for char_col in 0..64 {
let ascii = self.memory.read(0x6000 + i);
let ascii = self.read(0x6000 + i);
i += 1;
let char = ASCII_LOOPUP[ascii as usize];
let _ = queue!(
@ -165,32 +166,36 @@ impl Renderer for TerminalRenderer<'_> {
}
}
let _ = self.stdout.flush();
let _ = execute!(self.stdout, RestorePosition);
// let elapsed = now.elapsed();
// println!("{elapsed:?}");
// exit(0);
}
}
pub struct Screen<'a> {
cpu_controller: CpuController,
// renderer: Box<dyn Renderer>,
renderer: TerminalRenderer<'a>,
controller: CpuController,
}
impl<'a> Screen<'a> {
// 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 {
cpu_controller,
renderer,
controller,
}
}
pub fn draw(&mut self) {
self.controller.irq();
self.renderer.render();
}
pub fn run(&mut self) {
loop {
self.cpu_controller.irq();
sleep(Duration::from_millis(16));
self.draw();
}
}
// pub fn run(&mut self) {
// loop {
// // sleep(Duration::from_millis(16));
// self.draw();
// }
// }
}