This Week With My Coleco ADAM 9701.27

by Richard F. Drushel (drushel@apk.net)

[Administrative note: this article is appearing a week late because of the observance of my wife's birthday on 26 January and family illness this week. Thanks for bearing with me. The regularly-scheduled article for 2 February is appearing on time as a separate article.]

I. Low-memory code structure and code fossils in Pitfall.

I mentioned in my article on disassembling the ColecoVision game Pitfall by Activision (TWWMCA 9701.19) that, in addition to an unused sound, I discovered several complete code subroutines which were not used. I'm presenting them here with some comments. They are general-purpose routines which are called through the 1-byte RST xxH subroutine mechanism (designed so that frequently-used subroutines could be called using only one byte of machine code rather than the three required by CALL nnnn.

Since RST xxH causes a CALL to address xxH, and these addresses 08H-38H lie in the ColecoVision OS7 ROM, there are JP nnnn statements at each vector to defined locations in cartridge ROM address space. These defined locations are organized as a jump table (i.e., a series of consecutive JP nnnn statements). It is the responsibility of the cartridge programmer to make sure that each jump table entry points to his own routines. If a particular RST xxH is not used by the game, the jump table entry for that RST must point to a RET.

Although Pitfall(tm) makes no use of RST xxH commands, I presume that other Activision games do; I haven't looked, though.

Here follows the entire low memory setup of the Pitfall(tm) game cartridge, taken from my commented disassembly PITFALL.ASM. Some of the comments have been improved from the version I originally put up download as PITFALL.ZIP; I have now put up a fixed version, PITFALL2.ZIP, which supersedes the former. I indicate the division between the code that is common to (i.e., *required* by) all ColecoVision game cartridges, and the Pitfall-specific code. Symbols that begin with MY_ and disassembler-generated symbols of the form J$nnnn or D.nnnn are Pitfall-specific; all the rest are OS7 public globals (see OS7SYM.ASM).

;***********************************************
;*                                             *
;*   start of Pitfall(tm) game cartridge ROM   *
;*                                             *
;*     the initial pointers and jump table     *
;*    are required for every game cartridge    *
;*                                             *
;***********************************************

CARTRIDGE:
        DB      55H,0AAH        ;magic identifier for "test" cartridge
                                ;(i.e., don't use ColecoVision boot screen)
LOCAL_SPR_TBL:
        DW      MY_LOCAL_SPR_TBL   ;copy of sprite name table (mirror of VRAM)
SPRITE_ORDER:
        DW      MY_SPRITE_ORDER    ;sprite order table
WORK_BUFFER:
        DW      MY_WORK_BUFFER     ;scratch RAM for OS-7 function calls
CONTROLLER_MAP:
        DW      MY_CONTROLLER_MAP  ;data area to store state of game controller
START_GAME:
        DW      MY_START_GAME      ;first executable code of the game
;       -----------------

;***********************************************
;*                                             *
;*   low-memory RST, INT, and NMI jump table   *
;*                                             *
;*      the OS-7 ROM has JPs to this code      *
;*                                             *
;***********************************************

RST_8H_RAM:
        JP      MY_RST_8H_RAM     ;for RST 08H
;
;       -----------------
RST_10H_RAM:
        JP      MY_RST_10H_RAM    ;for RST 10H
;
;       -----------------
RST_18H_RAM:
        JP      MY_RST_18H_RAM    ;for RST 18H
;
;       -----------------
RST_20H_RAM:
        JP      MY_RST_20H_RAM    ;for RST 20H
;
;       -----------------
RST_28H_RAM:
        JP      MY_RST_28H_RAM    ;for RST 28H
;
;       -----------------
RST_30H_RAM:
        JP      MY_RST_30H_RAM    ;for RST 30H
;
;       -----------------
IRQ_INT_VECT:
        JP      MY_IRQ_INT_VECT   ;for RST 38H or INT interrupt
;
;       -----------------
NMI_INT_VECT:
        JP      MY_NMI_VECT       ;for NMI interrupt
;
;       -----------------
GAME_NAME:

;This is where the title string is stored for cartridges
;which use the ColecoVision boot screen (magic number
;AA55 hex at addresses 0-1).  Pitfall(tm) doesn't need a
;title string, since it's a "test" cartridge (55AA).  Thus,
;the game specific code begins here.

;**************************************
;*                                    *
;*    begin cartridge-specific code   *
;*                                    *
;**************************************

MY_RST_8H_RAM:

;not used in Pitfall(tm).  Routine to pause for a certain
;number of interrupt cycles.  On entry, the stack contains
;a pointer to a count (nominally the return address of the
;CALL or RST, but in this case the data byte is in-line
;with the code, immediately after the CALL or RST.  This
;count is fetched, and the CPU put into the HALT state
;for the specified number of counts.  HALT disables the
;CPU until an external interrupt (INT or NMI) occurs;
;execution then begins at the first statement after the
;HALT (which here is a DJNZ loop).  On exit, return is to
;the address immediately after the in-line count byte.

;This code, if useful at all on the ColecoVision/ADAM, can
;only be used with INT interrupts.  The TMS9928 VDP, which
;generates NMIs, must receive a handshake after each NMI in
;order to keep them coming (namely, a read of VDP register 8).
;This code doesn't do the handshake after the HALT.  INT
;interrupts, however, will keep coming as long as they have
;been enabled with EI.

;calling code has the form:

;       RST     08H
;       DB      COUNT
;       [next opcode]

        EX      (SP),HL     ;get HL=return address off stack
        PUSH    BC          ;save a scratch register
        LD      B,(HL)      ;get count byte
        INC     HL          ;point ahead one to real return address
J$8028:
        HALT                ;halt the CPU and wait for an interrupt
        DJNZ    J$8028      ;interrupt occurred, so go back and wait again
;
        POP     BC          ;all done, restore BC
        EX      (SP),HL     ;put return address back on stack
        RET                 ;and exit
;
;       -----------------
MY_RST_10H_RAM:

;not used in Pitfall(tm).  Routine to get a vector from
;a table and JP there.  On entry, HL=address of table,
;and A=index of vector.  On exit, a JP is performed to
;the desired vector.

        ADD     A,A         ;A=A*2 for word offset
        ADD     A,L         ;add it to lobyte of table base
        LD      L,A         ;and back into L; was there a carry?
        JR      NC,J$8034   ;no
;
        INC     H           ;yes, so we have to INC H
J$8034:
        LD      A,(HL)      ;get lobyte of vector
        INC     HL          ;point to hibyte
        LD      H,(HL)      ;get hibyte of vector
        LD      L,A         ;now HL=vector
        JP      (HL)        ;go there
;
;       -----------------
MY_RST_18H_RAM:

;not used in Pitfall(tm).  Looks like a custom routine to
;return a random number.  The built-in RAND_GEN routine
;doesn't give the greatest sequence of random numbers.  On
;entry, A=count of how many times to call RAND_GEN, and
;HL=seed value.  On exit, A=random number.

        PUSH    HL
        PUSH    DE
        PUSH    BC
        PUSH    AF          ;save count
        LD      B,A         ;count in B
J$803E:
        CALL    RAND_GEN    ;OS7 call, return A=random number
;
        DJNZ    J$803E      ;keep going until done
;
        POP     BC          ;restore count
        XOR     A           ;A=0
        LD      D,A         ;DE=0
        LD      E,A
        EX      DE,HL       ;HL=0, DE=entry seed
J$8048:
        ADD     HL,DE       ;add seed
        ADC     A,00H       ;and add the carry (if set)
        DJNZ    J$8048      ;keep going until done
;
        POP     BC
        POP     DE
        POP     HL
        RET
;
;       -----------------
MY_RST_20H_RAM:

;not used in Pitfall(tm).  Multiplies HL*A*2, returning
;product in HL.  Could be another way to calculate an offset
;into a vector table.

        PUSH    DE
        LD      H,00H
        LD      D,H
        LD      E,H
        EX      DE,HL       ;DE=entry L, HL=0
J$8057:
        SLA     E
        RL      D           ;DE=DE*2
        SRL     A           ;A=A/2; did DE*2 give a carry?
        JR      NC,J$8060   ;no
;
        ADD     HL,DE       ;yes, so HL=HL+DE
J$8060:
        OR      A           ;is A=0 yet?
        JR      NZ,J$8057   ;no, so keep going
;
        POP     DE
        RET
;
;       -----------------
MY_RST_28H_RAM:

;not implemented in Pitfall(tm).

        RET
;
;       -----------------
MY_IRQ_INT_VECT:

;This is for the maskable interrupt routine (for the spinners, trackball,
;and driving wheel).  Not implemented in Pitfall(tm).

        RET
;
;       -----------------
MY_RST_30H_RAM:

;not implemented in Pitfall(tm).

        RET
;
;       -----------------
MY_NMI_VECT:

;NMI routine.  Does a JP to address stored at D.7000.

        PUSH    HL              ;save HL
        LD      HL,(D.7000)     ;get the vector
        EX      (SP),HL         ;put it on the stack and restore HL
        RET                     ;go to the vector address
;
;       -----------------
START_MAIN:

;starts the MAIN program.

[code omitted]

II. The NMI routine: the heart of a ColecoVision game.

You may be somewhat disturbed by the lack of comments regarding the NMI routine at MY_NMI_VECTOR. That's because what happens during the NMI routine is extremely complicated (for Pitfall and for any other ColecoVision game). Stated briefly, the "main" program of a ColecoVision game is an idle loop,

IDLE_LOOP:
        JR IDLE_LOOP

waiting endlessly for some kind of interrupt (maskable or non-maskable) to occur. The game itself is played only after an interrupt occurs. In the case of Pitfall, which uses the 60 Hz (or 50 Hz on European systems) NMI as the system clock, everything happens as part of the MY_NMI_VECT routine. There is additional complexity to insure that the proper handshaking is performed with the TMS9928/9929 VDP (which generates the NMI) so that the NMI is re-enabled before the RETN command (return from NMI) is executed.

The NMI handler in Pitfall is quite interesting, but too complex for me to cover adequately at this time. I intend to make it a topic of discussion in a future issue of TWWMCA; stay tuned.

See you next week!

*Rich*


Next Article
Previous Article
TWWMCA Archive Main Page