📄 timing.inc
字号:
%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
;%define SNEeSe_No_GUI
;%define DELAY_FRAMES 5
; CPU/interrupt/graphics timing implementation
; Fixed events (always at same time in frame):
; Scanline 0: hidden
; Scanline 1-224/239, dots 0- 21: Hblank
; Scanline 1-224/239, dots 22-277: Displayed screen
; Scanline 1-224/239, dots 278-339: Hblank, HDMA start
; Scanline 225/240: Vblank, NMI
; Variable events (software-specified time in frame):
; H-IRQ - on a specific point on every scanline
; V-IRQ - short time after beginning of a specific scanline
; H+V-IRQ - on a specific point on the frame
;%define REPORT_EVENTS
%define ZERO_TRIP_CYCLE_COUNTER
%define JOYC1_OPEN_BUS_BITS (((1 << 6) - 1) << 2)
%define JOYC2_OPEN_BUS_BITS (((1 << 3) - 1) << 5)
%define JOYC2_ALWAYS_HIGH_BITS (((1 << 3) - 1) << 2)
%define RDNMI_OPEN_BUS_BITS (((1 << 3) - 1) << 4)
%define TIMEUP_OPEN_BUS_BITS ((1 << 7) - 1)
%define HVBJOY_OPEN_BUS_BITS (((1 << 5) - 1) << 1)
%define RDNMI_VBLANK_START (1 << 7)
%define HVBJOY_IN_VBLANK (1 << 7)
%define HVBJOY_IN_HBLANK (1 << 6)
%define HVBJOY_CONTROLLERS_BUSY (1 << 0)
%include "misc.inc"
%include "ppu/screen.inc"
%include "cpu/dma.inc"
%include "apu/spc.inc"
%include "ppu/sprites.inc"
%include "cycles.inc"
EXTERN_C FPSTicks
EXTERN_C UPDATE_KEYS
EXTERN_C CONTROLLER_1_TYPE,CONTROLLER_2_TYPE
EXTERN_C MickeyMouse,MickeyRead,MouseButtons
%define FRAME_LIMIT 0
; Set this to force emulation loop to break after x frames have been
; displayed (not emulated!) This does not save CPU regs, etc.!
; Primary use is for profiling!
%define FRAME_LIMIT_PROFILE 0
; Set this to change behavior of FRAME_LIMIT to enable internal
; profiling code
;%define VAR_FRAME_LIMIT
; Set this to force emulation loop to break after FrameLimit frames
section .bss
%if 0
ALIGND
EXPORT LineBG1OFS ,skipw 2*239
ALIGND
EXPORT LineBG2OFS ,skipw 2*239
ALIGND
EXPORT LineBG3OFS ,skipw 2*239
ALIGND
EXPORT LineBG4OFS ,skipw 2*239
ALIGND
EXPORT LineM7 ,skipw 6*239
EXPORT ScreenLineHoles ,skipb (256+128)/8
%endif
JOYC1: skipb ; This holds the controller read control byte
EXPORT_C Controller1_Pos,skipb ; Shift count for controller 1 read
EXPORT_C Controller23_Pos,skipb ; Shift count for controller 2/3 read
EXPORT_C Controller45_Pos,skipb ; Shift count for controller 4/5 read
ALIGNB
; These are updated per VBL rather than per read!
EXPORT_C JOY1L,skipb
EXPORT_C JOY1H,skipb 3
EXPORT_C JOY2L,skipb
EXPORT_C JOY2H,skipb 3
; These are not updated yet
EXPORT_C JOY3L,skipb
EXPORT_C JOY3H,skipb 3
EXPORT_C JOY4L,skipb
EXPORT_C JOY4H,skipb 3
RDDIV:
RDDIVL: skipb ; Quotient of divide
RDDIVH: skipb
RDMPY:
RDMPYL: skipb ; Multiplication or remainder
RDMPYH: skipb
WRMPY:
WRMPYA: skipb ; Multiplicand A
WRMPYB: skipb ; Multiplicand B
WRDIV:
WRDIVL: skipb ; Dividend C
WRDIVH: skipb
EXPORT_C LastRenderLine ,skipl
EXPORT_C LastVBLLine ,skipl
EXPORT FrameCount ,skipl ; Used for frameskipping
EXPORT Event_Handler ,skipl ; Used for render/HDMA timing/NMI/IRQ
EXPORT Fixed_Event ,skipl ; Used for render/HDMA timing
EXPORT Last_Trip ,skipl ; Clock cycle position of last scheduled event
HDMA_Next_Event: skipl ; Fixed event following HDMA
DMA_Next_Event: skipl ; Fixed event following DMA
DMA_Next_Trip: skipl
Render_Next_Event: skipl ; Fixed event following render
Render_Next_Trip: skipl
NMI_Event_Handler: skipl ; Event handler for Vblank start
NMI_Next_Event: skipl ; Fixed event following render
NMI_Next_Trip: skipl
Vblank_Start: skipl ; Scanline of last Vblank start
EXPORT HTimer ,skipl ; IRQ position on scanline
EXPORT HTimer_Set ,skipl ; H-IRQ position set in $4207-8
EXPORT_C HTIMEL ,skipb ; H IRQ position low
EXPORT_C HTIMEH ,skipb ; H IRQ position high
skipb
skipb
EXPORT_C VTIMEL ,skipb ; V IRQ position low
EXPORT_C VTIMEH ,skipb ; V IRQ position high
skipb
skipb
FPSCount: skipl ; Count of frames executed this second
EXPORT_C FPSMaxTicks,skipl ; Number of timer ticks after which FPS is calc'd
EXPORT_C FPSLast ,skipl ; Calculated FPS for last second
EXPORT_C BreaksLast ,skipl ; Render breaks for last frame
Latched_H: skipl ; These two are latched values!
Latched_V: skipl
MEMSEL: skipb ; FastROM switch
; Hack for FF6 glitch, to be removed when timing improved
EXPORT_C FastROM_offset,skipb
EXPORT OPHCT,skipb ; Whether reading lo or high byte
EXPORT OPVCT,skipb ; Whether reading lo or high byte
RDNMI: skipb ; x000vvvv x=disable/enable NMI,vvvv=version
;mask for RDNMI - (only) bit 7 cleared if NMI source raised and cleared
; during this frame
RDNMI_mask:skipb
EXPORT_C NMITIMEN,skipb ; a0yx000b a=NMI on/off,y=vert count,x=horiz count,b=joy read
; I/O port register - bit 7 affects H/V counter, all bits used for output
EXPORT_C WRIO,skipb ; lxxxxxxx l=counter latched when set
; I/O port register - value CPU is receiving
; RDIO register reads return this value ANDed with value in WRIO
EXPORT_C RDIO,skipb
EXPORT HVBJOY,skipb
; Nonzero if an IRQ set for current line has not been reached yet
;IRQ_set_this_line:skipb
section .text
;R_Cycles = C_LABEL(EventTrip) - C_LABEL(SNES_Cycles), trips on <= zero
;%1 = Execute
%macro Update_Cycles 0-1 0
mov eax,[C_LABEL(SNES_Cycles)] ; Update CPU and SPC cycles
mov edi,[C_LABEL(SNES_Cycles)]
sub edi,[C_LABEL(EventTrip)]
cmp eax,CYCLES_REFRESH_START
jb %%before_refresh
add eax,byte CYCLES_IN_REFRESH
%%before_refresh:
sub eax,[SPC_last_cycles]
mov [C_LABEL(SNES_Cycles)],edi
mov [SPC_last_cycles],edi
%ifidni %1,Execute
cmp byte [C_LABEL(SPC_ENABLED)],0
jz %%no_spc
add eax,[SPC_CPU_cycles]
Execute_SPC
%%no_spc:
%else
add [SPC_CPU_cycles],eax
%endif
%endmacro
; Uses 012-local labels, corrupts edx, zeroes eax
%macro Update_FPS_Counter 0
mov edx,[C_LABEL(FPSMaxTicks)]
xor eax,eax
cmp [C_LABEL(FPSTicks)],edx
jb %%no_update
mov edx,[FPSCount]
je %%fps_update
cmp edx,byte 1
ja %%fps_update
mov dword [C_LABEL(FPSLast)],0
jmp %%less_than_1_fps
%%fps_update:
mov [C_LABEL(FPSLast)],edx
%%less_than_1_fps:
mov [C_LABEL(FPSTicks)],eax
mov [FPSCount],eax
%%no_update:
%endmacro
; CPU/Render/Display/Blanking timing
; Master clock: 21.47MHz Dot clock: masterclock/4
; Vblank: Lines (DisplayEnd + 1) to 0
; Display: Lines 1 to DisplayEnd
; < 22d Hblank><256d display >< 64d Hblank> (render)
; <512c exec >< 40c memory refresh><816c exec > (execution)
; Keep one fixed event cycle-target/handler pointer
; Keep one variable (may be fixed or IRQ)
ALIGNC
EXPORT IRQNewFrameReset
mov dword [Last_Trip],0
mov dword [C_LABEL(EventTrip)],CYCLES_HDMA_START
mov dword [FixedTrip],CYCLES_HDMA_START
mov dword [Event_Handler],HDMA_Event
mov dword [Fixed_Event],HDMA_Event
mov dword [HDMA_Next_Event],C_LABEL(IRQFirstRender)
; Reset frameskip counter
mov ebx,1
cmp ebx,[C_LABEL(FRAME_SKIP_MIN)]
ja .no_fix_framecount
mov ebx,[C_LABEL(FRAME_SKIP_MIN)]
.no_fix_framecount:
mov [FrameCount],ebx
xor eax,eax
mov [C_LABEL(Timer_Counter_Throttle)],eax ; Reset speed-throttle timer
mov [C_LABEL(Current_Line_Timing)],eax ; Reset scanline counter
inc dword [FPSCount] ; For FPS counter - 0.25b14
;%define TRAP_HDMA_RELATCH
%ifdef TRAP_HDMA_RELATCH
EXTERN_C Dump_DMA
pusha
push byte 0
call C_LABEL(Dump_DMA)
pop edi
popa
RELATCH_HDMA
pusha
push byte 1
call C_LABEL(Dump_DMA)
pop edi
popa
%else
RELATCH_HDMA
%endif
Update_FPS_Counter
ret
EXTERN_C print_str,print_decnum,print_hexnum
section .data
gap_str:db " ",0
nl_str:db 10,0
section .text
%macro report_event 0
%ifdef REPORT_EVENTS
push 8
push dword [C_LABEL(EventTrip)]
call C_LABEL(print_hexnum)
push gap_str
call C_LABEL(print_str)
push 8
push dword [C_LABEL(SNES_Cycles)]
call C_LABEL(print_hexnum)
push .str
call C_LABEL(print_str)
push dword [C_LABEL(Current_Line_Timing)]
call C_LABEL(print_decnum)
push nl_str
call C_LABEL(print_str)
add esp,4*8
%endif
%endmacro
ALIGNC
HDMA_Event:
section .data
.str:db " HDMA ",0
section .text
report_event
mov eax,[C_LABEL(EventTrip)]
mov [Last_Trip],eax
mov eax,[HDMA_Next_Event]
;mov dword [C_LABEL(EventTrip)],CYCLES_NEW_SCANLINE
mov dword [FixedTrip],CYCLES_NEW_SCANLINE
;mov [Event_Handler],eax
mov [Fixed_Event],eax
cmp byte [HDMAON],0
jz .no_HDMA
LOAD_CYCLES
add R_65c816_Cycles,byte 18 ; HDMA processing
call do_HDMA
SAVE_CYCLES
%ifdef TRAP_HDMA_RELATCH
pusha
push byte 2
call C_LABEL(Dump_DMA)
pop edi
popa
%endif
.no_HDMA:
call IRQ_Check_Late ; chain
LOAD_CYCLES
test R_Cycles,R_Cycles
jl .no_chain
jmp dword [Event_Handler]
.no_chain:
jmp CPU_START ; Return to CPU
ALIGNC
Enabling_IRQ_Event:
section .data
.str:db " enabling IRQ ",0
section .text
report_event
cmp byte [NMI_pin],NMI_Raised
je .nmi_waiting
mov al,CEM_Instruction_After_IRQ_Enable
mov [CPU_Execution_Mode],al
.nmi_waiting:
call IRQ_Check_Late ; chain
LOAD_CYCLES
test R_Cycles,R_Cycles
jl .no_chain
jmp dword [Event_Handler]
.no_chain:
jmp CPU_START ; Return to CPU
ALIGNC
IRQ_Enabled_Event:
section .data
.str:db " enabled IRQ ",0
section .text
report_event
mov al,[CPU_Execution_Mode]
cmp al,CEM_Instruction_After_IRQ_Enable
jne .mode_changed
mov al,CEM_Normal_Execution
mov [CPU_Execution_Mode],al
.mode_changed:
mov al,[IRQ_pin]
test al,al
jz .no_irq
mov al,[CPU_Execution_Mode]
cmp al,CEM_In_DMA
je .no_irq
cmp al,CEM_Clock_Stopped
je .no_irq
cmp al,CEM_Waiting_For_Interrupt
jne .no_wai
%ifdef WAI_DELAY
; WAI delay after interrupt signal: 2 IO
add dword [C_LABEL(SNES_Cycles)],byte 12 ;*
%endif
inc word [CPU_LABEL(PC)]
mov byte [CPU_Execution_Mode],CEM_Normal_Execution
.no_wai:
push edi
LOAD_BASE
JUMP_FLAG SNES_FLAG_I,.irq_disabled ; Interrupts disabled?
LOAD_CYCLES
add R_65c816_Cycles,byte 12 ; IRQ processing: 2 IO
JUMP_NOT_FLAG SNES_FLAG_E,.native_irq
call E1_IRQ
jmp .irq_return
ALIGNC
.native_irq:
call E0_IRQ
.irq_return:
SAVE_CYCLES
.irq_disabled:
pop edi
.no_irq:
call IRQ_Check_Late ; chain
LOAD_CYCLES
test R_Cycles,R_Cycles
jl .no_chain
jmp dword [Event_Handler]
.no_chain:
jmp CPU_START ; Return to CPU
ALIGNC
DMA_Event:
section .data
.str:db " DMA ",0
section .text
report_event
mov byte [CPU_Execution_Mode],CEM_In_DMA
mov eax,[DMA_Next_Event]
mov [Fixed_Event],eax
mov eax,[DMA_Next_Trip]
mov [FixedTrip],eax
call IRQ_Check_Late ; chain
LOAD_CYCLES
test R_Cycles,R_Cycles
jl .no_chain
jmp dword [Event_Handler]
.no_chain:
jmp CPU_START ; Return to CPU
ALIGNC
Render_Event:
section .data
.str:db " render ",0
section .text
report_event
mov eax,[C_LABEL(EventTrip)]
mov [Last_Trip],eax
mov eax,[Render_Next_Event]
;mov [Event_Handler],eax
mov [Fixed_Event],eax
mov eax,[Render_Next_Trip]
;mov [C_LABEL(EventTrip)],eax
mov [FixedTrip],eax
RenderScanline
call IRQ_Check_Late ; chain
LOAD_CYCLES
test R_Cycles,R_Cycles
jl .no_chain
jmp dword [Event_Handler]
.no_chain:
jmp CPU_START ; Return to CPU
ALIGNC
speed_cap_wait_hlt:
hlt
jmp IRQNewFrame.speed_cap_wait
ALIGNC
EXPORT IRQNewFrame ; Check IRQ, Frame Skip
section .data
.str:db " new frame ",0
section .text
report_event
%ifdef DELAY_FRAMES
push dword DELAY_FRAMES
EXTERN_C rest
call C_LABEL(rest)
add esp,4
%endif
mov dword [Last_Trip],0
; Force SPC to catch up to eliminate lags and improve sound
Update_Cycles Execute
; Update SPC timers to prevent overflow
Update_SPC_Timer 0
Update_SPC_Timer 1
Update_SPC_Timer 2
call C_LABEL(update_sound) ; Added by Butcha
;mov dword [C_LABEL(EventTrip)],CYCLES_HDMA_START
mov dword [FixedTrip],CYCLES_HDMA_START
;mov dword [Event_Handler],HDMA_Event
mov dword [Fixed_Event],HDMA_Event
mov al,[HVBJOY]
and al,~HVBJOY_IN_VBLANK ; VBlank off
mov [HVBJOY],al
mov byte [RDNMI_mask],0xFF
mov byte [RDNMI],VERSION_NUMBER_5A22 ; Clear NMI enabled bit in 0x4210
mov byte [NMI_pin],NMI_None ;clear NMI input
mov al,[C_LABEL(INIDISP)]
test al,al
js .forced_blank
mov eax,[C_LABEL(OAMAddress_VBL)]
; Restore OAM address and reset OAM read/write odd/even select
; every Vblank, unless we're in forced blank
mov [C_LABEL(OAMAddress)],eax
xor eax,eax
mov [OAMHigh],al
.forced_blank:
xor eax,eax
dec dword [FrameCount] ; Should we redraw the screen this frame?
mov [C_LABEL(Current_Line_Timing)],eax ; Reset scanline counter
mov edi,C_LABEL(IRQFirst)
jnz .no_redraw
; this frame not skipped - determine how many frames following to skip
inc dword [FPSCount] ; For FPS counter - 0.25b14
mov edi,C_LABEL(IRQFirstRender)
; If fast-forward is on, skip the speed-throttle logic
EXTERN_C fast_forward_enabled
push ecx
push edx
call C_LABEL(fast_forward_enabled)
pop edx
pop ecx
test al,al
mov eax,0
jnz .fast_forward
; If minimum frameskip set, don't wait for timer to tell us to draw one
cmp [C_LABEL(FRAME_SKIP_MIN)],eax
jnz .no_speed_cap
; Wait for timer to tell us to draw a frame
.speed_cap_wait:
mov ebx,[C_LABEL(Timer_Counter_Throttle)]
test ebx,ebx
; HLT should be more multi-tasking friendly but appears
; to cause problems with pure DOS in some cases?
jz .speed_cap_wait ;speed_cap_wait_hlt
.no_speed_cap:
mov ebx,[C_LABEL(Timer_Counter_Throttle)]
cmp ebx,[C_LABEL(FRAME_SKIP_MAX)]
jb .no_cap_max_skip
.fast_forward:
mov ebx,[C_LABEL(FRAME_SKIP_MAX)]
mov [C_LABEL(Timer_Counter_Throttle)],eax ; Reset speed-throttle timer
jmp .have_skip_count
.no_cap_max_skip:
sub [C_LABEL(Timer_Counter_Throttle)],ebx ; Update speed-throttle timer
cmp ebx,[C_LABEL(FRAME_SKIP_MIN)]
ja .no_cap_min_skip
mov ebx,[C_LABEL(FRAME_SKIP_MIN)]
.no_cap_min_skip:
.have_skip_count:
mov [FrameCount],ebx ; Reset frame counter
%ifdef DEBUG
inc dword [C_LABEL(Frames)]
%if FRAME_LIMIT
cmp dword [C_LABEL(Frames)],FRAME_LIMIT
jne .no_limit
%if FRAME_LIMIT_PROFILE
EXTERN_C Profiler_Start
mov byte [C_LABEL(Profiler_Start)],1
%else
ret
%endif
.no_limit:
%endif
%endif
mov al,[C_LABEL(INIDISP)]
and al,0x0F
cmp al,[C_LABEL(BrightnessLevel)]
je .same_brightness
mov byte [C_LABEL(PaletteChanged)],1
mov [C_LABEL(BrightnessLevel)],al
.same_brightness:
.no_redraw:
mov [HDMA_Next_Event],edi
xor byte [STAT78],0x80 ; Toggle current field
%ifdef TRAP_HDMA_RELATCH
pusha
push byte 0
call C_LABEL(Dump_DMA)
pop edi
popa
RELATCH_HDMA
pusha
push byte 1
call C_LABEL(Dump_DMA)
pop edi
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -