Thursday, November 19, 2015

Star Rider clever FIRQ handler revealed

In furtherance of my goal to get Star Rider working with Dexter, I decoded the FIRQ handler in the Star Rider main CPU's ROM.  FIRQ stands for Fast IRQ and is an alternate interrupt available on the 6809E CPU which is "fast" because it does not push all of the registers on the stack before executing (meaning the programmer will have to assume this responsibility instead but has the option to use the registers cleverly to squeeze out some extra performance).

The Star Rider FIRQ handler is pretty nifty because it is designed to be called when the hardware blitter "special" chips have finished an operation and immediately give the blitter "special" chips more work to do.  The neat thing about this design is that the main CPU can still be doing other work while the blitter chips are running.  In previous Williams' games, such as Joust, the blitter chip halts the main CPU and no work can be done while the blitter chip is doing its thing.  With Star Rider, the hardware guys (apparently) realized that halting the main CPU was a potential waste of time and instead let the main CPU keep running while simultaneously disabling the main CPU's access to the video/DMA bus.  It's a really clever design for 1983/1984 or whenever.

Also cool is that the FIRQ handler can be called without an interrupt occurring (see $EAD9).  If called in this way, the program will mimic what the CPU does for a real FIRQ so that the RTI (return from interrupt) instruction works correctly regardless of whether there was a real FIRQ or if the program faked it.

Finally, the FIRQ handler is even more nifty because it has an optional "wait for vsync" type behavior built-in.  It can optionally check to see whether the next blit will cause visible vertical tearing and if so delay the blit until this is no longer a concern.  I am not sure if this code actually ends up getting used in production because, just from eyeballing it, I am not 100% convinced that the vertical counter hardware value matches up with the actual vertical line value (maybe it does!) and the ROM program definitely seems to be making this assumption.  Decide for yourself :)

