Added interrupts, some restructuring & refactoring

This commit is contained in:
august kline 2024-04-10 17:28:34 -04:00
parent 705dcd3185
commit c154bdc89a
23 changed files with 1187 additions and 3916 deletions

1
.gitignore vendored
View File

@ -1,3 +1,4 @@
/target /target
log log
george.o george.o
.DS_Store

204
Cargo.lock generated
View File

@ -2,6 +2,60 @@
# It is not intended for manual editing. # It is not intended for manual editing.
version = 3 version = 3
[[package]]
name = "anstream"
version = "0.6.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d96bd03f33fe50a863e394ee9718a706f988b9079b20c3784fb726e7678b62fb"
dependencies = [
"anstyle",
"anstyle-parse",
"anstyle-query",
"anstyle-wincon",
"colorchoice",
"utf8parse",
]
[[package]]
name = "anstyle"
version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8901269c6307e8d93993578286ac0edf7f195079ffff5ebdeea6a59ffb7e36bc"
[[package]]
name = "anstyle-parse"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c75ac65da39e5fe5ab759307499ddad880d724eed2f6ce5b5e8a26f4f387928c"
dependencies = [
"utf8parse",
]
[[package]]
name = "anstyle-query"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e28923312444cdd728e4738b3f9c9cac739500909bb3d3c94b43551b16517648"
dependencies = [
"windows-sys 0.52.0",
]
[[package]]
name = "anstyle-wincon"
version = "3.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1cd54b81ec8d6180e24654d0b371ad22fc3dd083b6ff8ba325b72e00c87660a7"
dependencies = [
"anstyle",
"windows-sys 0.52.0",
]
[[package]]
name = "anyhow"
version = "1.0.81"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0952808a6c2afd1aa8947271f3a60f1a6763c7b912d210184c5149b5cf147247"
[[package]] [[package]]
name = "autocfg" name = "autocfg"
version = "1.1.0" version = "1.1.0"
@ -78,6 +132,52 @@ version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "clap"
version = "4.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "90bc066a67923782aa8515dbaea16946c5bcc5addbd668bb80af688e53e548a0"
dependencies = [
"clap_builder",
"clap_derive",
]
[[package]]
name = "clap_builder"
version = "4.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ae129e2e766ae0ec03484e609954119f123cc1fe650337e155d03b022f24f7b4"
dependencies = [
"anstream",
"anstyle",
"clap_lex",
"strsim",
]
[[package]]
name = "clap_derive"
version = "4.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "528131438037fd55894f62d6e9f068b8f45ac57ffa77517819645d10aed04f64"
dependencies = [
"heck",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "clap_lex"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "98cc8fbded0c607b7ba9dd60cd98df59af97e84d24e49c8557331cfc26d301ce"
[[package]]
name = "colorchoice"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7"
[[package]] [[package]]
name = "cty" name = "cty"
version = "0.2.2" version = "0.2.2"
@ -99,6 +199,12 @@ version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ea835d29036a4087793836fa931b08837ad5e957da9e23886b29586fb9b6650" checksum = "9ea835d29036a4087793836fa931b08837ad5e957da9e23886b29586fb9b6650"
[[package]]
name = "equivalent"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
[[package]] [[package]]
name = "errno" name = "errno"
version = "0.3.8" version = "0.3.8"
@ -214,9 +320,35 @@ dependencies = [
name = "georgeemu" name = "georgeemu"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"anyhow",
"bdf", "bdf",
"bitvec", "bitvec",
"clap",
"minifb", "minifb",
"serde",
"toml",
]
[[package]]
name = "hashbrown"
version = "0.14.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604"
[[package]]
name = "heck"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
[[package]]
name = "indexmap"
version = "2.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26"
dependencies = [
"equivalent",
"hashbrown",
] ]
[[package]] [[package]]
@ -460,24 +592,33 @@ dependencies = [
[[package]] [[package]]
name = "serde" name = "serde"
version = "1.0.196" version = "1.0.197"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "870026e60fa08c69f064aa766c10f10b1d62db9ccd4d0abb206472bee0ce3b32" checksum = "3fb1c873e1b9b056a4dc4c0c198b24c3ffa059243875552b2bd0933b1aee4ce2"
dependencies = [ dependencies = [
"serde_derive", "serde_derive",
] ]
[[package]] [[package]]
name = "serde_derive" name = "serde_derive"
version = "1.0.196" version = "1.0.197"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "33c85360c95e7d137454dc81d9a4ed2b8efd8fbe19cee57357b32b9771fccb67" checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn", "syn",
] ]
[[package]]
name = "serde_spanned"
version = "0.6.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eb3622f419d1296904700073ea6cc23ad690adbd66f13ea683df73298736f0c1"
dependencies = [
"serde",
]
[[package]] [[package]]
name = "slab" name = "slab"
version = "0.4.9" version = "0.4.9"
@ -493,6 +634,12 @@ version = "1.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6ecd384b10a64542d77071bd64bd7b231f4ed5940fba55e98c3de13824cf3d7" checksum = "e6ecd384b10a64542d77071bd64bd7b231f4ed5940fba55e98c3de13824cf3d7"
[[package]]
name = "strsim"
version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
[[package]] [[package]]
name = "syn" name = "syn"
version = "2.0.48" version = "2.0.48"
@ -543,12 +690,52 @@ dependencies = [
"syn", "syn",
] ]
[[package]]
name = "toml"
version = "0.8.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e9dd1545e8208b4a5af1aa9bbd0b4cf7e9ea08fabc5d0a5c67fcaafa17433aa3"
dependencies = [
"serde",
"serde_spanned",
"toml_datetime",
"toml_edit",
]
[[package]]
name = "toml_datetime"
version = "0.6.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3550f4e9685620ac18a50ed434eb3aec30db8ba93b0287467bca5826ea25baf1"
dependencies = [
"serde",
]
[[package]]
name = "toml_edit"
version = "0.22.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e40bb779c5187258fd7aad0eb68cb8706a0a81fa712fbea808ab43c4b8374c4"
dependencies = [
"indexmap",
"serde",
"serde_spanned",
"toml_datetime",
"winnow",
]
[[package]] [[package]]
name = "unicode-ident" name = "unicode-ident"
version = "1.0.12" version = "1.0.12"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
[[package]]
name = "utf8parse"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a"
[[package]] [[package]]
name = "version-compare" name = "version-compare"
version = "0.1.1" version = "0.1.1"
@ -858,6 +1045,15 @@ version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04"
[[package]]
name = "winnow"
version = "0.6.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dffa400e67ed5a4dd237983829e66475f0a4a26938c4b04c21baede6262215b8"
dependencies = [
"memchr",
]
[[package]] [[package]]
name = "wyz" name = "wyz"
version = "0.5.1" version = "0.5.1"

View File

@ -6,6 +6,10 @@ edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies] [dependencies]
anyhow = "1.0.81"
bdf = "0.6.0" bdf = "0.6.0"
bitvec = "1.0.1" bitvec = "1.0.1"
clap = { version = "4.5.4", features = ["derive"] }
minifb = "0.25.0" minifb = "0.25.0"
serde = { version = "1.0.197", features = ["serde_derive", "derive"] }
toml = "0.8.12"

View File

@ -9,3 +9,15 @@ plenty of inspiration & guidance taken from [emulator_6502](https://docs.rs/emul
see [the george wiki](https://git.augustkline.com/august/george/wiki) for how george works, why she exists, who she is, etc. see [the george wiki](https://git.augustkline.com/august/george/wiki) for how george works, why she exists, who she is, etc.
the george emulator contains 3 main structs: `Cpu`, `Mem`, and `MemMappedDevice`. `Cpu` represents a 65c02 processor, with functions for interacting with memory and executing instructions. `Mem` is a collection of `MemMappedDevice`'s, which each hold the data for some address space. `Mem` can add `MemMappedDevice`'s, and read from and write to them. A `MemMappedDevice` can have multiple banks at the same address space, and can translate 'global' addresses to 'local' ones (e.g. 0xFFFF in the system's [memory map](https://git.augustkline.com/august/george-hardware/src/branch/main/memory.md) corresponds to 0x1FFF in the ROM). the george emulator contains 3 main structs: `Cpu`, `Mem`, and `MemMappedDevice`. `Cpu` represents a 65c02 processor, with functions for interacting with memory and executing instructions. `Mem` is a collection of `MemMappedDevice`'s, which each hold the data for some address space. `Mem` can add `MemMappedDevice`'s, and read from and write to them. A `MemMappedDevice` can have multiple banks at the same address space, and can translate 'global' addresses to 'local' ones (e.g. 0xFFFF in the system's [memory map](https://git.augustkline.com/august/george-hardware/src/branch/main/memory.md) corresponds to 0x1FFF in the ROM).
## font generation
george uses a modified version of [cozette](https://github.com/slavfox/Cozette) for her main font. the modified version has 8 pixel wide characters for use with the character generator rom, and has a limit of 256 (0xFF) characters. the toolchain to generate a rom binary is still pretty clunky, and someday i might get around to streamlining it, but for now to make any changes:
- open `./src/Cozette.sfd` in fontforge
- make your edits
- to reorder glyphs, edit `./src/georgeencoding.txt` and load it with "Encoding -> Load Encoding -> (select georgeencoding.txt, name it whatever) -> Reencode -> (name of encoding)"
- ensure that there are exactly 256 characters in the font
- generate a bdf font with "File -> Generate Fonts... -> (name of the font, select bdf in the options panel)"
- open the generated bdf file in a text editor and change the line `FONTBOUNDINGBOX 11 13 0 -3` to `FONTBOUNDINGBOX 8 13 0 -3`
- now open the font in [bdf view](https://emurenmrz.github.io/bdf_view/), and export a single-row png

2
config.toml Normal file
View File

@ -0,0 +1,2 @@
char_rom = "./src/roms/cozette.rom"
rom = "./src/roms/george.rom"

5
run.sh Executable file
View File

@ -0,0 +1,5 @@
#!/usr/bin/env bash
vasm6502_oldstyle ./src/george.asm -dotdir -wdc02 -ldots -Fbin -o ./src/george.rom;
cargo run;
# hexdump -C ./cpu_dump.bin;

File diff suppressed because it is too large Load Diff

Binary file not shown.

View File

@ -1,19 +1,23 @@
use crate::error::{ExecutionError, GeorgeError, GeorgeErrorKind, MemoryError}; // use crate::error::{ExecutionError, GeorgeError, GeorgeErrorKind, MemoryError};
use crate::instructions::{get_instruction, Instruction}; use crate::instructions::{get_instruction, Instruction};
use crate::memory::Mem; use crate::memory::Mem;
use crate::types::{Byte, Word}; use crate::types::{Byte, Word};
use std::path::PathBuf; use std::path::PathBuf;
use std::process::exit; use std::str::FromStr;
use std::sync::mpsc::Receiver;
use std::sync::Arc;
use std::sync::Mutex; use std::sync::Mutex;
use std::thread::sleep;
use std::time::Duration; use std::time::Duration;
use std::{str::FromStr, sync::Arc, thread::sleep};
use anyhow::{bail, Result};
#[derive(Clone, Copy)] #[derive(Clone, Copy)]
pub enum StatusFlag { pub enum StatusFlag {
Negative = 0b1000_0000, Negative = 0b1000_0000,
Overflow = 0b0100_0000, Overflow = 0b0100_0000,
Brk = 0b0011_0000, Brk = 0b0011_0000,
BrkIrq = 0b0010_0000, //BrkIrq = 0b0010_0000,
Decimal = 0b0000_1000, Decimal = 0b0000_1000,
IrqDisable = 0b0000_0100, IrqDisable = 0b0000_0100,
Zero = 0b000_0010, Zero = 0b000_0010,
@ -27,7 +31,7 @@ pub struct Cpu {
pub pc: Word, // Program Counter pub pc: Word, // Program Counter
pub s: Byte, // Stack Pointer pub s: Byte, // Stack Pointer
pub p: Byte, // Status Register pub p: Byte, // Status Register
pub irq: bool, pub irq: Receiver<bool>,
pub nmi: bool, pub nmi: bool,
pub memory: Arc<Mutex<Mem>>, pub memory: Arc<Mutex<Mem>>,
pub pending_cycles: usize, pub pending_cycles: usize,
@ -35,7 +39,7 @@ pub struct Cpu {
} }
impl Cpu { impl Cpu {
pub fn new(memory: Arc<Mutex<Mem>>) -> Self { pub fn new(memory: Arc<Mutex<Mem>>, irq: Receiver<bool>) -> Self {
Cpu { Cpu {
a: 0x00, a: 0x00,
x: 0x00, x: 0x00,
@ -43,30 +47,29 @@ impl Cpu {
pc: 0x0000, pc: 0x0000,
s: 0xFF, s: 0xFF,
p: 0b0010_0100, p: 0b0010_0100,
irq: false, irq,
nmi: false, nmi: false,
memory, memory,
pending_cycles: 0, pending_cycles: 0,
cycle_count: 0, cycle_count: 0,
} }
} }
pub fn reset(&mut self) -> Result<(), ExecutionError> { 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;
self.pending_cycles = 8; self.pending_cycles = 0;
Ok(()) Ok(())
} }
pub fn read(&self, address: Word) -> Result<Byte, MemoryError> { pub fn read(&self, address: Word) -> Result<Byte> {
let memory = match self.memory.lock() { let memory = match self.memory.lock() {
Ok(read) => read, Ok(read) => read,
Err(_) => { Err(_) => {
println!("Couldn't acquire read lock on memory in cpu thread"); bail!("Couldn't acquire lock on memory in cpu thread")
return Err(MemoryError::NoDataAtAddress); }
} // TODO: upgrade this error type to a `GeorgeError` and make an errorkind for thread lock errors
}; };
Ok(memory.read(address)) Ok(memory.read(address))
} }
pub fn read_word(&self, address: Word) -> Result<Word, MemoryError> { pub fn read_word(&self, address: Word) -> Result<Word> {
let low_byte = self.read(address)?; let low_byte = self.read(address)?;
let high_byte = self.read(address + 0x1)?; let high_byte = self.read(address + 0x1)?;
Ok((high_byte as u16) << 8 | (low_byte as u16)) Ok((high_byte as u16) << 8 | (low_byte as u16))
@ -77,27 +80,27 @@ impl Cpu {
0x0100 + self.s as u16 0x0100 + self.s as u16
} }
pub fn push_stack(&mut self, data: Byte) -> Result<(), ExecutionError> { pub fn push_stack(&mut self, data: Byte) -> Result<()> {
self.s -= 0x1; self.s = self.s.wrapping_sub(0x1);
self.write(self.stack_addr(), data)?; self.write(self.stack_addr(), data)?;
Ok(()) Ok(())
} }
pub fn push_stack_word(&mut self, address: Word) -> Result<(), ExecutionError> { pub fn push_stack_word(&mut self, address: Word) -> Result<()> {
self.s -= 0x1; self.s = self.s.wrapping_sub(0x1);
self.write(self.stack_addr(), address.to_le_bytes()[1])?; // Upper byte first self.write(self.stack_addr(), address.to_le_bytes()[1])?; // Upper byte first
self.s -= 0x1; self.s = self.s.wrapping_sub(0x1);
self.write(self.stack_addr(), address.to_le_bytes()[0])?; // Lower byte second self.write(self.stack_addr(), address.to_le_bytes()[0])?; // Lower byte second
Ok(()) Ok(())
} }
pub fn pop_stack(&mut self) -> Result<Byte, ExecutionError> { pub fn pop_stack(&mut self) -> Result<Byte> {
let byte = self.read(self.stack_addr())?; let byte = self.read(self.stack_addr())?;
self.s = self.s.wrapping_add(0x1); self.s = self.s.wrapping_add(0x1);
Ok(byte) Ok(byte)
} }
pub fn pop_stack_word(&mut self) -> Result<Word, ExecutionError> { pub fn pop_stack_word(&mut self) -> Result<Word> {
let low_byte = self.pop_stack()?; let low_byte = self.pop_stack()?;
let high_byte = self.pop_stack()?; let high_byte = self.pop_stack()?;
let word = ((high_byte as Word) << 8) + low_byte as u16; let word = ((high_byte as Word) << 8) + low_byte as u16;
@ -114,17 +117,17 @@ impl Cpu {
pub fn get_flag(&self, flag: StatusFlag) -> bool { pub fn get_flag(&self, flag: StatusFlag) -> bool {
(self.p & flag as Byte) > 0 (self.p & flag as Byte) > 0
} }
pub fn is_negative(&self, value: Byte) -> bool { pub fn is_negative(&self, value: Byte) -> bool {
value & 0b1000_0000 == 0b1000_0000 value & 0b1000_0000 == 0b1000_0000
} }
pub fn write(&mut self, address: Word, data: Byte) -> Result<(), MemoryError> { pub fn write(&mut self, address: Word, data: Byte) -> Result<()> {
let mut memory = match self.memory.lock() { let mut memory = match self.memory.lock() {
Ok(write) => write, Ok(write) => write,
Err(_) => { Err(_) => {
println!("Couldn't acquire write lock on memory in cpu thread"); bail!("Couldn't acquire write lock on memory in cpu thread")
return Err(MemoryError::NoDataAtAddress); }
} // TODO: upgrade this error type to a `GeorgeError` and make an errorkind for thread lock errors
}; };
memory.write(address, data); memory.write(address, data);
Ok(()) Ok(())
@ -141,74 +144,26 @@ impl Cpu {
unimplemented!() unimplemented!()
} }
// fn handle_interrupt(&mut self) -> Result<(), ExecutionError> { pub fn interrupt(&mut self) {
// match self.get_flag(StatusFlag::IrqDisable) { self.push_stack_word(self.pc).unwrap();
// // Check that interrupts aren't disabled self.push_stack(self.p).unwrap();
// true => Ok(self.execute()),
// false => {
// if self.irq {
// let irq_vector = 0xFFFE;
// match self.read_word(irq_vector) {
// Err(error) => Err(ExecutionError::MemoryError(error)),
// Ok(address) => {
// let isr_address = address;
// match self.read(isr_address) {
// Ok(_opcode) => Ok(self.execute()),
// Err(error) => Err(ExecutionError::MemoryError(error)),
// }
// }
// }
// } else {
// Ok(())
// }
// }
// }
// }
pub fn cycle(&mut self) {
sleep(Duration::from_nanos(100));
if self.pending_cycles == 0 {
if self.nmi || (self.irq && !self.get_flag(StatusFlag::IrqDisable)) {
let _ = self.push_stack_word(self.pc);
self.set_flag(StatusFlag::BrkIrq, true);
let _ = self.push_stack(self.p);
self.set_flag(StatusFlag::IrqDisable, true); self.set_flag(StatusFlag::IrqDisable, true);
if self.nmi { self.pc = self.read_word(0xFFFE).unwrap();
match self.read_word(0xFFFA) {
Ok(word) => self.pc = word,
Err(error) => {
let george_error = GeorgeError {
kind: GeorgeErrorKind::Memory(error),
desc: String::from_str("Couldn't read NMI vector").unwrap(),
};
println!("{:?}", george_error.desc);
} }
};
self.pending_cycles = 8; pub fn cycle(&mut self) {
} else { while self.pending_cycles != 0 {
match self.read_word(0xFFFE) { // roughly cycle-accurate timing
Ok(word) => self.pc = word, sleep(Duration::from_nanos(100));
Err(error) => { self.pending_cycles -= 1;
let george_error = GeorgeError {
kind: GeorgeErrorKind::Memory(error),
desc: String::from_str("Couldn't read IRQ vector").unwrap(),
};
println!("{:?}", george_error.desc);
} }
}; if !self.get_flag(StatusFlag::IrqDisable) && self.irq.recv().unwrap() {
self.pending_cycles = 7 self.interrupt();
} }
self.nmi = false;
self.irq = false;
}
} else {
let opcode = match self.read(self.pc) { let opcode = match self.read(self.pc) {
Ok(byte) => byte, Ok(byte) => byte,
Err(error) => { Err(_) => {
let george_error = GeorgeError { println!("Failed to read from memory at address {:#06x}!", self.pc);
desc: format!("Failed to read from memory at address {:#06x}!", self.pc),
kind: GeorgeErrorKind::Memory(error),
};
println!("{:?}", george_error.desc);
return; return;
} }
}; };
@ -226,11 +181,8 @@ impl Cpu {
Ok(_) => { Ok(_) => {
self.pending_cycles += valid_instruction.cycles as usize; self.pending_cycles += valid_instruction.cycles as usize;
} }
Err(error) => { Err(_) => {
let george_error = GeorgeError{ println!("An IncompatibleAddrMode was used at address {:#06x} for instruction {:?}", self.pc, valid_instruction);
desc: format!("An IncompatibleAddrMode was used at address {:#06x} for instruction {:?}",
self.pc, valid_instruction).to_string(), kind: error};
println!("{:?}", george_error.desc);
} }
}; };
} }
@ -240,36 +192,23 @@ impl Cpu {
Ok(read) => read, Ok(read) => read,
Err(_) => { Err(_) => {
println!("Couldn't acquire read lock on memory in cpu thread"); println!("Couldn't acquire read lock on memory in cpu thread");
let george_error = GeorgeError {
kind: GeorgeErrorKind::Memory(MemoryError::Unwritable),
desc: "Couldn't acquire read lock on memory in cpu thread"
.to_string(),
};
println!("{:?}", george_error.desc);
return; return;
} }
}; };
println!("a: {a:#04x}, x: {x:#04x}, y: {y:#04x}, pc: {pc:#06x}, sp: {s:#04x}, sr: {p:#010b}, irq: {irq:?}, nmi: {nmi:?}", a = self.a, x = self.x, y = self.y, pc = self.pc, s = self.s, p = self.p, irq = self.irq, nmi = self.nmi); println!("a: {a:#04x}, x: {x:#04x}, y: {y:#04x}, pc: {pc:#06x}, sp: {s:#04x}, sr: {p:#010b}, irq: {irq:?}, nmi: {nmi:?}", a = self.a, x = self.x, y = self.y, pc = self.pc, s = self.s, p = self.p, irq = self.irq.try_recv().unwrap_or_default(), nmi = self.nmi);
memory memory
.dump(PathBuf::from_str("./cpu_dump.bin").unwrap()) .dump(PathBuf::from_str("./cpu_dump.bin").unwrap())
.unwrap(); .unwrap();
exit(1);
} }
_ => { _ => {
let george_error = GeorgeError { println!(
kind: GeorgeErrorKind::Execution(ExecutionError::InvalidInstruction),
desc: format!(
"An invalid instruction with opcode {:#04x} was called at address {:#06x}", "An invalid instruction with opcode {:#04x} was called at address {:#06x}",
invalid_instruction.opcode, self.pc invalid_instruction.opcode, self.pc
), );
};
println!("{:?}", george_error.desc);
} }
}, },
} }
}
self.pending_cycles -= 1;
self.cycle_count += 1; self.cycle_count += 1;
} }

Binary file not shown.

View File

@ -1,240 +0,0 @@
.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
kb_row0 = $4400 ; keyboard hardware register, there are 5 more but i can just increment from here
.segment "ROM"
reset:
ldx #0; initialize data stack pointer
;lda #$80
;sta $200
;adc $200
;breakpoint
initdisplay:
lda #$20
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
main:
;jsr draw
jsr keyboard
jmp main
keyboard:
ldy #0 ; loop through each row
@loop:
lda kb_row0, y
beq @skip ; if row has no key pressed, skip checking which key
jsr key_pressed
@skip:
iny
cpy #5
bne @loop
rts
key_pressed: ; 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 set, go store character column
iny
cpy #8
bne @find_col ; loop until we've checked each bit
store_col:
sty key_col
keymap_index:
push
lda key_col
stz 1, x
sta 0, x
push
lda #8
stz 1, x
sta 0, x
push
lda key_row
stz 1, x
sta 0, x
jsr mult
jsr plus
lda 0, x
tay
do_something_w_key: ; we've stored the character position, now let's
lda keymap, y
ldy cursor
sta $6000, y
pla
ply
rts
keymap:
.byte "?outrew?"
.byte "?piygsq?"
.byte "a??khvd?"
.byte "42ljbfz?"
.byte "31?mncx?"
.byte "?????ssm"
draw:
; lda #%01000000
; bit $4400
; beq @1
; push_coords #0, #0
; push_char #$77
; jsr draw_char
; @1:
; lda #%00100000
; bit $4400
; beq @2
; push_coords #0, #0
; push_char #$65
; jsr draw_char
; @2:
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
; --- Data Stack --- ;
; on this channel we love garth wilson: https://wilsonminesco.com/stacks/StackOps.ASM
; data stack is built up of 2-byte cells
; TODO: this is broken, jumping here does nothing to the stack and skips several instructions, could be an emulator problem tho
push_lit: ; this bad boy lets you inline a literal (low byte first) right after `jsr push_lit` and put it on the stack, once again, on this channel we love garth wilson
push2
phx
tsx
txa
tay
plx
lda $102, y
sta 0, x
clc
adc #2
sta $102, y
lda $103, y
sta 1, x
adc #0
sta $103, y
fetch:
lda (0, x)
pha
inc 0, x
bne @1
inc 1, x
@1: lda (0, x)
bra put
push
put:
sta 1, x
pla
sta 0, x
rts
plus: ; add: (n1 n2 -- n1+n2)
clc
lda 0, x
adc 2, x
sta 2, x
lda 1, x
adc 3, x
sta 3, x
pop
rts
mult: ; multiply: (n1 n2 -- n1*n2), frankly, i don't know how this works, but TODO: will try to figure it out later
phy
stz n
ldy #0
@1: lsr 3, x
ror 2, x
bcc @2
clc
lda n
adc 0, x
sta n
tya
adc 1, x
tay
@2: asl 0, x
rol 1, x
lda 2, x
ora 3, x
bne @1
lda n
sta 2, x
sty 3, x
pop
ply
rts
.segment "VECTOR"
.word reset

View File

@ -1,10 +0,0 @@
MEMORY {
RAM: start = $0000, size = $8000, type = ro, fill = yes;
ROM: start = $8000, size = $7FFC, type = ro, fill = yes;
VECTOR: start = $FFFC, size = $4, type = ro, fill = yes;
}
SEGMENTS {
ROM: load = "ROM", type = ro;
VECTOR: load = "VECTOR", type = ro;
}

File diff suppressed because it is too large Load Diff

92
src/keyboard.rs Normal file
View File

@ -0,0 +1,92 @@
use minifb::{InputCallback, Key};
use std::{
path::PathBuf,
process::exit,
str::FromStr,
sync::{Arc, Mutex},
};
use crate::memory::Mem;
pub struct Keyboard {
memory: Arc<Mutex<Mem>>,
}
impl Keyboard {
pub fn new(memory: Arc<Mutex<Mem>>) -> Self {
Self { memory }
}
}
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,
_ => {}
};
match &mut self.memory.lock() {
Ok(memory) => {
memory.write(0x4400, row0);
memory.write(0x4401, row1);
memory.write(0x4402, row2);
memory.write(0x4403, row3);
memory.write(0x4404, row4);
memory.write(0x4405, row5);
}
Err(_error) => {
println!("couldnt get a lock on memory while saving keyboard registers");
}
}
}
}

View File

@ -9,27 +9,58 @@ mod types;
mod video; mod video;
use crate::cpu::Cpu; use crate::cpu::Cpu;
use crate::keyboard::Keyboard;
use crate::memory::Mem; use crate::memory::Mem;
use crate::video::Crtc; use crate::video::Crtc;
use clap::Parser;
use minifb::{Scale, ScaleMode, Window, WindowOptions};
use serde::{Deserialize, Serialize};
use std::fs::File;
use std::io::Read;
use std::process::exit;
use std::str::FromStr; use std::str::FromStr;
use std::sync::Mutex; use std::sync::{mpsc, Mutex};
use std::thread::sleep;
use std::time::Duration;
use std::{path::PathBuf, sync::Arc, thread}; use std::{path::PathBuf, sync::Arc, thread};
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>,
rom: String,
}
fn main() { fn main() {
let mut memory = Mem::new(); let config: Config = match File::open("./config.toml") {
let binary = match std::fs::File::open(PathBuf::from( Ok(mut file) => {
"/Users/kline/projects/winter/george-emu/src/george", let mut string = String::new();
)) { file.read_to_string(&mut string).unwrap();
Ok(file) => file, toml::from_str(string.as_str()).unwrap()
Err(error) => panic!("Couldn't open binary file! {:?}", error), }
Err(_) => return,
}; };
if let Err(error) = memory.read_from_bin(binary) { println!("{config:#?}");
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); println!("{:?}", error);
}; };
let (interrupt_tx, interrupt_rx) = mpsc::channel();
let (window_tx, window_rx) = mpsc::channel();
memory memory
.dump(PathBuf::from_str("./coredump.bin").unwrap()) .dump(PathBuf::from_str("./coredump.bin").unwrap())
.unwrap(); .unwrap();
@ -37,12 +68,45 @@ fn main() {
let shared_memory = Arc::new(Mutex::new(memory)); let shared_memory = Arc::new(Mutex::new(memory));
let cpu_memory = shared_memory.clone(); let cpu_memory = shared_memory.clone();
let display_memory = shared_memory.clone(); let display_memory = shared_memory.clone();
let keyboard_memory = shared_memory.clone();
thread::spawn(move || { thread::spawn(move || {
let mut cpu = Cpu::new(cpu_memory); let mut cpu = Cpu::new(cpu_memory, interrupt_rx);
cpu.reset().unwrap(); cpu.reset().unwrap();
cpu.execute(); cpu.execute();
}); });
let mut screen = Crtc::new(display_memory);
thread::spawn(move || {
let mut screen = Crtc::new(
display_memory,
config.char_rom.as_ref(),
interrupt_tx,
window_tx,
);
screen.run(); screen.run();
});
let mut window = Window::new(
"ʕ·ᴥ·ʔ-☆",
512,
380,
WindowOptions {
resize: true,
borderless: true,
title: true,
transparency: false,
scale: Scale::X2,
scale_mode: ScaleMode::AspectRatioStretch,
topmost: false,
none: true,
},
)
.unwrap();
window.set_input_callback(Box::new(Keyboard::new(keyboard_memory)));
while window.is_open() {
let buffer = window_rx.recv().unwrap();
window.update_with_buffer(&buffer, 512, 380).unwrap();
}
} }

