DEF START ** * Misc memory addresses VDPRD EQU >8800 ; VDP read data VDPSTA EQU >8802 ; VDP status VDPWD EQU >8C00 ; VDP write data VDPWA EQU >8C02 ; VDP set read/write address RNDSD EQU >83C0 ; Random number seed SOUND EQU >8400 ; Sound * VDP memory map NAMETB EQU >0000 PTRNTB EQU >0800 SPRPTB EQU >2000 TILATB EQU >3800 SPRATB EQU >3900 SPRLTB EQU >3980 GPUPRG EQU >3A00 ** * Scratch pad WRKSP EQU >8300 ; Workspace R0LB EQU WRKSP+1 ; R0 low byte required for VDP routines R1LB EQU WRKSP+3 R2LB EQU WRKSP+5 R3LB EQU WRKSP+7 R4LB EQU WRKSP+9 R5LB EQU WRKSP+11 R6LB EQU WRKSP+13 R7LB EQU WRKSP+15 STACK EQU >8320 COINC EQU STACK+>10 ********************************************************************* * * Main program execution starts here * AORG >A000 START LIMI 0 LWPI WRKSP LI R10,STACK BL @F18ADT ABS @F18A JEQ QUIT BL @GMODE BL @INIT * Init GPU routine * Set the GPU PC which also triggers it LI R0,GPUPRG/256+>3600 BL @VWTR LI R0,>3700 BL @VWTR * Loop LOOP BL @VSYNC BL @JOYST BL @CKQUIT JMP LOOP ********************************************************************* * * Check for quit * CKQUIT CLR R1 ; Test column 0 LI R12,>0024 ; Address for column selection LDCR R1,3 ; Select column LI R12,>0006 ; Address to read rows STCR R1,8 ANDI R1,>1100 JEQ QUIT * Return B *R11 * Quit QUIT BL @RSTPAL LI R0,>3280 ; Reset F18A BL @VWTR CLR @>83C4 ; Reset user IRS address BLWP @>0000 *// CKQUIT ********************************************************************* * * Wait for vertical retrace (CRU) * VSYNC CLR R12 VSYNC1 TB 2 ; Test CRU bit for VDP interrupt JEQ VSYNC1 MOVB @VDPSTA,R0 ANDI R0,>2000 MOV R0,@COINC B *R11 *// VSYNC *************************************************************************** * * Read joystick * JOYST MOV R11,*R10+ ; Push return address onto the stack * Read joystick using CRU LI R12,>0024 ; CRU address of the column decoder LI R0,>0600 ; Column 6, i.e joystick #1 LDCR R0,3 ; Select it LI R12,>0006 ; Base CRU address for joystick 1 * Reset variables CLR R3 ; Position change LI R4,-1 ; Speed change * Check right TB 2 JEQ JOYST1 LI R3,4 JMP JOYST2 * Check left JOYST1 TB 1 JEQ JOYST2 LI R3,-4 * Check down JOYST2 TB 3 JEQ JOYST3 LI R4,-2 JMP JOYST4 * Check up JOYST3 TB 4 JEQ JOYST4 LI R4,1 * Adjust position JOYST4 LI R0,CURVAT BL @VSWR NEG R1 A R3,R1 MPY @CARSPD,R1 SRA R2,4 A R2,@CARX MOV @CARX,R1 CI R1,159*64 JLT JOYST5 LI R1,159*64 JMP JOYST6 JOYST5 CI R1,-160*64 JGT JOYST6 LI R1,-160*64 JOYST6 MOV R1,@CARX LI R0,XCAR BL @VSWW * Adjust speed MOV @CARSPD,R1 A R4,R1 CI R1,8*64 JLT JOYST7 LI R1,8*64 JMP JOYST8 JOYST7 CI R1,0 JGT JOYST8 CLR R1 JOYST8 MOV R1,@CARSPD LI R0,SPEED BL @VSWW * Return DECT R10 ; Pop return address off the stack MOV *R10,R11 B *R11 *// JOYST ******************************************************************************** * * Init * INIT MOV R11,*R10+ ; Push return address onto the stack * Init palettes LI R0,0 LI R1,PAL0 LI R2,64 BL @SETPAL * Patterns LI R0,PTRNTB+>0000 LI R1,P0_0 LI R2,>800 BL @VMBW LI R0,PTRNTB+>0800 LI R1,P1_0 LI R2,>800 BL @VMBW LI R0,PTRNTB+>1000 LI R1,P2_0 LI R2,>800 BL @VMBW * Tile attributes LI R0,TILATB LI R1,TAT0 LI R2,>100 BL @VMBW * Name table LI R0,NAMETB LI R1,MD0 BL @CPYMAP LI R0,NAMETB+>400 LI R1,MD0+32 BL @CPYMAP * Sprite patterns LI R0,SPRPTB LI R1,S0_0 LI R2,12*32 BL @VMBW LI R0,SPRPTB+>0800 LI R1,S1_0 LI R2,12*32 BL @VMBW LI R0,SPRPTB+>1000 LI R1,S2_0 LI R2,12*32 BL @VMBW * Sprite attributes LI R0,SPRATB LI R1,SPRATT LI R2,4*8 BL @VMBW LI R0,SPRLTB LI R1,SPRLNK LI R2,8 BL @VMBW * Copy GPU code to VRAM LI R0,GPUPRG LI R1,GPUPR1 LI R2,GPUPR2-GPUPR1 BL @VMBW * Upload palettes LI R0,PALS LI R1,PAL1 LI R2,32 BL @VMBW * Init speed LI R0,SPEED MOV @CARSPD,R1 BL @VSWW * Return DECT R10 ; Pop return address off the stack MOV *R10,R11 B *R11 *// INIT ******************************************************************************** * * Copy map page to name table * * R0: Destination * R1: Source * CPYMAP MOV R11,*R10+ ; Push return address onto the stack BL @VWAD LI R3,30 CPYMA1 LI R2,32 CPYMA2 MOVB *R1+,@VDPWD DEC R2 JNE CPYMA2 AI R1,32 DEC R3 JNE CPYMA1 * Return DECT R10 ; Pop return address off the stack MOV *R10,R11 B *R11 *// CPYMAP ******************************************************************************** * * F18A detection * F18ADT MOV R11,*R10+ ; Push return address onto the stack * F18A Unlock LI R0,>391C ; VR1/57, value 00011100 BL @VWTR ; Write once BL @VWTR ; Write twice, unlock * Check the version status reg MOV @VDPSTA,R0 ; Clear status LI R0,>0F01 ; Set to read reg 1, Identity BL @VWTR MOVB @VDPSTA,R0 ANDI R0,>F000 CI R0,>E000 JNE F18AD1 SETO @F18A LI R0,>0F00 ; Set to read reg 0 BL @VWTR JMP F18AD2 F18AD1 CLR @F18A LI R0,>01E0 ; Set Reg 1 to a sane value BL @VWTR ; Write * Return F18AD2 DECT R10 ; Pop return address off the stack MOV *R10,R11 B *R11 F18A DATA >0000 *// F18ADT ********************************************************************************* * * Set graphics mode * GMODE MOV R11,*R10+ ; Push return address onto the stack LI R1,VREGS GMODE1 MOV *R1+,R0 JLT GMODE2 BL @VWTR ; Set register JMP GMODE1 * Clear VDP RAM GMODE2 CLR R0 CLR R1 LI R2,>4000 BL @VSMW * Return DECT R10 ; Pop return address off the stack MOV *R10,R11 B *R11 VREGS BYTE >00,>00 ; Graphics I mode BYTE >01,>62 ; 16K, display on, interrupt enabled BYTE >02,>00 ; NAMETB = >0000 BYTE >03,>E0 ; TILATB = >3800 BYTE >04,>01 ; PTRNTB = >0800 BYTE >05,>72 ; SPRATB = >2900 BYTE >06,>04 ; SPRPTB = >2000 BYTE >07,>00 ; Backdrop color BYTE >1D,>22 ; Page size 2x1 BYTE >1E,>08 ; Max sprites per scan line BYTE >31,>7F ; 30 rows, real sprite Y, link, ECM 3 for tiles and sprites BYTE >33,>08 ; Stop sprite BYTE >FF,>FF ; End *// GMODE ******************************************************************************** * * Restore palette * RSTPAL MOV R11,*R10+ ; Push return address onto the stack CLR R0 LI R1,STDPAL LI R2,16 BL @SETPAL * Return DECT R10 ; Pop return address off the stack MOV *R10,R11 B *R11 STDPAL DATA >0000 ; Black DATA >0000 ; Black DATA >02C3 ; Medium Green DATA >05D6 ; Light Green DATA >054F ; Dark Blue DATA >076F ; Light Blue DATA >0D54 ; Dark Red DATA >04EF ; Cyan DATA >0F54 ; Medium Red DATA >0F76 ; Light Red DATA >0DC3 ; Dark Yellow DATA >0ED6 ; Light Yellow DATA >02B2 ; Dark Green DATA >0C5C ; Magenta DATA >0CCC ; Gray DATA >0FFF ; White *// RSTPAL ******************************************************************************** * * Set palette * * R0: Start palette index * R1: Source palette data * R2: Number of entries * SETPAL MOV R11,*R10+ ; Push return address onto the stack * Load color paletttes ORI R0,>2FC0 ; Reg 47, DPM = 1, AUTO INC = 1, start PR BL @VWTR * Every two bytes written to the VDP now go to the palette registers. SLA R2,1 ; Each 12-bit palette entry requires 2 bytes SETPA1 MOVB *R1+,@VDPWD DEC R2 JNE SETPA1 LI R0,>2F00 ; Reg 47, exit DMP BL @VWTR * Return DECT R10 ; Pop return address off the stack MOV *R10,R11 B *R11 *// SETPAL ******************************************************************************** * * VDP Set Write Address * * R0: Address to set VDP address counter to * VWAD MOVB @R0LB,@VDPWA ; Send low byte of VDP RAM write address ORI R0,>4000 ; Set the two MSbits to 01 for write MOVB R0,@VDPWA ; Send high byte of VDP RAM write address ANDI R0,>3FFF ; Restore R0 top two MSbits B *R11 *// VWAD ******************************************************************************** * * VDP Single Byte Write * * R0: Write address in VDP RAM * R1: MSB of R1 sent to VDP RAM * * R0 is modified, but can be restored with: ANDI R0,>3FFF * VSBW MOVB @R0LB,@VDPWA ; Send low byte of VDP RAM write address ORI R0,>4000 ; Set read/write bits 14 and 15 to write (01) MOVB R0,@VDPWA ; Send high byte of VDP RAM write address MOVB R1,@VDPWD ; Write byte to VDP RAM B *R11 *// VSBW ******************************************************************************** * * VDP Single Word Write * * R0: Write address in VDP RAM * R1: Word to send to VDP RAM * * R0 is modified, but can be restored with: ANDI R0,>3FFF * VSWW MOVB @R0LB,@VDPWA ; Send low byte of VDP RAM write address ORI R0,>4000 ; Set read/write bits 14 and 15 to write (01) MOVB R0,@VDPWA ; Send high byte of VDP RAM write address MOVB R1,@VDPWD ; Write byte to VDP RAM MOVB @R1LB,@VDPWD ; Write byte to VDP RAM B *R11 *// VSWW ******************************************************************************** * * VDP Multiple Byte Write * * R0: Starting write address in VDP RAM * R1: Starting read address in CPU RAM * R2: Number of bytes to send to the VDP RAM * * R0 is modified, but can be restored with: ANDI R0,>3FFF * VMBW MOVB @R0LB,@VDPWA ; Send low byte of VDP RAM write address ORI R0,>4000 ; Set read/write bits 14 and 15 to write (01) MOVB R0,@VDPWA ; Send high byte of VDP RAM write address VMBWLP MOVB *R1+,@VDPWD ; Write byte to VDP RAM DEC R2 ; Byte counter JNE VMBWLP ; Check if done B *R11 *// VMBW ******************************************************************************** * * VDP Single Byte Multiple Write * * R0: Starting write address in VDP RAM * R1: MSB of R1 sent to VDP RAM * R2: Number of times to write the MSB byte of R1 to VDP RAM * * R0 is modified, but can be restored with: ANDI R0,>3FFF * VSMW MOVB @R0LB,@VDPWA ; Send low byte of VDP RAM write address ORI R0,>4000 ; Set read/write bits 14 and 15 to write (01) MOVB R0,@VDPWA ; Send high byte of VDP RAM write address VSMWLP MOVB R1,@VDPWD ; Write byte to VDP RAM DEC R2 ; Byte counter JNE VSMWLP ; Check if done B *R11 *// VSMW ******************************************************************************** * * VDP Write To Register * * R0 MSB: VDP register to write to * R0 LSB: Value to write * VWTR MOVB @R0LB,@VDPWA ; Send low byte (value) to write to VDP register ORI R0,>8000 ; Set up a VDP register write operation (10) MOVB R0,@VDPWA ; Send high byte (address) of VDP register B *R11 *// VWTR ********************************************************************* * * Fast CPU to VDP copy, replaces VMBW * * R0: Destination address * R1: Source address * R2: Number of bytes to copy * VDPCP SWPB R0 MOVB R0,@VDPWA ; Send low byte of VDP RAM write address SWPB R0 ORI R0,>4000 ; Set the two MSbits to 01 for write MOVB R0,@VDPWA ; Send high byte of VDP RAM write address LI R0,VDPWD MOV R2,R3 SRL R3,3 ; Number of groups of 8 JEQ VDPC2 VDPC1 MOVB *R1+,*R0 MOVB *R1+,*R0 MOVB *R1+,*R0 MOVB *R1+,*R0 MOVB *R1+,*R0 MOVB *R1+,*R0 MOVB *R1+,*R0 MOVB *R1+,*R0 DEC R3 JNE VDPC1 ANDI R2,>0007 ; Isolate number of remaining bytes JEQ VDPC3 VDPC2 MOVB *R1+,*R0 DEC R2 JNE VDPC2 VDPC3 B *R11 *// VDPCP ******************************************************************************** * * VDP Single Word Read * * R0: Read address in VDP RAM * * On return R1 contains the word read. * VSWR MOVB @R0LB,@VDPWA ; Send low byte of VDP RAM write address MOVB R0,@VDPWA ; Send high byte of VDP RAM write address MOVB @VDPRD,R1 ; Read byte from VDP RAM MOVB @VDPRD,@R1LB ; Read byte from VDP RAM B *R11 *// VSWR ********************************************************************* * * F18A GPU program * HILLSY EQU 120 ROADY1 EQU 136 ROADY2 EQU 239 ROADH EQU ROADY2-ROADY1+1 HEIGHT EQU 240 GPUPR1 XORG GPUPRG LI R15,>47FE ; Set up stack pointer * Set the HSYNC trigger LI R0,>4000 MOVB R0,@>6032 ; Write to VR 50 JMP GPU0 RETURN IDLE * HSYNC routine CLR R3 ; For storing scanline MOVB @>7000,R3 ; Get scanline no SWPB R3 ; Swap to LSB INC R3 ; Changes affect next line since HSYNC is at end CI R3,HEIGHT ; Check if last scanline JGT RETURN ; Greater - return JLT GPUP14 ; Smaller - continue GPU0 CLR R3 ; Equals - wrap to 0 * Now R3 holds the actual affected scanline * After last scanline, i.e. before scanline 0, setup next screen * Loop through scanlines, bottom to top, and calculate the X (scroll) offset * Also keep track of min and max values LI R0,(ROADH-1)*2+XOFFS ; Point to X offset for last scanline LI R1,ROADY2 ; Scanline number LI R2,ROADH ; Number of road scanlines MOV @XPOS,R4 ; X (FP 10.6) (currently just zero) S @XCAR,R4 ; Subtract players desired car position CLR R5 ; DX (FP 10.6) LI R6,>7FFF ; Min X, set to max int LI R7,->8000 ; Max X, set to min int MOV @SEGPOS,R10 ; Segment position as FP 14.2 SRL R10,2 ; Convert to integer GPUP1 MOV R4,*R0 ; Save X offset for scanline C R4,R6 ; Compare to minimum JGT GPUP2 ; Greater - continue MOV R4,R6 ; Save as minimum GPUP2 C R4,R7 ; Compare to maximum JLT GPUP3 ; Smaller - continue MOV R4,R7 ; Save as maximum GPUP3 C R1,R10 ; Compare scanline number to segment position JL GPUP4 ; If smaller we are in the top segment MOV @SEGBOT,R8 ; Get bottom segment pointer JMP GPUP5 GPUP4 MOV @SEGTOP,R8 ; Get top segment pointer GPUP5 A *R8,R5 ; DX += DDX A R5,R4 ; X += DX DECT R0 DEC R1 DEC R2 JNE GPUP1 * Check min and max values CLR R9 ; Reset X adjustment AI R6,160*64 ; Min X + 160 (FP 10.6) JGT GPUP6 ; If zero or greater JEQ GPUP6 ; it's within range -160 - 159 MOV R6,R9 ; Otherwise save adjustment JMP GPUP7 GPUP6 AI R7,-159*64 ; Max X - 159 (FP 10.6) JLT GPUP7 ; If zero or smaller JEQ GPUP7 ; it's within range -160 - 159 MOV R7,R9 ; Otherwise save adjustment * Loop again to adjust X values GPUP7 SRA R9,1 ; Divide adjustment by 2 JEQ GPUP9 ; Skip if zero LI R0,XOFFS LI R2,ROADH GPUP8 MOV *R0,R4 ; Get X offset S R9,R4 ; Adjust MOV R4,*R0+ ; Write X offset DEC R2 JNE GPUP8 * Adjust sprite in opposite direction SRA R9,6 ; Convert to integer NEG R9 AI R9,128-32 JGT GPU20 CLR R9 JMP GPU21 GPU20 CI R9,256-64 JLT GPU21 LI R9,256-64 GPU21 SWPB R9 MOVB R9,@SPRATB+1 * Clouds and hills offsets GPUP9 MOV @SEGBOT,R0 MOV *R0,R0 ; Get curvature for bottom segment (FP 10.6) MPY @SPEED,R0 ; Multiply by speed (FP 10.6) SRA R1,2 ; Scale S R1,@CLOUDX SLA R1,1 ; Hills move with double speed of clouds S R1,@HILLSX * Update position MOV @SPEED,R0 SRL R0,4 A R0,@ZPOS * Move segment MOV @SEGPOS,R1 SRL R0,3 A R0,R1 GPUP10 CI R1,HEIGHT*4 ; Bottom scanline in FP 14.2 JLE GPUP13 * Bottom segment out of screen MOV @SEGTOP,R0 MOV R0,@SEGBOT ; Bottom segment = top segment CI R0,SEGLSE ; Check if top segment is last in list JEQ GPUP11 INCT R0 ; Not last - get next JMP GPUP12 GPUP11 LI R0,SEGLST ; Last - get first GPUP12 MOV R0,@SEGTOP ; Set top segment LI R1,ROADY1*4 ; Top road scanline in FP 14.2 * Save segment position GPUP13 MOV R1,@SEGPOS ; Set segment position * Save a copy of curvature for bottom segment MOV @SEGBOT,R0 MOV *R0,@CURVAT * Scrolling: branch on landscape type GPUP14 CI R3,ROADY1 ; Is it road? JHE GPUP16 ; Yes - jump to road CI R3,HILLSY ; Is it hills? JHE GPUP15 ; Yes - jump to hills * Clouds / sky MOV @CLOUDX,R1 JMP GPUP17 * Hills GPUP15 MOV @HILLSX,R1 JMP GPUP17 * Road GPUP16 MOV R3,R1 ; Scanline AI R1,-ROADY1 ; Translate first road scanline to 0 SLA R1,1 ; Convert to word MOV @XOFFS(R1),R1 ; Get offset from table * Scroll scanline GPUP17 LI R2,>2000 ; Center of horizontal scrolling in FP 10.6 S R1,R2 ; Subtract scanline offset MOV R2,R1 ; Copy SLA R1,2 ; Convert to FP 8.8 MOVB R1,@>601B ; Set scroll register ANDI R2,>4000 ; Isolate bit that decides name table SRL R2,6 ; Move bit into place MOVB R2,@>6002 ; Set name table register to 0 or 1 * Road markings CI R3,ROADY1 ; Test scanline JLT GPUP19 ; No road above line ROADH1 * Get scanline depth from map MOV R3,R1 ; Get the scanline AI R1,-ROADY1 ; Translate first road scanline to 0 SLA R1,1 ; Convert to word MOV @ZTBL(R1),R1 ; Get depth from table * Decide which palette to use MOV @ZPOS,R2 ; Get position SLA R2,6 ; Convert to 8.8 FP A R2,R1 ; Add position to make road marking move ANDI R1,>1000 ; 0000-0FFF pal 0, 1000-1FFF pal 1 SRL R1,8 ; Shift bit to fit size of palette AI R1,PALS ; Add palette base * Set palette LI R2,8 ; Number of colors/words in a palette LI R0,>5010 ; Palette reg 8 GPUP18 MOV *R1+,*R0+ ; Copy palette word DEC R2 ; Counter JNE GPUP18 ; Copy loop * Return GPUP19 B @RETURN XPOS DATA 0 ; X position of bottom, middle of road (FP 10.6) ZPOS DATA 0 ; Z position - position on track (FP 14.2) SPEED DATA 0 ; Speed (FP 10.6) XCAR DATA 0 SEGPOS DATA ROADY1*4 ; Segment position on screen (FP 14.2) SEGBOT DATA SEGLST ; Bottom segment pointer SEGTOP DATA SEGLST+2 ; Top segment pointer CURVAT DATA 0 CLOUDX DATA 0 ; X offset of clouds (FP 10.6) HILLSX DATA 0 ; X offset of hills (FP 10.6) XOFFS BSS 104*2 ; X offsets for each road scanline (FP 10.6) * Road curvatures (DDX) in FP 10.6 format SEGLST DATA >0000 DATA >0001 DATA >0002 DATA >0001 DATA >0002 DATA >0003 DATA >0004 DATA ->0003 DATA >0001 DATA >0000 DATA >0000 DATA ->0002 DATA >0003 DATA >0000 DATA ->0001 SEGLSE DATA ->0002 ********************************************************************* * * Depth table * ZTBL DATA >FFFF,>F5C2,>EC4E,>E38D,>DB6D,>D3DC,>CCCC,>C631 DATA >BFFF,>BA2E,>B4B4,>AF8A,>AAAA,>A60D,>A1AF,>9D89 DATA >9999,>95DA,>9249,>8EE2,>8BA2,>8888,>8590,>82B9 DATA >8000,>7D63,>7AE1,>7878,>7627,>73EC,>71C7,>6FB5 DATA >6DB6,>6BCA,>69EE,>6822,>6666,>64B8,>6318,>6186 DATA >6000,>5E86,>5D17,>5BB3,>5A5A,>590B,>57C5,>5689 DATA >5555,>542A,>5307,>51EB,>50D7,>4FCA,>4EC5,>4DC5 DATA >4CCD,>4BDA,>4AED,>4A06,>4924,>4848,>4771,>469F DATA >45D1,>4508,>4444,>4384,>42C8,>4210,>415C,>40AC DATA >4000,>3F57,>3EB1,>3E0F,>3D70,>3CD5,>3C3C,>3BA6 DATA >3B13,>3A83,>39F6,>396B,>38E3,>385E,>37DB,>375A DATA >36DB,>365F,>35E5,>356D,>34F7,>3483,>3411,>33A1 DATA >3333,>32C7,>325C,>31F3,>318C,>3127,>30C3,>3061 PALS BSS 32 ********************************************************************* AORG GPUPR2 CARX DATA 0 CARSPD DATA 0 COPY "road.a99" * Sprite attributes SPRATT BYTE 192,96,0,6 BYTE 0,16,4,6 BYTE 0,32,8,6 BYTE 0,48,12,6 BYTE 16,0,16,6 BYTE 16,16,20,6 BYTE 16,32,24,6 BYTE 16,48,28,6 * Sprite linking SPRLNK BYTE 0,32,32,32,32,32,32,32 END START