[stella] Is there a relationship between NUSIZ and HMOVE?

Subject: [stella] Is there a relationship between NUSIZ and HMOVE?
From: Dusty Reichwein <badpandabear@xxxxxxxxxx>
Date: Fri, 1 Jul 2005 01:46:12 -0400
Hi everyone,

I'm perplexed by some behavior I've been seeing from the TIA and I was 
hoping someone here could enlighten me about what the heck's going on.  
I've been working on a display kernel that varies the NUSIZ width of a 
sprite on every scan line, so I can have sprites with a small but higher 
detailed top and a large but less detailed bottom.  To do this, I also 
have to do an HMOVE to move the bottom portion of the sprite to the left 
so it appears in the correct relationship with the top portion.  
However, I have a problem.  When the sprite is at the left most portion 
of the screen, the scan lines where the NUSIZ values change appear to be 
moved too far to the left and get out of sync with the rest of the 
sprite..  As the sprite moves to the right, the scan lines realign and 
the sprite starts to look normal.  Once it scrolls off the right edge of 
the screen and back onto the left edge, the scrolled left edge has the 
same problem. 

I've attached a test program I wrote to test this problem.  It shows 
four variations on displaying the same sprite:

Top Sprite: NUSIZ0 at cycle 74, HMOVE on cycle 3.
Second Sprite: NUSIZ0 at cycle 71, HMOVE on cycle 74
Third Sprite: NUSIZ0 at 76, HMOVE at cycle 3
Fourth Sprite: HMOVE at 74, NUSIZ0 at 1