View File

@ -30,17 +30,30 @@ impl Mem {
self.data[address as usize] = data; self.data[address as usize] = data;
} }
pub fn read_from_bin(&mut self, f: File) -> Result<(), MemoryError> { pub fn load_rom(&mut self, rom: File) -> Result<(), MemoryError> {
let bytes = f.bytes(); let bytes = rom.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 Word + 0x8000, value),
Err(_) => { Err(_) => {
println!("couldn't write byte {:#04x}", address); println!("Loading rom: couldn't write byte {:#04x}", address);
return Err(MemoryError::Unwritable); return Err(MemoryError::Unwritable);
} }
} }
} }
Ok(()) Ok(())
} }
//pub fn read_from_bin(&mut self, f: File) -> Result<(), MemoryError> {
// let bytes = f.bytes();
// for (address, byte) in bytes.enumerate() {
// match byte {
// Ok(value) => self.write(address as Word, value),
// Err(_) => {
// println!("couldn't write byte {:#04x}", address);
// return Err(MemoryError::Unwritable);
// }
// }
// }
// Ok(())
//}
} }

BIN
src/roms/cozette.rom Normal file

Binary file not shown.

164
src/roms/george.asm Normal file
View File

@ -0,0 +1,164 @@
; .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
reset:
sei
ldx #0; initialize data stack pointer
initdisplay:
lda #$20
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:
jsr draw
jmp main
keyboard:
ldy #0
.loop: ; loop through each row
lda kb_row, y
beq .skip ; if row has no key pressed, skip checking which key
sta kb_row_cache, y ; if key pressed, cache it
lda kb_row, y
cmp kb_row_cache, y ; has key changed?
beq key_down
.skip:
iny
cpy #5
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
store_col:
sty key_col
keymap_index:
push
lda key_col
stz 1, x
sta 0, x
push
lda #8
stz 1, x
sta 0, x
push
lda key_row
stz 1, x
sta 0, x
jsr mult
jsr plus
lda 0, x
tay
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:
.byte "?outrew?"
.byte "?piygsq?"
.byte "a??khvd?"
.byte "42ljbfz?"
.byte "31?mncx?"
.byte "????? m"
draw:
push_coords #0, #0
push_char #$00
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 keyboard
ply
plx
pla
rti
.include "math.inc"
.org $fffc
.word reset
.word isr

