;; ======================================================================== ;;
;;  CART.MAC                                                                ;;
;;                                                                          ;;
;;  Cartridge Support Routines                                              ;;
;;                                                                          ;;
;;  The macros and code in this file are intended to manage the memory map  ;;
;;  of your Intellivision cartridge.  This includes allocating your code    ;;
;;  to its ROM segments, and allocating variables in the Intellivision's    ;;
;;  8-bit scratch and 16-bit system memories.                               ;;
;;                                                                          ;;
;;  This file provides several useful macros.                               ;;
;;                                                                          ;;
;;  ROM functions:                                                          ;;
;;                                                                          ;;
;;      ROMSETUP    Sets memory map & stack for cart; Provides ROM header.  ;;
;;      ROMSEG      Switches between ROM segments when assembling           ;;
;;      ROMSEGSZ    Select ROM segment with room for at least "size" words  ;;
;;      ROMEND      Used at end of program to detect any assembly errors    ;;
;;                                                                          ;;
;;  Misc functions:                                                         ;;
;;                                                                          ;;
;;      REQ_ECS     Mark the ECS as required for this game.  MUST be        ;;
;;                  before ROMSETUP.  Will give an error screen if the      ;;
;;                  ECS is not attached.  If REQ_ECS set, then              ;;
;;                  BYTEVAR/BYTEARRAY will allocate from ECS RAM also.      ;;
;;                                                                          ;;
;;  RAM functions:                                                          ;;
;;                                                                          ;;
;;      BYTEVAR     Allocates a single byte variable in 8-bit RAM           ;;
;;      WORDVAR     Allocates a single word variable in 16-bit RAM          ;;
;;      BYTEARRAY   Allocates a multibyte variable in 8-bit RAM             ;;
;;      WORDARRAY   Allocates a multiword variable in 16-bit RAM            ;;
;;                                                                          ;;
;;  Alternate RAM functions:                                                ;;
;;                                                                          ;;
;;      SCRATCH     Allocates memory in 8-bit scratch RAM ($102-$1EF)       ;;
;;      SYSTEM      Allocates memory in 16-bit system RAM ($2F0-$35F)       ;;
;;      ECSRAM      Allocates memory in 8-bit ECS RAM ($4040-$47FF)         ;;
;;      CARTRAM     Allocates memory in 16-bit cartridge RAM ($8040-$9EFF)  ;;
;;                                                                          ;;
;; ======================================================================== ;;

        ROMW    16
_m.ECS  QSET    0       ; can be overridden by REQ_ECS

;; ======================================================================== ;;
;;  REQ_ECS     Mark the ECS as *required* for this game.                   ;;
;; ======================================================================== ;;
MACRO   REQ_ECS
        LISTING "off"
        IF  (DEFINED _m.map)
            ERR "REQ_ECS directive must appear before ROMSETUP"
        ENDI
_m.ECS  QSET    1
        LISTING "prev"
ENDM

