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

Binary file not shown.

View File

@ -1,27 +1,65 @@
.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
n = $01 ; temporary storage for data stack operations
key_row = $200 ; used for character lookup when key pressed
key_col = $201
cursor = $202
.macro breakpoint ; $02 isn't a valid instruction, the emulator will see this and halt, dump memory contents
.byte $02
.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
;lda #$80
;sta $200
;adc $200
;breakpoint
jsr initdisplay
main:
jsr draw
initdisplay:
lda #$20
ldy #0
jsr cleardisplay
rts
cleardisplay:
sta $6000,y
@ -34,104 +72,27 @@ cleardisplay:
sta $6700,y ; this goes slightly over but it's fine
iny
bne cleardisplay
main:
;jsr draw
jsr keyboard
jmp main
keyboard:
ldy #0 ; loop through each row
@loop:
lda kb_row0, y
beq @skip ; if row has no key pressed, skip checking which key
jsr key_pressed
@skip:
iny
cpy #5
bne @loop
rts
key_pressed: ; a is loaded with the row byte
sty key_row ; store character row
inc cursor
pha
phy
ldy #0
@find_col: ; test each row bit, store column if key pressed
lsr ; test bit 7
bcs store_col ; if set, go store character column
iny
cpy #8
bne @find_col ; loop until we've checked each bit
store_col:
sty key_col
keymap_index:
draw: ; draw a character at (20, 30)
lda #63
push
lda key_col
stz 1, x
sta 0, x
sta 0, x ; low byte
stz 1,x ; high byte is zero
lda #28
push
lda #8
sta 0,x ; same here
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
lda #4
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),
; (n1: x n2: y -- n: $6000 + x + (64 * y))
;jsr push_lit ; push 64 onto stack, low byte first
;.byte 64
;.byte 0
pha
lda #64
push ; doing this instead until `push_lit` is fixed
sta 0, x
@ -148,14 +109,18 @@ get_char_address: ; gets vram address for a character at (x, y),
stz 0, x
jsr plus ; add vram start address to result
pla
rts
fill: ; fills an area from (x1, y1) to (x2, y2) will character c, (n1: c n2: x1 n3: y1 n4: x2 n5: y2 -- )
; 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
; 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
.segment "VECTOR"
.word reset
.segment "VRAM"

View File

