Added interrupts, some restructuring & refactoring
This commit is contained in:
parent
705dcd3185
commit
c154bdc89a
|
@ -1,3 +1,4 @@
|
||||||
/target
|
/target
|
||||||
log
|
log
|
||||||
george.o
|
george.o
|
||||||
|
.DS_Store
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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"
|
||||||
|
|
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.
|
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
|
||||||
|
|
|
@ -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.
237
src/cpu.rs
237
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::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,135 +144,71 @@ 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()),
|
self.set_flag(StatusFlag::IrqDisable, true);
|
||||||
// false => {
|
self.pc = self.read_word(0xFFFE).unwrap();
|
||||||
// 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);
|
|
||||||
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.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);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
self.pending_cycles = 7
|
|
||||||
}
|
|
||||||
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);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
let instruction = get_instruction(opcode);
|
|
||||||
match instruction {
|
|
||||||
Instruction::Valid(valid_instruction) => {
|
|
||||||
//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!(
|
|
||||||
// "Instruction: {:?}, {:#04x}",
|
|
||||||
// valid_instruction.opcode, opcode
|
|
||||||
//);
|
|
||||||
//println!("");
|
|
||||||
self.pc += 1;
|
|
||||||
match valid_instruction.opcode.call(self) {
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
Instruction::Invalid(invalid_instruction) => match invalid_instruction.opcode {
|
|
||||||
0x02 => {
|
|
||||||
let memory = match self.memory.lock() {
|
|
||||||
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);
|
pub fn cycle(&mut self) {
|
||||||
memory
|
while self.pending_cycles != 0 {
|
||||||
.dump(PathBuf::from_str("./cpu_dump.bin").unwrap())
|
// roughly cycle-accurate timing
|
||||||
.unwrap();
|
sleep(Duration::from_nanos(100));
|
||||||
exit(1);
|
self.pending_cycles -= 1;
|
||||||
|
}
|
||||||
|
if !self.get_flag(StatusFlag::IrqDisable) && self.irq.recv().unwrap() {
|
||||||
|
self.interrupt();
|
||||||
|
}
|
||||||
|
let opcode = match self.read(self.pc) {
|
||||||
|
Ok(byte) => byte,
|
||||||
|
Err(_) => {
|
||||||
|
println!("Failed to read from memory at address {:#06x}!", self.pc);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let instruction = get_instruction(opcode);
|
||||||
|
match instruction {
|
||||||
|
Instruction::Valid(valid_instruction) => {
|
||||||
|
//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!(
|
||||||
|
// "Instruction: {:?}, {:#04x}",
|
||||||
|
// valid_instruction.opcode, opcode
|
||||||
|
//);
|
||||||
|
//println!("");
|
||||||
|
self.pc += 1;
|
||||||
|
match valid_instruction.opcode.call(self) {
|
||||||
|
Ok(_) => {
|
||||||
|
self.pending_cycles += valid_instruction.cycles as usize;
|
||||||
}
|
}
|
||||||
_ => {
|
Err(_) => {
|
||||||
let george_error = GeorgeError {
|
println!("An IncompatibleAddrMode was used at address {:#06x} for instruction {:?}", self.pc, valid_instruction);
|
||||||
kind: GeorgeErrorKind::Execution(ExecutionError::InvalidInstruction),
|
}
|
||||||
desc: format!(
|
};
|
||||||
|
}
|
||||||
|
Instruction::Invalid(invalid_instruction) => match invalid_instruction.opcode {
|
||||||
|
0x02 => {
|
||||||
|
let memory = match self.memory.lock() {
|
||||||
|
Ok(read) => read,
|
||||||
|
Err(_) => {
|
||||||
|
println!("Couldn't acquire read lock on memory in cpu thread");
|
||||||
|
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.try_recv().unwrap_or_default(), nmi = self.nmi);
|
||||||
|
memory
|
||||||
|
.dump(PathBuf::from_str("./cpu_dump.bin").unwrap())
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
println!(
|
||||||
"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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
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");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
90
src/main.rs
90
src/main.rs
|
@ -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);
|
|
||||||
screen.run();
|
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;
|
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(())
|
||||||
|
//}
|
||||||
}
|
}
|
||||||
|
|
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
|
193
src/video.rs
193
src/video.rs
|
@ -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>
|
||||||
let mut file = File::open(path).unwrap();
|
where
|
||||||
let mut buffer = vec![0; 0xFFFF];
|
P: AsRef<Path>,
|
||||||
file.read_exact(&mut buffer).unwrap();
|
{
|
||||||
buffer
|
match char_rom {
|
||||||
|
Some(path) => {
|
||||||
|
let mut file = File::open(path).unwrap();
|
||||||
|
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 {
|
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,127 +65,38 @@ impl Crtc {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
let hw_ctrl = memory.read(0x4000);
|
// the rest of this function is arcane wizardry based on the specifics of george's weird
|
||||||
match hw_ctrl & 0b0000_1000 == 0b0000_1000 {
|
// display and characters... don't fuck around w it
|
||||||
false => {
|
let mut i = 0;
|
||||||
// the rest of this function is arcane wizardry based on the specifics of george's weird
|
for char_row in 0..29 {
|
||||||
// display and characters... don't fuck around w it
|
for char_col in 0..64 {
|
||||||
let mut i = 0;
|
let ascii = memory.read(0x6000 + i);
|
||||||
for char_row in 0..29 {
|
i += 1;
|
||||||
for char_col in 0..64 {
|
for row in 0..13 {
|
||||||
let ascii = memory.read(0x6000 + i);
|
let byte = self.char_rom[ascii as usize + (row * 0x100)];
|
||||||
i += 1;
|
for i in (0..8).rev() {
|
||||||
for row in 0..13 {
|
let buffer_index = ((char_row) * 13 + (row)) * 512 + (char_col * 8 + i);
|
||||||
let byte = self.char_rom[ascii as usize + (row * 0x101)];
|
if (byte << i) & 0x80 == 0x80 {
|
||||||
for i in (0..8).rev() {
|
self.buffer[buffer_index] = FG_COLOR;
|
||||||
let buffer_index =
|
} else {
|
||||||
((char_row) * 13 + (row)) * 512 + (char_col * 8 + i);
|
self.buffer[buffer_index] = BG_COLOR;
|
||||||
if (byte << i) & 0x80 == 0x80 {
|
|
||||||
self.buffer[buffer_index] = FG_COLOR;
|
|
||||||
} else {
|
|
||||||
self.buffer[buffer_index] = BG_COLOR;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
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;
|
self.draw();
|
||||||
let mut row3 = 0;
|
self.interrupt.send(true).unwrap();
|
||||||
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();
|
|
||||||
previous_draw = now;
|
|
||||||
}
|
|
||||||
sleep(Duration::from_millis(1));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue