Compare commits

..

No commits in common. "705dcd31851f6677da1b021a8db51b951ca4d3b7" and "9808616203c7ce9dc56023fc40192f889d6093e2" have entirely different histories.

12 changed files with 27614 additions and 1529 deletions

2940
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -8,4 +8,5 @@ edition = "2021"
[dependencies] [dependencies]
bdf = "0.6.0" bdf = "0.6.0"
bitvec = "1.0.1" bitvec = "1.0.1"
iced = { version = "0.12.0", features = ["canvas", "smol"] }
minifb = "0.25.0" minifb = "0.25.0"

File diff suppressed because it is too large Load Diff

View File

@ -1,12 +1,14 @@
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::{self, Mem};
use crate::types::{Byte, Word}; use crate::types::{Byte, Word};
use std::path::PathBuf;
use std::process::exit; use std::process::exit;
use std::sync::Mutex;
use std::time::Duration; use std::time::Duration;
use std::{str::FromStr, sync::Arc, thread::sleep}; use std::{
str::FromStr,
sync::{Arc, RwLock},
thread::sleep,
};
#[derive(Clone, Copy)] #[derive(Clone, Copy)]
pub enum StatusFlag { pub enum StatusFlag {
@ -29,13 +31,13 @@ pub struct Cpu {
pub p: Byte, // Status Register pub p: Byte, // Status Register
pub irq: bool, pub irq: bool,
pub nmi: bool, pub nmi: bool,
pub memory: Arc<Mutex<Mem>>, pub memory: Arc<RwLock<Mem>>,
pub pending_cycles: usize, pub pending_cycles: usize,
cycle_count: usize, cycle_count: usize,
} }
impl Cpu { impl Cpu {
pub fn new(memory: Arc<Mutex<Mem>>) -> Self { pub fn new(memory: Arc<RwLock<Mem>>) -> Self {
Cpu { Cpu {
a: 0x00, a: 0x00,
x: 0x00, x: 0x00,
@ -57,14 +59,14 @@ impl Cpu {
Ok(()) Ok(())
} }
pub fn read(&self, address: Word) -> Result<Byte, MemoryError> { pub fn read(&self, address: Word) -> Result<Byte, MemoryError> {
let memory = match self.memory.lock() { let memory = match self.memory.try_read() {
Ok(read) => read, Ok(read) => read,
Err(_) => { Err(_) => {
println!("Couldn't acquire read lock on memory in cpu thread"); println!("Couldn't acquire read lock on memory in cpu thread");
return Err(MemoryError::NoDataAtAddress); return Err(MemoryError::NoDataAtAddress);
} // TODO: upgrade this error type to a `GeorgeError` and make an errorkind for thread lock errors } // TODO: upgrade this error type to a `GeorgeError` and make an errorkind for thread lock errors
}; };
Ok(memory.read(address)) memory.read(address)
} }
pub fn read_word(&self, address: Word) -> Result<Word, MemoryError> { pub fn read_word(&self, address: Word) -> Result<Word, MemoryError> {
let low_byte = self.read(address)?; let low_byte = self.read(address)?;
@ -93,7 +95,7 @@ impl Cpu {
pub fn pop_stack(&mut self) -> Result<Byte, ExecutionError> { pub fn pop_stack(&mut self) -> Result<Byte, ExecutionError> {
let byte = self.read(self.stack_addr())?; let byte = self.read(self.stack_addr())?;
self.s = self.s.wrapping_add(0x1); self.s += 0x1;
Ok(byte) Ok(byte)
} }
@ -119,21 +121,21 @@ impl Cpu {
} }
pub fn write(&mut self, address: Word, data: Byte) -> Result<(), MemoryError> { pub fn write(&mut self, address: Word, data: Byte) -> Result<(), MemoryError> {
let mut memory = match self.memory.lock() { let mut memory = match self.memory.write() {
Ok(write) => write, Ok(write) => write,
Err(_) => { Err(_) => {
println!("Couldn't acquire write lock on memory in cpu thread"); println!("Couldn't acquire write lock on memory in cpu thread");
return Err(MemoryError::NoDataAtAddress); return Err(MemoryError::NoDataAtAddress);
} // TODO: upgrade this error type to a `GeorgeError` and make an errorkind for thread lock errors } // 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(())
} }
pub fn execute(&mut self) { pub fn execute(&mut self) {
self.cycle(); self.cycle().unwrap();
while self.pending_cycles != 0 { while self.pending_cycles != 0 {
self.cycle(); self.cycle().unwrap();
} }
} }
@ -164,7 +166,7 @@ impl Cpu {
// } // }
// } // }
// } // }
pub fn cycle(&mut self) { pub fn cycle(&mut self) -> Result<(), GeorgeError> {
sleep(Duration::from_nanos(100)); sleep(Duration::from_nanos(100));
if self.pending_cycles == 0 { if self.pending_cycles == 0 {
if self.nmi || (self.irq && !self.get_flag(StatusFlag::IrqDisable)) { if self.nmi || (self.irq && !self.get_flag(StatusFlag::IrqDisable)) {
@ -176,11 +178,10 @@ impl Cpu {
match self.read_word(0xFFFA) { match self.read_word(0xFFFA) {
Ok(word) => self.pc = word, Ok(word) => self.pc = word,
Err(error) => { Err(error) => {
let george_error = GeorgeError { return Err(GeorgeError {
kind: GeorgeErrorKind::Memory(error), kind: GeorgeErrorKind::Memory(error),
desc: String::from_str("Couldn't read NMI vector").unwrap(), desc: String::from_str("Couldn't read NMI vector").unwrap(),
}; })
println!("{:?}", george_error.desc);
} }
}; };
self.pending_cycles = 8; self.pending_cycles = 8;
@ -188,11 +189,10 @@ impl Cpu {
match self.read_word(0xFFFE) { match self.read_word(0xFFFE) {
Ok(word) => self.pc = word, Ok(word) => self.pc = word,
Err(error) => { Err(error) => {
let george_error = GeorgeError { return Err(GeorgeError {
kind: GeorgeErrorKind::Memory(error), kind: GeorgeErrorKind::Memory(error),
desc: String::from_str("Couldn't read IRQ vector").unwrap(), desc: String::from_str("Couldn't read IRQ vector").unwrap(),
}; })
println!("{:?}", george_error.desc);
} }
}; };
self.pending_cycles = 7 self.pending_cycles = 7
@ -204,73 +204,62 @@ impl Cpu {
let opcode = match self.read(self.pc) { let opcode = match self.read(self.pc) {
Ok(byte) => byte, Ok(byte) => byte,
Err(error) => { Err(error) => {
let george_error = GeorgeError { return Err(GeorgeError {
desc: format!("Failed to read from memory at address {:#06x}!", self.pc), desc: String::from_str("Failed to read the program counter!").unwrap(),
kind: GeorgeErrorKind::Memory(error), kind: GeorgeErrorKind::Memory(error),
}; })
println!("{:?}", george_error.desc);
return;
} }
}; };
let instruction = get_instruction(opcode); let instruction = get_instruction(opcode);
match instruction { match instruction {
Instruction::Valid(valid_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!("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!( println!(
// "Instruction: {:?}, {:#04x}", "Instruction: {:?}, {:#04x}",
// valid_instruction.opcode, opcode valid_instruction.opcode, opcode
//); );
//println!(""); println!("");
self.pc += 1; self.pc += 1;
match valid_instruction.opcode.call(self) { match valid_instruction.opcode.call(self) {
Ok(_) => { Ok(_) => {
self.pending_cycles += valid_instruction.cycles as usize; self.pending_cycles += valid_instruction.cycles as usize;
} },
Err(error) => { Err(error) => return Err(GeorgeError{
let george_error = GeorgeError{
desc: format!("An IncompatibleAddrMode was used at address {:#06x} for instruction {:?}", desc: format!("An IncompatibleAddrMode was used at address {:#06x} for instruction {:?}",
self.pc, valid_instruction).to_string(), kind: error}; self.pc, valid_instruction).to_string(), kind: error})
println!("{:?}", george_error.desc);
}
}; };
} }
Instruction::Invalid(invalid_instruction) => match invalid_instruction.opcode { Instruction::Invalid(invalid_instruction) => match invalid_instruction.opcode {
0x02 => { 0x02 => {
let memory = match self.memory.lock() { let memory = match self.memory.try_read() {
Ok(read) => read, Ok(read) => read,
Err(_) => { Err(_) => {
println!("Couldn't acquire read lock on memory in cpu thread"); println!("Couldn't acquire read lock on memory in cpu thread");
let george_error = GeorgeError { return Err(GeorgeError {
kind: GeorgeErrorKind::Memory(MemoryError::Unwritable), kind: GeorgeErrorKind::Memory(MemoryError::Unwritable),
desc: "Couldn't acquire read lock on memory in cpu thread" desc: "Couldn't acquire read lock on memory in cpu thread"
.to_string(), .to_string(),
}; });
println!("{:?}", george_error.desc);
return;
} }
}; };
memory.dump().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); exit(1);
} }
_ => { _ => {
let george_error = GeorgeError { return Err(GeorgeError {
kind: GeorgeErrorKind::Execution(ExecutionError::InvalidInstruction), kind: GeorgeErrorKind::Execution(ExecutionError::InvalidInstruction),
desc: format!( desc: format!(
"An invalid instruction with opcode {:#04x} was called at address {:#06x}", "An invalid instruction with opcode {:#04x} was called at address {:#06x}",
invalid_instruction.opcode, self.pc invalid_instruction.opcode, self.pc
), ),
}; });
println!("{:?}", george_error.desc);
} }
}, },
} }
} }
self.pending_cycles -= 1; self.pending_cycles -= 1;
self.cycle_count += 1; self.cycle_count += 1;
Ok(())
} }
pub fn stop(&mut self) { pub fn stop(&mut self) {

Binary file not shown.

View File

@ -1,27 +1,65 @@
.setcpu "65C02" .setcpu "65C02"
.include "macro.inc" .segment "CODE"
; okay so rn i wanna set up a very basic system init, and write a few subroutines to draw characters at x,y coordinates ; 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 n = $01 ; temporary storage for data stack operations
key_row = $200 ; used for character lookup when key pressed .macro breakpoint ; $02 isn't a valid instruction, the emulator will see this and halt, dump memory contents
key_col = $201 .byte $02
cursor = $202 .endmacro
kb_row0 = $4400 ; keyboard hardware register, there are 5 more but i can just increment from here .macro pop ; drops a stack cell
inx
inx
.endmacro
.segment "ROM" .macro pop2 ; drops 2 data stack cells
inx
inx
inx
inx
.endmacro
reset: .macro push ;
dex
dex
.endmacro
.macro push2 ;
dex
dex
dex
dex
.endmacro
.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
.endmacro
.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
.endmacro
init:
ldx #0; initialize data stack pointer ldx #0; initialize data stack pointer
;lda #$80 jsr initdisplay
;sta $200
;adc $200 main:
;breakpoint jsr draw
initdisplay: initdisplay:
lda #$20 lda #$20
ldy #0 ldy #0
jsr cleardisplay
rts
cleardisplay: cleardisplay:
sta $6000,y sta $6000,y
@ -34,104 +72,27 @@ cleardisplay:
sta $6700,y ; this goes slightly over but it's fine sta $6700,y ; this goes slightly over but it's fine
iny iny
bne cleardisplay 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 rts
key_pressed: ; a is loaded with the row byte draw: ; draw a character at (20, 30)
sty key_row ; store character row lda #63
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 push
lda key_col sta 0, x ; low byte
stz 1, x stz 1,x ; high byte is zero
sta 0, x lda #28
push push
lda #8 sta 0,x ; same here
stz 1, x 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 jsr get_char_address ; calculate where to put the character in memory
lda #4
sta (0, x) ; store a at the address pointed to on the stack sta (0, x) ; store a at the address pointed to on the stack
rts brk
get_char_address: ; gets vram address for a character at (x, y), get_char_address: ; gets vram address for a character at (x, y),
; (n1: x n2: y -- n: $6000 + x + (64 * y)) ; (n1: x n2: y -- n: $6000 + x + (64 * y))
;jsr push_lit ; push 64 onto stack, low byte first ;jsr push_lit ; push 64 onto stack, low byte first
;.byte 64 ;.byte 64
;.byte 0 ;.byte 0
pha
lda #64 lda #64
push ; doing this instead until `push_lit` is fixed push ; doing this instead until `push_lit` is fixed
sta 0, x sta 0, x
@ -148,14 +109,18 @@ get_char_address: ; gets vram address for a character at (x, y),
stz 0, x stz 0, x
jsr plus ; add vram start address to result jsr plus ; add vram start address to result
pla
rts rts
fill: ; fills an area from (x1, y1) to (x2, y2) will character c, (n1: c n2: x1 n3: y1 n4: x2 n5: y2 -- ) ; inc16
; add 1 to a 16-bit pointer in zero page
jsr get_char_address ;inc16:
; inc ptr
; bne :+
; inc ptr+1
;: rts
;
; --- Data Stack --- ;
; on this channel we love garth wilson: https://wilsonminesco.com/stacks/StackOps.ASM ; on this channel we love garth wilson: https://wilsonminesco.com/stacks/StackOps.ASM
; data stack is built up of 2-byte cells ; data stack is built up of 2-byte cells
@ -235,6 +200,4 @@ mult: ; multiply: (n1 n2 -- n1*n2), frankly, i don't know how this works, but TO
rts rts
.segment "VECTOR" .segment "VRAM"
.word reset

View File

@ -1,10 +1,12 @@
MEMORY { MEMORY {
RAM: start = $0000, size = $8000, type = ro, fill = yes; RAM: start = $0000, size = $0200, type = rw, fill = true;
ROM: start = $8000, size = $7FFC, type = ro, fill = yes; PROGRAM: start = $0200, size = $3E00, type = rw, fill = true;
VECTOR: start = $FFFC, size = $4, type = ro, fill = yes; CTRL: start = $4000, size = $2000, type = rw, fill = true;
VRAM: start = $6000, size = $8000, type = rw, fill = true;
ROM: start = $E000, size = $2000, type = ro, fill = true;
} }
SEGMENTS { SEGMENTS {
ROM: load = "ROM", type = ro; CODE: load = "PROGRAM", type = rw;
VECTOR: load = "VECTOR", type = ro; VRAM: load = "VRAM", type = rw;
} }

View File

@ -7,10 +7,10 @@
# column #2 is the Unicode (in hex as 0xXXXX) # column #2 is the Unicode (in hex as 0xXXXX)
# column #3 the Unicode name (follows a comment sign, '#') # column #3 the Unicode name (follows a comment sign, '#')
0x00 0x00 # 0x00 0x0295 # ʕ
0x01 0x2591 # 0x01 0x00B7 # Middle Dot
0x02 0x2592 # ᴥ 0x02 0x1D25 # ᴥ
0x03 0x2593 # ʔ 0x03 0x0294 # ʔ
0x04 0x2661 # White heart 0x04 0x2661 # White heart
0x05 0x2665 # Black heart 0x05 0x2665 # Black heart
0x06 0x2B50 # White star 0x06 0x2B50 # White star
@ -189,70 +189,70 @@
0xB3 0x258D # 0xB3 0x258D #
0xB4 0x258E # 0xB4 0x258E #
0xB5 0x258F # 0xB5 0x258F #
0xB6 0x0295 # 0xB6 0x2591 #
0xB7 0x00B7 # 0xB7 0x2592 #
0xB8 0x1D25 # 0xB8 0x2593 #
0xB9 0x0294 0xB9 0x2596 #
0xBA 0x2596 0xBA 0x2597 #
0xBB 0x2597 0xBB 0x2598 #
0xBC 0x2598 0xBC 0x2599 #
0xBD 0x2599 0xBD 0x259A #
0xBE 0x259A 0xBE 0x259B #
0xBF 0x259B 0xBF 0x259C #
0xC0 0x259C 0xC0 0x259D #
0xC1 0x259D 0xC1 0x259E #
0xC2 0x259E 0xC2 0x259F #
0xC3 0x259F 0xC3 0x2190 #
0xC4 0x2190 0xC4 0x2191 #
0xC5 0x2191 0xC5 0x2192 #
0xC6 0x2192 0xC6 0x2193 #
0xC7 0x2193 0xC7 0x2B60 #
0xC8 0x2B60 0xC8 0x2B61 #
0xC9 0x2B61 0xC9 0x2B62 #
0xCA 0x2B62 0xCA 0x2B63 #
0xCB 0x2B63 0xCB 0x2B80 #
0xCC 0x2B80 0xCC 0x2B81 #
0xCD 0x2B81 0xCD 0x2B82 #
0xCE 0x2B82 0xCE 0x2B83 #
0xCF 0x2B83 0xCF 0xF049 #
0xD0 0xF049 0xD0 0xF04A #
0xD1 0xF04A 0xD1 0x23F3 #
0xD2 0x23F3 0xD2 0xF07B #
0xD3 0xF07B 0xD3 0xF07C #
0xD4 0xF07C 0xD4 0xF114 #
0xD5 0xF114 0xD5 0xF115 #
0xD6 0xF115 0xD6 0xF250 #
0xD7 0xF250 0xD7 0xF251 #
0xD8 0xF251 0xD8 0xF253 #
0xD9 0xF253 0xD9 0xF254 #
0xDA 0xF254 0xDA 0xF461 #
0xDB 0xF461 0xDB 0xF016 #
0xDC 0xF016 0xDC 0xF401 #
0xDD 0xF401 0xDD 0x1F52E #
0xDE 0x1F52E 0xDE 0xF2DB #
0xDF 0xF2DB 0xDF 0xF008 #
0xE0 0xF008 0xE0 0x25C7 #
0xE1 0x25C7 0xE1 0x25C8 #
0xE2 0x25C8 0xE2 0x1F311 #
0xE3 0x1F311 0xE3 0x1F312 #
0xE4 0x1F312 0xE4 0x1F313 #
0xE5 0x1F313 0xE5 0x1F314 #
0xE6 0x1F314 0xE6 0x1F315 #
0xE7 0x1F315 0xE7 0x1F316 #
0xE8 0x1F316 0xE8 0x1F317 #
0xE9 0x1F317 0xE9 0x1F318 #
0xEA 0x1F318 0xEA 0xF04C #
0xEB 0xF04C 0xEB 0x2714 #
0xEC 0x2714 0xEC 0x2718 #
0xED 0x2718 0xED 0x25C6 #
0xEE 0x25C6 0xEE 0xF15D #
0xEF 0xF15D 0xEF 0xF15E #
0xF0 0xF15E 0xF0 0xF071 #
0xF1 0xF071 0xF1 0xF449 #
0xF2 0xF449 0xF2 0xF529 #
0xF3 0xF529 0xF3 0xF658 #
0xF4 0xF658 0xF4 0xF659 #
0xF5 0xF659 # 0xF5 0x0020 #
0xF6 0x1f381 # Space 0xF6 0x1f381 # Space
0xF7 0xf05a # Space 0xF7 0xf05a # Space
0xF8 0xf06a # Space 0xF8 0xf06a # Space

View File

@ -2063,7 +2063,6 @@ impl Opcode {
let byte = cpu.read(address.try_into()?)?; let byte = cpu.read(address.try_into()?)?;
let carry = cpu.get_flag(StatusFlag::Carry); let carry = cpu.get_flag(StatusFlag::Carry);
let result = cpu.a as Word + byte as Word + carry as Word; let result = cpu.a as Word + byte as Word + carry as Word;
println!("{result:#04x}");
cpu.set_flag(StatusFlag::Carry, result > Word::from(u8::max_value())); cpu.set_flag(StatusFlag::Carry, result > Word::from(u8::max_value()));
cpu.set_flag(StatusFlag::Zero, result as Byte == 0); cpu.set_flag(StatusFlag::Zero, result as Byte == 0);
cpu.set_flag( cpu.set_flag(
@ -2074,7 +2073,6 @@ impl Opcode {
); );
cpu.set_flag(StatusFlag::Negative, cpu.is_negative(result as Byte)); cpu.set_flag(StatusFlag::Negative, cpu.is_negative(result as Byte));
cpu.a = result as Byte; cpu.a = result as Byte;
println!("{:#04x}", cpu.a);
Ok(()) Ok(())
} }
_ => Err(GeorgeErrorKind::AddrMode( _ => Err(GeorgeErrorKind::AddrMode(
@ -2105,7 +2103,10 @@ impl Opcode {
fn asl(cpu: &mut Cpu, value: Byte) -> Byte { fn asl(cpu: &mut Cpu, value: Byte) -> Byte {
cpu.set_flag(StatusFlag::Carry, value & 0b1000_0000 == 0b1000_0000); cpu.set_flag(StatusFlag::Carry, value & 0b1000_0000 == 0b1000_0000);
let shifted_value = value << 1; let shifted_value = value << 1;
cpu.set_flag(StatusFlag::Negative, cpu.is_negative(shifted_value)); cpu.set_flag(
StatusFlag::Negative,
value & StatusFlag::Negative as Byte > 0,
);
cpu.set_flag(StatusFlag::Zero, shifted_value == 0); cpu.set_flag(StatusFlag::Zero, shifted_value == 0);
shifted_value shifted_value
} }
@ -2531,8 +2532,7 @@ impl Opcode {
Opcode::BRK(mode) => match mode { Opcode::BRK(mode) => match mode {
AddressingMode::Implied => { AddressingMode::Implied => {
cpu.set_flag(StatusFlag::Brk, true); cpu.set_flag(StatusFlag::Brk, true);
Ok(()) panic!("Interrupts unimplemented!");
//panic!("Interrupts unimplemented!");
} }
_ => Err(GeorgeErrorKind::AddrMode( _ => Err(GeorgeErrorKind::AddrMode(
AddressingModeError::IncompatibleAddrMode, AddressingModeError::IncompatibleAddrMode,
@ -2662,8 +2662,6 @@ impl Opcode {
Opcode::DEX(mode) => match mode { Opcode::DEX(mode) => match mode {
AddressingMode::Implied => { AddressingMode::Implied => {
cpu.x = cpu.x.wrapping_sub(1); cpu.x = cpu.x.wrapping_sub(1);
cpu.set_flag(StatusFlag::Zero, cpu.x == 0);
cpu.set_flag(StatusFlag::Negative, cpu.is_negative(cpu.x));
Ok(()) Ok(())
} }
_ => Err(GeorgeErrorKind::AddrMode( _ => Err(GeorgeErrorKind::AddrMode(
@ -2672,9 +2670,7 @@ impl Opcode {
}, },
Opcode::DEY(mode) => match mode { Opcode::DEY(mode) => match mode {
AddressingMode::Implied => { AddressingMode::Implied => {
cpu.y = cpu.y.wrapping_sub(1); cpu.y -= 1;
cpu.set_flag(StatusFlag::Zero, cpu.y == 0);
cpu.set_flag(StatusFlag::Negative, cpu.is_negative(cpu.y));
Ok(()) Ok(())
} }
_ => Err(GeorgeErrorKind::AddrMode( _ => Err(GeorgeErrorKind::AddrMode(
@ -2828,8 +2824,11 @@ impl Opcode {
fn lsr(cpu: &mut Cpu, value: Byte) -> Byte { fn lsr(cpu: &mut Cpu, value: Byte) -> Byte {
cpu.set_flag(StatusFlag::Carry, value & 1 == 1); cpu.set_flag(StatusFlag::Carry, value & 1 == 1);
let shifted_value = value >> 1; let shifted_value = value >> 1;
cpu.set_flag(
StatusFlag::Negative,
value & StatusFlag::Negative as Byte > 0,
);
cpu.set_flag(StatusFlag::Zero, shifted_value == 0); cpu.set_flag(StatusFlag::Zero, shifted_value == 0);
cpu.set_flag(StatusFlag::Negative, cpu.is_negative(value));
shifted_value shifted_value
} }
match mode { match mode {
@ -3049,7 +3048,10 @@ impl Opcode {
let carry = cpu.get_flag(StatusFlag::Carry) as Byte; let carry = cpu.get_flag(StatusFlag::Carry) as Byte;
cpu.set_flag(StatusFlag::Carry, value >> 7 == 1); cpu.set_flag(StatusFlag::Carry, value >> 7 == 1);
let shifted_value = (value << 1) + carry; let shifted_value = (value << 1) + carry;
cpu.set_flag(StatusFlag::Negative, cpu.is_negative(shifted_value)); cpu.set_flag(
StatusFlag::Negative,
shifted_value & StatusFlag::Negative as Byte > 0,
);
cpu.set_flag(StatusFlag::Zero, shifted_value == 0); cpu.set_flag(StatusFlag::Zero, shifted_value == 0);
shifted_value shifted_value
} }
@ -3078,7 +3080,10 @@ impl Opcode {
let carry = cpu.get_flag(StatusFlag::Carry) as Byte; let carry = cpu.get_flag(StatusFlag::Carry) as Byte;
cpu.set_flag(StatusFlag::Carry, value & 1 == 1); cpu.set_flag(StatusFlag::Carry, value & 1 == 1);
let shifted_value = (value >> 1) + (carry << 7); let shifted_value = (value >> 1) + (carry << 7);
cpu.set_flag(StatusFlag::Negative, cpu.is_negative(shifted_value)); cpu.set_flag(
StatusFlag::Negative,
shifted_value & StatusFlag::Negative as Byte > 0,
);
cpu.set_flag(StatusFlag::Zero, shifted_value == 0); cpu.set_flag(StatusFlag::Zero, shifted_value == 0);
shifted_value shifted_value
} }

View File

@ -3,23 +3,35 @@
mod cpu; mod cpu;
mod error; mod error;
mod instructions; mod instructions;
mod keyboard;
mod memory; mod memory;
mod types; mod types;
mod video; mod video;
use crate::cpu::Cpu; use crate::cpu::Cpu;
use crate::memory::Mem; use crate::memory::{Mem, MemMappedDevice};
use crate::video::Crtc; use crate::video::Crtc;
use std::str::FromStr; use std::{
use std::sync::Mutex; path::PathBuf,
use std::thread::sleep; sync::{Arc, RwLock},
use std::time::Duration; thread,
use std::{path::PathBuf, sync::Arc, thread}; };
fn main() { fn main() {
let mut memory = Mem::new(); let ram = MemMappedDevice::new(0x0000, 0x3FFF, 2, "ram".into());
let control = MemMappedDevice::new(0x4000, 0x5FFF, 4, "control".into());
let vram = MemMappedDevice::new(0x6000, 0xDFFF, 1, "vram".into());
let rom = MemMappedDevice::new(0xE000, 0xFFFF, 4, "rom".into());
let mut memory = Mem::new(ram);
if let Err(error) = memory.add_area(control) {
println!("Error adding vram: {:?}", error.desc);
};
if let Err(error) = memory.add_area(vram) {
println!("Error adding vram: {:?}", error.desc);
};
if let Err(error) = memory.add_area(rom) {
println!("Error adding rom: {:?}", error.desc);
};
let binary = match std::fs::File::open(PathBuf::from( let binary = match std::fs::File::open(PathBuf::from(
"/Users/kline/projects/winter/george-emu/src/george", "/Users/kline/projects/winter/george-emu/src/george",
)) { )) {
@ -29,20 +41,20 @@ fn main() {
if let Err(error) = memory.read_from_bin(binary) { if let Err(error) = memory.read_from_bin(binary) {
println!("{:?}", error); println!("{:?}", error);
}; };
memory.write(0xFFFC, 0x00).unwrap();
memory.write(0xFFFD, 0x02).unwrap();
memory let shared_memory = Arc::new(RwLock::new(memory));
.dump(PathBuf::from_str("./coredump.bin").unwrap())
.unwrap();
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 mut screen = Crtc::new(display_memory);
thread::spawn(move || { thread::spawn(move || {
let mut cpu = Cpu::new(cpu_memory); let mut cpu = Cpu::new(cpu_memory);
cpu.reset().unwrap(); cpu.reset().unwrap();
cpu.execute(); cpu.execute();
}); });
let mut screen = Crtc::new(display_memory);
screen.run(); screen.run();
shared_memory.write().unwrap().dump().unwrap();
} }

View File

@ -1,44 +1,124 @@
use crate::error::MemoryError; use crate::error::{GeorgeError, GeorgeErrorKind, MappingError, MemoryError};
use crate::types::{Byte, Word}; use crate::types::{Byte, Word};
use std::io::{self, Write}; use std::io::{self, Write};
use std::path::PathBuf; use std::path::PathBuf;
use std::u16;
use std::{fs::File, io::Read}; use std::{fs::File, io::Read};
#[derive(Debug, Clone)]
pub struct MemMappedDevice {
start: Word,
end: Word,
pages: usize,
page: usize,
data: Vec<Byte>,
label: String,
}
impl MemMappedDevice {
pub fn new(start: Word, end: Word, pages: usize, label: String) -> Self {
Self {
start,
end,
pages,
page: 0,
data: vec![0x00; (end as usize + 1 - start as usize) * pages],
label,
}
}
fn contains(&self, address: Word) -> bool {
self.start <= address && self.end >= address
}
fn swap_page(&mut self, page: usize) -> Result<(), GeorgeError> {
match page > self.pages {
true => Err(GeorgeError {
kind: GeorgeErrorKind::Memory(MemoryError::Unmapped), //TODO: should be MappingError::InvalidPage,
desc: format!(
"{:?} tried to swap to a page outside of its range",
self.label
),
}),
false => {
self.page = page;
Ok(())
}
}
}
fn size(&self) -> Word {
self.end - self.start + 1
}
fn translate_address(&self, address: Word) -> Word {
(address - self.start) + self.size() * (self.page as Word)
} // This needs to translate memory address from CPU land to local land, so
// for rom an address like 0xFFFF needs to be translated to Page X, 0xFFF
}
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct Mem { pub struct Mem {
pub data: Vec<Byte>, areas: Vec<MemMappedDevice>,
} }
impl Mem { impl Mem {
pub fn new() -> Self { pub fn new(area: MemMappedDevice) -> Self {
Self { Self { areas: vec![area] }
data: vec![0; u16::MAX as usize + 1], }
pub fn dump(&self) -> io::Result<()> {
let mut outfile = File::create("./coredump.bin")?;
let mut data = Vec::new();
for area in &self.areas {
for byte in &area.data {
data.push(byte.to_owned());
} }
} }
pub fn dump(&self, path: PathBuf) -> io::Result<()> { outfile.set_len(0xFFFF)?;
let mut outfile = File::create(path)?; outfile.write_all(&data)?;
outfile.write_all(&self.data)?;
Ok(()) Ok(())
} }
pub fn add_area(&mut self, area: MemMappedDevice) -> Result<(), GeorgeError> {
pub fn read(&self, address: Word) -> Byte { for existing_area in &self.areas {
self.data[address as usize] if existing_area.contains(area.end) || existing_area.contains(area.start) {
return Err(GeorgeError {
desc: format!(
"Tried to assign an area already allocated to {:?}",
existing_area
),
kind: GeorgeErrorKind::Mapping(MappingError::RegionOccupied),
});
}
}
self.areas.push(area);
Ok(())
}
pub fn read(&self, address: Word) -> Result<Byte, MemoryError> {
for area in &self.areas {
if area.contains(address) {
let translated_address = area.translate_address(address);
match area.data.get(translated_address as usize) {
Some(data) => return Ok(*data),
None => return Err(MemoryError::NoDataAtAddress),
}
}
}
Err(MemoryError::Unmapped)
} }
pub fn write(&mut self, address: Word, data: Byte) { pub fn write(&mut self, address: Word, data: Byte) -> Result<(), MemoryError> {
self.data[address as usize] = data; for area in self.areas.iter_mut() {
if area.contains(address) {
// println!("Writing to area {label}", label = area.label);
let translated_address = area.translate_address(address);
area.data[translated_address as usize] = data;
return Ok(());
};
}
Err(MemoryError::Unmapped)
} }
pub fn read_from_bin(&mut self, f: File) -> Result<(), MemoryError> { pub fn read_from_bin(&mut self, f: File) -> Result<(), MemoryError> {
let bytes = f.bytes(); let bytes = f.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, value)?,
Err(_) => { Err(_) => return Err(MemoryError::Unwritable),
println!("couldn't write byte {:#04x}", address);
return Err(MemoryError::Unwritable);
}
} }
} }
Ok(()) Ok(())

View File

@ -3,7 +3,7 @@ use minifb::{Key, Scale, ScaleMode, Window, WindowOptions};
use std::{ use std::{
fs::File, fs::File,
io::Read, io::Read,
sync::{Arc, Mutex}, sync::{Arc, RwLock},
thread::sleep, thread::sleep,
time::{Duration, Instant}, time::{Duration, Instant},
}; };
@ -12,7 +12,7 @@ const FG_COLOR: u32 = 0xFFCC00;
const BG_COLOR: u32 = 0x110500; const BG_COLOR: u32 = 0x110500;
pub struct Crtc { pub struct Crtc {
memory: Arc<Mutex<Mem>>, memory: Arc<RwLock<Mem>>,
buffer: Vec<u32>, buffer: Vec<u32>,
window: minifb::Window, window: minifb::Window,
char_rom: Vec<u8>, char_rom: Vec<u8>,
@ -26,7 +26,7 @@ pub fn get_char_bin(path: &str) -> Vec<u8> {
} }
impl Crtc { impl Crtc {
pub fn new(memory: Arc<Mutex<Mem>>) -> Self { pub fn new(memory: Arc<RwLock<Mem>>) -> Self {
let window = Window::new( let window = Window::new(
"ʕ·ᴥ·ʔ-☆", "ʕ·ᴥ·ʔ-☆",
512, 512,
@ -52,28 +52,45 @@ impl Crtc {
} }
} }
fn draw(&mut self) { fn draw(&mut self) {
let memory = match self.memory.lock() { match &mut self.memory.clone().try_read() {
Ok(memory) => memory, Ok(memory) => {
let hw_ctrl = memory.read(0x4000).unwrap();
match hw_ctrl & 0b0000_1000 == 0b0000_1000 {
false => self.draw_chars(),
true => self.draw_hires(),
};
}
Err(_) => { Err(_) => {
println!("Couldn't acquire read on shared memory in video thread"); println!("Couldn't acquire read on shared memory in main thread");
return; return;
} }
}; };
let hw_ctrl = memory.read(0x4000);
match hw_ctrl & 0b0000_1000 == 0b0000_1000 { self.window
false => { .update_with_buffer(&self.buffer, 512, 380)
.unwrap();
}
fn draw_chars(&mut self) {
let memory = match self.memory.try_read() {
Ok(read) => read,
Err(_) => {
println!("Couldn't acquire read on shared memory in main thread");
return;
}
};
// the rest of this function is arcane wizardry based on the specifics of george's weird // the rest of this function is arcane wizardry based on the specifics of george's weird
// display and characters... don't fuck around w it // display and characters... don't fuck around w it
let mut i = 0; let mut i = 0;
for char_row in 0..29 { for char_row in 0..29 {
for char_col in 0..64 { for char_col in 0..64 {
let ascii = memory.read(0x6000 + i); let ascii = memory.read(0x6000 + i).unwrap();
i += 1; i += 1;
for row in 0..13 { for row in 0..13 {
let byte = self.char_rom[ascii as usize + (row * 0x101)]; let byte = self.char_rom[ascii as usize + (row * 0x101)];
for i in (0..8).rev() { for i in (0..8).rev() {
let buffer_index = let buffer_index = ((char_row) * 13 + (row)) * 512 + (char_col * 8 + i);
((char_row) * 13 + (row)) * 512 + (char_col * 8 + i);
if (byte << i) & 0x80 == 0x80 { if (byte << i) & 0x80 == 0x80 {
self.buffer[buffer_index] = FG_COLOR; self.buffer[buffer_index] = FG_COLOR;
} else { } else {
@ -84,11 +101,18 @@ impl Crtc {
} }
} }
} }
true => { fn draw_hires(&mut self) {
let memory = match self.memory.try_read() {
Ok(read) => read,
Err(_) => {
println!("Couldn't acquire read on shared memory in main thread");
return;
}
};
for addr in 0x6000..0xBF00 { for addr in 0x6000..0xBF00 {
// TODO: eventually this will access memory in the weird interleaved way the hardware is // TODO: eventually this will access memory in the weird interleaved way the hardware is
// designed, but this is easiest to implement for now // designed, but this is easiest to implement for now
let byte = memory.read(addr); let byte = memory.read(addr).unwrap();
for i in 0..8 { for i in 0..8 {
match byte & 0x80 >> i == 0 { match byte & 0x80 >> i == 0 {
true => self.buffer[(addr - 0x6000) as usize * 8 + i] = BG_COLOR, true => self.buffer[(addr - 0x6000) as usize * 8 + i] = BG_COLOR,
@ -97,82 +121,13 @@ impl Crtc {
} }
} }
} }
};
self.window
.update_with_buffer(&self.buffer, 512, 380)
.unwrap();
}
pub fn run(&mut self) { pub fn run(&mut self) {
let frame_duration = Duration::from_millis(16); let frame_duration = Duration::from_millis(16);
let mut previous_draw = Instant::now(); let mut previous_draw = Instant::now();
loop { loop {
let mut row0 = 0; if self.window.is_key_down(Key::Q) {
let mut row1 = 0; return;
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(); let now = Instant::now();
if now - previous_draw > frame_duration { if now - previous_draw > frame_duration {