@ -1,10 +1,12 @@
MEMORY {
RAM: start = $0000, size = $8000, type = ro, fill = yes;
ROM: start = $8000, size = $7FFC, type = ro, fill = yes;
VECTOR: start = $FFFC, size = $4, type = ro, fill = yes;
RAM: start = $0000, size = $0200, type = rw, fill = true;
PROGRAM: start = $0200, size = $3E00, type = rw, fill = true;
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 {
ROM: load = "ROM", type = ro;
VECTOR: load = "VECTOR", type = ro;
CODE: load = "PROGRAM", type = rw;
VRAM: load = "VRAM", type = rw;
}

View File

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

View File

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

View File

@ -3,23 +3,35 @@
mod cpu;
mod error;
mod instructions;
mod keyboard;
mod memory;
mod types;
mod video;
use crate::cpu::Cpu;
use crate::memory::Mem;
use crate::memory::{Mem, MemMappedDevice};
use crate::video::Crtc;
use std::str::FromStr;
use std::sync::Mutex;
use std::thread::sleep;
use std::time::Duration;
use std::{path::PathBuf, sync::Arc, thread};
use std::{
path::PathBuf,
sync::{Arc, RwLock},
thread,
};
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(
"/Users/kline/projects/winter/george-emu/src/george",
)) {
@ -29,20 +41,20 @@ fn main() {
if let Err(error) = memory.read_from_bin(binary) {
println!("{:?}", error);
};
memory.write(0xFFFC, 0x00).unwrap();
memory.write(0xFFFD, 0x02).unwrap();
memory
.dump(PathBuf::from_str("./coredump.bin").unwrap())
.unwrap();
let shared_memory = Arc::new(Mutex::new(memory));
let shared_memory = Arc::new(RwLock::new(memory));
let cpu_memory = shared_memory.clone();
let display_memory = shared_memory.clone();
let mut screen = Crtc::new(display_memory);
thread::spawn(move || {
let mut cpu = Cpu::new(cpu_memory);
cpu.reset().unwrap();
cpu.execute();
});
let mut screen = Crtc::new(display_memory);
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 std::io::{self, Write};
use std::path::PathBuf;
use std::u16;
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)]
pub struct Mem {
pub data: Vec<Byte>,
areas: Vec<MemMappedDevice>,
}
impl Mem {
pub fn new() -> Self {
Self {
data: vec![0; u16::MAX as usize + 1],
pub fn new(area: MemMappedDevice) -> Self {
Self { areas: vec![area] }
}
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<()> {
let mut outfile = File::create(path)?;
outfile.write_all(&self.data)?;
outfile.set_len(0xFFFF)?;
outfile.write_all(&data)?;
Ok(())
}
pub fn read(&self, address: Word) -> Byte {
self.data[address as usize]
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) {
self.data[address as usize] = data;
pub fn write(&mut self, address: Word, data: Byte) -> Result<(), MemoryError> {
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> {
let bytes = f.bytes();
for (address, byte) in bytes.enumerate() {
match byte {
Ok(value) => self.write(address as Word, value),
Err(_) => {
println!("couldn't write byte {:#04x}", address);
return Err(MemoryError::Unwritable);
}
Ok(value) => self.write(address as Word, value)?,
Err(_) => return Err(MemoryError::Unwritable),
}
}
Ok(())

View File

@ -3,7 +3,7 @@ use minifb::{Key, Scale, ScaleMode, Window, WindowOptions};
use std::{
fs::File,
io::Read,
sync::{Arc, Mutex},
sync::{Arc, RwLock},
thread::sleep,
time::{Duration, Instant},
};
@ -12,7 +12,7 @@ const FG_COLOR: u32 = 0xFFCC00;
const BG_COLOR: u32 = 0x110500;
pub struct Crtc {
memory: Arc<Mutex<Mem>>,
memory: Arc<RwLock<Mem>>,
buffer: Vec<u32>,
window: minifb::Window,
char_rom: Vec<u8>,
@ -26,7 +26,7 @@ pub fn get_char_bin(path: &str) -> Vec<u8> {
}
impl Crtc {
pub fn new(memory: Arc<Mutex<Mem>>) -> Self {
pub fn new(memory: Arc<RwLock<Mem>>) -> Self {
let window = Window::new(
"ʕ·ᴥ·ʔ-☆",
512,
@ -52,28 +52,45 @@ impl Crtc {
}
}
fn draw(&mut self) {
let memory = match self.memory.lock() {
Ok(memory) => memory,
match &mut self.memory.clone().try_read() {
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(_) => {
println!("Couldn't acquire read on shared memory in video thread");
println!("Couldn't acquire read on shared memory in main thread");
return;
}
};
let hw_ctrl = memory.read(0x4000);
match hw_ctrl & 0b0000_1000 == 0b0000_1000 {
false => {
self.window
.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
// 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);
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);
let buffer_index = ((char_row) * 13 + (row)) * 512 + (char_col * 8 + i);
if (byte << i) & 0x80 == 0x80 {
self.buffer[buffer_index] = FG_COLOR;
} 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 {
// 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);
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,
@ -97,82 +121,13 @@ impl Crtc {
}
}
}
};
self.window
.update_with_buffer(&self.buffer, 512, 380)
.unwrap();
}
pub fn run(&mut self) {
let frame_duration = Duration::from_millis(16);
let mut previous_draw = Instant::now();
loop {
let mut row0 = 0;
let mut row1 = 0;
let mut row2 = 0;
let mut row3 = 0;
let mut row4 = 0;
let mut row5 = 0;
self.window.get_keys().iter().for_each(|key| match key {
Key::Escape => row0 ^= 0b1000_0000,
Key::W => row0 ^= 0b0100_0000,
Key::E => row0 ^= 0b0010_0000,
Key::R => row0 ^= 0b0001_0000,
Key::T => row0 ^= 0b0000_1000,
Key::U => row0 ^= 0b0000_0100,
Key::O => row0 ^= 0b0000_0010,
Key::Backspace => row0 ^= 0b0000_0001,
Key::Tab => row1 ^= 0b1000_0000,
Key::Q => row1 ^= 0b0100_0000,
Key::S => row1 ^= 0b0010_0000,
Key::G => row1 ^= 0b0001_0000,
Key::Y => row1 ^= 0b0000_1000,
Key::I => row1 ^= 0b0000_0100,
Key::P => row1 ^= 0b0000_0010,
Key::Enter => row1 ^= 0b0000_0001,
Key::LeftShift | Key::RightShift => row2 ^= 0b1000_0000,
Key::D => row2 ^= 0b0100_0000,
Key::V => row2 ^= 0b0010_0000,
Key::H => row2 ^= 0b0001_0000,
Key::K => row2 ^= 0b0000_1000,
Key::Apostrophe => row2 ^= 0b0000_0100,
Key::Slash => row2 ^= 0b0000_0010,
Key::A => row2 ^= 0b0000_0001,
Key::LeftCtrl | Key::RightCtrl => row3 ^= 0b1000_0000,
Key::Z => row3 ^= 0b0100_0000,
Key::F => row3 ^= 0b0010_0000,
Key::B => row3 ^= 0b0001_0000,
Key::J => row3 ^= 0b0000_1000,
Key::L => row3 ^= 0b0000_0100,
Key::Key2 => row3 ^= 0b0000_0010,
Key::Key4 => row3 ^= 0b0000_0001,
Key::LeftAlt | Key::RightAlt => row4 ^= 0b1000_0000,
Key::X => row4 ^= 0b0100_0000,
Key::C => row4 ^= 0b0010_0000,
Key::N => row4 ^= 0b0001_0000,
Key::M => row4 ^= 0b0000_1000,
Key::Comma => row4 ^= 0b0000_0100,
Key::Key1 => row4 ^= 0b0000_0010,
Key::Key3 => row4 ^= 0b0000_0001,
Key::LeftSuper => row5 ^= 0b1000_0000,
Key::Space => row5 ^= 0b0100_0000,
Key::RightSuper => row5 ^= 0b0010_0000,
_ => {}
});
match &mut self.memory.lock() {
Ok(memory) => {
memory.write(0x4400, row0);
memory.write(0x4401, row1);
memory.write(0x4402, row2);
memory.write(0x4403, row3);
memory.write(0x4404, row4);
memory.write(0x4405, row5);
}
Err(error) => println!("{error}"),
if self.window.is_key_down(Key::Q) {
return;
}
let now = Instant::now();
if now - previous_draw > frame_duration {