ANESE/roms/tests/ppu/nmi_sync/nmi_sync.s
2017-10-21 17:33:02 -07:00

185 lines
3 KiB
ArmAsm
Vendored

; Allows precise PPU synchronization in NMI handler, without
; having to cycle-time code outside NMI handler.
.zeropage
nmi_sync_count: .res 1
.code
.align 256 ; branches must not cross page
; Initializes synchronization and enables NMI
; Preserved: X, Y
; Time: 15 frames average, 28 frames max
init_nmi_sync:
; Disable interrupts and rendering
sei
lda #0
sta $2000
sta $2001
; Coarse synchronize
bit $2002
init_nmi_sync_1:
bit $2002
bpl init_nmi_sync_1
; Synchronize to odd CPU cycle
sta $4014
; Fine synchronize
lda #3
init_nmi_sync_2:
sta nmi_sync_count
bit $2002
bit $2002
php
eor #$02
nop
nop
plp
bpl init_nmi_sync_2
; Delay one frame
init_nmi_sync_3:
bit $2002
bpl init_nmi_sync_3
; Enable rendering long enough for frame to
; be shortened if it's a short one, but not long
; enough that background will get displayed.
lda #$08
sta $2001
; Can reduce delay by up to 5 and this still works,
; so there's a good margin.
; delay 2377
lda #216
init_nmi_sync_4:
nop
nop
sec
sbc #1
bne init_nmi_sync_4
sta $2001
lda nmi_sync_count
; Wait for this and next frame to finish.
; If this frame was short, loop ends. If it was
; long, loop runs for a third frame.
init_nmi_sync_5:
bit $2002
bit $2002
php
eor #$02
sta nmi_sync_count
nop
nop
plp
bpl init_nmi_sync_5
; Enable NMI
lda #$80
sta $2000
rts
; Initializes synchronization and enables NMI on PAL NES
; Preserved: X, Y
; Time: about 20 frames
init_nmi_sync_pal:
; NMI will first occur within frame 2 after
; synchronization
lda #2
sta nmi_sync_count
; Disable interrupts and rendering
sei
lda #0
sta $2000
sta $2001
; Coarse synchronize
bit $2002
init_nmi_sync_pal_1:
bit $2002
bpl init_nmi_sync_pal_1
; Synchronize to odd CPU cycle
sta $4014
bit <0
; Fine synchronize
init_nmi_sync_pal_2:
bit <0
nop
bit $2002
bit $2002
bpl init_nmi_sync_pal_2
; Enable NMI
lda #$80
sta $2000
rts
; Waits until NMI occurs.
; Preserved: A, X, Y
wait_nmi:
pha
; Reset high/low flag so NMI can depend on it
bit $2002
; NMI must not occur during taken branch, so we
; only use branch to get out of loop.
lda nmi_sync_count
wait_nmi_1:
cmp nmi_sync_count
bne wait_nmi_2
jmp wait_nmi_1
wait_nmi_2:
pla
rts
; Must be called in NMI handler, before sprite DMA.
; Preserved: X, Y
begin_nmi_sync:
lda nmi_sync_count
and #$02
beq begin_nmi_sync_1
begin_nmi_sync_1:
rts
; Must be called after sprite DMA. Instructions before this
; must total 1715 (NTSC)/6900 (PAL) cycles, treating
; JSR begin_nmi_sync and STA $4014 as taking 10 cycles total)
; Next instruction will begin 2286 (NTSC)/7471 (PAL) cycles
; after the cycle that the frame began in.
; Preserved: X, Y
end_nmi_sync:
lda nmi_sync_count
inc nmi_sync_count
and #$02
bne end_nmi_sync_1
end_nmi_sync_1:
lda $2002
bmi end_nmi_sync_2
end_nmi_sync_2:
bmi end_nmi_sync_3
end_nmi_sync_3:
rts
; Keeps track of synchronization on frames where no
; synchronization is needed (where begin_nmi_sync/end_nmi_sync
; aren't called).
; Preserved: A, X, Y
track_nmi_sync:
inc nmi_sync_count
rts