TI-99/4A Yin Yang Generator in Assembly
Matthew Hagerty, Feb 2006 (http://digitalstratum.com)
This code is public domain.
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