www.pudn.com > sn068s.zip > TIMING.NI


;%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-223/239, dots   0- 21: Hblank 
; Scanline 1-223/239, dots  22-277: Displayed screen 
; Scanline 1-223/239, dots 278-339: Hblank, HDMA start 
; Scanline 224/240: Hblank, HDMA start 
 
; 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 ZERO_TRIP_CYCLE_COUNTER 
 
%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.ni" 
%include "screen.ni" 
%include "DMA.ni" 
%include "SPC.ni" 
%include "sprites.ni" 
%include "scankeys.ni" 
%include "key.ni" 
%include "cycles.ni" 
 
extern _FPSTicks 
extern _UPDATE_KEYS 
 
%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 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 
 
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   ; Fixed event following DMA 
Render_Next_Event:  skipl   ; Fixed event following render 
Render_Next_Trip:   skipl   ; Fixed event following render 
NMI_Event_Handler:  skipl   ; Event handler for NMI 
Vblank_Start:       skipl   ; Scanline of last Vblank start 
EXPORT HTimer           ,skipl  ; Clock cycle position of H-IRQ delay start 
 
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 
 
MEMSEL:     skipl   ; FastROM switch 
Latched_H:  skipl   ; These two are latched values! 
Latched_V:  skipl 
 
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 
EXPORT_C NMITIMEN,skipb ; a0yx000b  a=NMI on/off,y=vert count,x=horiz count,b=joy read 
EXPORT HVBJOY,skipb 
 
section .text 
 
;R_Cycles = _EventTrip - _SNES_Cycles, trips on <= zero 
 
;%1 = Execute 
%macro Update_Cycles 0-1 0 
 mov eax,[_SNES_Cycles]     ; Update CPU and SPC cycles 
 
 mov edi,[_SNES_Cycles] 
 sub edi,[_EventTrip] 
 
 cmp eax,CYCLES_REFRESH_START 
 jb %%before_refresh 
 add eax,byte CYCLES_IN_REFRESH 
%%before_refresh: 
 
 sub eax,[SPC_last_cycles] 
 mov [_SNES_Cycles],edi 
 mov [SPC_last_cycles],edi 
 
%ifidni %1,Execute 
 cmp byte [_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,[_FPSMaxTicks] 
 xor eax,eax 
 cmp [_FPSTicks],edx 
 jb %%no_update 
 mov edx,[FPSCount] 
 je %%fps_update 
 cmp edx,byte 1 
 ja %%fps_update 
 mov dword [_FPSLast],0 
 jmp short %%less_than_1_fps 
%%fps_update: 
 mov [_FPSLast],edx 
%%less_than_1_fps: 
 mov [_FPSTicks],eax 
 mov [FPSCount],eax 
ALIGNC 
%%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 [_EventTrip],CYCLES_HBLANK_START 
 mov dword [FixedTrip],CYCLES_HBLANK_START 
 mov dword [Event_Handler],HDMA_Event 
 mov dword [Fixed_Event],HDMA_Event 
 mov dword [HDMA_Next_Event],IRQFirstRender 
 
 ; Reset frameskip counter 
 mov ebx,1 
 cmp ebx,[_FRAME_SKIP_MIN] 
 ja .no_fix_framecount 
 mov ebx,[_FRAME_SKIP_MIN] 
.no_fix_framecount: 
 mov [FrameCount],ebx 
 
 xor eax,eax 
 mov [_Timer_Counter_Throttle],eax  ; Reset speed-throttle timer 
 mov [_Current_Line_Timing],eax  ; Reset scanline counter 
 inc dword [FPSCount]           ; For FPS counter - 0.25b14 
 
 RELATCH_HDMA 
 Update_FPS_Counter 
 ret 
 
ALIGNC 
HDMA_Event: 
 mov eax,[_EventTrip] 
 mov [Last_Trip],eax 
 
 mov eax,[HDMA_Next_Event] 
 mov dword [_EventTrip],CYCLES_NEW_SCANLINE 
 mov dword [FixedTrip],CYCLES_NEW_SCANLINE 
 mov [Event_Handler],eax 
 mov [Fixed_Event],eax 
 call do_HDMA 
 call HIRQ_Check_Late   ; chain 
 
 LOAD_CYCLES 
 test R_Cycles,R_Cycles 
 jl .no_irq 
 jmp dword [Event_Handler] 
.no_irq: 
 
 jmp near CPU_START     ; Return to CPU 
 
ALIGNC 
DMA_Event: 
 mov eax,[_EventTrip] 
 mov [Last_Trip],eax 
 
 mov eax,[DMA_Next_Event] 
 mov [Event_Handler],eax 
 mov [Fixed_Event],eax 
 mov eax,[DMA_Next_Trip] 
 mov [_EventTrip],eax 
 mov [FixedTrip],eax 
;do DMA here 
 call HIRQ_Check_Late   ; chain 
 
 LOAD_CYCLES 
 test R_Cycles,R_Cycles 
 jl .no_irq 
 jmp dword [Event_Handler] 
.no_irq: 
 
 jmp near CPU_START     ; Return to CPU 
 
ALIGNC 
Render_Event: 
 mov eax,[_EventTrip] 
 mov [Last_Trip],eax 
 
 mov eax,[Render_Next_Event] 
 mov [Event_Handler],eax 
 mov [Fixed_Event],eax 
 mov eax,[Render_Next_Trip] 
 mov [_EventTrip],eax 
 mov [FixedTrip],eax 
 RenderScanline 
 call HIRQ_Check_Late   ; chain 
 
 LOAD_CYCLES 
 test R_Cycles,R_Cycles 
 jl .no_irq 
 jmp dword [Event_Handler] 
.no_irq: 
 
 jmp near CPU_START     ; Return to CPU 
 
ALIGNC 
speed_cap_wait_hlt: 
 hlt 
 jmp near IRQNewFrame.speed_cap_wait 
ALIGNC 
EXPORT IRQNewFrame              ; Check IRQ, Frame Skip 
%ifdef DELAY_FRAMES 
 push dword DELAY_FRAMES 
extern _rest 
 call _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 _update_sound     ; Added by Butcha 
 
 mov dword [_EventTrip],CYCLES_HBLANK_START 
 mov dword [FixedTrip],CYCLES_HBLANK_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],VERSION_NUMBER_5A22   ; Clear NMI enabled bit in 0x4210 
 
 mov al,[_INIDISP] 
 test al,al 
 js .forced_blank 
 mov eax,[_OAMAddress_VBL] 
 ; Restore OAM address and reset OAM read/write odd/even select 
 ;  every Vblank, unless we're in forced blank 
 mov [_OAMAddress],eax 
 xor eax,eax 
 mov [OAMHigh],al 
.forced_blank: 
 
 xor eax,eax 
 dec dword [FrameCount] ; Should we redraw the screen this frame? 
 mov [_Current_Line_Timing],eax ; Reset scanline counter 
 mov edi,IRQFirst 
 jnz near .no_redraw 
 
 ; this frame not skipped - determine how many frames following to skip 
 inc dword [FPSCount]   ; For FPS counter - 0.25b14 
 mov edi,IRQFirstRender 
 
; If fast-forward is on, skip the speed-throttle logic 
 JUMP_KEY_DOWN KEY_TILDE,.fast_forward,al 
 
; If minimum frameskip set, don't wait for timer to tell us to draw one 
 cmp [_FRAME_SKIP_MIN],eax 
 jnz .no_speed_cap 
 
; Wait for timer to tell us to draw a frame 
.speed_cap_wait: 
 mov ebx,[_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,[_Timer_Counter_Throttle] 
 cmp ebx,[_FRAME_SKIP_MAX] 
 jb .no_cap_max_skip 
 
.fast_forward: 
 mov ebx,[_FRAME_SKIP_MAX] 
 mov [_Timer_Counter_Throttle],eax  ; Reset speed-throttle timer 
 jmp short .have_skip_count 
 
.no_cap_max_skip: 
 sub [_Timer_Counter_Throttle],ebx  ; Update speed-throttle timer 
 
 cmp ebx,[_FRAME_SKIP_MIN] 
 ja .no_cap_min_skip 
 mov ebx,[_FRAME_SKIP_MIN] 
 
.no_cap_min_skip: 
.have_skip_count: 
 mov [FrameCount],ebx   ; Reset frame counter 
 
%ifdef DEBUG 
 inc dword [_Frames] 
 
%if FRAME_LIMIT 
 cmp dword [_Frames],FRAME_LIMIT 
 jne .no_limit 
 ret 
.no_limit: 
%endif 
%endif 
 
 mov al,[_INIDISP] 
 and al,0x0F 
 cmp al,[_BrightnessLevel] 
 je .same_brightness 
 mov byte [_PaletteChanged],1 
 mov [_BrightnessLevel],al 
.same_brightness: 
 
.no_redraw: 
 mov [HDMA_Next_Event],edi 
 
 xor byte [STAT78],0x80 ; Toggle current field 
 
 RELATCH_HDMA 
 
 Update_FPS_Counter 
 
 pusha 
 call _update_sound_block 
 popa 
 
 xor eax,eax 
 jmp near CPU_START_IRQ ; Return to CPU 
 
ALIGNC 
EXPORT_C IRQFirstRender 
IRQFirstRender: ; Check HDMA, IRQ, Render 
 mov dword [Last_Trip],0 
 
 mov dword [_EventTrip],CYCLES_DISPLAY_START 
 mov dword [FixedTrip],CYCLES_DISPLAY_START 
 mov dword [Event_Handler],Render_Event 
 mov dword [Fixed_Event],Render_Event 
 mov dword [Render_Next_Event],HDMA_Event 
 mov dword [Render_Next_Trip],CYCLES_HBLANK_START 
 mov dword [HDMA_Next_Event],IRQRender 
 
 Render_Start_Frame ; Reset framebuffer render address 
 
 Update_Cycles 
 
 mov eax,[_Current_Line_Timing]  ; Get current scanline 
 inc eax 
 mov [_Current_Line_Timing],eax 
 jmp near CPU_START_IRQ ; Return to CPU 
 
ALIGNC 
EXPORT_C IRQRender 
IRQRender:      ; Check HDMA, IRQ, Render 
 mov dword [Last_Trip],0 
 
 Update_Cycles 
 
 mov dword [_EventTrip],CYCLES_DISPLAY_START 
 mov dword [FixedTrip],CYCLES_DISPLAY_START 
 mov dword [Event_Handler],Render_Event 
 mov dword [Fixed_Event],Render_Event 
 
 mov eax,[_Current_Line_Timing] ; Get current scanline 
 inc eax 
 mov [_Current_Line_Timing],eax 
 cmp [_LastRenderLine],eax 
 jbe .end_of_display 
 jmp near CPU_START_IRQ ; Return to CPU 
ALIGNC 
.end_of_display: 
 mov dword [NMI_Event_Handler],NMI 
 mov dword [Render_Next_Event],VBL_First_Line 
 mov dword [Render_Next_Trip],CYCLES_NEW_SCANLINE 
 jmp near CPU_START_IRQ ; Return to CPU 
 
ALIGNC 
VBL_First_Line: 
 mov dword [Last_Trip],0 
 
 Update_Cycles 
 
 mov al,[HVBJOY] 
 ; VBlank on, controllers being read 
 or al,HVBJOY_IN_VBLANK | HVBJOY_CONTROLLERS_BUSY 
 mov [HVBJOY],al 
 
 mov al,RDNMI_VBLANK_START | VERSION_NUMBER_5A22 
 mov [RDNMI],al         ; Set NMI enabled bit in 0x4210 
 
 mov eax,[NMI_Event_Handler] 
 mov dword [_EventTrip],CYCLES_NEW_SCANLINE 
 mov dword [FixedTrip],CYCLES_NEW_SCANLINE 
 mov [Event_Handler],eax 
 mov [Fixed_Event],eax 
 
 mov eax,[_Current_Line_Timing] ; Get current scanline 
 inc eax 
 mov [_Current_Line_Timing],eax 
 mov [Vblank_Start],eax 
 
 jmp near CPU_START_IRQ ; Return to CPU 
 
ALIGNC 
VBL_Update_Controllers: 
 mov dword [Last_Trip],0 
 
 Update_Cycles 
 
 mov eax,[_Current_Line_Timing] 
 inc eax 
 mov [_Current_Line_Timing],eax 
 sub eax,[Vblank_Start] 
 cmp eax,byte 3 
 jb .no_controller_update 
 
 call UPDATE_CONTROLLERS 
 mov dword [Event_Handler],VBL 
 mov dword [Fixed_Event],VBL 
 and byte [HVBJOY],~HVBJOY_CONTROLLERS_BUSY ; Controllers ready 
.no_controller_update: 
 mov eax,[_Current_Line_Timing] ; Get current scanline 
 jmp near CPU_START_IRQ ; Return to CPU 
 
ALIGNC 
VBL: 
 mov dword [Last_Trip],0 
 
 Update_Cycles 
 
 mov eax,[_Current_Line_Timing] ; Get current scanline 
 inc eax 
 mov [_Current_Line_Timing],eax 
 cmp [_LastVBLLine],eax ; Needs adjustment for odd line 
 ja .not_end_of_frame 
 mov dword [Event_Handler],IRQNewFrame 
 mov dword [Fixed_Event],IRQNewFrame 
.not_end_of_frame: 
 jmp near CPU_START_IRQ ; Return to CPU 
 
ALIGNC 
EXPORT_C IRQFirst 
IRQFirst:       ; Check HDMA, IRQ, Render 
 mov dword [Last_Trip],0 
 
 mov dword [_EventTrip],CYCLES_HBLANK_START 
 mov dword [FixedTrip],CYCLES_HBLANK_START 
 mov dword [Event_Handler],HDMA_Event 
 mov dword [Fixed_Event],HDMA_Event 
 mov dword [HDMA_Next_Event],IRQNoRender 
 
 Render_Start_Frame_Skipped 
 
 Update_Cycles 
 
 mov eax,[_Current_Line_Timing] ; Get current scanline 
 inc eax 
 mov [_Current_Line_Timing],eax 
 jmp near CPU_START_IRQ ; Return to CPU 
 
ALIGNC 
IRQNoRender:            ; Check HDMA, IRQ 
 mov dword [Last_Trip],0 
 
 Update_Cycles 
 
 mov eax,[_Current_Line_Timing] ; Get current scanline 
 inc eax 
 mov [_Current_Line_Timing],eax 
 cmp [_LastRenderLine],eax 
 jbe .end_of_display 
 mov dword [_EventTrip],CYCLES_HBLANK_START 
 mov dword [FixedTrip],CYCLES_HBLANK_START 
 mov dword [Event_Handler],HDMA_Event 
 mov dword [Fixed_Event],HDMA_Event 
 jmp near CPU_START_IRQ ; Return to CPU 
ALIGNC 
.end_of_display: 
 mov dword [NMI_Event_Handler],NMI_NoRender 
 mov dword [Event_Handler],VBL_First_Line 
 mov dword [Fixed_Event],VBL_First_Line 
 jmp near CPU_START_IRQ ; Return to CPU 
 
ALIGNC 
EXPORT_C NMI 
EXPORT NMI 
 SyncRender  ;* 
 mov ax,[_Real_SNES_Palette] 
 push eax 
 
 mov al,[_fixedpalettecheck] 
 test al,al 
 jz .Do_Copy 
 
.Fixed_Back_Color_Add_Hack: 
%if 0 
 push ebx 
 mov ax,[_COLDATA] 
 mov bx,[_Real_SNES_Palette] 
 and eax,~((1<<5)+(1<<10)+(1<<15))  ; remove 'carry' bits 
 and ebx,~((1<<5)+(1<<10)+(1<<15))  ; remove 'carry' bits 
 add eax,ebx 
 test eax,((1<<5)+(1<<10)+(1<<15)) 
 jz .Add_Hack_End 
 ; Saturation 
 test al,0x20 
 jz .no_blue_saturation 
 or al,0x1F 
.no_blue_saturation: 
 test ah,0x04 
 jz .no_green_saturation 
 or eax,0x3E0 
.no_green_saturation: 
 test ah,0x80 
 jz .no_blue_saturation 
 or ah,0x7C 
.no_blue_saturation: 
 and eax,~((1<<5)+(1<<10)+(1<<15))  ; remove 'carry' bits 
.Add_Hack_End: 
 pop ebx 
 mov [_Real_SNES_Palette],ax 
%endif 
 mov ax,[_COLDATA] 
 mov [_Real_SNES_Palette],ax 
 
 mov byte [_PaletteChanged],1 
 
.Do_Copy: 
 cmp byte [_FPS_ENABLED],0 
 jz .no_fps_counter 
%ifndef SNEeSe_No_GUI 
 call _ShowFPS 
%endif 
.no_fps_counter: 
 cmp byte [_BREAKS_ENABLED],0 
 jz .no_breaks_counter 
%ifndef SNEeSe_No_GUI 
 call _ShowBreaks 
%endif 
.no_breaks_counter: 
 mov dword [_BreaksLast],0 
%ifndef SINGLE_STEP 
 call _Copy_Screen 
%endif 
;call _Display_Debug 
 
 pop eax 
 mov [_Real_SNES_Palette],ax 
EXPORT NMI_NoRender 
EXPORT_C NMI_NoRender 
 mov dword [Last_Trip],0 
 
 mov dword [Event_Handler],VBL_Update_Controllers 
 mov dword [Fixed_Event],VBL_Update_Controllers 
 Update_Cycles 
 
 mov eax,[_Current_Line_Timing] 
 inc eax 
 mov [_Current_Line_Timing],eax 
CheckNMI: 
%ifdef DEBUG 
 mov al,[_PB] 
 mov [_OLD_PB],al       ; Save program bank 
 mov eax,[_PC] 
 mov [_OLD_PC],eax      ; Save program counter 
%endif 
 test byte [_NMITIMEN],0x80 
 jz .CHECK_KEYS         ; NMI off? (via hardware register) 
 
 ; Neill's timing doc: 19-23 clock delay 
 mov dword [_EventTrip],CYCLES_NMI_DELAY 
 mov dword [FixedTrip],CYCLES_NMI_DELAY 
 mov dword [Event_Handler],NMI_Event 
 mov dword [Fixed_Event],NMI_Event 
 
.CHECK_KEYS: 
 call _UPDATE_KEYS 
 test eax,eax 
 jnz .break 
 
; Return to emulation 
 mov eax,[_Current_Line_Timing] ; Get current scanline 
 jmp near CPU_START_IRQ ; Return to CPU 
 
.break: 
;pusha 
 mov ebx,[_PB_Shifted] 
 mov bx,[_PC] 
 
 GET_BYTE 
 mov [_Map_Byte],al 
 
;popa 
 
 mov eax,[_Current_Line_Timing] ; Get current scanline 
 CheckVIRQ 
 ret 
 
ALIGNC 
NMI_Event: 
 mov eax,[_EventTrip] 
 mov [Last_Trip],eax 
 
 mov dword [_EventTrip],CYCLES_NEW_SCANLINE 
 mov dword [FixedTrip],CYCLES_NEW_SCANLINE 
 mov dword [Event_Handler],VBL_Update_Controllers 
 mov dword [Fixed_Event],VBL_Update_Controllers 
 
 mov al,[CPU_Execution_Mode] 
 cmp al,CEM_Waiting_For_Interrupt 
 jne .no_wai 
 
 ; WAI delay after interrupt signal: 2 IO 
;add dword [_SNES_Cycles],byte 12   ;* 
 
 inc word [_PC] 
 
 mov byte [CPU_Execution_Mode],CEM_Normal_Execution 
 
.no_wai: 
 push edx 
 LOAD_BASE 
%ifdef FAST_STACK_ACCESS_EMULATION_MODE 
 JUMP_NOT_FLAG SNES_FLAG_E,E0_NMI,near  ; Are we in emulation mode? 
%else 
 JUMP_NOT_FLAG SNES_FLAG_E,E0_NMI,near  ; Are we in emulation mode? 
%endif 
 
E1_NMI: 
 ; Emulation mode NMI 
 mov eax,[_PC] 
 E1_PUSH_W 
;CLR_FLAG SNES_FLAG_B   ; Clear break bit on stack 
 E1_SETUPFLAGS 0        ; put flags into SNES packed flag format 
;SET_FLAG SNES_FLAG_B 
 E1_PUSH_B 
 
 mov eax,[_NMI_Evector] ; Get Emulation mode NMI vector 
 mov ebx,[_NMI_Eoffset] 
 jmp NMI_completion 
 
ALIGNC 
E0_NMI: 
 add dword [_SNES_Cycles],byte 8    ; NMI processing: +1 bank0 for E=0 
 
 ; Native mode NMI 
 mov al,[_PB] 
 E0_PUSH_B 
 mov eax,[_PC] 
 E0_PUSH_W 
 E0_SETUPFLAGS          ; put flags into SNES packed flag format 
 E0_PUSH_B 
 
 mov eax,[_NMI_Nvector] ; Get Native mode NMI vector 
 mov ebx,[_NMI_Noffset] 
NMI_completion: 
 mov [_PC],eax          ; Setup PC vector 
 mov [_PBOffset],ebx 
 mov byte [_PB],0       ; Setup bank 
 add dword [_SNES_Cycles],byte 52   ; NMI processing: +1 bank0 for E=0 
;SET_FLAG SNES_FLAG_I   ; Disable IRQs 
 STORE_FLAGS_I 1 
 mov byte [CycleTable],4    ; SlowROM bank 
;CLR_FLAG SNES_FLAG_D   ; Disable decimal mode 
 STORE_FLAGS_D 0 
 pop edx 
 call HIRQ_Check_Late   ; chain 
 
 LOAD_CYCLES 
 test R_Cycles,R_Cycles 
 jl .no_irq 
 jmp dword [Event_Handler] 
.no_irq: 
 
 jmp near CPU_START 
 
ALIGNC 
VIRQ_Event: 
 mov eax,[_EventTrip] 
 mov [Last_Trip],eax 
 
 mov dh,0x80        ; V-IRQ 
 jmp short HIRQ_Event.do_IRQ 
 
ALIGNC 
HIRQ_Event: 
 mov eax,[_EventTrip] 
 mov [Last_Trip],eax 
 
 mov dh,0x40        ; H-IRQ 
;test dl,0x20      ; Vertical IRQ enabled? 
;jz .no_virq 
;mov dh,0xC0       ; H+V-IRQ 
.do_IRQ: 
;mov dh,[_NMITIMEN] 
;and dh,0x30 
;shl dh,2 
;mov byte [TIMEUP],0x80 ; Set IRQ recv'd flag 
 or [IRQ_pin],dh 
 
 mov al,[CPU_Execution_Mode] 
 cmp al,CEM_Waiting_For_Interrupt 
 jne .no_wai 
 
 ; WAI delay after interrupt signal: 2 IO 
;add dword [_SNES_Cycles],byte 12   ;* 
 
 inc word [_PC] 
 
 mov byte [CPU_Execution_Mode],CEM_Normal_Execution 
 
.no_wai: 
 push edx 
 LOAD_BASE 
 JUMP_FLAG SNES_FLAG_I,.irq_disabled    ; Interrupts disabled? 
 JUMP_NOT_FLAG SNES_FLAG_E,.native_irq 
 add dword [_SNES_Cycles],byte 52   ; IRQ processing: 2 IO + 5 bank0 
 call E1_IRQ 
 jmp .irq_return 
ALIGNC 
.native_irq: 
 add dword [_SNES_Cycles],byte 60   ; IRQ processing: 2 IO + 6 bank0 
 call E0_IRQ 
.irq_return: 
.irq_disabled: 
 pop edx 
.no_irq: 
 mov edi,[FixedTrip] 
 mov ebx,[Fixed_Event] 
 mov [_EventTrip],edi 
 mov [Event_Handler],ebx 
;call HIRQ_Check_Late   ; chain 
 jmp near CPU_START 
 
ALIGNC 
; This can only trash ebx/edi! 
HIRQ_Check: 
 cmp byte [In_CPU],0 
 jz .not_in_cpu 
 GET_CYCLES edi 
 jmp short .got_cycles 
.not_in_cpu: 
 mov edi,[_SNES_Cycles] 
.got_cycles: 
 push edi 
 mov bl,[_NMITIMEN] 
 test bl,0x10       ; H-IRQ enabled? 
 jz .no_irq 
 test bl,0x20       ; V-IRQ enabled? 
 jz .no_virq 
 mov edi,[_Current_Line_Timing] 
 cmp [_VTIMEL],edi 
 jne .no_irq 
.no_virq: 
 mov ebx,[HTimer] 
 mov edi,[FixedTrip] 
 cmp [esp],ebx      ; Check against current cycle 
 jae .past_irq      ; Are we after the IRQ position? 
 cmp edi,ebx 
 jb .past_irq       ; Next static event is before IRQ? 
 mov [_EventTrip],ebx 
 mov dword [Event_Handler],HIRQ_Event 
 jmp short .no_irq 
.past_irq: 
 mov ebx,[Fixed_Event] 
 mov [_EventTrip],edi 
 mov [Event_Handler],ebx 
.no_irq: 
 pop edi 
 ret 
 
ALIGNC 
; This can only trash ebx/edi! 
HIRQ_Check_Late: 
 mov bl,[_NMITIMEN] 
 test bl,0x10       ; H-IRQ enabled? 
 jz .no_irq 
 test bl,0x20       ; V-IRQ enabled? 
 jz .no_virq 
 mov edi,[_Current_Line_Timing] 
 cmp [_VTIMEL],edi 
 jne .no_irq 
.no_virq: 
 mov ebx,[HTimer] 
 mov edi,[FixedTrip] 
 cmp [Last_Trip],ebx    ; Check against last event trip cycle 
 jae .past_irq      ; Are we after the IRQ? 
 cmp edi,ebx 
 jb .past_irq       ; Next static event is before IRQ? 
 mov [_EventTrip],ebx 
 mov dword [Event_Handler],HIRQ_Event 
 jmp short .no_irq 
.past_irq: 
 mov ebx,[Fixed_Event] 
 mov [_EventTrip],edi 
 mov [Event_Handler],ebx 
.no_irq: 
 ret 
 
ALIGNC 
EXPORT SNES_R2137 ; SLHV  ; This latches the counter for horizontal/vertical data! 
 push ebx 
 and byte [IRQ_pin],0x1F 
 cmp byte [In_CPU],0 
 jz .not_in_cpu 
 GET_CYCLES ebx 
 jmp short .got_cycles 
.not_in_cpu: 
 mov ebx,[_SNES_Cycles] 
.got_cycles: 
 
 ; Set up H counters to cycles executed / 4, V counter to current scanline 
 ; All this below ensures bits 9-15 to be same as bits 1-7 
 shr ebx,2 
 mov al,0xFE 
 cmp ebx,128 
 jb .before_refresh 
 add ebx,byte 10    ; Adjust for memory refresh cycles 
.before_refresh: 
 and bh,1 
 and al,bl 
 or bh,al 
 mov [Latched_H],ebx    ; Cycles / 4 = rough H counter 
 
 mov ebx,[_Current_Line_Timing] 
 mov al,0xFE 
 and bh,1 
 and al,bl 
 or bh,al 
 mov [Latched_V],ebx 
 
 mov al,0 
 pop ebx 
 mov [OPHCT],al 
 mov [OPVCT],al 
 ret 
 
ALIGNC 
EXPORT SNES_R213C ; OPHCT 
 xor byte [OPHCT],1 
 jz .return_high 
 mov al,[Latched_H] 
 ret 
.return_high: 
 mov al,[Latched_H+1] 
 ret 
 
ALIGNC 
EXPORT SNES_R213D ; OPVCT 
 xor byte [OPVCT],1 
 jz .return_high 
 mov al,[Latched_V] 
 ret 
.return_high: 
 mov al,[Latched_V+1] 
 ret 
 
ALIGNC 
EXPORT SNES_R4200 ; NMITIMEN 
 mov al,[_NMITIMEN] 
 ret 
 
ALIGNC 
EXPORT SNES_R4210 ; RDNMI 
 mov al,[RDNMI] 
 mov byte [RDNMI],VERSION_NUMBER_5A22   ; NMI on/off + version # 
 ret 
 
ALIGNC 
EXPORT SNES_R4211 ; TIMEUP 
 ; Should return bit 7 set if IRQ active, bit 6 set if cur line = V count 
;mov al,[TIMEUP] 
;mov byte [TIMEUP],0 
 mov al,[IRQ_pin] 
 test al,0xC0 
 jz .no_irq 
 mov al,0x80 
.no_irq: 
 
 cmp byte [In_CPU],0 
 jz .not_in_cpu 
 GET_CYCLES edi 
 jmp short .got_cycles 
.not_in_cpu: 
 mov edi,[_SNES_Cycles] 
.got_cycles: 
 
 and byte [IRQ_pin],0x1F    ; Clear H/V IRQ 
 cmp edi,CYCLES_HBLANK_START    ; Hblank after display 
 jae .hblank 
 cmp edi,CYCLES_DISPLAY_START   ; Hblank before display 
 jae .no_hblank 
.hblank: 
 or al,HVBJOY_IN_HBLANK ; We're in Hblank... 
.no_hblank: 
 
 ret 
 
ALIGNC 
EXPORT SNES_R4212 ; HVBJOY 
; Excerpts: Neill Corlett's "SNES Timing: The Brutal Truth" Version: 1.0b 
;  <-22 dots-> <------------256 dots--------------> <-62 dots-> 
; 5. Something freaky 
; ------------------- 
; On every scanline, starting at dot 128, the CPU appears to be frozen for 40 
; master cycles.  DMA is also apparently frozen during this time.  This happens 
; whether the scanline is visible or not, and whether HDMA is enabled or not. 
; 
; My current theory is that this time is used for refreshing RAM. 
; *End excerpts* 
 cmp byte [In_CPU],0 
 jz .not_in_cpu 
 GET_CYCLES edi 
 jmp short .got_cycles 
.not_in_cpu: 
 mov edi,[_SNES_Cycles] 
.got_cycles: 
 mov al,[HVBJOY] 
 cmp edi,CYCLES_HBLANK_START    ; Hblank after display 
 jae .hblank 
 cmp edi,CYCLES_DISPLAY_START   ; Hblank before display 
 jae .no_hblank 
.hblank: 
 or al,HVBJOY_IN_HBLANK ; We're in Hblank... 
.no_hblank: 
 ret 
 
ALIGNC 
EXPORT SNES_W4200 ; NMITIMEN 
; a0yx000b  a=NMI on/off,y=vert count,x=horiz count,b=joy read 
 cmp [_NMITIMEN],al 
 je .no_change 
 push ebx 
 mov bl,[_NMITIMEN] 
 mov [_NMITIMEN],al 
 mov bh,al 
 and ebx,0x3030 
 cmp bh,bl 
 je .no_irq_change 
 call HIRQ_Check 
.no_irq_change: 
 pop ebx 
.no_change: 
 and byte [IRQ_pin],0x1F 
 ret 
 
ALIGNC 
EXPORT SNES_W4207 ; HTIMEL 
 push ebx 
 mov ebx,[_HTIMEL] 
 cmp al,bl 
 je .no_change 
 mov bl,al 
 mov [_HTIMEL],ebx 
 shl ebx,2 
 
 ; Do some magic for the memory refresh 'missing' cycles 
 cmp ebx,CYCLES_REFRESH_START   ; Before memory refresh 
 jb .not_in_refresh 
 cmp ebx,CYCLES_REFRESH_END     ; During memory refresh 
 jb .in_refresh 
 sub ebx,byte CYCLES_IN_REFRESH 
 jmp short .not_in_refresh 
ALIGNC 
.in_refresh: 
 mov ebx,CYCLES_REFRESH_START   ; During memory refresh 
.not_in_refresh: 
 mov [HTimer],ebx 
 test byte [_NMITIMEN],0x10 
 jz .no_change 
 call HIRQ_Check 
.no_change: 
 pop ebx 
 ret 
 
ALIGNC 
EXPORT SNES_W4208 ; HTIMEH 
 push eax 
 push ebx 
 mov ebx,[_HTIMEL] 
 and al,1 
 and bh,1 
 cmp al,bh 
 je .no_change 
 mov bh,al 
 mov [_HTIMEL],ebx 
 shl ebx,2 
 
 ; Do some magic for the memory refresh 'missing' cycles 
 cmp ebx,CYCLES_REFRESH_START   ; Before memory refresh 
 jb .not_in_refresh 
 cmp ebx,CYCLES_REFRESH_END     ; During memory refresh 
 jb .in_refresh 
 sub ebx,byte CYCLES_IN_REFRESH 
 jmp short .not_in_refresh 
ALIGNC 
.in_refresh: 
 mov ebx,CYCLES_REFRESH_START   ; During memory refresh 
.not_in_refresh: 
 mov [HTimer],ebx 
 test byte [_NMITIMEN],0x10 
 jz .no_change 
 call HIRQ_Check 
.no_change: 
 pop ebx 
 pop eax 
 ret 
 
ALIGNC 
EXPORT SNES_W4209 ; VTIMEL 
 push ebx 
 mov ebx,[_VTIMEL] 
 cmp al,bl 
 je .no_change 
 mov [_VTIMEL],al 
 test byte [_NMITIMEN],0x20 
 jz .no_change 
 call HIRQ_Check 
.no_change: 
 pop ebx 
 ret 
 
ALIGNC 
EXPORT SNES_W420A ; VTIMEH   ; Handled in screen core! 
 push eax 
 push ebx 
 mov ebx,[_VTIMEL] 
 and al,1 
 and bh,1 
 cmp al,bh 
 je .no_change 
 mov [_VTIMEH],al 
 test byte [_NMITIMEN],0x20 
 jz .no_change 
 call HIRQ_Check 
.no_change: 
 pop ebx 
 pop eax 
 ret 
 
ALIGNC 
EXPORT SNES_W420D ; MEMSEL    ; FastROM switch 
 push eax 
 and eax,byte 1 
;shl eax,0x17   ; Set bit 7 of bank to switch flag 
 shl eax,7      ; Set bit 7 to switch flag 
 
 test [(_cpu_65c816_PB_Shifted+2)],al   ; Check bus speed against PB 
 mov [MEMSEL],eax 
 mov al,5 
 jnz .fastrom 
 mov al,4 
.fastrom: 
 mov [CycleTable],al    ; Update bus speed 
 
 pop eax 
 ret 
 
ALIGNC 
EXPORT SNES_W4210 ; RDNMI 
 mov byte [RDNMI],VERSION_NUMBER_5A22   ; NMI on/off + version 
 ret 
 
ALIGNC 
EXPORT SNES_W4211 ; TIMEUP 
;mov byte [TIMEUP],0 
 and byte [IRQ_pin],0x1F 
 ret