Added interrupts, some restructuring & refactoring

This commit is contained in:
2024-04-10 17:28:34 -04:00
parent 705dcd3185
commit c154bdc89a
23 changed files with 1187 additions and 3916 deletions
-2943
View File
File diff suppressed because it is too large Load Diff
BIN
View File
Binary file not shown.
+88 -149
View File
@@ -1,19 +1,23 @@
use crate::error::{ExecutionError, GeorgeError, GeorgeErrorKind, MemoryError};
// use crate::error::{ExecutionError, GeorgeError, GeorgeErrorKind, MemoryError};
use crate::instructions::{get_instruction, Instruction};
use crate::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,135 +144,71 @@ 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);
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;
}
};
pub fn interrupt(&mut self) {
self.push_stack_word(self.pc).unwrap();
self.push_stack(self.p).unwrap();
self.set_flag(StatusFlag::IrqDisable, true);
self.pc = self.read_word(0xFFFE).unwrap();
}
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);
memory
.dump(PathBuf::from_str("./cpu_dump.bin").unwrap())
.unwrap();
exit(1);
pub fn cycle(&mut self) {
while self.pending_cycles != 0 {
// roughly cycle-accurate timing
sleep(Duration::from_nanos(100));
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;
}
_ => {
let george_error = GeorgeError {
kind: GeorgeErrorKind::Execution(ExecutionError::InvalidInstruction),
desc: format!(
Err(_) => {
println!("An IncompatibleAddrMode was used at address {:#06x} for instruction {:?}", self.pc, valid_instruction);
}
};
}
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}",
invalid_instruction.opcode, self.pc
),
};
println!("{:?}", george_error.desc);
}
},
}
);
}
},
}
self.pending_cycles -= 1;
self.cycle_count += 1;
}
BIN
View File
Binary file not shown.
-240
View File
@@ -1,240 +0,0 @@
.setcpu "65C02"
.include "macro.inc"
; okay so rn i wanna set up a very basic system init, and write a few subroutines to draw characters at x,y coordinates
n = $01 ; temporary storage for data stack operations
key_row = $200 ; used for character lookup when key pressed
key_col = $201
cursor = $202
kb_row0 = $4400 ; keyboard hardware register, there are 5 more but i can just increment from here
.segment "ROM"
reset:
ldx #0; initialize data stack pointer
;lda #$80
;sta $200
;adc $200
;breakpoint
initdisplay:
lda #$20
ldy #0
cleardisplay:
sta $6000,y
sta $6100,y
sta $6200,y
sta $6300,y
sta $6400,y
sta $6500,y
sta $6600,y
sta $6700,y ; this goes slightly over but it's fine
iny
bne cleardisplay
main:
;jsr draw
jsr keyboard
jmp main
keyboard:
ldy #0 ; loop through each row
@loop:
lda kb_row0, y
beq @skip ; if row has no key pressed, skip checking which key
jsr key_pressed
@skip:
iny
cpy #5
bne @loop
rts
key_pressed: ; a is loaded with the row byte
sty key_row ; store character row
inc cursor
pha
phy
ldy #0
@find_col: ; test each row bit, store column if key pressed
lsr ; test bit 7
bcs store_col ; if set, go store character column
iny
cpy #8
bne @find_col ; loop until we've checked each bit
store_col:
sty key_col
keymap_index:
push
lda key_col
stz 1, x
sta 0, x
push
lda #8
stz 1, x
sta 0, x
push
lda key_row
stz 1, x
sta 0, x
jsr mult
jsr plus
lda 0, x
tay
do_something_w_key: ; we've stored the character position, now let's
lda keymap, y
ldy cursor
sta $6000, y
pla
ply
rts
keymap:
.byte "?outrew?"
.byte "?piygsq?"
.byte "a??khvd?"
.byte "42ljbfz?"
.byte "31?mncx?"
.byte "?????ssm"
draw:
; lda #%01000000
; bit $4400
; beq @1
; push_coords #0, #0
; push_char #$77
; jsr draw_char
; @1:
; lda #%00100000
; bit $4400
; beq @2
; push_coords #0, #0
; push_char #$65
; jsr draw_char
; @2:
rts
draw_char: ; draw a character c at (x, y) (n1: x n2: y n3: c -- )
lda 0, x ; load a with character to draw
pop ; and pop it off the stack
jsr get_char_address ; calculate where to put the character in memory
sta (0, x) ; store a at the address pointed to on the stack
rts
get_char_address: ; gets vram address for a character at (x, y),
; (n1: x n2: y -- n: $6000 + x + (64 * y))
;jsr push_lit ; push 64 onto stack, low byte first
;.byte 64
;.byte 0
pha
lda #64
push ; doing this instead until `push_lit` is fixed
sta 0, x
stz 1, x
jsr mult ; multiply 64 with y (n2)
jsr plus ; add result with x (n1)
;jsr push_lit ; push vram address onto the stack
;.byte $00
;.byte $60
lda #$60
push
sta 1, x
stz 0, x
jsr plus ; add vram start address to result
pla
rts
fill: ; fills an area from (x1, y1) to (x2, y2) will character c, (n1: c n2: x1 n3: y1 n4: x2 n5: y2 -- )
jsr get_char_address
; --- Data Stack --- ;
; on this channel we love garth wilson: https://wilsonminesco.com/stacks/StackOps.ASM
; data stack is built up of 2-byte cells
; TODO: this is broken, jumping here does nothing to the stack and skips several instructions, could be an emulator problem tho
push_lit: ; this bad boy lets you inline a literal (low byte first) right after `jsr push_lit` and put it on the stack, once again, on this channel we love garth wilson
push2
phx
tsx
txa
tay
plx
lda $102, y
sta 0, x
clc
adc #2
sta $102, y
lda $103, y
sta 1, x
adc #0
sta $103, y
fetch:
lda (0, x)
pha
inc 0, x
bne @1
inc 1, x
@1: lda (0, x)
bra put
push
put:
sta 1, x
pla
sta 0, x
rts
plus: ; add: (n1 n2 -- n1+n2)
clc
lda 0, x
adc 2, x
sta 2, x
lda 1, x
adc 3, x
sta 3, x
pop
rts
mult: ; multiply: (n1 n2 -- n1*n2), frankly, i don't know how this works, but TODO: will try to figure it out later
phy
stz n
ldy #0
@1: lsr 3, x
ror 2, x
bcc @2
clc
lda n
adc 0, x
sta n
tya
adc 1, x
tay
@2: asl 0, x
rol 1, x
lda 2, x
ora 3, x
bne @1
lda n
sta 2, x
sty 3, x
pop
ply
rts
.segment "VECTOR"
.word reset
-10
View File
@@ -1,10 +0,0 @@
MEMORY {
RAM: start = $0000, size = $8000, type = ro, fill = yes;
ROM: start = $8000, size = $7FFC, type = ro, fill = yes;
VECTOR: start = $FFFC, size = $4, type = ro, fill = yes;
}
SEGMENTS {
ROM: load = "ROM", type = ro;
VECTOR: load = "VECTOR", type = ro;
}
+185 -415
View File
File diff suppressed because it is too large Load Diff
+92
View File
@@ -0,0 +1,92 @@
use minifb::{InputCallback, Key};
use std::{
path::PathBuf,
process::exit,
str::FromStr,
sync::{Arc, Mutex},
};
use crate::memory::Mem;
pub struct Keyboard {
memory: Arc<Mutex<Mem>>,
}
impl Keyboard {
pub fn new(memory: Arc<Mutex<Mem>>) -> Self {
Self { memory }
}
}
impl InputCallback for Keyboard {
fn add_char(&mut self, _uni_char: u32) {}
fn set_key_state(&mut self, key: Key, _state: bool) {
let mut row0 = 0;
let mut row1 = 0;
let mut row2 = 0;
let mut row3 = 0;
let mut row4 = 0;
let mut row5 = 0;
match key {
Key::Escape => row0 ^= 0b1000_0000,
Key::W => row0 ^= 0b0100_0000,
Key::E => row0 ^= 0b0010_0000,
Key::R => row0 ^= 0b0001_0000,
Key::T => row0 ^= 0b0000_1000,
Key::U => row0 ^= 0b0000_0100,
Key::O => row0 ^= 0b0000_0010,
Key::Backspace => row0 ^= 0b0000_0001,
Key::Tab => row1 ^= 0b1000_0000,
Key::Q => row1 ^= 0b0100_0000,
Key::S => row1 ^= 0b0010_0000,
Key::G => row1 ^= 0b0001_0000,
Key::Y => row1 ^= 0b0000_1000,
Key::I => row1 ^= 0b0000_0100,
Key::P => row1 ^= 0b0000_0010,
Key::Enter => row1 ^= 0b0000_0001,
Key::LeftShift | Key::RightShift => row2 ^= 0b1000_0000,
Key::D => row2 ^= 0b0100_0000,
Key::V => row2 ^= 0b0010_0000,
Key::H => row2 ^= 0b0001_0000,
Key::K => row2 ^= 0b0000_1000,
Key::Apostrophe => row2 ^= 0b0000_0100,
Key::Slash => row2 ^= 0b0000_0010,
Key::A => row2 ^= 0b0000_0001,
Key::LeftCtrl | Key::RightCtrl => row3 ^= 0b1000_0000,
Key::Z => row3 ^= 0b0100_0000,
Key::F => row3 ^= 0b0010_0000,
Key::B => row3 ^= 0b0001_0000,
Key::J => row3 ^= 0b0000_1000,
Key::L => row3 ^= 0b0000_0100,
Key::Key2 => row3 ^= 0b0000_0010,
Key::Key4 => row3 ^= 0b0000_0001,
Key::LeftAlt | Key::RightAlt => row4 ^= 0b1000_0000,
Key::X => row4 ^= 0b0100_0000,
Key::C => row4 ^= 0b0010_0000,
Key::N => row4 ^= 0b0001_0000,
Key::M => row4 ^= 0b0000_1000,
Key::Comma => row4 ^= 0b0000_0100,
Key::Key1 => row4 ^= 0b0000_0010,
Key::Key3 => row4 ^= 0b0000_0001,
Key::LeftSuper => row5 ^= 0b1000_0000,
Key::Space => row5 ^= 0b0100_0000,
Key::RightSuper => row5 ^= 0b0010_0000,
_ => {}
};
match &mut self.memory.lock() {
Ok(memory) => {
memory.write(0x4400, row0);
memory.write(0x4401, row1);
memory.write(0x4402, row2);
memory.write(0x4403, row3);
memory.write(0x4404, row4);
memory.write(0x4405, row5);
}
Err(_error) => {
println!("couldnt get a lock on memory while saving keyboard registers");
}
}
}
}
+77 -13
View File
@@ -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);
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();
}
}
+17 -4
View File
@@ -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.
+164
View File
@@ -0,0 +1,164 @@
; .setcpu "65C02"
.include "./macro.inc"
; okay so rn i wanna set up a very basic system init, and write a few subroutines to draw characters at x,y coordinates
n = $01 ; temporary storage for data stack operations
key_row = $200 ; used for character lookup when key pressed
key_col = $201
cursor = $202
char_buffer = $300 ; 256 byte character buffer
kb_row = $4400 ; keyboard hardware register, there are 5 more but i can just increment from here
kb_row_cache = $203 ; cache
.org $8000
reset:
sei
ldx #0; initialize data stack pointer
initdisplay:
lda #$20
ldy #0
cleardisplay:
sta $6000,y
sta $6100,y
sta $6200,y
sta $6300,y
sta $6400,y
sta $6500,y
sta $6600,y
sta $6700,y ; this goes slightly over but it's fine
iny
bne cleardisplay
cli
main:
jsr draw
jmp main
keyboard:
ldy #0
.loop: ; loop through each row
lda kb_row, y
beq .skip ; if row has no key pressed, skip checking which key
sta kb_row_cache, y ; if key pressed, cache it
lda kb_row, y
cmp kb_row_cache, y ; has key changed?
beq key_down
.skip:
iny
cpy #5
bne .loop
rts
key_down: ; a is loaded with the row byte
sty key_row ; store character row
inc cursor
pha
phy
ldy #0
.find_col: ; test each row bit, store column if key pressed
lsr ; test bit 7
bcs store_col ; if unset, don't go store character columnb
.skip:
iny
cpy #8
bne .find_col ; loop until we've checked each bit
store_col:
sty key_col
keymap_index:
push
lda key_col
stz 1, x
sta 0, x
push
lda #8
stz 1, x
sta 0, x
push
lda key_row
stz 1, x
sta 0, x
jsr mult
jsr plus
lda 0, x
tay
do_something_w_key: ; we've stored the character position, now let's
lda keymap, y
ldy cursor
sta $6000, y
ply
pla
rts
keymap:
.byte "?outrew?"
.byte "?piygsq?"
.byte "a??khvd?"
.byte "42ljbfz?"
.byte "31?mncx?"
.byte "????? m"
draw:
push_coords #0, #0
push_char #$00
jsr draw_char
rts
draw_char: ; draw a character c at (x, y) (n1: x n2: y n3: c -- )
lda 0, x ; load a with character to draw
pop ; and pop it off the stack
jsr get_char_address ; calculate where to put the character in memory
sta (0, x) ; store a at the address pointed to on the stack
rts
get_char_address: ; gets vram address for a character at (x, y),
; (n1: x n2: y -- n: $6000 + x + (64 * y))
;jsr push_lit ; push 64 onto stack, low byte first
;.byte 64
;.byte 0
pha
lda #64
push ; doing this instead until `push_lit` is fixed
sta 0, x
stz 1, x
jsr mult ; multiply 64 with y (n2)
jsr plus ; add result with x (n1)
;jsr push_lit ; push vram address onto the stack
;.byte $00
;.byte $60
lda #$60
push
sta 1, x
stz 0, x
jsr plus ; add vram start address to result
pla
rts
fill: ; fills an area from (x1, y1) to (x2, y2) will character c, (n1: c n2: x1 n3: y1 n4: x2 n5: y2 -- )
jsr get_char_address
isr: ; interrupt service routine
pha
phx
phy
jsr keyboard
ply
plx
pla
rti
.include "math.inc"
.org $fffc
.word reset
.word isr
Binary file not shown.
+142
View File
@@ -0,0 +1,142 @@
; .setcpu "65C02"
.include "./macro.inc"
; okay so rn i wanna set up a very basic system init, and write a few subroutines to draw characters at x,y coordinates
n = $01 ; temporary storage for data stack operations
key_row = $200 ; used for character lookup when key pressed
key_col = $201
cursor = $202
char_buffer = $300 ; 256 byte character buffer
kb_row = $4400 ; keyboard hardware register, there are 5 more but i can just increment from here
kb_row_cache = $203 ; cache
.org $8000
reset:
sei
ldx #0; initialize data stack pointer
initdisplay:
lda #$20
ldy #0
cleardisplay:
sta $6000,y
sta $6100,y
sta $6200,y
sta $6300,y
sta $6400,y
sta $6500,y
sta $6600,y
sta $6700,y ; this goes slightly over but it's fine
iny
bne cleardisplay
cli
main:
jsr draw
jmp main
keyboard:
ldy #5
.loop: ; loop through each row
lda kb_row, y
bne key_down; if row has key pressed, go to key_down subroutine
dey
bne .loop
rts
key_down: ; a is loaded with the row byte
sty key_row ; store character row
inc cursor
pha
phy
ldy #0
.find_col: ; test each row bit, store column if key pressed
lsr ; test bit 7
bcs store_col ; if unset, don't go store character columnb
.skip:
iny
cpy #8
bne .find_col ; loop until we've checked each bit
do_something_w_key: ; we've stored the character position, now let's
lda keymap, y
ldy cursor
sta $6000, y
ply
pla
rts
keymap_0:
.byte "?outrew?"
keymap_1:
.byte "?piygsq?"
keymap_2:
.byte "a??khvd?"
keymap_3:
.byte "42ljbfz?"
keymap_4:
.byte "31?mncx?"
keymap_5:
.byte "????? m"
draw:
push_coords #0, #0
push_char #$00
jsr draw_char
rts
draw_char: ; draw a character c at (x, y) (n1: x n2: y n3: c -- )
lda 0, x ; load a with character to draw
pop ; and pop it off the stack
jsr get_char_address ; calculate where to put the character in memory
sta (0, x) ; store a at the address pointed to on the stack
rts
get_char_address: ; gets vram address for a character at (x, y),
; (n1: x n2: y -- n: $6000 + x + (64 * y))
;jsr push_lit ; push 64 onto stack, low byte first
;.byte 64
;.byte 0
pha
lda #64
push ; doing this instead until `push_lit` is fixed
sta 0, x
stz 1, x
jsr mult ; multiply 64 with y (n2)
jsr plus ; add result with x (n1)
;jsr push_lit ; push vram address onto the stack
;.byte $00
;.byte $60
lda #$60
push
sta 1, x
stz 0, x
jsr plus ; add vram start address to result
pla
rts
fill: ; fills an area from (x1, y1) to (x2, y2) will character c, (n1: c n2: x1 n3: y1 n4: x2 n5: y2 -- )
jsr get_char_address
isr: ; interrupt service routine
pha
phx
phy
jsr keyboard
ply
plx
pla
rti
.include "math.inc"
.org $fffc
.word reset
.word isr
+62
View File
@@ -0,0 +1,62 @@
.macro breakpoint ; $02 isn't a valid instruction, the emulator will see this and halt, dump memory contents
.byte $02
.endm
.macro pop ; drops a data stack cell
inx
inx
.endm
.macro pop2 ; drops 2 data stack cells
inx
inx
inx
inx
.endm
.macro push ; push a data stack cell
dex
dex
.endm
.macro push2 ; push 2 data stack cells
dex
dex
dex
dex
.endm
.macro push_char, char ; pushes an ascii character code onto the stack
lda \char
push
sta 0, x ; char low byte
stz 1, x ; char high byte
.endm
.macro push_coords, coord_x, coord_y ; push a set of (x,y) coordinates onto the data stack
lda \coord_x
push
sta 0, x ; low byte
stz 1,x ; high byte is zero
lda \coord_y
push
sta 0,x ; same here
stz 1,x
.endm
.macro to_r ; pop the top of the stack off and save it in the return (hardware) stack: (n -- )
lda 1, x
pha
lda 0, x
pha
pop
.endm
.macro from_r ; pop the top of the return stack off and put it on the data stack: ( -- n)
push
pla
sta 0, x
pla
sta 1, x
.endm
+81
View File
@@ -0,0 +1,81 @@
; --- Data Stack --- ;
; on this channel we love garth wilson: https://wilsonminesco.com/stacks/StackOps.ASM
; data stack is built up of 2-byte cells
; TODO: this is broken, jumping here does nothing to the stack and skips several instructions, could be an emulator problem tho
; push_lit: ; this bad boy lets you inline a literal (low byte first) right after `jsr push_lit` and put it on the stack, once again, on this channel we love garth wilson
; push2
; phx
; tsx
; txa
; tay
; plx
; lda $102, y
; sta 0, x
; clc
; adc #2
; sta $102, y
; lda $103, y
; sta 1, x
; adc #0
; sta $103, y
; fetch:
; lda (0, x)
; pha
; inc 0, x
; bne .1
; inc 1, x
; .1:
; lda (0, x)
; bra put
; push
; put:
; sta 1, x
; pla
; sta 0, x
; rts
plus: ; add: (n1 n2 -- n1+n2)
clc
lda 0, x
adc 2, x
sta 2, x
lda 1, x
adc 3, x
sta 3, x
pop
rts
mult: ; multiply: (n1 n2 -- n1*n2), frankly, i don't know how this works, but TODO: will try to figure it out later
phy
stz n
ldy #0
.1:
lsr 3, x
ror 2, x
bcc .2
clc
lda n
adc 0, x
sta n
tya
adc 1, x
tay
.2:
asl 0, x
rol 1, x
lda 2, x
ora 3, x
bne .1
lda n
sta 2, x
sty 3, x
pop
ply
rts
+55 -138
View File
@@ -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> {
let mut file = File::open(path).unwrap();
let mut buffer = vec![0; 0xFFFF];
file.read_exact(&mut buffer).unwrap();
buffer
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 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,127 +65,38 @@ 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;
for char_row in 0..29 {
for char_col in 0..64 {
let ascii = memory.read(0x6000 + i);
i += 1;
for row in 0..13 {
let byte = self.char_rom[ascii as usize + (row * 0x101)];
for i in (0..8).rev() {
let buffer_index =
((char_row) * 13 + (row)) * 512 + (char_col * 8 + i);
if (byte << i) & 0x80 == 0x80 {
self.buffer[buffer_index] = FG_COLOR;
} else {
self.buffer[buffer_index] = BG_COLOR;
}
}
// 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;
for char_row in 0..29 {
for char_col in 0..64 {
let ascii = memory.read(0x6000 + i);
i += 1;
for row in 0..13 {
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);
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
.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.draw();
previous_draw = now;
}
sleep(Duration::from_millis(1));
self.interrupt.send(false).unwrap();
sleep(Duration::from_millis(16));
self.draw();
self.interrupt.send(true).unwrap();
}
}
}