The top two look okay when at the left edge of the screen. When moved to 
the right edge (using the joystick with this test program), the problem 
shows up on the left edge of the screen when the sprite scrolls over. 
(Both sprites also look stretched when they reach the point NUSIZ0 is 
set, but that's to be expected)

The second two show the problem more clearly.  I was thinking that the 
problem was caused by setting NUSIZ0 too close to or after the HMOVE, 
but I'm confused by why it is different when doing an early HMOVE vs a 
normal HMOVE.  Perhaps its the position on the scan line that matters 
and not the time between setting NUSIZ0 and HMOVE?

Note that none of these problems happen in Stella or Z26, although they 
do show another problem where an early HMOVE seems to be applied 
immediately, unlike on the real hardware.  This led me to worry that 
maybe I'm using an unreliable feature of the TIA.  So I tested it on 
several consoles. Specifically a 4 switch Vader, heavy sixer, light 
sixer, 2600 jr, 7800, gemini, 2600s, CCE supergame, and a 5200 using the 
2600 adapter (phew).  I got the same behavior on each, so I'm pretty 
sure its consistent for all NTSC versions of the TIA.

Sorry for this long post, but this problem has me confused.  Can someone 
here enlighten me on what exactly is going on?  Also, would someone be 
willing to test this on a PAL Atari 2600? 

Thanks,
Dusty

Archives (includes files) at http://www.biglist.com/lists/stella/archives/
Unsub & more at http://stella.biglist.com

[demime 1.01d removed an attachment with a content-type header it could not parse.]
[Content-Type: data; name="multisize_sprites_ntsc.bin"]

[demime 1.01d removed an attachment with a content-type header it could not parse.]
[Content-Type: data; name="multisize_sprites_pal.bin"]
   PROCESSOR 6502
   INCLUDE vcs.h
   INCLUDE macro.h
;;;;;;;;;;;;;;;;;;;;;; Place constants here ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

PAL                     = 0
NTSC                    = 1
COMPILE_VERSION         = NTSC

   IF COMPILE_VERSION == PAL
VBLANK_TIMER            = 44
SCREEN_SIZE             = 284
OVERSCAN_COUNT          = 30
BACKGROUND              = $D0
FOREGROUND              = $FF
   ELSE
VBLANK_TIMER            = 44 ;; was 54
SCREEN_SIZE             = 228
OVERSCAN_COUNT          = 36
BACKGROUND              = $80
FOREGROUND              = $FF
   ENDIF

;;; The Atari 2600 RAM area.
   SEG.U Ram
   ORG $80
spriteXPos   ds 1
frameCounter ds 1
direction    ds 1
tempVar1     ds 1

; Macro to Position an object horizontally
; Inputs: A = Desired pixel position (0-159)
; Macro Argument: Desired object to be positioned (0-4)
; scanlines: Exactly one scanline is consumed
; Caller must call 5 cycles into scanline. Example:
;           STA WSYNC
;           SLEEP 5
;           POS_OBJECT
;
; Outputs: Y = unchanged
; A = The fine adjust value used
; X = the "remainder" of the division by 15 minus an additional 15.
; control is returned at the start of the next scanline.
; Credits: Originally from a post on the Stella List from R. Mundschau.
; This was taken from the AtariAge 2600 Programming For Newbies forum
; where it was posted by Andrew Davie.  It has been modified based
; on suggestions from Eric Ball to avoid a 1 cycle penalty when
; crossing a page boundary.  It has also been converted into a macro
; to avoid function call overhead

            MAC POS_OBJECT
.WHICH      SET {1}

            sec                         ; 07     Set the carry flag so no borrow will be applied during the division.
.divideby15 sbc #15                     ; 09     Waste the necessary amount of time dividing X-pos by 15!
            bcs .divideby15             ; 11/12  16/21/26/31/36/41/46/51/56/61

            tax                         ; 2
            lda fineAdjustTable,x       ; 4 cycles (Table setup guarantees no crossing page boundary)
            sta HMP0 + .WHICH           ; 3

            sta RESP0 + .WHICH          ; 3  (23/28/33/38/43/48/53/58/63/68/73)
            ;; End the scanline. If A was > 150 there is just enough cycles
            ;; to do this
            STA WSYNC

            ;; Double check Positioning:
            ;; If A = 0:
            ;;          STA RESP0,X happens at 23 cycles
            ;;          STA HMP0,X is Left 6 (-15 in lookup table)
            ;;          TIA clock position is 23*3 + 5 -6 = 68 = correct
            ;; If A = 14:
            ;;          STA RESP0,X happens at 23 cycles
            ;;          STA HMP0,X is Right 8 (-1 in lookup table)
            ;;          TIA clock position is 23*3+5 +8 = 82 = correct
            ;; If A = 15:
            ;;          STA RESP0,X happens at 28 cycles
            ;;          STA HMP0,X is Left 6 (-15 in lookup table)
            ;;          TIA clock position is 29*3+5 - 6 = 83 = correct
            ;; If A = 159:
            ;;          STA RESP0,X happens at 73 cycles
            ;;          STA HMP0,X is Right 3 (-6 in lookup table)
            ;;          TIA clock position is 73*3+5 +3 = 227 = correct


.notLessThan120

            ENDM


;;; Start of 4K rom space
   SEG ROM
   ORG $F000

;;; Where code starts executing
Start

CLEAN_START

   ;; Initialize sprite position and speed
   LDA #0
   STA direction

   LDA #0
   STA spriteXPos

   ;; Set port A to input for reading from the joystick
   STA SWACNT

;;; Start of a screen display
FrameStart

   VERTICAL_SYNC


   JSR VerticalBlankCalc
   JSR ScanlineKernel
   JSR Overscan
   JMP FrameStart


;;; Performs game logic during vertical blank
VerticalBlankCalc SUBROUTINE

   ;; Setup timer
   LDA #VBLANK_TIMER
   STA TIM64T

   ;; Setup background color
   LDA #BACKGROUND
   STA COLUBK

   ;; Setup sprite color
   LDA #FOREGROUND
   STA COLUP0

   ;; Move sprite 1 pixel every 4 frames
   ;; if the joystick is pressed left or right.
   LDA frameCounter
   AND #$03
   BNE .noMove

   ;; see if bit 6 is 0 (meaning left is pressed)
   LDA #%01000000
   BIT SWCHA
   BNE .skipMoveLeft
   LDA #-1
   STA direction
   JMP .move
.skipMoveLeft

   LDA #%10000000	;See if bit 7 0 (meaning rigt is pressed)
   BIT SWCHA
   BNE .noMove
   LDA #1
   STA direction
   JMP .move

.noMove
   LDA #0
   STA direction

.move

   LDA spriteXPos
   CLC
   ADC direction
   CMP #160
   BNE .lessThan

   LDA #159
   JMP .endMove
.lessThan
   CMP #-1
   BNE .endMove
   LDA #0

.endMove
   STA spriteXPos

   ;; Increment frame counter
   INC frameCounter

   ;; Now wait for the end of vertical blank
.loop
   LDA INTIM
   BNE .loop
   STA WSYNC
   STA VBLANK  ;;; Note A will be 0, so this turns of VBLANK
   RTS

;;; Draws the visible scanlines
ScanlineKernel SUBROUTINE

   ;; Draw scanlines here.
   ;; This is sample code that doesn't do anything
   ;; Skip 10 scanlines
   LDX #10
.loop
   STA WSYNC
   DEX
   BNE .loop
   ;; Scanline 11

   ;; Display a multi size sprite using normal HMOVE
   LDY #<BattleShipType
   LDA spriteXPos
   JSR DisplayMultiSizeSprite

   ;; A few blank lines to seperate sections
   STA WSYNC
   STA WSYNC
   STA WSYNC

   ;; Display a multisize sprite using early HMOVE
   LDY #<BattleShipType
   LDX spriteXPos
   JSR DisplayMultiSizeSprite2

   ;; Now display a multi size sprite using normal HMOVE, and a NUSIZ store
   ;; that is too close to the HMOVE and will cause errors on real hardware
   LDY #<BattleShipType
   LDA spriteXPos
   JSR DisplayMultiSizeSprite3

   ;; A few blank lines to seperate sections
   STA WSYNC
   STA WSYNC
   STA WSYNC

   ;; Now display a multi size sprite using early HMOVE, and a NUSIZ store
   ;; that is after the HMOVE and will cause errors on real hardware
   LDY #<BattleShipType
   LDX spriteXPos
   JSR DisplayMultiSizeSprite4

   ;; Finish out the rest of the screen

   LDX #SCREEN_SIZE - 135
.loop2
   STA WSYNC
   DEX
   BNE .loop2
   RTS

;;; Set VBlank for overscan
Overscan SUBROUTINE

   LDA #2
   STA VBLANK
   LDY #OVERSCAN_COUNT
.loop
   STA WSYNC
   DEY
   BNE .loop
   RTS

;; Displays a multi size sprite
;; Arguments
;; A horizontal position of sprite (0-159)
;; Y 8 bit pointer to sprite offset in SpriteData and SpriteSize
;; Returns on cycle 14
DisplayMultiSizeSprite SUBROUTINE

;; Do Horizontal positioning
   STA WSYNC
   SLEEP 5
   POS_OBJECT 0
   STA HMOVE
   STA WSYNC
   ;; Delay to get the loop to start on a consistent
   ;; point on the scanline
   LDX #4           ; (2,2)
.delay1
   DEX              ; (2,4+(n-1)*5)
   BNE .delay1      ; (3 or 2, ends at 6+(n-1)*5) 21 when n = 4
   SLEEP 3          ; (3, 24)
.spriteLoop
   LDA SizeData,Y   ; (4,28)
   STA HMP0         ; (3,31)
   AND #$0F         ; (2,33)
   LDX #7           ; (2,35)
.delay2
   DEX              ; (2,37+(n-1)*5)
   BNE .delay2      ; (3 or 2, 39+(n-1)*5) with n = 4, ends at 69)
   SLEEP 2          ; (3,71)

   sta NUSIZ0       ; (3,74)
   SLEEP 2          ; (2,76)

   sta HMOVE        ; (3,3)
   LDX SpriteData,Y ; (4,07)
   SLEEP 7          ; (7,14)
   STX GRP0         ; (3,17)

   DEY              ; (2,19)

   ;; check for end of sprite
   AND #$08          ; (2,21)
   BEQ .spriteLoop   ; (3,24) or (2,23)

   ;; End of sprite, clear out motion register and graphics
   STA WSYNC
   LDA #0      ;(3,3)
   STA HMP0    ;(3,6)
   STA GRP0    ;(3,9)
   RTS         ;(6,14)

;; Displays a multi size sprite using an early hmove
;; Arguments
;; X horizontal position of sprite (0-159)
;; Y 8 bit pointer to sprite offset in SpriteData and SpriteSize2
;; Returns on cycle 14
DisplayMultiSizeSprite2 SUBROUTINE

;; Do Horizontal positioning

   LDA HorzTable,X
   STA HMP0
   STA WSYNC
   AND #$0F            ; (2,2)
   STX tempVar1        ; (3,5)
   TAX                 ; (2,7)
   SLEEP 12 ;; for timing (12,19)
.loop1
   DEX                 ; (2,21+5*X)
   BPL .loop1          ; (2,23+5*X) on final loop
   STA RESP0           ; (3,26+5*X)

   ;; if X was > 150, it is now the next line. If it wasn't it is now
   ;; at most 71 cycles on the positioning line. To determine if a WSYNC
   ;; is needed we'll check against 135, because that gives enough time
   ;; to actually do the WSYNC
   ;;                  X < 135 | 136 <=  X <= 150 | X > 150
   LDA tempVar1      ; (3,69)  | (3,74)           | (3,3)
   CMP #136          ; (2,71)  | (2,76)           | (2,5)
   BCS .noSync       ; (2,73)  | (3,3)            | (3,8)

   ;; X < 135 Theres just enough time for the WSYNC
   STA WSYNC

.noSync
   ;; It is now 0, 3, or 8 cycles on this scanline
   ;; Do a WSYNC to get to a consistent location on a scanline
   STA WSYNC

   ;; Now we must do a HMOVE on cycle 73
   LDX #12       ;; (2,2)
   ;; Loop needs to take 61-2 = 59 cycles
   ;; (X-1)*5 + 4 = 59
   ;; X = 12
.loop2
   DEX          ;; (2,4,9,.................)    (2,59)
   BNE .loop2   ;; (3,7,12,17,22,27,32...52,57) (2,61)

   LDA SizeData2,Y  ;; (4,65)
   AND #$0F         ;; (2,67)
.spriteLoop
   sta.w NUSIZ0     ;; (4, 71)
   STA HMOVE        ;; (3,74)
   LDX SpriteData,Y ;; (4,2)
   STX GRP0         ;; (3,5)

   ;; check for end of sprite
   AND #$08         ;; (2,7)
   BNE .endSprite   ;; (2,9) or (3,10)
   DEY              ;; (2,11)
   LDA SizeData2,Y  ;; (4,15)
   ;; It's been 17 since the HMOVE, need to wait until 24 after
   SLEEP 7          ;; (7,22)
   STA HMP0         ;; (3,25)
   AND #$0F         ;; (2,27)
   ;; waste some time so that the next HMOVE is at the right cycle
   LDX #6           ;; (2,29)
   ;; Loop will take (6-1)*5+4 = 29 cycles
.loop3
   DEX              ;; (2,31,36)      (2,56)
   BNE .loop3       ;; (3,34,39,...54)(2,58)
   SLEEP 6          ;; (6,64)
   JMP .spriteLoop  ;; (3,67)

.endSprite
   LDA #80
   STA HMP0
   STA WSYNC
   LDA #00
   STA GRP0
   STA NUSIZ0
   RTS


;; Displays a multi size sprite, using an NUSIZ store that
;; causes errors on real hardware
;; Arguments
;; A horizontal position of sprite (0-159)
;; Y 8 bit pointer to sprite offset in SpriteData and SpriteSize
;; Returns on cycle 14
DisplayMultiSizeSprite3 SUBROUTINE

;; Do Horizontal positioning
   STA WSYNC
   SLEEP 5
   POS_OBJECT 0
   STA HMOVE
   STA WSYNC
   ;; Delay to get the loop to start on a consistent
   ;; point on the scanline
   LDX #4           ; (2,2)
.delay1
   DEX              ; (2,4+(n-1)*5)
   BNE .delay1      ; (3 or 2, ends at 6+(n-1)*5) 21 when n = 4
   SLEEP 3          ; (3, 24)
.spriteLoop
   LDA SizeData,Y   ; (4,28)
   STA HMP0         ; (3,31)
   AND #$0F         ; (2,33)
   LDX #7           ; (2,35)
.delay2
   DEX              ; (2,37+(n-1)*5)
   BNE .delay2      ; (3 or 2, 39+(n-1)*5) with n = 4, ends at 69)
   SLEEP 4          ; (4,73)

   STA NUSIZ0       ; (3,76)  ;; Set size before HMOVE
   sta HMOVE        ; (3,3)
   LDX SpriteData,Y ; (4,07)
   SLEEP 7          ; (7,14) ;; just here for timing cause I'm too lazy
                             ;; to absorb it in a delay loop
   STX GRP0         ; (3,17)

   DEY              ; (2,19)

   ;; check for end of sprite
   AND #$08          ; (2,21)
   BEQ .spriteLoop   ; (3,24) or (2,23)

   ;; End of sprite, clear out motion register and graphics
   STA WSYNC
   LDA #0      ;(3,3)
   STA HMP0    ;(3,6)
   STA GRP0    ;(3,9)
   RTS         ;(6,14)

;; Displays a multi size sprite using an early hmove, and a NUSIZ store
;; that will cause problems on real hardware
;; Arguments
;; X horizontal position of sprite (0-159)
;; Y 8 bit pointer to sprite offset in SpriteData and SpriteSize2
;; Returns on cycle 14
DisplayMultiSizeSprite4 SUBROUTINE

;; Do Horizontal positioning

   LDA HorzTable,X
   STA HMP0
   STA WSYNC
   AND #$0F            ; (2,2)
   STX tempVar1        ; (3,5)
   TAX                 ; (2,7)
   SLEEP 12 ;; for timing (12,19)
.loop1
   DEX                 ; (2,21+5*X)
   BPL .loop1          ; (2,23+5*X) on final loop
   STA RESP0           ; (3,26+5*X)

   ;; if X was > 150, it is now the next line. If it wasn't it is now
   ;; at most 71 cycles on the positioning line. To determine if a WSYNC
   ;; is needed we'll check against 135, because that gives enough time
   ;; to actually do the WSYNC
   ;;                  X < 135 | 136 <=  X <= 150 | X > 150
   LDA tempVar1      ; (3,69)  | (3,74)           | (3,3)
   CMP #136          ; (2,71)  | (2,76)           | (2,5)
   BCS .noSync       ; (2,73)  | (3,3)            | (3,8)

   ;; X < 135 Theres just enough time for the WSYNC
   STA WSYNC

.noSync
   ;; It is now 0, 3, or 8 cycles on this scanline
   ;; Do a WSYNC to get to a consistent location on a scanline
   STA WSYNC

   ;; Now we must do a HMOVE on cycle 73
   LDX #12       ;; (2,2)
   ;; Loop needs to take 61-2 = 59 cycles
   ;; (X-1)*5 + 4 = 59
   ;; X = 12
.loop2
   DEX          ;; (2,4,9,.................)    (2,59)
   BNE .loop2   ;; (3,7,12,17,22,27,32...52,57) (2,61)

   LDA SizeData2,Y  ;; (4,65)
   AND #$0F         ;; (2,67)

.spriteLoop
   SLEEP 4          ;; (4, 71)
   STA HMOVE        ;; (3,74)
   STA NUSIZ0       ;; (3,77 or 1 on next scanline)
   LDX SpriteData,Y ;; (4,5)
   STX GRP0         ;; (3,8)

   ;; check for end of sprite
   AND #$08         ;; (2,10)
   BNE .endSprite   ;; (2,12) or (3,13)
   DEY              ;; (2,14)
   LDA SizeData2,Y  ;; (4,18)
   ;; It's been 22 since HMOVE, SLEEP 2 more
   ;; to make sure it's okay to set HMP0
   SLEEP 2          ;; (2,20)
   STA HMP0         ;; (3,23)
   AND #$0F         ;; (2,25)
   ;; waste some time so that the next HMOVE is at the right cycle
   LDX #7           ;; (2,27)
   ;; Loop will take (7-1)*5+4 = 34 cycles
