added data stack, breakpoints, and character coordinates :)

This commit is contained in:
august kline 2024-02-21 23:27:44 -05:00
parent 5b9312f643
commit 10559bde8b
10 changed files with 3200 additions and 64 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"

View File

@ -1,7 +1,8 @@
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::process::exit;
use std::time::Duration;
use std::{
str::FromStr,
@ -166,7 +167,7 @@ impl Cpu {
// }
// }
pub fn cycle(&mut self) -> Result<(), GeorgeError> {
sleep(Duration::from_nanos(500));
sleep(Duration::from_nanos(100));
if self.pending_cycles == 0 {
if self.nmi || (self.irq && !self.get_flag(StatusFlag::IrqDisable)) {
let _ = self.push_stack_word(self.pc);
@ -228,15 +229,32 @@ impl Cpu {
self.pc, valid_instruction).to_string(), kind: error})
};
}
Instruction::Invalid(invalid_instruction) => {
return Err(GeorgeError {
kind: GeorgeErrorKind::Execution(ExecutionError::InvalidInstruction),
desc: format!(
Instruction::Invalid(invalid_instruction) => match invalid_instruction.opcode {
0x02 => {
let memory = match self.memory.try_read() {
Ok(read) => read,
Err(_) => {
println!("Couldn't acquire read lock on memory in cpu thread");
return Err(GeorgeError {
kind: GeorgeErrorKind::Memory(MemoryError::Unwritable),
desc: "Couldn't acquire read lock on memory in cpu thread"
.to_string(),
});
}
};
memory.dump().unwrap();
exit(1);
}
_ => {
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
),
})
}
});
}
},
}
}
self.pending_cycles -= 1;

Binary file not shown.

View File

@ -1,25 +1,204 @@
.setcpu "65C02"
.segment "CODE"
LDA #$60
STA $01
LDY #$0
fill:
LDA #$20
STY $00
STA ($00)
INY
CPY #$ff
BNE fill
; 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
.macro breakpoint ; $02 isn't a valid instruction, the emulator will see this and halt, dump memory contents
.byte $02
.endmacro
.macro pop ; drops a stack cell
inx
inx
.endmacro
.macro pop2 ; drops 2 data stack cells
inx
inx
inx
inx
.endmacro
.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
jsr initdisplay
main:
LDY #$0
STY $6000
LDY #$1
STY $6001
LDY #$2
STY $6002
LDY #$1
STY $6003
LDY #$3
STY $6004
JMP main
jsr draw
initdisplay:
lda #$20
ldy #0
jsr cleardisplay
rts
cleardisplay:
sta $6000,y
sta $6100,y
sta $6200,y
sta $6300,y
sta $6400,y
sta $6500,y
sta $6600,y
sta $6700,y ; this goes slightly over but it's fine
iny
bne cleardisplay
rts
; TODO: get this to work, (also i think emu-side i need better tooling), rn it just draws a heart to $6000, so i think the addition isn't working or something
draw: ; draw a character at (20, 30)
lda #63
push
sta 0, x ; low byte
stz 1,x ; high byte is zero
lda #28
push
sta 0,x ; same here
stz 1,x
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
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
lda #64
push ; doing this instead until `push_lit` is fixed
sta 0, x
stz 1, x
jsr mult ; multiply 64 with y (n2)
jsr plus ; add result with x (n1)
;jsr push_lit ; push vram address onto the stack
;.byte $00
;.byte $60
lda #$60
push
sta 1, x
stz 0, x
jsr plus ; add vram start address to result
rts
; inc16
; add 1 to a 16-bit pointer in zero page
;inc16:
; inc ptr
; bne :+
; inc ptr+1
;: rts
;
; on this channel we love garth wilson: https://wilsonminesco.com/stacks/StackOps.ASM
; data stack is built up of 2-byte cells
; TODO: this is broken, the return address gets mangled somewhere in here, could be an emulator problem tho
push_lit: ; this bad boy lets you inline a literal (low byte first) right after `jsr push_lit` and put it on the stack, once again, on this channel we love garth wilson
push2
phx
tsx
txa
tay
plx
lda $102, y
sta 0, x
clc
adc #2
sta $102, y
lda $103, y
sta 1, x
adc #0
sta $103, y
fetch:
lda (0, x)
pha
inc 0, x
bne @1
inc 1, x
@1: lda (0, x)
bra put
push
put:
sta 1, x
pla
sta 0, x
rts
plus: ; add: (n1 n2 -- n1+n2)
clc
lda 0, x
adc 2, x
sta 2, x
lda 1, x
adc 3, x
sta 3, x
pop
rts
mult: ; multiply: (n1 n2 -- n1*n2), frankly, i don't know how this works, but TODO: will try to figure it out later
phy
stz n
ldy #0
@1: lsr 3, x
ror 2, x
bcc @2
clc
lda n
adc 0, x
sta n
tya
adc 1, x
tay
@2: asl 0, x
rol 1, x
lda 2, x
ora 3, x
bne @1
lda n
sta 2, x
sty 3, x
pop
ply
rts
.segment "VRAM"

View File

