www.pudn.com > sn068s.zip > SPC700.ASM


; SNEeSe SPC700 CPU emulation core 
; Originally written by Lee Hammerton in AT&T assembly 
; Maintained/rewritten/ported to NASM by Charles Bilyue' 
; 
; Compile under NASM 
; This code assumes preservation of ebx, ebp, esi, edi in C/C++ calls 
 
;%define TRACKERS 1048576 
;%define WATCH_SPC_BREAKS 
%define LOG_SOUND_DSP_READ 
%define LOG_SOUND_DSP_WRITE 
;%define TRAP_INVALID_READ 
;%define TRAP_INVALID_WRITE 
 
extern _SPC_CPU_cycle_multiplicand,_SPC_CPU_cycle_divisor 
extern SPC_CPU_cycles_mul,SPC_CPU_cycles 
extern _sound_cycle_latch 
extern _SPC_DSP 
extern _SPC_DSP_ADDR,_SPC_DSP_DATA 
extern _SPC_READ_DSP,_SPC_WRITE_DSP 
extern _Update_SPC_Timer_0,_Update_SPC_Timer_1,_Update_SPC_Timer_2 
extern _Wrap_SPC_Cyclecounter 
extern _Map_Byte,_Map_Address 
 
extern _SNES_Cycles,_EventTrip,SPC_last_cycles,In_CPU 
extern _DisplaySPC 
 
; This file contains: 
;  CPU core info 
;  Reset 
;  Execution Loop 
;  Invalid Opcode Handler 
;  Flag format conversion tables 
;  Variable definitions (registers, interrupt vectors, etc.) 
;  CPU opcode emulation handlers 
;  CPU opcode handler table 
;  CPU opcode timing table 
; 
; CPU core info: 
;  Nearly all general registers are now used in SPC700 emulation: 
;   EAX,EBX are used by the memory mapper; 
;   EDX is used as CPU work register; 
;   EBP is used to hold cycle counter; 
;   ESI is used by the opcode fetcher; 
;   EDI is used as memory mapper work register. 
; 
;    A register              - _A 
;    Y register              - _Y 
;    YA register pair        - _YA 
;    X register              - _X 
;    Stack pointer           - _SP 
;    Program Counter         - _PC 
;    Processor status word   - _PSW 
;       True x86 layout = |V|-|-|-|S|Z|-|A|-|-|-|C| 
;    True SPC700 layout =         |N|V|P|B|H|I|Z|C| 
;                   Using         |N|Z|P|H|B|I|V|C| 
; 
; SPC timers 
;  SPC700 timing is not directly related to 65c816 timing, but for 
;   simplicity in emulation we act as if it is. SPC gets 11264 cycles 
;   for every 118125 (21.47727..MHz) 65c816 cycles. Since the timers 
;   run at ~8KHz and ~64KHz and the main chip runs at 1.024Mhz, the 
;   timers are clocked as follows: 
;    2.048MHz / 8KHz  = 256 cycles    (Timers 0 and 1) 
;    2.048MHz / 64KHz = 32  cycles    (Timer 2) 
; 
; 
 
%define SNEeSe_SPC700_asm 
 
%include "misc.ni" 
%include "SPC.ni" 
%include "PPU.ni" 
 
section .text 
EXPORT_C SPC_text_start 
section .data 
EXPORT_C SPC_data_start 
section .bss 
EXPORT_C SPC_bss_start 
 
%define _SPC_CTRL 0xF1 
%define _SPC_DSP_ADDR 0xF2 
 
; These are the bits for flag set/clr operations 
SPC_FLAG_C equ 1    ; Carry 
SPC_FLAG_V equ 2    ; Overflow 
SPC_FLAG_I equ 4    ; Interrupt Disable 
SPC_FLAG_B equ 8    ; Break 
SPC_FLAG_H equ 0x10 ; Half-carry 
SPC_FLAG_P equ 0x20 ; Page (direct page) 
SPC_FLAG_Z equ 0x40 ; Zero result 
SPC_FLAG_N equ 0x80 ; Negative result 
 
SPC_FLAG_NZ equ (SPC_FLAG_N | SPC_FLAG_Z) 
SPC_FLAG_NZC equ (SPC_FLAG_NZ | SPC_FLAG_C) 
SPC_FLAG_NHZC equ (SPC_FLAG_NZC | SPC_FLAG_H) 
 
REAL_SPC_FLAG_C equ 1       ; Carry 
REAL_SPC_FLAG_Z equ 2       ; Zero result 
REAL_SPC_FLAG_I equ 4       ; Interrupt Disable 
REAL_SPC_FLAG_H equ 8       ; Half-carry 
REAL_SPC_FLAG_B equ 0x10    ; Break 
REAL_SPC_FLAG_P equ 0x20    ; Page (direct page) 
REAL_SPC_FLAG_V equ 0x40    ; Overflow 
REAL_SPC_FLAG_N equ 0x80    ; Negative result 
 
%define _PSW __SPC_PSW 
%define _YA __SPC_YA 
%define _A  __SPC_A 
%define _Y  __SPC_Y 
%define _X  __SPC_X 
%define _SP __SPC_SP 
%define _PC __SPC_PC 
 
%define R_Base       R_SPC700_Base 
%define R_Cycles     R_SPC700_Cycles 
%define R_NativePC   R_SPC700_NativePC 
 
 
%define B_SPC_Code_Base     [R_Base-SPC_Register_Base+SPC_Code_Base] 
%define B_PC                [R_Base-SPC_Register_Base+_PC] 
%define B_YA                [R_Base-SPC_Register_Base+_YA] 
%define B_A                 [R_Base-SPC_Register_Base+_A] 
%define B_Y                 [R_Base-SPC_Register_Base+_Y] 
%define B_SPC_PAGE          [R_Base-SPC_Register_Base+SPC_PAGE] 
%define B_SPC_PAGE_H        byte [R_Base-SPC_Register_Base+SPC_PAGE_H] 
%define B_SP                [R_Base-SPC_Register_Base+_SP] 
%define B_SPC_Cycles        [R_Base-SPC_Register_Base+_SPC_Cycles] 
%define B_PSW               [R_Base-SPC_Register_Base+_PSW] 
%define B_X                 [R_Base-SPC_Register_Base+_X] 
 
%define B_N_flag            [R_Base-SPC_Register_Base+_N_flag] 
%define B_V_flag            [R_Base-SPC_Register_Base+_V_flag] 
%define B_P_flag            [R_Base-SPC_Register_Base+_P_flag] 
%define B_H_flag            [R_Base-SPC_Register_Base+_H_flag] 
%define B_Z_flag            [R_Base-SPC_Register_Base+_Z_flag] 
%define B_I_flag            [R_Base-SPC_Register_Base+_I_flag] 
%define B_B_flag            [R_Base-SPC_Register_Base+_B_flag] 
%define B_C_flag            [R_Base-SPC_Register_Base+_C_flag] 
 
%define B_SPC_PORT0R        [R_Base-SPC_Register_Base+_SPC_PORT0R] 
%define B_SPC_PORT1R        [R_Base-SPC_Register_Base+_SPC_PORT1R] 
%define B_SPC_PORT2R        [R_Base-SPC_Register_Base+_SPC_PORT2R] 
%define B_SPC_PORT3R        [R_Base-SPC_Register_Base+_SPC_PORT3R] 
%define B_SPC_PORT0W        [R_Base-SPC_Register_Base+_SPC_PORT0W] 
%define B_SPC_PORT1W        [R_Base-SPC_Register_Base+_SPC_PORT1W] 
%define B_SPC_PORT2W        [R_Base-SPC_Register_Base+_SPC_PORT2W] 
%define B_SPC_PORT3W        [R_Base-SPC_Register_Base+_SPC_PORT3W] 
%ifdef DEBUG 
%define B_SPC_TEMP_ADD      [R_Base-SPC_Register_Base+SPC_TEMP_ADD] 
%endif 
 
; Load cycle counter to register R_Cycles 
%macro LOAD_CYCLES 0 
 mov edi,[_SPC_Cycles] 
 mov dword R_Cycles,[_TotalCycles] 
 sub dword R_Cycles,edi 
%endmacro 
 
; Get cycle counter to register argument 
%macro GET_CYCLES 1 
 mov dword %1,[_SPC_Cycles] 
 add dword %1,R_Cycles 
%endmacro 
 
; Save register R_Cycles to cycle counter 
%macro SAVE_CYCLES 0 
 GET_CYCLES edi 
 mov [_TotalCycles],edi 
%endmacro 
 
; Load base pointer to CPU register set 
%macro LOAD_BASE 0 
 mov dword R_Base,SPC_Register_Base 
%endmacro 
 
; Load register R_NativePC with pointer to code at PC 
%macro LOAD_PC 0 
 mov dword R_NativePC,[SPC_Code_Base] 
 add dword R_NativePC,[_PC] 
%endmacro 
 
; Get PC from register R_NativePC 
;%1 = with 
%macro GET_PC 1 
 mov %1,R_NativePC 
 sub dword %1,[SPC_Code_Base] 
%endmacro 
 
; Save PC from register R_NativePC 
;%1 = with 
%macro SAVE_PC 1 
 GET_PC %1 
 mov dword [_PC],%1 
%endmacro 
 
; Set up the flags from PC flag format to SPC flag format 
; Corrupts arg 2, returns value in arg 3 (default to cl, al) 
;%1 = break flag, %2 = scratchpad, %3 = output 
%macro SETUPFLAGS_SPC 0-3 1,cl,al 
;%macro Flags_Native_to_SPC 0-3 1,cl,al 
 mov byte %2,B_N_flag 
 add byte %2,%2 
 adc byte %3,%3 
 
 mov byte %2,B_V_flag 
 add byte %2,-1 
 adc byte %3,%3 
 
 mov byte %2,B_P_flag 
 add byte %2,-1 
 adc byte %3,%3 
 
 mov byte %2,B_H_flag 
 add byte %3,%3 
%if %1 != 0 
 inc byte %3 
%endif 
 shl byte %2,4 
 adc byte %3,%3 
 
 mov byte %2,B_I_flag 
 add byte %2,-1 
 adc byte %3,%3 
 
 mov byte %2,B_Z_flag 
 cmp byte %2,1 
 adc byte %3,%3 
 
 mov byte %2,B_C_flag 
 add byte %2,-1 
 adc byte %3,%3 
%endmacro 
 
; Restore the flags from SPC flag format to PC flag format 
; Corrupts arg 2, returns value in arg 3 (default to cl, al) 
;%1 = break flag, %2 = scratchpad, %3 = input 
%macro RESTOREFLAGS_SPC 0-3 1,cl,al 
;%macro Flags_SPC_to_Native 0-3 1,cl,al 
 add byte %3,%3 ;start first (negative) 
 sbb byte %2,%2 
 add byte %3,%3 ;start next (overflow) 
 mov byte B_N_flag,%2 
 
 sbb byte %2,%2 
 add byte %3,%3 ;start next (direct page) 
 mov byte B_V_flag,%2 
 
 mov byte %2,0 
 adc byte %2,%2 
 add byte %3,%3 ;start next (break flag, ignore) 
 mov byte B_P_flag,%2 
 add byte %3,%3 ;start next (half-carry) 
 mov byte B_SPC_PAGE_H,%2 
 
 sbb byte %2,%2 
 mov byte B_B_flag,%1 
 
;and byte %2,0x10 
 add byte %3,%3 ;start next (interrupt enable) 
 mov byte B_H_flag,%2 
 
 sbb byte %2,%2 
 add byte %3,%3 ;start next (zero) 
 mov byte B_I_flag,%2 
 
 sbb byte %2,%2 
 xor byte %2,0xFF 
 add byte %3,%3 ;start next (carry) 
 mov byte B_Z_flag,%2 
 
 sbb byte %2,%2 
 mov byte B_C_flag,%2 
%endmacro 
 
 
; SPC MEMORY MAPPER IS PLACED HERE (ITS SIMPLER THAN THE CPU ONE!) 
 
; bx - contains the actual address, al is where the info should be stored, edi is free 
; NB bx is not corrupted! edi is corrupted! 
; NB eax is not corrupted barring returnvalue in al... e.g. ah should not be used etc! 
 
section .text 
%macro OPCODE_EPILOG 0 
%if 0 
 test R_Cycles,R_Cycles 
 jle near SPC_START_NEXT 
 jmp near SPC_OUT 
%else 
 jmp near SPC_RETURN 
%endif 
%endmacro 
 
ALIGNC 
SPC_READ_MAPPER: 
;and ebx,0xFFFF 
 test bh,bh 
 jz SPC_READ_ZERO_PAGE 
 
 cmp ebx,0xFFC0 
 jae SPC_READ_RAM_ROM 
SPC_READ_RAM: 
 mov al,[_SPCRAM+ebx] 
 ret 
 
ALIGNC 
SPC_READ_RAM_ROM: 
 mov al,[_SPC_ROM_CODE - 0xFFC0 + ebx] 
SPC_FFC0_Address equ $-4 
 ret 
 
ALIGNC 
SPC_READ_ZERO_PAGE: 
 cmp bl,0xF0 
 jb SPC_READ_RAM 
 
SPC_READ_FUNC: 
%ifdef LOG_SOUND_DSP_READ 
 SAVE_PC edi 
%endif 
 SAVE_CYCLES 
 jmp dword [Read_Func_Map - 0xF0 * 4 + ebx * 4] 
 
ALIGNC 
SPC_READ_INVALID: 
 mov al,0xFF    ; v0.15 
%ifdef TRAP_INVALID_READ 
%ifdef DEBUG 
extern _InvalidSPCHWRead 
;and ebx,0xFFFF 
 mov [_Map_Address],ebx ; Set up Map Address so message works! 
 mov [_Map_Byte],al     ; Set up Map Byte so message works 
 
 pusha 
 call _InvalidSPCHWRead ; Display read from invalid HW warning 
 popa 
%endif 
%endif 
 ret 
 
;   -------- 
 
SPC_WRITE_MAPPER: 
;and ebx,0xFFFF 
 test bh,bh 
 jz SPC_WRITE_ZERO_PAGE 
 
SPC_WRITE_RAM: 
 mov [_SPCRAM+ebx],al 
 ret 
 
ALIGNC 
SPC_WRITE_ZERO_PAGE: 
 cmp bl,0xF0 
 jb SPC_WRITE_RAM 
 
SPC_WRITE_FUNC: 
%ifdef LOG_SOUND_DSP_WRITE 
 SAVE_PC edi 
%endif 
 SAVE_CYCLES 
 jmp dword [Write_Func_Map - 0xF0 * 4 + ebx * 4] 
 
SPC_WRITE_INVALID: 
%ifdef TRAP_INVALID_WRITE 
%ifdef DEBUG 
extern _InvalidSPCHWWrite 
;and ebx,0xFFFF 
 mov [_Map_Address],ebx     ; Set up Map Address so message works! 
 mov [_Map_Byte],al         ; Set up Map Byte so message works 
 
 pusha 
 call _InvalidSPCHWWrite    ; Display write to invalid HW warning 
 popa 
%endif 
%endif 
 ret 
 
; GET_BYTE & GET_WORD now assume ebx contains the read address and  
; eax the place to store value also, corrupts edi 
 
%macro GET_BYTE_SPC 0 
;call SPC_READ_MAPPER 
 cmp ebx,0xFFC0 
 jnb %%read_mapper 
 
 test bh,bh 
 jnz %%read_direct 
 
 cmp bl,0xF0 
 jb %%read_direct 
 call SPC_READ_FUNC 
 jmp short %%done 
%%read_mapper: 
 call SPC_READ_RAM_ROM 
 jmp short %%done 
%%read_direct: 
 mov al,[_SPCRAM+ebx] 
%%done: 
%endmacro 
 
%macro GET_WORD_SPC 0 
 cmp ebx,0xFFC0-1 
 jnb %%read_mapper 
 
 test bh,bh 
 jnz %%read_direct 
 
 cmp bl,0xF0-1 
 jb %%read_direct 
 
 cmp bl,0xFF 
 je %%read_mapper 
 
 call SPC_READ_FUNC 
 mov ah,al 
 inc ebx 
 call SPC_READ_FUNC 
 ror ax,8 
 jmp short %%done 
%%read_mapper: 
 call SPC_GET_WORD 
 jmp short %%done 
%%read_direct: 
 mov ax,[_SPCRAM+ebx] 
 inc ebx 
%%done: 
%endmacro 
 
; SET_BYTE & SET_WORD now assume ebx contains the write address and  
; eax the value to write, corrupts edi 
 
%macro SET_BYTE_SPC 0 
;call SPC_WRITE_MAPPER 
 test bh,bh 
 jnz %%write_direct 
 cmp bl,0xF0 
 jb %%write_direct 
 call SPC_WRITE_FUNC 
 jmp short %%done 
%%write_direct: 
 mov [_SPCRAM+ebx],al 
%%done: 
%endmacro 
 
%macro  SET_WORD_SPC 0 
 SET_BYTE_SPC 
 mov al,ah 
 inc bx 
 SET_BYTE_SPC 
%endmacro 
 
; Push / Pop Macros assume eax contains value - corrupt ebx,edi 
%macro PUSH_B 0         ; Push Byte (SP--) 
 mov ebx,B_SP 
 mov [_SPCRAM+ebx],al   ; Store data on stack 
 dec ebx 
 mov B_SP,bl            ; Decrement S (Byte) 
%endmacro 
 
%macro POP_B 0          ; Pop Byte (++SP) 
 mov ebx,B_SP 
 inc bl 
 mov B_SP,bl 
 mov al,[_SPCRAM+ebx]   ; Fetch data from stack 
%endmacro 
 
%macro PUSH_W 0         ; Push Word (SP--) 
 mov ebx,B_SP 
 mov [_SPCRAM+ebx],ah   ; Store data on stack 
 mov [_SPCRAM+ebx-1],al ; Store data on stack 
 sub bl,2 
 mov B_SP,bl            ; Postdecrement SP 
%endmacro 
 
%macro POP_W 0          ; Pop Word (++SP) 
 mov ebx,B_SP 
 add bl,2               ; Preincrement SP 
 mov B_SP,bl 
 mov ah,[_SPCRAM+ebx]   ; Fetch data from stack 
 mov al,[_SPCRAM+ebx-1] ; Fetch data from stack 
%endmacro 
 
; --- Ease up on the finger cramps ;-) 
 
;%1 = flag 
%macro SET_FLAG_SPC 1 
%if %1 & SPC_FLAG_N 
 mov byte [_N_flag],0x80 
%endif 
%if %1 & SPC_FLAG_V 
 mov byte [_V_flag],1 
%endif 
%if %1 & SPC_FLAG_Z 
 mov byte [_Z_flag],0 
%endif 
%if %1 & SPC_FLAG_C 
 mov byte [_C_flag],1 
%endif 
%if %1 & SPC_FLAG_P 
 mov byte [_P_flag],1 
%endif 
%if %1 & SPC_FLAG_I 
 mov byte [_I_flag],1 
%endif 
%if %1 &~ (SPC_FLAG_N | SPC_FLAG_V | SPC_FLAG_Z | SPC_FLAG_C | SPC_FLAG_P | SPC_FLAG_I) 
%error Unhandled flag in SET_FLAG_SPC 
%endif 
%endmacro 
 
;%1 = flag 
%macro CLR_FLAG_SPC 1 
%if %1 == SPC_FLAG_H 
 mov byte [_H_flag],0 
%endif 
%if %1 == SPC_FLAG_V 
 mov byte [_V_flag],0 
%endif 
%if %1 == SPC_FLAG_Z 
 mov byte [_Z_flag],1 
%endif 
%if %1 == SPC_FLAG_C 
 mov byte [_C_flag],0 
%endif 
%if %1 == SPC_FLAG_P 
 mov byte [_P_flag],0 
%endif 
%if %1 == SPC_FLAG_I 
 mov byte [_I_flag],0 
%endif 
%if %1 &~ (SPC_FLAG_H | SPC_FLAG_V | SPC_FLAG_Z | SPC_FLAG_C | SPC_FLAG_P | SPC_FLAG_I) 
%error Unhandled flag in CLR_FLAG_SPC 
%endif 
%endmacro 
 
;%1 = flag 
%macro CPL_FLAG_SPC 1 
%if %1 == SPC_FLAG_C 
 push eax 
 mov al,[_C_flag] 
 test al,al 
 setz [_C_flag] 
 pop eax 
%endif 
%endmacro 
 
;%1 = flag, %2 = wheretogo 
%macro JUMP_FLAG_SPC 2 
%if %1 == SPC_FLAG_N 
 mov ch,B_N_flag 
 test ch,ch 
 js %2 
%elif %1 == SPC_FLAG_Z 
 mov ch,B_Z_flag 
 test ch,ch 
 jz %2 
%elif %1 == SPC_FLAG_V 
 mov ch,B_V_flag 
 test ch,ch 
 jnz %2 
%elif %1 == SPC_FLAG_C 
 mov ch,B_C_flag 
 test ch,ch 
 jnz %2 
%else 
%error Unhandled flag in JUMP_FLAG_SPC 
%endif 
%endmacro 
 
;%1 = flag, %2 = wheretogo 
%macro JUMP_NOT_FLAG_SPC 2 
%if %1 == SPC_FLAG_N 
 mov ch,B_N_flag 
 test ch,ch 
 jns %2 
%elif %1 == SPC_FLAG_Z 
 mov ch,B_Z_flag 
 test ch,ch 
 jnz %2 
%elif %1 == SPC_FLAG_V 
 mov ch,B_V_flag 
 test ch,ch 
 jz %2 
%elif %1 == SPC_FLAG_C 
 mov ch,B_C_flag 
 test ch,ch 
 jz %2 
%else 
%error Unhandled flag in JUMP_NOT_FLAG_SPC 
%endif 
%endmacro 
 
%macro STORE_FLAGS_P 1 
 mov byte B_P_flag,%1 
%endmacro 
 
%macro STORE_FLAGS_V 1 
 mov byte B_V_flag,%1 
%endmacro 
 
%macro STORE_FLAGS_H 1 
 mov byte B_H_flag,%1 
%endmacro 
 
%macro STORE_FLAGS_N 1 
 mov byte B_N_flag,%1 
%endmacro 
 
%macro STORE_FLAGS_Z 1 
 mov byte B_Z_flag,%1 
%endmacro 
 
%macro STORE_FLAGS_I 1 
 mov byte B_I_flag,%1 
%endmacro 
 
%macro STORE_FLAGS_C 1 
 mov byte B_C_flag,%1 
%endmacro 
 
%macro STORE_FLAGS_NZ 1 
 STORE_FLAGS_N %1 
 STORE_FLAGS_Z %1 
%endmacro 
 
%macro STORE_FLAGS_NZC 2 
 STORE_FLAGS_N %1 
 STORE_FLAGS_Z %1 
 STORE_FLAGS_C %2 
%endmacro 
 
section .data 
ALIGND 
EXPORT SPCOpTable 
dd _SPC_NOP          ,_SPC_TCALL_0       ,_SPC_SET1          ,_SPC_BBS                ; 00 
dd _SPC_OR_A_dp      ,_SPC_OR_A_abs      ,_SPC_OR_A_OXO      ,_SPC_OR_A_OOdp_XOO 
dd _SPC_OR_A_IM      ,_SPC_OR_dp_dp      ,_SPC_OR1           ,_SPC_ASL_dp 
dd _SPC_ASL_abs      ,_SPC_PUSH_PSW      ,_SPC_TSET1         ,_SPC_INVALID 
dd _SPC_BPL          ,_SPC_TCALL_1       ,_SPC_CLR1          ,_SPC_BBC                ; 10 
dd _SPC_OR_A_Odp_XO  ,_SPC_OR_A_Oabs_XO  ,_SPC_OR_A_Oabs_YO  ,_SPC_OR_A_OOdpO_YO 
dd _SPC_OR_dp_IM     ,_SPC_OR_OXO_OYO    ,_SPC_DECW_dp       ,_SPC_ASL_Odp_XO 
dd _SPC_ASL_A        ,_SPC_DEC_X         ,_SPC_CMP_X_abs     ,_SPC_JMP_Oabs_XO 
dd _SPC_CLRP         ,_SPC_TCALL_2       ,_SPC_SET1          ,_SPC_BBS                ; 20 
dd _SPC_AND_A_dp     ,_SPC_AND_A_abs     ,_SPC_AND_A_OXO     ,_SPC_AND_A_OOdp_XOO 
dd _SPC_AND_A_IM     ,_SPC_AND_dp_dp     ,_SPC_OR1C          ,_SPC_ROL_dp 
dd _SPC_ROL_abs      ,_SPC_PUSH_A        ,_SPC_CBNE_dp       ,_SPC_BRA 
dd _SPC_BMI          ,_SPC_TCALL_3       ,_SPC_CLR1          ,_SPC_BBC                ; 30 
dd _SPC_AND_A_Odp_XO ,_SPC_AND_A_Oabs_XO ,_SPC_AND_A_Oabs_YO ,_SPC_AND_A_OOdpO_YO 
dd _SPC_AND_dp_IM    ,_SPC_AND_OXO_OYO   ,_SPC_INCW_dp       ,_SPC_ROL_Odp_XO 
dd _SPC_ROL_A        ,_SPC_INC_X         ,_SPC_CMP_X_dp      ,_SPC_CALL 
dd _SPC_SETP         ,_SPC_TCALL_4       ,_SPC_SET1          ,_SPC_BBS                ; 40 
dd _SPC_EOR_A_dp     ,_SPC_EOR_A_abs     ,_SPC_EOR_A_OXO     ,_SPC_EOR_A_OOdp_XOO 
dd _SPC_EOR_A_IM     ,_SPC_EOR_dp_dp     ,_SPC_AND1          ,_SPC_LSR_dp 
dd _SPC_LSR_abs      ,_SPC_PUSH_X        ,_SPC_TCLR1         ,_SPC_PCALL 
dd _SPC_BVC          ,_SPC_TCALL_5       ,_SPC_CLR1          ,_SPC_BBC                ; 50 
dd _SPC_EOR_A_Odp_XO ,_SPC_EOR_A_Oabs_XO ,_SPC_EOR_A_Oabs_YO ,_SPC_EOR_A_OOdpO_YO 
dd _SPC_EOR_dp_IM    ,_SPC_EOR_OXO_OYO   ,_SPC_CMPW_YA_dp    ,_SPC_LSR_Odp_XO 
dd _SPC_LSR_A        ,_SPC_MOV_X__A      ,_SPC_CMP_Y_abs     ,_SPC_JMP_abs 
dd _SPC_CLRC         ,_SPC_TCALL_6       ,_SPC_SET1          ,_SPC_BBS                ; 60 
dd _SPC_CMP_A_dp     ,_SPC_CMP_A_abs     ,_SPC_CMP_A_OXO     ,_SPC_CMP_A_OOdp_XOO 
dd _SPC_CMP_A_IM     ,_SPC_CMP_dp_dp     ,_SPC_AND1C         ,_SPC_ROR_dp 
dd _SPC_ROR_abs      ,_SPC_PUSH_Y        ,_SPC_DBNZ_dp       ,_SPC_RET 
dd _SPC_BVS          ,_SPC_TCALL_7       ,_SPC_CLR1          ,_SPC_BBC                ; 70 
dd _SPC_CMP_A_Odp_XO ,_SPC_CMP_A_Oabs_XO ,_SPC_CMP_A_Oabs_YO ,_SPC_CMP_A_OOdpO_YO 
dd _SPC_CMP_dp_IM    ,_SPC_CMP_OXO_OYO   ,_SPC_ADDW_YA_dp    ,_SPC_ROR_Odp_XO 
dd _SPC_ROR_A        ,_SPC_MOV_A__X      ,_SPC_CMP_Y_dp      ,_SPC_INVALID 
dd _SPC_SETC         ,_SPC_TCALL_8       ,_SPC_SET1          ,_SPC_BBS                ; 80 
dd _SPC_ADC_A_dp     ,_SPC_ADC_A_abs     ,_SPC_ADC_A_OXO     ,_SPC_ADC_A_OOdp_XOO 
dd _SPC_ADC_A_IM     ,_SPC_ADC_dp_dp     ,_SPC_EOR1          ,_SPC_DEC_dp 
dd _SPC_DEC_abs      ,_SPC_MOV_Y_IM      ,_SPC_POP_PSW       ,_SPC_MOV_dp_IM 
dd _SPC_BCC          ,_SPC_TCALL_9       ,_SPC_CLR1          ,_SPC_BBC                ; 90 
dd _SPC_ADC_A_Odp_XO ,_SPC_ADC_A_Oabs_XO ,_SPC_ADC_A_Oabs_YO ,_SPC_ADC_A_OOdpO_YO 
dd _SPC_ADC_dp_IM    ,_SPC_ADC_OXO_OYO   ,_SPC_SUBW_YA_dp    ,_SPC_DEC_Odp_XO 
dd _SPC_DEC_A        ,_SPC_MOV_X__SP     ,_SPC_DIV           ,_SPC_XCN 
dd _SPC_EI           ,_SPC_TCALL_10      ,_SPC_SET1          ,_SPC_BBS                ; A0 
dd _SPC_SBC_A_dp     ,_SPC_SBC_A_abs     ,_SPC_SBC_A_OXO     ,_SPC_SBC_A_OOdp_XOO 
dd _SPC_SBC_A_IM     ,_SPC_SBC_dp_dp     ,_SPC_MOV1_C_       ,_SPC_INC_dp 
dd _SPC_INC_abs      ,_SPC_CMP_Y_IM      ,_SPC_POP_A         ,_SPC_MOV_OXOInc_A 
dd _SPC_BCS          ,_SPC_TCALL_11      ,_SPC_CLR1          ,_SPC_BBC                ; B0 
dd _SPC_SBC_A_Odp_XO ,_SPC_SBC_A_Oabs_XO ,_SPC_SBC_A_Oabs_YO ,_SPC_SBC_A_OOdpO_YO 
dd _SPC_SBC_dp_IM    ,_SPC_SBC_OXO_OYO   ,_SPC_MOVW_YA_dp    ,_SPC_INC_Odp_XO 
dd _SPC_INC_A        ,_SPC_MOV_SP_X      ,_SPC_INVALID       ,_SPC_MOV_A_OXOInc 
dd _SPC_DI           ,_SPC_TCALL_12      ,_SPC_SET1          ,_SPC_BBS                ; C0 
dd _SPC_MOV_dp__A    ,_SPC_MOV_abs__A    ,_SPC_MOV_OXO__A    ,_SPC_MOV_OOdp_XOO__A 
dd _SPC_CMP_X_IM     ,_SPC_MOV_abs__X    ,_SPC_MOV1__C       ,_SPC_MOV_dp__Y 
dd _SPC_MOV_abs__Y   ,_SPC_MOV_X_IM      ,_SPC_POP_X         ,_SPC_MUL 
dd _SPC_BNE          ,_SPC_TCALL_13      ,_SPC_CLR1          ,_SPC_BBC                ; D0 
dd _SPC_MOV_Odp_XO__A,_SPC_MOV_Oabs_XO__A,_SPC_MOV_Oabs_YO__A,_SPC_MOV_OOdpO_YO__A 
dd _SPC_MOV_dp__X    ,_SPC_MOV_Odp_YO__X ,_SPC_MOVW_dp_YA    ,_SPC_MOV_Odp_XO__Y 
dd _SPC_DEC_Y        ,_SPC_MOV_A__Y      ,_SPC_CBNE_Odp_XO   ,_SPC_INVALID 
dd _SPC_CLRV         ,_SPC_TCALL_14      ,_SPC_SET1          ,_SPC_BBS                ; E0 
dd _SPC_MOV_A_dp     ,_SPC_MOV_A_abs     ,_SPC_MOV_A_OXO     ,_SPC_MOV_A_OOdp_XOO 
dd _SPC_MOV_A_IM     ,_SPC_MOV_X_abs     ,_SPC_NOT1          ,_SPC_MOV_Y_dp 
dd _SPC_MOV_Y_abs    ,_SPC_NOTC          ,_SPC_POP_Y         ,_SPC_INVALID ;_SPC_SLEEP 
dd _SPC_BEQ          ,_SPC_TCALL_15      ,_SPC_CLR1          ,_SPC_BBC                ; F0 
dd _SPC_MOV_A_Odp_XO ,_SPC_MOV_A_Oabs_XO ,_SPC_MOV_A_Oabs_YO ,_SPC_MOV_A_OOdpO_YO 
dd _SPC_MOV_X_dp     ,_SPC_MOV_X_Odp_YO  ,_SPC_MOV_dp_dp     ,_SPC_MOV_Y_Odp_XO 
dd _SPC_INC_Y        ,_SPC_MOV_Y__A      ,_SPC_DBNZ_Y        ,_SPC_INVALID ;_SPC_STOP 
 
; This holds the base instruction timings in cycles 
ALIGND 
SPCCycleTable: 
db 2,8,4,5,3,4,3,6,2,6,5,4,5,4,6,8  ; 00 
db 2,8,4,5,4,5,5,6,5,5,6,5,2,2,4,6  ; 10 
db 2,8,4,5,3,4,3,6,2,6,5,4,5,4,5,4  ; 20 
db 2,8,4,5,4,5,5,6,5,5,6,5,2,2,3,8  ; 30 
db 2,8,4,5,3,4,3,6,2,6,4,4,5,4,6,6  ; 40 
db 2,8,4,5,4,5,5,6,5,5,4,5,2,2,4,3  ; 50 
db 2,8,4,5,3,4,3,6,2,6,4,4,5,4,5,5  ; 60 
db 2,8,4,5,4,5,5,6,5,5,5,5,2,2,3,6  ; 70 
db 2,8,4,5,3,4,3,6,2,6,5,4,5,2,4,5  ; 80 
db 2,8,4,5,4,5,5,6,5,5,5,5,2,2,12,5 ; 90 
db 3,8,4,5,3,4,3,6,2,6,4,4,5,2,4,4  ; A0 
db 2,8,4,5,4,5,5,6,5,5,5,5,2,2,3,4  ; B0 
db 3,8,4,5,4,5,4,7,2,5,6,4,5,2,4,9  ; C0 
db 2,8,4,5,5,6,6,7,4,5,4,5,2,2,6,3  ; D0 
db 2,8,4,5,3,4,3,6,2,4,5,3,4,3,4,3  ; E0 
db 2,8,4,5,4,5,5,6,3,4,5,4,2,2,4,3  ; F0 
 
; This code should be copied into the top of the address space 
ALIGND 
EXPORT_C SPC_ROM_CODE 
 db 0xCD,0xEF,0xBD,0xE8,0x00,0xC6,0x1D,0xD0 
 db 0xFC,0x8F,0xAA,0xF4,0x8F,0xBB,0xF5,0x78 
 db 0xCC,0xF4,0xD0,0xFB,0x2F,0x19,0xEB,0xF4 
 db 0xD0,0xFC,0x7E,0xF4,0xD0,0x0B,0xE4,0xF5 
 db 0xCB,0xF4,0xD7,0x00,0xFC,0xD0,0xF3,0xAB 
 db 0x01,0x10,0xEF,0x7E,0xF4,0x10,0xEB,0xBA 
 db 0xF6,0xDA,0x00,0xBA,0xF4,0xC4,0xF4,0xDD 
 db 0x5D,0xD0,0xDB,0x1F,0x00,0x00,0xC0,0xFF 
 
ALIGND 
Read_Func_Map:              ; Mappings for SPC Registers 
 dd SPC_READ_INVALID 
 dd SPC_READ_CTRL 
 dd SPC_READ_DSP_ADDR 
 dd SPC_READ_DSP_DATA 
 dd SPC_READ_PORT0R 
 dd SPC_READ_PORT1R 
 dd SPC_READ_PORT2R 
 dd SPC_READ_PORT3R 
 dd SPC_READ_INVALID 
 dd SPC_READ_INVALID 
 dd SPC_READ_INVALID 
 dd SPC_READ_INVALID 
 dd SPC_READ_INVALID 
 dd SPC_READ_COUNTER_0 
 dd SPC_READ_COUNTER_1 
 dd SPC_READ_COUNTER_2 
 
ALIGND 
Write_Func_Map:             ; Mappings for SPC Registers 
 dd SPC_WRITE_INVALID 
 dd SPC_WRITE_CTRL 
 dd SPC_WRITE_DSP_ADDR 
 dd SPC_WRITE_DSP_DATA 
 dd SPC_WRITE_PORT0W 
 dd SPC_WRITE_PORT1W 
 dd SPC_WRITE_PORT2W 
 dd SPC_WRITE_PORT3W 
 dd SPC_WRITE_INVALID 
 dd SPC_WRITE_INVALID 
 dd SPC_WRITE_TIMER_0 
 dd SPC_WRITE_TIMER_1 
 dd SPC_WRITE_TIMER_2 
 dd SPC_WRITE_INVALID 
 dd SPC_WRITE_INVALID 
 dd SPC_WRITE_INVALID 
 
offset_to_bit:  db 0x01,0x02,0x04,0x08,0x10,0x20,0x40,0x80 
offset_to_not:  db 0xFE,0xFD,0xFB,0xF7,0xEF,0xDF,0xBF,0x7F 
 
section .bss 
ALIGNB 
EXPORT_C TotalCycles,skipl 
 
EXPORT_C SPC_T0_cycle_latch,skipl 
EXPORT_C SPC_T0_position,skipw 
EXPORT_C SPC_T0_target,skipw 
 
EXPORT_C SPC_T1_cycle_latch,skipl 
EXPORT_C SPC_T1_position,skipw 
EXPORT_C SPC_T1_target,skipw 
 
EXPORT_C SPC_T2_cycle_latch,skipl 
EXPORT_C SPC_T2_position,skipw 
EXPORT_C SPC_T2_target,skipw 
 
EXPORT_C SPC_T0_counter,skipb 
EXPORT_C SPC_T1_counter,skipb 
EXPORT_C SPC_T2_counter,skipb 
 
ALIGNB 
SPC_Register_Base: 
 
SPC_Code_Base:  skipl 
EXPORT _PC ,skipl 
EXPORT _YA 
EXPORT _A  ,skipb 
EXPORT _Y  ,skipb 
            skipw 
SPC_PAGE:   skipl 
SPC_PAGE_H equ SPC_PAGE+1 
EXPORT _N_flag,skipb 
EXPORT _H_flag,skipb 
EXPORT _I_flag,skipb 
EXPORT _B_flag,skipb 
EXPORT _SP,skipl 
 
EXPORT _SPC_Cycles,skipl    ; Number of cycles to execute for SPC 
 
EXPORT _PSW,skipb   ; Processor status word 
EXPORT _X  ,skipb 
EXPORT _Z_flag,skipb 
EXPORT _P_flag,skipb 
 
EXPORT _V_flag,skipb 
EXPORT _C_flag,skipb 
 
EXPORT_C SPC_PORT0R,skipb 
EXPORT_C SPC_PORT1R,skipb 
EXPORT_C SPC_PORT2R,skipb 
EXPORT_C SPC_PORT3R,skipb 
 
EXPORT_C SPC_PORT0W,skipb 
EXPORT_C SPC_PORT1W,skipb 
EXPORT_C SPC_PORT2W,skipb 
EXPORT_C SPC_PORT3W,skipb 
 
ALIGNB 
%ifdef DEBUG 
SPC_TEMP_ADD:   skipl 
%endif 
 
section .text 
ALIGNC 
SNES_R2140_SPC: ; APUI00 
 Execute_SPC SaveCycles 
 mov al,[_SPC_PORT0W] 
 ret 
 
ALIGNC 
SNES_R2141_SPC: ; APUI01 
 Execute_SPC SaveCycles 
 mov al,[_SPC_PORT1W] 
 ret 
 
ALIGNC 
SNES_R2142_SPC: ; APUI02 
 Execute_SPC SaveCycles 
 mov al,[_SPC_PORT2W] 
 ret 
 
ALIGNC 
SNES_R2143_SPC: ; APUI03 
 Execute_SPC SaveCycles 
 mov al,[_SPC_PORT3W] 
 ret 
 
ALIGNC 
SNES_W2140_SPC: ; APUI00 
 Execute_SPC SaveCycles 
 mov [_SPC_PORT0R],al 
 ret 
 
ALIGNC 
SNES_W2141_SPC: ; APUI01 
 Execute_SPC SaveCycles 
 mov [_SPC_PORT1R],al 
 ret 
 
ALIGNC 
SNES_W2142_SPC: ; APUI02 
 Execute_SPC SaveCycles 
 mov [_SPC_PORT2R],al 
 ret 
 
ALIGNC 
SNES_W2143_SPC: ; APUI03 
 Execute_SPC SaveCycles 
 mov [_SPC_PORT3R],al 
 ret 
 
ALIGNC 
EXPORT_C Make_SPC 
 pusha 
 mov eax,SNES_R2140_SPC 
 mov edx,SNES_R2141_SPC 
 mov esi,SNES_R2142_SPC 
 mov edi,SNES_R2143_SPC 
 
 mov ebx,Read_21_Address(0x40) 
 mov cl,0x40 / 4 
 
.set_read_loop: 
 mov [ebx],eax 
 mov [ebx+1*4],edx 
 mov [ebx+2*4],esi 
 mov [ebx+3*4],edi 
 add ebx,4*4 
 dec cl 
 jnz .set_read_loop 
 
 mov eax,SNES_W2140_SPC 
 mov edx,SNES_W2141_SPC 
 mov esi,SNES_W2142_SPC 
 mov edi,SNES_W2143_SPC 
 
 mov ebx,Write_21_Address(0x40) 
 mov cl,0x40 / 4 
 
.set_write_loop: 
 mov [ebx],eax 
 mov [ebx+1*4],edx 
 mov [ebx+2*4],esi 
 mov [ebx+3*4],edi 
 add ebx,4*4 
 dec cl 
 jnz .set_write_loop 
 
 popa 
 ret 
 
ALIGNC 
EXPORT_C Reset_SPC 
 pusha 
 
 ; Get ROM reset vector and setup Program Counter 
 movzx eax,word [_SPC_ROM_CODE+(0xFFFE-0xFFC0)] 
 mov [_PC],eax 
 
 mov eax,0  ;[_SNES_Cycles] 
 mov [SPC_last_cycles],eax 
 
 ; Reset the sound DSP registers 
 mov [_SPC_Cycles],eax  ; Clear Cycle Count 
 mov [_TotalCycles],eax 
 mov [SPC_PAGE],eax     ; Used to save looking up P flag for Direct page stuff! 
 mov dword [_SP],0x01EF ; Reset registers 
 mov [_YA],eax 
 mov [_X],al 
 mov [_PSW],al          ; Clear Flags Register 
 mov [_N_flag],al       ; Clear Flags Register 
 mov byte [_Z_flag],1 
 mov [_H_flag],al 
 mov [_V_flag],al 
 mov [_I_flag],al 
 mov [_P_flag],al 
 mov [_B_flag],al 
 mov [_C_flag],al 
 
 mov byte [_SPCRAM+_SPC_CTRL],0x80 
 mov dword [SPC_FFC0_Address],_SPC_ROM_CODE-0xFFC0 
 mov dword [SPC_Code_Base],_SPC_ROM_CODE-0xFFC0 
 
 ; Reset timers 
 mov [_SPC_T0_counter],al 
 mov [_SPC_T1_counter],al 
 mov [_SPC_T2_counter],al 
 mov word [_SPC_T0_target],256 
 mov word [_SPC_T1_target],256 
 mov word [_SPC_T2_target],256 
 mov [_SPC_T0_position],ax 
 mov [_SPC_T1_position],ax 
 mov [_SPC_T2_position],ax 
 mov [_SPC_T0_cycle_latch],eax 
 mov [_SPC_T1_cycle_latch],eax 
 mov [_SPC_T2_cycle_latch],eax 
 mov [_sound_cycle_latch],eax 
 
 ; Reset SPC700 output ports 
 mov [_SPC_PORT0W],al 
 mov [_SPC_PORT1W],al 
 mov [_SPC_PORT2W],al 
 mov [_SPC_PORT3W],al 
 
 ; Reset SPC700 input ports 
 mov [_SPC_PORT0R],al 
 mov [_SPC_PORT1R],al 
 mov [_SPC_PORT2R],al 
 mov [_SPC_PORT3R],al 
 
 ; Reset sound DSP port address 
 mov [_SPC_DSP+_SPC_DSP_ADDR],al 
 mov [_SPC_DSP_DATA],eax 
 
 popa 
 ret 
 
SPC_SHOW_REGISTERS: 
 pusha 
 call _DisplaySPC 
 popa 
 ret 
 
ALIGNC 
EXPORT_C get_SPC_PSW 
 LOAD_BASE 
 SETUPFLAGS_SPC 
 ret 
 
ALIGNC 
SPC_GET_WORD: 
 GET_BYTE_SPC 
 mov ah,al 
 inc bx 
 GET_BYTE_SPC 
 ror ax,8 
 ret 
 
ALIGNC 
EXPORT_C SPC_START 
%ifdef WATCH_SPC_BREAKS 
extern _BreaksLast 
 inc dword [_BreaksLast] 
%endif 
 
 mov al,[In_CPU] 
 push eax 
 mov byte [In_CPU],0 
 
 LOAD_CYCLES 
 LOAD_PC 
 LOAD_BASE 
 xor eax,eax 
 jmp short SPC_START_NEXT 
 
ALIGNC 
SPC_RETURN: 
;cmp R_Base,SPC_Register_Base 
;jne 0b 
 
%ifdef DEBUG 
;mov ebx,[SPC_TEMP_ADD] 
;mov [_OLD_SPC_ADDRESS],ebx 
%endif 
 test R_Cycles,R_Cycles 
%ifdef TRACKERS 
 jg near SPC_OUT        ; Do another instruction if cycles left 
%else 
;jg SPC_OUT             ; Do another instruction if cycles left 
 jg near SPC_OUT        ; Do another instruction if cycles left 
%endif 
 
SPC_START_NEXT: 
 
; This code is for a SPC-tracker dump... #define TRACKERS to make a dump 
; of the CPU state before each instruction - uncomment the calls to 
; _Wangle__Fv and _exit to force the emulator to exit when the buffer 
; fills. TRACKERS must be defined to the size of the buffer to be used - 
; which must be a power of two, and the variables required by this and the 
; write in Wangle() (romload.cc) exist only if DEBUG and SPCTRACKERS are 
; also defined in romload.cc. 
%ifdef TRACKERS 
extern _SPC_LastIns 
extern _SPC_InsAddress 
extern _Wangle__Fv 
extern _exit 
 mov edi,[_SPC_LastIns]     ; 
 add edi,[_SPC_InsAddress]  ; 
 SAVE_PC eax                ; 
 mov [edi],ah               ; 
 mov [1+edi],al             ; 
 mov al,[_A]                ; 
 mov [2+edi],al             ; 
 mov al,[_X]                ; 
 mov [3+edi],al             ; 
 mov al,[_Y]                ; 
 mov [4+edi],al             ; 
 mov al,[_SP]               ; 
 mov [5+edi],al             ; 
 SETUPFLAGS_SPC             ; 
 mov [6+edi],al             ; 
 
 mov al,[esi]               ; 
 mov [7+edi],al             ; 
 mov eax,[1+esi]            ; 
 mov [8+edi],eax            ; 
 mov eax,[5+esi]            ; 
 mov [12+edi],eax           ; 
 
 mov edi,[_SPC_LastIns]     ; 
 add edi,byte 16            ; 
 and edi,(TRACKERS-1)       ; 
 mov [_SPC_LastIns],edi     ; 
 test edi,edi               ; 
 jnz .buffer_not_full       ; 
 call _Wangle__Fv           ; 
 jmp _exit                  ; 
                            ; 
.buffer_not_full:           ; 
 xor eax,eax                ; 
%endif 
 
;mov ebx,[_PC]          ; PC now setup 
;mov R_NativePC,[SPC_Code_Base] 
;add R_NativePC,ebx 
%ifdef DEBUG 
;mov [SPC_TEMP_ADD],ebx 
%endif 
 
;%define OPCODE_DUMP 
%ifdef OPCODE_DUMP 
 mov eax,B_YA 
 mov bl,B_X 
 mov [D_YA],ax 
 mov [D_X],bl 
 GET_PC eax 
 bswap eax 
 mov [D_PC],eax 
 mov eax,[R_NativePC] 
 mov bl,B_SP 
 mov [D_CODE],eax 
 mov [D_SP],bl 
 call _get_SPC_PSW 
 mov [D_PSW],al 
 push edx 
extern _dump_spc_opcode 
 call _dump_spc_opcode 
 pop edx 
%endif 
 xor eax,eax 
 mov al,[R_NativePC]    ; Fetch opcode 
 xor ebx,ebx 
 mov bl,[SPCCycleTable+eax] 
 add R_Cycles,ebx               ; Update cycle counter 
 jmp dword [SPCOpTable+eax*4]   ; jmp to opcode handler 
 
%define OPCODE_DUMP 
%ifdef OPCODE_DUMP 
section .data 
ALIGND 
EXPORT_C SPC_DUMP_AREA 
D_PSW: 
D_PC:   dd 0 
D_CODE: dd 0 
D_YA:   dw 0 
D_X:    db 0 
D_SP:   db 0 
D_PAGE: dd 0 
 
section .text 
%endif 
 
ALIGNC 
SPC_OUT: 
 SAVE_PC ebx 
 SAVE_CYCLES   ; Set cycle counter 
 
%ifdef INDEPENDENT_SPC 
 ; Update SPC timers to prevent overflow 
 Update_SPC_Timer 0 
 Update_SPC_Timer 1 
 Update_SPC_Timer 2 
%endif 
 
 pop eax 
 mov [In_CPU],al 
 ret                    ; Return to CPU emulation 
 
%include "SPCaddr.ni"   ; Include addressing mode macros 
%include "SPCmacro.ni"  ; Include instruction macros 
 
EXPORT_C spc_ops_start 
 
ALIGNC 
EXPORT_C SPC_INVALID 
 mov [_Map_Byte],al     ; al contains opcode! 
 
 SAVE_PC ebx 
 SAVE_CYCLES   ; Set cycle counter 
 
 mov eax,[_PC]          ; Adjust address to correct for pre-increment 
 mov [_Map_Address],eax ; this just sets the error output up correctly! 
 
extern _InvalidSPCOpcode 
 jmp _InvalidSPCOpcode  ; This exits.. avoids conflict with other things! 
 
ALIGNC 
EXPORT_C SPC_SET1 
 shr eax,5 
 mov ebx,B_SPC_PAGE 
 mov bl,[1+R_NativePC] 
 add R_NativePC,byte 2 
 mov ah,[offset_to_bit+eax] 
 GET_BYTE_SPC      
 or al,ah 
 SET_BYTE_SPC 
 OPCODE_EPILOG 
 
ALIGNC 
EXPORT_C SPC_CLR1 
 shr eax,5 
 mov ebx,B_SPC_PAGE 
 mov bl,[1+R_NativePC] 
 add R_NativePC,byte 2 
 mov ah,[offset_to_not+eax] 
 GET_BYTE_SPC      
 and al,ah 
 SET_BYTE_SPC 
 OPCODE_EPILOG 
 
ALIGNC 
EXPORT_C SPC_BBS 
 shr eax,5 
 mov ebx,B_SPC_PAGE 
 mov bl,[1+R_NativePC] 
 mov ah,[offset_to_bit+eax] 
 GET_BYTE_SPC 
 test al,ah 
 jz .not_taken 
 movsx eax,byte [2+R_NativePC] 
 add R_NativePC,eax 
 add R_Cycles,byte 2    ; branch taken 
.not_taken: 
 add R_NativePC,byte 3 
 OPCODE_EPILOG 
 
