+TheBF Posted June 16, 2023 Author Share Posted June 16, 2023 A while back I saw code for erasing memory that was created by GCC. It was 2X faster than 0 FILL and so I took it as is and put it in a library. I got around to testing it and found that it was erasing 1 cell more than in the input argument. This is version is correct. It will always erase an even number of cells even if you give it an odd number which is ok if you keep you Forth memory aligned to word boundaries. CODE ERASE ( addr cnt -- ) \ 2x faster than 0 FILL *SP+ R1 MOV, BEGIN, TOS DECT, OC WHILE, *R1+ CLR, REPEAT, TOS POP, NEXT, ENDCODE 3 Quote Link to comment https://forums.atariage.com/topic/273872-camel99-forth-information-goes-here/page/66/#findComment-5270337 Share on other sites More sharing options...
+TheBF Posted June 21, 2023 Author Share Posted June 21, 2023 Things you learn when forced to use your own system. I did some work to improve the Breakout game clone and I learned something that others might want to know about how to better handle cooperative tasking under Forth. The new version makes a separate task for the paddle so the player has better control of it. The original paddle task called the joy stick function JOYST, which is small and fast but it ran the output through a case statement, did a timed delay and looped around again. It worked but it was not ideal. I could not get the ball moving fast enough for "Olympic" level play. I tried re-compiling with the direct threaded compiler and it was almost no different. That's when the light went on! : PADDLE-MOVER BEGIN 0 JOYST CASE 2 OF -2 PADDLE+! ENDOF 4 OF 2 PADDLE+! ENDOF ENDCASE SPEED @ 2* TICKS AGAIN ; (TICKS is multi-tasking friendly delay that reads the 9901 timer for duration) The problem was that even when the joystick was not outputting anything, the loop ran the entire case statement, which used CPU power for no purpose. The solution is to make a joystick version of KEY that passes control to other tasks if the joystick is doing nothing. The reason why pause is first is so that when the joystick is used the loop exits right away. : JOYKEY ( -- n) \ wait for any joystick output BEGIN PAUSE 0 JOYST ?DUP UNTIL ; Then put JOYKEY is the task loop. : PADDLE-MOVER BEGIN JOYKEY CASE 2 OF -2 PADDLE+! ENDOF 4 OF 2 PADDLE+! ENDOF ENDCASE SPEED @ 2* TICKS AGAIN ; ' PADDLE-MOVER CONSTANT PADDLE-TASK This worked much better at preventing the paddle movement from slowing down the ball movement. Updated code is here: CAMEL99-ITC/GAMES.TI/BREAKOUT/src/BREAKOUT-MTASK.FTH at master · bfox9900/CAMEL99-ITC · GitHub BTW the way, compiling this same program with the direct threaded Forth compiler added 4K to the image size !!! 3 Quote Link to comment https://forums.atariage.com/topic/273872-camel99-forth-information-goes-here/page/66/#findComment-5272927 Share on other sites More sharing options...
+TheBF Posted July 16, 2023 Author Share Posted July 16, 2023 (edited) I finally figured out how to get this code working that I found in an old TI book from 1981, called Software Development. "2nd Edition" The way the thing works is: Reset the UART Set the protocol control register to a predefined value (Camel99 is 8,n,1) Loops until the UART input line goes low, the "mark" condition, meaning a key was pressed. Enters a counter loop waiting for the input line to go high. The value of the counter is compared to a *table entry, that also has the correct baud rate divisor for each timed value. Set the baud rate register to the value in the table. Read a key and throw it away. I had to change the table values to match the timings of a TI-99 with registers in scratchpad but the program running in expansion RAM. They were about 2X smaller than the values in the TI book because our machine is slower. The other thing that tripped me up two years ago was the need to reset the input buffer bit. This is not in the original code but was needed for Forth running over the serial port. I suspect because I hit a key to start autobaud but then the autobaud program resets the UART and the key is not read. I added one more table entry so you can use 38400 bps. The video shows the operation. Code is in the spoiler. https://youtu.be/923vD4ElP9Q Spoiler \ * autobaud for 9902 for CAMEL TTY Forth July 2023 \ * Source: Software Development, Texas Instruments 1981 \ Polls the Receive Line Input (RIN) BIT and counts the time \ it takes to return to 0. This count tells you the tms9902 \ speed to READ 8 bits ie: the baud rate of the sender. \ Use that number to look up the correct baud rate divisor \ and set the TI-99 UART baud rate. Voila! \ 9902 bit # references \ 13 EQU LDIR \ "load interval register" \ 16 EQU RTSON \ request to send \ 18 EQU RIENB \ rcv interrupt enable \ 21 EQU RXRL \ receive register loaded bit \ 22 EQU TXRE \ transmit register empty bit \ 27 EQU -DSR \ NOT data set ready \ 28 EQU -CTS \ NOT clear to send \ 31 EQU RESET \ 9902 reset bit \ Defined in the kernel. Here for reference \ HEX \ 1300 CONSTANT CARD \ card CRU base address \ 0040 CONTANT UART \ RS232/1 uart offset from base \ VARIABLE PROTO \ holds protocol NEEDS DUMP FROM DSK1.TOOLS NEEDS MOV, FROM DSK1.ASM9900 MARKER REMOVE HEX CREATE BAUDTB \ counter baudrate \ -------- -------- 0002 , 000D , \ 38400 0004 , 001A , \ 19200 0008 , 0034 , \ 9600 000F , 0068 , \ 4800 001E , 00D0 , \ 2400 003C , 01A0 , \ 1200 0078 , 0340 , \ 600 00F0 , 04D0 , \ 300 01E0 , 0638 , \ 110 CODE AUTOBAUD ( -- n) DECIMAL TOS PUSH, \ TOS=R4, save R4 to free up for use R3 CLR, \ this is our time counter register CARD @@ R12 MOV, \ load the card address 7 SBO, \ LED on 5 SBZ, \ CARD CTS line LOW. You are clear to send UART @@ R12 ADD, \ add 9902 port address 31 SBO, \ reset UART PROTO @@ 8 LDCR, \ set protocol (8,n,1) 13 SBZ, \ reset LDIR , allows setting baud rate \ ******* critical time measuring loops ******** \ ** HAD TO ADD THIS INSTRUCTION TO ORIGINAL TI CODE ** 18 SBZ, \ RIENB resets bit 15 \ * wait for start bit to go to "mark" (logic 0) BEGIN, 15 TB, NE UNTIL, \ * wait until rin goes back to "space" (logic 1) BEGIN, R3 INC, 15 TB, EQ UNTIL, 18 SBZ, \ reset 9902 rcv buffer \ ************************************************ \ * find the baud rate timer divisor in BAUDTB TOS BAUDTB 2- LI, \ set table base address-2 in R4 BEGIN, TOS INCT, \ select table record R3 *TOS+ CMP, \ compare counter to table record, bump R4 LE UNTIL, \ * baud rate found - set receive and transmit data interval *TOS 12 LDCR, \ need to write 12 bits. \ * read a char and throw it away BEGIN, 21 TB, \ test receive register loaded bit EQ UNTIL, \ loop until true 18 SBZ, \ reset 9902 rcv buffer CARD @@ R12 MOV, \ select the card address 7 SBZ, \ LED off TOS POP, \ refill top of stack cache register NEXT, ENDCODE Edited July 17, 2023 by TheBF fixed more typos 2 Quote Link to comment https://forums.atariage.com/topic/273872-camel99-forth-information-goes-here/page/66/#findComment-5284987 Share on other sites More sharing options...
+FarmerPotato Posted July 17, 2023 Share Posted July 17, 2023 (edited) @TheBF I love that book! More about that later. I think this assumes an odd or even character? It measures duration from the start bit to the first SPACE bit. So either: 1. Assuming even: Measures duration of start bit MARK assuming LSBit is SPACE. (Maybe TI's "Gold" key?) 2. Assuming odd: measures duration of start bit (MARK) plus least significant bit (MARK) until a SPACE. The characters 'A' and CR meet this condition: (LSBit first) CR 1011 0000 'A' 1000 0010 So it measures the time for two bits. Probably better accuracy! A character having the unexpected LSBit would mess with it. I think you could detect that by watching the framing error bit; if next char is a framing error, try autobaud again. (When stop bit is not MARK.) Edited July 17, 2023 by FarmerPotato 3 Quote Link to comment https://forums.atariage.com/topic/273872-camel99-forth-information-goes-here/page/66/#findComment-5285572 Share on other sites More sharing options...
+TheBF Posted July 18, 2023 Author Share Posted July 18, 2023 2 hours ago, FarmerPotato said: @TheBF I love that book! More about that later. I think this assumes an odd or even character? It measures duration from the start bit to the first SPACE bit. So either: 1. Assuming even: Measures duration of start bit MARK assuming LSBit is SPACE. (Maybe TI's "Gold" key?) 2. Assuming odd: measures duration of start bit (MARK) plus least significant bit (MARK) until a SPACE. The characters 'A' and CR meet this condition: (LSBit first) CR 1011 0000 'A' 1000 0010 So it measures the time for two bits. Probably better accuracy! A character having the unexpected LSBit would mess with it. I think you could detect that by watching the framing error bit; if next char is a framing error, try autobaud again. (When stop bit is not MARK.) Yes my understanding from the code is that is measures how long it takes to go from mark to space. I only tested with the enter key actually. There might be a key that beats it. ?? You are too clever. I was wondering if I could do more with the framing error but it starts to get bigger. It's 130 bytes now and I think that's probably enough. When vi99 boots on the version I am working on, it writes to the VDP screen and says: VI99 TTY EDITOR RS232/1 8,n,1 RTS/CTS handshake Press enter on your terminal to set the baud rate 2 Quote Link to comment https://forums.atariage.com/topic/273872-camel99-forth-information-goes-here/page/66/#findComment-5285624 Share on other sites More sharing options...
+TheBF Posted July 26, 2023 Author Share Posted July 26, 2023 I was away for a few days and when I came back I wanted to address an idea that had been bubbling in my head for ASMForth II. It's not rocket science but it does make the "language" a bit higher level. The idea is to leverage the fact that 9900 can use memory almost as easily as it uses registers. Remember that ASMForth is an Assembler that renames 9900 instructions to use Forth nomenclature where ever possible. The new idea was to add some simple smarts so that operations would detect if an argument was an address or a register. It turned out that I needed to deal with two situations. Source arguments and destination arguments. Once I realized that, it became pretty simple. DECIMAL : ADDRESS? ( n -- ?) 8191 U> ; \ lowest RAM address is 8192 HEX : REG? ( n -- ?) 0 10 WITHIN ; : MODE? ( n -- ?) 1F 30 WITHIN ; : <SRC> ( n -- n | n 20) \ used with fetch operators DUP ADDRESS? IF @@ EXIT THEN DUP REG? IF ** EXIT THEN DUP MODE? IF ( nothing) THEN ; : <DST> ( n -- n | n 20) \ used with store operators DUP ADDRESS? IF @@ THEN ; With these simple tests we just modified @ and ! like this: Using Fetch '@' on a register simply gives you indirect addressing mode; on an address you get symbolic addressing. \ SMART fetch operator : @ ( addr|reg -- u | Rx n) <SRC> ; : C@ ( addr|reg -- u | Rx n) <SRC> ; \ Add some smarts to store : ! ( src dst -- ) <DST> MOV, ; : +! ( src mem -- ) <DST> ADD, ; : C! ( c dst -- ) <DST> MOVB, ; I also added some compile time error checks to protect operations that must have a register argument and also where an address was expected. \ aborting error detection words : ?ADDR ( addr -- addr) DUP ADDRESS? 0= ABORT" Valid address expected" ; : ?REGISTER ( reg --reg) DUP REG? 0= ABORT" Register expected" ; So what you might ask? Well the result is that we can get some pretty nice code out of this thing that reads kind of like Forth but generates native machine code. This is the one I think I like the best. VARIABLE X VARIABLE Y CODE MEM2MEM X @ Y ! ;CODE This generates the following code: DE76 C820 mov @>de5c,@>de66 The implication of this is that all the operations that can work on memory or registers like those below now do not need any extra syntax. They just work. And if you try to use an address where a register is required it aborts on that line. (at least on the operations that I have added that too) This might take some getting use to coming from Assembler, but it makes a pretty high level Assembler. : 1+ ( arg -- ) <DST> INC, ; : 1- ( arg -- ) <DST> DEC, ; : 2+ ( arg -- ) <DST> INCT, ; : 2- ( arg -- ) <DST> DECT, ; : >< ( arg -- ) <DST> SWPB, ; : ABS ( arg -- ) <DST> ABS, ; : NEGATE ( arg -- ) <DST> NEG, ; : ON ( arg -- ) <DST> SETO, ; : OFF ( arg -- ) <DST> CLR, ; : NEGATE ( arg -- ) <DST> NEG, ; : OR ( src dst --) <DST> SOC, ; : ON ( arg -- ) <DST> SETO, ; : OFF ( arg -- ) <DST> CLR, ; : CMP ( reg gad) <DST> CMP, ; : CMPB ( reg gad) <DST> CMPB, ; Another minor change was made to make 9900 LI and AI instructions work more like Forth where the number comes first, then the destination register and then the instruction. 99 R1 #! 7 R1 #+! 2 1 Quote Link to comment https://forums.atariage.com/topic/273872-camel99-forth-information-goes-here/page/66/#findComment-5289571 Share on other sites More sharing options...
+TheBF Posted July 27, 2023 Author Share Posted July 27, 2023 Of course now I have to go back and review the instruction manual and demo programs and then test the programs. To give you a feel of how this looks compared to Forth and Assembler here is a demo that can copy data from one array to another. \ CODE word that can be called from Forth CODE MOVECELLS ( src dst n -- ) \ tos=n NOS=DST 3rd=src NOS^ R1 ! \ POP dst into a register for auto-incrementing NOS^ R0 ! \ POP source into a register TOS FOR \ FOR takes n from TOS register and pushes it onto Rstack R0 @+ R1 @+ ! \ store a cell and auto-increment both registers NEXT DROP \ refill TOS register from data stack memory ;CODE In the video we copy 4096 CELLS (8k bytes) from the ROM at >0000 to low RAM at >2000 very fast. ASMForth II MOVECELLS.mp4 3 1 Quote Link to comment https://forums.atariage.com/topic/273872-camel99-forth-information-goes-here/page/66/#findComment-5289831 Share on other sites More sharing options...
+TheBF Posted August 5, 2023 Author Share Posted August 5, 2023 Now that I have my head in this ASMForth, I realized that my FOR NEXT loop could be improved very easily. Edit: Updated binary program files are here: ASMFORTH/bin/DSK2 at main · bfox9900/ASMFORTH · GitHub The first version pushed the loop limit onto the return stack and decremented the value in memory. *RP DEC, According to my notes this is about 11% slower than using a register as the down counter. I was ok with that because I was just happy the darned thing worked at all. However now I realize that R11 is available as a loop counter as long as I save it on the return stack and then pop it back with NEXT. Re-running the Byte Mag. Sieve with this new FOR NEXT loop reduced the runtime from 9.94 seconds to 9.25 seconds, a 7 % improvement. The high level parts of the compiler are written in ASMForth II , so here is what the FOR NEXT code looks like now. I am still giddy over this kind of thing actually working. : FOR ( arg --) R11 RPUSH ( ARG) R11 ! BEGIN ; \ Alternative using literal number, N goes thru TOS -> R11 : #FOR ( n --) # \ put literal into TOS register R11 RPUSH \ save R11 TOS R11 ! \ put loop limit into R11 DROP \ restore TOS register from stack memory BEGIN ; : NEXT ( -- ) R11 1- \ DEC R11 NC UNTIL \ JOC BEGIN R11 RPOP ; \ MOV *RP+,R11 : NEXT2 \ dect loop counter by 2 R11 2- NC UNTIL R11 RPOP ; Here is the slightly altered Sieve code. (FILLW had one instruction removed) Spoiler \ SIEVE in ASMFORTH for Camel99 Forth Aug 2023 Brian Fox \ based on code by @Reciprocating Bill atariage.com \ Minor mods for Version 0.81 \ Original notes by BIll. \ * SIEVE OF ERATOSTHENES ------------------------------------------ \ * WSM 4/2022 \ * TMS9900 assembly adapted from BYTE magazine 9/81 and 1/83 issues \ * 10 iterations 6.4 seconds on 16-bit console \ * ~10 seconds on stock console \ * ASMForth II V.81 version runs in 9.26 seconds HOST INCLUDE DSK1.ELAPSE \ for timing measurements DECIMAL 8190 CONSTANT SIZE HEX 2000 CONSTANT FLAGS \ array in Low RAM ASMFORTH : FILLW ( addr size U --) R0 POP \ size in R0 for FOR to pickup R1 POP \ Memory location in R1 R0 FOR \ FOR takes the R0 argument TOS *R1+ ! \ write U to addr, bump addr by 2 NEXT2 \ *NEW* counts down by 2 DROP ; HEX CODE DO-PRIME ( -- n) FLAGS # SIZE # 0101 # FILLW \ inits R0 OFF \ 0 constant R3 OFF \ clear array index register FLAGS R5 #! \ array base address SIZE R8 #! \ size of array 0 # \ counter on top of Forth stack (ie: in R4) R8 FOR \ use R8 to load loop counter R5 @+ R0 CMPB \ FLAGS byte-compared to 0 <> IF \ not equal to zero ? R3 R1 ! \ I -> R1 R1 2* 3 R1 #+! \ R1 3+ R3 R2 ! \ I -> R2 ( R2 is K index) R1 R2 + \ PRIME K +! BEGIN R2 R8 CMP \ K SIZE compare < WHILE R0 FLAGS (R2) C! \ reset byte FLAGS(R2) R1 R2 + \ PRIME K +! REPEAT TOS 1+ \ increment count of primes THEN R3 1+ \ bump LOOP index register NEXT ;CODE HOST ( Switch back to Host Forth ) DECIMAL : PRIMES ( -- ) PAGE ." 10 Iterations" 10 0 DO DO-PRIME CR . ." primes" LOOP CR ." Done!" ; ASMforth II .81 Sieve.mp4 2 Quote Link to comment https://forums.atariage.com/topic/273872-camel99-forth-information-goes-here/page/66/#findComment-5294665 Share on other sites More sharing options...
+TheBF Posted August 6, 2023 Author Share Posted August 6, 2023 I looked at this after a few hours and realized that since the loop counter is now a register, #FOR can take the loop length as a literal number in the source code. I can load R11 with #!. #! is an alias for the LI instruction. So this gets far simpler and removes a bunch of instructions generated by the # operator. : #FOR ( arg --) R11 RPUSH \ save R11 ( arg) R11 #! \ LI R11,arg BEGIN ; 1 Quote Link to comment https://forums.atariage.com/topic/273872-camel99-forth-information-goes-here/page/66/#findComment-5294770 Share on other sites More sharing options...
+TheBF Posted August 6, 2023 Author Share Posted August 6, 2023 Just for testing I ran my FORNEXT demo program with elapsed timing. Wow did this change to the new #FOR code make a difference! \ 1,000,000 iterations \ V .73 14.51 seconds \ V .81 10.11 seconds CODE NESTED 100 #FOR 100 #FOR 100 #FOR NEXT NEXT NEXT ;CODE \ 1,000,000 iterations \ v .73 35.1 seconds \ V.81 23.8 seconds CODE DEEPER 10 #FOR 10 #FOR 10 #FOR 10 #FOR 10 #FOR 10 #FOR NEXT NEXT NEXT NEXT NEXT NEXT ;CODE 2 Quote Link to comment https://forums.atariage.com/topic/273872-camel99-forth-information-goes-here/page/66/#findComment-5294799 Share on other sites More sharing options...
+TheBF Posted August 24, 2023 Author Share Posted August 24, 2023 While snooping around the internet I found a version of XMODEM written in Forth83 dated Sept 1985 but ported to ANS Forth in 2010. I am looking at how hard it will be to make it run on Camel99 Forth. I think it's doable. That would be really handy for sending/receiving files to real iron with CAMELTTY Forth running on my TI99. One thing that it needs is a long duration timer. I remembered playing with an ISR based 32 bit counter. I went back to that code and reflected on something that MPE Forth had back in the 1990s. That is, a programmable timer "creator". The concept is that you make a word that records a duration and a time in future. (DURATION+CURRENT_TIME) Every time you use that word it checks if the current time is past the future time you set. If is not, just return false. ELSE if it is past the current time, the timer has expired so... Reset the future time one more duration in the future, and return true. With 16mS interrupt ticks and a 32 bit unsigned number you can keep continuous time for over 19 hours. 828 days. Edit: Oops bad math. Edited. Edited again. \ This allows timing up to: \ 1/60 * 2^32 \ = 71,582,788 seconds \ = 1,193,046.5 minutes \ = 19,884 hours \ = 828.5 days However you have to use double integer computations and comparisons. Here is what I came up with. I have not figured how to deal with what happens if the master timer rolls over to zero except to say reset the master timer when the program starts? There is also problem here with the current ELAPSE function in that it RESETS the master timer but since that is mostly for interactive testing at the console, I will ignore it for now. (wouldn't take much to fix it) But for XMODEM waiting for packets to start and such this would be great because you just poll the timer you made as you have time. The ISR is keeping track of real time. Spoiler \ ELAPSE32.FTH elapsed time measurment words \ This timer installs a 32bit ISR driven timer \ This allows timing up to: \ 1/60 * 2^32 \ = 71,582,788 frames \ = 1,193,046.5 secs \ = 19.884 minutes \ = 331.4 hours \ = 13.8 days NEEDS INSTALL FROM DSK1.ISRSUPPORT INCLUDE DSK1.TOOLS \ debug HEX CREATE TIMER32 0 , 0 , \ 32 bit variable : RESET32 ( -- ) 0 0 TIMER32 2! ; CREATE COUNT32 \ native sub-routine. 05A0 , TIMER32 CELL+ , \ TIMER32 CELL+ @@ INC, 1702 , \ OC IF, 05A0 , TIMER32 , \ TIMER32 @@ INC, \ ENDIF, 045B , \ RT, DECIMAL \ UTILITY WORDS : 2, , , ; : D0= ( d -- ?) OR 0= ; : DU< ( d d -- ?) ROT U> IF 2DROP -1 ELSE U< THEN ; : CELLS+ ( a n -- a') CELLS + ; : SEXTAL 6 BASE ! ; : <:> [CHAR] : HOLD ; : <.> [CHAR] . HOLD ; : :00 DECIMAL # SEXTAL # <:> ; : .00 ( ticks) DECIMAL # SEXTAL # <.> ; \ time$ is volatile. TYPE it or SAVE it shortly after use. : TIME$ ( d -- addr len) BASE @ >R \ frames secs minutes hours <# .00 :00 :00 DECIMAL # # #> R> BASE ! ; : .TIME TIMER32 2@ TIME$ TYPE ; : .ELAPSED ( -- ) CR ." Elapsed time =" .TIME ; : ELAPSE ( -- <text> ) 1 PARSE RESET32 EVALUATE .ELAPSED ; : TIME>SECS ( hr min secs -- d) ROT 3600 * ROT 60 * + + ; : SECS>TICKS ( n -- d) 60 UM* ; : >TICKS ( hr min sec -- d ) TIME>SECS SECS>TICKS ; \ TIMER: creates a data structure of 2 doubles \ - the duration in ticks \ - the end time in ticks \ compile time usage: 0 5 30 TIMER: T1 \ 5 minute 30 second timer \ Run time T1 returns false until timer expires. \ T1 returns true when it expires and resets itself : TIMER: ( hr min secs -- ) CREATE >TICKS 2DUP \ compute duration in ticks, dup 2, \ compile duration ( field 1) ( duration) TIMER32 2@ D+ 2, \ compute end time & compile ( field2 ) DOES> ( -- ?) ( field1) DUP \ address of this timer's data 2 CELLS+ 2@ TIMER32 2@ DU< \ compare field2 to running isr timer 0= IF DROP FALSE EXIT THEN \ no time out, return false & get out \ timer has expired: ( field1) DUP 2@ TIMER32 2@ D+ \ compute new endtime ROT 2 CELLS+ 2! \ store the new end time in field2 TRUE ; \ redefined to stop isr timer before resetting : COLD 0 INSTALL COLD ; COUNT32 INSTALL CR .( 32bit counter interrupt installed) \ TEST 0 0 5 TIMER: 5SECS? \ 5 second timer : TEST BEGIN BEGIN 5SECS? UNTIL \ loop until expires CR .TIME ?TERMINAL UNTIL ; 2 Quote Link to comment https://forums.atariage.com/topic/273872-camel99-forth-information-goes-here/page/66/#findComment-5304148 Share on other sites More sharing options...
+Lee Stewart Posted August 25, 2023 Share Posted August 25, 2023 21 hours ago, TheBF said: Edit: Oops bad math. Almost two weeks is probably long enough for my purposes. \ This allows timing up to: \ 1/60 * 2^32 \ = 71,582,788 frames \ = 1,193,046.5 secs \ = 19,884 minutes \ = 331.4 hours \ = 13.8 days I have not yet examined your code, but, if you are ticking a 32-bit timer 60 times a second, you get 828.5 days before it rolls over! ...lee Quote Link to comment https://forums.atariage.com/topic/273872-camel99-forth-information-goes-here/page/66/#findComment-5304615 Share on other sites More sharing options...
+TheBF Posted August 25, 2023 Author Share Posted August 25, 2023 3 hours ago, Lee Stewart said: I have not yet examined your code, but, if you are ticking a 32-bit timer 60 times a second, you get 828.5 days before it rolls over! ...lee LOL. Thanks! That's even better. Essentially don't worry about it. 1 Quote Link to comment https://forums.atariage.com/topic/273872-camel99-forth-information-goes-here/page/66/#findComment-5304735 Share on other sites More sharing options...
+TheBF Posted August 25, 2023 Author Share Posted August 25, 2023 19 hours ago, Lee Stewart said: I have not yet examined your code, but, if you are ticking a 32-bit timer 60 times a second, you get 828.5 days before it rolls over! ...lee BTW where did I go wrong? 16.66666 ms per tick ~= 60 ticks per second. Edited per Lee's correction 2^32= 4,294,967,296 ticks / 60 = 71582788 seconds / 60 = 1193046 minutes / 60 = 19884 hrs / 24 = 828.5 days = ~ 2.27 years 2 Quote Link to comment https://forums.atariage.com/topic/273872-camel99-forth-information-goes-here/page/66/#findComment-5304739 Share on other sites More sharing options...
+Lee Stewart Posted August 26, 2023 Share Posted August 26, 2023 13 hours ago, TheBF said: BTW where did I go wrong? 16.66666 ms per tick ~= 60 ticks per second. 2^32= 4,294,967,296 ticks / 60 = 71582788 seconds / 60 = 1193046 minutes / 60 = 19884 minutes / 60 = 331.4 hrs / 24 = 13.8 days ??? Dividing the number of ticks by 60 gives you seconds, not frames (as you first posted). In the above post, your second "minutes" line is 19884 hours, which divided by 24 gives you 828.5 days. ...lee 1 1 Quote Link to comment https://forums.atariage.com/topic/273872-camel99-forth-information-goes-here/page/66/#findComment-5304899 Share on other sites More sharing options...
+TheBF Posted September 7, 2023 Author Share Posted September 7, 2023 (edited) I have always found it inconvenient that I can save my Forth programs as E/A5 images but I have to include the compiler, interpreter and the dictionary headers with the program. I have been noodling on how to use the existing compiler to build a new Forth system on the TI-99 where we don't include words we don't need AND we don't include the dictionary headers either. Dictionary headers can take up to 30% of the total size of the binary image in a Forth program. This is not very different than the cross-compiler that I have that runs under MS DOS, but the one advantage I have on TI-99 is there is ton of code primitives in the kernel that are just there for the picking. I am calling this a "RECOMPILER" for now. I now have all the tools now on the TI-99 that I have in the DOS Forth so this could replace my DOS cross-compiler one day. All this thinking lead to the word IMPORT: which copies CODE words from the kernel into a memory buffer (low RAM) but keeps the dictionary info in a vocabulary in Camel99 Forth. It isn't a 100% solution because some of the kernel words jump into other words to save space, but most them are useable. For the regular Forth CODE words it's just this easy to "steal" their code for the new system. \ steal some primitives code from the Kernel and copy into target IMPORT: C! 2! 2@ COUNT +! C+! IMPORT: RP@ RP! DUP>R >R R> R@ SP@ SP! 2>R 2R> IMPORT: NIP ?DUP SWAP OVER ROT -ROT >< 2DROP 2DUP 2SWAP PICK IMPORT: AND OR XOR IMPORT: 1+ 1- 2+ 2- 2* 4* 8* 2/ IMPORT: 1+! 1-! IMPORT: - D+ RSHIFT LSHIFT INVERT ABS NEGATE ALIGNED IMPORT: UM* * UM/MOD M/MOD IMPORT: = OVER= 0< U< > < IMPORT: MIN MAX SPLIT FUSE IMPORT: MOVE FILL SKIP SCAN IMPORT: ON OFF Here is what it took to make IMPORT: (there are some words used below [ COMPILER T, ] that are defined in the cross-assembler and the preamble code) \ import.fth COMPILER HEX 045A CONSTANT 'NEXT' \ 9900 CODE for B *R10 Camel99 Forth's NEXT code : TNEXT, 'NEXT' T, ; \ Read code word from kernel, compile into target memory : TCODE, ( xt --) >BODY 80 CELLS ( -- addr len) BOUNDS ( -- IPend IPstart) BEGIN DUP @ 'NEXT' <> \ the instruction is not 'RET' WHILE DUP @ ( -- IP instruction) T, \ compile instruction CELL+ \ advance IP 2DUP < ABORT" End of code not found" REPEAT 2DROP ; : ?CODE ( xt --) DUP @ 2- - ABORT" Not a CODE word" ; \ like CREATE but takes the name from a stack string : CREATE, ( addr len -- ) HEADER, POSTPONE DOVAR ; \ IMPORT, finds a code word in the kernel and copies the code into TARGET \ It creates a VARIABLE in the Forth dictioinary that returns the target \ execution token in interpret mode. : IMPORT, ( addr len -- ) PAD PLACE \ save the name in PAD PAD FIND 0= ABORT" IMPORT, can't find" ( xt) DUP ?CODE \ CREATE/DOES> but with a string argument PAD COUNT CREATE, THERE , \ record the target address in Forth TCODE, \ compile code from Kernel into the target TNEXT, \ append NEXT to the TARGET code DOES> STATE @ ABORT" Can't run TARGET word" @ ( interpret mode, return the address ) ; : IMPORT: BEGIN PARSE-NAME DUP WHILE IMPORT, REPEAT 2DROP ; Next I need to make a version of VARIABLE CONSTANT : and ; and I should be able to recompile high-level Forth words and build a small program , in threaded code, that has no headers or branch/loop compilers built-in. I am envisioning this for making utility programs that don't require disk in the beginning, which means I should be able to put both stacks in scratchpad RAM. Edited September 7, 2023 by TheBF typo 3 Quote Link to comment https://forums.atariage.com/topic/273872-camel99-forth-information-goes-here/page/66/#findComment-5310801 Share on other sites More sharing options...
+FarmerPotato Posted September 7, 2023 Share Posted September 7, 2023 This is fascinating. I've felt stuck over how to "package" a Forth (game) without all the compiler stuff. Smalltalk approached this problem with a "packager", but Smalltalk's runtime flexibility made it hard to predict what was used and what wasn't. Could you compile the dictionary to a dummy address? Say, at >6000 for a ROM cartridge. Like, a game written in Forth. Assuming 32K RAM required. Variables would need to point to RAM. Maybe you could have a defining word PADVARIABLE? ALLOT would consume 32K RAM. I'm also recalling Lee's fbForth cartridge--where names are not mixed in with the code. A really crazy idea I just had is: your IMPORT: is kind of like an assembly REF. What if the re-compilation made "relocatable" code containing unresolved references? I see this is completely unnecessary if compilation just uses a dummy base address, say 6000, while actually building the image in RAM elsewhere. But: Analogous to the TI assembler, a re-compiler would build a REF/DEF table. A REF table entry is a linked list of everywhere the symbol is needed. Each new word gets a DEF with its address. (All addresses to be relative to a base.) The recompiler could even infer the IMPORT: for any word not yet encountered. (Not yet present in the DEF table.) Again just like the E/A loader: at load/link time, there is a base address, like 6000 or A000, and DEFs are calculated to be absolute addresses. REFs are then resolved to the corresponding DEF. Probably an overly complicated solution. Anyway, your work here is a big step toward solving the "packaging" problem! 3 Quote Link to comment https://forums.atariage.com/topic/273872-camel99-forth-information-goes-here/page/66/#findComment-5311238 Share on other sites More sharing options...
+TheBF Posted September 8, 2023 Author Share Posted September 8, 2023 9 hours ago, FarmerPotato said: This is fascinating. I've felt stuck over how to "package" a Forth (game) without all the compiler stuff. Smalltalk approached this problem with a "packager", but Smalltalk's runtime flexibility made it hard to predict what was used and what wasn't. Could you compile the dictionary to a dummy address? Say, at >6000 for a ROM cartridge. Like, a game written in Forth. Assuming 32K RAM required. Variables would need to point to RAM. Maybe you could have a defining word PADVARIABLE? ALLOT would consume 32K RAM. I'm also recalling Lee's fbForth cartridge--where names are not mixed in with the code. A really crazy idea I just had is: your IMPORT: is kind of like an assembly REF. What if the re-compilation made "relocatable" code containing unresolved references? I see this is completely unnecessary if compilation just uses a dummy base address, say 6000, while actually building the image in RAM elsewhere. But: Analogous to the TI assembler, a re-compiler would build a REF/DEF table. A REF table entry is a linked list of everywhere the symbol is needed. Each new word gets a DEF with its address. (All addresses to be relative to a base.) The recompiler could even infer the IMPORT: for any word not yet encountered. (Not yet present in the DEF table.) Again just like the E/A loader: at load/link time, there is a base address, like 6000 or A000, and DEFs are calculated to be absolute addresses. REFs are then resolved to the corresponding DEF. Probably an overly complicated solution. Anyway, your work here is a big step toward solving the "packaging" problem! I love your excitement. You have touched on a bunch of possibilities. I am going to get something working and then start to think about where it goes For example it is relatively simple to change the threading method from indirect threading to direct threading or even sub-routine threading with inlining. The infer idea could possibly be done with a first pass through the source code and compile the run time code for all the primitives that are required. That might be overkill however since it's pretty simple to add primitives to an import statement. I am thinking about adding an import statement to every source file and make IMPORT: a bit smarter so it only compiles a primitive once. Lots to think about. And yes I can compiler headers in other memory because I can move the Forth dictionary to other memory. I am also considering putting the compiled code into VDP RAM so that I can make programs bigger than 8k, but that will mean putting some of the compiler in SuperCart Ram or SAMS. BTW the way I think I will "borrow" that word 'PACKAGE' for something. 3 Quote Link to comment https://forums.atariage.com/topic/273872-camel99-forth-information-goes-here/page/66/#findComment-5311846 Share on other sites More sharing options...
+FarmerPotato Posted September 8, 2023 Share Posted September 8, 2023 For more historical context: check "Image Based Persistence" under Wikipedia Smalltalk article. Citation 37 looks like a whole (online) book. Quote Smalltalk images are similar to (restartable) core dumps BSAVE 🙂 1 Quote Link to comment https://forums.atariage.com/topic/273872-camel99-forth-information-goes-here/page/66/#findComment-5312249 Share on other sites More sharing options...
+TheBF Posted September 8, 2023 Author Share Posted September 8, 2023 6 minutes ago, FarmerPotato said: For more historical context: check "Image Based Persistence" under Wikipedia Smalltalk article. Citation 37 looks like a whole (online) book. Smalltalk images are similar to (restartable) core dumps BSAVE 🙂 Yes Forth systems work the same way. I have the function in a word called SAVESYS ( OR if you run Forth from SUPER CART I call it SUPERSAVE) I didn't use the name BSAVE because that has historical usage for saving a Forth system in BLOCKS. Here is the code and an example of a minimal save start up word call GO. WARM inits Forth and ABORT restarts the interpreter. This newest version saves the program and if LOW RAM has been used ( H variable <> 0) it saves that too as a separate program file with the appropriate header for the E/A5 loader to pull it in. One day I will add saving VDP RAM and SAMS. VDP is simplest because I have a VDP memory management variable in the kernel (VP) so it will work like LOW RAM. Spoiler CR .( SAVESYS.FTH V2 creates EA5 program Dec 2022 B Fox) \ creates a binary program E/A 5 format. \ Makes as many files as needed to save the system \ Jun 2022 version fixed section overlap. Tested with check sum. \ Dec 2022 saves the HEAP (Low RAM) as a file if variable H <> 0 \ Usage example: \ INCLUDE DSK2.MYPOGRAM ( load all your code) \ : STARTUP WARM CR ." Myprogram ready" ABORT" ; \ LOCK ( this locks the dictionary to the current size ) \ \ INCLUDE DSK1.SAVESYS \ ' STARTUP SAVESYS DSK3.MYFILENAME \ NEEDS DUMP FROM DSK1.TOOLS NEEDS LOCK FROM DSK1.MARKER NEEDS LOAD-FILE FROM DSK1.LOADSAVE \ we use SAVE-FILE from this library NEEDS U.R FROM DSK1.UDOTR HERE HEX A000 CONSTANT HIMEM \ start of Camel99 Forth program in CPU RAM 1000 CONSTANT VDPBUFF \ Programs write to file from VDP Ram 2000 CONSTANT LOWRAM 2000 CONSTANT 8K 8K 3 CELLS - CONSTANT IMGSIZE \ makes space for header cells 13 CONSTANT PROGRAM \ file mode for Program files \ define the file header fields. *THESE ARE VDP ADDRESSES* VDPBUFF CONSTANT MULTIFLAG VDPBUFF 1 CELLS + CONSTANT PROGSIZE VDPBUFF 2 CELLS + CONSTANT LOADADDR VDPBUFF 3 CELLS + CONSTANT CODEBUFF \ COPY 8K program chunks to here 3 CELLS CONSTANT HEADLEN : HEADER ( Vaddr size ?) \ store header info in VDP RAM MULTIFLAG V! PROGSIZE V! LOADADDR V! ; : END ( -- addr ) ORGDP @ DUP C000 < IF HONK CR ." WARNING: missing LOCK directive" THEN ; \ words to compute Forth system properties : SYS-SIZE ( -- n) HIMEM END SWAP - ; : #FILES ( -- n) SYS-SIZE 8K /MOD SWAP IF 1+ THEN ; : CODECHUNK ( n -- addr) IMGSIZE * HIMEM + ; : CHUNKSIZE ( n -- n ) CODECHUNK END SWAP - IMGSIZE MIN ; : LASTCHAR++ ( Caddr len --) 1- + 1 SWAP C+! ; : HEAPSIZE ( -- n) H @ LOWRAM - ; : ?PATH ( addr len -- addr len ) 2DUP [CHAR] . SCAN NIP 0= ABORT" Path expected" ; : GET-PATH ( <text>) BL PARSE-WORD ?PATH PAD PLACE ; : FILENAME ( -- addr len) PAD COUNT ; VARIABLE FILECOUNT : SAVE-IMAGE ( addr len Vaddr size -- ) CR ." Writing file: " FILENAME TYPE HEADLEN + PROGRAM SAVE-FILE FILENAME LASTCHAR++ FILECOUNT 1+! ; : SAVELO ( -- ) HEAPSIZE IF LOWRAM HEAPSIZE DUP>R FALSE HEADER \ heap is last file saved LOWRAM CODEBUFF R@ VWRITE \ copy HEAP to VDP FILENAME VDPBUFF R> SAVE-IMAGE THEN ; HEX : SAVEHI ( XT -- <textpath> ) #FILES 0 ?DO \ compute file header values I CODECHUNK I CHUNKSIZE ( -- addr size ) I 1+ #FILES <> HEAPSIZE 0> OR \ multiflag=true if heap has data ( addr size ?) HEADER \ store in file header \ Copy to VDP RAM LOADADDR V@ CODEBUFF PROGSIZE V@ HEADLEN + VWRITE \ write VDP to disk" FILENAME VDPBUFF PROGSIZE V@ SAVE-IMAGE LOOP ; : .BYTES&ADDR ( addr size --) DECIMAL 5 U.R ." bytes, at " HEX ." >" 4 U.R ; : REPORT CR CR ." Himem : " HIMEM ORGDP @ OVER - .BYTES&ADDR CR ." Heap : " LOWRAM HEAPSIZE .BYTES&ADDR CR ." Saved in " FILECOUNT @ . ." EA5 files" CR ; : SAVESYS ( xt -- <path>) BOOT ! FILECOUNT OFF GET-PATH SAVEHI SAVELO REPORT ; HERE SWAP - CR DECIMAL . .( bytes) \ ---------------- \ TEST CODE INCLUDE DSK1.MALLOC HEX 800 MALLOC CONSTANT MYBUFFER \ mybuffer is in Low RAM MYBUFFER 800 CHAR $ FILL : GO WARM ABORT ; \ minimum startup code to start Forth interpreter LOCK \ lock dictionary to current size on re-boot ' GO SAVESYS DSK7.TESTKERNEL 3 Quote Link to comment https://forums.atariage.com/topic/273872-camel99-forth-information-goes-here/page/66/#findComment-5312275 Share on other sites More sharing options...
+TheBF Posted September 13, 2023 Author Share Posted September 13, 2023 Now I remember why I never tried to completely re-write my cross-compiler. My head is spinning. Last Wednesday I got the import utility working. It as taken me until now, working on and off to get this re-compiler to work. I just had to get it to build a program and see it run. Here is the first recompiled program with all the magic incantations it currently takes to make it go. I will work on improving the noise but it's good in the beginning to understand what is happening. I also have see if my relocation method in Machine Forth can be squeezed into service here. Right now the programs are AORG >2000 only. The demo starts and runs BYE. It is real indirect threaded code but there is no dictionary or interpreter or compiler included in the program. That is all provided by the "HOST" Forth. It compiles to 122 bytes. \ TEST PROGRAM 1 COMPILER NEW HEX 2000 ORG WARNINGS OFF INCLUDE DSK7.EXECUTORS \ load EXIT DOCOL DOVAR ETC. INCLUDE DSK7.BRANCHING \ compilers: IF THEN BEGIN AGAIN... INCLUDE DSK7.ITCTYPES \ CONSTANT VARIABLE : ; ETC. WARNINGS ON TARGET ALSO FORTH \ import needs to see Forth to find kernel primitives IMPORT: + COMPILER HEX TARGET VARIABLE BOOT \ hold the cfa of the word that boots from COLD \ bye does not end with NEXT so we can' import it CODE BYE 0 LIMI, 0 @@ BLWP, NEXT, ENDCODE CODE COLD \ COLD is a key primitive that builds the Forth VM 8300 LWPI, \ set 9900 workspace SP 3FFE LI, \ data stack RP 3FE0 LI, \ return stack IP BOOT LI, \ load interpreter pointer with boot word R10 EXIT CELL+ LI, \ EXIT + 2 = NEXT -> R10 *R10 B, \ jump to NEXT (inner interpreter) ENDCODE TARGET : MAIN BYE ; COMPILER HEX T' MAIN BOOT HOST ! \ set the boot variable COMPILER T' COLD >BODY 2002 HOST ! SAVE DSK7.TESTPROG1 For the masochists in the group you can see the source code here and the bin folder has everything in TI-99 format. bfox9900/RECOMPILER: Re-compiles Forth code as binary program without interpreter or dictionary (github.com) Here is a little video showing the process. The program is totally "under-whelming" but its a start. forth recompiler.mp4 3 Quote Link to comment https://forums.atariage.com/topic/273872-camel99-forth-information-goes-here/page/66/#findComment-5315773 Share on other sites More sharing options...
+TheBF Posted September 14, 2023 Author Share Posted September 14, 2023 No matter how many times I do these Forth compiler things I always get something messed up initially. I guess if it was easy everybody would be doing it. I had an error in what I compiled into the program when ';' is encountered but thanks to the Classi99 debug window it was easy to see. I had forgotten that EXIT must be a proper CODE word so the Forth compiler can"compile" it correctly. I initially gave it a label like DOVAR DOCON etc. The new file is here: RECOMPILER/src/EXECUTORS.FTH at main · bfox9900/RECOMPILER · GitHub Here is the little program I used to test "nested" colon definitions and it works as advertised now. It compiles to 176 bytes. Something to be aware of is that these programs have no code in scratchpad RAM currently. So they will run ~20% slower than Camel99 Forth. But that can be fixed later. I am slowly learning how to simplify the "noise" in the code. I realized that COLD just needs to be a label since I branch directly into it at startup. I think I will wrap up the first 26 lines into a "PREAMBLE" file that sets up the program. Things I think I like: The Virtual machine is a separate file that you load first The branch and loop compilers are a separate file The data description words are a separate file In "theory" this means we could change the threading type to direct-threaded with very little trouble. 😇 So with this level of compiler working I should be able to make a little VDP I/O library and write HELLO world next. I played too many games with Camel99 VDP I/O to allow it to imported as is. (I used a BL sub-routine to set VDP addresses which is not relocatable) No worries. I can make something smaller if I don't worry about speed. \ NESTTEST.FTH test nested calls COMPILER NEW HEX 2000 ORG WARNINGS OFF INCLUDE DSK7.EXECUTORS \ load EXIT DOCOL DOVAR ETC. INCLUDE DSK7.BRANCHING \ compilers: IF THEN BEGIN AGAIN... INCLUDE DSK7.ITCTYPES \ CONSTANT VARIABLE : ; etc. WARNINGS ON COMPILER HEX TARGET VARIABLE BOOT \ hold the cfa of the word that boots from COLD L: COLD \ COLD runs at boot time to build the Forth VM 8300 LWPI, \ set 9900 workspace SP 83FE LI, \ data stack in scratchpad RP 83D0 LI, \ return stack in scratchpad IP BOOT LI, \ load interpreter pointer with boot word R10 _NEXT LI, \ inner interpreter stays in R10 *R10 B, \ jump into the interpreter TARGET ALSO FORTH IMPORT: DUP DROP 1- TARGET CODE BYE 0 LIMI, 0 @@ BLWP, ENDCODE : SUB3 BEEF ; : SUB2 SUB3 ; : SUB1 SUB2 ; : MAIN 4000 BEGIN 1- DUP WHILE SUB1 DROP REPEAT DROP BYE ; COMPILER HEX COLD 2002 T! \ jumps into cold on startup T' MAIN BOOT T! \ set the boot variable SAVE DSK7.NESTTEST 1 Quote Link to comment https://forums.atariage.com/topic/273872-camel99-forth-information-goes-here/page/66/#findComment-5316888 Share on other sites More sharing options...
+TheBF Posted September 15, 2023 Author Share Posted September 15, 2023 So now that the compiler works correctly I took some time to improve the user experience. As mentioned I consolidated all the preamble into a file called ITC-FORTH. So you just include that file before any other code is compiled. I changed IMPORT: so it knows to search in the CAMEL99 Forth dictionary for primitive words. Before you had to manually add Forth to the search order. I add the command AUTOSTART so it is simple to specify the routine that will run after COLD builds the Forth stacks and sets the workspace. And with that here is modestly complicated "recompiled" Forth program that compiles and runs. \ TESTPROG2.FTH Demo IMPORT: CODE loops and AUTOSTART Sep 2023 Fox HEX 2000 ORG \ this must be set before compiling any code INCLUDE DSK7.ITC-FORTH \ preamble for indirect threaded Forth IMPORT: DUP DROP 1- 0= COMPILER HEX TARGET CODE BYE ( --) 0 LIMI, 0 @@ BLWP, NEXT, ENDCODE : LOOP2 ( -- ) 5000 BEGIN 1- DUP 0= UNTIL DROP ; : LOOP1 ( -- ) 5000 BEGIN 1- DUP WHILE REPEAT DROP ; : MAIN ( -- ) LOOP1 LOOP2 BYE ; COMPILER AUTOSTART MAIN SAVE DSK7.TEST2 That's enough for one day. I am behind on my Viola practicing. 2 Quote Link to comment https://forums.atariage.com/topic/273872-camel99-forth-information-goes-here/page/66/#findComment-5317060 Share on other sites More sharing options...
+TheBF Posted September 15, 2023 Author Share Posted September 15, 2023 So I wondered what would happen if I wrote an entire VDP driver in Forth? 🙂 It's not fast. That for certain. But to satisfy everyone's curiosity here is a set of VDP words written entirely in Forth except of a code word to turn off the interrupts. I added -UNTIL (not until) which is something Chuck invented for Machine Forth because it is faster than 0= UNTIL. I chose to try it without DO/LOOP first because DO LOOP adds some runtime code to the program. It's pretty tidy, but it would benefit greatly from a code word like R+! or R1-! to manage the counter on the return stack As I think about it adding a machine Forth style FOR/NEXT loop would be trivial with R1-! in the system. : -UNTIL POSTPONE WHILE POSTPONE REPEAT ; IMMEDIATE HEX 8800 CONSTANT VDPRD \ vdp ram read data 8802 CONSTANT VDPSTS \ vdp status 8C00 CONSTANT VDPWD \ vdp ram write data 8C02 CONSTANT VDPWA \ vdp ram read/write address \ VDP set-address sub-routines CODE 0LIMI 0 LIMI, NEXT, ENDCODE : RMODE ( vdpaddr -- ) DUP 0LIMI VDPWA C! >< VDPWA C! ; : WMODE ( vdpaddr -- ) 4000 OR RMODE ; : VC@+ ( Vdpaddr -- c) VDPRD C@ ; \ read & inc. address : VC!+ ( c --) VDPWD C! ; \ write & inc. address : VC@ ( VDP-adr -- char ) RMODE VDPRD C@ ; : VC! ( c vaddr --) WMODE VC!+ ; \ set address and write \ VDP integer fetch & store : V@ ( VDPadr -- n) VC@ VC@+ FUSE ; : V! ( n vaddr --) >R SPLIT R> VC! VC!+ ; : VWRITE ( RAM-addr VDP-addr cnt -- ) SWAP WMODE >R BEGIN COUNT VC!+ R> 1- DUP >R -UNTIL R> 2DROP ; : VREAD ( Vaddr Ram cnt --) >R SWAP RMODE BEGIN VC@+ OVER C! R> 1- DUP >R -UNTIL R> 2DROP ; : VFILL ( vaddr cnt char --) SWAP >R SWAP WMODE BEGIN DUP VC!+ R> 1- DUP >R -UNTIL R> 2DROP ; 1 1 Quote Link to comment https://forums.atariage.com/topic/273872-camel99-forth-information-goes-here/page/66/#findComment-5317374 Share on other sites More sharing options...
+TheBF Posted September 15, 2023 Author Share Posted September 15, 2023 Once you have the VDP driver you can make some standard output words. I am testing all this on Camel Forth before making it a library for the recompiler project. I will probably have to relent and use Assembler for the VDP library to make work better but this was a fun exercise. Spoiler \ Standard Forth output words VARIABLE C/L C/L@ C/L ! VARIABLE COL VARIABLE ROW VARIABLE CURSOR VARIABLE C/SCR 3C0 C/SCR ! 20 CONSTANT BL : >VPOS ( col row -- vaddr) C/L @ * + ; : CLIP ( n lo hi -- n) ROT MIN MAX ; : CURSOR ( -- Vaddr) COL @ ROW @ >VPOS 0 C/SCR @ CLIP ; : COL+! ( n -- ) COL @ + DUP C/SCR @ > IF DROP COL OFF EXIT THEN COL ! ; : ROW+! ( n -- ) ROW @ + 0 23 CLIP ROW ! ; : EMIT ( c --) CURSOR VC! 1 COL+! ; : CR ( -- ) 1 ROW+! COL OFF ; : SPACE BL EMIT ; : TYPE ( addr len -- ) >R BEGIN COUNT EMIT R> 1- DUP >R -UNTIL R> 2DROP ; : AT-XY ( col row -- ) ROW ! COL ! CURSOR WMODE ; : PAGE 0 C/SCR @ 20 VFILL 0 0 AT-XY ; : VDPTYPE ( addr len --) TUCK CURSOR SWAP VWRITE COL+! ; It not FAST doing it this way, putting a slooooow EMIT in a loop, but it works. (PAGE is painful to watch) But the text writing speed is not bad if you use the block write word VDPTYPE as seen in the 2nd video Test programs : TEST PAGE 100 BEGIN S" HELLO WORLD! " TYPE 1- DUP -UNTIL DROP ; : TEST2 PAGE 40 BEGIN S" Hello World! " VDPTYPE 1- DUP -UNTIL DROP ; VDP Driver in Forth.mp4 VDP fastwrite.mp4 2 Quote Link to comment https://forums.atariage.com/topic/273872-camel99-forth-information-goes-here/page/66/#findComment-5317396 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.