;; ======================================================================== ;;
;;  ROMSETUP    Sets memory map & stack for cart; Provides ROM header       ;;
;;                                                                          ;;
;;  This macro must be called very early in your program, probably before   ;;
;;  anything else.  It configures your memory map (as seen by the ROMSEG,   ;;
;;  SYSTEM and CARTMEM macros), and gives you a simple and well defined     ;;
;;  cartridge header.                                                       ;;
;;                                                                          ;;
;;  USAGE                                                                   ;;
;;                                                                          ;;
;;      ROMSETUP map, year, "title", startaddr, stacksize                   ;;
;;                                                                          ;;
;;  where:                                                                  ;;
;;                                                                          ;;
;;      "map" is one of "16K" or "42K".  "16K" sets the cartridge up for    ;;
;;      the historic Mattel 16K-word memory map and "42K" sets things up    ;;
;;      for a much more aggressive memory map that offers 2.5x the space.   ;;
;;                                                                          ;;
;;      "year" is the copyright year (e.g. 2008).                           ;;
;;                                                                          ;;
;;      "title" is the title of the game, in quotes.                        ;;
;;                                                                          ;;
;;      "startaddr" is the label of the entry point for the game.           ;;
;;                                                                          ;;
;;      "stacksize" is the number of words to reserve for the stack.        ;;
;;      32 words is usually a reasonable number.                            ;;
;;                                                                          ;;
;;  EXAMPLE                                                                 ;;
;;                                                                          ;;
;;      ROMSETUP 16K, 2008, "Hello World", MAIN, 32                         ;;
;;                                                                          ;;
;;  DETAILS                                                                 ;;
;;                                                                          ;;
;;      ROMSETUP lets you pick between two memory maps.  The 16K memory     ;;
;;      map corresponds to the historic Mattel 16K-word ROM memory map.     ;;
;;      Games such as Stonix and Space Patrol adhere to this memory map.    ;;
;;                                                                          ;;
;;      That memory map has three ROM segments:                             ;;
;;                                                                          ;;
;;      SEGMENT     RANGE                                                   ;;
;;         0        $5000 - $6FFF                                           ;;
;;         1        $D000 - $DFFF                                           ;;
;;         2        $F000 - $FFFF                                           ;;
;;                                                                          ;;
;;      You can switch between segments using the ROMSEG macro.  This       ;;
;;      makes it easy to put portions of code in each of the three ROM      ;;
;;      segments and make the most use of the available ROM space.          ;;
;;                                                                          ;;
;;      The 42K mapping has six ROM segments:                               ;;
;;                                                                          ;;
;;      SEGMENT     RANGE                                                   ;;
;;         0        $5000 - $6FFF                                           ;;
;;         1        $A000 - $C020                                           ;;
;;         2        $C022 - $FFFF                                           ;;
;;         3        $2000 - $2FFF                                           ;;
;;         4        $7100 - $7FFF                                           ;;
;;         5        $4800 - $4FFF                                           ;;
;;                                                                          ;;
;;      It also contains an additional RAM segment from $8040 - $9EFF.      ;;
;;      This memory map is available on the CC3, the Intellicart, and       ;;
;;      the JLP home brew production cart.                                  ;;
;;                                                                          ;;
;;      Both memory maps insert a ROM header that configures the basics,    ;;
;;      bypasses the EXEC, configures a stack and then jumps to the user    ;;
;;      code.  The 42K map also includes a small stub at $4800 that goes    ;;
;;      and switches out the ECS ROMs at $2xxx, $7xxx and $Exxx, so they    ;;
;;      do not interfere with the user's program.                           ;;
;;                                                                          ;;
;; ======================================================================== ;;
MACRO   ROMSETUP map, year, title, startaddr, stacksize

        LISTING "code"

_m.map  QSET    _m.%map%

        IF  _m.map = _m.16K
_m.segs QSET    3
_m.0s   QSET    $5000
_m.0e   QSET    $6FFF
_m.1s   QSET    $D000
_m.1e   QSET    $DFFF
_m.2s   QSET    $F000
_m.2e   QSET    $FFFF
_m.3s   QSET    $0000
_m.3e   QSET    $0000
_m.4s   QSET    $0000
_m.4e   QSET    $0000
_m.5s   QSET    $0000
_m.5e   QSET    $0000
_m.rams QSET    $0000
_m.rame QSET    $0000
_m.5pos QSET    0
        ENDI

        IF  _m.map = _m.42K
_m.segs QSET    6
_m.0s   QSET    $5000
_m.0e   QSET    $6FFF
_m.1s   QSET    $A000
_m.1e   QSET    $C020
_m.2s   QSET    $C022
_m.2e   QSET    $FFFF
_m.3s   QSET    $2000
_m.3e   QSET    $2FFF
_m.4s   QSET    $7100
_m.4e   QSET    $7FFF
_m.5s   QSET    $4800
_m.5e   QSET    $4FFF
_m.rams QSET    $8040
_m.rame QSET    $9EFF


        ORG     $4800
        ; Disable ECS ROMs so that they don't conflict with us
        MVII    #$2A5F, R0
        MVO     R0,     $2FFF
        MVII    #$7A5F, R0
        MVO     R0,     $7FFF
        MVII    #$EA5F, R0
        MVO     R0,     $EFFF

        B       $1041       ; resume boot

_m.5pos QSET    $

        ENDI

