added data stack, breakpoints, and character coordinates :)
This commit is contained in:
parent
5b9312f643
commit
10559bde8b
File diff suppressed because it is too large
Load Diff
|
@ -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"
|
||||
|
|
26
src/cpu.rs
26
src/cpu.rs
|
@ -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) => {
|
||||
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;
|
||||
|
|
BIN
src/george
BIN
src/george
Binary file not shown.
221
src/george.asm
221
src/george.asm
|
@ -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"
|
||||
|
|
|
@ -8,4 +8,5 @@ MEMORY {
|
|||
|
||||
SEGMENTS {
|
||||
CODE: load = "PROGRAM", type = rw;
|
||||
VRAM: load = "VRAM", type = rw;
|
||||
}
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -56,4 +56,5 @@ fn main() {
|
|||
cpu.execute();
|
||||
});
|
||||
screen.run();
|
||||
shared_memory.write().unwrap().dump().unwrap();
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
31
src/video.rs
31
src/video.rs
|
@ -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();
|
||||
|
|
Loading…
Reference in New Issue