we can type all the letters!

This commit is contained in:
august kline 2024-03-26 20:56:04 -04:00
parent 40ede17ae1
commit 705dcd3185
10 changed files with 1415 additions and 24678 deletions

File diff suppressed because it is too large Load Diff

View File

@ -2,14 +2,11 @@ use crate::error::{ExecutionError, GeorgeError, GeorgeErrorKind, MemoryError};
use crate::instructions::{get_instruction, Instruction};
use crate::memory::Mem;
use crate::types::{Byte, Word};
use std::path::PathBuf;
use std::process::exit;
use std::sync::Mutex;
use std::time::Duration;
use std::{
str::FromStr,
sync::{Arc, RwLock},
thread::sleep,
};
use std::{str::FromStr, sync::Arc, thread::sleep};
#[derive(Clone, Copy)]
pub enum StatusFlag {
@ -67,7 +64,7 @@ impl Cpu {
return Err(MemoryError::NoDataAtAddress);
} // 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> {
let low_byte = self.read(address)?;
@ -96,7 +93,7 @@ impl Cpu {
pub fn pop_stack(&mut self) -> Result<Byte, ExecutionError> {
let byte = self.read(self.stack_addr())?;
self.s += 0x1;
self.s = self.s.wrapping_add(0x1);
Ok(byte)
}
@ -129,7 +126,7 @@ impl Cpu {
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(())
}
@ -252,7 +249,11 @@ impl Cpu {
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);
}
_ => {

Binary file not shown.

View File

@ -1,84 +1,27 @@
.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
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
.byte $02
.endmacro
key_row = $200 ; used for character lookup when key pressed
key_col = $201
cursor = $202
.macro pop ; drops a data stack cell
inx
inx
.endmacro
kb_row0 = $4400 ; keyboard hardware register, there are 5 more but i can just increment from here
.macro pop2 ; drops 2 data stack cells
inx
inx
inx
inx
.endmacro
.segment "ROM"
.macro push ; push a data stack cell
dex
dex
.endmacro
.macro push2 ; push 2 data stack cells
dex
dex
dex
dex
.endmacro
.macro push_char char ; pushes an ascii character code onto the stack
lda char
push
sta 0, x ; char low byte
stz 1, x ; char high byte
.endmacro
.macro push_coords coord_x, coord_y ; push a set of (x,y) coordinates onto the data stack
lda coord_x
push
sta 0, x ; low byte
stz 1,x ; high byte is zero
lda coord_y
push
sta 0,x ; same here
stz 1,x
.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:
reset:
ldx #0; initialize data stack pointer
jsr initdisplay
main:
jsr draw
jmp main
;lda #$80
;sta $200
;adc $200
;breakpoint
initdisplay:
lda #$20
ldy #0
jsr cleardisplay
rts
cleardisplay:
sta $6000,y
@ -91,16 +34,89 @@ 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:
push
lda key_col
stz 1, x
sta 0, x
push
lda #8
stz 1, x
sta 0, x
push
lda key_row
stz 1, x
sta 0, x
jsr mult
jsr plus
lda 0, x
tay
do_something_w_key: ; we've stored the character position, now let's
lda keymap, y
ldy cursor
sta $6000, y
pla
ply
rts
keymap:
.byte "?outrew?"
.byte "?piygsq?"
.byte "a??khvd?"
.byte "42ljbfz?"
.byte "31?mncx?"
.byte "?????ssm"
draw:
lda #%01000000
bit $4400
beq @1
push_coords #0, #0
push_char #$77
jsr draw_char
@1:
; lda #%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 -- )
@ -217,6 +233,8 @@ mult: ; multiply: (n1 n2 -- n1*n2), frankly, i don't know how this works, but TO
pop
ply
rts
.segment "VRAM"
.segment "VECTOR"
.word reset

View File

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

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 0x0295 # ʕ
0x01 0x00B7 # Middle Dot
0x02 0x1D25 # ᴥ
0x03 0x0294 # ʔ
0x00 0x00 #
0x01 0x2591 #
0x02 0x2592 # ᴥ
0x03 0x2593 # ʔ
0x04 0x2661 # White heart
0x05 0x2665 # Black heart
0x06 0x2B50 # White star
@ -189,70 +189,70 @@
0xB3 0x258D #
0xB4 0x258E #
0xB5 0x258F #
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 #
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 #
0xF6 0x1f381 # Space
0xF7 0xf05a # Space
0xF8 0xf06a # Space

View File

@ -2063,6 +2063,7 @@ 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(
@ -2073,6 +2074,7 @@ 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(
@ -2103,10 +2105,7 @@ 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,
value & StatusFlag::Negative as Byte > 0,
);
cpu.set_flag(StatusFlag::Negative, cpu.is_negative(shifted_value));
cpu.set_flag(StatusFlag::Zero, shifted_value == 0);
shifted_value
}
@ -2532,7 +2531,8 @@ impl Opcode {
Opcode::BRK(mode) => match mode {
AddressingMode::Implied => {
cpu.set_flag(StatusFlag::Brk, true);
panic!("Interrupts unimplemented!");
Ok(())
//panic!("Interrupts unimplemented!");
}
_ => Err(GeorgeErrorKind::AddrMode(
AddressingModeError::IncompatibleAddrMode,
@ -2662,6 +2662,8 @@ 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(
@ -2670,7 +2672,9 @@ impl Opcode {
},
Opcode::DEY(mode) => match mode {
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(())
}
_ => Err(GeorgeErrorKind::AddrMode(
@ -2824,11 +2828,8 @@ 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 {
@ -3048,10 +3049,7 @@ 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,
shifted_value & StatusFlag::Negative as Byte > 0,
);
cpu.set_flag(StatusFlag::Negative, cpu.is_negative(shifted_value));
cpu.set_flag(StatusFlag::Zero, shifted_value == 0);
shifted_value
}
@ -3080,10 +3078,7 @@ 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,
shifted_value & StatusFlag::Negative as Byte > 0,
);
cpu.set_flag(StatusFlag::Negative, cpu.is_negative(shifted_value));
cpu.set_flag(StatusFlag::Zero, shifted_value == 0);
shifted_value
}

View File

@ -9,31 +9,17 @@ mod types;
mod video;
use crate::cpu::Cpu;
use crate::memory::{Mem, MemMappedDevice};
use crate::memory::Mem;
use crate::video::Crtc;
use std::str::FromStr;
use std::sync::Mutex;
use std::{
path::PathBuf,
sync::{Arc, RwLock},
thread,
};
use std::thread::sleep;
use std::time::Duration;
use std::{path::PathBuf, sync::Arc, thread};
fn main() {
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 mut memory = Mem::new();
let binary = match std::fs::File::open(PathBuf::from(
"/Users/kline/projects/winter/george-emu/src/george",
)) {
@ -43,8 +29,10 @@ 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 cpu_memory = shared_memory.clone();
@ -57,5 +45,4 @@ fn main() {
});
let mut screen = Crtc::new(display_memory);
screen.run();
shared_memory.lock().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 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 {
areas: Vec<MemMappedDevice>,
pub data: Vec<Byte>,
}
impl Mem {
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 new() -> Self {
Self {
data: vec![0; u16::MAX as usize + 1],
}
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(())
}
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> {
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(&self, address: Word) -> Byte {
self.data[address as usize]
}
pub fn write(&mut self, address: Word, data: Byte) {
self.data[address as usize] = data;
}
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(_) => return Err(MemoryError::Unwritable),
Ok(value) => self.write(address as Word, value),
Err(_) => {
println!("couldn't write byte {:#04x}", address);
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, RwLock},
sync::{Arc, Mutex},
thread::sleep,
time::{Duration, Instant},
};
@ -59,7 +59,7 @@ impl Crtc {
return;
}
};
let hw_ctrl = memory.read(0x4000).unwrap();
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
@ -67,7 +67,7 @@ impl Crtc {
let mut i = 0;
for char_row in 0..29 {
for char_col in 0..64 {
let ascii = memory.read(0x6000 + i).unwrap();
let ascii = memory.read(0x6000 + i);
i += 1;
for row in 0..13 {
let byte = self.char_rom[ascii as usize + (row * 0x101)];
@ -88,7 +88,7 @@ impl Crtc {
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();
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,
@ -165,12 +165,12 @@ impl Crtc {
match &mut self.memory.lock() {
Ok(memory) => {
memory.write(0x4400, row0).unwrap();
memory.write(0x4401, row1).unwrap();
memory.write(0x4402, row2).unwrap();
memory.write(0x4403, row3).unwrap();
memory.write(0x4404, row4).unwrap();
memory.write(0x4405, row5).unwrap();
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}"),
}