_m.1pos     QSET    _m.1s
_m.2pos     QSET    _m.2s
_m.3pos     QSET    _m.3s
_m.4pos     QSET    _m.4s
.CARTMEM    QSET    _m.rams

        IF _m.rams > 0
            ORG     _m.rams, _m.rams, "=RW"
            RMB     _m.rame - _m.rams + 1
        ENDI

        ORG     $5000
        BIDECLE $500D       ; 5000  MOB picture base   (points to NULL list)
        BIDECLE $500D       ; 5002  Process table      (points to NULL list)
        BIDECLE %startaddr% ; 5004  Program start address
        BIDECLE $500D       ; 5006  Bkgnd picture base (points to NULL list)
        BIDECLE $500F       ; 5008  GRAM pictures      (points to NULL list)
        BIDECLE $5014       ; 500A  Cartridge title/date
        DECLE   $03C0       ; 500C  Skip ECS, run code after title, no clicks
        DECLE   $0000       ; 500D  Screen border control
        DECLE   $0000       ; 500E  0 = color stack, 1 = f/b mode
        DECLE   1,1,1,1,1   ; 500F  Initial color stack 0 and 1: Blue
       
        IF %year% < 2000    ; Y2K hurray!
            IF %year% < 78
                DECLE  %year% + 100 ; $5014
            ELSE
                DECLE  %year%       ; $5014
            ENDI
        ELSE
            DECLE  %year% - 1900    ; $5014
        ENDI
       
        STRING      %title%, 0
        WORDARRAY   _m.stk, %stacksize%     ; allocate stack in 16-bit RAM
        MVII        #_m.stk, R6

        ; If the ECS is not required just jump to the start address
        IF          _m.ECS = 0
        B           %startaddr%

        ELSE
        ; If the ECS is *required*, detect it and error out if it's not here
        MVII        #$A5,   R0  
        MVO         R0,     $4111
        CMP         $4111,  R0
        BNEQ        _m.no_ecs
        MVII        #$5A,   R0
        MVO         R0,     $4111
        CMP         $4111,  R0
        BEQ         %startaddr%

_m.no_ecs
        MVII        #_m.no_ecs, R0      
        MVO         R0,     $100
        SWAP        R0
        MVO         R0,     $101

        MVII        #_m.stk, R6
        
        MVO         R0,     $20
        MVI         $21,    R0
                   
        MVII        #2,     R0
        MVO         R0,     $28
        MVO         R0,     $2C

        MVII        #$200,  R4
_m.no_ecs_clr
        MVO@        R0,     R4
        CMPI        #$2F0,  R4
        BNEQ        _m.no_ecs_clr
                   
        MVII        #$200+6*20+1, R4
        MVII        #_m.no_ecs_err, R5
        MVII        #18,    R1
_m.no_ecs_loop     
        MVI@        R5,     R0
        MVO@        R0,     R4
        DECR        R1
        BNEQ        _m.no_ecs_loop
        EIS
        DECR        PC
                   
_m.no_ecs_err:     
_m.eidx QSET        0
        REPEAT      18
        DECLE       (ASC("ECS Unit Required!", _m.eidx) - 32) * 8 + 6
_m.eidx QSET        _m.eidx + 1
        ENDR

        ENDI

_m.cur  QSET    0
_m.0pos QSET    $

        __m_set_range 0

        LISTING "prev"
ENDM

;; ======================================================================== ;;
;;  ROMSEG  Switch between one of the various ROM segments.                 ;;
;;                                                                          ;;
;;  USAGE                                                                   ;;
;;      ROMSEG s                                                            ;;
;;                                                                          ;;
;;  where s is 0..n-1, and 'n' is the number of segments, determined by a   ;;
;;  previous call to ROMSETUP.                                              ;;
;;                                                                          ;;
;;  When the memory map is 16K, there are 3 ROM segments:                   ;;
;;                                                                          ;;
;;      SEGMENT     RANGE                                                   ;;
;;         0        $5000 - $6FFF                                           ;;
;;         1        $D000 - $DFFF                                           ;;
;;         2        $F000 - $FFFF                                           ;;
;;                                                                          ;;
;;  When the memory map is 42K, there are 6 ROM segments and an add'l RAM   ;;
;;  segment:                                                                ;;
;;                                                                          ;;
;;      SEGMENT     RANGE                                                   ;;
;;         0        $5000 - $6FFF                                           ;;
;;         1        $A000 - $C020                                           ;;
;;         2        $C022 - $FFFF                                           ;;
;;         3        $2000 - $2FFF                                           ;;
;;         4        $7100 - $7FFF                                           ;;
;;         5        $4800 - $4FFF                                           ;;
;;                                                                          ;;
;; ======================================================================== ;;
MACRO   ROMSEG s

    LISTING "code"
    IF %s% < _m.segs
        __m_chk_range
        __m_rec_range
        __m_set_range %s%
    ELSE
        ERR "Invalid segment number for memory map."
    ENDI

    LISTING "prev"
ENDM

;; ======================================================================== ;;
;;  ROMSEGSZ  Switch to a segment with room for at least "size" words.      ;;
;; ======================================================================== ;;
MACRO   ROMSEGSZ s

    LISTING "code"
        __m_chk_range
        __m_rec_range
