+TheBF Posted October 8, 2021 Author Share Posted October 8, 2021 I have something that seems useful now although some code that I have in my libraries breaks when loaded in SAMS. Need to understand why. This version runs faster because MAPPER is now smart and does not map in a SAMS page if it is already in memory. I will do a benchmark comparison between CPU RAM and SAMS memory. Here is the code Spoiler \ Code in SAMS memory based on concept in TurboForth by Mark Wills \ Ported to Camel99 Forth Oct 7, 2021 \ Changes from original: \ Remove bank stack. Used return stack for bank# storage \ Removed BANKS word. Changed to preset DP array \ CMAP is a fast sub-routine for mapping SAMS pages F0..FF \ CMAP Remembers the last bank that was paged in variable LASTBNK \ Changed to compile a far "colon" definition to reduce dictionary overhead \ Oct 7, 2021: Compares input to CMAP to LAstbnk and \ does nothing if page is still in memory \ FAR word data structure: \ CELL: link \ BYTE: immediate field \ BYTE: name length \ BYTES: <....> \ CELL: code field \ CELL: DATA field #1 , bank# \ CELL: DATA field #2 , SAMS code field address INCLUDE DSK1.MARKER INCLUDE DSK1.VALUES \ INCLUDE DSK1.TOOLS INCLUDE DSK1.ASM9900 HERE F0 VALUE _1STBANK FF VALUE _MAXBANK \ SAMS memory management for code HEX 3000 CONSTANT CSEG \ code seg in CPU RAM 4000 CSEG 0B RSHIFT + CONSTANT CREG \ compute CSEG SAMS register CSEG 0C RSHIFT CONSTANT PASSTHRU \ default page for CSEG DECIMAL CREATE []DP \ DP for 0 .. 15 pages of SAMS CSEG , CSEG , CSEG , CSEG , CSEG , CSEG , CSEG , CSEG , CSEG , CSEG , CSEG , CSEG , CSEG , CSEG , CSEG , CSEG , HEX CODE ]DP ( bank# -- addr ) TOS _1STBANK NEGATE AI, \ remove first bank offset to index []DP TOS TOS ADD, TOS []DP AI, NEXT, ENDCODE VARIABLE SAVHERE \ temp holder for RAM Dictionary pointer VARIABLE BANK# \ active SAMS bank# for compiling VARIABLE LASTBNK \ last bank# passed to MAPPER CODE BANK#@ ( -- bank#) \ Used 8 times so this saves space and is fast TOS PUSH, BANK# @@ TOS MOV, NEXT, ENDCODE HEX CREATE MAPPER ( R1: 0 .. 16 ) \ smart MAPPER R1 LASTBNK @@ CMP, \ already mapped? NE IF, R1 LASTBNK @@ MOV, \ update the last bank used R1 SWPB, \ swap bytes R12 1E00 LI, \ DO CMAP 0 SBO, \ turn on the card R1 CREG @@ MOV, \ restore bank# from return stack 0 SBZ, \ turn off card ENDIF, RT, CODE CMAP ( bank# --) \ Forth word to map SAMS pages TOS R1 MOV, MAPPER @@ BL, TOS POP, NEXT, ENDCODE CODE GOTO ( addr -- ) *IP IP MOV, NEXT, ENDCODE CREATE FARCOL \ run time executor for SAMS colon words. IP RPUSH, W IP MOV, RP DECT, LASTBNK @@ *RP MOV, \ Rpush the active bank *IP+ R1 MOV, \ fetch bank# from DATA FIELD -> R1, inc IP MAPPER @@ BL, \ & switch to SAMS page for this word *IP IP MOV, \ get SAMS DP & set new IP NEXT, : FAR: ( -- ) \ special colon for words in FAR memory !CSP HEADER \ compile Forth header with name FARCOL , \ compile the new executor as CFA BANK#@ , \ compile bank# as the DATA field BANK#@ ]DP @ , \ compile this word's SAMS address ( ie: FAR XT) HERE SAVHERE ! \ save "normal here" BANK#@ ]DP @ DP ! \ set dp to CSEG. Compiling goes here now BANK#@ CMAP \ map SAMS for compiling HIDE ] \ turn on the compiler ; HEX CODE FAREXIT R1 RPOP, MAPPER @@ BL, IP RPOP, NEXT, ENDCODE : FARSEMIS ( -- ) POSTPONE FAREXIT POSTPONE [ REVEAL ?CSP ; IMMEDIATE : ;FAR ( -- ) \ end banked compilation POSTPONE GOTO SAVHERE @ , HERE BANK#@ ]DP ! \ update here for bank SAVHERE @ DP ! \ restore dp to "normal" memory POSTPONE FARSEMIS ; IMMEDIATE DECIMAL : SETBANK ( bank# -- ) \ 0..15 are valid args DUP 256 240 WITHIN ABORT" Bad bank number" BANK# ! ; HEX : _BFREE ( -- n) 4000 BANK#@ ]DP @ - ; : .BFREE ( -- ) DECIMAL CR ." Bank# " BANK#@ . ." , " _BFREE . ." bytes free." CR ; HERE SWAP - DECIMAL CR . .( bytes) \ free 11,566 PASSTHRU CMAP \ init the Forth memory bank# as LASTBNK \ REMOVE-TOOLS HEX F0 SETBANK Here is a test suite using a SAMS wordlist to hold a the SAMS version of : and ; plus all the SAMS words. Spoiler \ redirecting : and ; INCLUDE DSK1.WORDLISTS ONLY FORTH DEFINITIONS \ rename normal : ; so we don't over-ride them and can still use them : H: : ; : ;H POSTPONE ; ; IMMEDIATE VOCABULARY SAMS ALSO SAMS DEFINITIONS \ Put : and ; in SAMS VOCABULARY as aliased FAR: ;FAR H: : FAR: ;H H: ; POSTPONE ;FAR ;H IMMEDIATE \ SAMS must first in the search order to find the special \ version of : and ; FORTH DEFINITIONS INCLUDE DSK1.DEFER \ Defer seems to break SAMS compiling ?? SAMS DEFINITIONS HEX F0 SETBANK INCLUDE DSK1.TOOLS SAMS DEFINITIONS HEX F1 SETBANK INCLUDE DSK1.ANSFILES INCLUDE DSK1.CATALOG INCLUDE DSK1.MORE SAMS DEFINITIONS HEX F2 SETBANK INCLUDE DSK1.GRAFIX INCLUDE DSK1.DIRSPRIT INCLUDE DSK1.COOLSPRITE ONLY SAMS ALSO FORTH DEFINITIONS COMPILE-2-SAMS.mp4 1 Quote Link to comment Share on other sites More sharing options...
+TheBF Posted October 8, 2021 Author Share Posted October 8, 2021 So I did a benchmark program in two versions. One that lives in completely in CPU RAM. The 2nd has the outer loop in SAMS F9 but the inner loop is in SAMS F8. This forces a cross SAMS page call. Here is the code. Spoiler \ sams benchmark vs CPU RAM INCLUDE DSK1.WORDLISTS ONLY FORTH DEFINITIONS INCLUDE DSK1.VALUES INCLUDE DSK1.ELAPSE \ rename normal : ; so we don't over-ride them and can still use them : H: : ; : ;H POSTPONE ; ; IMMEDIATE \ redirecting : and ; VOCABULARY SAMS ALSO SAMS DEFINITIONS H: : FAR: ;H H: ; POSTPONE ;FAR ;H IMMEDIATE FORTH DEFINITIONS HEX 5 CONSTANT FIVE 100 CONSTANT MASK 0 VALUE BVAR : INNERBENCH BEGIN DUP SWAP DUP ROT DROP 1 AND IF FIVE + ELSE 1- THEN TO BVAR BVAR DUP MASK AND UNTIL ; : BENCHIE MASK 0 DO 1 INNERBENCH DROP LOOP ; \ __________________________________________ SAMS DEFINITIONS F8 SETBANK \ Force a call to another SAMS bank : INNERBENCH BEGIN DUP SWAP DUP ROT DROP 1 AND IF FIVE + ELSE 1- THEN TO BVAR BVAR DUP MASK AND UNTIL ; F9 SETBANK : BENCHIE MASK 0 DO 1 INNERBENCH DROP LOOP ; FORTH ELAPSE BENCHIE SAMS ELAPSE BENCHIE The screen shots tell the tale. Pages were toggling... Speed is VERY good. 3 Quote Link to comment Share on other sites More sharing options...
+TheBF Posted October 12, 2021 Author Share Posted October 12, 2021 I was doing some folder housekeeping and found something I tried to do a long time ago. I guess I have learned something over the last few years because it was clear to me that it was all wrong. The objective was to make a character pattern compiler the lets you "draw" your character patterns in text in your source code like these examples. CREATE BIG-O \ 01234567 | **** | | * * | | * * | | * * | | * * | | * * | | * * | | **** | CREATE SQUARE \ 01234567 | ********| | ********| | ********| | ********| | ********| | ********| | ********| | ********| The better idea was to convert a string of asterisks and spaces into a string of ones and zeros. A string of ones and zeros can be "evaluated" by Forth as a binary number. That number can then be compiled into memory as one byte of a pattern definition. It's not nearly as fancy as a program that let's you draw on the screen but its a heck of lot easier than HEX bytes in a string. Here is the code with the examples above. Code Edit: Removed HOLDER buffer and used PAD instead. Saves bytes Spoiler \ Character shape compiler using text Oct 12, 2021 B Fox DECIMAL VARIABLE PH : BINARY 2 BASE ! ; : DIGIT! ( byte -- ) PAD PH @ + C! \ store byte in PAD buffer PH 1+! \ increment the PAD pointer 1 PAD C+! ; \ increment the count byte : STAR># ( char -- ) \ convert '*' to one or space to zero [CHAR] * = IF [CHAR] 1 DIGIT! \ store characters in PAD ELSE [CHAR] 0 DIGIT! THEN ; : >STAR#S ( addr len -- addr len) \ convert string of * and spaces to 1s and zeros PAD OFF 1 PH ! \ init PAD and point position BOUNDS DO I C@ STAR># LOOP PAD COUNT ; \ return PAD as a stack string : | ( -- ) [CHAR] | PARSE-WORD >STAR#S \ parse string, convert to binary # string BINARY EVALUATE C, ; \ evaluate the string & compile the byte HEX ( these are in DSK1.GRAFIX ) : ]PDT ( char# -- 'pdt[n] ) 8* 800 + ; : CHARDEF ( addr char# --) ]PDT 8 VWRITE ; CREATE BIG-O \ 01234567 | **** | | * * | | * * | | * * | | * * | | * * | | * * | | **** | CREATE SQUARE \ 01234567 | ********| | ********| | ********| | ********| | ********| | ********| | ********| | ********| DECIMAL BIG-O 0 CHARDEF SQUARE 1 CHARDEF CR 0 EMIT SPACE 1 EMIT \ You can remove the shape compiler from the system when you are done with it. \ but the character patterns DATA stays in VDP RAM. \ FORGET PH 2 1 Quote Link to comment Share on other sites More sharing options...
GDMike Posted October 12, 2021 Share Posted October 12, 2021 9 hours ago, TheBF said: I was doing some folder housekeeping and found something I tried to do a long time ago. I guess I have learned something over the last few years because it was clear to me that it was all wrong. The objective was to make a character pattern compiler the lets you "draw" your character patterns in text in your source code like these examples. CREATE BIG-O \ 01234567 | **** | | * * | | * * | | * * | | * * | | * * | | * * | | **** | CREATE SQUARE \ 01234567 | ********| | ********| | ********| | ********| | ********| | ********| | ********| | ********| The better idea was to convert a string of asterisks and spaces into a string of ones and zeros. A string of ones and zeros can be "evaluated" by Forth as a binary number. That number can then be compiled into memory as one byte of a pattern definition. It's not nearly as fancy as a program that let's you draw on the screen but its a heck of lot easier than HEX bytes in a string. Here is the code with the examples above. Reveal hidden contents \ Character shape compiler using text Oct 12, 2021 B Fox VARIABLE PH DECIMAL CREATE HOLDER 10 ALLOT HOLDER 10 0 FILL : BINARY 2 BASE ! ; : DIGIT! ( byte -- ) HOLDER PH @ + C! \ store byte in holder string PH 1+! \ increment the holder pointer 1 HOLDER C+! ; \ increment the count byte : STAR># ( char -- ) \ convert '*' to one or space to zero [CHAR] * = IF [CHAR] 1 DIGIT! \ store characters in HOLDER ELSE [CHAR] 0 DIGIT! THEN ; : >STAR#S ( addr len -- addr len) \ convert string of * and spaces to 1s and zeros HOLDER OFF 1 PH ! \ init HOLDER and point position BOUNDS DO I C@ STAR># LOOP HOLDER COUNT ; \ return HOLDER as a stack string : | ( -- ) [CHAR] | PARSE-WORD >STAR#S \ parse string, convert to binary # string BINARY EVALUATE C, ; \ evaluate the string & compile the byte HEX ( these are in DSK1.GRAFIX ) : ]PDT ( char# -- 'pdt[n] ) 8* 800 + ; : CHARDEF ( addr char# --) ]PDT 8 VWRITE ; CREATE BIG-O \ 01234567 | **** | | * * | | * * | | * * | | * * | | * * | | * * | | **** | CREATE SQUARE \ 01234567 | ********| | ********| | ********| | ********| | ********| | ********| | ********| | ********| DECIMAL BIG-O 0 CHARDEF SQUARE 1 CHARDEF CR 0 EMIT SPACE 1 EMIT \ You can even remove the shape compiler from the system when you are done with it. \ but the character patterns DATA stays in VDP RAM. FORGET PH Neat. Cool Quote Link to comment Share on other sites More sharing options...
+TheBF Posted October 12, 2021 Author Share Posted October 12, 2021 I updated the code above to remove the HOLDER buffer and just used PAD. Then I realized if you wanted to, the compiler could just put the bytes directly into VDP RAM in the pattern table. You just replace C, with VC, Then you just need a word to set the VDP memory pointer to the correct location in the pattern table for the character you want to change. This code does that. So no CPU RAM is used to hold the patterns. The downside of course is that the patterns have to be re-compiled each time you want them. But I see this as simple way to design patterns on real iron. Spoiler \ Character shape compiler Direct to VDP Oct 12, 2021 B Fox : TASK ; INCLUDE DSK1.VDPMEM DECIMAL VARIABLE PH : BINARY 2 BASE ! ; : DIGIT! ( byte -- ) PAD PH @ + C! \ store byte in PAD buffer PH 1+! \ increment the PAD pointer 1 PAD C+! ; \ increment the count byte : STAR># ( char -- ) \ convert '*' to one or space to zero [CHAR] * = IF [CHAR] 1 DIGIT! \ store characters in PAD ELSE [CHAR] 0 DIGIT! THEN ; : >STAR#S ( addr len -- addr len) \ convert string of * and spaces to 1s and zeros PAD OFF 1 PH ! \ init PAD and point position BOUNDS DO I C@ STAR># LOOP PAD COUNT ; \ return PAD as a stack string : | ( -- ) [CHAR] | PARSE-WORD >STAR#S \ parse string, convert to binary # string BINARY EVALUATE VC, ; \ evaluate the string & compile the byte HEX 800 CONSTANT PDT : SHAPE ( ascii -- ) 8* PDT + VP ! ; \ set VDP memory to the char's pattern table DECIMAL 0 SHAPE \ 01234567 | **** | | * * | | * * | | * * | | * * | | * * | | * * | | **** | 1 SHAPE \ 01234567 | ********| | ********| | ********| | ********| | ********| | ********| | ********| | ********| CHAR A SHAPE \ 01234567 | | | * | | * * | | * * | | ***** | | * * | | * * | | | DECIMAL CR 0 EMIT CR 1 EMIT CR 65 EMIT \ Remove shape compiler from the system when you are done with it. \ FORGET TASK 2 Quote Link to comment Share on other sites More sharing options...
+TheBF Posted October 14, 2021 Author Share Posted October 14, 2021 Obsessed with SAMS I have managed to take out 4 more bytes per definition. Two in SAMS memory and two in the forth Dictionary entry. I removed the GOTO SAVEHERE line and replaced it with FARSEMIS. This is a run-time exit from a SAMS definition so it can go in SAMS memory at the end of a definition. The caller's IP address is pulled from the return stack so that's that. Changing the dictionary pointer is a compile time complication so that is handled by ;FAR when we compile a new SAMS word. My ultimate goal here is to get the entire FAR: definition living in SAMS memory and then modify FIND to search through SAMS banks first in declining order before searching Forth. This will make FAR: definitions look similar to ANS Forth wordlists but we can have a lot more of these SAMS lists than wordlists. I only have room for 9 in the kernel. Spoiler \ Code in SAMS memory based on concept in TurboForth by Mark Wills \ Ported to Camel99 Forth Oct 13, 2021 \ Changes from original: \ Remove bank stack. Used return stack for bank# storage \ Removed BANKS word. Changed to preset DP array \ CMAP is a fast sub-routine for mapping SAMS pages F0..FF \ CMAP Remembers the last bank that was paged in variable LASTBNK \ Changed to compile a far "colon" definition to reduce dictionary overhead \ Oct 7, 2021: Compares input to CMAP to LAstbnk and \ does nothing if page is still in memory \ Oct 13, 2021 Remove GOTO in FAR exit. Not needed. \ Compile FARSEMIS on the SAMS side, not in RAM Dictionary \ Saves 2 bytes in dictionary and in 2 bytes SAMS definition \ FAR word data structure: \ CELL: link \ BYTE: immediate field \ BYTE: name length \ BYTES: <....> \ CELL: code field \ CELL: DATA field #1 , bank# \ CELL: DATA field #2 , SAMS code field address \ 10 bytes + name length INCLUDE DSK1.MARKER INCLUDE DSK1.ASM9900 HERE F0 CONSTANT _1STBANK FF CONSTANT _MAXBANK \ SAMS memory management for code HEX 3000 CONSTANT CSEG \ code seg in CPU RAM 4000 CSEG 0B RSHIFT + CONSTANT CREG \ compute CSEG SAMS register CSEG 0C RSHIFT CONSTANT PASSTHRU \ default page for CSEG DECIMAL CREATE []DP \ DP for 0 .. 15 pages of SAMS CSEG , CSEG , CSEG , CSEG , CSEG , CSEG , CSEG , CSEG , CSEG , CSEG , CSEG , CSEG , CSEG , CSEG , CSEG , CSEG , HEX CODE ]DP ( bank# -- addr ) TOS _1STBANK NEGATE AI, \ remove first bank offset to index []DP TOS TOS ADD, TOS []DP AI, NEXT, ENDCODE VARIABLE SAVHERE \ temp holder for RAM Dictionary pointer VARIABLE BANK# \ active SAMS bank# for compiling VARIABLE LASTBNK \ last bank# passed to MAPPER CODE BANK#@ ( -- bank#) \ Used 8 times so this saves space and is fast TOS PUSH, BANK# @@ TOS MOV, NEXT, ENDCODE HEX CREATE MAPPER ( R1: 0 .. 16 ) \ smart MAPPER R1 LASTBNK @@ CMP, \ already mapped? NE IF, R1 LASTBNK @@ MOV, \ update the last bank used R1 SWPB, \ swap bytes R12 1E00 LI, \ DO CMAP 0 SBO, \ turn on the card R1 CREG @@ MOV, \ restore bank# from return stack 0 SBZ, \ turn off card ENDIF, RT, CODE CMAP ( bank# --) \ Forth word to map SAMS pages TOS R1 MOV, MAPPER @@ BL, TOS POP, NEXT, ENDCODE CREATE FARCOL \ run time executor for SAMS colon words. IP RPUSH, W IP MOV, RP DECT, LASTBNK @@ *RP MOV, \ Rpush the active bank *IP+ R1 MOV, \ fetch bank# from DATA FIELD -> R1, inc IP MAPPER @@ BL, \ & switch to SAMS page for this word *IP IP MOV, \ get SAMS DP & set new IP NEXT, : FAR: ( -- ) \ special colon for words in FAR memory !CSP HEADER \ compile Forth header with name FARCOL , \ compile the new executor as CFA BANK#@ , \ compile bank# as the DATA field BANK#@ ]DP @ , \ compile this word's SAMS address ( ie: FAR XT) HERE SAVHERE ! \ save "normal here" BANK#@ ]DP @ DP ! \ set dp to CSEG. Compiling goes here now BANK#@ CMAP \ map SAMS for compiling HIDE ] \ turn on the compiler ; HEX CODE FAREXIT R1 RPOP, \ get bank# save by FARCOL MAPPER @@ BL, \ map it in IP RPOP, \ Regular FORTH EXIT NEXT, ENDCODE : FARSEMIS ( -- ) POSTPONE FAREXIT POSTPONE [ \ turn compiler off REVEAL ?CSP ; IMMEDIATE : ;FAR ( -- ) \ end banked compilation POSTPONE FARSEMIS \ compile ending in SAMS part of definition HERE BANK#@ ]DP ! \ update here for this bank SAVHERE @ DP ! \ restore dp to CPU RAM ; IMMEDIATE DECIMAL : SETBANK ( bank# -- ) \ 0..15 are valid args DUP 256 240 WITHIN ABORT" Bad bank number" BANK# ! ; HEX : _BFREE ( -- n) 4000 BANK#@ ]DP @ - ; : .BFREE ( -- ) DECIMAL CR ." Bank# " BANK#@ . ." , " _BFREE . ." bytes free." CR ; HERE SWAP - DECIMAL CR . .( bytes) \ free 11,566 PASSTHRU CMAP \ init the Forth memory bank# as LASTBNK \ REMOVE-TOOLS HEX F0 SETBANK I have also moved my INCLUDE line buffer into high RAM. I was using PAD but when INCLUDEing into a SAMS block PAD is in the SAMS memory and so it prevents using the last 127 bytes of the SAMS bank. The other thing that is interesting is how compact headless Forth code is. All these files compiled into 4K with 380 bytes to spare. Of course there was another 1800 bytes of header code over in CPU RAM. (I need to make a way to leverage this idea to create headless programs that can be saved as EA5 images.) DECIMAL 240 SETBANK HEX INCLUDE DSK1.ELAPSE INCLUDE DSK1.UDOTR INCLUDE DSK1.ANSFILES INCLUDE DSK1.CATALOG INCLUDE DSK1.DIR INCLUDE DSK1.MORE INCLUDE DSK1.BUFFER INCLUDE DSK1.COMPARE INCLUDE DSK1.CASE INCLUDE DSK1.VALUES INCLUDE DSK1.TRAILING INCLUDE DSK1.SYNONYM INCLUDE DSK1.3RD4TH INCLUDE DSK1.MALLOC INCLUDE DSK1.VDPMEM INCLUDE DSK1.FASTCASE INCLUDE DSK1.ASMLABELS INCLUDE DSK1.GRAFIX INCLUDE DSK1.DIRSPRIT INCLUDE DSK1.COOLSPRITE 4 Quote Link to comment Share on other sites More sharing options...
GDMike Posted October 14, 2021 Share Posted October 14, 2021 (edited) Did a cool thing here. Awesome stuff Edited October 14, 2021 by GDMike 1 Quote Link to comment Share on other sites More sharing options...
+TheBF Posted October 21, 2021 Author Share Posted October 21, 2021 Sometimes maintenance is worthwhile. I had hacked together a version of -TRAILING like this. (-TRAILING removes trailing spaces from a string) CODE -TRAILING ( addr len -- addr len') *SP W MOV, \ DUP addr in W TOS W ADD, \ add len R1 2000 LI, \ R1 holds the space character W DEC, \ w=address of last char BEGIN, *W R1 CMPB, \ compare to ascii >20 EQ WHILE, W DEC, \ move back 1 char TOS DEC, \ dec len in TOS REPEAT, TOS 0 CI, 1 JGT, TOS CLR, NEXT, ENDCODE Looking at it I didn't like having two DEC instructions inside the loop and I really didn't like the negative length test at the end. This is about 13% faster. It seems to test solidly. HEX CODE -TRAILING ( addr len -- addr len') *SP TOS ADD, \ add len TOS DEC, R1 2000 LI, \ R1 holds the space character BEGIN, *TOS R1 CMPB, \ compare to ascii >20 EQ WHILE, TOS DEC, \ move back 1 char REPEAT, *SP TOS SUB, TOS INC, NEXT, ENDCODE 4 Quote Link to comment Share on other sites More sharing options...
+TheBF Posted October 23, 2021 Author Share Posted October 23, 2021 "It takes nine months to have a baby no matter how many women you put on the job." (Fred Brookes, The Mythical Man Month) Well actually my newest granddaughter arrived in 8 months plus one week, about 2 weeks ago and she is really cute. Unlike little baby Ivory, my documentation for the linker took longer than it should but here it is. I included @FarmerPotato 's disk with some additions of my own to aid in the getting used to this beasty. I have been testing on an unreleased version of Camel99 (2.68F) but it should work on the latest released version. Maybe it is of some use to someone out there. :) Erik I believe your original question was because you have some bit map graphics code that you want to run under Forth. If you want to post it here I can take a run at making it work (if it assembles with the standard TI Assembler) LINKER99.zip 4 Quote Link to comment Share on other sites More sharing options...
HOME AUTOMATION Posted October 23, 2021 Share Posted October 23, 2021 Congratulations!? Grandpa!?? 1 1 Quote Link to comment Share on other sites More sharing options...
GDMike Posted October 24, 2021 Share Posted October 24, 2021 Congratulations papa.. 1 Quote Link to comment Share on other sites More sharing options...
+TheBF Posted October 28, 2021 Author Share Posted October 28, 2021 I have a feeling that I posted this already but it might have been on comp.lang.forth. Can't find the title here so here goes... While looking for more crazy people on the internet who like stack architectures I re-found this paper by another Canuck, Charles LaForet who did his undergrad at Waterloo U. This paper has some neat history in the Historical Review chapter (where else) on the origins of Polish notation, reverse Polish notation and even some very early stack machines. There is also a ton of research on pros and cons of registers and stacks. I love the dedication page! http://fpgacpu.ca/stack/Second-Generation_Stack_Computer_Architecture.pdf 1 Quote Link to comment Share on other sites More sharing options...
+TheBF Posted November 2, 2021 Author Share Posted November 2, 2021 @Retrospect created some really nice demos for XB256 in the Billy Ball topic and I just couldn't resist trying to do this with Camel99 Forth. I have zero imagination about games so I need to leverage his kind of imagination if I ever want to push the system. @Retrospect did an excellent job IMHO of organizing the code around a large set of sub-routines that live in a loop to control all the activities. That is the normal way to do this kind of thing. I wondered if I could do it with a set of tasks that manage the motion of Billy and Bobby independently and also manage the shooting in a task. I don't have it done yet but the concept seems to work. It was always risky for me to try since I have to stand on the pyramid of code that I built over the past few years. Something that is interesting about this idea, something I don't think about much, but it's called re-entrant code. This is code that runs with it's own copy of data so that many tasks can use the same routine at the same time. I tried this with ROTATOR and BOUNCER, using the same code to run Billy Ball and Bobby Ball. It's kind of neat. Forth is kind a natural for this if you use the data stack for everything or use special "user variables" that can be created for each task. Another handy thing is Forth's MS word which looks like a delay in milli-seconds but while it's counting down it passes control to the next task in the task queue so everything keeps running smoothly. This makes delays much easier to manage. Here is the code and the video shows the current state of what it can do. (not very much) I also compiled MTOOLS in the video so I could show some of the stats of the tasks. The system is looking pretty stable when I can compile code while sprites are moving. The Pulse number at the end of the video is the time it takes for the entire task queue to be visited and return to the console task. 4 mS is not too bad for our favourite old computer on four active tasks. Gotta love those workspace context switches!. Spoiler \ BILLYBALL XB256 DEMO by @Retrospect on atariage.com Nov 1 2021 \ Test harness for Camel99 forth B Fox INCLUDE DSK1.TOOLS \ DEBUG ONLY INCLUDE DSK1.MARKER INCLUDE DSK1.MALLOC INCLUDE DSK1.GRAFIX INCLUDE DSK1.RANDOM INCLUDE DSK1.SOUND INCLUDE DSK1.DIRSPRIT \ direct control sprites INCLUDE DSK1.MTASK99 \ *********************** \ CHAR DEFINITION HELPERS \ *********************** DECIMAL : CHARDEF32 ( data[] ascii# -- ) ]PDT 32 VWRITE ; \ def 4 chars at once \ convert long text string to 16 bit HEX numbers and \ compile each number into memory sequentially : HEX#, ( addr len --) BASE @ >R \ save radix HEX \ converting string to hex numbers BEGIN DUP WHILE \ while len<>0 OVER 4 \ used 4 digits from left end of string NUMBER? ABORT" Bad number" \ convert string to number , \ compile the integer into memory 4 /STRING \ cut 4 digits off left side of string REPEAT \ keep going until string is exhausted 2DROP R> BASE ! \ restore radix ; \ *********************** \ * BALL ANIMATION DEFS * \ *********************** \ Compile contiguos data for each frame of Ball animation CREATE BALLS ( patterns for 23 chars ) S" 00030F1F3F3C787A787F7F3C3E1F0F0300E0F8FCFE9E8FAF8FFFFF1E3EFCF8E0" HEX#, S" 00030F1F3F397175717F7F383C1F0F0300E0F8FCFE3E1F5F1FFFFF3E7EFCF8E0" HEX#, S" 00030F1F3F32626A627F7F30381F0F0300E0F8FCFE7E3FBF3FFFFF7EFEFCF8E0" HEX#, S" 00030F1F3F244455447F7F20311F0F0300E0F8FCFEFE7F7F7FFFFFFEFEFCF8E0" HEX#, S" 00030F1F3F09082A087F7F01231F0F0300E0F8FCFEFEFFFFFFFFFFFEFEFCF8E0" HEX#, S" 00030F1F3F131155117F7F03071F0F0300E0F8FCFEFEFFFFFFFFFFFEFEFCF8E0" HEX#, S" 00030F1F3F27232B237F7F070F1F0F0300E0F8FCFEFEFFFFFFFFFFFEFEFCF8E0" HEX#, S" 00030F1F3F0F4757477F7F0F3F1F0F0300E0F8FCFEFEFFFFFFFFFFFEFEFCF8E0" HEX#, S" 00030F1F3F1F0F2F0F7F7F1F3F1F0F0300E0F8FCFEFEFFFFFFFFFFFEFEFCF8E0" HEX#, S" 00030F1F3F1F1F5F1F7F7F3F3F1F0F0300E0F8FCFEFEFFFFFFFFFFFEFEFCF8E0" HEX#, S" 00030F1F3F3F7F7F7F7F7F3F3F1F0F0300E0F8FCFEFEFFFFFFFFFFFEFEFCF8E0" HEX#, S" 00030F1F3F3F7F7F7F7F7F3F3F1F0F0300E0F8FCFEFEFFFFFFFFFFFEFEFCF8E0" HEX#, S" 00030F1F3F3F7F7F7F7F7F3F3F1F0F0300E0F8FCFEFEFFFFFFFFFFFEFEFCF8E0" HEX#, S" 00030F1F3F3F7F7F7F7F7F3F3F1F0F0300E0F8FCFEFEFFFFFFFFFFFEFEFCF8E0" HEX#, S" 00030F1F3F3F7F7F7F7F7F3F3F1F0F0300E0F8FCFEFCFCFDFCFFFFFEFEFCF8E0" HEX#, S" 00030F1F3F3F7F7F7F7F7F3F3F1F0F0300E0F8FCFEFCF8FAF8FFFFFCFEFCF8E0" HEX#, S" 00030F1F3F3F7F7F7F7F7F3F3F1F0F0300E0F8FCFEF8F1F5F1FFFFF8FEFCF8E0" HEX#, S" 00030F1F3F3F7F7F7F7F7F3F3F1F0F0300E0F8FCFEF2E2EAE2FFFFF0F8FCF8E0" HEX#, S" 00030F1F3F3F7F7F7F7F7F3F3F1F0F0300E0F8FCFEE4C4D5C4FFFFE0F0FCF8E0" HEX#, S" 00030F1F3F3F7F7F7F7F7F3F3F1F0F0300E0F8FCFEC888AA88FFFFC0E2FCF8E0" HEX#, S" 00030F1F3F3F7F7F7F7F7F3F3F1F0F0300E0F8FCFE92115511FFFF82C6FCF8E0" HEX#, S" 00030F1F3F3F7E7E7E7F7F3F3F1F0F0300E0F8FCFE2623AB23FFFF068EFCF8E0" HEX#, S" 00030F1F3F3E7C7D7C7F7F3E3F1F0F0300E0F8FCFE4E475747FFFF0E1EFCF8E0" HEX#, \ expose BALLS as an array of 32 byte records : ]BALL ( n -- addr ) 32 * BALLS + ; CREATE EXPLOSION S" 0030787C3E1C0070FCF8F83103030100000E1E1C382000071F0F8680C0E08000" HEX#, CREATE MISSLE S" 000000000000000000000F1F0F00000000000000000000000000E0F0E0000000" HEX#, \ ************** \ * STAR CHR'S * \ ************** DECIMAL CREATE STARS 160 , 168 , 176 , 184 , 192 , 200 , 208 , : ]STAR ( n -- addr) CELLS STARS + ; PAD CHAR . CHARPAT \ read '.' char pattern PAD 0 ]STAR CHARDEF \ assign to star characters PAD 1 ]STAR CHARDEF PAD 2 ]STAR CHARDEF PAD 3 ]STAR CHARDEF PAD 4 ]STAR CHARDEF PAD 5 ]STAR CHARDEF PAD 6 ]STAR CHARDEF \ ***************************** \ MAKE SPRITES \ ***************************** DECIMAL 2 MAGNIFY 128 CONSTANT Billy 132 CONSTANT Bobby 136 CONSTANT Missle 02 CONSTANT WEAPON ( char colr x y sp# -- ) Billy 16 10 10 0 SPRITE Bobby 5 215 10 1 SPRITE Missle 2 20 20 WEAPON SPRITE MISSLE 136 CHARDEF32 \ ********************* \ Multi-Task actions must be in an endless loop. Control with WAKE/SLEEP \ ********************* : ROTATOR ( char -- ) BEGIN 23 0 DO I ]BALL OVER CHARDEF32 60 MS LOOP AGAIN ; : BOUNCER ( spr# --) BEGIN 100 10 DO I OVER SP.Y VC! 40 MS LOOP 10 100 DO I OVER SP.Y VC! 40 MS -1 +LOOP AGAIN ; : SPARKLER ( stars[] -- ) ; \ INC/DEC byte in VDP RAM : +!V ( n Vaddr -- ) S" TUCK VC@ + SWAP VC!" EVALUATE ; IMMEDIATE : STOP ( pid -- ) SLEEP PAUSE ; DECIMAL : SHOOT ( -- ) 7 NOISE 0 DB 16 0 DO I DB 30 MS PAUSE LOOP MUTE ; : EXPLODE ( -- ) 4 NOISE 0 DB 100 MS 5 NOISE 16 0 DO PAUSE I DB 60 MS LOOP MUTE ; : FIRE ( sp.X sp.Y -- ) SHOOT 0 POSITION WEAPON LOCATE \ Put weapon inside sprite 0 9 WEAPON SP.COLOR \ give it a color 8 DB 240 0 DO 1 WEAPON SP.X +!V 1 WEAPON 10 COINC IF 1 WEAPON SP.COLOR EXPLODE LEAVE THEN PAUSE LOOP MUTE 1 WEAPON SP.COLOR ; : SPIN-BILL Billy ROTATOR ; : SPIN-BOB Bobby ROTATOR ; : BOUNCE-BILL 0 BOUNCER ; : BOUNCE-BOB 1 BOUNCER ; \ task creator : TASK: ( n -- ) USIZE MALLOC DUP FORK CONSTANT ; \ returns PID (address) DECIMAL TASK: JOB1 TASK: JOB2 \ BILLY TASK: JOB3 TASK: JOB4 \ BOBBY TASK: JOB5 \ stars ' SPIN-BILL JOB1 ASSIGN ' BOUNCE-BILL JOB2 ASSIGN ' SPIN-BOB JOB3 ASSIGN ' BOUNCE-BOB JOB4 ASSIGN MULTI CLEAR BillyBall-tests.mp4 2 Quote Link to comment Share on other sites More sharing options...
Retrospect Posted November 2, 2021 Share Posted November 2, 2021 That's great! Seeing it "live" like that whilst you've got the parser running is weird! Glad you're having fun with it. I won't be able to get my head around this Forth stuff though but it looks mega cool. If you wanna do an explosion for bobby that continue whilst other stuff is going on , in my XB256 example I had a variable called EX2 and EXT2 ... EX2 is turned off whilst Bobby is alive. When he's hit, EX2 is on. Most of the routines that involve the movement and firing of Bobby have a check to see if EX2 is switched on. If it is, his movement routine is ignored, as are the Bobby firing routines and more importantly it ignores the coincidences for Billy's missiles against bobby. The explosion time counts up using EXT2, till a certain value is reached and then it's over. EX1 and EXT1 are for Billy but they're unused in my example as it was just a scribble of sorts but I decided to document my scribble with good comments so that other people can scribble too. 1 1 Quote Link to comment Share on other sites More sharing options...
GDMike Posted November 2, 2021 Share Posted November 2, 2021 Hmm, maybe throwing a pie instead of shooting a bullet instead though. Lol 1 1 Quote Link to comment Share on other sites More sharing options...
+TheBF Posted November 2, 2021 Author Share Posted November 2, 2021 29 minutes ago, Retrospect said: That's great! Seeing it "live" like that whilst you've got the parser running is weird! Glad you're having fun with it. I won't be able to get my head around this Forth stuff though but it looks mega cool. If you wanna do an explosion for bobby that continue whilst other stuff is going on , in my XB256 example I had a variable called EX2 and EXT2 ... EX2 is turned off whilst Bobby is alive. When he's hit, EX2 is on. Most of the routines that involve the movement and firing of Bobby have a check to see if EX2 is switched on. If it is, his movement routine is ignored, as are the Bobby firing routines and more importantly it ignores the coincidences for Billy's missiles against bobby. The explosion time counts up using EXT2, till a certain value is reached and then it's over. EX1 and EXT1 are for Billy but they're unused in my example as it was just a scribble of sorts but I decided to document my scribble with good comments so that other people can scribble too. Ya the Forth stuff is definitely strange. And then it gets stranger with the separate tasks. It has taken me a while time to get somewhat comfortable with it. They way you describe your explosion method is how you do it when everything is in a common loop. (If I understand your code and what you wrote correctly) In theory.... I can make an explosion task. I just tell it to start and it will play it's explosion and even do some kind of fragmentation of poor Bobby and then it puts itself to sleep when it's finished. That will all happen while everything else is still running. How cool is that? So that's what I will try and see how it works out. It is a really different brain process when you can make these "baby computers" (tasks) and give them loops that have nothing to do with the main program. It is not free however. Each task in this system takes 192 bytes for the registers and system variables and each switch to task takes a some time as well. As I said before I am really grateful for your well commented examples and creative ideas. 1 Quote Link to comment Share on other sites More sharing options...
+TheBF Posted November 2, 2021 Author Share Posted November 2, 2021 39 minutes ago, GDMike said: Hmm, maybe throwing a pie instead of shooting a bullet instead though. Lol LOL. That could be arranged. I guess it should be pumpkin with Thanksgiving coming up. 1 Quote Link to comment Share on other sites More sharing options...
+TheBF Posted November 4, 2021 Author Share Posted November 4, 2021 Make that Camel Sweat So @Retrospect just keeps cranking out the demonstrations. The one that put all the "balls in the air" was interesting for me to see what happens if I created 10 tasks to move the balls and 10 more to spin the balls. Overall the results are not bad IMHO. The overhead per task when 20 are running looks like ~1.2 mS per task at least that is what my measurements say. The code is not beautiful. I just used lots of text to define everything. I would have to add a bit more code to dynamically spawn and destroy tasks but that was not important for this test. As you can see with a cooperative tasker when I added spin it loaded the system down even though I am not spinning very quickly. Some of that is because we are calling VMBW which takes the entire machine until it is finished changing 4 character patterns. I could fix that as well if this was a real application by chunking the pattern into small pieces but that might make the animation chunky too. No such thing as a free lunch. Another thing is that waiting for a key using KSCAN is about a 1.1mS affair so that slows all the task switching down as well. The WAIT in the demo calls >0020 which scans only for BREAK and is much faster. You can what happens when I use it. Anyway this was good workout for the Camel Multi-tasker. And so far everything worked as expected. Spoiler \ OHBALLS XB256 DEMO by @Retrospect on atariage.com Nov 4 2021 \ Test harness for Camel99 forth B Fox INCLUDE DSK1.TOOLS \ DEBUG ONLY INCLUDE DSK1.MARKER INCLUDE DSK1.MALLOC INCLUDE DSK1.GRAFIX INCLUDE DSK1.RANDOM INCLUDE DSK1.SOUND INCLUDE DSK1.DIRSPRIT \ direct control sprites INCLUDE DSK1.MTASK99 INCLUDE DSK1.MTOOLS \ *********************** \ CHAR DEFINITION HELPERS \ *********************** DECIMAL \ pause makes this multi-tasker friendly : CHARDEF32 ( data[] ascii# -- ) PAUSE ]PDT 32 VWRITE ; \ def 4 chars at once \ convert long text string to 16 bit HEX numbers and \ compile each number into memory sequentially : HEX#, ( addr len --) BASE @ >R \ save radix HEX \ converting string to hex numbers BEGIN DUP WHILE \ while len<>0 OVER 4 \ used 4 digits from left end of string NUMBER? ABORT" Bad number" \ convert string to number , \ compile the integer into memory 4 /STRING \ cut 4 digits off left side of string REPEAT \ keep going until string is exhausted 2DROP R> BASE ! \ restore radix ; \ *********************** \ * BALL ANIMATION DEFS * \ *********************** \ Compile contiguos data for each frame of Ball animation CREATE BALLS ( patterns for 23 chars ) S" 00030F1F3F3C787A787F7F3C3E1F0F0300E0F8FCFE9E8FAF8FFFFF1E3EFCF8E0" HEX#, S" 00030F1F3F397175717F7F383C1F0F0300E0F8FCFE3E1F5F1FFFFF3E7EFCF8E0" HEX#, S" 00030F1F3F32626A627F7F30381F0F0300E0F8FCFE7E3FBF3FFFFF7EFEFCF8E0" HEX#, S" 00030F1F3F244455447F7F20311F0F0300E0F8FCFEFE7F7F7FFFFFFEFEFCF8E0" HEX#, S" 00030F1F3F09082A087F7F01231F0F0300E0F8FCFEFEFFFFFFFFFFFEFEFCF8E0" HEX#, S" 00030F1F3F131155117F7F03071F0F0300E0F8FCFEFEFFFFFFFFFFFEFEFCF8E0" HEX#, S" 00030F1F3F27232B237F7F070F1F0F0300E0F8FCFEFEFFFFFFFFFFFEFEFCF8E0" HEX#, S" 00030F1F3F0F4757477F7F0F3F1F0F0300E0F8FCFEFEFFFFFFFFFFFEFEFCF8E0" HEX#, S" 00030F1F3F1F0F2F0F7F7F1F3F1F0F0300E0F8FCFEFEFFFFFFFFFFFEFEFCF8E0" HEX#, S" 00030F1F3F1F1F5F1F7F7F3F3F1F0F0300E0F8FCFEFEFFFFFFFFFFFEFEFCF8E0" HEX#, S" 00030F1F3F3F7F7F7F7F7F3F3F1F0F0300E0F8FCFEFEFFFFFFFFFFFEFEFCF8E0" HEX#, S" 00030F1F3F3F7F7F7F7F7F3F3F1F0F0300E0F8FCFEFEFFFFFFFFFFFEFEFCF8E0" HEX#, S" 00030F1F3F3F7F7F7F7F7F3F3F1F0F0300E0F8FCFEFEFFFFFFFFFFFEFEFCF8E0" HEX#, S" 00030F1F3F3F7F7F7F7F7F3F3F1F0F0300E0F8FCFEFEFFFFFFFFFFFEFEFCF8E0" HEX#, S" 00030F1F3F3F7F7F7F7F7F3F3F1F0F0300E0F8FCFEFCFCFDFCFFFFFEFEFCF8E0" HEX#, S" 00030F1F3F3F7F7F7F7F7F3F3F1F0F0300E0F8FCFEFCF8FAF8FFFFFCFEFCF8E0" HEX#, S" 00030F1F3F3F7F7F7F7F7F3F3F1F0F0300E0F8FCFEF8F1F5F1FFFFF8FEFCF8E0" HEX#, S" 00030F1F3F3F7F7F7F7F7F3F3F1F0F0300E0F8FCFEF2E2EAE2FFFFF0F8FCF8E0" HEX#, S" 00030F1F3F3F7F7F7F7F7F3F3F1F0F0300E0F8FCFEE4C4D5C4FFFFE0F0FCF8E0" HEX#, S" 00030F1F3F3F7F7F7F7F7F3F3F1F0F0300E0F8FCFEC888AA88FFFFC0E2FCF8E0" HEX#, S" 00030F1F3F3F7F7F7F7F7F3F3F1F0F0300E0F8FCFE92115511FFFF82C6FCF8E0" HEX#, S" 00030F1F3F3F7E7E7E7F7F3F3F1F0F0300E0F8FCFE2623AB23FFFF068EFCF8E0" HEX#, S" 00030F1F3F3E7C7D7C7F7F3E3F1F0F0300E0F8FCFE4E475747FFFF0E1EFCF8E0" HEX#, \ expose BALLS as an array of 32 byte records DECIMAL : ]BALL ( n -- addr ) 32 * BALLS + ; : >CHAR ( n -- n') 8* 128 + ; \ convert sprite# to the character# \ ********************* \ These 2 routines do all the work. \ ********************* DECIMAL : ROTATOR ( spr# -- ) >CHAR BEGIN 23 0 DO I ]BALL OVER CHARDEF32 120 MS LOOP PAUSE AGAIN ; : TRAVELER ( spr# --) \ Endless loop. Must assign to a task. BEGIN PAUSE 250 10 DO PAUSE I OVER SP.X VC! LOOP 10 250 DO PAUSE I OVER SP.X VC! -1 +LOOP PAUSE AGAIN ; \ Hard coded was the fastest way to assign the parameter (not fancy) : ROTATE0 0 ROTATOR ; : ROTATE1 1 ROTATOR ; : ROTATE2 2 ROTATOR ; : ROTATE3 3 ROTATOR ; : ROTATE4 4 ROTATOR ; : ROTATE5 5 ROTATOR ; : ROTATE6 6 ROTATOR ; : ROTATE7 7 ROTATOR ; : ROTATE8 8 ROTATOR ; : ROTATE9 9 ROTATOR ; : TRAVEL0 0 TRAVELER ; : TRAVEL1 1 TRAVELER ; : TRAVEL2 2 TRAVELER ; : TRAVEL3 3 TRAVELER ; : TRAVEL4 4 TRAVELER ; : TRAVEL5 5 TRAVELER ; : TRAVEL6 6 TRAVELER ; : TRAVEL7 7 TRAVELER ; : TRAVEL8 8 TRAVELER ; : TRAVEL9 9 TRAVELER ; \ ***************************** \ MAKE SPRITES \ ***************************** DECIMAL 2 MAGNIFY : MAKE-BALLS 10 0 DO ( char colr x y sp# -- ) I >CHAR I 6 + 10 I 16 * I SPRITE \ make a sprite \ set the char patterns. Each sprite gets a different phase of the rotation I ]BALL I >CHAR CHARDEF32 LOOP 16 2 SP.COLOR ; ( fix the cyan sprite ) \ task creator : TASK: ( n -- ) USIZE MALLOC DUP FORK CONSTANT ; \ returns PID (address) \ 20 tasks are created to rotate and move the balls independantly TASK: MOVE0 ' TRAVEL0 MOVE0 ASSIGN TASK: MOVE1 ' TRAVEL1 MOVE1 ASSIGN TASK: MOVE2 ' TRAVEL2 MOVE2 ASSIGN TASK: MOVE3 ' TRAVEL3 MOVE3 ASSIGN TASK: MOVE4 ' TRAVEL4 MOVE4 ASSIGN TASK: MOVE5 ' TRAVEL5 MOVE5 ASSIGN TASK: MOVE6 ' TRAVEL6 MOVE6 ASSIGN TASK: MOVE7 ' TRAVEL7 MOVE7 ASSIGN TASK: MOVE8 ' TRAVEL8 MOVE8 ASSIGN TASK: MOVE9 ' TRAVEL9 MOVE9 ASSIGN TASK: SPIN0 ' ROTATE0 SPIN0 ASSIGN TASK: SPIN1 ' ROTATE1 SPIN1 ASSIGN TASK: SPIN2 ' ROTATE2 SPIN2 ASSIGN TASK: SPIN3 ' ROTATE3 SPIN3 ASSIGN TASK: SPIN4 ' ROTATE4 SPIN4 ASSIGN TASK: SPIN5 ' ROTATE5 SPIN5 ASSIGN TASK: SPIN6 ' ROTATE6 SPIN6 ASSIGN TASK: SPIN7 ' ROTATE7 SPIN7 ASSIGN TASK: SPIN8 ' ROTATE8 SPIN8 ASSIGN TASK: SPIN9 ' ROTATE9 SPIN9 ASSIGN : TRAVEL MOVE0 RESTART 50 MS \ RESTART each one and wait MOVE1 RESTART 50 MS MOVE2 RESTART 50 MS MOVE3 RESTART 50 MS MOVE4 RESTART 50 MS MOVE5 RESTART 50 MS MOVE6 RESTART 50 MS MOVE7 RESTART 50 MS MOVE8 RESTART 50 MS MOVE9 RESTART 50 MS ; : SPINALL SPIN0 RESTART 250 MS SPIN1 RESTART 250 MS SPIN2 RESTART 250 MS SPIN3 RESTART 250 MS SPIN4 RESTART 250 MS SPIN5 RESTART 250 MS SPIN6 RESTART 250 MS SPIN7 RESTART 250 MS SPIN8 RESTART 250 MS SPIN9 RESTART 250 MS ; : WAIT BEGIN PAUSE PAUSE PAUSE ?TERMINAL UNTIL ; MULTI MAKE-BALLS CLEAR BILLYBALL-20TASKS.mp4 4 Quote Link to comment Share on other sites More sharing options...
+TheBF Posted November 5, 2021 Author Share Posted November 5, 2021 I was curious if my ideas about writing 32 bytes to the pattern table at once was hogging too much CPU time. I broke up the code that writes 32 bytes into four chunks, writing data for one character at a time. After each chunk is written there is a PAUSE to allow other tasks to run. DECIMAL \ chopped into 4 pieces for smooth multi-tasking : CHARDEF4 ( data[] ascii# -- ) >R ( hold ascii# on Rstack like a local variable) DUP R@ CHARDEF PAUSE 8 + DUP R@ 1+ CHARDEF PAUSE 8 + DUP R@ 2+ CHARDEF PAUSE 8 + R> 3 + CHARDEF PAUSE ; Looks like the context switching time is still so small that I can't see any difference in the face rotation. I am impressed! :) This gives more time for the travel code or whatever you need to run. This also means that I might be better off making the BALLS array index 1 character pattern at a time rather than four. This is the one big thing you have to manage with a cooperative tasker, is tuning the code to give everybody the correct amount of time. The rule of thumb is every I/O operation needs at least one PAUSE. The reality is you have to test to make sure you got what you need. The good news is that PAUSE makes it pretty simple to adjust your code. Another cool thing is it does not interfere at all with putting other jobs on the interrupt if you want to. They can interoperate seamlessly. I could move the balls with AUTOMOTION and rotate the faces with Forth code. I added screen color changes to WAIT so we see something similar to the original XB256 demo. : WAIT ( -- ) 2 SCREEN BEGIN PAUSE ?TERMINAL UNTIL 8 SCREEN ; I have a better feel for what's possible with this tasker now on TI-99. I think I have exhausted this for now. BILLYBALLS-RETUNED.mp4 3 1 Quote Link to comment Share on other sites More sharing options...
GDMike Posted November 5, 2021 Share Posted November 5, 2021 (edited) Its quick Edited November 5, 2021 by GDMike 1 Quote Link to comment Share on other sites More sharing options...
+TheBF Posted November 6, 2021 Author Share Posted November 6, 2021 14 hours ago, GDMike said: Its quick Yes it is thanks to the clever 9900 architecture. To jump from one task to the next task is one instruction. RTWP. Then we test the task's local awake/asleep variable. If the task is asleep try the next one. Sleeping tasks run 4 instructions. (I am sure Windows is just as efficient...) YIELD RTWP * Enter next task’s workspace STWP R1 * Use the WP as base address MOV >20(R1),R0 * read the TFLAG variable JEQ YIELD * if task is asleep jump back I knew something like this was possible 35 years ago but did not know how to "git 'er done". The big revelation was to "pre-setup" R13,R14,R15 in each task when it is created so you can switch to it with RTWP not BLWP. The workspace is just memory so why keep separate vectors when the very same data is in the workspace? This little piece of code made my life feel better. Does that make me a nerd? ? 1 2 Quote Link to comment Share on other sites More sharing options...
RXB Posted November 6, 2021 Share Posted November 6, 2021 (edited) The key routine should be just a CRU scan for keyboard key being pressed and a word scan for any bit changes. That way the IO CHIP is doing the work instead of CPU. This should speed up any test for a keypress. After all CRU bits 6 to 14 are Keyboard bits and looking at these bits is faster then checking all CRU lines. i.e. 6=binary 110 and 14=binary 1110 Thus if these 4 bits are a 1 you have a key pressed, if all are 0000 then no key. Edited November 6, 2021 by RXB Quote Link to comment Share on other sites More sharing options...
+TheBF Posted November 6, 2021 Author Share Posted November 6, 2021 (edited) 2 hours ago, RXB said: The key routine should be just a CRU scan for keyboard key being pressed and a word scan for any bit changes. That way the IO CHIP is doing the work instead of CPU. This should speed up any test for a keypress. After all CRU bits 6 to 14 are Keyboard bits and looking at these bits is faster then checking all CRU lines. i.e. 6=binary 110 and 14=binary 1110 Thus if these 4 bits are a 1 you have a key pressed, if all are 0000 then no key. I don't think I understand that Rich but that would not be the first time. Maybe you mean I can test with separate code and only call KSCAN if there is something waiting? For clarity when I say 1.2 milli-seconds above, I measure like this: 1. Read the 9901 timer and remember the number. 2. Call KSCAN : BL @>000E in ROM ( with a few setup instructions of course) - disable interrupts - change workspace - BL @KSCAN - Read the byte at >8374 (save it in R4) - etc. you know the drill 3. Read the 9901 timer again. 4. Subtract the two readings and get the absolute value. I get 56 ticks on Classic99 and I just checked again on real iron and it wobbles between 56 and 57. I multiply 56 ticks by 21.3 uS, the tick time of the 9901 timer. That gives us 1,192.8 uS or about 1.2 mS. I could go faster if I made my own KSCAN code but then I need a translation table for CRU to standard key codes and it gets big real fast. I want the compiler, the interpreter and disk services to fit into 8K so there is more RAM for programs. Forth Assembler code is in the spoiler for reference. Spoiler CODE KEY? ( -- ?) \ *WARNING* it takes 1,128uS for KEY? scan to run TOS PUSH, TOS CLR, \ TOS will be our true/false flag 0 LIMI, TOS 837C @@ MOVB, \ clear GPL flags 83E0 LWPI, \ switch to GPL workspace R11 83BE @@ MOV, \ save in empty scratch PAD location 000E @@ BL, \ call ROM keyboard scanning routine 83BE @@ R11 MOV, WRKSP0 LWPI, \ return to Forth's workspace 837C @@ R1 MOVB, \ read GPL status byte (=2000 if key pressed) R1 3 SLA, \ check the key bit OC IF, \ if carry flag set 8374 @@ TOS MOV, \ read the key ENDIF, 2 LIMI, NEXT, \ return ENDCODE Edited November 6, 2021 by TheBF Fixed a wrong comment in the code 1 1 Quote Link to comment Share on other sites More sharing options...
+Lee Stewart Posted November 7, 2021 Share Posted November 7, 2021 6 hours ago, TheBF said: I don't think I understand that Rich but that would not be the first time. Maybe you mean I can test with separate code and only call KSCAN if there is something waiting? Exactly! Check Thierry’s discussion about reading the keyboard. The problem with calling the console’s KSCAN is that it often does a lot more than you want and there are two places it can call the time delay routine. Each time that happens, you lose about 8.5 ms! I think it is used for debouncing, but pretty much everyone agrees that so much time is unnecessary and that using a method other than a fixed delay is superior. There is also GROM access to set an address and then, either to fetch that byte to get the key value for the particular keyboard (3,4,5) in use, or to read two bytes for joystick 0 or 1—may not slow it down that much, but there it is. ...lee 3 1 Quote Link to comment Share on other sites More sharing options...
RXB Posted November 7, 2021 Share Posted November 7, 2021 (edited) Yea guys I just think reading the CRU lines 6 to 14 would be faster as you just look for a non zero value for a key pressed. No key pressed would mean those 4 bits are zero, keypress would be a value depending on that value. These are checked each time you enable then disable a interrupt. What you guys are doing seems way more complicated then needed. My point was that would see to me as damn small code and damn fast. Edited November 7, 2021 by RXB missing text 1 Quote Link to comment Share on other sites More sharing options...
Recommended Posts
Join the conversation
You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.