@ -8,4 +8,5 @@ MEMORY {
SEGMENTS {
CODE: load = "PROGRAM", type = rw;
VRAM: load = "VRAM", type = rw;
}

View File

@ -1900,7 +1900,7 @@ fn get_address(
) -> Result<AddressingModeValue, ExecutionError> {
let byte = cpu.read(cpu.pc)?;
cpu.pc += 1;
let address: Word = (byte + cpu.x) as Word;
let address: Word = (byte.wrapping_add(cpu.x)) as Word;
Ok(AddressingModeValue::Absolute(address))
}
@ -2072,6 +2072,7 @@ impl Opcode {
// check out https://docs.rs/emulator_6502/latest/src/emulator_6502/opcodes/mod.rs.html
);
cpu.set_flag(StatusFlag::Negative, cpu.is_negative(result as Byte));
cpu.a = result as Byte;
Ok(())
}
_ => Err(GeorgeErrorKind::AddrMode(
@ -2607,7 +2608,7 @@ impl Opcode {
let byte = cpu.read(address.try_into()?)?;
cpu.set_flag(StatusFlag::Carry, cpu.a >= byte);
cpu.set_flag(StatusFlag::Zero, cpu.a == byte);
cpu.set_flag(StatusFlag::Negative, cpu.is_negative(cpu.a - byte));
cpu.set_flag(StatusFlag::Negative, cpu.a <= byte);
Ok(())
}
_ => Err(GeorgeErrorKind::AddrMode(
@ -2660,7 +2661,7 @@ impl Opcode {
},
Opcode::DEX(mode) => match mode {
AddressingMode::Implied => {
cpu.x -= 1;
cpu.x = cpu.x.wrapping_sub(1);
Ok(())
}
_ => Err(GeorgeErrorKind::AddrMode(
@ -2698,7 +2699,7 @@ impl Opcode {
},
Opcode::INC(mode) => match mode {
AddressingMode::Accumulator => {
cpu.a += 1;
cpu.a = cpu.a.wrapping_add(1);
cpu.set_flag(StatusFlag::Zero, cpu.a == 0);
cpu.set_flag(StatusFlag::Negative, cpu.is_negative(cpu.a));
Ok(())
@ -2720,7 +2721,9 @@ impl Opcode {
},
Opcode::INX(mode) => match mode {
AddressingMode::Implied => {
cpu.x += 1;
cpu.x = cpu.x.wrapping_add(1);
cpu.set_flag(StatusFlag::Zero, cpu.x == 0);
cpu.set_flag(StatusFlag::Negative, cpu.is_negative(cpu.x));
Ok(())
}
_ => Err(GeorgeErrorKind::AddrMode(
@ -2729,7 +2732,9 @@ impl Opcode {
},
Opcode::INY(mode) => match mode {
AddressingMode::Implied => {
cpu.y += 1;
cpu.y = cpu.y.wrapping_add(1);
cpu.set_flag(StatusFlag::Zero, cpu.y == 0);
cpu.set_flag(StatusFlag::Negative, cpu.is_negative(cpu.y));
Ok(())
}
_ => Err(GeorgeErrorKind::AddrMode(
@ -3113,9 +3118,9 @@ impl Opcode {
)),
},
Opcode::RTS(mode) => match mode {
AddressingMode::Implied => {
AddressingMode::Stack => {
let return_address = cpu.pop_stack_word()?;
cpu.pc = return_address + 1;
cpu.pc = return_address + 3; // Go back to where we jsr'ed, skipping the operand
Ok(())
}
_ => Err(GeorgeErrorKind::AddrMode(

View File

@ -56,4 +56,5 @@ fn main() {
cpu.execute();
});
screen.run();
shared_memory.write().unwrap().dump().unwrap();
}

View File

@ -1,5 +1,7 @@
use crate::error::{GeorgeError, GeorgeErrorKind, MappingError, MemoryError};
use crate::types::{Byte, Word};
use std::io::{self, Write};
use std::path::PathBuf;
use std::{fs::File, io::Read};
#[derive(Debug, Clone)]
@ -59,6 +61,18 @@ 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());
}
}
outfile.set_len(0xFFFF)?;
outfile.write_all(&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) {

View File

@ -1,5 +1,5 @@
use crate::Mem;
use minifb::{Window, WindowOptions};
use minifb::{Key, Scale, ScaleMode, Window, WindowOptions};
use std::{
fs::File,
io::Read,
@ -27,7 +27,22 @@ pub fn get_char_bin(path: &str) -> Vec<u8> {
impl Crtc {
pub fn new(memory: Arc<RwLock<Mem>>) -> Self {
let window = Window::new("screen", 512, 380, WindowOptions::default()).unwrap();
let window = Window::new(
"ʕ·ᴥ·ʔ-☆",
512,
380,
WindowOptions {
resize: true,
borderless: true,
title: true,
transparency: false,
scale: Scale::X2,
scale_mode: ScaleMode::AspectRatioStretch,
topmost: false,
none: true,
},
)
.unwrap();
let char_rom = get_char_bin("./src/cozette.bin");
Self {
memory,
@ -64,11 +79,14 @@ impl Crtc {
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 + (char_row as u16 * char_col as u16))
.unwrap();
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() {
@ -108,6 +126,9 @@ impl Crtc {
let mut previous_draw = Instant::now();
loop {
if self.window.is_key_down(Key::Q) {
return;
}
let now = Instant::now();
if now - previous_draw > frame_duration {
self.draw();