_m.cur  SET -1
        __m_try_range 5, %s%
        __m_try_range 4, %s%
        __m_try_range 3, %s%
        __m_try_range 2, %s%
        __m_try_range 1, %s%
        __m_try_range 0, %s%
        IF (_m.cur = -1)
            ERR "ROMSEGSZ could not find a segment with %s% words available"
        ENDI
    LISTING "prev"
ENDM

;; ======================================================================== ;;
;;  ROMEND  Close any open segment, and set symbols in the assembler's own  ;;
;;          symbol table to indicate how much space was used and how much   ;;
;;          was left.                                                       ;;
;; ======================================================================== ;;
MACRO   ROMEND
    LISTING "code"
        __m_chk_range
        __m_rec_range
_m.0sz  SET _m.0pos - _m.0s 
_m.1sz  SET _m.1pos - _m.1s 
_m.2sz  SET _m.2pos - _m.2s 
_m.3sz  SET _m.3pos - _m.3s 
_m.4sz  SET _m.4pos - _m.4s 
_m.5sz  SET _m.5pos - _m.5s 
_m.size SET _m.0sz + _m.1sz + _m.2sz + _m.3sz + _m.4sz + _m.5sz
_m.0av  SET _m.0e - _m.0pos + 1
_m.1av  SET _m.1e - _m.1pos + 1
_m.2av  SET _m.2e - _m.2pos + 1
_m.3av  SET _m.3e - _m.3pos + 1
_m.4av  SET _m.4e - _m.4pos + 1
_m.5av  SET _m.5e - _m.5pos + 1
_m.avail SET _m.0av + _m.1av + _m.2av + _m.3av + _m.4av + _m.5av
    LISTING "prev"
ENDM


_m      PROC
@@16K   QEQU 0 ; default $5000-$6FFF, $D000-$DFFF, $F000-$FFFF
@@42K   QEQU 1 ; $4800-$6FFF, $7100-$7FFF, $A000-$C020, $C022-$FFFF,
               ; ... RAM $8040 - $9EFF
        ENDP

MACRO   __m_set_range_ r

        IF  _m.cur = %r%
_m.cs   QSET _m.%r%s
_m.ce   QSET _m.%r%e
_m.cpos QSET _m.%r%pos
        ENDI
        ORG _m.cpos
ENDM

MACRO   __m_rec_range_ r

        IF  _m.cur = %r%
_m.%r%pos QSET $
        ENDI
ENDM

MACRO   __m_set_range s
        
_m.cur  SET %s%
        __m_set_range_ 0
        __m_set_range_ 1
        __m_set_range_ 2
        __m_set_range_ 3
        __m_set_range_ 4
        __m_set_range_ 5
ENDM

MACRO   __m_rec_range
        
        __m_rec_range_ 0
        __m_rec_range_ 1
        __m_rec_range_ 2
        __m_rec_range_ 3
        __m_rec_range_ 4
        __m_rec_range_ 5
ENDM

MACRO   __m_overflow n
    IF _m.cur = %n%
        ERR "Segment overflow in segment %n%"
    ENDI
ENDM

MACRO   __m_chk_range

    IF  ($ < _m.cs) OR ($ - 1 > _m.ce)
        __m_overflow 0
        __m_overflow 1
        __m_overflow 2
        __m_overflow 3
        __m_overflow 4
        __m_overflow 5
    ENDI
ENDM

MACRO   __m_try_range r, s

    IF (_m.segs > %r%) AND (_m.%r%pos + %s% <= _m.%r%e + 1)
        __m_set_range %r%
    ENDI
ENDM

.SCRMEM     SET     $102
.SYSMEM     SET     $2F0
.ECSMEM     SET     $4040

;; ======================================================================== ;;
;;  _equorg Implement "EQU" by abusing "ORG" to avoid warnings              ;;
;; ======================================================================== ;;
MACRO       _equorg label, addr

_m.curorg   QSET    $
            LISTING "on"
%label%     ORG     %addr%
            LISTING "prev"
            ORG     _m.curorg
ENDM