.loop3
   DEX              ;; (2,29,34,      (2,59)
   BNE .loop3       ;; (3,32,37,...57)(2,61)
   SLEEP 3          ;; (3,64)
   JMP .spriteLoop  ;; (3,67)

.endSprite
   LDA #80
   STA HMP0
   STA WSYNC
   LDA #00
   STA GRP0
   STA NUSIZ0
   RTS



   ALIGN 256
SpriteData
BattleShip
    .byte %11111111
    .byte %11111111
    .byte %00111110
    .byte %01111111
    .byte %00011100
    .byte %00111110   ;; This is first quad sized line
    .byte %00011100   ;; This is the first (and only) double sized line
    .byte %01110110
    .byte %01110110
    .byte %00110110
    .byte %00110100
    .byte %00110000
    .byte %01111000
    .byte %00100000
BattleShipType
    .byte %00100000

   ALIGN 256
SizeData
;; BattleShip
    .byte %00001111
    .byte %00000111
    .byte %00000111
    .byte %00000111
    .byte %00000111
    .byte %01110111
    .byte %01100101
    .byte %00000000
    .byte %00000000
    .byte %00000000
    .byte %00000000
    .byte %00000000
    .byte %00000000
    .byte %00000000
    .byte %00000000

    ALIGN 256
;; Size data with HMPx values setup for early hmove
SizeData2
    .byte %10001111
    .byte %10000111
    .byte %10000111
    .byte %10000111
    .byte %10000111
;;    .byte %00100111  ;; original data from fireone sprite
;;    .byte %11010101
    .byte %11110111    ;; modified data so it will look like normal HMOVE sprite
    .byte %11100101
    .byte %10000000
    .byte %10000000
    .byte %10000000
    .byte %10000000
    .byte %10000000
    .byte %10000000
    .byte %10000000
    .byte %10000000

;-----------------------------
; This table converts the "remainder" of the division by 15 (-1 to -15) to the correct
; fine adjustment value.
; This table originally came from code posted to the Stella list by R. Mundschau,
; and then posted to the AtariAge 2600 Programming For Newbies Forum by Andrew Davie.
; Per suggestion by Eric Ball, the table has been moved to be just before a page boundary
; so that no penalty is generated when accessing the table.
; The adjustments have been moved right by 1 so that they go from Left 6 to Right 8.
; This allowed for a position of 0 to map to a pixel position of 68 TIA clocks.

            ORG  $FEF1
fineAdjustBegin

            DC.B %01100000 ; Left 6         -15
            DC.B %01010000 ; Left 5         -14
            DC.B %01000000 ; Left 4         -13
            DC.B %00110000 ; Left 3         -12
            DC.B %00100000 ; Left 2         -11
            DC.B %00010000 ; Left 1         -10
            DC.B %00000000 ; No movement.   -9
            DC.B %11110000 ; Right 1        -8
            DC.B %11100000 ; Right 2        -7
            DC.B %11010000 ; Right 3        -6
            DC.B %11000000 ; Right 4        -5
            DC.B %10110000 ; Right 5        -4
            DC.B %10100000 ; Right 6        -3
            DC.B %10010000 ; Right 7        -2
            DC.B %10000000 ; Right 8        -1

fineAdjustTable EQU fineAdjustBegin - %11110001 ; NOTE: %11110001 = -15


;; This table holds the coarse and fine grained positioning values for
;; a given X pos (0-159)  The high nibble is the fine grained value,
;; the low nibble is the coarse grained value
    ALIGN 256
HorzTable
    .byte $70, $60, $50, $40, $30, $20, $10, $00, $F0, $E0
    .byte $D0, $C0, $B0, $A0, $90, $80, $61, $51, $41, $31
    .byte $21, $11, $01, $F1, $E1, $D1, $C1, $B1, $A1, $91
    .byte $81, $62, $52, $42, $32, $22, $12, $02, $F2, $E2
    .byte $D2, $C2, $B2, $A2, $92, $82, $63, $53, $43, $33
    .byte $23, $13, $03, $F3, $E3, $D3, $C3, $B3, $A3, $93
    .byte $83, $64, $54, $44, $34, $24, $14, $04, $F4, $E4
    .byte $D4, $C4, $B4, $A4, $94, $84, $65, $55, $45, $35
    .byte $25, $15, $05, $F5, $E5, $D5, $C5, $B5, $A5, $95
    .byte $85, $66, $56, $46, $36, $26, $16, $06, $F6, $E6
    .byte $D6, $C6, $B6, $A6, $96, $86, $67, $57, $47, $37
    .byte $27, $17, $07, $F7, $E7, $D7, $C7, $B7, $A7, $97
    .byte $87, $68, $58, $48, $38, $28, $18, $08, $F8, $E8
    .byte $D8, $C8, $B8, $A8, $98, $88, $69, $59, $49, $39
    .byte $29, $19, $09, $F9, $E9, $D9, $C9, $B9, $A9, $99
    .byte $89
    ;; At 151 there is a discontinuity because the RESP0 is on the next
    ;; line
    .byte $4A, $3A, $2A, $1A, $0A, $FA, $EA, $DA, $CA


;;; Interrupt vector table
   ORG $FFFA
   .word Start    ;; NMI
   .word Start    ;; Reset
   .word Start    ;; IRQ

Current Thread