ALIGNC 
EXPORT_C SPC_BBC 
 shr eax,5 
 mov ebx,B_SPC_PAGE 
 mov bl,[1+R_NativePC] 
 mov ah,[offset_to_bit+eax] 
 GET_BYTE_SPC          
 test al,ah 
 jnz .not_taken 
 movsx eax,byte [2+R_NativePC] 
 add R_NativePC,eax 
 add R_Cycles,byte 2    ; branch taken 
.not_taken: 
 add R_NativePC,byte 3 
 OPCODE_EPILOG 
 
%include "SPCops.ni"    ; Include opcodes 
 
SPC_READ_CTRL: 
SPC_READ_DSP_ADDR: 
 mov al,[_SPCRAM+ebx] 
 ret 
 
SPC_READ_DSP_DATA: 
 push ecx 
 push edx 
 push eax 
 call _SPC_READ_DSP 
 xor ecx,ecx 
 pop eax 
 mov cl,[_SPCRAM+_SPC_DSP_ADDR] 
 pop edx 
 mov al,[_SPC_DSP+ecx]    ; read from DSP register 
 pop ecx 
 ret 
 
SPC_READ_PORT0R: 
 mov al,[_SPC_PORT0R] 
 ret 
SPC_READ_PORT1R: 
 mov al,[_SPC_PORT1R] 
 ret 
SPC_READ_PORT2R: 
 mov al,[_SPC_PORT2R] 
 ret 
SPC_READ_PORT3R: 
 mov al,[_SPC_PORT3R] 
 ret 
 
; WOOPS... TIMER registers are write only, the actual timer clock is internal not accessible! 
 
; COUNTERS ARE 4 BIT, upon read they reset to 0 status 
 
SPC_READ_COUNTER_0: 
 push ecx 
 push edx 
 push eax 
 SAVE_CYCLES    ; Set cycle counter 
 Update_SPC_Timer 0 
;call _Update_SPC_Timer_0 
 pop eax 
 pop edx 
 pop ecx 
 mov al,[_SPC_T0_counter] 
 mov [_SPC_T0_counter],bh 
 ret 
 
SPC_READ_COUNTER_1: 
 push ecx 
 push edx 
 push eax 
 SAVE_CYCLES    ; Set cycle counter 
 Update_SPC_Timer 1 
;call _Update_SPC_Timer_1 
 pop eax 
 pop edx 
 pop ecx 
 mov al,[_SPC_T1_counter] 
 mov byte [_SPC_T1_counter],bh 
 ret 
 
SPC_READ_COUNTER_2: 
 push ecx 
 push edx 
 push eax 
 SAVE_CYCLES    ; Set cycle counter 
 Update_SPC_Timer 2 
;call _Update_SPC_Timer_2 
 pop eax 
 pop edx 
 pop ecx 
 mov al,[_SPC_T2_counter] 
 mov byte [_SPC_T2_counter],bh 
 ret 
 
; | ROMEN | TURBO | PC32  | PC10  | ----- |  ST2  |  ST1  |  ST0  | 
; 
; ROMEN - enable mask ROM in top 64-bytes of address space for CPU read 
; TURBO - enable turbo CPU clock 
; PC32  - clear SPC read ports 2 & 3 
; PC10  - clear SPC read ports 0 & 1 
; ST2   - start timer 2 (64kHz) 
; ST1   - start timer 1 (8kHz) 
; ST0   - start timer 0 (8kHz) 
 
SPC_WRITE_CTRL: 
 push eax 
 mov ah,0 
 test al,al     ; New for 0.25 - read hidden RAM 
 mov edi,_SPCRAM 
 jns .rom_disabled 
 mov edi,_SPC_ROM_CODE-0xFFC0 
 
.rom_disabled: 
 mov [SPC_FFC0_Address],edi 
 
 test al,0x10       ; Reset ports 0/1 to 00 if set 
 jz .no_clear_01 
 mov [_SPC_PORT0R],ah   ; Ports read by SPC should be reset!  
 mov [_SPC_PORT1R],ah   ; Thanks to Butcha for fix! 
 
.no_clear_01: 
 test al,0x20       ; Reset ports 2/3 to 00 if set 
 jz .no_clear_23 
 mov [_SPC_PORT2R],ah 
 mov [_SPC_PORT3R],ah 
 
.no_clear_23: 
 mov edi,R_Cycles 
 test byte [_SPCRAM+ebx],4 
 jnz .no_enable_timer_2 
 test al,4 
 jz  .no_enable_timer_2 
 mov byte [_SPC_T2_counter],0 
 mov word [_SPC_T2_position],0 
 mov [_SPC_T2_cycle_latch],edi 
 
.no_enable_timer_2: 
 test byte [_SPCRAM+ebx],2 
 jnz .no_enable_timer_1 
 test al,2 
 jz .no_enable_timer_1 
 mov byte [_SPC_T1_counter],0 
 mov word [_SPC_T1_position],0 
 mov [_SPC_T1_cycle_latch],edi 
 
.no_enable_timer_1: 
 test byte [_SPCRAM+ebx],1 
 jnz .no_enable_timer_0 
 test al,1 
 jz .no_enable_timer_0 
 mov byte [_SPC_T0_counter],0 
 mov word [_SPC_T0_position],0 
 mov [_SPC_T0_cycle_latch],edi 
 
.no_enable_timer_0: 
 pop eax 
 mov [_SPCRAM+ebx],al 
 ret 
 
SPC_WRITE_DSP_ADDR: 
 mov [_SPCRAM+ebx],al 
 ret 
 
SPC_WRITE_DSP_DATA: 
 mov [_SPC_DSP_DATA],al 
 push ecx 
 push edx 
 push eax 
 call _SPC_WRITE_DSP 
 pop eax 
 pop edx 
 pop ecx 
 ret 
 
SPC_WRITE_PORT0W: 
 mov [_SPC_PORT0W],al 
 ret 
SPC_WRITE_PORT1W: 
 mov [_SPC_PORT1W],al 
 ret 
SPC_WRITE_PORT2W: 
 mov [_SPC_PORT2W],al 
 ret 
SPC_WRITE_PORT3W: 
 mov [_SPC_PORT3W],al 
 ret 
 
SPC_WRITE_TIMER_0: 
 push ecx 
 push edx 
 push eax 
 SAVE_CYCLES    ; Set cycle counter 
 Update_SPC_Timer 0 
;call _Update_SPC_Timer_0   ; Timer must catch up before changing target 
 pop eax 
 pop edx 
 pop ecx 
 test al,al 
 mov [_SPC_T0_target],al    ; (0.32) Butcha - timer targets are writable 
 setz [_SPC_T0_target+1]    ; 0 = 256 
 ret 
 
SPC_WRITE_TIMER_1: 
 push ecx 
 push edx 
 push eax 
 SAVE_CYCLES    ; Set cycle counter 
 Update_SPC_Timer 1 
;call _Update_SPC_Timer_1   ; Timer must catch up before changing target 
 pop eax 
 pop edx 
 pop ecx 
 test al,al 
 mov [_SPC_T1_target],al    ; (0.32) Butcha - timer targets are writable 
 setz [_SPC_T1_target+1]    ; 0 = 256 
 ret 
 
SPC_WRITE_TIMER_2: 
 push ecx 
 push edx 
 push eax 
 SAVE_CYCLES    ; Set cycle counter 
 Update_SPC_Timer 2 
;call _Update_SPC_Timer_2   ; Timer must catch up before changing target 
 pop eax 
 pop edx 
 pop ecx 
 test al,al 
 mov [_SPC_T2_target],al    ; (0.32) Butcha - timer targets are writable 
 setz [_SPC_T2_target+1]    ; 0 = 256 
 ret 
 
section .text 
ALIGNC 
section .data 
ALIGND 
section .bss 
ALIGNB