Get Firefox!

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