Compare commits

..

2 Commits

Author SHA1 Message Date
august kline 705dcd3185 we can type all the letters! 2024-03-26 20:56:04 -04:00
august kline 40ede17ae1 we can type the letter w! 2024-03-03 13:55:56 -05:00
12 changed files with 1591 additions and 27676 deletions

2940
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -8,5 +8,4 @@ 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,14 +1,12 @@
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::{self, Mem}; use crate::memory::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::{ use std::{str::FromStr, sync::Arc, thread::sleep};
str::FromStr,
sync::{Arc, RwLock},
thread::sleep,
};
#[derive(Clone, Copy)] #[derive(Clone, Copy)]
pub enum StatusFlag { pub enum StatusFlag {
@ -31,13 +29,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<RwLock<Mem>>, pub memory: Arc<Mutex<Mem>>,
pub pending_cycles: usize, pub pending_cycles: usize,
cycle_count: usize, cycle_count: usize,
} }
impl Cpu { impl Cpu {
pub fn new(memory: Arc<RwLock<Mem>>) -> Self { pub fn new(memory: Arc<Mutex<Mem>>) -> Self {
Cpu { Cpu {
a: 0x00, a: 0x00,
x: 0x00, x: 0x00,
@ -59,14 +57,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.try_read() { 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"); 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
}; };
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, MemoryError> {
let low_byte = self.read(address)?; let low_byte = self.read(address)?;
@ -95,7 +93,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 += 0x1; self.s = self.s.wrapping_add(0x1);
Ok(byte) Ok(byte)
} }
@ -121,21 +119,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.write() { 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"); 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().unwrap(); self.cycle();
while self.pending_cycles != 0 { while self.pending_cycles != 0 {
self.cycle().unwrap(); self.cycle();
} }
} }
@ -166,7 +164,7 @@ impl Cpu {
// } // }
// } // }
// } // }
pub fn cycle(&mut self) -> Result<(), GeorgeError> { pub fn cycle(&mut self) {
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)) {
@ -178,10 +176,11 @@ 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) => {
return Err(GeorgeError { let george_error = 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;
@ -189,10 +188,11 @@ 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) => {
return Err(GeorgeError { let george_error = 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,62 +204,73 @@ 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) => {
return Err(GeorgeError { let george_error = GeorgeError {
desc: String::from_str("Failed to read the program counter!").unwrap(), desc: format!("Failed to read from memory at address {:#06x}!", self.pc),
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) => return Err(GeorgeError{ Err(error) => {
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.try_read() { 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"); println!("Couldn't acquire read lock on memory in cpu thread");
return Err(GeorgeError { let george_error = 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);
} }
_ => { _ => {
return Err(GeorgeError { let george_error = 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,65 +1,27 @@
.setcpu "65C02" .setcpu "65C02"
.segment "CODE" .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 ; 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
.macro breakpoint ; $02 isn't a valid instruction, the emulator will see this and halt, dump memory contents key_row = $200 ; used for character lookup when key pressed
.byte $02 key_col = $201
.endmacro cursor = $202
.macro pop ; drops a stack cell kb_row0 = $4400 ; keyboard hardware register, there are 5 more but i can just increment from here
inx
inx
.endmacro
.macro pop2 ; drops 2 data stack cells .segment "ROM"
inx
inx
inx
inx
.endmacro
.macro push ; reset:
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
jsr initdisplay ;lda #$80
;sta $200
main: ;adc $200
jsr draw ;breakpoint
initdisplay: initdisplay:
lda #$20 lda #$20
ldy #0 ldy #0
jsr cleardisplay
rts
cleardisplay: cleardisplay:
sta $6000,y sta $6000,y
@ -72,27 +34,104 @@ 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
draw: ; draw a character at (20, 30) key_pressed: ; a is loaded with the row byte
lda #63 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 push
sta 0, x ; low byte lda key_col
stz 1,x ; high byte is zero stz 1, x
lda #28 sta 0, x
push push
sta 0,x ; same here lda #8
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
brk rts
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
@ -109,18 +148,14 @@ 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 jsr get_char_address
; add 1 to a 16-bit pointer in zero page
;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
@ -198,6 +233,8 @@ mult: ; multiply: (n1 n2 -- n1*n2), frankly, i don't know how this works, but TO
pop pop
ply ply
rts rts
.segment "VRAM"
.segment "VECTOR"
.word reset

View File

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

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 0x0295 # ʕ 0x00 0x00 #
0x01 0x00B7 # Middle Dot 0x01 0x2591 #
0x02 0x1D25 # ᴥ 0x02 0x2592 # ᴥ
0x03 0x0294 # ʔ 0x03 0x2593 # ʔ
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 0x2591 # 0xB6 0x0295 #
0xB7 0x2592 # 0xB7 0x00B7 #
0xB8 0x2593 # 0xB8 0x1D25 #
0xB9 0x2596 # 0xB9 0x0294
0xBA 0x2597 # 0xBA 0x2596
0xBB 0x2598 # 0xBB 0x2597
0xBC 0x2599 # 0xBC 0x2598
0xBD 0x259A # 0xBD 0x2599
0xBE 0x259B # 0xBE 0x259A
0xBF 0x259C # 0xBF 0x259B
0xC0 0x259D # 0xC0 0x259C
0xC1 0x259E # 0xC1 0x259D
0xC2 0x259F # 0xC2 0x259E
0xC3 0x2190 # 0xC3 0x259F
0xC4 0x2191 # 0xC4 0x2190
0xC5 0x2192 # 0xC5 0x2191
0xC6 0x2193 # 0xC6 0x2192
0xC7 0x2B60 # 0xC7 0x2193
0xC8 0x2B61 # 0xC8 0x2B60
0xC9 0x2B62 # 0xC9 0x2B61
0xCA 0x2B63 # 0xCA 0x2B62
0xCB 0x2B80 # 0xCB 0x2B63
0xCC 0x2B81 # 0xCC 0x2B80
0xCD 0x2B82 # 0xCD 0x2B81
0xCE 0x2B83 # 0xCE 0x2B82
0xCF 0xF049 # 0xCF 0x2B83
0xD0 0xF04A # 0xD0 0xF049
0xD1 0x23F3 # 0xD1 0xF04A
0xD2 0xF07B # 0xD2 0x23F3
0xD3 0xF07C # 0xD3 0xF07B
0xD4 0xF114 # 0xD4 0xF07C
0xD5 0xF115 # 0xD5 0xF114
0xD6 0xF250 # 0xD6 0xF115
0xD7 0xF251 # 0xD7 0xF250
0xD8 0xF253 # 0xD8 0xF251
0xD9 0xF254 # 0xD9 0xF253
0xDA 0xF461 # 0xDA 0xF254
0xDB 0xF016 # 0xDB 0xF461
0xDC 0xF401 # 0xDC 0xF016
0xDD 0x1F52E # 0xDD 0xF401
0xDE 0xF2DB # 0xDE 0x1F52E
0xDF 0xF008 # 0xDF 0xF2DB
0xE0 0x25C7 # 0xE0 0xF008
0xE1 0x25C8 # 0xE1 0x25C7
0xE2 0x1F311 # 0xE2 0x25C8
0xE3 0x1F312 # 0xE3 0x1F311
0xE4 0x1F313 # 0xE4 0x1F312
0xE5 0x1F314 # 0xE5 0x1F313
0xE6 0x1F315 # 0xE6 0x1F314
0xE7 0x1F316 # 0xE7 0x1F315
0xE8 0x1F317 # 0xE8 0x1F316
0xE9 0x1F318 # 0xE9 0x1F317
0xEA 0xF04C # 0xEA 0x1F318
0xEB 0x2714 # 0xEB 0xF04C
0xEC 0x2718 # 0xEC 0x2714
0xED 0x25C6 # 0xED 0x2718
0xEE 0xF15D # 0xEE 0x25C6
0xEF 0xF15E # 0xEF 0xF15D
0xF0 0xF071 # 0xF0 0xF15E
0xF1 0xF449 # 0xF1 0xF071
0xF2 0xF529 # 0xF2 0xF449
0xF3 0xF658 # 0xF3 0xF529
0xF4 0xF659 # 0xF4 0xF658
0xF5 0x0020 # 0xF5 0xF659 #
0xF6 0x1f381 # Space 0xF6 0x1f381 # Space
0xF7 0xf05a # Space 0xF7 0xf05a # Space
0xF8 0xf06a # Space 0xF8 0xf06a # Space

View File

@ -2063,6 +2063,7 @@ 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(
@ -2073,6 +2074,7 @@ 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(
@ -2103,10 +2105,7 @@ 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( cpu.set_flag(StatusFlag::Negative, cpu.is_negative(shifted_value));
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
} }
@ -2532,7 +2531,8 @@ 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);
panic!("Interrupts unimplemented!"); Ok(())
//panic!("Interrupts unimplemented!");
} }
_ => Err(GeorgeErrorKind::AddrMode( _ => Err(GeorgeErrorKind::AddrMode(
AddressingModeError::IncompatibleAddrMode, AddressingModeError::IncompatibleAddrMode,
@ -2662,6 +2662,8 @@ 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(
@ -2670,7 +2672,9 @@ impl Opcode {
}, },
Opcode::DEY(mode) => match mode { Opcode::DEY(mode) => match mode {
AddressingMode::Implied => { AddressingMode::Implied => {
cpu.y -= 1; cpu.y = cpu.y.wrapping_sub(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(
@ -2824,11 +2828,8 @@ 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 {
@ -3048,10 +3049,7 @@ 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( cpu.set_flag(StatusFlag::Negative, cpu.is_negative(shifted_value));
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
} }
@ -3080,10 +3078,7 @@ 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( cpu.set_flag(StatusFlag::Negative, cpu.is_negative(shifted_value));
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,35 +3,23 @@
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, MemMappedDevice}; use crate::memory::Mem;
use crate::video::Crtc; use crate::video::Crtc;
use std::{ use std::str::FromStr;
path::PathBuf, use std::sync::Mutex;
sync::{Arc, RwLock}, use std::thread::sleep;
thread, use std::time::Duration;
}; use std::{path::PathBuf, sync::Arc, thread};
fn main() { fn main() {
let ram = MemMappedDevice::new(0x0000, 0x3FFF, 2, "ram".into()); let mut memory = Mem::new();
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",
)) { )) {
@ -41,20 +29,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();
let shared_memory = Arc::new(RwLock::new(memory)); 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,124 +1,44 @@
use crate::error::{GeorgeError, GeorgeErrorKind, MappingError, MemoryError}; use crate::error::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 {
areas: Vec<MemMappedDevice>, pub data: Vec<Byte>,
} }
impl Mem { impl Mem {
pub fn new(area: MemMappedDevice) -> Self { pub fn new() -> Self {
Self { areas: vec![area] } Self {
} 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());
}
} }
outfile.set_len(0xFFFF)?; }
outfile.write_all(&data)?; pub fn dump(&self, path: PathBuf) -> io::Result<()> {
let mut outfile = File::create(path)?;
outfile.write_all(&self.data)?;
Ok(()) Ok(())
} }
pub fn add_area(&mut self, area: MemMappedDevice) -> Result<(), GeorgeError> {
for existing_area in &self.areas {
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) -> Result<(), MemoryError> { pub fn read(&self, address: Word) -> Byte {
for area in self.areas.iter_mut() { self.data[address as usize]
if area.contains(address) { }
// println!("Writing to area {label}", label = area.label);
let translated_address = area.translate_address(address); pub fn write(&mut self, address: Word, data: Byte) {
area.data[translated_address as usize] = data; self.data[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(_) => return Err(MemoryError::Unwritable), Err(_) => {
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, RwLock}, sync::{Arc, Mutex},
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<RwLock<Mem>>, memory: Arc<Mutex<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<RwLock<Mem>>) -> Self { pub fn new(memory: Arc<Mutex<Mem>>) -> Self {
let window = Window::new( let window = Window::new(
"ʕ·ᴥ·ʔ-☆", "ʕ·ᴥ·ʔ-☆",
512, 512,
@ -52,82 +52,127 @@ impl Crtc {
} }
} }
fn draw(&mut self) { fn draw(&mut self) {
match &mut self.memory.clone().try_read() { let memory = match self.memory.lock() {
Ok(memory) => { Ok(memory) => 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 main thread"); println!("Couldn't acquire read on shared memory in video thread");
return; return;
} }
}; };
let hw_ctrl = memory.read(0x4000);
match hw_ctrl & 0b0000_1000 == 0b0000_1000 {
false => {
// the rest of this function is arcane wizardry based on the specifics of george's weird
// 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;
}
}
}
}
}
}
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 self.window
.update_with_buffer(&self.buffer, 512, 380) .update_with_buffer(&self.buffer, 512, 380)
.unwrap(); .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
// 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).unwrap();
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;
}
}
}
}
}
}
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 {
// 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).unwrap();
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,
}
}
}
}
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 {
if self.window.is_key_down(Key::Q) { let mut row0 = 0;
return; 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(); let now = Instant::now();
if now - previous_draw > frame_duration { if now - previous_draw > frame_duration {