;; ======================================================================== ;; ;; 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 ;; ;; ======================================================================== ;;