BIN
src/roms/george.rom Normal file

Binary file not shown.

142
src/roms/keyboard.asm Normal file
View File

@ -0,0 +1,142 @@
; .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
reset:
sei
ldx #0; initialize data stack pointer
initdisplay:
lda #$20
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:
jsr draw
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
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 keyboard
ply
plx
pla
rti
.include "math.inc"
.org $fffc
.word reset
.word isr

62
src/roms/macro.inc Normal file
View File

@ -0,0 +1,62 @@
.macro breakpoint ; $02 isn't a valid instruction, the emulator will see this and halt, dump memory contents
.byte $02
.endm
.macro pop ; drops a data stack cell
inx
inx
.endm
.macro pop2 ; drops 2 data stack cells
inx
inx
inx
inx
.endm
.macro push ; push a data stack cell
dex
dex
.endm
.macro push2 ; push 2 data stack cells
dex
dex
dex
dex
.endm
.macro push_char, char ; pushes an ascii character code onto the stack
lda \char
push
sta 0, x ; char low byte
stz 1, x ; char high byte
.endm
.macro push_coords, coord_x, coord_y ; push a set of (x,y) coordinates onto the data stack
lda \coord_x
push
sta 0, x ; low byte
stz 1,x ; high byte is zero
lda \coord_y
push
sta 0,x ; same here
stz 1,x
.endm
.macro to_r ; pop the top of the stack off and save it in the return (hardware) stack: (n -- )
lda 1, x
pha
lda 0, x
pha
pop
.endm
.macro from_r ; pop the top of the return stack off and put it on the data stack: ( -- n)
push
pla
sta 0, x
pla
sta 1, x
.endm

