Compare commits
No commits in common. "705dcd31851f6677da1b021a8db51b951ca4d3b7" and "9808616203c7ce9dc56023fc40192f889d6093e2" have entirely different histories.
705dcd3185
...
9808616203
File diff suppressed because it is too large
Load Diff
|
@ -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"
|
||||||
|
|
25424
src/Cozette.sfd
25424
src/Cozette.sfd
File diff suppressed because it is too large
Load Diff
89
src/cpu.rs
89
src/cpu.rs
|
@ -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) {
|
||||||
|
|
BIN
src/george
BIN
src/george
Binary file not shown.
173
src/george.asm
173
src/george.asm
|
@ -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
|
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
40
src/main.rs
40
src/main.rs
|
@ -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();
|
||||||
}
|
}
|
||||||
|
|
118
src/memory.rs
118
src/memory.rs
|
@ -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(())
|
||||||
|
|
125
src/video.rs
125
src/video.rs
|
@ -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 {
|
||||||
|
|
Loading…
Reference in New Issue