ROM:EAD9 ; =============== S U B R O U T I N E =======================================
ROM:EAD9 ; Execute the FIRQ interrupt handler without the FIRQ interrupt firing.
ROM:EAD9 ; This appears to create an environment where RTI will act like RTS.
ROM:EAD9 ; NOTE: it appears that IRQ's are not disabled
ROM:EAD9 FakeFIRQ:                               ; CODE XREF: DoBlit:loc_0_EA90 P
ROM:EAD9                                         ; sub_0_EAB4+18 P
ROM:EAD9                 tfr     cc, a
ROM:EADB                 anda    #$7F ; ''      ; disable E flag (for subsequent RTI call)
ROM:EADD                 pshs    a               ; save CC flags (For subsequent RTI call)
ROM:EADF                 orcc    #%1000000       ; disable F flag (for subsequent RTI call)
ROM:EAE1 FIRQ:                                   ; DATA XREF: ROM:FFF6 o
ROM:EAE1                 tst     PIA_VGG_U7_PERFDIR_A ; clear PIA's IRQA'
ROM:EAE4                 dec     BlitterPendingOperationsCount ; When FIRQ gets called, it means a blitter operation just finished.
ROM:EAE4                                         ; So decrement the pending count.
ROM:EAE7                 beq     FIRQ_End        ; branch if we have no more blitter operations left
ROM:EAE9                 pshs    a,b,x           ; save regs (since this is FIRQ, they won't be automatically saved)
ROM:EAEB                 ldx     BlitterContextPtr ; points to the current blitter context to be sent to the blitter hardware
ROM:EAEE                 lda     BlitDontAvoidTearing ; 0: try to avoid vsync tearing artifacts
ROM:EAEE                                         ; 1: blit as fast as possible ignoring vertical tearing
ROM:EAEE                                         ; (see EAEE)
ROM:EAF1                 bne     DoBlitAndAdvanceBlitterContextPtr ; X points to the blitter context
ROM:EAF3                 lda     8,x
ROM:EAF5                 bmi     DoBlitAndAdvanceBlitterContextPtr ; branch if IMPG value has high-bit set
ROM:EAF7                 lda     5,x             ; A = blitter destination Y value
ROM:EAF9                 adda    7,x             ; add blit size height to A
ROM:EAF9                                         ; This makes A now the bottom coordinate of the blit rectangle
ROM:EAFB                 suba    HW_VerticalCounter ; Subtract current vertical counter value from blit bottom Y value.
ROM:EAFB                                         ; If positive, beam is above bottom of blit can tearing can occur.
ROM:EAFB                                         ; If negative, beam is below bottom of blit and tearing is not a risk.
ROM:EAFB                                         ; AT LEAST that's what it is looking like to me! :)
ROM:EAFE                 bls     DoBlitAndAdvanceBlitterContextPtr ; branch if tearing is not a concern
ROM:EB00                 ldb     #65             ; this appears to create a new blit size
ROM:EB00                                         ; of up to 64x65 pixels, but can be less.
ROM:EB00                                         ; It appears to be a way to stall to avoid tearing artifacts on the screen.
ROM:EB02                 cmpa    #64
ROM:EB04                 bcs     DoNullBlit      ; if width is already less than 64 pixels, branch
ROM:EB06                 lda     #64             ; force width down to 64 pixels
ROM:EB08 DoNullBlit:                             ; CODE XREF: FakeFIRQ+2B j
ROM:EB08                 std     HW_BlitterSize
ROM:EB0B                 inc     BlitterPendingOperationsCount ; make sure next time FIRQ fires, the blit we didn't perform gets redone
ROM:EB0E                 lda     #%11000000      ; this appears to execute a NOP to the blitter chips by suppressing all writes.  It may be to queue up a future FIRQ.
ROM:EB10                 sta     HW_BlitterControl ; writing here executes the blitter chips.
ROM:EB10                                         ; Control Bits
ROM:EB10                                         ;  7: 1=write suppress odd pixels
ROM:EB10                                         ;  6: 1=write suppress even pixels
ROM:EB10                                         ;  5: 1=shift image 1 pixel to the right (odd flavor)
ROM:EB10                                         ;  4: 1=solid color mode (constant substitution)
ROM:EB10                                         ;  3: 1=zero write suppress (only blit non-zero color values)
ROM:EB10                                         ;  2: 1=sync to E clock (half speed writes, RAM to RAM)
ROM:EB10                                         ;  1: write format (0=serial, 1=block)
ROM:EB10                                         ;  0: read format (0=serial, 1=block)
ROM:EB13                 bra     FIRQ_RestoreRegsThenEnd
ROM:EB15 ; ---------------------------------------------------------------------------
ROM:EB15 DoBlitAndAdvanceBlitterContextPtr:      ; CODE XREF: FakeFIRQ+18 j
ROM:EB15                                         ; FakeFIRQ+1C j ...
ROM:EB15                 ldd     8,x             ; X points to the blitter context
ROM:EB17                 std     HW_IMPG_PRIME
ROM:EB1A                 ldd     6,x
ROM:EB1C                 std     HW_BlitterSize
ROM:EB1F                 ldd     4,x
ROM:EB21                 std     HW_BlitterDest
ROM:EB24                 ldd     2,x
ROM:EB26                 std     HW_BlitterSource
ROM:EB29                 ldd     ,x
ROM:EB2B                 stb     HW_BlitterSolidClr
ROM:EB2E                 sta     HW_BlitterControl ; writing here executes the blitter chips.
ROM:EB2E                                         ; Control Bits
ROM:EB2E                                         ;  7: 1=write suppress odd pixels
ROM:EB2E                                         ;  6: 1=write suppress even pixels
ROM:EB2E                                         ;  5: 1=shift image 1 pixel to the right (odd flavor)
ROM:EB2E                                         ;  4: 1=solid color mode (constant substitution)
ROM:EB2E                                         ;  3: 1=zero write suppress (only blit non-zero color values)
ROM:EB2E                                         ;  2: 1=sync to E clock (half speed writes, RAM to RAM)
ROM:EB2E                                         ;  1: write format (0=serial, 1=block)
ROM:EB2E                                         ;  0: read format (0=serial, 1=block)
ROM:EB31                 leax    10,x            ; advance to the next blitter context in the array
ROM:EB31                                         ; (each context is 10 bytes long)
ROM:EB33                 cmpx    #word_0_AC1C    ; have we got to the end of the blitter contexts?
ROM:EB36                 bcs     StoreNewBlitterContextPointer
ROM:EB38                 ldx     #BlitterContextArray ; a 1000 byte array which has room for 100 10-byte blitter context entries.
ROM:EB3B StoreNewBlitterContextPointer:          ; CODE XREF: FakeFIRQ+5D j
ROM:EB3B                 stx     BlitterContextPtr ; points to the current blitter context to be sent to the blitter hardware
ROM:EB3E FIRQ_RestoreRegsThenEnd:                ; CODE XREF: FakeFIRQ+3A j
ROM:EB3E                 puls    x,b,a
ROM:EB40 FIRQ_End:                               ; CODE XREF: FakeFIRQ+E j
ROM:EB40                 rti
ROM:EB40 ; End of function FakeFIRQ

No comments:

Post a Comment