;; ======================================================================== ;;
;;  BYTEVAR   label                                                         ;;
;;  WORDVAR   label                                                         ;;
;;  BYTEARRAY label, len                                                    ;;
;;  WORDARRAY label, len                                                    ;;
;;                                                                          ;;
;;  BYTEVAR and WORDVAR allocate single 8-bit and 16-bit variables in RAM,  ;;
;;  respectively, setting the labels to point to the allocated bytes/words. ;;
;;                                                                          ;;
;;  BYTEARRAY and WORDARRAY allocate blocks of 8-bit and 16-bit variables   ;;
;;  in RAM.  All locations in the block are contiguous.                     ;;
;;                                                                          ;;
;;  The WORDVAR and WORDARRAY macros will allocate from System RAM first,   ;;
;;  and then cartridge RAM second (if available).  The BYTEVAR and          ;;
;;  BYTEARRAY macros will allocate from Scratchpad RAM only, unless         ;;
;;  the program specifies "REQ_ECS" before ROMSETUP.                        ;;
;; ======================================================================== ;;
MACRO       BYTEARRAY label, len

            LISTING "off"
            IF .SCRMEM + %len% < $1F1
_m.alloc        QSET .SCRMEM
.SCRMEM         SET  .SCRMEM + %len%
            ELSE
                IF (_m.ECS = 0) OR (.ECSMEM + %len% > $4800)
                    ERR "8-bit RAM overflow allocating %label%"
                ENDI
_m.alloc        QSET .ECSMEM
.ECSMEM         SET  .ECSMEM + %len%
            ENDI

            _equorg %label%, _m.alloc
            LISTING "prev"
ENDM

MACRO       WORDARRAY label, len

            LISTING "off"
            IF .SYSMEM + %len% < $361
_m.alloc        QSET .SYSMEM
.SYSMEM         SET  .SYSMEM + %len%
            ELSE
                IF .CARTMEM + %len% > _m.rame
                    ERR "16-bit RAM overflow allocating %label%"
                ENDI
_m.alloc        QSET .CARTMEM
.CARTMEM        SET  .CARTMEM + %len%
            ENDI

            _equorg %label%, _m.alloc
            LISTING "prev"
ENDM

MACRO       BYTEVAR a

            LISTING "off"
            BYTEARRAY %a%, 1
            LISTING "prev"
ENDM

MACRO       WORDVAR a

            LISTING "off"
            WORDARRAY %a%, 1
            LISTING "prev"
ENDM

;; ======================================================================== ;;
;;  label   SCRATCH len                                                     ;;
;;  label   SYSTEM  len                                                     ;;
;;  label   ECSRAM  len                                                     ;;
;;  label   CARTRAM len                                                     ;;
;;                                                                          ;;
;;  Each of these macros allocates RAM from the same pools of memory as     ;;
;;  BYTEVAR, WORDVAR, etc.   There is a slight difference in syntax,        ;;
;;  though.  Also, SYSTEM/CARTRAM make explicit which memory you allocate   ;;
;;  from, whereas WORDVAR/WORDARRAY just allocates from the first memory    ;;
;;  with space available.                                                   ;;
;;                                                                          ;;
;;  The ECSRAM directive is special:  It always allocates from ECSRAM,      ;;
;;  regardless of whether a cartridge *requires* the ECS.  This makes it    ;;
;;  possible to allocate variables to the ECS that will only get used if    ;;
;;  the ECS is present.  The BYTEVAR/BYTEARRAY directives won't allocate    ;;
;;  from ECS's RAM unless you set REQ_ECS.                                  ;;
;; ======================================================================== ;;
MACRO       SCRATCH len
            EQU     .SCRMEM
            LISTING "off"
.SCRMEM     SET     .SCRMEM + %len%

            IF      .SCRMEM > $1F0
                ERR "8-bit Scratch RAM overflow"
            ENDI
            LISTING "prev"
ENDM

MACRO       ECSRAM  len
            EQU     .ECSMEM
            LISTING "off"
.ECSMEM     SET     .ECSMEM + %len%

            IF      .ECSMEM > $4800
                ERR "8-bit ECS RAM overflow"
            ENDI
            LISTING "prev"
ENDM


MACRO       SYSTEM   len
            EQU     .SYSMEM
            LISTING "off"
.SYSMEM     SET     .SYSMEM + %len%

            IF      .SYSMEM > $360
                ERR "16-bit System RAM overflow"
            ENDI
            LISTING "prev"
ENDM

MACRO       CARTRAM len
            EQU     .CARTMEM
            LISTING "off"
.CARTMEM    SET     .CARTMEM + %len%

            IF      .CARTMEM > _m.rame
                ERR "16-bit Cartridge RAM overflow"
            ENDI
            LISTING "prev"
ENDM

;; ======================================================================== ;;
;;  End of file:  cart.mac                                                  ;;
;; ======================================================================== ;;
