Added interrupts, some restructuring & refactoring
This commit is contained in:
parent
705dcd3185
commit
c154bdc89a
|
@ -1,3 +1,4 @@
|
|||
/target
|
||||
log
|
||||
george.o
|
||||
.DS_Store
|
||||
|
|
|
@ -2,6 +2,60 @@
|
|||
# It is not intended for manual editing.
|
||||
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]]
|
||||
name = "autocfg"
|
||||
version = "1.1.0"
|
||||
|
@ -78,6 +132,52 @@ version = "1.0.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
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]]
|
||||
name = "cty"
|
||||
version = "0.2.2"
|
||||
|
@ -99,6 +199,12 @@ version = "1.2.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9ea835d29036a4087793836fa931b08837ad5e957da9e23886b29586fb9b6650"
|
||||
|
||||
[[package]]
|
||||
name = "equivalent"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
|
||||
|
||||
[[package]]
|
||||
name = "errno"
|
||||
version = "0.3.8"
|
||||
|
@ -214,9 +320,35 @@ dependencies = [
|
|||
name = "georgeemu"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bdf",
|
||||
"bitvec",
|
||||
"clap",
|
||||
"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]]
|
||||
|
@ -460,24 +592,33 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.196"
|
||||
version = "1.0.197"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "870026e60fa08c69f064aa766c10f10b1d62db9ccd4d0abb206472bee0ce3b32"
|
||||
checksum = "3fb1c873e1b9b056a4dc4c0c198b24c3ffa059243875552b2bd0933b1aee4ce2"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.196"
|
||||
version = "1.0.197"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "33c85360c95e7d137454dc81d9a4ed2b8efd8fbe19cee57357b32b9771fccb67"
|
||||
checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_spanned"
|
||||
version = "0.6.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "eb3622f419d1296904700073ea6cc23ad690adbd66f13ea683df73298736f0c1"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "slab"
|
||||
version = "0.4.9"
|
||||
|
@ -493,6 +634,12 @@ version = "1.13.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e6ecd384b10a64542d77071bd64bd7b231f4ed5940fba55e98c3de13824cf3d7"
|
||||
|
||||
[[package]]
|
||||
name = "strsim"
|
||||
version = "0.11.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.48"
|
||||
|
@ -543,12 +690,52 @@ dependencies = [
|
|||
"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]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
|
||||
|
||||
[[package]]
|
||||
name = "utf8parse"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a"
|
||||
|
||||
[[package]]
|
||||
name = "version-compare"
|
||||
version = "0.1.1"
|
||||
|
@ -858,6 +1045,15 @@ version = "0.52.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04"
|
||||
|
||||
[[package]]
|
||||
name = "winnow"
|
||||
version = "0.6.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dffa400e67ed5a4dd237983829e66475f0a4a26938c4b04c21baede6262215b8"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wyz"
|
||||
version = "0.5.1"
|
||||
|
|
|
@ -6,6 +6,10 @@ edition = "2021"
|
|||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0.81"
|
||||
bdf = "0.6.0"
|
||||
bitvec = "1.0.1"
|
||||
clap = { version = "4.5.4", features = ["derive"] }
|
||||
minifb = "0.25.0"
|
||||
serde = { version = "1.0.197", features = ["serde_derive", "derive"] }
|
||||
toml = "0.8.12"
|
||||
|
|
12
README.md
12
README.md
|
@ -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.
|
||||
|
||||
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
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
char_rom = "./src/roms/cozette.rom"
|
||||
rom = "./src/roms/george.rom"
|
|
@ -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;
|
2943
src/cozette.bdf
2943
src/cozette.bdf
File diff suppressed because it is too large
Load Diff
BIN
src/cozette.bin
BIN
src/cozette.bin
Binary file not shown.
155
src/cpu.rs
155
src/cpu.rs
|
@ -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::memory::Mem;
|
||||
use crate::types::{Byte, Word};
|
||||
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::thread::sleep;
|
||||
use std::time::Duration;
|
||||
use std::{str::FromStr, sync::Arc, thread::sleep};
|
||||
|
||||
use anyhow::{bail, Result};
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub enum StatusFlag {
|
||||
Negative = 0b1000_0000,
|
||||
Overflow = 0b0100_0000,
|
||||
Brk = 0b0011_0000,
|
||||
BrkIrq = 0b0010_0000,
|
||||
//BrkIrq = 0b0010_0000,
|
||||
Decimal = 0b0000_1000,
|
||||
IrqDisable = 0b0000_0100,
|
||||
Zero = 0b000_0010,
|
||||
|
@ -27,7 +31,7 @@ pub struct Cpu {
|
|||
pub pc: Word, // Program Counter
|
||||
pub s: Byte, // Stack Pointer
|
||||
pub p: Byte, // Status Register
|
||||
pub irq: bool,
|
||||
pub irq: Receiver<bool>,
|
||||
pub nmi: bool,
|
||||
pub memory: Arc<Mutex<Mem>>,
|
||||
pub pending_cycles: usize,
|
||||
|
@ -35,7 +39,7 @@ pub struct Cpu {
|
|||
}
|
||||
|
||||
impl Cpu {
|
||||
pub fn new(memory: Arc<Mutex<Mem>>) -> Self {
|
||||
pub fn new(memory: Arc<Mutex<Mem>>, irq: Receiver<bool>) -> Self {
|
||||
Cpu {
|
||||
a: 0x00,
|
||||
x: 0x00,
|
||||
|
@ -43,30 +47,29 @@ impl Cpu {
|
|||
pc: 0x0000,
|
||||
s: 0xFF,
|
||||
p: 0b0010_0100,
|
||||
irq: false,
|
||||
irq,
|
||||
nmi: false,
|
||||
memory,
|
||||
pending_cycles: 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)?;
|
||||
self.pc = reset_vector_pointer;
|
||||
self.pending_cycles = 8;
|
||||
self.pending_cycles = 0;
|
||||
Ok(())
|
||||
}
|
||||
pub fn read(&self, address: Word) -> Result<Byte, MemoryError> {
|
||||
pub fn read(&self, address: Word) -> Result<Byte> {
|
||||
let memory = match self.memory.lock() {
|
||||
Ok(read) => read,
|
||||
Err(_) => {
|
||||
println!("Couldn't acquire read 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
|
||||
bail!("Couldn't acquire lock on memory in cpu thread")
|
||||
}
|
||||
};
|
||||
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 high_byte = self.read(address + 0x1)?;
|
||||
Ok((high_byte as u16) << 8 | (low_byte as u16))
|
||||
|
@ -77,27 +80,27 @@ impl Cpu {
|
|||
0x0100 + self.s as u16
|
||||
}
|
||||
|
||||
pub fn push_stack(&mut self, data: Byte) -> Result<(), ExecutionError> {
|
||||
self.s -= 0x1;
|
||||
pub fn push_stack(&mut self, data: Byte) -> Result<()> {
|
||||
self.s = self.s.wrapping_sub(0x1);
|
||||
self.write(self.stack_addr(), data)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn push_stack_word(&mut self, address: Word) -> Result<(), ExecutionError> {
|
||||
self.s -= 0x1;
|
||||
pub fn push_stack_word(&mut self, address: Word) -> Result<()> {
|
||||
self.s = self.s.wrapping_sub(0x1);
|
||||
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
|
||||
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())?;
|
||||
self.s = self.s.wrapping_add(0x1);
|
||||
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 high_byte = self.pop_stack()?;
|
||||
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 {
|
||||
(self.p & flag as Byte) > 0
|
||||
}
|
||||
|
||||
pub fn is_negative(&self, value: Byte) -> bool {
|
||||
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() {
|
||||
Ok(write) => write,
|
||||
Err(_) => {
|
||||
println!("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
|
||||
bail!("Couldn't acquire write lock on memory in cpu thread")
|
||||
}
|
||||
};
|
||||
memory.write(address, data);
|
||||
Ok(())
|
||||
|
@ -141,74 +144,26 @@ impl Cpu {
|
|||
unimplemented!()
|
||||
}
|
||||
|
||||
// fn handle_interrupt(&mut self) -> Result<(), ExecutionError> {
|
||||
// match self.get_flag(StatusFlag::IrqDisable) {
|
||||
// // Check that interrupts aren't disabled
|
||||
// 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);
|
||||
pub fn interrupt(&mut self) {
|
||||
self.push_stack_word(self.pc).unwrap();
|
||||
self.push_stack(self.p).unwrap();
|
||||
self.set_flag(StatusFlag::IrqDisable, true);
|
||||
if self.nmi {
|
||||
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.pc = self.read_word(0xFFFE).unwrap();
|
||||
}
|
||||
};
|
||||
self.pending_cycles = 8;
|
||||
} else {
|
||||
match self.read_word(0xFFFE) {
|
||||
Ok(word) => self.pc = word,
|
||||
Err(error) => {
|
||||
let george_error = GeorgeError {
|
||||
kind: GeorgeErrorKind::Memory(error),
|
||||
desc: String::from_str("Couldn't read IRQ vector").unwrap(),
|
||||
};
|
||||
println!("{:?}", george_error.desc);
|
||||
|
||||
pub fn cycle(&mut self) {
|
||||
while self.pending_cycles != 0 {
|
||||
// roughly cycle-accurate timing
|
||||
sleep(Duration::from_nanos(100));
|
||||
self.pending_cycles -= 1;
|
||||
}
|
||||
};
|
||||
self.pending_cycles = 7
|
||||
if !self.get_flag(StatusFlag::IrqDisable) && self.irq.recv().unwrap() {
|
||||
self.interrupt();
|
||||
}
|
||||
self.nmi = false;
|
||||
self.irq = false;
|
||||
}
|
||||
} else {
|
||||
let opcode = match self.read(self.pc) {
|
||||
Ok(byte) => byte,
|
||||
Err(error) => {
|
||||
let george_error = GeorgeError {
|
||||
desc: format!("Failed to read from memory at address {:#06x}!", self.pc),
|
||||
kind: GeorgeErrorKind::Memory(error),
|
||||
};
|
||||
println!("{:?}", george_error.desc);
|
||||
Err(_) => {
|
||||
println!("Failed to read from memory at address {:#06x}!", self.pc);
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
@ -226,11 +181,8 @@ impl Cpu {
|
|||
Ok(_) => {
|
||||
self.pending_cycles += valid_instruction.cycles as usize;
|
||||
}
|
||||
Err(error) => {
|
||||
let george_error = GeorgeError{
|
||||
desc: format!("An IncompatibleAddrMode was used at address {:#06x} for instruction {:?}",
|
||||
self.pc, valid_instruction).to_string(), kind: error};
|
||||
println!("{:?}", george_error.desc);
|
||||
Err(_) => {
|
||||
println!("An IncompatibleAddrMode was used at address {:#06x} for instruction {:?}", self.pc, valid_instruction);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -240,36 +192,23 @@ impl Cpu {
|
|||
Ok(read) => read,
|
||||
Err(_) => {
|
||||
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;
|
||||
}
|
||||
};
|
||||
|
||||
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
|
||||
.dump(PathBuf::from_str("./cpu_dump.bin").unwrap())
|
||||
.unwrap();
|
||||
exit(1);
|
||||
}
|
||||
_ => {
|
||||
let george_error = GeorgeError {
|
||||
kind: GeorgeErrorKind::Execution(ExecutionError::InvalidInstruction),
|
||||
desc: format!(
|
||||
println!(
|
||||
"An invalid instruction with opcode {:#04x} was called at address {:#06x}",
|
||||
invalid_instruction.opcode, self.pc
|
||||
),
|
||||
};
|
||||
println!("{:?}", george_error.desc);
|
||||
);
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
self.pending_cycles -= 1;
|
||||
self.cycle_count += 1;
|
||||
}
|
||||
|
||||
|
|
BIN
src/george
BIN
src/george
Binary file not shown.
240
src/george.asm
240
src/george.asm
|
@ -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
|
||||
|
|
@ -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
|
@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
88
src/main.rs
88
src/main.rs
|
@ -9,27 +9,58 @@ mod types;
|
|||
mod video;
|
||||
|
||||
use crate::cpu::Cpu;
|
||||
use crate::keyboard::Keyboard;
|
||||
use crate::memory::Mem;
|
||||
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::sync::Mutex;
|
||||
use std::thread::sleep;
|
||||
use std::time::Duration;
|
||||
use std::sync::{mpsc, Mutex};
|
||||
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() {
|
||||
let mut memory = Mem::new();
|
||||
let binary = match std::fs::File::open(PathBuf::from(
|
||||
"/Users/kline/projects/winter/george-emu/src/george",
|
||||
)) {
|
||||
Ok(file) => file,
|
||||
Err(error) => panic!("Couldn't open binary file! {:?}", error),
|
||||
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,
|
||||
};
|
||||
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);
|
||||
};
|
||||
|
||||
let (interrupt_tx, interrupt_rx) = mpsc::channel();
|
||||
let (window_tx, window_rx) = mpsc::channel();
|
||||
|
||||
memory
|
||||
.dump(PathBuf::from_str("./coredump.bin").unwrap())
|
||||
.unwrap();
|
||||
|
@ -37,12 +68,45 @@ fn main() {
|
|||
let shared_memory = Arc::new(Mutex::new(memory));
|
||||
let cpu_memory = shared_memory.clone();
|
||||
let display_memory = shared_memory.clone();
|
||||
let keyboard_memory = shared_memory.clone();
|
||||
|
||||
thread::spawn(move || {
|
||||
let mut cpu = Cpu::new(cpu_memory);
|
||||
let mut cpu = Cpu::new(cpu_memory, interrupt_rx);
|
||||
cpu.reset().unwrap();
|
||||
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();
|
||||
});
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -30,17 +30,30 @@ impl Mem {
|
|||
self.data[address as usize] = data;
|
||||
}
|
||||
|
||||
pub fn read_from_bin(&mut self, f: File) -> Result<(), MemoryError> {
|
||||
let bytes = f.bytes();
|
||||
pub fn load_rom(&mut self, rom: File) -> Result<(), MemoryError> {
|
||||
let bytes = rom.bytes();
|
||||
for (address, byte) in bytes.enumerate() {
|
||||
match byte {
|
||||
Ok(value) => self.write(address as Word, value),
|
||||
Ok(value) => self.write(address as Word + 0x8000, value),
|
||||
Err(_) => {
|
||||
println!("couldn't write byte {:#04x}", address);
|
||||
println!("Loading rom: couldn't write byte {:#04x}", address);
|
||||
return Err(MemoryError::Unwritable);
|
||||
}
|
||||
}
|
||||
}
|
||||
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(())
|
||||
//}
|
||||
}
|
||||
|
|
Binary file not shown.
|
@ -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
|
Binary file not shown.
|
@ -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
|
|
@ -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
|
||||
|
|
@ -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
|
161
src/video.rs
161
src/video.rs
|
@ -1,9 +1,12 @@
|
|||
use crate::Mem;
|
||||
use minifb::{Key, Scale, ScaleMode, Window, WindowOptions};
|
||||
use std::{
|
||||
fs::File,
|
||||
io::Read,
|
||||
sync::{Arc, Mutex},
|
||||
path::Path,
|
||||
sync::{
|
||||
mpsc::{Sender, SyncSender},
|
||||
Arc, Mutex,
|
||||
},
|
||||
thread::sleep,
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
|
@ -14,41 +17,44 @@ const BG_COLOR: u32 = 0x110500;
|
|||
pub struct Crtc {
|
||||
memory: Arc<Mutex<Mem>>,
|
||||
buffer: Vec<u32>,
|
||||
window: minifb::Window,
|
||||
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 buffer = vec![0; 0xFFFF];
|
||||
file.read_exact(&mut buffer).unwrap();
|
||||
buffer
|
||||
let mut bin = vec![0; 0x8000];
|
||||
file.read_exact(&mut bin).unwrap();
|
||||
println!("reading char rom");
|
||||
bin
|
||||
}
|
||||
None => include_bytes!("./roms/cozette.rom").to_vec(),
|
||||
}
|
||||
}
|
||||
|
||||
impl Crtc {
|
||||
pub fn new(memory: Arc<Mutex<Mem>>) -> Self {
|
||||
let 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();
|
||||
let char_rom = get_char_bin("./src/cozette.bin");
|
||||
pub fn new<P>(
|
||||
memory: Arc<Mutex<Mem>>,
|
||||
char_rom: Option<P>,
|
||||
interrupt: Sender<bool>,
|
||||
window: Sender<Vec<u32>>,
|
||||
) -> Self
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
{
|
||||
let char_rom = get_char_bin(char_rom);
|
||||
Self {
|
||||
memory,
|
||||
buffer: vec![0; 512 * 380],
|
||||
window,
|
||||
char_rom,
|
||||
interrupt,
|
||||
}
|
||||
}
|
||||
fn draw(&mut self) {
|
||||
|
@ -59,9 +65,6 @@ impl Crtc {
|
|||
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
|
||||
// display and characters... don't fuck around w it
|
||||
let mut i = 0;
|
||||
|
@ -70,10 +73,9 @@ impl Crtc {
|
|||
let ascii = memory.read(0x6000 + i);
|
||||
i += 1;
|
||||
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() {
|
||||
let buffer_index =
|
||||
((char_row) * 13 + (row)) * 512 + (char_col * 8 + i);
|
||||
let buffer_index = ((char_row) * 13 + (row)) * 512 + (char_col * 8 + i);
|
||||
if (byte << i) & 0x80 == 0x80 {
|
||||
self.buffer[buffer_index] = FG_COLOR;
|
||||
} 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
|
||||
.update_with_buffer(&self.buffer, 512, 380)
|
||||
.unwrap();
|
||||
let buffer = self.buffer.to_owned();
|
||||
|
||||
self.window.send(buffer).unwrap();
|
||||
}
|
||||
|
||||
pub fn run(&mut self) {
|
||||
let frame_duration = Duration::from_millis(16);
|
||||
let mut previous_draw = Instant::now();
|
||||
|
||||
loop {
|
||||
let mut row0 = 0;
|
||||
let mut row1 = 0;
|
||||
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.interrupt.send(false).unwrap();
|
||||
sleep(Duration::from_millis(16));
|
||||
self.draw();
|
||||
previous_draw = now;
|
||||
}
|
||||
sleep(Duration::from_millis(1));
|
||||
self.interrupt.send(true).unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue