UPI Procedures & Conventions

The User Program Interface (UPI) is a set of procedures and conventions used by cartridges to access the facilities provided by the Bally Arcade. By adhering to these conventions, a cartridge will be system independent, allowing improvements to be made to later version of the system and onboard games while maintaining upward compatibility.

The basic rule for using the UPI is: with exception to the system dope vector, no cartridge should ever address system ROM directly, nor expect a given memory cell to always equal a certain value.

The mechanism for calling a system routine is:

        RST     56
        DB      (type + routine # + option)

The system routine call breaks down as follows:

  • The RST instruction ($38/56) initiates the system routine call.
  • The parameter byte comprises three parts (read right-to-left):
    • The option bit (0) specifies how arguments are being passed to the system routine. If 0, the arguments are presumed to exist in CPU registers; if 1, the arguments are presumed to follow inline after the parameter byte. These arguments are loaded into the context block automatically (i.e., “sucked”) before the called routine is entered. (The arguments required by each system routine are given in the routine's description section.)
    • The routine number is an even number specifying which subroutine to transfer to. (Symbolic identifiers, which are equated to routine numbers, are provided in HVGLIB.)
    • The type bit (7) indicates whether the user is calling a standard subroutine (0) or a user-defined subroutine (1).

All UPI routines are reentrant.

UPI Parameter Byte Summary

Bit(s) Function Options
0 Option 0 = no parameters follow; 1 = parameters follow in subsequent bytes
1–6 Subroutine number (always even) to transfer to
7 Subroutine type 0 = standard subroutine; 1 = user-defined subroutine

Example: Calling PAWS with a Parameter

        RST     38H     ; call subroutine
        DB      81      ; parameter byte (type, subroutine #, option)
        DB      30      ; passed parameter

In the example above, the first parameter byte 81 expands to 01010001B. Bit 0 is 1, indicating that parameters will follow this byte. Bits 1–6, 101000B, indicate subroutine $50 (80D). Bit 7 is set to 0, indicating that this is a standard subroutine call.

According to the onboard subroutines list, subroutine 80 is PAWS, which waits for a user-defined number of interrupts, then returns. It requires one parameter, the number of interrupts, to be passed in register B. So the third byte in the example above is passing 30 to B, requesting a 30-interrupt pause.

For standard subroutines, users do not need to list the verbose RST call as illustrated in the example above. Instead, HVGLIB provides a list of symbolic identifiers for each system subroutine. Those identifiers are then invoked using the SYSTEM and SYSSUK calls.

SYSTEM / SYSSUK

The Bally Arcade uses two labels, SYSTEM and SYSSUK, to call system routines. Internally, both labels are represented by $FF, the “system sentinel” byte that readies the Arcade for a system routine number in the following byte. Their difference lies in how arguments are passed to the requested system routine.

The SYSTEM macro generates the system routine call with option = 0. Option 0 indicates that any system routine arguments will be loaded in their appropriate registers immediately prior to the SYSTEM call. For instance, returning to the example above, a user could execute the same interrupt wait routine using the following syntax:

        LD         B,30
        SYSTEM     PAWS

The SYSSUK macro generates the system routine call with option = 1. Option 1 indicates that any system routine arguments will be loaded in their appropriate registers directly after the SYSSUK call. In other words, the system 'sucks' them into the routine. The prior example would now look like this:

        SYSSUK     PAWS
        DB         30

Both methods have advantages and disadvantages. As Andy Guevara wrote in the Astrocade Machine Language Programming Tutorial:

The advantage of the SYSSUK structure is more compact code. The disadvantage is the inflexibility. It is better used for absolute cases than for iterative loops. Like for borders that will always be in the same place, rather than objects that will move across the screen.

In short, the choice of SYSTEM/SYSSUK depends on your specific programming task.

The Interpreter

Frequently it is desirable to string several system routine calls together. If four or more calls follow in sequence, it is more efficient to use the interpreter, because it voids the overhead of the RST 56 instruction. Once the interpreter is invoked, SYSSUK/SYSTEM calls are replaced with DO/DONT calls, respectively.

Special call indexes INTPC and EXIT are used to enter and exit interpretive mode.

Example: Interpreter Call and Exit

     SYSTEM     INTPC          ;BEGIN INTERPRETING
     DO         FILL           ;DO FILL ROUTINE
     DW         NORMEM         ;START AT TOP OF SCREEN
     DW         92*BYTEPL      ;CONTINUE FOR 92 LINES
     DB         0              ;FILL WITH ZEROES
     DO         CHRDIS         ;DO CHARACTER DISPLAY ROUTINE
     DB         0              ;CHARACTER Y-COORDINATE
     DB         10             ;CHARACTER X-COORDINATE
     DB         8              ;OPTIONS-PLOP, 10-ON, 00-OFF
     DB         "A"            ;CHARACTER TO DISPLAY
     EXIT                      ;EXIT INTERPRETER

System Routine Conventions

A system routine is coded like a conventional machine language subroutine, except that output parameters are not passed through registers but through the context block.

The context block is created by the RST $38/56 call. The user's register set (AF, BC, DE, HL, IX, IY) is pushed onto the stack. Register IY is set to point at this stack frame. Thus a copy of the input arguments exists in RAM, which the system routine may refer to as needed. These arguments are also present in the registers when the system routine is entered, so it is only necessary to refer to the context block when one has clobbered an input argument.

An output argument is returned to the caller by setting it in the context block. If a register was changed, but the associated cell in the context block was not, then the register will have its old value on return. Thus a system routine is free to use any of the registers it needs without worrying about saving and restoring. The user can also assume that no registers will change except those defined as returning an output argument.

The Context Block

The following illustration describes the context block and equates provided in HVGLIB for each field.

The UPI uses four tables in the control transfer process. The first two tables give the routine's starting address indexed via call number. The systems table is named SYSDPT. The user may extend this table by storing the address of their extended table into USERTB, USERTB+1. This address should point 128 bytes before the first entry.

The remaining two tables describe, if specified, the inline arguments a call expects. This table gives a one-byte bitstring, also indexed via call number. The systems name is MRARGT. The user's address is in UMARGT. UMARGT must point 64 bytes ahead. Arguments must follow the call in a specified order.

Note that the context contains additional information not shown. This information exists both above and below the context. User programs should never use this information or even assume that it exists. The user should only address this area by using IY.

Inline Argument Mask Table Entry

If optioned (i.e., bit 0 of call index is set), the Macro Routine Argument Table (MRARGT) is indexed giving a mask which specifies how to transfer inline arguments into the context block. The mask is formatted as follows:

Arguments must follow the call index in the following order (if an argument isn't specified, it is omitted):

(INDEX), IXL, IXH, E, D, C, B, A, L, H

User-Defined Subroutines

Section needs clarification.

The UPI has been extended to support user-defined system routines. If the UPI parameter byte's type bit (7) is set (or the byte's value is $80–$FF), and “suck inline” is optioned, the user's macro routine address table (USERMT) and argument table (UMARGT) are used. The User Macro Routine Argument Table (UMARGT) is indexed for a parameter mask. The address of this table is assumed to be in (UMARGT),(UMARGT+1). This pointer should point 64 bytes before the first real entry.


From system BIOS:

The UPI has been extended to support user-supplied routines. 
If the call index provided is negative [or from $ff through $80] 
then the users dispatch table pointer (USERTB) is used [also UMARGT]. 
Note that the sign bit isn't zapped before being used as an index, 
this means that the users dispatch table pointer should point 
128 bytes before the first entry.

Also:

THE UPI HAS BEEN EXTENDED TO PROVIDE USER PROVIDED
  ;  SYSTEM ROUTINES. IF A NEGATIVE CALL INDEX IS ENCOUNTERED
  ;  BY THE INTERPRETER, AND 'SUCK INLINE' IS OPTIONED, THE
  ;  USER MACRO ROUTINE ARGUMENT TABLE IS INDEXED FOR A
  ;  PARAMETER MASK.  THE ADDRESS OF THIS TABLE IS ASSUMED
  ;  TO BE IN (UMARGT),(UMARGT+1).  THIS POINTER SHOULD
  ;  POINT 64 BYTES BEFORE THE FIRST REAL ENTRY.
  ;  I.E. LD      HL,USERMT-64  ; WHERE USERMT POINTS AT the USER's Macro Table
  ;       LD      (UMARGT),HL

The user must set up the subroutine as follows:

Location Function
4FFB/4FFC (UMARGT) (Base address of register load specifications) - 40H (64D)
4FFD/4FFE (USERTB) (Base address of subroutine jump table) - 80H (128D)

The user is responsible for storing the addresses of these tables into dedicated system RAM cells.


From HVGLIB:

; User UPI Routines, even numbers from $80 to $FE ( + 1 for SUCK):
UMARGT  EQU     $4FFB   ; User Mask ARGument Table + (routine / 2)
USERTB  EQU     $4FFD   ; USER Table Base + routine = JumP address

Example: Space Fortress User Subroutine

;  User's Macro Subroutine $80
;  ---------------------------
;    JumP address          = $2027 ($1FA7 + $80)
;    Mask ARGument address = $2029 ($1FE9 + ($80/2))
;  User Macro $80 has two arguments based on a mask of 0000 0011B
;  Registers D and E are loaded when the User Macro is SYSSUKed. 
;  See BIOS listing for more details.
L2019:  EXX     
        LD      HL,L2025
        PUSH    HL
        LD      HL,($010D)
        PUSH    HL
        EXX     
        DI      
        RET     
 
L2025:  EI      
        RET
 
;  
L2027:  DW      L2019           ; User's Macro Subroutine $80, JumP address
L2029:  DB      00000011B       ; User's Macro Subroutine $80, Mask ARGument 
 
; [...]
        LD      HL,$1FA7
        LD      ($4FFD),HL      ; $4FFD is USER Table Base + routine = JumP address
        LD      HL,$1FE9   
        LD      ($4FFB),HL      ; $4FFB is User Mask ARGument Table + (routine / 2)
        LD      A,R
        AND     $1C
        LD      ($4F2A),A
L2FEE   LD      HL,L2FEE        ; $2FEE is current program counter address 
        PUSH    HL
        EI 
 
;  Call User's Macro Subroutine $80
;  Requires two inline arguments.
;  Loads E and D (in that order)  
L2FF3:  SYSSUK  $80             ; User's MACRO routine $80
        DB      $14             ; Inline argument for register E 
        DB      $02             ; Inline argument for register D

[NM:2-7, source listing p. 20]