81
src/roms/math.inc Normal file
View File

@ -0,0 +1,81 @@
; --- Data Stack --- ;
; on this channel we love garth wilson: https://wilsonminesco.com/stacks/StackOps.ASM
; data stack is built up of 2-byte cells
; TODO: this is broken, jumping here does nothing to the stack and skips several instructions, could be an emulator problem tho
; push_lit: ; this bad boy lets you inline a literal (low byte first) right after `jsr push_lit` and put it on the stack, once again, on this channel we love garth wilson
; push2
; phx
; tsx
; txa
; tay
; plx
; lda $102, y
; sta 0, x
; clc
; adc #2
; sta $102, y
; lda $103, y
; sta 1, x
; adc #0
; sta $103, y
; fetch:
; lda (0, x)
; pha
; inc 0, x
; bne .1
; inc 1, x
; .1:
; lda (0, x)
; bra put
; push
; put:
; sta 1, x
; pla
; sta 0, x
; rts
plus: ; add: (n1 n2 -- n1+n2)
clc
lda 0, x
adc 2, x
sta 2, x
lda 1, x
adc 3, x
sta 3, x
pop
rts
mult: ; multiply: (n1 n2 -- n1*n2), frankly, i don't know how this works, but TODO: will try to figure it out later
phy
stz n
ldy #0
.1:
lsr 3, x
ror 2, x
bcc .2
clc
lda n
adc 0, x
sta n
tya
adc 1, x
tay
.2:
asl 0, x
rol 1, x
lda 2, x
ora 3, x
bne .1
lda n
sta 2, x
sty 3, x
pop
ply
rts

