📄 spc700.asm
字号:
%if 0
SNEeSe, an Open Source Super NES emulator.
Copyright (c) 1998-2004 Charles Bilyue'.
Portions Copyright (c) 2003-2004 Daniel Horchner.
This is free software. See 'LICENSE' for details.
You must read and accept the license prior to use.
%endif
; 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
%define UPDATE_SOUND_ON_RAM_WRITE
; 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 memory mapper work register;
; EBP is used to hold cycle counter;
; ESI is used by the opcode fetcher;
; EDI is used as CPU 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 2.048Mhz, 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.inc"
%include "apu/spc.inc"
%include "apu/regs.inc"
%include "ppu/ppu.inc"
EXTERN_C SPC_CPU_cycle_multiplicand,SPC_CPU_cycle_divisor
EXTERN SPC_CPU_cycles_mul,SPC_CPU_cycles
EXTERN_C sound_cycle_latch
EXTERN_C SPC_DSP
EXTERN_C SPC_DSP_DATA
EXTERN_C SPC_READ_DSP,SPC_WRITE_DSP
EXTERN_C Update_SPC_Timer_0,Update_SPC_Timer_1,Update_SPC_Timer_2
EXTERN_C Wrap_SPC_Cyclecounter
EXTERN_C Map_Byte,Map_Address
EXTERN_C SNES_Cycles,EventTrip
EXTERN SPC_last_cycles,In_CPU
EXTERN_C DisplaySPC
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 C_LABEL(_SPC_PSW)
%define _YA C_LABEL(_SPC_YA)
%define _A C_LABEL(_SPC_A)
%define _Y C_LABEL(_SPC_Y)
%define _X C_LABEL(_SPC_X)
%define _SP C_LABEL(_SPC_SP)
%define _PC C_LABEL(_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+C_LABEL(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+C_LABEL(SPC_PORT0R)]
%define B_SPC_PORT1R [R_Base-SPC_Register_Base+C_LABEL(SPC_PORT1R)]
%define B_SPC_PORT2R [R_Base-SPC_Register_Base+C_LABEL(SPC_PORT2R)]
%define B_SPC_PORT3R [R_Base-SPC_Register_Base+C_LABEL(SPC_PORT3R)]
%define B_SPC_PORT0W [R_Base-SPC_Register_Base+C_LABEL(SPC_PORT0W)]
%define B_SPC_PORT1W [R_Base-SPC_Register_Base+C_LABEL(SPC_PORT1W)]
%define B_SPC_PORT2W [R_Base-SPC_Register_Base+C_LABEL(SPC_PORT2W)]
%define B_SPC_PORT3W [R_Base-SPC_Register_Base+C_LABEL(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 eax,[C_LABEL(SPC_Cycles)]
mov dword R_Cycles,[C_LABEL(TotalCycles)]
sub dword R_Cycles,eax
%endmacro
; Get cycle counter to register argument
%macro GET_CYCLES 1
mov dword %1,[C_LABEL(SPC_Cycles)]
add dword %1,R_Cycles
%endmacro
; Save register R_Cycles to cycle counter
%macro SAVE_CYCLES 0
GET_CYCLES R_SPC700_MemMap_Trash
mov [C_LABEL(TotalCycles)],R_SPC700_MemMap_Trash
%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
%ifnidn %1,R_NativePC
mov dword %1,R_NativePC
%endif
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 %3,B_N_flag
shr byte %3,7
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
mov byte B_N_flag,%3 ;negative
shl byte %3,2 ;start next (overflow)
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, edx is free
; NB bx is not corrupted! edx 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 SPC_START_NEXT
jmp SPC_OUT
%else
jmp SPC_RETURN
%endif
%endmacro
ALIGNC
EXPORT_C SPC_READ_MAPPER
;and ebx,0xFFFF
test bh,bh
jz C_LABEL(SPC_READ_ZERO_PAGE)
cmp ebx,0xFFC0
jae C_LABEL(SPC_READ_RAM_ROM)
EXPORT_C SPC_READ_RAM
mov al,[C_LABEL(SPCRAM)+ebx]
ret
ALIGNC
EXPORT_C SPC_READ_RAM_ROM
mov edx,[SPC_FFC0_Address]
mov al,[ebx + edx]
ret
ALIGNC
EXPORT_C SPC_READ_ZERO_PAGE
cmp bl,0xF0
jb C_LABEL(SPC_READ_RAM)
EXPORT_C SPC_READ_FUNC
%ifdef LOG_SOUND_DSP_READ
SAVE_PC edx
%endif
SAVE_CYCLES ; Set cycle counter
jmp dword [Read_Func_Map - 0xF0 * 4 + ebx * 4]
ALIGNC
EXPORT_C SPC_READ_INVALID
mov al,0xFF ; v0.15
%ifdef TRAP_INVALID_READ
%ifdef DEBUG
EXTERN_C InvalidSPCHWRead
;and ebx,0xFFFF
mov [C_LABEL(Map_Address)],ebx ; Set up Map Address so message works!
mov [C_LABEL(Map_Byte)],al ; Set up Map Byte so message works
pusha
call _InvalidSPCHWRead ; Display read from invalid HW warning
popa
%endif
%endif
ret
; --------
EXPORT_C SPC_WRITE_MAPPER
;and ebx,0xFFFF
test bh,bh
jz C_LABEL(SPC_WRITE_ZERO_PAGE)
EXPORT_C SPC_WRITE_RAM
%ifdef UPDATE_SOUND_ON_RAM_WRITE
push ecx
;push edx
push eax
SAVE_CYCLES ; Set cycle counter
EXTERN_C update_sound
call C_LABEL(update_sound)
pop eax
;pop edx
pop ecx
%endif
mov [C_LABEL(SPCRAM)+ebx],al
ret
ALIGNC
EXPORT_C SPC_WRITE_ZERO_PAGE
cmp bl,0xF0
jb C_LABEL(SPC_WRITE_RAM)
EXPORT_C SPC_WRITE_FUNC
%ifdef LOG_SOUND_DSP_WRITE
SAVE_PC edx
%endif
SAVE_CYCLES ; Set cycle counter
jmp dword [Write_Func_Map - 0xF0 * 4 + ebx * 4]
EXPORT_C SPC_WRITE_INVALID
%ifdef TRAP_INVALID_WRITE
%ifdef DEBUG
EXTERN_C InvalidSPCHWWrite
;and ebx,0xFFFF
mov [C_LABEL(Map_Address)],ebx ; Set up Map Address so message works!
mov [C_LABEL(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 edx
%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 C_LABEL(SPC_READ_FUNC)
jmp %%done
%%read_mapper:
call C_LABEL(SPC_READ_RAM_ROM)
jmp %%done
%%read_direct:
mov al,[C_LABEL(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
je %%read_mapper
cmp bl,0xFF
je %%read_mapper
call C_LABEL(SPC_READ_FUNC)
mov ah,al
inc ebx
call C_LABEL(SPC_READ_FUNC)
ror ax,8
jmp %%done
%%read_mapper:
call SPC_GET_WORD
jmp %%done
%%read_direct:
mov ax,[C_LABEL(SPCRAM)+ebx]
inc ebx
%%done:
%endmacro
; SET_BYTE & SET_WORD now assume ebx contains the write address and
; eax the value to write, corrupts edx
%macro SET_BYTE_SPC 0
%ifdef UPDATE_SOUND_ON_RAM_WRITE
call C_LABEL(SPC_WRITE_MAPPER)
%else
test bh,bh
jnz %%write_direct
cmp bl,0xF0
jb %%write_direct
call C_LABEL(SPC_WRITE_FUNC)
jmp %%done
%%write_direct:
mov [C_LABEL(SPCRAM)+ebx],al
%%done:
%endif
%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,edx
%macro PUSH_B 0 ; Push Byte (SP--)
mov ebx,B_SP
mov [C_LABEL(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,[C_LABEL(SPCRAM)+ebx] ; Fetch data from stack
%endmacro
%macro PUSH_W 0 ; Push Word (SP--)
mov ebx,B_SP
mov [C_LABEL(SPCRAM)+ebx],ah ; Store data on stack
mov [C_LABEL(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,[C_LABEL(SPCRAM)+ebx] ; Fetch data from stack
mov al,[C_LABEL(SPCRAM)+ebx-1] ; Fetch data from stack
%endmacro
; --- Ease up on the finger cramps ;-)
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -