diff --git a/snes/Makefile b/snes/Makefile index 253cac4..cbf90f5 100644 --- a/snes/Makefile +++ b/snes/Makefile @@ -1,4 +1,4 @@ -OBJS = header.ips reset.o65 main.o65 font.o65 palette.o65 data.o65 const.o65 logo.o65 logospr.o65 text.o65 dma.o65 menu.o65 pad.o65 time.o65 mainmenu.o65 sysinfo.o65 # gfx.o65 # vars.o65 +OBJS = header.ips reset.o65 main.o65 font.o65 palette.o65 data.o65 const.o65 logo.o65 logospr.o65 text.o65 dma.o65 menu.o65 pad.o65 time.o65 mainmenu.o65 sysinfo.o65 spc700.o65 spcplay.o65 # gfx.o65 # vars.o65 all: clean menu.bin map diff --git a/snes/const.a65 b/snes/const.a65 index d548063..8763c7b 100644 --- a/snes/const.a65 +++ b/snes/const.a65 @@ -168,4 +168,24 @@ text_mm_vmode_game .byt "Game video mode", 0 text_mm_sysinfo .byt "System Information", 0 text_statusbar_keys .byt "A:Select B:Back X:Menu", 0 text_last .byt "Run previous ROM: Press Start again to confirm", 0 +text_system .byt "PPU1 Rev.: x PPU2 Rev.: y CPU Rev.: z",0 + +text_spcplay .byt "SPC Music Player", 0 +spcplay_win_x .byt 15 +spcplay_win_y .byt 15 +spcplay_win_w .byt 33 +spcplay_win_h .byt 5 + +text_spcload .byt "Loading SPC data to SPC700...", 0 +text_spcstarta .byt "**** Now playing SPC tune ****", 0 +text_spcstartb .byt "Name: ",0 +text_spcstartc .byt "Song: ",0 +text_spcstartd .byt "Artist:",0 + +spcstart_win_x .byt 10 +spcstart_win_y .byt 13 +spcstart_win_w .byt 44 +spcstart_win_h .byt 9 + +text_spcid .byt "SNES-SPC700" diff --git a/snes/data.a65 b/snes/data.a65 index 9d73140..c98870e 100644 --- a/snes/data.a65 +++ b/snes/data.a65 @@ -183,5 +183,10 @@ direntry_xscroll .word 0 direntry_xscroll_wait .word 0 -infloop .byt 0,0 ; to be filled w/ 80 FE +infloop .byt 0,0 ; to be filled w/ 80 FE +;------------------------ +saved_sp + .word 0 +warm_signature + .word 0 wram_fadeloop .byt 0 diff --git a/snes/main.a65 b/snes/main.a65 index 2b0fa07..8981deb 100644 --- a/snes/main.a65 +++ b/snes/main.a65 @@ -11,6 +11,35 @@ GAME_MAIN: sta @AVR_PARAM+2 sep #$20 : .as stz $4200 ; inhibit VBlank NMI + + rep #$20 : .al + lda @warm_signature ; Was CMD_RESET issued before reset? + cmp #$fa50 ; If yes, then perform warm boot procedure + bne coldboot + lda #$0000 + sta @warm_signature + lda @saved_sp ; Restore previous stack pointer + tcs + sep #$20 : .as + + jsr killdma ; The following initialization processes must not touch memory + jsr waitblank ; structures used by the main menu ! + jsr snes_init + lda #$01 + sta $420d ; fast cpu + jsr setup_gfx + jsr colortest + jsr setup_hdma + jsr tests + + jmp @set_bank ; Set bios bank, just to be sure +set_bank: + plp ; Restore processor state + rts ; Jump to the routine which called the sub-routine issuing CMD_RESET + +coldboot: ; Regular, cold-start init + sep #$20 : .as + jsr killdma jsr waitblank jsr snes_init @@ -70,12 +99,15 @@ killdma: waitblank: + php + sep #$30 : .as : .xs - lda $4212 and #$80 bne - - lda $4212 and #$80 beq - + plp rts colortest: @@ -225,6 +257,7 @@ tests: sta $212e sta $212f stz $2121 + jsr waitblank lda #$0f sta $2100 ;screen on, full brightness lda #9 diff --git a/snes/memmap.i65 b/snes/memmap.i65 index baefb8c..aa79e30 100644 --- a/snes/memmap.i65 +++ b/snes/memmap.i65 @@ -6,6 +6,11 @@ /* These must be defined as constants, because they're used * in calculation that is sent to PPU as parameters */ +#define APUIO0 $2140 +#define APUIO1 $2141 +#define APUIO2 $2142 +#define APUIO3 $2143 + #define BG1_TILE_BASE $5800 #define BG2_TILE_BASE $5000 @@ -27,3 +32,9 @@ #define ROOT_DIR $C10000 #define CMD_SYSINFO $03 +#define CMD_LOADSPC $05 +#define CMD_RESET $06 + +#define SPC_DATA $DD0000 +#define SPC_HEADER $DE0000 +#define SPC_DSP_REGS $DE0100 diff --git a/snes/menu.a65 b/snes/menu.a65 index 2cd7cbb..d334976 100644 --- a/snes/menu.a65 +++ b/snes/menu.a65 @@ -272,6 +272,10 @@ dirent_is_file lda #$0000 bra dirent_type_cont + + cmp #$0003 ;SPC -> palette 2 + bne + + lda #$0002 + bra dirent_type_cont cmp #$0004 ;IPS -> palette 2 (green) bne + lda #$0002 @@ -502,6 +506,8 @@ select_item: lda [dirptr_addr], y cmp #$01 beq sel_is_file + cmp #$03 + beq sel_is_spc cmp #$04 beq sel_is_file cmp #$80 @@ -519,6 +525,9 @@ sel_is_parent sel_is_dir jsr select_dir bra select_item_cont +sel_is_spc + jsr select_spc + bra select_item_cont select_file: ; have avr load the rom @@ -635,6 +644,27 @@ select_parent: sta @menu_dirty rts +select_spc: + dey + rep #$20 : .al + lda [dirptr_addr], y + and #$00ff + sta @AVR_PARAM+2 + dey + dey + lda [dirptr_addr], y + sta @AVR_PARAM + sep #$20 : .as + lda #CMD_LOADSPC + sta @AVR_CMD +wait_spc: + lda @AVR_CMD + cmp #$00 + bne wait_spc + jsr spcplayer + jsr restore_screen + rts + menu_key_x: jsr mainmenu rts diff --git a/snes/spc700.a65 b/snes/spc700.a65 new file mode 100644 index 0000000..01f45d8 --- /dev/null +++ b/snes/spc700.a65 @@ -0,0 +1,61 @@ +; All SPC700 routines in SPC700 machine code +; SPC loader & transfer routines by Shay Green + +loader ; .org $0002 + .byt $F8,$21 ; mov x,@loader_data + .byt $BD ; mov sp,x + .byt $CD,$22 ; mov x,#@loader_data+1 + + ; Push PC and PSW from SPC header + .byt $BF ; mov a,(x)+ + .byt $2D ; push a + .byt $BF ; mov a,(x)+ + .byt $2D ; push a + .byt $BF ; mov a,(x)+ + .byt $2D ; push a + + ; Set FLG to $60 rather than value from SPC + .byt $E8,$60 ; mov a,#$60 + .byt $D4,$6C ; mov FLG+x,a + + ; Restore DSP registers + .byt $8D,$00 ; mov y,#0 + .byt $BF ; next: mov a,(x)+ + .byt $CB,$F2 ; mov $F2,y + .byt $C4,$F3 ; mov $F3,a + .byt $FC ; inc y + .byt $10,-8 ; bpl next + + .byt $8F,$6C,$F2 ; mov $F2,#FLG ; set for later + + ; Rerun loader + .byt $5F,$C0,$FF ; jmp $FFC0 + +;--------------------------------------- + +transfer ; .org $0002 + + .byt $CD,$FE ; mov x,#$FE ; transfer 254 pages + + ; Transfer four-byte chunks + .byt $8D,$3F ; page: mov y,#$3F + .byt $E4,$F4 ; quad: mov a,$F4 + .byt $D6,$00,$02 ; mov0: mov !$0200+y,a + .byt $E4,$F5 ; mov a,$F5 + .byt $D6,$40,$02 ; mov1: mov !$0240+y,a + .byt $E4,$F6 ; mov a,$F6 + .byt $D6,$80,$02 ; mov2: mov !$0280+y,a + .byt $E4,$F7 ; mov a,$F7 ; tell S-CPU we're ready for more + .byt $CB,$F7 ; mov $F7,Y + .byt $D6,$C0,$02 ; mov3: mov !$02C0+y,a + .byt $DC ; dec y + .byt $10,-25 ; bpl quad + ; Increment MSBs of addresses + .byt $AB,$0A ; inc mov0+2 + .byt $AB,$0F ; inc mov1+2 + .byt $AB,$14 ; inc mov2+2 + .byt $AB,$1B ; inc mov3+2 + .byt $1D ; dec x + .byt $D0,-38 ; bne page + ; Rerun loader + .byt $5F,$C0,$FF ; jmp $FFC0 diff --git a/snes/spcplay.a65 b/snes/spcplay.a65 new file mode 100644 index 0000000..4ad3c78 --- /dev/null +++ b/snes/spcplay.a65 @@ -0,0 +1,648 @@ +#include "memmap.i65" + +; SPC Player +; SPC700 transfer and IO routines by Shay Green + +spcplayer: + php + sep #$30 : .as : .xs + + ldx #$0a ; Check if SPC header is present +- + lda @SPC_HEADER,x + cmp @text_spcid,x + beq + + jmp spc_exit ++ + dey + bne - + + rep #$10 : .xl ; Now draw lots of stuff + + stz bar_wl + dec bar_wl + stz bar_xl + dec bar_xl + stz bar_yl + dec bar_yl + jsr backup_screen + + lda #^text_spcplay ; Loading window + sta window_tbank + ldx #!text_spcplay + stx window_taddr + lda @spcplay_win_x + sta window_x + lda @spcplay_win_y + sta window_y + lda @spcplay_win_w + sta window_w + lda @spcplay_win_h + sta window_h + jsr draw_window + + lda #^text_spcload ; Loading text + ldx #!text_spcload + sta print_bank + stx print_src + stz print_pal + lda #29 + sta print_count + lda #17 + sta print_y + lda #17 + sta print_x + jsr hiprint + + stz isr_done +- + lda isr_done ; Wait until text is being printed... + beq - + + jsr spc700_load ; Load SPC into SPC700 + + lda #^text_spcplay + sta window_tbank + ldx #!text_spcplay + stx window_taddr + lda @spcstart_win_x + sta window_x + lda @spcstart_win_y + sta window_y + lda @spcstart_win_w + sta window_w + lda @spcstart_win_h + sta window_h + jsr draw_window + + lda #^text_spcstarta + ldx #!text_spcstarta + sta print_bank + stx print_src + lda #$01 + sta print_pal + lda #30 + sta print_count + lda #15 + sta print_y + lda #17 + sta print_x + jsr hiprint + + lda #^text_spcstartb + ldx #!text_spcstartb + sta print_bank + stx print_src + lda #$01 + sta print_pal + lda #07 + sta print_count + lda #17 + sta print_y + lda #12 + sta print_x + jsr hiprint + + lda #$de + ldx #$004e + sta print_bank + stx print_src + stz print_pal + lda #32 + sta print_count + lda #17 + sta print_y + lda #20 + sta print_x + jsr hiprint + + lda #^text_spcstartc + ldx #!text_spcstartc + sta print_bank + stx print_src + lda #$01 + sta print_pal + lda #07 + sta print_count + lda #18 + sta print_y + lda #12 + sta print_x + jsr hiprint + + lda #$de + ldx #$002e + sta print_bank + stx print_src + stz print_pal + lda #32 + sta print_count + lda #18 + sta print_y + lda #20 + sta print_x + jsr hiprint + + lda #^text_spcstartd + ldx #!text_spcstartd + sta print_bank + stx print_src + lda #$01 + sta print_pal + lda #07 + sta print_count + lda #19 + sta print_y + lda #12 + sta print_x + jsr hiprint + + lda #$de + ldx #$00b1 + sta print_bank + stx print_src + stz print_pal + lda #32 + sta print_count + lda #19 + sta print_y + lda #20 + sta print_x + jsr hiprint + +spc_playloop: + lda isr_done ; SPC player loop + lsr + bcc spc_playloop + jsr printtime + stz isr_done + + jsr read_pad + lda #$80 + and pad1trig+1 + bne spc_key_b + bra spc_playloop + +spc_key_b: + rep #$20 : .al + tsc + sta saved_sp ; Save SP for later re-entry + lda #$fa50 ; Write reset signature + sta @warm_signature + sep #$20 : .as + + sei ; Blank screen & issue CMD_RESET command to Microcontroller... + stz $2100 ; ...this is required, because there is no other way to stop S-SMP & S-DSP + lda #CMD_RESET + sta @AVR_CMD +- + bra - ; At this point, the SNES waits for an external reset from the Microcontroller + +spc_exit: ; Return from player in case of wrong SPC file data + plp + rts + +;--------------------------------------- +spc700_load: + php + sep #$20 : .as + rep #$10 : .xl + + sei ; Disable NMI & IRQ + stz $4200 ; The SPC player code is really timing sensitive ;) + jsr upload_dsp_regs ; Upload S-DSP registers + jsr upload_high_ram ; Upload 63.5K of SPC700 ram + jsr upload_low_ram ; Upload rest of ram + jsr restore_final ; Restore SPC700 state & start execution + + lda #$81 ; VBlank NMI + Auto Joypad Read + sta $4200 ; enable V-BLANK NMI + cli + plp + rts +;--------------------------------------- +; Uploads DSP registers and some other setup code +upload_dsp_regs: + +; ---- Begin upload + + ldy #$0002 + jsr spc_begin_upload + +; ---- Upload loader + + ldx #$0000 +- + lda @loader,x + jsr spc_upload_byte + inx + cpy #31 ; size of loader + bne - + +; ---- Upload SP, PC & PSW + + lda @SPC_HEADER+43 + jsr spc_upload_byte + lda @SPC_HEADER+38 + jsr spc_upload_byte + lda @SPC_HEADER+37 + jsr spc_upload_byte + lda @SPC_HEADER+42 + jsr spc_upload_byte + +; ---- Upload DSP registers + + ldx #$0000 +- + lda @SPC_DSP_REGS,x + jsr spc_upload_byte + inx + cpx #128 + bne - + +; --- Upload fixed values for $F1-$F3 + + ldy #$00F1 + jsr spc_next_upload + + lda #$80 ; stop timers + jsr spc_upload_byte + lda #$6c ; get dspaddr set for later + jsr spc_upload_byte + lda #$60 + jsr spc_upload_byte + +; ---- Upload $f8-$1ff + + ldy #$00F8 + jsr spc_next_upload + + ldx #$00F8 +- + lda @SPC_DATA,x + jsr spc_upload_byte + inx + cpx #$200 + bne - + +; ---- Execute loader + + ldy #$0002 + jsr spc_execute + rts +;--------------------------------------- +upload_high_ram: + + ldy #$0002 + jsr spc_begin_upload + +; ---- Upload transfer routine + + ldx #$0000 +- + lda @transfer,x + jsr spc_upload_byte + inx + cpy #43 ; size of transfer routine + bne - + + ldx #$023f ; prepare transfer address + +; ---- Execute transfer routine + + ldy #$0002 + sty APUIO2 + stz APUIO1 + lda APUIO0 + inc + inc + sta APUIO0 +; Wait for acknowledgement +- + cmp APUIO0 + bne - + +; ---- Burst transfer of 63.5K using custom routine + +outer_transfer_loop: + ldy #$003f ; 3 +inner_transfer_loop: + lda @SPC_DATA,x ; 5 | + sta APUIO0 ; 4 | + lda @SPC_DATA+$40,x ; 5 | + sta APUIO1 ; 4 | + lda @SPC_DATA+$80,x ; 5 | + sta APUIO2 ; 4 | + lda @SPC_DATA+$C0,x ; 5 | + sta APUIO3 ; 4 | + tya ; 2 >> 38 cycles +- + cmp APUIO3 ; 4 | + bne - ; 3 | + dex ; 2 | + dey ; 2 | + bpl inner_transfer_loop ; 3 >> 14 cycles + + rep #$21 : .al ; 3 | + txa ; 2 | + adc #$140 ; 3 | + tax ; 2 | + sep #$20 : .as ; 3 | + cpx #$003f ; 3 | + bne outer_transfer_loop ; 3 >> 19 cycles + + rts + +;--------------------------------------- +upload_low_ram: + +; ---- Upload $0002-$00EF + + ldy #$0002 + jsr spc_begin_upload + + ldx #$0002 +- + lda @SPC_DATA,x + jsr spc_upload_byte + inx + cpx #$00F0 + bne - + rts +;--------------------------------------- +; Executes final restoration code +restore_final: + jsr start_exec_io + stz $420d ; SPC700 I/O code requires SLOW timing + +; ---- Restore first two bytes of RAM + + lda @SPC_DATA + xba + lda #$e8 ; MOV A,#@SPC_DATA + tax + jsr exec_instr + ldx #$00C4 ; MOV $00,A + jsr exec_instr + + lda @SPC_DATA+1 + xba + lda #$e8 ; MOV A,#@SPC_DATA+1 + tax + jsr exec_instr + ldx #$01C4 ; MOV $01,A + jsr exec_instr + +; ---- Restore SP + + lda @SPC_HEADER+43 + sec + sbc #3 + xba + lda #$cd ; MOV X,#@SPC_HEADER+43 + tax + jsr exec_instr + ldx #$bd ; MOV SP,X + jsr exec_instr + +; ---- Restore X + + lda @SPC_HEADER+40 + xba + lda #$cd ; MOV X,#@SPC_HEADER+40 + tax + jsr exec_instr + +; ---- Restore Y + + lda @SPC_HEADER+41 + xba + lda #$8d ; MOV Y,#@SPC_HEADER+41 + tax + jsr exec_instr + +; ---- Restore DSP FLG register + + lda @SPC_DSP_REGS+$6c + xba + lda #$e8 ; MOV A,#@SPC_DSP_REGS+$6c + tax + jsr exec_instr + ldx #$f3C4 ; MOV $f3,A -> $f2 has been set-up before by SPC700 loader + jsr exec_instr + +; ---- Restore DSP KON register + + lda #$4C + xba + lda #$e8 ; MOV A,#$4c + tax + jsr exec_instr + ldx #$f2C4 ; MOV $f2,A + jsr exec_instr + lda @SPC_DSP_REGS+$4C + xba + lda #$e8 ; MOV A,#@SPC_DSP_REGS+$4c + tax + jsr exec_instr + ldx #$f3C4 ; MOV $f3,A + jsr exec_instr + +; ---- Restore DSP register address + + lda @SPC_DATA+$F2 + xba + lda #$e8 ; MOV A,#@SPC_DATA+$F2 + tax + jsr exec_instr + ldx #$f2C4 ; MOV dest,A + jsr exec_instr + +; ---- Restore CONTROL register + + lda @SPC_DATA+$F1 + and #$CF ; don't clear input ports + xba + lda #$e8 ; MOV A,#@SPC_DATA+$F1 + tax + jsr exec_instr + ldx #$f1C4 ; MOV $F1,A + jsr exec_instr + +;---- Restore A + + lda @SPC_HEADER+39 + xba + lda #$e8 ; MOV A,#@SPC_HEADER+39 + tax + jsr exec_instr + +;---- Restore PSW and PC + + ldx #$7F00 ; NOP; RTI + stx APUIO0 + lda #$FC ; Patch loop to execute instruction just written + sta APUIO3 + +;---- restore IO ports $f4 - $f7 + + rep #$20 : .al + lda @SPC_DATA+$F4 + tax + lda @SPC_DATA+$F6 + sta APUIO2 + stx APUIO0 ; last to avoid overwriting RETI before run + sep #$20 : .as + + lda #$01 + sta $420d ; restore FAST CPU operation + rts +;--------------------------------------- +spc_begin_upload: + + sty APUIO2 ; Set address + + ldy #$BBAA ; Wait for SPC +- + cpy APUIO0 + bne - + + lda #$CC ; Send acknowledgement + sta APUIO1 + sta APUIO0 + +- ; Wait for acknowledgement + cmp APUIO0 + bne - + + ldy #0 ; Initialize index + rts +;--------------------------------------- +spc_upload_byte: + sta APUIO1 + + tya ; Signal it's ready + sta APUIO0 +- ; Wait for acknowledgement + cmp APUIO0 + bne - + + iny + + rts +;--------------------------------------- +spc_next_upload: + sty APUIO2 + +; Send command +; Special case operation has been fully tested. + lda APUIO0 + inc + inc + bne + + inc ++ + sta APUIO1 + sta APUIO0 + +; Wait for acknowledgement +- + cmp APUIO0 + bne - + + ldy #0 + rts +;--------------------------------------- +spc_execute: + sty APUIO2 + + stz APUIO1 + + lda APUIO0 + inc + inc + sta APUIO0 + +; Wait for acknowledgement +- + cmp APUIO0 + bne - + + rts +;--------------------------------------- +start_exec_io: +; Set execution address + ldx #$00F5 + stx APUIO2 + + stz APUIO1 ; NOP + ldx #$FE2F ; BRA *-2 + +; Signal to SPC that we're ready + lda APUIO0 + inc + inc + sta APUIO0 + +; Wait for acknowledgement +- + cmp APUIO0 + bne - + +; Quickly write branch + stx APUIO2 + + rts +;--------------------------------------- +exec_instr: +; Replace instruction + stx APUIO0 + lda #$FC + sta APUIO3 ; 30 + + ; SPC BRA loop takes 4 cycles, so it reads + ; the branch offset every 4 SPC cycles (84 master). + ; We must handle the case where it read just before + ; the write above, and when it reads just after it. + ; If it reads just after, we have at least 7 SPC + ; cycles (147 master) to change restore the branch + ; offset. + + ; 48 minimum, 90 maximum + ora #0 + ora #0 + ora #0 + nop + nop + nop + + ; 66 delay, about the middle of the above limits + phd ;4 + pld ;5 + + ; Give plenty of extra time if single execution + ; isn't needed, as this avoids such tight timing + ; requirements. + +; phd ;4 +; pld ;5 +; phd ;4 +; pld ;5 + + ; Patch loop to skip first two bytes + lda #$FE ; 16 + sta APUIO3 ; 30 + + ; 38 minimum (assuming 66 delay above) + phd ; 4 + pld ; 5 + + ; Give plenty of extra time if single execution + ; isn't needed, as this avoids such tight timing + ; requirements. + + phd + pld + phd + pld + rts