Add new testcases with cc65 support
Some checks failed
Cargo Build & Test / Rust project - latest (stable) (push) Failing after 8s
Some checks failed
Cargo Build & Test / Rust project - latest (stable) (push) Failing after 8s
This commit is contained in:
78
build.rs
78
build.rs
@@ -1,4 +1,31 @@
|
||||
use std::{io::Read, process::Stdio};
|
||||
use std::{
|
||||
fmt::Arguments,
|
||||
io::Read,
|
||||
process::{Command, Stdio},
|
||||
};
|
||||
|
||||
fn run(name: Arguments, cmd: &mut Command) {
|
||||
let mut proc = cmd
|
||||
.current_dir("src/test_roms")
|
||||
.stdout(Stdio::piped())
|
||||
.stderr(Stdio::piped())
|
||||
.spawn()
|
||||
.expect("Failed to start process");
|
||||
let rc = proc.wait();
|
||||
if rc.is_err() || rc.is_ok_and(|s| !s.success()) {
|
||||
let mut stdout = String::new();
|
||||
proc.stdout
|
||||
.unwrap()
|
||||
.read_to_string(&mut stdout)
|
||||
.expect("Failed to read stdout");
|
||||
let mut stderr = String::new();
|
||||
proc.stderr
|
||||
.unwrap()
|
||||
.read_to_string(&mut stderr)
|
||||
.expect("Failed to read stderr");
|
||||
panic!("Failed to run {name}\n=== STDOUT ===\n{stdout}\n=== STDERR ===\n{stderr}");
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
println!("cargo::rerun-if-changed=src/test_roms/");
|
||||
@@ -16,30 +43,31 @@ fn main() {
|
||||
let file_name = file.file_name();
|
||||
let file_name = file_name.to_str().unwrap();
|
||||
if let Some(file_name) = file_name.strip_suffix(".asm") {
|
||||
let mut proc = std::process::Command::new("./asm6f")
|
||||
.arg(file.file_name())
|
||||
.arg(format!("{name}/{file_name}.nes"))
|
||||
.current_dir("src/test_roms")
|
||||
.stdout(Stdio::piped())
|
||||
.stderr(Stdio::piped())
|
||||
.spawn()
|
||||
.expect("Failed to start process");
|
||||
let rc = proc.wait();
|
||||
if rc.is_err() || rc.is_ok_and(|s| !s.success()) {
|
||||
let mut stdout = String::new();
|
||||
proc.stdout
|
||||
.unwrap()
|
||||
.read_to_string(&mut stdout)
|
||||
.expect("Failed to read stdout");
|
||||
let mut stderr = String::new();
|
||||
proc.stderr
|
||||
.unwrap()
|
||||
.read_to_string(&mut stderr)
|
||||
.expect("Failed to read stderr");
|
||||
panic!(
|
||||
"Failed to compile {file_name}\n=== STDOUT ===\n{stdout}\n=== STDERR ===\n{stderr}"
|
||||
);
|
||||
}
|
||||
run(
|
||||
format_args!("asm6f {file_name}"),
|
||||
Command::new("./asm6f")
|
||||
.arg(file.file_name())
|
||||
.arg(format!("{name}/{file_name}.nes")),
|
||||
);
|
||||
} else if let Some(file_name) = file_name.strip_suffix(".s") {
|
||||
run(
|
||||
format_args!("ca65 {file_name}"),
|
||||
Command::new("/home/matthew/cc65/bin/ca65")
|
||||
.arg("-I")
|
||||
.arg("common")
|
||||
.arg("-o")
|
||||
.arg(format!("{name}/{file_name}.o"))
|
||||
.arg(file.file_name()),
|
||||
);
|
||||
run(
|
||||
format_args!("ld65 {file_name}"),
|
||||
Command::new("/home/matthew/cc65/bin/ld65")
|
||||
.arg("-C")
|
||||
.arg("nes.cfg")
|
||||
.arg("-o")
|
||||
.arg(format!("{name}/{file_name}.nes"))
|
||||
.arg(format!("{name}/{file_name}.o"))
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -158,13 +158,16 @@ impl DebuggerState {
|
||||
],
|
||||
]
|
||||
.spacing(5.),
|
||||
scrollable(column(
|
||||
nes.debug_log()
|
||||
.history()
|
||||
.into_iter()
|
||||
.rev()
|
||||
.map(|s| text(s).line_height(0.9).into())
|
||||
).spacing(0))
|
||||
scrollable(
|
||||
column(
|
||||
nes.debug_log()
|
||||
.history()
|
||||
.into_iter()
|
||||
.rev()
|
||||
.map(|s| text(s).line_height(0.9).into())
|
||||
)
|
||||
.spacing(0)
|
||||
)
|
||||
.width(Fill),
|
||||
],],
|
||||
]
|
||||
@@ -176,11 +179,20 @@ impl DebuggerState {
|
||||
fn run_n_clock_cycles(nes: &mut NES, n: usize) {
|
||||
for _ in 0..n {
|
||||
nes.run_one_clock_cycle();
|
||||
if nes.halted {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
fn run_until(nes: &mut NES, mut f: impl FnMut(CycleResult, &NES) -> bool) {
|
||||
fn run_until(nes: &mut NES, mut f: impl FnMut(CycleResult, &NES) -> bool, mut count: usize) {
|
||||
loop {
|
||||
if f(nes.run_one_clock_cycle(), nes) {
|
||||
count -= 1;
|
||||
if count <= 0 {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if nes.halted {
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -196,13 +208,15 @@ impl DebuggerState {
|
||||
DebuggerMessage::SetFrames(n) => self.frames = n,
|
||||
DebuggerMessage::RunPPUCycles => Self::run_n_clock_cycles(nes, self.ppu_cycles),
|
||||
DebuggerMessage::RunCPUCycles => Self::run_n_clock_cycles(nes, self.cpu_cycles * 3),
|
||||
DebuggerMessage::RunInstructions => Self::run_until(nes, |c, _| c.cpu_exec),
|
||||
DebuggerMessage::RunInstructions => {
|
||||
Self::run_until(nes, |c, _| c.cpu_exec, self.instructions)
|
||||
}
|
||||
DebuggerMessage::RunScanLines => Self::run_n_clock_cycles(nes, self.scan_lines * 341),
|
||||
DebuggerMessage::RunToScanLine => {
|
||||
Self::run_until(nes, |_, n| n.ppu.scanline == self.to_scan_line)
|
||||
Self::run_until(nes, |_, n| n.ppu.scanline == self.to_scan_line, 1)
|
||||
}
|
||||
DebuggerMessage::RunFrames => Self::run_n_clock_cycles(nes, self.frames * 341 * 262),
|
||||
DebuggerMessage::Run => todo!(),
|
||||
DebuggerMessage::Run => Self::run_until(nes, |_, nes| nes.halted, 1),
|
||||
DebuggerMessage::Pause => todo!(),
|
||||
}
|
||||
}
|
||||
|
||||
32
src/lib.rs
32
src/lib.rs
@@ -1777,7 +1777,7 @@ impl NES {
|
||||
self.cpu.status.set_carry(self.cpu.a & 0b1 == 0b1);
|
||||
self.cpu.a = self.cpu.a >> 1 | old_carry;
|
||||
self.cpu.status.set_zero(self.cpu.a == 0);
|
||||
self.cpu.status.set_negative(false);
|
||||
self.cpu.status.set_negative(self.cpu.a & 0x80 == 0x80);
|
||||
log!("{addr:04X}: ROR A | {:02X}", self.cpu.a);
|
||||
}),
|
||||
0x66 => inst!("ROR zp", 3, |off| {
|
||||
@@ -1786,7 +1786,7 @@ impl NES {
|
||||
self.cpu.status.set_carry(val & 0b1 == 0b1);
|
||||
let val = val >> 1 | old_carry;
|
||||
self.cpu.status.set_zero(val == 0);
|
||||
self.cpu.status.set_negative(false);
|
||||
self.cpu.status.set_negative(val & 0x80 == 0x80);
|
||||
self.write_abs(off, 0, val);
|
||||
log!("{addr:04X}: ROR ${:02X} | {:02X}", off, self.cpu.a);
|
||||
}),
|
||||
@@ -1796,9 +1796,9 @@ impl NES {
|
||||
self.cpu.status.set_carry(val & 0b1 == 0b1);
|
||||
let val = val >> 1 | old_carry;
|
||||
self.cpu.status.set_zero(val == 0);
|
||||
self.cpu.status.set_negative(false);
|
||||
self.cpu.status.set_negative(val & 0x80 == 0x80);
|
||||
self.write_zp_x(off, val);
|
||||
log!("{addr:04X}: ROR ${:02X},x | {:02X}", off, self.cpu.a);
|
||||
log!("{addr:04X}: ROR ${:02X},x | {:02X}", off, val);
|
||||
}),
|
||||
0x6E => inst!("ROR abs", 3, |low, high| {
|
||||
let old_carry = if self.cpu.status.carry() { 0x80 } else { 0x00 };
|
||||
@@ -1806,13 +1806,13 @@ impl NES {
|
||||
self.cpu.status.set_carry(val & 0b1 == 0b1);
|
||||
let val = val >> 1 | old_carry;
|
||||
self.cpu.status.set_zero(val == 0);
|
||||
self.cpu.status.set_negative(false);
|
||||
self.cpu.status.set_negative(val & 0x80 == 0x80);
|
||||
self.write_abs(low, high, val);
|
||||
log!(
|
||||
"{addr:04X}: ROR ${:02X}{:02X} | {:02X}",
|
||||
high,
|
||||
low,
|
||||
self.cpu.a
|
||||
val
|
||||
);
|
||||
}),
|
||||
0x7E => inst!("ROR abs,x", 4, |low, high| {
|
||||
@@ -1821,13 +1821,13 @@ impl NES {
|
||||
self.cpu.status.set_carry(val & 0b1 == 0b1);
|
||||
let val = val >> 1 | old_carry;
|
||||
self.cpu.status.set_zero(val == 0);
|
||||
self.cpu.status.set_negative(false);
|
||||
self.cpu.status.set_negative(val & 0x80 == 0x80);
|
||||
self.write_abs_x(low, high, val);
|
||||
log!(
|
||||
"{addr:04X}: ROR ${:02X}{:02X},x | {:02X}",
|
||||
high,
|
||||
low,
|
||||
self.cpu.a
|
||||
val
|
||||
);
|
||||
}),
|
||||
0x2A => inst!("ROL A", 1, || {
|
||||
@@ -1835,7 +1835,7 @@ impl NES {
|
||||
self.cpu.status.set_carry(self.cpu.a & 0x80 == 0x80);
|
||||
self.cpu.a = self.cpu.a << 1 | old_carry;
|
||||
self.cpu.status.set_zero(self.cpu.a == 0);
|
||||
self.cpu.status.set_negative(false);
|
||||
self.cpu.status.set_negative(self.cpu.a & 0x80 == 0x80);
|
||||
log!("{addr:04X}: ROL A | {:02X}", self.cpu.a);
|
||||
}),
|
||||
0x26 => inst!("ROL zp", 3, |off| {
|
||||
@@ -1844,9 +1844,9 @@ impl NES {
|
||||
self.cpu.status.set_carry(val & 0x80 == 0x80);
|
||||
let val = val << 1 | old_carry;
|
||||
self.cpu.status.set_zero(val == 0);
|
||||
self.cpu.status.set_negative(false);
|
||||
self.cpu.status.set_negative(val & 0x80 == 0x80);
|
||||
self.write_abs(off, 0, val);
|
||||
log!("{addr:04X}: ROL ${:02X} | {:02X}", off, self.cpu.a);
|
||||
log!("{addr:04X}: ROL ${:02X} | {:02X}", off, val);
|
||||
}),
|
||||
0x36 => inst!("ROL zp,x", 4, |off| {
|
||||
let old_carry = if self.cpu.status.carry() { 0x01 } else { 0x00 };
|
||||
@@ -1854,9 +1854,9 @@ impl NES {
|
||||
self.cpu.status.set_carry(val & 0x80 == 0x80);
|
||||
let val = val << 1 | old_carry;
|
||||
self.cpu.status.set_zero(val == 0);
|
||||
self.cpu.status.set_negative(false);
|
||||
self.cpu.status.set_negative(val & 0x80 == 0x80);
|
||||
self.write_zp_x(off, val);
|
||||
log!("{addr:04X}: ROL ${:02X},x | {:02X}", off, self.cpu.a);
|
||||
log!("{addr:04X}: ROL ${:02X},x | {:02X}", off, val);
|
||||
}),
|
||||
0x2E => inst!("ROL abs", 3, |low, high| {
|
||||
let old_carry = if self.cpu.status.carry() { 0x01 } else { 0x00 };
|
||||
@@ -1864,7 +1864,7 @@ impl NES {
|
||||
self.cpu.status.set_carry(val & 0x80 == 0x80);
|
||||
let val = val << 1 | old_carry;
|
||||
self.cpu.status.set_zero(val == 0);
|
||||
self.cpu.status.set_negative(false);
|
||||
self.cpu.status.set_negative(val & 0x80 == 0x80);
|
||||
self.write_abs(low, high, val);
|
||||
log!(
|
||||
"{addr:04X}: ROL ${:02X}{:02X} | {:02X}",
|
||||
@@ -1879,13 +1879,13 @@ impl NES {
|
||||
self.cpu.status.set_carry(val & 0x80 == 0x80);
|
||||
let val = val << 1 | old_carry;
|
||||
self.cpu.status.set_zero(val == 0);
|
||||
self.cpu.status.set_negative(false);
|
||||
self.cpu.status.set_negative(val & 0x80 == 0x80);
|
||||
self.write_abs_x(low, high, val);
|
||||
log!(
|
||||
"{addr:04X}: ROL ${:02X}{:02X},x | {:02X}",
|
||||
high,
|
||||
low,
|
||||
self.cpu.a
|
||||
val
|
||||
);
|
||||
}),
|
||||
|
||||
|
||||
@@ -22,7 +22,8 @@ use tokio::runtime::Runtime;
|
||||
use tracing_subscriber::EnvFilter;
|
||||
|
||||
// const ROM_FILE: &str = concat!(env!("ROM_DIR"), "/", "even_odd.nes");
|
||||
const ROM_FILE: &str = "./Super Mario Bros. (World).nes";
|
||||
const ROM_FILE: &str = concat!(env!("ROM_DIR"), "/", "crc_check.nes");
|
||||
// const ROM_FILE: &str = "./Super Mario Bros. (World).nes";
|
||||
|
||||
extern crate nes_emu;
|
||||
|
||||
|
||||
BIN
src/test_roms/common/ascii.chr
Normal file
BIN
src/test_roms/common/ascii.chr
Normal file
Binary file not shown.
118
src/test_roms/common/build_rom.s
Normal file
118
src/test_roms/common/build_rom.s
Normal file
@@ -0,0 +1,118 @@
|
||||
; Builds program as iNES ROM
|
||||
|
||||
; Default is 32K PRG and 8K CHR ROM, NROM (0)
|
||||
|
||||
.if 0 ; Options to set before .include "shell.inc":
|
||||
CHR_RAM=1 ; Use CHR-RAM instead of CHR-ROM
|
||||
CART_WRAM=1 ; Use mapper that supports 8K WRAM in cart
|
||||
CUSTOM_MAPPER=n ; Specify mapper number
|
||||
.endif
|
||||
|
||||
.ifndef CUSTOM_MAPPER
|
||||
.ifdef CART_WRAM
|
||||
CUSTOM_MAPPER = 2 ; UNROM
|
||||
.else
|
||||
CUSTOM_MAPPER = 0 ; NROM
|
||||
.endif
|
||||
.endif
|
||||
|
||||
;;;; iNES header
|
||||
.ifndef CUSTOM_HEADER
|
||||
.segment "HEADER"
|
||||
.byte $4E,$45,$53,26 ; "NES" EOF
|
||||
|
||||
.ifdef CHR_RAM
|
||||
.byte 2,0 ; 32K PRG, CHR RAM
|
||||
.else
|
||||
.byte 2,1 ; 32K PRG, 8K CHR
|
||||
.endif
|
||||
|
||||
.byte CUSTOM_MAPPER*$10+$01 ; vertical mirroring
|
||||
.endif
|
||||
|
||||
.ifndef CUSTOM_VECTORS
|
||||
.segment "VECTORS"
|
||||
.word $FFFF,$FFFF,$FFFF, nmi, reset, irq
|
||||
.endif
|
||||
|
||||
;;;; CHR-RAM/ROM
|
||||
.ifdef CHR_RAM
|
||||
.define CHARS "CHARS_PRG"
|
||||
.segment CHARS
|
||||
ascii_chr:
|
||||
|
||||
.segment "CHARS_PRG_ASCII"
|
||||
.align $200
|
||||
.incbin "ascii.chr"
|
||||
ascii_chr_end:
|
||||
.else
|
||||
.define CHARS "CHARS"
|
||||
.segment "CHARS_ASCII"
|
||||
.align $200
|
||||
.incbin "ascii.chr"
|
||||
.res $1800
|
||||
.endif
|
||||
|
||||
.segment "CODE"
|
||||
.ifndef CUSTOM_RESET
|
||||
reset:
|
||||
.endif
|
||||
std_reset:
|
||||
jmp main
|
||||
|
||||
zp_byte nmi_count
|
||||
zp_byte flags_from_nmi
|
||||
zp_byte pclo_from_nmi
|
||||
zp_byte nmi_temp
|
||||
.ifndef CUSTOM_NMI
|
||||
nmi:
|
||||
.endif
|
||||
std_nmi:
|
||||
sta nmi_temp
|
||||
pla
|
||||
sta flags_from_nmi
|
||||
pla
|
||||
sta pclo_from_nmi
|
||||
pha
|
||||
lda flags_from_nmi
|
||||
pha
|
||||
lda nmi_temp
|
||||
inc nmi_count
|
||||
rti
|
||||
|
||||
zp_byte flags_from_irq
|
||||
zp_byte pclo_from_irq
|
||||
zp_byte irq_count
|
||||
|
||||
.ifndef CUSTOM_IRQ
|
||||
irq:
|
||||
.endif
|
||||
std_irq: ; Record flags and PC low byte from stack
|
||||
pla
|
||||
sta flags_from_irq
|
||||
pla
|
||||
sta pclo_from_irq
|
||||
pha
|
||||
lda flags_from_irq
|
||||
pha
|
||||
inc irq_count
|
||||
bit SNDCHN ; clear frame IRQ flag
|
||||
rti
|
||||
|
||||
fail:
|
||||
lda #$01
|
||||
.ifndef CUSTOM_EXIT
|
||||
exit:
|
||||
.endif
|
||||
std_exit:
|
||||
.byte $02
|
||||
|
||||
.segment CHARS
|
||||
.res $10,0
|
||||
|
||||
; Move code to $C000
|
||||
.segment "CODE"
|
||||
.res $4000
|
||||
|
||||
.code
|
||||
.align 256
|
||||
331
src/test_roms/common/console.s
Normal file
331
src/test_roms/common/console.s
Normal file
@@ -0,0 +1,331 @@
|
||||
; Scrolling text console with line wrapping, 30x29 characters.
|
||||
; Buffers lines for speed. Will work even if PPU doesn't
|
||||
; support scrolling (until text reaches bottom). Keeps border
|
||||
; along bottom in case TV cuts it off.
|
||||
;
|
||||
; Defers most initialization until first newline, at which
|
||||
; point it clears nametable and makes palette non-black.
|
||||
;
|
||||
; ** ASCII font must already be in CHR, and mirroring
|
||||
; must be vertical or single-screen.
|
||||
|
||||
.ifndef CONSOLE_COLOR
|
||||
CONSOLE_COLOR = $30 ; white
|
||||
.endif
|
||||
|
||||
console_screen_width = 32 ; if lower than 32, left-justifies
|
||||
|
||||
; Number of characters of margin on left and right, to avoid
|
||||
; text getting cut off by common TVs. OK if either/both are 0.
|
||||
console_left_margin = 1
|
||||
console_right_margin = 1
|
||||
|
||||
console_width = console_screen_width - console_left_margin - console_right_margin
|
||||
|
||||
zp_byte console_pos ; 0 to console_width
|
||||
zp_byte console_scroll
|
||||
zp_byte console_temp
|
||||
bss_res console_buf,console_width
|
||||
|
||||
|
||||
; Initializes console
|
||||
console_init:
|
||||
; Flag that console hasn't been initialized
|
||||
setb console_scroll,$FF
|
||||
|
||||
setb console_pos,0
|
||||
rts
|
||||
|
||||
|
||||
; Hides console by disabling PPU rendering and blacking out
|
||||
; first four entries of palette.
|
||||
; Preserved: A, X, Y
|
||||
console_hide:
|
||||
pha
|
||||
jsr console_wait_vbl_
|
||||
setb PPUMASK,0
|
||||
lda #$0F
|
||||
jsr console_load_palette_
|
||||
pla
|
||||
rts
|
||||
|
||||
|
||||
; Shows console display
|
||||
; Preserved: X, Y
|
||||
console_show:
|
||||
pha
|
||||
lda #CONSOLE_COLOR
|
||||
jsr console_show_custom_color_
|
||||
pla
|
||||
rts
|
||||
|
||||
|
||||
; Prints char A to console. Will not appear until
|
||||
; a newline or flush occurs.
|
||||
; Preserved: A, X, Y
|
||||
console_print:
|
||||
cmp #10
|
||||
beq console_newline
|
||||
|
||||
sty console_temp
|
||||
|
||||
ldy console_pos
|
||||
cpy #console_width
|
||||
beq console_full_
|
||||
sta console_buf,y
|
||||
iny
|
||||
sty console_pos
|
||||
|
||||
ldy console_temp
|
||||
rts
|
||||
|
||||
|
||||
; Displays current line and starts new one
|
||||
; Preserved: A, X, Y
|
||||
console_newline:
|
||||
pha
|
||||
jsr console_wait_vbl_
|
||||
jsr console_flush_
|
||||
jsr console_scroll_up_
|
||||
setb console_pos,0
|
||||
pla
|
||||
rts
|
||||
|
||||
|
||||
; Displays current line's contents without scrolling.
|
||||
; Preserved: A, X, Y
|
||||
console_flush:
|
||||
pha
|
||||
jsr console_wait_vbl_
|
||||
jsr console_flush_
|
||||
jsr console_apply_scroll_
|
||||
pla
|
||||
rts
|
||||
|
||||
|
||||
;**** Internal routines ****
|
||||
|
||||
console_full_:
|
||||
ldy console_temp
|
||||
|
||||
; Line is full
|
||||
|
||||
; If space, treat as newline
|
||||
cmp #' '
|
||||
beq console_newline
|
||||
|
||||
; Wrap current line at appropriate point
|
||||
pha
|
||||
tya
|
||||
pha
|
||||
jsr console_wrap_
|
||||
pla
|
||||
tay
|
||||
pla
|
||||
|
||||
jmp console_print
|
||||
|
||||
|
||||
; Inserts newline into buffer at appropriate position, leaving
|
||||
; next line ready in buffer
|
||||
; Preserved: X, console_temp
|
||||
console_wrap_:
|
||||
; Find beginning of last word
|
||||
ldy #console_width
|
||||
lda #' '
|
||||
: dey
|
||||
bmi console_newline
|
||||
cmp console_buf,y
|
||||
bne :-
|
||||
|
||||
; y = 0 to console_width-1
|
||||
|
||||
; Flush through current word and put remaining
|
||||
; in buffer for next line
|
||||
jsr console_wait_vbl_
|
||||
|
||||
; Time to last PPU write: 207 + 32*(26 + 10)
|
||||
|
||||
lda console_scroll
|
||||
jsr console_set_ppuaddr_
|
||||
|
||||
stx console_pos ; save X
|
||||
|
||||
ldx #0
|
||||
|
||||
; Print everything before last word
|
||||
: lda console_buf,x
|
||||
sta PPUDATA
|
||||
inx
|
||||
dey
|
||||
bpl :-
|
||||
|
||||
; x = 1 to console_width
|
||||
|
||||
; Move last word to beginning of buffer, and
|
||||
; print spaces for rest of line
|
||||
ldy #0
|
||||
beq :++
|
||||
: lda #' '
|
||||
sta PPUDATA
|
||||
lda console_buf,x
|
||||
inx
|
||||
sta console_buf,y
|
||||
iny
|
||||
: cpx #console_width
|
||||
bne :--
|
||||
|
||||
ldx console_pos ; restore X
|
||||
|
||||
; Append new text after that
|
||||
sty console_pos
|
||||
|
||||
; FALL THROUGH
|
||||
|
||||
|
||||
; Scrolls up 8 pixels and clears one line BELOW new line
|
||||
; Preserved: X, console_temp
|
||||
console_scroll_up_:
|
||||
; Scroll up 8 pixels
|
||||
lda console_scroll
|
||||
jsr console_add_8_to_scroll_
|
||||
sta console_scroll
|
||||
|
||||
; Clear line AFTER that on screen
|
||||
jsr console_add_8_to_scroll_
|
||||
jsr console_set_ppuaddr_
|
||||
|
||||
ldy #console_width
|
||||
lda #' '
|
||||
: sta PPUDATA
|
||||
dey
|
||||
bne :-
|
||||
; FALL THROUGH
|
||||
|
||||
|
||||
; Applies current scrolling position to PPU
|
||||
; Preserved: X, Y, console_temp
|
||||
console_apply_scroll_:
|
||||
lda #0
|
||||
sta PPUADDR
|
||||
sta PPUADDR
|
||||
|
||||
sta PPUSCROLL
|
||||
lda console_scroll
|
||||
jsr console_add_8_to_scroll_
|
||||
jsr console_add_8_to_scroll_
|
||||
sta PPUSCROLL
|
||||
rts
|
||||
|
||||
|
||||
; Sets PPU address for row
|
||||
; In: A = scroll position
|
||||
; Preserved: X, Y
|
||||
console_set_ppuaddr_:
|
||||
sta console_temp
|
||||
lda #$08
|
||||
asl console_temp
|
||||
rol a
|
||||
asl console_temp
|
||||
rol a
|
||||
sta PPUADDR
|
||||
lda console_temp
|
||||
ora #console_left_margin
|
||||
sta PPUADDR
|
||||
rts
|
||||
|
||||
|
||||
; A = (A + 8) % 240
|
||||
; Preserved: X, Y
|
||||
console_add_8_to_scroll_:
|
||||
cmp #240-8
|
||||
bcc :+
|
||||
adc #16-1;+1 for set carry
|
||||
: adc #8
|
||||
rts
|
||||
|
||||
|
||||
console_show_custom_color_:
|
||||
pha
|
||||
jsr console_wait_vbl_
|
||||
setb PPUMASK,PPUMASK_BG0
|
||||
pla
|
||||
jsr console_load_palette_
|
||||
jmp console_apply_scroll_
|
||||
|
||||
|
||||
console_load_palette_:
|
||||
pha
|
||||
setb PPUADDR,$3F
|
||||
setb PPUADDR,$00
|
||||
setb PPUDATA,$0F ; black
|
||||
pla
|
||||
sta PPUDATA
|
||||
sta PPUDATA
|
||||
sta PPUDATA
|
||||
rts
|
||||
|
||||
|
||||
; Initializes PPU if necessary, then waits for VBL
|
||||
; Preserved: A, X, Y, console_temp
|
||||
console_wait_vbl_:
|
||||
lda console_scroll
|
||||
cmp #$FF
|
||||
bne @already_initialized
|
||||
|
||||
; Deferred initialization of PPU until first use of console
|
||||
|
||||
; In case PPU doesn't support scrolling, start a
|
||||
; couple of lines down
|
||||
setb console_scroll,16
|
||||
|
||||
jsr console_hide
|
||||
tya
|
||||
pha
|
||||
|
||||
; Fill nametable with spaces
|
||||
setb PPUADDR,$20
|
||||
setb PPUADDR,$00
|
||||
ldy #240
|
||||
lda #' '
|
||||
: sta PPUDATA
|
||||
sta PPUDATA
|
||||
sta PPUDATA
|
||||
sta PPUDATA
|
||||
dey
|
||||
bne :-
|
||||
|
||||
; Clear attributes
|
||||
lda #0
|
||||
ldy #$40
|
||||
: sta PPUDATA
|
||||
dey
|
||||
bne :-
|
||||
|
||||
pla
|
||||
tay
|
||||
|
||||
jsr console_show
|
||||
@already_initialized:
|
||||
jmp wait_vbl_optional
|
||||
|
||||
|
||||
; Flushes current line
|
||||
; Preserved: X, Y
|
||||
console_flush_:
|
||||
lda console_scroll
|
||||
jsr console_set_ppuaddr_
|
||||
|
||||
sty console_temp
|
||||
|
||||
; Copy line
|
||||
ldy #0
|
||||
beq :++
|
||||
: lda console_buf,y
|
||||
sta PPUDATA
|
||||
iny
|
||||
: cpy console_pos
|
||||
bne :--
|
||||
|
||||
ldy console_temp
|
||||
rts
|
||||
118
src/test_roms/common/crc.s
Normal file
118
src/test_roms/common/crc.s
Normal file
@@ -0,0 +1,118 @@
|
||||
; CRC-32 checksum calculation
|
||||
|
||||
zp_res checksum,4
|
||||
zp_byte checksum_temp
|
||||
zp_byte checksum_off_
|
||||
|
||||
; Turns CRC updating on/off. Allows nesting.
|
||||
; Preserved: A, X, Y
|
||||
crc_off:
|
||||
dec checksum_off_
|
||||
rts
|
||||
|
||||
crc_on: inc checksum_off_
|
||||
beq :+
|
||||
jpl internal_error ; catch unbalanced crc calls
|
||||
: rts
|
||||
|
||||
|
||||
; Initializes checksum module. Might initialize tables
|
||||
; in the future.
|
||||
init_crc:
|
||||
jmp reset_crc
|
||||
|
||||
|
||||
; Clears checksum and turns it on
|
||||
; Preserved: X, Y
|
||||
reset_crc:
|
||||
lda #0
|
||||
sta checksum_off_
|
||||
lda #$FF
|
||||
sta checksum
|
||||
sta checksum + 1
|
||||
sta checksum + 2
|
||||
sta checksum + 3
|
||||
rts
|
||||
|
||||
|
||||
; Updates checksum with byte in A (unless disabled via crc_off)
|
||||
; Preserved: A, X, Y
|
||||
; Time: 357 clocks average
|
||||
update_crc:
|
||||
bit checksum_off_
|
||||
bmi update_crc_off
|
||||
update_crc_:
|
||||
pha
|
||||
stx checksum_temp
|
||||
eor checksum
|
||||
ldx #8
|
||||
@bit: lsr checksum+3
|
||||
ror checksum+2
|
||||
ror checksum+1
|
||||
ror a
|
||||
bcc :+
|
||||
sta checksum
|
||||
lda checksum+3
|
||||
eor #$ED
|
||||
sta checksum+3
|
||||
lda checksum+2
|
||||
eor #$B8
|
||||
sta checksum+2
|
||||
lda checksum+1
|
||||
eor #$83
|
||||
sta checksum+1
|
||||
lda checksum
|
||||
eor #$20
|
||||
: dex
|
||||
bne @bit
|
||||
sta checksum
|
||||
ldx checksum_temp
|
||||
pla
|
||||
update_crc_off:
|
||||
rts
|
||||
|
||||
|
||||
; Prints checksum as 8-character hex value
|
||||
print_crc:
|
||||
jsr crc_off
|
||||
|
||||
; Print complement
|
||||
ldx #3
|
||||
: lda checksum,x
|
||||
eor #$FF
|
||||
jsr print_hex
|
||||
dex
|
||||
bpl :-
|
||||
|
||||
jmp crc_on
|
||||
|
||||
|
||||
; EQ if checksum matches CRC
|
||||
; Out: A=0 and EQ if match, A>0 and NE if different
|
||||
; Preserved: X, Y
|
||||
.macro is_crc crc
|
||||
jsr_with_addr is_crc_,{.dword crc}
|
||||
.endmacro
|
||||
|
||||
is_crc_:
|
||||
tya
|
||||
pha
|
||||
|
||||
; Compare with complemented checksum
|
||||
ldy #3
|
||||
: lda (ptr),y
|
||||
sec
|
||||
adc checksum,y
|
||||
bne @wrong
|
||||
dey
|
||||
bpl :-
|
||||
pla
|
||||
tay
|
||||
lda #0
|
||||
rts
|
||||
|
||||
@wrong:
|
||||
pla
|
||||
tay
|
||||
lda #1
|
||||
rts
|
||||
124
src/test_roms/common/crc_fast.s
Normal file
124
src/test_roms/common/crc_fast.s
Normal file
@@ -0,0 +1,124 @@
|
||||
; Fast table-based CRC-32
|
||||
zp_res checksum,4
|
||||
zp_byte checksum_temp
|
||||
zp_byte checksum_off_
|
||||
; Clears checksum and turns it on
|
||||
; Preserved: X, Y
|
||||
reset_crc:
|
||||
lda #0
|
||||
sta checksum_off_
|
||||
lda #$FF
|
||||
sta checksum
|
||||
sta checksum + 1
|
||||
sta checksum + 2
|
||||
sta checksum + 3
|
||||
rts
|
||||
|
||||
; Initializes fast CRC tables and resets checksum.
|
||||
; Preserved: Y
|
||||
init_crc_fast = reset_crc
|
||||
|
||||
; Updates checksum with byte from A
|
||||
; Preserved: X, Y
|
||||
; Time: 54 clocks
|
||||
update_crc_fast:
|
||||
stx checksum_temp
|
||||
|
||||
; Updates checksum with byte from A
|
||||
; Preserved: Y
|
||||
; Time: 42 clocks
|
||||
.macro update_crc_fast
|
||||
eor checksum
|
||||
tax
|
||||
lda checksum+1
|
||||
eor checksum_t0,x
|
||||
sta checksum
|
||||
lda checksum+2
|
||||
eor checksum_t1,x
|
||||
sta checksum+1
|
||||
lda checksum+3
|
||||
eor checksum_t2,x
|
||||
sta checksum+2
|
||||
lda checksum_t3,x
|
||||
sta checksum+3
|
||||
.endmacro
|
||||
|
||||
update_crc_fast
|
||||
ldx checksum_temp
|
||||
rts
|
||||
|
||||
.pushseg
|
||||
.segment "RODATA"
|
||||
.align 256
|
||||
checksum_t0:
|
||||
.byte $8D,$1B,$A1,$37,$94,$02,$B8,$2E,$BF,$29,$93,$05,$A6,$30,$8A,$1C
|
||||
.byte $E9,$7F,$C5,$53,$F0,$66,$DC,$4A,$DB,$4D,$F7,$61,$C2,$54,$EE,$78
|
||||
.byte $45,$D3,$69,$FF,$5C,$CA,$70,$E6,$77,$E1,$5B,$CD,$6E,$F8,$42,$D4
|
||||
.byte $21,$B7,$0D,$9B,$38,$AE,$14,$82,$13,$85,$3F,$A9,$0A,$9C,$26,$B0
|
||||
.byte $1D,$8B,$31,$A7,$04,$92,$28,$BE,$2F,$B9,$03,$95,$36,$A0,$1A,$8C
|
||||
.byte $79,$EF,$55,$C3,$60,$F6,$4C,$DA,$4B,$DD,$67,$F1,$52,$C4,$7E,$E8
|
||||
.byte $D5,$43,$F9,$6F,$CC,$5A,$E0,$76,$E7,$71,$CB,$5D,$FE,$68,$D2,$44
|
||||
.byte $B1,$27,$9D,$0B,$A8,$3E,$84,$12,$83,$15,$AF,$39,$9A,$0C,$B6,$20
|
||||
.byte $AD,$3B,$81,$17,$B4,$22,$98,$0E,$9F,$09,$B3,$25,$86,$10,$AA,$3C
|
||||
.byte $C9,$5F,$E5,$73,$D0,$46,$FC,$6A,$FB,$6D,$D7,$41,$E2,$74,$CE,$58
|
||||
.byte $65,$F3,$49,$DF,$7C,$EA,$50,$C6,$57,$C1,$7B,$ED,$4E,$D8,$62,$F4
|
||||
.byte $01,$97,$2D,$BB,$18,$8E,$34,$A2,$33,$A5,$1F,$89,$2A,$BC,$06,$90
|
||||
.byte $3D,$AB,$11,$87,$24,$B2,$08,$9E,$0F,$99,$23,$B5,$16,$80,$3A,$AC
|
||||
.byte $59,$CF,$75,$E3,$40,$D6,$6C,$FA,$6B,$FD,$47,$D1,$72,$E4,$5E,$C8
|
||||
.byte $F5,$63,$D9,$4F,$EC,$7A,$C0,$56,$C7,$51,$EB,$7D,$DE,$48,$F2,$64
|
||||
.byte $91,$07,$BD,$2B,$88,$1E,$A4,$32,$A3,$35,$8F,$19,$BA,$2C,$96,$00
|
||||
|
||||
checksum_t1:
|
||||
.byte $EF,$DF,$8E,$BE,$2B,$1B,$4A,$7A,$67,$57,$06,$36,$A3,$93,$C2,$F2
|
||||
.byte $FF,$CF,$9E,$AE,$3B,$0B,$5A,$6A,$77,$47,$16,$26,$B3,$83,$D2,$E2
|
||||
.byte $CF,$FF,$AE,$9E,$0B,$3B,$6A,$5A,$47,$77,$26,$16,$83,$B3,$E2,$D2
|
||||
.byte $DF,$EF,$BE,$8E,$1B,$2B,$7A,$4A,$57,$67,$36,$06,$93,$A3,$F2,$C2
|
||||
.byte $AE,$9E,$CF,$FF,$6A,$5A,$0B,$3B,$26,$16,$47,$77,$E2,$D2,$83,$B3
|
||||
.byte $BE,$8E,$DF,$EF,$7A,$4A,$1B,$2B,$36,$06,$57,$67,$F2,$C2,$93,$A3
|
||||
.byte $8E,$BE,$EF,$DF,$4A,$7A,$2B,$1B,$06,$36,$67,$57,$C2,$F2,$A3,$93
|
||||
.byte $9E,$AE,$FF,$CF,$5A,$6A,$3B,$0B,$16,$26,$77,$47,$D2,$E2,$B3,$83
|
||||
.byte $6C,$5C,$0D,$3D,$A8,$98,$C9,$F9,$E4,$D4,$85,$B5,$20,$10,$41,$71
|
||||
.byte $7C,$4C,$1D,$2D,$B8,$88,$D9,$E9,$F4,$C4,$95,$A5,$30,$00,$51,$61
|
||||
.byte $4C,$7C,$2D,$1D,$88,$B8,$E9,$D9,$C4,$F4,$A5,$95,$00,$30,$61,$51
|
||||
.byte $5C,$6C,$3D,$0D,$98,$A8,$F9,$C9,$D4,$E4,$B5,$85,$10,$20,$71,$41
|
||||
.byte $2D,$1D,$4C,$7C,$E9,$D9,$88,$B8,$A5,$95,$C4,$F4,$61,$51,$00,$30
|
||||
.byte $3D,$0D,$5C,$6C,$F9,$C9,$98,$A8,$B5,$85,$D4,$E4,$71,$41,$10,$20
|
||||
.byte $0D,$3D,$6C,$5C,$C9,$F9,$A8,$98,$85,$B5,$E4,$D4,$41,$71,$20,$10
|
||||
.byte $1D,$2D,$7C,$4C,$D9,$E9,$B8,$88,$95,$A5,$F4,$C4,$51,$61,$30,$00
|
||||
|
||||
checksum_t2:
|
||||
.byte $02,$05,$0C,$0B,$6F,$68,$61,$66,$D9,$DE,$D7,$D0,$B4,$B3,$BA,$BD
|
||||
.byte $B5,$B2,$BB,$BC,$D8,$DF,$D6,$D1,$6E,$69,$60,$67,$03,$04,$0D,$0A
|
||||
.byte $6C,$6B,$62,$65,$01,$06,$0F,$08,$B7,$B0,$B9,$BE,$DA,$DD,$D4,$D3
|
||||
.byte $DB,$DC,$D5,$D2,$B6,$B1,$B8,$BF,$00,$07,$0E,$09,$6D,$6A,$63,$64
|
||||
.byte $DE,$D9,$D0,$D7,$B3,$B4,$BD,$BA,$05,$02,$0B,$0C,$68,$6F,$66,$61
|
||||
.byte $69,$6E,$67,$60,$04,$03,$0A,$0D,$B2,$B5,$BC,$BB,$DF,$D8,$D1,$D6
|
||||
.byte $B0,$B7,$BE,$B9,$DD,$DA,$D3,$D4,$6B,$6C,$65,$62,$06,$01,$08,$0F
|
||||
.byte $07,$00,$09,$0E,$6A,$6D,$64,$63,$DC,$DB,$D2,$D5,$B1,$B6,$BF,$B8
|
||||
.byte $BA,$BD,$B4,$B3,$D7,$D0,$D9,$DE,$61,$66,$6F,$68,$0C,$0B,$02,$05
|
||||
.byte $0D,$0A,$03,$04,$60,$67,$6E,$69,$D6,$D1,$D8,$DF,$BB,$BC,$B5,$B2
|
||||
.byte $D4,$D3,$DA,$DD,$B9,$BE,$B7,$B0,$0F,$08,$01,$06,$62,$65,$6C,$6B
|
||||
.byte $63,$64,$6D,$6A,$0E,$09,$00,$07,$B8,$BF,$B6,$B1,$D5,$D2,$DB,$DC
|
||||
.byte $66,$61,$68,$6F,$0B,$0C,$05,$02,$BD,$BA,$B3,$B4,$D0,$D7,$DE,$D9
|
||||
.byte $D1,$D6,$DF,$D8,$BC,$BB,$B2,$B5,$0A,$0D,$04,$03,$67,$60,$69,$6E
|
||||
.byte $08,$0F,$06,$01,$65,$62,$6B,$6C,$D3,$D4,$DD,$DA,$BE,$B9,$B0,$B7
|
||||
.byte $BF,$B8,$B1,$B6,$D2,$D5,$DC,$DB,$64,$63,$6A,$6D,$09,$0E,$07,$00
|
||||
|
||||
checksum_t3:
|
||||
.byte $D2,$A5,$3C,$4B,$D5,$A2,$3B,$4C,$DC,$AB,$32,$45,$DB,$AC,$35,$42
|
||||
.byte $CF,$B8,$21,$56,$C8,$BF,$26,$51,$C1,$B6,$2F,$58,$C6,$B1,$28,$5F
|
||||
.byte $E9,$9E,$07,$70,$EE,$99,$00,$77,$E7,$90,$09,$7E,$E0,$97,$0E,$79
|
||||
.byte $F4,$83,$1A,$6D,$F3,$84,$1D,$6A,$FA,$8D,$14,$63,$FD,$8A,$13,$64
|
||||
.byte $A4,$D3,$4A,$3D,$A3,$D4,$4D,$3A,$AA,$DD,$44,$33,$AD,$DA,$43,$34
|
||||
.byte $B9,$CE,$57,$20,$BE,$C9,$50,$27,$B7,$C0,$59,$2E,$B0,$C7,$5E,$29
|
||||
.byte $9F,$E8,$71,$06,$98,$EF,$76,$01,$91,$E6,$7F,$08,$96,$E1,$78,$0F
|
||||
.byte $82,$F5,$6C,$1B,$85,$F2,$6B,$1C,$8C,$FB,$62,$15,$8B,$FC,$65,$12
|
||||
.byte $3F,$48,$D1,$A6,$38,$4F,$D6,$A1,$31,$46,$DF,$A8,$36,$41,$D8,$AF
|
||||
.byte $22,$55,$CC,$BB,$25,$52,$CB,$BC,$2C,$5B,$C2,$B5,$2B,$5C,$C5,$B2
|
||||
.byte $04,$73,$EA,$9D,$03,$74,$ED,$9A,$0A,$7D,$E4,$93,$0D,$7A,$E3,$94
|
||||
.byte $19,$6E,$F7,$80,$1E,$69,$F0,$87,$17,$60,$F9,$8E,$10,$67,$FE,$89
|
||||
.byte $49,$3E,$A7,$D0,$4E,$39,$A0,$D7,$47,$30,$A9,$DE,$40,$37,$AE,$D9
|
||||
.byte $54,$23,$BA,$CD,$53,$24,$BD,$CA,$5A,$2D,$B4,$C3,$5D,$2A,$B3,$C4
|
||||
.byte $72,$05,$9C,$EB,$75,$02,$9B,$EC,$7C,$0B,$92,$E5,$7B,$0C,$95,$E2
|
||||
.byte $6F,$18,$81,$F6,$68,$1F,$86,$F1,$61,$16,$8F,$F8,$66,$11,$88,$FF
|
||||
.popseg
|
||||
190
src/test_roms/common/delay.s
Normal file
190
src/test_roms/common/delay.s
Normal file
@@ -0,0 +1,190 @@
|
||||
; Delays in CPU clocks, milliseconds, etc. All routines are re-entrant
|
||||
; (no global data). No routines touch X or Y during execution.
|
||||
; Code generated by macros is relocatable; it contains no JMPs to itself.
|
||||
|
||||
zp_byte delay_temp_ ; only written to
|
||||
|
||||
; Delays n clocks, from 2 to 16777215
|
||||
; Preserved: A, X, Y, flags
|
||||
.macro delay n
|
||||
.if (n) < 0 .or (n) = 1 .or (n) > 16777215
|
||||
.error "Delay out of range"
|
||||
.endif
|
||||
delay_ (n)
|
||||
.endmacro
|
||||
|
||||
|
||||
; Delays n milliseconds (1/1000 second)
|
||||
; n can range from 0 to 1100.
|
||||
; Preserved: A, X, Y, flags
|
||||
.macro delay_msec n
|
||||
.if (n) < 0 .or (n) > 1100
|
||||
.error "time out of range"
|
||||
.endif
|
||||
delay ((n)*CLOCK_RATE+500)/1000
|
||||
.endmacro
|
||||
|
||||
|
||||
; Delays n microseconds (1/1000000 second).
|
||||
; n can range from 0 to 100000.
|
||||
; Preserved: A, X, Y, flags
|
||||
.macro delay_usec n
|
||||
.if (n) < 0 .or (n) > 100000
|
||||
.error "time out of range"
|
||||
.endif
|
||||
delay ((n)*((CLOCK_RATE+50)/100)+5000)/10000
|
||||
.endmacro
|
||||
|
||||
.align 64
|
||||
|
||||
; Delays A clocks + overhead
|
||||
; Preserved: X, Y
|
||||
; Time: A+25 clocks (including JSR)
|
||||
: sbc #7 ; carry set by CMP
|
||||
delay_a_25_clocks:
|
||||
cmp #7
|
||||
bcs :- ; do multiples of 7
|
||||
lsr a ; bit 0
|
||||
bcs :+
|
||||
: ; A=clocks/2, either 0,1,2,3
|
||||
beq @zero ; 0: 5
|
||||
lsr a
|
||||
beq :+ ; 1: 7
|
||||
bcc :+ ; 2: 9
|
||||
@zero: bne :+ ; 3: 11
|
||||
: rts ; (thanks to dclxvi for the algorithm)
|
||||
|
||||
|
||||
; Delays A*256 clocks + overhead
|
||||
; Preserved: X, Y
|
||||
; Time: A*256+16 clocks (including JSR)
|
||||
delay_256a_16_clocks:
|
||||
cmp #0
|
||||
bne :+
|
||||
rts
|
||||
delay_256a_11_clocks_:
|
||||
: pha
|
||||
lda #256-19-22
|
||||
jsr delay_a_25_clocks
|
||||
pla
|
||||
clc
|
||||
adc #$FF
|
||||
bne :-
|
||||
rts
|
||||
|
||||
|
||||
; Delays A*65536 clocks + overhead
|
||||
; Preserved: X, Y
|
||||
; Time: A*65536+16 clocks (including JSR)
|
||||
delay_65536a_16_clocks:
|
||||
cmp #0
|
||||
bne :+
|
||||
rts
|
||||
delay_65536a_11_clocks_:
|
||||
: pha
|
||||
lda #256-19-22-13
|
||||
jsr delay_a_25_clocks
|
||||
lda #255
|
||||
jsr delay_256a_11_clocks_
|
||||
pla
|
||||
clc
|
||||
adc #$FF
|
||||
bne :-
|
||||
rts
|
||||
|
||||
max_short_delay = 41
|
||||
|
||||
; delay_short_ macro jumps into these
|
||||
.res (max_short_delay-12)/2,$EA ; NOP
|
||||
delay_unrolled_:
|
||||
rts
|
||||
|
||||
.macro delay_short_ n
|
||||
.if n < 0 .or n = 1 .or n > max_short_delay
|
||||
.error "Internal delay error"
|
||||
.endif
|
||||
.if n = 0
|
||||
; nothing
|
||||
.elseif n = 2
|
||||
nop
|
||||
.elseif n = 3
|
||||
sta <delay_temp_
|
||||
.elseif n = 4
|
||||
nop
|
||||
nop
|
||||
.elseif n = 5
|
||||
sta <delay_temp_
|
||||
nop
|
||||
.elseif n = 6
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
.elseif n = 7
|
||||
php
|
||||
plp
|
||||
.elseif n = 8
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
.elseif n = 9
|
||||
php
|
||||
plp
|
||||
nop
|
||||
.elseif n = 10
|
||||
sta <delay_temp_
|
||||
php
|
||||
plp
|
||||
.elseif n = 11
|
||||
php
|
||||
plp
|
||||
nop
|
||||
nop
|
||||
.elseif n = 13
|
||||
php
|
||||
plp
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
.elseif n & 1
|
||||
sta <delay_temp_
|
||||
jsr delay_unrolled_-((n-15)/2)
|
||||
.else
|
||||
jsr delay_unrolled_-((n-12)/2)
|
||||
.endif
|
||||
.endmacro
|
||||
|
||||
.macro delay_nosave_ n
|
||||
; 65536+17 = maximum delay using delay_256a_11_clocks_
|
||||
; 255+27 = maximum delay using delay_a_25_clocks
|
||||
; 27 = minimum delay using delay_a_25_clocks
|
||||
.if n > 65536+17
|
||||
lda #^(n - 15)
|
||||
jsr delay_65536a_11_clocks_
|
||||
; +2 ensures remaining clocks is never 1
|
||||
delay_nosave_ (((n - 15) & $FFFF) + 2)
|
||||
.elseif n > 255+27
|
||||
lda #>(n - 15)
|
||||
jsr delay_256a_11_clocks_
|
||||
; +2 ensures remaining clocks is never 1
|
||||
delay_nosave_ (<(n - 15) + 2)
|
||||
.elseif n >= 27
|
||||
lda #<(n - 27)
|
||||
jsr delay_a_25_clocks
|
||||
.else
|
||||
delay_short_ n
|
||||
.endif
|
||||
.endmacro
|
||||
|
||||
.macro delay_ n
|
||||
.if n > max_short_delay
|
||||
php
|
||||
pha
|
||||
delay_nosave_ (n - 14)
|
||||
pla
|
||||
plp
|
||||
.else
|
||||
delay_short_ n
|
||||
.endif
|
||||
.endmacro
|
||||
|
||||
BIN
src/test_roms/common/devcart.bin
Normal file
BIN
src/test_roms/common/devcart.bin
Normal file
Binary file not shown.
245
src/test_roms/common/instr_test.inc
Normal file
245
src/test_roms/common/instr_test.inc
Normal file
@@ -0,0 +1,245 @@
|
||||
REGION_FREE = 1
|
||||
.include "shell.inc"
|
||||
.include "crc_fast.s"
|
||||
|
||||
zp_byte in_p
|
||||
zp_byte in_a
|
||||
zp_byte in_x
|
||||
zp_byte in_y
|
||||
zp_byte in_s
|
||||
|
||||
.macro entry opcode,name,crc_0,crc_1,crc_2,crc_3
|
||||
jsr reset_crc
|
||||
lda #opcode
|
||||
sta opcode_byte - testcase_entry + testcase_instance
|
||||
jsr run_testcase
|
||||
; testcase_0 OPERANDS + 4,{testcase opcode}
|
||||
lda checksum
|
||||
cmp #crc_0
|
||||
jne fail
|
||||
lda checksum+1
|
||||
cmp #crc_1
|
||||
jne fail
|
||||
lda checksum+2
|
||||
cmp #crc_2
|
||||
jne fail
|
||||
lda checksum+3
|
||||
cmp #crc_3
|
||||
jne fail
|
||||
.endmacro
|
||||
|
||||
testcase_entry:
|
||||
lda in_p
|
||||
pha
|
||||
lda in_a
|
||||
ldx in_x
|
||||
ldy in_y
|
||||
plp
|
||||
opcode_byte: .byte 0
|
||||
.if OPERANDS > 0
|
||||
; .out .sprintf(" 1: %s", .string(op_1))
|
||||
.byte 0
|
||||
.endif
|
||||
.if OPERANDS > 1
|
||||
; .out .sprintf(" 2: %s", .string(op_2))
|
||||
.byte 0
|
||||
.endif
|
||||
php
|
||||
pha
|
||||
txa
|
||||
pha
|
||||
tya
|
||||
pha
|
||||
; Stack: <y, x, a, p>
|
||||
pla
|
||||
jsr update_crc_fast
|
||||
pla
|
||||
jsr update_crc_fast
|
||||
pla
|
||||
jsr update_crc_fast
|
||||
pla
|
||||
jsr update_crc_fast
|
||||
rts
|
||||
testcase_entry_size = * - testcase_entry
|
||||
|
||||
; .macro testcase opcode, reg_p, reg_a, reg_x, reg_y, op_1, op_2
|
||||
; jsr crc_calc
|
||||
; .out .sprintf("OP: %s", .string(opcode))
|
||||
; .out .sprintf(" S: %s", .string(reg_p))
|
||||
; .out .sprintf(" A: %s", .string(reg_a))
|
||||
; .out .sprintf(" X: %s", .string(reg_x))
|
||||
; .out .sprintf(" Y: %s", .string(reg_y))
|
||||
; .endmacro
|
||||
; .macro testcase_6 opcode, ops
|
||||
; testcase opcode, ops
|
||||
; ; .mid(0, 1, ops), .mid(1, 1, ops), .mid(2, 1, ops), .mid(3, 1, ops), .mid(4, 1, ops), .mid(5, 1, ops)
|
||||
; .endmacro
|
||||
; .macro testcase_0 count,ins
|
||||
; .if count > 0
|
||||
; testcase_0 count - 1, {ins,0}
|
||||
; testcase_0 count - 1, {ins,1}
|
||||
; testcase_0 count - 1, {ins,2}
|
||||
; testcase_0 count - 1, {ins,$40}
|
||||
; testcase_0 count - 1, {ins,$7F}
|
||||
; testcase_0 count - 1, {ins,$80}
|
||||
; testcase_0 count - 1, {ins,$81}
|
||||
; testcase_0 count - 1, {ins,$FF}
|
||||
; ; .byte 0,1,2,$40,$7F,$80,$81,$FF
|
||||
; .else
|
||||
; ins
|
||||
; .endif
|
||||
; .endmacro
|
||||
.pushseg
|
||||
.segment "BSS"
|
||||
testcase_instance: .res testcase_entry_size
|
||||
.popseg
|
||||
|
||||
testcase_init:
|
||||
ldx #0
|
||||
@loop: lda testcase_entry,x
|
||||
sta testcase_instance,x
|
||||
inx
|
||||
cpx #testcase_entry_size
|
||||
bmi @loop
|
||||
rts
|
||||
|
||||
; values:
|
||||
; .byte 0,1,2,$40,$7F,$80,$81,$FF
|
||||
; values_size = * - values
|
||||
; .byte 0,1,2,$40,$7F,$80,$81,$FF
|
||||
.macro increment pos, next, done
|
||||
lda pos
|
||||
cmp #0
|
||||
bne @a_1
|
||||
lda #1
|
||||
sta pos
|
||||
jmp done
|
||||
@a_1: cmp #1
|
||||
bne @a_2
|
||||
lda #2
|
||||
sta pos
|
||||
jmp done
|
||||
@a_2: cmp #2
|
||||
bne @a_40
|
||||
lda #40
|
||||
sta pos
|
||||
jmp done
|
||||
@a_40: cmp #$40
|
||||
bne @a_7F
|
||||
lda #$7F
|
||||
sta pos
|
||||
jmp done
|
||||
@a_7F: cmp #$7F
|
||||
bne @a_80
|
||||
lda #$80
|
||||
sta pos
|
||||
jmp done
|
||||
@a_80: cmp #$80
|
||||
bne @a_81
|
||||
lda #$81
|
||||
sta pos
|
||||
jmp done
|
||||
@a_81: cmp #$81
|
||||
bne @a_FF
|
||||
lda #$FF
|
||||
sta pos
|
||||
jmp done
|
||||
@a_FF: cmp #$FF
|
||||
lda #0
|
||||
sta pos
|
||||
jmp next
|
||||
.endmacro
|
||||
run_testcase:
|
||||
increment in_a, increment_x, done
|
||||
increment_x:
|
||||
increment in_x, increment_y, done
|
||||
increment_y:
|
||||
increment in_y, increment_p, done
|
||||
increment_p:
|
||||
increment in_p, ret, done
|
||||
done:
|
||||
jsr testcase_instance
|
||||
jmp run_testcase
|
||||
ret:
|
||||
rts
|
||||
|
||||
; Defines instruction to test
|
||||
|
||||
; Values set_paxyso sets registers to. Set
|
||||
; before calling test_values, which can then
|
||||
; overwrite them if desired.
|
||||
|
||||
; Temporary space for check_paxyso
|
||||
zp_byte out_a
|
||||
zp_byte out_x
|
||||
zp_byte out_s
|
||||
|
||||
; Values to cycle through for registers
|
||||
|
||||
; ; Sets bytes on stack around in_s
|
||||
; .macro set_stack
|
||||
; ldx in_s
|
||||
; inx
|
||||
; inx
|
||||
; ldy #6
|
||||
; : txa
|
||||
; asl
|
||||
; eor #$A5
|
||||
; sta $100,x
|
||||
; dex
|
||||
; dey
|
||||
; bne :-
|
||||
; .endmacro
|
||||
|
||||
; ; Checksums bytes on stack around in_s
|
||||
; .macro check_stack
|
||||
; ldx in_s
|
||||
; inx
|
||||
; inx
|
||||
; ldy #6
|
||||
; : lda $100,x
|
||||
; dex
|
||||
; jsr update_crc
|
||||
; dey
|
||||
; bne :-
|
||||
; .endmacro
|
||||
|
||||
; ; Sets P, A, X, Y, S, and operand
|
||||
; .macro set_paxyso
|
||||
; ldx in_s
|
||||
; txs
|
||||
; lda values,y
|
||||
; sta operand
|
||||
; lda in_p
|
||||
; pha
|
||||
; lda in_a
|
||||
; ldx in_x
|
||||
; ldy in_y
|
||||
; plp
|
||||
; .endmacro
|
||||
|
||||
; ; Checksums P, A, X, Y, S, and operand
|
||||
; .macro check_paxyso
|
||||
; php
|
||||
; sta out_a
|
||||
; pla
|
||||
|
||||
; stx out_x
|
||||
; tsx
|
||||
; stx out_s
|
||||
; ldx saved_s
|
||||
; txs
|
||||
|
||||
; cld
|
||||
; jsr update_crc_fast
|
||||
; lda out_a
|
||||
; jsr update_crc_fast
|
||||
; lda out_x
|
||||
; jsr update_crc_fast
|
||||
; tya
|
||||
; jsr update_crc_fast
|
||||
; lda out_s
|
||||
; jsr update_crc_fast
|
||||
; lda operand
|
||||
; jsr update_crc_fast
|
||||
; .endmacro
|
||||
199
src/test_roms/common/instr_test_end.s
Normal file
199
src/test_roms/common/instr_test_end.s
Normal file
@@ -0,0 +1,199 @@
|
||||
; Offset of current instruction
|
||||
zp_byte instrs_idx
|
||||
|
||||
zp_byte failed_count
|
||||
|
||||
main:
|
||||
; Stack slightly lower than top
|
||||
ldx #$A2
|
||||
txs
|
||||
|
||||
jsr init_crc_fast
|
||||
|
||||
; Test each instruction
|
||||
lda #0
|
||||
@loop: sta instrs_idx
|
||||
tay
|
||||
|
||||
jsr reset_crc
|
||||
lda instrs,y
|
||||
jsr test_instr
|
||||
jsr check_result
|
||||
|
||||
lda instrs_idx
|
||||
clc
|
||||
adc #4
|
||||
cmp #instrs_size
|
||||
bne @loop
|
||||
|
||||
.ifdef BUILD_DEVCART
|
||||
lda #0
|
||||
jmp exit
|
||||
.endif
|
||||
|
||||
lda failed_count
|
||||
jne test_failed
|
||||
jmp tests_passed
|
||||
|
||||
; Check result of test
|
||||
check_result:
|
||||
.ifdef BUILD_DEVCART
|
||||
; Print correct CRC
|
||||
jsr crc_off
|
||||
print_str ".dword $"
|
||||
ldx #0
|
||||
: lda checksum,x
|
||||
jsr print_hex
|
||||
inx
|
||||
cpx #4
|
||||
bne :-
|
||||
jsr print_newline
|
||||
jsr crc_on
|
||||
.else
|
||||
; Verify CRC
|
||||
ldx #3
|
||||
ldy instrs_idx
|
||||
: lda checksum,x
|
||||
cmp correct_checksums,y
|
||||
bne @wrong
|
||||
iny
|
||||
dex
|
||||
bpl :-
|
||||
.endif
|
||||
rts
|
||||
|
||||
; Print failed opcode and name
|
||||
@wrong:
|
||||
ldy instrs_idx
|
||||
lda instrs,y
|
||||
jsr print_a
|
||||
jsr play_byte
|
||||
lda instrs+2,y
|
||||
sta addr
|
||||
lda instrs+3,y
|
||||
sta addr+1
|
||||
jsr print_str_addr
|
||||
jsr print_newline
|
||||
inc failed_count
|
||||
rts
|
||||
|
||||
; Place where instruction is executed
|
||||
instr = $3A0
|
||||
|
||||
; Tests instr A
|
||||
test_instr:
|
||||
sta instr
|
||||
jsr avoid_silent_nsf
|
||||
|
||||
; Copy rest of template
|
||||
ldx #instr_template_size - 1
|
||||
: lda instr_template,x
|
||||
sta instr,x
|
||||
dex
|
||||
bne :-
|
||||
|
||||
; Disable and be sure APU IRQs are clear, since
|
||||
; I flag gets cleared during testing.
|
||||
setb SNDMODE,$C0
|
||||
setb $4010,0
|
||||
nop
|
||||
lda SNDCHN
|
||||
|
||||
; Default stack
|
||||
lda #$90
|
||||
sta in_s
|
||||
|
||||
; Test with different flags
|
||||
lda #$00
|
||||
jsr test_flags
|
||||
lda #$FF
|
||||
jsr test_flags
|
||||
|
||||
rts
|
||||
|
||||
; Position in operand table
|
||||
zp_byte operand_idx
|
||||
|
||||
test_flags:
|
||||
sta in_p
|
||||
|
||||
ldy #values_size-1
|
||||
: sty operand_idx
|
||||
|
||||
lda values,y
|
||||
sta in_a
|
||||
|
||||
lda values+1,y
|
||||
sta in_x
|
||||
|
||||
lda values+2,y
|
||||
sta in_y
|
||||
|
||||
jsr test_values
|
||||
|
||||
ldy operand_idx
|
||||
dey
|
||||
bpl :-
|
||||
|
||||
rts
|
||||
|
||||
.ifndef values2
|
||||
values2 = values
|
||||
values2_size = values_size
|
||||
.endif
|
||||
|
||||
.macro test_normal
|
||||
zp_byte a_idx
|
||||
zp_byte saved_s
|
||||
|
||||
tsx
|
||||
stx saved_s
|
||||
|
||||
set_stack
|
||||
|
||||
ldy #values2_size-1
|
||||
inner: sty a_idx
|
||||
|
||||
lda values2,y
|
||||
sta operand
|
||||
|
||||
set_in
|
||||
|
||||
; For debugging
|
||||
.if 0
|
||||
; P A X Y S O (z,x) (z),y
|
||||
jsr print_p
|
||||
jsr print_a
|
||||
jsr print_x
|
||||
jsr print_y
|
||||
jsr print_s
|
||||
lda operand
|
||||
jsr print_a
|
||||
.ifdef address
|
||||
lda (address,x)
|
||||
jsr print_a
|
||||
lda (address),y
|
||||
jsr print_a
|
||||
.else
|
||||
lda operand,x
|
||||
jsr print_a
|
||||
lda operand,y
|
||||
jsr print_a
|
||||
.endif
|
||||
jsr print_newline
|
||||
.endif
|
||||
|
||||
jmp instr
|
||||
instr_done:
|
||||
|
||||
check_out
|
||||
|
||||
ldy a_idx
|
||||
dey
|
||||
bpl inner
|
||||
|
||||
check_stack
|
||||
|
||||
ldx saved_s
|
||||
txs
|
||||
.endmacro
|
||||
169
src/test_roms/common/macros.inc
Normal file
169
src/test_roms/common/macros.inc
Normal file
@@ -0,0 +1,169 @@
|
||||
; jxx equivalents to bxx
|
||||
.macpack longbranch
|
||||
|
||||
; blt, bge equivalents to bcc, bcs
|
||||
.define blt bcc
|
||||
.define bge bcs
|
||||
.define jge jcs
|
||||
.define jlt jcc
|
||||
|
||||
; Puts data in another segment
|
||||
.macro seg_data seg,data
|
||||
.pushseg
|
||||
.segment seg
|
||||
data
|
||||
.popseg
|
||||
.endmacro
|
||||
|
||||
; Reserves size bytes in zeropage/bss for name.
|
||||
; If size is omitted, reserves one byte.
|
||||
.macro zp_res name,size
|
||||
.ifblank size
|
||||
zp_res name,1
|
||||
.else
|
||||
seg_data "ZEROPAGE",{name: .res size}
|
||||
.endif
|
||||
.endmacro
|
||||
|
||||
.macro bss_res name,size
|
||||
.ifblank size
|
||||
bss_res name,1
|
||||
.else
|
||||
seg_data "BSS",{name: .res size}
|
||||
.endif
|
||||
.endmacro
|
||||
|
||||
.macro nv_res name,size
|
||||
.ifblank size
|
||||
nv_res name,1
|
||||
.else
|
||||
seg_data "NVRAM",{name: .res size}
|
||||
.endif
|
||||
.endmacro
|
||||
|
||||
; Reserves one byte in zeropage for name (very common)
|
||||
.macro zp_byte name
|
||||
seg_data "ZEROPAGE",{name: .res 1}
|
||||
.endmacro
|
||||
|
||||
; Passes constant data to routine in addr
|
||||
; Preserved: A, X, Y
|
||||
.macro jsr_with_addr routine,data
|
||||
.local Addr
|
||||
pha
|
||||
lda #<Addr
|
||||
sta addr
|
||||
lda #>Addr
|
||||
sta addr+1
|
||||
pla
|
||||
jsr routine
|
||||
seg_data "RODATA",{Addr: data}
|
||||
.endmacro
|
||||
|
||||
; Calls routine multiple times, with A having the
|
||||
; value 'start' the first time, 'start+step' the
|
||||
; second time, up to 'end' for the last time.
|
||||
.macro for_loop routine,start,end,step
|
||||
lda #start
|
||||
: pha
|
||||
jsr routine
|
||||
pla
|
||||
clc
|
||||
adc #step
|
||||
cmp #<((end)+(step))
|
||||
bne :-
|
||||
.endmacro
|
||||
|
||||
; Calls routine n times. The value of A in the routine
|
||||
; counts from 0 to n-1.
|
||||
.macro loop_n_times routine,n
|
||||
for_loop routine,0,n-1,+1
|
||||
.endmacro
|
||||
|
||||
; Same as for_loop, except uses 16-bit value in YX.
|
||||
; -256 <= step <= 255
|
||||
.macro for_loop16 routine,start,end,step
|
||||
.if (step) < -256 || (step) > 255
|
||||
.error "Step must be within -256 to 255"
|
||||
.endif
|
||||
ldy #>(start)
|
||||
lda #<(start)
|
||||
: tax
|
||||
pha
|
||||
tya
|
||||
pha
|
||||
jsr routine
|
||||
pla
|
||||
tay
|
||||
pla
|
||||
clc
|
||||
adc #step
|
||||
.if (step) > 0
|
||||
bcc :+
|
||||
iny
|
||||
.else
|
||||
bcs :+
|
||||
dey
|
||||
.endif
|
||||
: cmp #<((end)+(step))
|
||||
bne :--
|
||||
cpy #>((end)+(step))
|
||||
bne :--
|
||||
.endmacro
|
||||
|
||||
; Copies byte from in to out
|
||||
; Preserved: X, Y
|
||||
.macro mov out, in
|
||||
lda in
|
||||
sta out
|
||||
.endmacro
|
||||
|
||||
; Stores byte at addr
|
||||
; Preserved: X, Y
|
||||
.macro setb addr, byte
|
||||
lda #byte
|
||||
sta addr
|
||||
.endmacro
|
||||
|
||||
; Stores word at addr
|
||||
; Preserved: X, Y
|
||||
.macro setw addr, word
|
||||
lda #<(word)
|
||||
sta addr
|
||||
lda #>(word)
|
||||
sta addr+1
|
||||
.endmacro
|
||||
|
||||
; Loads XY with 16-bit immediate or value at address
|
||||
.macro ldxy Arg
|
||||
.if .match( .left( 1, {Arg} ), # )
|
||||
ldy #<(.right( .tcount( {Arg} )-1, {Arg} ))
|
||||
ldx #>(.right( .tcount( {Arg} )-1, {Arg} ))
|
||||
.else
|
||||
ldy (Arg)
|
||||
ldx (Arg)+1
|
||||
.endif
|
||||
.endmacro
|
||||
|
||||
; Increments word at Addr and sets Z flag appropriately
|
||||
; Preserved: A, X, Y
|
||||
.macro incw Addr
|
||||
.local @incw_skip ; doesn't work, so HOW THE HELL DO YOU MAKE A LOCAL LABEL IN A MACRO THAT DOESN"T DISTURB INVOKING CODE< HUH?????? POS
|
||||
inc Addr
|
||||
bne @incw_skip
|
||||
inc Addr+1
|
||||
@incw_skip:
|
||||
.endmacro
|
||||
|
||||
; Increments XY as 16-bit register, in CONSTANT time.
|
||||
; Z flag set based on entire result.
|
||||
; Preserved: A
|
||||
; Time: 7 clocks
|
||||
.macro inxy
|
||||
iny ; 2
|
||||
beq *+4 ; 3
|
||||
; -1
|
||||
bne *+3 ; 3
|
||||
; -1
|
||||
inx ; 2
|
||||
.endmacro
|
||||
43
src/test_roms/common/neshw.inc
Normal file
43
src/test_roms/common/neshw.inc
Normal file
@@ -0,0 +1,43 @@
|
||||
; NES I/O locations and masks
|
||||
|
||||
; Clocks per second
|
||||
.ifndef CLOCK_RATE
|
||||
CLOCK_RATE = 1789773 ; NTSC
|
||||
; CLOCK_RATE = 1662607 ; PAL
|
||||
.endif
|
||||
|
||||
.ifndef BUILD_NSF
|
||||
|
||||
; PPU
|
||||
PPUCTRL = $2000
|
||||
PPUMASK = $2001
|
||||
PPUSTATUS = $2002
|
||||
SPRADDR = $2003
|
||||
SPRDATA = $2004
|
||||
PPUSCROLL = $2005
|
||||
PPUADDR = $2006
|
||||
PPUDATA = $2007
|
||||
SPRDMA = $4014
|
||||
|
||||
PPUCTRL_NMI = $80
|
||||
PPUMASK_BG0 = $0A
|
||||
PPUCTRL_8X8 = $00
|
||||
PPUCTRL_8X16 = $20
|
||||
PPUMASK_SPR = $14
|
||||
PPUMASK_BG0CLIP = $08
|
||||
|
||||
.endif
|
||||
|
||||
; APU
|
||||
SNDCHN = $4015
|
||||
JOY1 = $4016
|
||||
JOY2 = $4017
|
||||
SNDMODE = $4017
|
||||
|
||||
SNDMODE_NOIRQ = $40
|
||||
|
||||
.if CLOCK_RATE = 1789773
|
||||
PPU_FRAMELEN = 29781
|
||||
.elseif CLOCK_RATE = 1662607
|
||||
PPU_FRAMELEN = 33248
|
||||
.endif
|
||||
142
src/test_roms/common/ppu.s
Normal file
142
src/test_roms/common/ppu.s
Normal file
@@ -0,0 +1,142 @@
|
||||
; PPU utilities
|
||||
|
||||
bss_res ppu_not_present
|
||||
|
||||
; Sets PPUADDR to w
|
||||
; Preserved: X, Y
|
||||
.macro set_ppuaddr w
|
||||
bit PPUSTATUS
|
||||
setb PPUADDR,>w
|
||||
setb PPUADDR,<w
|
||||
.endmacro
|
||||
|
||||
|
||||
; Delays by no more than n scanlines
|
||||
.macro delay_scanlines n
|
||||
.if CLOCK_RATE <> 1789773
|
||||
.error "Currently only supports NTSC"
|
||||
.endif
|
||||
delay ((n)*341)/3
|
||||
.endmacro
|
||||
|
||||
|
||||
; Waits for VBL then disables PPU rendering.
|
||||
; Preserved: A, X, Y
|
||||
disable_rendering:
|
||||
pha
|
||||
jsr wait_vbl_optional
|
||||
setb PPUMASK,0
|
||||
pla
|
||||
rts
|
||||
|
||||
|
||||
; Fills first nametable with $00
|
||||
; Preserved: Y
|
||||
clear_nametable:
|
||||
ldx #$20
|
||||
bne clear_nametable_
|
||||
|
||||
clear_nametable2:
|
||||
ldx #$24
|
||||
clear_nametable_:
|
||||
lda #0
|
||||
jsr fill_screen_
|
||||
|
||||
; Clear pattern table
|
||||
ldx #64
|
||||
: sta PPUDATA
|
||||
dex
|
||||
bne :-
|
||||
rts
|
||||
|
||||
|
||||
; Fills screen with tile A
|
||||
; Preserved: A, Y
|
||||
fill_screen:
|
||||
ldx #$20
|
||||
bne fill_screen_
|
||||
|
||||
; Same as fill_screen, but fills other nametable
|
||||
fill_screen2:
|
||||
ldx #$24
|
||||
fill_screen_:
|
||||
stx PPUADDR
|
||||
ldx #$00
|
||||
stx PPUADDR
|
||||
ldx #240
|
||||
: sta PPUDATA
|
||||
sta PPUDATA
|
||||
sta PPUDATA
|
||||
sta PPUDATA
|
||||
dex
|
||||
bne :-
|
||||
rts
|
||||
|
||||
|
||||
; Fills palette with $0F
|
||||
; Preserved: Y
|
||||
clear_palette:
|
||||
set_ppuaddr $3F00
|
||||
ldx #$20
|
||||
lda #$0F
|
||||
: sta PPUDATA
|
||||
dex
|
||||
bne :-
|
||||
|
||||
|
||||
; Fills OAM with $FF
|
||||
; Preserved: Y
|
||||
clear_oam:
|
||||
lda #$FF
|
||||
|
||||
; Fills OAM with A
|
||||
; Preserved: A, Y
|
||||
fill_oam:
|
||||
ldx #0
|
||||
stx SPRADDR
|
||||
: sta SPRDATA
|
||||
dex
|
||||
bne :-
|
||||
rts
|
||||
|
||||
|
||||
; Initializes wait_vbl_optional. Must be called before
|
||||
; using it.
|
||||
.align 32
|
||||
init_wait_vbl:
|
||||
; Wait for VBL flag to be set, or ~60000
|
||||
; clocks (2 frames) to pass
|
||||
ldy #24
|
||||
ldx #1
|
||||
bit PPUSTATUS
|
||||
: bit PPUSTATUS
|
||||
bmi @set
|
||||
dex
|
||||
bne :-
|
||||
dey
|
||||
bpl :-
|
||||
@set:
|
||||
; Be sure flag didn't stay set (in case
|
||||
; PPUSTATUS always has high bit set)
|
||||
tya
|
||||
ora PPUSTATUS
|
||||
sta ppu_not_present
|
||||
rts
|
||||
|
||||
|
||||
; Same as wait_vbl, but returns immediately if PPU
|
||||
; isn't working or doesn't support VBL flag
|
||||
; Preserved: A, X, Y
|
||||
.align 16
|
||||
wait_vbl_optional:
|
||||
bit ppu_not_present
|
||||
bmi :++
|
||||
; FALL THROUGH
|
||||
|
||||
; Clears VBL flag then waits for it to be set.
|
||||
; Preserved: A, X, Y
|
||||
wait_vbl:
|
||||
bit PPUSTATUS
|
||||
: bit PPUSTATUS
|
||||
bpl :-
|
||||
: rts
|
||||
235
src/test_roms/common/print.s
Normal file
235
src/test_roms/common/print.s
Normal file
@@ -0,0 +1,235 @@
|
||||
; Prints values in various ways to output,
|
||||
; including numbers and strings.
|
||||
|
||||
newline = 10
|
||||
|
||||
zp_byte print_temp_
|
||||
|
||||
; Prints indicated register to console as two hex
|
||||
; chars and space
|
||||
; Preserved: A, X, Y, flags
|
||||
print_a:
|
||||
php
|
||||
pha
|
||||
print_reg_:
|
||||
jsr print_hex
|
||||
lda #' '
|
||||
jsr print_char_
|
||||
pla
|
||||
plp
|
||||
rts
|
||||
|
||||
print_x:
|
||||
php
|
||||
pha
|
||||
txa
|
||||
jmp print_reg_
|
||||
|
||||
print_y:
|
||||
php
|
||||
pha
|
||||
tya
|
||||
jmp print_reg_
|
||||
|
||||
print_p:
|
||||
php
|
||||
pha
|
||||
php
|
||||
pla
|
||||
jmp print_reg_
|
||||
|
||||
print_s:
|
||||
php
|
||||
pha
|
||||
txa
|
||||
tsx
|
||||
inx
|
||||
inx
|
||||
inx
|
||||
inx
|
||||
jsr print_x
|
||||
tax
|
||||
pla
|
||||
plp
|
||||
rts
|
||||
|
||||
|
||||
; Prints A as two hex characters, NO space after
|
||||
; Preserved: A, X, Y
|
||||
print_hex:
|
||||
jsr update_crc
|
||||
|
||||
pha
|
||||
lsr a
|
||||
lsr a
|
||||
lsr a
|
||||
lsr a
|
||||
jsr @nibble
|
||||
pla
|
||||
|
||||
pha
|
||||
and #$0F
|
||||
jsr @nibble
|
||||
pla
|
||||
rts
|
||||
|
||||
@nibble:
|
||||
cmp #10
|
||||
blt @digit
|
||||
adc #6;+1 since carry is set
|
||||
@digit: adc #'0'
|
||||
jmp print_char_
|
||||
|
||||
|
||||
; Prints character and updates checksum UNLESS
|
||||
; it's a newline.
|
||||
; Preserved: A, X, Y
|
||||
print_char:
|
||||
cmp #newline
|
||||
beq :+
|
||||
jsr update_crc
|
||||
: pha
|
||||
jsr print_char_
|
||||
pla
|
||||
rts
|
||||
|
||||
|
||||
; Prints space. Does NOT update checksum.
|
||||
; Preserved: A, X, Y
|
||||
print_space:
|
||||
pha
|
||||
lda #' '
|
||||
jsr print_char_
|
||||
pla
|
||||
rts
|
||||
|
||||
|
||||
; Advances to next line. Does NOT update checksum.
|
||||
; Preserved: A, X, Y
|
||||
print_newline:
|
||||
pha
|
||||
lda #newline
|
||||
jsr print_char_
|
||||
pla
|
||||
rts
|
||||
|
||||
|
||||
; Prints string
|
||||
; Preserved: A, X, Y
|
||||
.macro print_str str,str2
|
||||
jsr print_str_
|
||||
.byte str
|
||||
.ifnblank str2
|
||||
.byte str2
|
||||
.endif
|
||||
.byte 0
|
||||
.endmacro
|
||||
|
||||
|
||||
print_str_:
|
||||
sta print_temp_
|
||||
|
||||
pla
|
||||
sta addr
|
||||
pla
|
||||
sta addr+1
|
||||
|
||||
jsr inc_addr
|
||||
jsr print_str_addr
|
||||
|
||||
lda print_temp_
|
||||
jmp (addr)
|
||||
|
||||
|
||||
; Prints string at addr and leaves addr pointing to
|
||||
; byte AFTER zero terminator.
|
||||
; Preserved: A, X, Y
|
||||
print_str_addr:
|
||||
pha
|
||||
tya
|
||||
pha
|
||||
|
||||
ldy #0
|
||||
beq :+ ; always taken
|
||||
@loop: jsr print_char
|
||||
jsr inc_addr
|
||||
: lda (addr),y
|
||||
bne @loop
|
||||
|
||||
pla
|
||||
tay
|
||||
pla
|
||||
; FALL THROUGH
|
||||
|
||||
; Increments 16-bit value in addr.
|
||||
; Preserved: A, X, Y
|
||||
inc_addr:
|
||||
inc addr
|
||||
beq :+
|
||||
rts
|
||||
: inc addr+1
|
||||
rts
|
||||
|
||||
|
||||
; Prints A as 1-3 digit decimal value, NO space after.
|
||||
; Preserved: A, X, Y
|
||||
print_dec:
|
||||
pha
|
||||
sta print_temp_
|
||||
txa
|
||||
pha
|
||||
lda print_temp_
|
||||
|
||||
; Hundreds
|
||||
cmp #10
|
||||
blt @ones
|
||||
cmp #100
|
||||
blt @tens
|
||||
ldx #'0'-1
|
||||
: inx
|
||||
sbc #100
|
||||
bge :-
|
||||
adc #100
|
||||
jsr @digit
|
||||
|
||||
; Tens
|
||||
@tens: sec
|
||||
ldx #'0'-1
|
||||
: inx
|
||||
sbc #10
|
||||
bge :-
|
||||
adc #10
|
||||
jsr @digit
|
||||
|
||||
; Ones
|
||||
@ones: ora #'0'
|
||||
jsr print_char
|
||||
pla
|
||||
tax
|
||||
pla
|
||||
rts
|
||||
|
||||
; Print a single digit
|
||||
@digit: pha
|
||||
txa
|
||||
jsr print_char
|
||||
pla
|
||||
rts
|
||||
|
||||
|
||||
; Prints one of two characters based on condition.
|
||||
; SEC; print_cc bcs,'C','-' prints 'C'.
|
||||
; Preserved: A, X, Y, flags
|
||||
.macro print_cc cond,yes,no
|
||||
; Avoids labels since they're not local
|
||||
; to macros in ca65.
|
||||
php
|
||||
pha
|
||||
cond *+6
|
||||
lda #no
|
||||
bne *+4
|
||||
lda #yes
|
||||
jsr print_char
|
||||
pla
|
||||
plp
|
||||
.endmacro
|
||||
64
src/test_roms/common/run_at_reset.s
Normal file
64
src/test_roms/common/run_at_reset.s
Normal file
@@ -0,0 +1,64 @@
|
||||
; Keeps track of number of times reset, and prompts user.
|
||||
|
||||
power_flag_value = $42
|
||||
|
||||
nv_res power_flag_
|
||||
nv_res num_resets_
|
||||
|
||||
; Out: A = number of times NES has been reset since turned on
|
||||
; Preserved: X, Y
|
||||
num_resets:
|
||||
lda power_flag_
|
||||
cmp #power_flag_value
|
||||
bne :+
|
||||
lda num_resets_
|
||||
rts
|
||||
: lda #0
|
||||
rts
|
||||
|
||||
|
||||
; Prompts user to press reset after message disappears,
|
||||
; then hides message, increments reset count, and asks
|
||||
; emulator to reset NES.
|
||||
; Preserved: X, Y
|
||||
prompt_to_reset:
|
||||
print_str {newline,newline,"Press reset AFTER this message",newline,"disappears"}
|
||||
|
||||
; Add "again" if this isn't first requested reset
|
||||
jsr num_resets
|
||||
beq :+
|
||||
print_str ", again"
|
||||
:
|
||||
; Show for a few seconds
|
||||
print_str {newline,newline,newline}
|
||||
jsr console_show
|
||||
delay_msec 1000
|
||||
delay_msec 1000
|
||||
|
||||
jsr inc_reset_count
|
||||
|
||||
; Tell emulator that NES should be reset now
|
||||
lda #$81
|
||||
jsr set_final_result
|
||||
|
||||
jsr console_hide
|
||||
rts
|
||||
|
||||
|
||||
; Increments reset count and marks it as valid
|
||||
; Preserved: X, Y
|
||||
inc_reset_count:
|
||||
jsr num_resets
|
||||
clc
|
||||
adc #1
|
||||
bcc :+
|
||||
lda #$FF ; don't wrap around
|
||||
: sta num_resets_
|
||||
setb power_flag_,power_flag_value
|
||||
rts
|
||||
|
||||
|
||||
; Waits in infinite loop for reset
|
||||
; Preserved: A, X, Y, flags
|
||||
wait_reset:
|
||||
jmp wait_reset
|
||||
26
src/test_roms/common/shell.inc
Normal file
26
src/test_roms/common/shell.inc
Normal file
@@ -0,0 +1,26 @@
|
||||
; Included at beginning of program
|
||||
|
||||
.macro stp
|
||||
.byte #$02
|
||||
.endmacro
|
||||
.include "macros.inc"
|
||||
.include "neshw.inc"
|
||||
|
||||
.ifdef CUSTOM_PREFIX
|
||||
.include "custom_prefix.s"
|
||||
.endif
|
||||
|
||||
; ; Devcart
|
||||
; .ifdef BUILD_DEVCART
|
||||
; .include "build_devcart.s"
|
||||
; .endif
|
||||
|
||||
; NES internal RAM
|
||||
.ifdef BUILD_NOCART
|
||||
.include "build_nocart.s"
|
||||
.endif
|
||||
|
||||
; NES ROM (default)
|
||||
.ifndef SHELL_INCLUDED
|
||||
.include "build_rom.s"
|
||||
.endif
|
||||
384
src/test_roms/common/shell.s
Normal file
384
src/test_roms/common/shell.s
Normal file
@@ -0,0 +1,384 @@
|
||||
; Common routines and runtime
|
||||
|
||||
; Detect inclusion loops (otherwise ca65 goes crazy)
|
||||
.ifdef SHELL_INCLUDED
|
||||
.error "shell.s included twice"
|
||||
.end
|
||||
.endif
|
||||
SHELL_INCLUDED = 1
|
||||
|
||||
; Temporary variables that ANY routine might modify, so
|
||||
; only use them between routine calls.
|
||||
temp = <$A
|
||||
temp2 = <$B
|
||||
temp3 = <$C
|
||||
addr = <$E
|
||||
ptr = addr
|
||||
|
||||
; Move code to $E200 ($200 bytes for text output in devcarts
|
||||
; where WRAM is mirrored to $E000)
|
||||
.segment "CODE"
|
||||
.res $2200
|
||||
|
||||
; Put shell code after user code, so user code is in more
|
||||
; consistent environment
|
||||
.segment "CODE2"
|
||||
|
||||
; Any user code which runs off end might end up here,
|
||||
; so catch that mistake.
|
||||
nop ; in case there was three-byte opcode before this
|
||||
nop
|
||||
jmp internal_error
|
||||
|
||||
;**** Common routines ****
|
||||
|
||||
.include "macros.inc"
|
||||
.include "neshw.inc"
|
||||
.include "delay.s"
|
||||
.include "print.s"
|
||||
.include "crc.s"
|
||||
.include "testing.s"
|
||||
|
||||
.ifdef NEED_CONSOLE
|
||||
.include "console.s"
|
||||
.else
|
||||
; Stubs so code doesn't have to care whether
|
||||
; console exists
|
||||
console_init:
|
||||
console_show:
|
||||
console_hide:
|
||||
console_print:
|
||||
console_flush:
|
||||
rts
|
||||
.endif
|
||||
|
||||
.ifndef CUSTOM_PRINT
|
||||
.include "text_out.s"
|
||||
|
||||
print_char_:
|
||||
jsr write_text_out
|
||||
jmp console_print
|
||||
|
||||
stop_capture:
|
||||
rts
|
||||
.endif
|
||||
|
||||
;**** Shell core ****
|
||||
|
||||
.ifndef CUSTOM_RESET
|
||||
reset:
|
||||
sei
|
||||
jmp std_reset
|
||||
.endif
|
||||
|
||||
|
||||
; Sets up hardware then runs main
|
||||
run_shell:
|
||||
sei
|
||||
cld ; unnecessary on NES, but might help on clone
|
||||
ldx #$FF
|
||||
txs
|
||||
jsr init_shell
|
||||
set_test $FF
|
||||
jmp run_main
|
||||
|
||||
|
||||
; Initializes shell
|
||||
init_shell:
|
||||
jsr clear_ram
|
||||
jsr init_wait_vbl ; waits for VBL once here,
|
||||
jsr wait_vbl_optional ; so only need to wait once more
|
||||
; jsr init_text_out
|
||||
jsr init_testing
|
||||
.byte $02
|
||||
jsr init_runtime
|
||||
jsr console_init
|
||||
rts
|
||||
|
||||
|
||||
; Runs main in consistent PPU/APU environment, then exits
|
||||
; with code 0
|
||||
run_main:
|
||||
jsr pre_main
|
||||
jsr main
|
||||
lda #0
|
||||
jmp exit
|
||||
|
||||
|
||||
; Sets up environment for main to run in
|
||||
pre_main:
|
||||
|
||||
.ifndef BUILD_NSF
|
||||
jsr disable_rendering
|
||||
setb PPUCTRL,0
|
||||
jsr clear_palette
|
||||
jsr clear_nametable
|
||||
jsr clear_nametable2
|
||||
jsr clear_oam
|
||||
.endif
|
||||
|
||||
lda #$34
|
||||
pha
|
||||
lda #0
|
||||
tax
|
||||
tay
|
||||
jsr wait_vbl_optional
|
||||
plp
|
||||
sta SNDMODE
|
||||
rts
|
||||
|
||||
|
||||
.ifndef CUSTOM_EXIT
|
||||
exit:
|
||||
.endif
|
||||
|
||||
; Reports result and ends program
|
||||
std_exit:
|
||||
.byte $02
|
||||
sei
|
||||
cld
|
||||
ldx #$FF
|
||||
txs
|
||||
|
||||
ldx #0
|
||||
stx SNDCHN
|
||||
.ifndef BUILD_NSF
|
||||
stx PPUCTRL
|
||||
.endif
|
||||
|
||||
jsr report_result
|
||||
jmp post_exit
|
||||
|
||||
|
||||
; Reports final result code in A
|
||||
report_result:
|
||||
jsr :+
|
||||
jmp play_byte
|
||||
|
||||
: jsr print_newline
|
||||
jsr console_show
|
||||
|
||||
; 0: ""
|
||||
cmp #1
|
||||
bge :+
|
||||
rts
|
||||
:
|
||||
; 1: "Failed"
|
||||
bne :+
|
||||
print_str {"Failed",newline}
|
||||
rts
|
||||
|
||||
; n: "Failed #n"
|
||||
: print_str "Failed #"
|
||||
jsr print_dec
|
||||
jsr print_newline
|
||||
rts
|
||||
|
||||
;**** Other routines ****
|
||||
|
||||
; Reports internal error and exits program
|
||||
internal_error:
|
||||
print_str newline,"Internal error"
|
||||
lda #255
|
||||
jmp exit
|
||||
|
||||
|
||||
.import __NVRAM_LOAD__, __NVRAM_SIZE__
|
||||
|
||||
.macro fill_ram_ Begin, End
|
||||
.local Neg_size
|
||||
Neg_size = (Begin) - (End)
|
||||
ldxy #(Begin) - <Neg_size
|
||||
sty addr
|
||||
stx addr+1
|
||||
ldxy #Neg_size
|
||||
: sta (addr),y
|
||||
iny
|
||||
bne :-
|
||||
inc addr+1
|
||||
inx
|
||||
bne :-
|
||||
.endmacro
|
||||
|
||||
; Clears 0 through ($100+S), $200 through __NVRAM_LOAD__-1, and
|
||||
; __NVRAM_LOAD__+__NVRAM_SIZE__ through $7FF
|
||||
clear_ram:
|
||||
lda #0
|
||||
|
||||
bss_begin = $200
|
||||
|
||||
fill_ram_ bss_begin,__NVRAM_LOAD__
|
||||
fill_ram_ __NVRAM_LOAD__+__NVRAM_SIZE__,$800
|
||||
|
||||
; Zero-page
|
||||
tax
|
||||
: sta 0,x
|
||||
inx
|
||||
bne :-
|
||||
|
||||
; Stack below S
|
||||
tsx
|
||||
inx
|
||||
: dex
|
||||
sta $100,x
|
||||
bne :-
|
||||
|
||||
rts
|
||||
|
||||
|
||||
nv_res unused_nv_var ; to avoid size=0
|
||||
|
||||
; Clears nvram
|
||||
clear_nvram:
|
||||
lda #0
|
||||
fill_ram_ __NVRAM_LOAD__,__NVRAM_LOAD__+__NVRAM_SIZE__
|
||||
rts
|
||||
|
||||
|
||||
; Prints filename and newline, if available, otherwise nothing.
|
||||
; Preserved: A, X, Y
|
||||
print_filename:
|
||||
.ifdef FILENAME_KNOWN
|
||||
pha
|
||||
jsr print_newline
|
||||
setw addr,filename
|
||||
jsr print_str_addr
|
||||
jsr print_newline
|
||||
pla
|
||||
.endif
|
||||
rts
|
||||
|
||||
.pushseg
|
||||
.segment "RODATA"
|
||||
; Filename terminated with zero byte.
|
||||
filename:
|
||||
.ifdef FILENAME_KNOWN
|
||||
.incbin "ram:nes_temp"
|
||||
.endif
|
||||
.byte 0
|
||||
.popseg
|
||||
|
||||
|
||||
;**** ROM-specific ****
|
||||
.ifndef BUILD_NSF
|
||||
|
||||
.include "ppu.s"
|
||||
|
||||
avoid_silent_nsf:
|
||||
play_byte:
|
||||
rts
|
||||
|
||||
; Loads ASCII font into CHR RAM
|
||||
.macro load_ascii_chr
|
||||
bit PPUSTATUS
|
||||
setb PPUADDR,$00
|
||||
setb PPUADDR,$00
|
||||
setb addr,<ascii_chr
|
||||
ldx #>ascii_chr
|
||||
ldy #0
|
||||
@page:
|
||||
stx addr+1
|
||||
: lda (addr),y
|
||||
sta PPUDATA
|
||||
iny
|
||||
bne :-
|
||||
inx
|
||||
cpx #>ascii_chr_end
|
||||
bne @page
|
||||
.endmacro
|
||||
|
||||
; Disables interrupts and loops forever
|
||||
.ifndef CUSTOM_FOREVER
|
||||
forever:
|
||||
sei
|
||||
lda #0
|
||||
sta PPUCTRL
|
||||
: beq :-
|
||||
.res $10,$EA ; room for code to run loader
|
||||
.endif
|
||||
|
||||
|
||||
; Default NMI
|
||||
.ifndef CUSTOM_NMI
|
||||
zp_byte nmi_count
|
||||
|
||||
nmi:
|
||||
inc nmi_count
|
||||
rti
|
||||
|
||||
; Waits for NMI. Must be using NMI handler that increments
|
||||
; nmi_count, with NMI enabled.
|
||||
; Preserved: X, Y
|
||||
wait_nmi:
|
||||
lda nmi_count
|
||||
: cmp nmi_count
|
||||
beq :-
|
||||
rts
|
||||
.endif
|
||||
|
||||
|
||||
; Default IRQ
|
||||
.ifndef CUSTOM_IRQ
|
||||
irq:
|
||||
bit SNDCHN ; clear APU IRQ flag
|
||||
rti
|
||||
.endif
|
||||
|
||||
.endif
|
||||
|
||||
|
||||
; Reports A in binary as high and low tones, with
|
||||
; leading low tone for reference. Omits leading
|
||||
; zeroes. Doesn't hang if no APU is present.
|
||||
; Preserved: A, X, Y
|
||||
play_hex:
|
||||
pha
|
||||
|
||||
; Make low reference beep
|
||||
clc
|
||||
jsr @beep
|
||||
|
||||
; Remove high zero bits
|
||||
sec
|
||||
: rol a
|
||||
bcc :-
|
||||
|
||||
; Play remaining bits
|
||||
beq @zero
|
||||
: jsr @beep
|
||||
asl a
|
||||
bne :-
|
||||
@zero:
|
||||
|
||||
delay_msec 300
|
||||
pla
|
||||
rts
|
||||
|
||||
; Plays low/high beep based on carry
|
||||
; Preserved: A, X, Y
|
||||
@beep:
|
||||
pha
|
||||
|
||||
; Set up square
|
||||
lda #1
|
||||
sta SNDCHN
|
||||
sta $4001
|
||||
sta $4003
|
||||
adc #$FE ; period=$100 if carry, $1FF if none
|
||||
sta $4002
|
||||
|
||||
; Fade volume
|
||||
lda #$0F
|
||||
: ora #$30
|
||||
sta $4000
|
||||
delay_msec 8
|
||||
sec
|
||||
sbc #$31
|
||||
bpl :-
|
||||
|
||||
; Silence
|
||||
sta SNDCHN
|
||||
delay_msec 160
|
||||
|
||||
pla
|
||||
rts
|
||||
106
src/test_roms/common/testing.s
Normal file
106
src/test_roms/common/testing.s
Normal file
@@ -0,0 +1,106 @@
|
||||
; Utilities for writing test ROMs
|
||||
|
||||
; In NVRAM so these can be used before initializing runtime,
|
||||
; then runtime initialized without clearing them
|
||||
nv_res test_code ; code of current test
|
||||
nv_res test_name,2 ; address of name of current test, or 0 of none
|
||||
|
||||
|
||||
; Sets current test code and optional name. Also resets
|
||||
; checksum.
|
||||
; Preserved: A, X, Y
|
||||
.macro set_test code,name
|
||||
pha
|
||||
lda #code
|
||||
jsr set_test_
|
||||
.ifblank name
|
||||
setb test_name+1,0
|
||||
.else
|
||||
.local Addr
|
||||
setw test_name,Addr
|
||||
seg_data "RODATA",{Addr: .byte name,0}
|
||||
.endif
|
||||
pla
|
||||
.endmacro
|
||||
|
||||
set_test_:
|
||||
sta test_code
|
||||
jmp reset_crc
|
||||
|
||||
|
||||
; Initializes testing module
|
||||
init_testing:
|
||||
jmp init_crc
|
||||
|
||||
|
||||
; Reports that all tests passed
|
||||
tests_passed:
|
||||
jsr print_filename
|
||||
print_str newline,"Passed"
|
||||
lda #0
|
||||
jmp exit
|
||||
|
||||
|
||||
; Reports "Done" if set_test has never been used,
|
||||
; "Passed" if set_test 0 was last used, or
|
||||
; failure if set_test n was last used.
|
||||
tests_done:
|
||||
ldx test_code
|
||||
jeq tests_passed
|
||||
inx
|
||||
bne test_failed
|
||||
jsr print_filename
|
||||
print_str newline,"Done"
|
||||
lda #0
|
||||
jmp exit
|
||||
|
||||
|
||||
; Reports that the current test failed. Prints code and
|
||||
; name last set with set_test, or just "Failed" if none
|
||||
; have been set yet.
|
||||
test_failed:
|
||||
ldx test_code
|
||||
|
||||
; Treat $FF as 1, in case it wasn't ever set
|
||||
inx
|
||||
bne :+
|
||||
inx
|
||||
stx test_code
|
||||
:
|
||||
; If code >= 2, print name
|
||||
cpx #2-1 ; -1 due to inx above
|
||||
blt :+
|
||||
lda test_name+1
|
||||
beq :+
|
||||
jsr print_newline
|
||||
sta addr+1
|
||||
lda test_name
|
||||
sta addr
|
||||
jsr print_str_addr
|
||||
jsr print_newline
|
||||
:
|
||||
jsr print_filename
|
||||
|
||||
; End program
|
||||
lda test_code
|
||||
jmp exit
|
||||
|
||||
|
||||
; If checksum doesn't match expected, reports failed test.
|
||||
; Clears checksum afterwards.
|
||||
; Preserved: A, X, Y
|
||||
.macro check_crc expected
|
||||
jsr_with_addr check_crc_,{.dword expected}
|
||||
.endmacro
|
||||
|
||||
check_crc_:
|
||||
pha
|
||||
jsr is_crc_
|
||||
bne :+
|
||||
jsr reset_crc
|
||||
pla
|
||||
rts
|
||||
|
||||
: jsr print_newline
|
||||
jsr print_crc
|
||||
jmp test_failed
|
||||
61
src/test_roms/common/text_out.s
Normal file
61
src/test_roms/common/text_out.s
Normal file
@@ -0,0 +1,61 @@
|
||||
; Text output as expanding zero-terminated string at text_out_base
|
||||
|
||||
; The final exit result byte is written here
|
||||
final_result = $6000
|
||||
|
||||
; Text output is written here as an expanding
|
||||
; zero-terminated string
|
||||
text_out_base = $6004
|
||||
|
||||
bss_res text_out_temp
|
||||
zp_res text_out_addr,2
|
||||
|
||||
init_text_out:
|
||||
ldx #0
|
||||
|
||||
; Put valid data first
|
||||
setb text_out_base,0
|
||||
|
||||
lda #$80
|
||||
jsr set_final_result
|
||||
|
||||
; Now fill in signature that tells emulator there's
|
||||
; useful data there
|
||||
setb text_out_base-3,$DE
|
||||
setb text_out_base-2,$B0
|
||||
setb text_out_base-1,$61
|
||||
|
||||
ldx #>text_out_base
|
||||
stx text_out_addr+1
|
||||
setb text_out_addr,<text_out_base
|
||||
rts
|
||||
|
||||
|
||||
; Sets final result byte in memory
|
||||
set_final_result:
|
||||
sta final_result
|
||||
rts
|
||||
|
||||
|
||||
; Writes character to text output
|
||||
; In: A=Character to write
|
||||
; Preserved: A, X, Y
|
||||
write_text_out:
|
||||
sty text_out_temp
|
||||
|
||||
; Write new terminator FIRST, then new char before it,
|
||||
; in case emulator looks at string in middle of this routine.
|
||||
ldy #1
|
||||
pha
|
||||
lda #0
|
||||
sta (text_out_addr),y
|
||||
dey
|
||||
pla
|
||||
sta (text_out_addr),y
|
||||
|
||||
inc text_out_addr
|
||||
bne :+
|
||||
inc text_out_addr+1
|
||||
:
|
||||
ldy text_out_temp
|
||||
rts
|
||||
17
src/test_roms/cpu_reset_ram.rs
Normal file
17
src/test_roms/cpu_reset_ram.rs
Normal file
@@ -0,0 +1,17 @@
|
||||
use super::rom_test;
|
||||
|
||||
// TODO: need to implement soft reset behavior
|
||||
// rom_test!(basic_cpu, "cpu_reset_ram.nes", |nes| {
|
||||
// assert_eq!(nes.last_instruction, "0x8001 HLT :2 []");
|
||||
// // Off by one from Mesen, since Mesen doesn't count the clock cycle attempting to execute the 'invalid' opcode
|
||||
// assert_eq!(nes.cycle, 11);
|
||||
// // This is off by one from Mesen, because Mesen is left pointing at the 'invalid' opcode
|
||||
// assert_eq!(nes.cpu.pc, 0x8002);
|
||||
// // Off by one from Mesen, since Mesen doesn't count the clock cycle attempting to execute the 'invalid' opcode
|
||||
// assert_eq!(nes.ppu.pixel, 35);
|
||||
|
||||
// assert_eq!(nes.cpu.sp, 0xFD);
|
||||
// // assert_eq!(nes.cpu.a, 0x00);
|
||||
// // assert_eq!(nes.cpu.x, 0x00);
|
||||
// // assert_eq!(nes.cpu.y, 0x00);
|
||||
// });
|
||||
84
src/test_roms/cpu_reset_ram.s.old
Normal file
84
src/test_roms/cpu_reset_ram.s.old
Normal file
@@ -0,0 +1,84 @@
|
||||
; Verifies that reset doesn't alter any RAM.
|
||||
|
||||
CUSTOM_RESET=1
|
||||
.include "shell.inc"
|
||||
.include "run_at_reset.s"
|
||||
|
||||
nv_res bad_addr,2
|
||||
|
||||
reset: sei
|
||||
cld
|
||||
ldx #$FF
|
||||
txs
|
||||
|
||||
ldx #7
|
||||
ldy #0
|
||||
|
||||
; Check first byte, and assume just powered
|
||||
; if not as expected
|
||||
lda <0
|
||||
cmp #$DB
|
||||
jne std_reset
|
||||
iny
|
||||
|
||||
; Check second byte
|
||||
lda <1
|
||||
cmp #$B6
|
||||
bne failed
|
||||
iny
|
||||
|
||||
; Rest of internal memory
|
||||
setb <0,0
|
||||
setb <1,0
|
||||
lda #$6D
|
||||
clc
|
||||
: eor (<0),y
|
||||
bne failed
|
||||
lda (<0),y
|
||||
rol a
|
||||
iny
|
||||
bne :-
|
||||
inc <1
|
||||
dex
|
||||
bpl :-
|
||||
jmp tests_passed
|
||||
|
||||
failed:
|
||||
sty bad_addr
|
||||
txa
|
||||
eor #$07
|
||||
sta bad_addr+1
|
||||
|
||||
jsr init_shell
|
||||
|
||||
print_str "Addr: "
|
||||
lda bad_addr+1
|
||||
jsr print_hex
|
||||
lda bad_addr
|
||||
jsr print_a
|
||||
|
||||
set_test 3,"Reset shouldn't modify RAM"
|
||||
; stp
|
||||
|
||||
main:
|
||||
jsr prompt_to_reset
|
||||
|
||||
; Fill RAM with pattern
|
||||
setb <0,0
|
||||
setb <1,0
|
||||
ldx #8
|
||||
ldy #2
|
||||
lda #$6D
|
||||
clc
|
||||
: sta (<0),y
|
||||
rol a
|
||||
iny
|
||||
bne :-
|
||||
inc <1
|
||||
dex
|
||||
bne :-
|
||||
|
||||
setb <0,$DB
|
||||
setb <1,$B6
|
||||
|
||||
jmp wait_reset
|
||||
63
src/test_roms/crc_check.s
Normal file
63
src/test_roms/crc_check.s
Normal file
@@ -0,0 +1,63 @@
|
||||
|
||||
.include "shell.inc"
|
||||
.include "crc_fast.s"
|
||||
|
||||
run_crc:
|
||||
pha
|
||||
jsr reset_crc
|
||||
pla
|
||||
jsr update_crc_fast
|
||||
ldy #0
|
||||
lda checksum
|
||||
sta ($0),y
|
||||
iny
|
||||
lda checksum+1
|
||||
sta ($0),y
|
||||
iny
|
||||
lda checksum+2
|
||||
sta ($0),y
|
||||
iny
|
||||
lda checksum+3
|
||||
sta ($0),y
|
||||
rts
|
||||
.macro crc_check crc_val,val
|
||||
lda #.lobyte(crc_val)
|
||||
sta $0
|
||||
lda #.hibyte(crc_val)
|
||||
sta $1
|
||||
lda #val
|
||||
jsr run_crc
|
||||
.endmacro
|
||||
|
||||
main:
|
||||
crc_check crc_1,$00
|
||||
crc_check crc_2,$01
|
||||
crc_check crc_3,$02
|
||||
crc_check crc_4,$7F
|
||||
crc_check crc_5,$80
|
||||
crc_check crc_6,$81
|
||||
crc_check crc_7,$FF
|
||||
lda #$00
|
||||
jmp exit
|
||||
|
||||
.org $20
|
||||
crc_1: .res 4
|
||||
crc_2: .res 4
|
||||
crc_3: .res 4
|
||||
crc_4: .res 4
|
||||
crc_5: .res 4
|
||||
crc_6: .res 4
|
||||
crc_7: .res 4
|
||||
crc_8: .res 4
|
||||
crc_9: .res 4
|
||||
crc_10: .res 4
|
||||
crc_11: .res 4
|
||||
crc_12: .res 4
|
||||
crc_13: .res 4
|
||||
crc_14: .res 4
|
||||
crc_15: .res 4
|
||||
crc_16: .res 4
|
||||
crc_17: .res 4
|
||||
crc_18: .res 4
|
||||
crc_19: .res 4
|
||||
crc_20: .res 4
|
||||
130
src/test_roms/implied_instrs.s
Normal file
130
src/test_roms/implied_instrs.s
Normal file
@@ -0,0 +1,130 @@
|
||||
|
||||
OPERANDS = 0
|
||||
.include "shell.inc"
|
||||
; .include "instr_test.inc"
|
||||
; .include "macros.inc"
|
||||
|
||||
values:
|
||||
.byte 0,1,2,$40,$7F,$80,$81,$FF
|
||||
values_size = * - values
|
||||
|
||||
zp_res in_a,2
|
||||
zp_res in_x,2
|
||||
zp_res in_y,2
|
||||
zp_res in_p,2
|
||||
zp_res in_s,2
|
||||
zp_res out_a
|
||||
zp_res out_x
|
||||
zp_res out_y
|
||||
zp_res out_p
|
||||
zp_res out_s
|
||||
|
||||
.macro store_ptr zp_ptr, value
|
||||
lda #.lobyte(value)
|
||||
sta zp_ptr
|
||||
lda #.hibyte(value)
|
||||
sta zp_ptr+1
|
||||
.endmacro
|
||||
|
||||
testcase_init:
|
||||
store_ptr in_a, values
|
||||
store_ptr in_x, values
|
||||
store_ptr in_y, values
|
||||
store_ptr in_p, values
|
||||
store_ptr in_s, values
|
||||
rts
|
||||
|
||||
; Calling convention: Will trash stack - anything that needs to be saved must be elsewhere
|
||||
; Will call `test_code` with the requisite
|
||||
zp_res return_addr
|
||||
run_tc:
|
||||
lda (in_s,x)
|
||||
tax
|
||||
txs ; As long as the following code has the same number of pushes & pulls, stack ptr will be correct
|
||||
ldx #0
|
||||
lda (in_p,x)
|
||||
pha
|
||||
lda (in_a,x)
|
||||
pha
|
||||
lda (in_x,x)
|
||||
pha
|
||||
lda (in_y,x)
|
||||
pha
|
||||
; Stack <y, x, a, p>
|
||||
pla
|
||||
tay
|
||||
; Stack <x, a, p>, y correct
|
||||
pla
|
||||
tax
|
||||
; Stack <a, p>, C: x, y
|
||||
pla
|
||||
; Stack <p>, C: x, y
|
||||
plp
|
||||
; Stack <>, C: x, y, p, (also S)
|
||||
jsr test_code
|
||||
php
|
||||
sta out_a
|
||||
pla
|
||||
sta out_p
|
||||
stx out_x
|
||||
sty out_y
|
||||
tsx
|
||||
stx out_s
|
||||
|
||||
jmp (return_addr)
|
||||
|
||||
test_code:
|
||||
rts
|
||||
|
||||
main:
|
||||
jsr testcase_init
|
||||
; $A2,$67,$77,$F8
|
||||
; entry $2A,"ROL A",$CB,$4B,$9A,$4C ; A = op A
|
||||
; entry $0A,"ASL A",$00,$00,$00,$00
|
||||
; entry $0A,"ROR A",$00,$00,$00,$00
|
||||
; entry $4A,"LSR A",$00,$00,$00,$00
|
||||
|
||||
; entry $8A,"TXA",$00,$00,$00,$00 ; AXY = AXY
|
||||
; entry $98,"TYA",$00,$00,$00,$00
|
||||
; entry $AA,"TAX",$00,$00,$00,$00
|
||||
; entry $A8,"TAY",$00,$00,$00,$00
|
||||
|
||||
; entry $E8,"INX",$00,$00,$00,$00 ; XY = op XY
|
||||
; entry $C8,"INY",$00,$00,$00,$00
|
||||
; entry $CA,"DEX",$00,$00,$00,$00
|
||||
; entry $88,"DEY",$00,$00,$00,$00
|
||||
|
||||
; entry $38,"SEC",$00,$00,$00,$00 ; flags = op flags
|
||||
; entry $18,"CLC",$00,$00,$00,$00
|
||||
; entry $F8,"SED",$00,$00,$00,$00
|
||||
; entry $D8,"CLD",$00,$00,$00,$00
|
||||
; entry $78,"SEI",$00,$00,$00,$00
|
||||
; entry $58,"CLI",$00,$00,$00,$00
|
||||
; entry $B8,"CLV",$00,$00,$00,$00
|
||||
|
||||
; entry $EA,"NOP",$00,$00,$00,$00
|
||||
|
||||
lda #$00
|
||||
jmp exit
|
||||
|
||||
|
||||
; .ifndef OFFICIAL_ONLY
|
||||
; entry $1A,"NOP"
|
||||
; entry $3A,"NOP"
|
||||
; entry $5A,"NOP"
|
||||
; entry $7A,"NOP"
|
||||
; entry $DA,"NOP"
|
||||
; entry $FA,"NOP"
|
||||
; .endif
|
||||
; instrs_size = * - instrs
|
||||
|
||||
; instr_template:
|
||||
; nop
|
||||
; jmp instr_done
|
||||
; instr_template_size = * - instr_template
|
||||
|
||||
; .include "instr_test_end.s"
|
||||
|
||||
; test_values:
|
||||
; test_normal
|
||||
; rts
|
||||
63
src/test_roms/instr_test_v3.rs
Normal file
63
src/test_roms/instr_test_v3.rs
Normal file
@@ -0,0 +1,63 @@
|
||||
use std::fmt;
|
||||
|
||||
use crate::{NES, hex_view::Memory};
|
||||
|
||||
use super::rom_test;
|
||||
|
||||
enum HexDump<'a> {
|
||||
NES(&'a NES, u16, usize),
|
||||
Lit(&'a [u8]),
|
||||
}
|
||||
|
||||
impl HexDump<'_> {
|
||||
fn get(&self, off: usize) -> Option<u8> {
|
||||
match self {
|
||||
Self::NES(_, _, len) if off >= *len => None,
|
||||
Self::NES(nes, addr, _) => nes.cpu_mem().peek(addr + off as u16),
|
||||
Self::Lit(items) => items.get(off).copied(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for HexDump<'_> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
let mut i = 0;
|
||||
while let Some(val) = self.get(i) {
|
||||
if i % 16 == 0 {
|
||||
write!(f, "\n{i:04X}:")?;
|
||||
}
|
||||
write!(f, " {val:02X}")?;
|
||||
i += 1;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn mem_cmp(nes: &NES, addr: u16, vals: &[u8]) {
|
||||
for (i, v) in vals.iter().enumerate() {
|
||||
if nes.cpu_mem().peek(addr + i as u16) != Some(*v) {
|
||||
panic!(
|
||||
"memcmp assertion failed:\nNES:{}\nTest:{}",
|
||||
HexDump::NES(nes, addr, vals.len()),
|
||||
HexDump::Lit(vals)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
rom_test!(crc_check, "crc_check.nes", |nes| {
|
||||
assert_eq!(nes.last_instruction, "0x8026 HLT :2 []");
|
||||
assert_eq!(nes.cpu.a, 0x00);
|
||||
mem_cmp(&nes, 0x20, &[0xFF, 0xFF, 0xFF, 0xFF]);
|
||||
mem_cmp(&nes, 0x24, &[0x69, 0xCF, 0xF8, 0x88]);
|
||||
mem_cmp(&nes, 0x28, &[0xD3, 0x9E, 0xF1, 0x11]);
|
||||
mem_cmp(&nes, 0x2C, &[0x52, 0x93, 0x45, 0x3F]);
|
||||
mem_cmp(&nes, 0x30, &[0xDF, 0x7C, 0x47, 0x12]);
|
||||
mem_cmp(&nes, 0x34, &[0x49, 0x4C, 0x40, 0x65]);
|
||||
mem_cmp(&nes, 0x38, &[0x72, 0x10, 0xFD, 0xD2]);
|
||||
});
|
||||
|
||||
rom_test!(implied_instructions, "implied_instrs.nes", |nes| {
|
||||
assert_eq!(nes.last_instruction, "0x8026 HLT :2 []");
|
||||
assert_eq!(nes.cpu.a, 0x00);
|
||||
});
|
||||
@@ -1,15 +1,18 @@
|
||||
use crate::{NES, hex_view::Memory};
|
||||
mod cpu_reset_ram;
|
||||
mod instr_test_v3;
|
||||
|
||||
use crate::hex_view::Memory;
|
||||
|
||||
macro_rules! rom_test {
|
||||
($name:ident, $rom:literal, |$nes:ident| $eval:expr) => {
|
||||
rom_test!($name, $rom, timeout = 10000000, |$nes| $eval);
|
||||
rom_test!($name, $rom, timeout = 10000000, |$nes| $eval);
|
||||
};
|
||||
($name:ident, $rom:literal, timeout = $timeout:expr, |$nes:ident| $eval:expr) => {
|
||||
#[test]
|
||||
fn $name() {
|
||||
let rom_file = concat!(env!("ROM_DIR"), "/", $rom);
|
||||
println!("{}: {}", stringify!($name), rom_file);
|
||||
let mut $nes = NES::load_nes_file(rom_file).expect("Failed to create nes object");
|
||||
let mut $nes = crate::NES::load_nes_file(rom_file).expect("Failed to create nes object");
|
||||
$nes.reset_and_run_with_timeout($timeout);
|
||||
println!("Final: {:?}", $nes);
|
||||
$eval
|
||||
@@ -23,7 +26,7 @@ macro_rules! rom_test {
|
||||
fn $name() {
|
||||
let rom_file = $rom;
|
||||
println!("{}: {}", stringify!($name), rom_file);
|
||||
let mut $nes = NES::load_nes_file(rom_file).expect("Failed to create nes object");
|
||||
let mut $nes = crate::NES::load_nes_file(rom_file).expect("Failed to create nes object");
|
||||
$nes.reset_and_run_with_timeout($timeout);
|
||||
println!("Final: {:?}", $nes);
|
||||
$eval
|
||||
@@ -31,6 +34,8 @@ macro_rules! rom_test {
|
||||
};
|
||||
}
|
||||
|
||||
pub(crate) use rom_test;
|
||||
|
||||
rom_test!(basic_cpu, "basic-cpu.nes", |nes| {
|
||||
assert_eq!(nes.last_instruction, "0x8001 HLT :2 []");
|
||||
// Off by one from Mesen, since Mesen doesn't count the clock cycle attempting to execute the 'invalid' opcode
|
||||
|
||||
36
src/test_roms/nes.cfg
Normal file
36
src/test_roms/nes.cfg
Normal file
@@ -0,0 +1,36 @@
|
||||
# 32K iNES ROM with optional 8K CHR
|
||||
|
||||
MEMORY
|
||||
{
|
||||
ZP: start = $10, size = $E0; # leave $10 free at each end
|
||||
RAM: start = $200, size = $500;
|
||||
|
||||
HEADER: start = 0, size = $10, fill=yes;
|
||||
|
||||
ROM: start = $8000, size = $7E00, fill=yes, fillval=$FF;
|
||||
LOADER: start = $FE00, size = $100, fill=yes, fillval=$FF;
|
||||
FF00: start = $FF00, size = $F4, fill=yes, fillval=$FF;
|
||||
VECTORS:start = $FFF4, size = $C, fill=yes;
|
||||
|
||||
CHARS: start = 0, size = $3000, fillval=$FF;
|
||||
}
|
||||
|
||||
SEGMENTS
|
||||
{
|
||||
ZEROPAGE: load = ZP, type = zp;
|
||||
BSS: load = RAM, type = bss,align=$100;
|
||||
NVRAM: load = RAM, type = bss,define=yes, optional=yes;
|
||||
|
||||
HEADER: load = HEADER, type = ro;
|
||||
CODE: load = ROM, type = ro, align=$100;
|
||||
CODE2: load = ROM, type = ro, align=$100, optional=yes;
|
||||
RODATA: load = ROM, type = ro, align=$100;
|
||||
CHARS_PRG: load = ROM, type = ro, align=$200, optional=yes;
|
||||
CHARS_PRG_ASCII:load = ROM, type = ro, align=$200, optional=yes;
|
||||
LOADER: load = LOADER, type = ro, optional=yes;
|
||||
FF00: load = FF00, type = ro, align=$100, optional=yes;
|
||||
VECTORS: load = VECTORS, type = ro;
|
||||
|
||||
CHARS: load = CHARS, type = ro, align=$100, optional=yes;
|
||||
CHARS_ASCII:load = CHARS, type = ro, align=$200, optional=yes;
|
||||
}
|
||||
@@ -1,9 +1,9 @@
|
||||
; FINAL = ""
|
||||
|
||||
.inesprg 2 ; 2 banks
|
||||
.ineschr 1 ;
|
||||
.inesmap 0 ; mapper 0 = NROM
|
||||
.inesmir 0 ; background mirroring, horizontal
|
||||
; .inesprg 2 ; 2 banks
|
||||
; .ineschr 1 ;
|
||||
; .inesmap 0 ; mapper 0 = NROM
|
||||
; .inesmir 0 ; background mirroring, horizontal
|
||||
|
||||
.org $8000
|
||||
RESET:
|
||||
|
||||
Reference in New Issue
Block a user