TI-99/4A Yin Yang Generator in Assembly
Matthew Hagerty, Feb 2006 (http://digitalstratum.com)
This code is public domain.
Win994a Simulator disk file: yinyang.TIDisk
|
Screen Shot Yin Yang in bitmap mode (graphics mode 2), about 10 seconds to generate.
|
Yin Yang - I've Come Full Circle
My first introduction to computers was around 1978 when my Dad was taking a computer course at Western Michigan University. He didn't have a sitter one night so he had to take the kids (my sister and I.) He stuck us in the room with the line printers (132 column, green/white banded paper) and we watched in awe as such things rolled out like ASCII art images of Snoopy on his dog house. It was so cool to an 8-year old!
Zoom ahead 5 or so years and I now have a computer of my very own, a TI-99/4A, and it even fits in the living room! After about a year of complaining about storing my programs on cassette tape, by parents finally broke down and bought me the PEB with E/A cartridge for Christmas. I was looking for something to do with the bitmap mode when I found some of my Dad's printouts from 1978, and one of them was a huge yin/yang, and low-and-behold at the bottom of the page was the source listing. Lucky for me it was in BASIC, had it been Cobol or Fortran I probably would have never made the port. I asked my Dad the story behind it a few months ago and he said, as far as he can recall, the idea was that of his professor and he (my father) did the actual implementation.
Anyway, trying not to bore you too much, I converted it to assembly in no time and waited with great anticipation as the image appeared, slowly, on the screen. I remember the image taking in the neighborhood of about 4 minutes to generate (about 1 second per line times 192 lines.) But like the line printers, I enjoyed watching it and I didn't know it could be faster.
I don't have my original TI code (one of the programs lost forever I'm afraid), but ironically I do still have those printouts from 1978!! So I dug out the yin/yang, relearned what the code was doing, and put 20+ years of programming behind the new version, as well as my new fangled VDP routines. Needless to say this version completes in about the time it took my original code to just initialize the bitmap mode. Better look out, I'm dangerous now! :-)
I have tested this with the Win994a Simulator on WinXP, but I have not had time to test it on my real TI yet, it should work though. It should assemble just fine with the E/A (make sure and use the R option), then run. Program name/entry point is called YIN.
I didn't write a functional description yet, but basically the way it works is by using The Pythagorean theorem (a^2+b^2=c^2) to quickly (relative to other circle generation functions) determine if a given x, y point is inside a circle. The c^2 part is precalculated, and x,y are used as the a^2 and b^2 parts. Nice clean integers and no trig functions.
Someone asked for the original BASIC source, so here it is as well. It was desiged for output on a 132 column printer where the characters are taller then they are wide, thus all the extra ratio conversion math making things look confusing.
Original DEC-10 BASIC Source
10 REM PRINTS YIN + YANG 20 FILES YIN 30 MARGIN #1,132 40 SCRATCH #1 50 PRINT "FOR COPY QUEUE YIN.BAS/U" 100 FOR J=1 TO 81 110 FOR K=1 TO 129 120 IF ((13*(K-65))/21)^2+(41-J)^2 > 1332.25 THEN 200 130 IF 30.25 >= ((13*(K-65))/21)^2+(23-J)^2 THEN 190 140 IF 342.25 >= ((13*(K-65))/21)^2+(23-J)^2 THEN 180 150 IF 30.25 >= ((13*(K-65))/21)^2+(59-J)^2 THEN 180 160 IF 342.25 >= ((13*(K-65))/21)^2+(59-J)^2 THEN 190 170 IF K > 65 THEN 190 180 PRINT #1," "; 185 GOTO 210 190 PRINT #1,"X"; 195 GOTO 210 200 PRINT #1,"."; 210 NEXT K 220 PRINT #1, 230 NEXT J 240 END
TI-99/4A TMS9900 Assembly Source
*
* Generates a Bitmap Yin/Yang on the TI-99/4A Computer
*
* Matthew Hagerty - March 2006
*
* This code is Public Domain
*
DEF YIN
* VDP MEMORY MAP
*
VDPRD EQU >8800 VDP RAM READ DATA
VDPSTA EQU >8802 VDP RAM STATUS
VDPWD EQU >8C00 VDP RAM WRITE DATA
VDPWA EQU >8C02 VDP RAM READ/WRITE ADDRESS
VR1CPY EQU >83D4 COPY OF VDP REGISTER 1 - SEE E/A MANUAL PG. 248
* WORKSPACES
*
WRKSP0 EQU >8300 WORKSPACE 0 FOR PROGRAM USE
WRKSP1 EQU >8320 WORKSPACE 1 FOR VDP AND RELATED ROUTINES
W1R0LB EQU WRKSP1+1 WORKSPACE 1 R0 LOW BYTE
* EQUATES
*
SIZE EQU 192
TWELVE EQU SIZE/12
HALF EQU SIZE/2
QTR EQU SIZE/4
QTR3 EQU SIZE/4*3
GREAT EQU HALF*HALF
HEAD EQU QTR*QTR
EYE EQU TWELVE*TWELVE
* DATA
*
* Pixel Look Up Table
PLUT DATA >8040,>2010,>0804,>0201
* Set up bitmap mode.
*
YIN LIMI 0 DISABLE INTERRUPTS
LWPI WRKSP1 USE VDP WORKSPACE 1
LI R0,>0002 SET BIT 6 OF VDP REG 0
BL @VWTR
LI R0,>0206 MOVE SCREEN IMAGE TABLE TO >1800
BL @VWTR
LI R0,>03FF MOVE COLOR TABLE TO >2000
BL @VWTR
LI R0,>0403 PATTERN DESCRIPTOR TABLE TO >0000
BL @VWTR
LI R0,>0536 MOVE SPRITE ATTRIBUTE LIST TO >1B00
BL @VWTR
LI R0,>1B00 DISABLE UNUSED SPRITES
LI R1,>D000 ALL SPRITES
BL @VSBW
* WRITE 0 - 255 THREE TIMES. USE AN INLINE VSBW FOR FASTER OPERATION.
*
LI R0,>1800 SCREEN IMAGE TABLE START ADDRESS
MOVB @W1R0LB,@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
CLR R1 START AT ZERO
LI R2,3 NUMBER OF TIMES TO LOOP 0 - 255 PATTERN
INIT1 MOVB R1,@VDPWD WRITE BYTE TO VDP RAM
AI R1,>0100 INC THE VALUE TO WRITE
JNE INIT1 CHECK IF DONE WRITING PATTERN
DEC R2 PATTERN COUNTER
JNE INIT1 CHECK IF DONE
* CLEAR PATTERN DESCRIPTOR TABLE
*
CLR R0 ADDRESS OF PDT >0000
CLR R1 VALUE TO WRITE - ALL ZERO
LI R2,>1800 NUMBER OF TIMES TO WRITE ZERO
BL @VSMW
* CLEAR COLOR TABLE
*
LI R0,>2000 ADDRESS OF COLOR TABLE >2000
LI R1,>FE00 DEFAULT COLOR IS WHITE ON GRAY
LI R2,>1800 NUMER OF TIMES TO WRITE ZERO
BL @VSMW
* INITIALIZE THE YIN YANG
*
LI R0,32 START ADDRESS X CENTERED
CLR R1 BIT PATTERN
CLR R3 CURRENT PIXEL FROM LEFT IN BYTE
CLR R4 X START = 0
CLR R5 Y START = 0
CLR R10 IF NOT 0, THEN HOLDS COLOR FOR CURRENT BYTE
* THIS IS USED IN EVERY CHECK, SO CALCULATE IT ONCE INSTEAD OF POSSIBLY
* FOUR TIMES.
* ((HALF - X) * (HALF - X))
*
MAINLP LI R6,HALF
S R4,R6 HALF - X
MPY R6,R6 R7 = (HALF-X)*(HALF-X)
* TEST IF OUTSIDE OF THE GREAT CIRCLE
* ((HALF - X) * (HALF - X)) + ((HALF - Y) * (HALF - Y)) > GREAT
*
LI R8,HALF
S R5,R8 HALF - Y
MPY R8,R8 R9 = (HALF-Y)*(HALF-Y)
A R7,R9
CI R9,GREAT
JLE NEXT0 IN GREAT CIRCLE, FIND OUT WHERE
CI R4,HALF SEE WHICH SIDE OF THE BACKGROUND WE ARE IN
JHE XLOOP ON THE LIGHT SIDE, DO NOTHING
SOCB @PLUT(R3),R1 ON DARK SIDE, SET BACKGROUND PIXEL
LI R10,>E100 SET THIS BYTE TO HAVE GRAY/BLACK COLOR
JMP XLOOP
* CHECK IF IN UPPER HALF OF GREAT CIRCLE, AND WITHIN THE EYE.
* ((HALF - X) * (HALF - X)) + ((QTR - Y) * (QTR - Y)) <= EYE
*
NEXT0 LI R8,QTR
S R5,R8 QTR - Y
MPY R8,R8 R9 = (QTR-Y)*(QTR-Y)
A R7,R9
CI R9,EYE
JGT NEXT1 NOT IN EYE
LI R10,>F100 IN BLACK EYE, SET COLOR WHITE/BLACK
JMP XLOOP
* CHECK IF IN UPPER HALF OF GREAT CIRCLE, AND WITHIN THE HEAD.
* ((HALF - X) * (HALF - X)) + ((QTR - Y) * (QTR - Y)) <= HEAD
*
NEXT1 CI R9,HEAD
JGT NEXT2 NOT IN HEAD
SOCB @PLUT(R3),R1 SET PIXEL
JMP XLOOP
* CHECK IF IN LOWER HALF OF GREAT CIRCLE, AND WITHIN THE EYE.
* ((HALF - X) * (HALF - X)) + ((QTR3 - Y) * (QTR3 - Y)) <= EYE
*
NEXT2 LI R8,QTR3
S R5,R8 QTR3 - Y
MPY R8,R8 R9 = (QTR3-Y)*(QTR3-Y)
A R7,R9
CI R9,EYE
JGT NEXT3 NOT IN EYE
SOCB @PLUT(R3),R1 SET PIXEL
LI R10,>F100 IN WHITE EYE, SET COLOR WHITE/BLACK
JMP XLOOP
* CHECK IF IN LOWER HALF OF GREAT CIRCLE, AND WITHIN THE HEAD.
* ((HALF - X) * (HALF - X)) + ((QTR3 - Y) * (QTR3 - Y)) <= HEAD
*
NEXT3 CI R9,HEAD
JGT NEXT4 NOT IN HEAD
CI R10,0 IF COLOR IS ALREADY SET, SKIP IT
JNE XLOOP
LI R10,>F100 IN DARK HEAD, SET COLOR TO WHITE/BLACK
JMP XLOOP
* IN A TAIL, WHICH ONE?
*
NEXT4 CI R4,HALF
JHE NEXT5 IN LIGHT TAIL
CI R10,0 IF COLOR IS ALREADY SET, SKIP IT
JNE XLOOP
LI R10,>F100 IN DARK TAIL, SET COLOR TO WHITE/BLACK
JMP XLOOP
NEXT5 SOCB @PLUT(R3),R1 SET PIXEL
* DONE WITH THIS PIXEL. IF THIS IS THE LAST PIXEL IN THE CURRENT
* BYTE, WRITE TO THE VDP.
*
XLOOP INC R3 NEXT PIXEL IN THE CURRENT BYTE
CI R3,8
JNE XLOOP1 MORE PIXELS IN THE BYTE
BL @VSBW WRITE THE CURRENT BYTE (8 PIXLES)
CI R10,0 TEST IF WE NEED TO SET THE COLOR FOR THIS BYTE
JEQ XLOOP0
AI R0,>2000 ADDRESS OF BYTE IN COLOR TABLE
MOV R10,R1 NEW COLOR VALUE
BL @VSBW WRITE COLOR BYTE
AI R0,->2000 ADJUST BYTE ADDRESS BACK TO PDT
CLR R10 RESET COLOR VALUE
XLOOP0 AI R0,8 ADDRESS OF NEXT BYTE IN CURRENT PIXEL ROW
CLR R1 RESET BIT PATTERN
CLR R3 RESET CURRENT PIXEL COUNT
XLOOP1 INC R4 NEXT X PIXEL
CI R4,SIZE
JHE YLOOP
JMP MAINLP
YLOOP INC R5
CI R5,SIZE
JHE DONE
LI R4,32 TEMPORARY X LOCATION FOR CENTERING
BL @PXADDR CALCULATE THE NEW BYTE ADDRESS
CLR R3 RESET PIXEL
CLR R4 RESET X
JMP MAINLP
DONE LIMI 2 ENABLE INTERRUPTS
QUIT JMP QUIT
***
*
* SUBROUTINES
*
***
* PIXEL OFFSET ADDRESS
*
* Calculate a byte and bit offset in bitmap mode. The register use
* is such that when done R0 contains the address offset for reading
* the existing byte from the pattern descriptor table into R1. Then
* set the correct pixel and write back to the pattern descriptor
* table.
*
* R4 = Column (X), R5 = Row (Y)
* R0 = Byte Offset, R3 = Bit to Change
*
PXADDR MOV R5,R0 ROW TO R0
SLA R0,5 DIV ROW BY 8 AND MUL BY 256
SOC R5,R0 ADD REMAINDER TO OFFSET
ANDI R0,>FF07 ADJUST FOR DIVISION
MOV R4,R3 COL TO R3
ANDI R3,7 COL MODULO 8
A R4,R0 ADD COL TO OFFSET
S R3,R0 ADJUST BY COL REMAINDER
RT
**
* VDP ROUTINES
**
* Uses a dedicated workspace in fast 16-bit RAM.
*
* General register use is:
* R0 VDP RAM starting address.
* R1 MSB contains the value to write or to receive a value when reading.
* For multipe byte reads/writes, contains the CPU buffer address.
* R2 Counter (counts down) for multiple reads/writes.
*
* WRITING VDP DATA
*
* VDP SINGLE BYTE WRITE - SINGLE BYTE MULTIPLE WRITE
*
* Writes the value in the most-significant byte of R1 to the
* VDP RAM address indicated in R0. For VSMW, the value in R2
* determines how many times to write the byte. This is very
* useful when initializing large amounts of VDP RAM for things
* such as clearing the screen or setting up bitmap mode.
*
VSBW LI R2,1 FORCE THE BYTE COUNT TO 1 FOR SINGLE BYTE WRITE
VSMW MOVB @W1R0LB,@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
ANDI R0,>3FFF WAIT FOR THE VDP AND RESTORE R0
VSMWLP MOVB R1,@VDPWD WRITE BYTE TO VDP RAM
DEC R2 BYTE COUNTER
JNE VSMWLP CHECK IF DONE
RT
* VDP MULTIPLE BYTE WRITE
*
* Writes the number of bytes indicated in R2 from the CPU RAM
* starting at the address indicated by R1 and places them in
* the VDP RAM starting at the address indicated by R0.
*
VMBW MOVB @W1R0LB,@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
ANDI R0,>3FFF WAIT FOR THE VDP AND RESTORE R0
VMBWLP MOVB *R1+,@VDPWD WRITE BYTE TO VDP RAM
DEC R2 BYTE COUNTER
JNE VMBWLP CHECK IF DONE
RT
*
* READING VDP DATA
*
* VDP SINGLE BYTE READ
*
* Reads a byte from VDP RAM address indicated in R0 and places
* it in the most-significate byte of R1.
*
VSBR MOVB @W1R0LB,@VDPWA SEND LOW BYTE OF VDP RAM WRITE ADDRESS
ANDI R0,>3FFF SET READ/WRITE BITS 14 AND 15 TO READ (00)
MOVB R0,@VDPWA SEND HIGH BYTE OF VDP RAM WRITE ADDRESS
NOP WAIT FOR THE VDP
MOVB @VDPRD,R1 READ BYTE FROM VDP RAM
RT
* VDP MULTIPLE BYTE READ
*
* Reads the number of bytes indicated in R2 from the VDP RAM
* starting at the address indicated by R0 and places them in
* the CPU RAM starting at the address indicated by R1.
*
VMBR MOVB @W1R0LB,@VDPWA SEND LOW BYTE OF VDP RAM WRITE ADDRESS
ANDI R0,>3FFF SET READ/WRITE BITS 14 AND 15 TO READ (00)
MOVB R0,@VDPWA SEND HIGH BYTE OF VDP RAM WRITE ADDRESS
NOP WAIT FOR THE VDP
VMBRLP MOVB @VDPRD,*R1+ READ BYTE FROM VDP RAM
DEC R2 BYTE COUNTER
JNE VMBRLP CHECK IF FINISHED
RT
*
* VDP REGISTERS
*
* VDP WRITE TO REGISTER
*
* Writes the value in the least-significant byte of R0 to the VDP
* register indicated in the most-significate byte of R0.
*
VWTR MOVB @W1R0LB,@VDPWA SEND LOW BYTE (VALUE) TO WRITE TO VDP REGISTER
ORI R0,>8000 SET UP A VDP REGISTER WRITE OPERATION
MOVB R0,@VDPWA SEND HIGH BYTE (ADDRESS) OF VDP REGISTER
RT
END