View File

@ -1,9 +1,12 @@
use crate::Mem; use crate::Mem;
use minifb::{Key, Scale, ScaleMode, Window, WindowOptions};
use std::{ use std::{
fs::File, fs::File,
io::Read, io::Read,
sync::{Arc, Mutex}, path::Path,
sync::{
mpsc::{Sender, SyncSender},
Arc, Mutex,
},
thread::sleep, thread::sleep,
time::{Duration, Instant}, time::{Duration, Instant},
}; };
@ -14,41 +17,44 @@ const BG_COLOR: u32 = 0x110500;
pub struct Crtc { pub struct Crtc {
memory: Arc<Mutex<Mem>>, memory: Arc<Mutex<Mem>>,
buffer: Vec<u32>, buffer: Vec<u32>,
window: minifb::Window,
char_rom: Vec<u8>, char_rom: Vec<u8>,
interrupt: Sender<bool>,
window: Sender<Vec<u32>>,
} }
pub fn get_char_bin(path: &str) -> Vec<u8> { pub fn get_char_bin<P>(char_rom: Option<P>) -> Vec<u8>
where
P: AsRef<Path>,
{
match char_rom {
Some(path) => {
let mut file = File::open(path).unwrap(); let mut file = File::open(path).unwrap();
let mut buffer = vec![0; 0xFFFF]; let mut bin = vec![0; 0x8000];
file.read_exact(&mut buffer).unwrap(); file.read_exact(&mut bin).unwrap();
buffer println!("reading char rom");
bin
}
None => include_bytes!("./roms/cozette.rom").to_vec(),
}
} }
impl Crtc { impl Crtc {
pub fn new(memory: Arc<Mutex<Mem>>) -> Self { pub fn new<P>(
let window = Window::new( memory: Arc<Mutex<Mem>>,
"ʕ·ᴥ·ʔ-☆", char_rom: Option<P>,
512, interrupt: Sender<bool>,
380, window: Sender<Vec<u32>>,
WindowOptions { ) -> Self
resize: true, where
borderless: true, P: AsRef<Path>,
title: true, {
transparency: false, let char_rom = get_char_bin(char_rom);
scale: Scale::X2,
scale_mode: ScaleMode::AspectRatioStretch,
topmost: false,
none: true,
},
)
.unwrap();
let char_rom = get_char_bin("./src/cozette.bin");
Self { Self {
memory, memory,
buffer: vec![0; 512 * 380], buffer: vec![0; 512 * 380],
window, window,
char_rom, char_rom,
interrupt,
} }
} }
fn draw(&mut self) { fn draw(&mut self) {
@ -59,9 +65,6 @@ impl Crtc {
return; return;
} }
}; };
let hw_ctrl = memory.read(0x4000);
match hw_ctrl & 0b0000_1000 == 0b0000_1000 {
false => {
// the rest of this function is arcane wizardry based on the specifics of george's weird // the rest of this function is arcane wizardry based on the specifics of george's weird
// display and characters... don't fuck around w it // display and characters... don't fuck around w it
let mut i = 0; let mut i = 0;
@ -70,10 +73,9 @@ impl Crtc {
let ascii = memory.read(0x6000 + i); let ascii = memory.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 * 0x101)]; let byte = self.char_rom[ascii as usize + (row * 0x100)];
for i in (0..8).rev() { for i in (0..8).rev() {
let buffer_index = let buffer_index = ((char_row) * 13 + (row)) * 512 + (char_col * 8 + i);
((char_row) * 13 + (row)) * 512 + (char_col * 8 + i);
if (byte << i) & 0x80 == 0x80 { if (byte << i) & 0x80 == 0x80 {
self.buffer[buffer_index] = FG_COLOR; self.buffer[buffer_index] = FG_COLOR;
} else { } else {
@ -83,103 +85,18 @@ impl Crtc {
} }
} }
} }
}
true => {
for addr in 0x6000..0xBF00 {
// TODO: eventually this will access memory in the weird interleaved way the hardware is
// designed, but this is easiest to implement for now
let byte = memory.read(addr);
for i in 0..8 {
match byte & 0x80 >> i == 0 {
true => self.buffer[(addr - 0x6000) as usize * 8 + i] = BG_COLOR,
false => self.buffer[(addr - 0x6000) as usize * 8 + i] = FG_COLOR,
}
}
}
}
};
self.window let buffer = self.buffer.to_owned();
.update_with_buffer(&self.buffer, 512, 380)
.unwrap(); self.window.send(buffer).unwrap();
} }
pub fn run(&mut self) { pub fn run(&mut self) {
let frame_duration = Duration::from_millis(16);
let mut previous_draw = Instant::now();
loop { loop {
let mut row0 = 0; self.interrupt.send(false).unwrap();
let mut row1 = 0; sleep(Duration::from_millis(16));
let mut row2 = 0;
let mut row3 = 0;
let mut row4 = 0;
let mut row5 = 0;
self.window.get_keys().iter().for_each(|key| 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 &mut self.memory.lock() {
Ok(memory) => {
memory.write(0x4400, row0);
memory.write(0x4401, row1);
memory.write(0x4402, row2);
memory.write(0x4403, row3);
memory.write(0x4404, row4);
memory.write(0x4405, row5);
}
Err(error) => println!("{error}"),
}
let now = Instant::now();
if now - previous_draw > frame_duration {
self.draw(); self.draw();
previous_draw = now; self.interrupt.send(true).unwrap();
}
sleep(Duration::from_millis(1));
} }
} }
} }