+TheBF Posted February 20, 2022 Author Share Posted February 20, 2022 40 minutes ago, apersson850 said: The language and system depends on who delivers it. We have our own controllers, usually running under Linux and using some C-compiler for the software development. Then we have systems purchased from the outside. Today, many use ISO 61131 programming, where we most of the time write programs using Structured text. That's a control system language developed with influence from Pascal. Some of these also support C programming. Some have limited communcation capabilities and thus require some form of low level programming in the motor drives themselves. Some systems are outside of that "standard". We have some that are programmed in somethnig called Trio Motion BASIC. That's a parallel processing BASIC version, with deterministic timing in task execution and motion system commands. It's typically used for synchronizing several motors in a machine. There could be dozens of motors in the same machine, where some of them belong to groups that are linked together in more or less complex ways. Safety related systems are running on special, redundant hardware, where they execute special programs, written by combininig a finite number of function blocks with each other. Very nice. So I was thinking this might be related to PLCs. I guess with ISO 61131 it is a component of this work. It sounds like you have custom equipment as well. Are you involved in a specific industry with all this cool stuff? Quote Link to comment Share on other sites More sharing options...
apersson850 Posted February 20, 2022 Share Posted February 20, 2022 (edited) Some systems are typical PLC devices, some are specific motion controllers, some comprise machine control, motion control, user interface, IoT server and safety functions in the same system. Our own are more like microcontrollers, or embedded devices. They lack complex motion and safety functions. Our business is peripheral equipment around high speed electronic printing devices. Tecnau Edited February 20, 2022 by apersson850 1 Quote Link to comment Share on other sites More sharing options...
Willsy Posted February 20, 2022 Share Posted February 20, 2022 IL is a 61131 language. Worth checking out, as its stack-based 1 Quote Link to comment Share on other sites More sharing options...
+TheBF Posted February 21, 2022 Author Share Posted February 21, 2022 (edited) Getting back to Machine Forth, I was re-reading the original Machine Forth source code by Chuck Moore and realized that I did not implement branching and looping the way Chuck did it in his processor. I had relied on work by Sam Falvo for the RISC V machine. Sam had used a JLE instruction for -IF ( NOT IF) Lee had mentioned that he wondered if that would work correctly. Chuck's CPU had three branch instructions: Un-conditional branch Branch if TOS=false ( I use the EQ bit) Branch if Carry=false I missed that third one. I in fact wrote it manually into the FOR NEXT loop structure to detect the zero crossing. DUH! So here is how the newly organized branching and looping looks now. COMPILER \ compute signed offset & compile into the 2nd byte of any 9900 JUMP instruction : RESOLVE ( byte --) 2- 2/ SWAP 1+ C! ; : <BACK ( addr addr' -- ) TUCK - RESOLVE ; : ?JUMP ( addr1 addr2) 0 JEQ, ; \ conditional jump : JUMP ( addr1 addr2) 0 JMP, ; \ un-conditional jump \ Changed to match Chuck's original code and use Carry flag : -IF ( -- $$) THERE 0 JOC, ; \ goto THEN if Carry=TRUE : IF ( -- $$) THERE ?JUMP ; \ goto THEN if EQ=TRUE : THEN ( addr --) THERE OVER - RESOLVE ; : ELSE ( -- $$ ) THERE JUMP SWAP THEN ; : BEGIN THERE ; IMMEDIATE \ THERE current dictionary address : WHILE IF SWAP ; \ loop while EQ=false : -WHILE -IF SWAP ; \ loop while Carry=false : AGAIN ( addr --) THERE JUMP <BACK ; \ Jump back always : UNTIL ( addr --) THERE ?JUMP <BACK ; \ jump back until EQ=false : -UNTIL ( addr --) -IF <BACK ; \ jump back until Carry=false : REPEAT ( addr -- ) AGAIN THEN ; So far the tests programs still run. (The reason I got here was because I am trying learn how to use IF/THEN optimally in this weird Forth dialect for a screen driver) FOR/NEXT becomes : FOR ( n --) I RPUSH, I! POSTPONE BEGIN ; : NEXT ( -- ) I1-! -UNTIL I RPOP, ; Edited February 21, 2022 by TheBF fixed comment 2 Quote Link to comment Share on other sites More sharing options...
apersson850 Posted February 21, 2022 Share Posted February 21, 2022 13 hours ago, Willsy said: IL is a 61131 language. Worth checking out, as its stack-based You can just as well use assembly. It's the same look and feel. IL is there just to plese the people who did PLC programming using hand units, where that was the only thing available. Just like ladder logic is there to try to make electricians think they can do programming. 3 Quote Link to comment Share on other sites More sharing options...
Willsy Posted February 21, 2022 Share Posted February 21, 2022 Is the stack comment for RESOLVE correct? Looks iffy. What is that SWAP doing? Quote Link to comment Share on other sites More sharing options...
+TheBF Posted February 21, 2022 Author Share Posted February 21, 2022 4 hours ago, Willsy said: Is the stack comment for RESOLVE correct? Looks iffy. What is that SWAP doing? You are correct there should also be an address shown which is the location of the jump instruction. What is SWAP doing? That's PFM. That is what we called it when I was in an engineering department. ( Pure f... g magic) With IF and -IF I believe the SWAP is shuttling the THERE address from BEGIN to the top, so it can be used by AGAIN UNTIL or -UNTIL. If there was a control flow stack the THERE would sitting on top of that stack and SWAP would not be needed. That's my story and I'm sticking to it. 1 Quote Link to comment Share on other sites More sharing options...
+TheBF Posted February 26, 2022 Author Share Posted February 26, 2022 After building what seems to be a stable VDP library and SCREEN I/O words for MachForth I have decided not to follow Chucks lead with the Carry flag based loops. Using the carry flag means that loops iterated 1 extra time. This makes the code strange because you can't use the natural value of the loop iterator like a string count. You have to decrement it first. I don't like that. So I have simply used JEQ and JNE instructions for the default branching. Spoiler \ compute signed offset & compile into the 2nd byte of any 9900 JUMP instruction : RESOLVE ( addrofjmp byte --) 2- 2/ SWAP 1+ C! ; : <BACK ( addr addr' -- ) TUCK - RESOLVE ; \ Changed from Chuck's original code and use EQ flag : -IF ( -- $$) THERE 0 JEQ, ; \ GOTO then if EQ=true : IF ( -- $$) THERE 0 JNE, ; \ GOTO then if EQ=false \ Chuck used Carry flag conditional. Kept it with new name : NC.IF ( -- $$) THERE 0 JOC, ; \ GOTO then if Carry=True : THEN ( addr --) THERE OVER - RESOLVE ; : ELSE ( -- $$ ) THERE 0 JMP, SWAP THEN ; : BEGIN THERE ; IMMEDIATE \ THERE current dictionary address : WHILE IF SWAP ; \ loop while EQ=false : -WHILE -IF SWAP ; \ loop while Carry=false : AGAIN ( addr --) THERE 0 JMP, <BACK ; \ Jump back always : UNTIL ( addr --) -IF <BACK ; \ jump back until EQ=false : -UNTIL ( addr --) IF <BACK ; \ jump back until EQ=true : REPEAT ( addr -- ) AGAIN THEN ; A complication that I needed for screen I/O was magnitude comparisons. I didn't quite see how to that in Chuck's machine Forth short of using subtraction. That did not appeal to me so I decided to add the words >IF and <IF. These use the SIGNED jump instructions and the C (compare) instruction. They are consistent with Machine Forth principal that they do not clean off the top of stack so that it can be used, but they do remove the next item on stack (NOS). It seems to be a workable way to do this. With these I was able to successfully build a working set of Forth screen words. Spoiler : = *SP+ TOS CMP, ; : 0= TOS 0 CI, ; \ signed comparisons : >IF ( n n -- $$) = THERE 0 JLT, ; \ goto then low or eq : <IF ( n n -- $$) = THERE 0 JGT, ; SCREENIO library code Edit: fixed VPOS and EMIT and SCROLL comment (A cool thing is the use of tail-call optimization which saves 9 bytes in this simple example) Spoiler INCLUDE DSK2.VDPLIB \ Screen I/O code COMPILER DECIMAL 40 CONSTANT C/L \ chars per line 960 CONSTANT C/SCR \ chars per screen 920 CONSTANT 23LINES OPT-ON TARGET VARIABLE ROW VARIABLE COL VARIABLE MDP : MFHERE MDP @ ; \ points to empty memory above CODE : AT-XY ( col row --) ROW ! COL ! ; : VPOS ( -- Vaddr) ROW @ C/L * COL @ + ; : PAGE ( -- ) 0 0 AT-XY VPOS C/SCR BL VFILL -; \ tail call optimize : SCROLL ( -- ) \ full-screen buffer. wasteful but fast 0 DUP C/L + MFHERE 23LINES VREAD \ get 2nd line MFHERE OVER 23LINES VWRITE \ write to 1st line DROP 0 23 AT-XY VPOS C/L BL VFILL -; \ tail call optimize : CR ( -- ) COL OFF ROW DUP 1+! @ 24 >IF SCROLL THEN DROP ; : EMIT ( c --) VPOS VC! COL DUP 1+! @ C/L 1- >IF CR THEN DROP ; : TYPE ( addr len --) FOR COUNT EMIT NEXT DROP ; : SPACE ( -- ) BL EMIT -; \ tail call optimize : SPACES ( n -- ) FOR SPACE NEXT ; The code above with OPT-ON uses 656 bytes of code space including the VDPLIB code. (The POP/PUSH optimizer saves 14 bytes) I cheaped out on SCROLL and just copy all the screen minus the topline into empty memory above the code. I will change that in future but it works for now. It means I have to patch the value in the MDP variable after compiling the program. Below is the test program that exercised thE screen I/O words. At the moment the POP/PUSH optimizer fails on the SCROLLTEST word. (Not sure why) So we just disable it for that line. Spoiler \ scroll and screen i/o Test program COMPILER \ preamble to set up target image NEW. HEX 2000 ORIGIN. INCLUDE DSK2.SCREENIO : WAIT 65000 FOR NEXT ; CREATE A$ S" This is line 1. " S, CREATE B$ S" 2nd line of text" S, OPT-OFF : SCROLLTEST CR 300 FOR A$ COUNT TYPE NEXT ; OPT-ON DECIMAL PROG: MAIN PAGE 0 0 AT-XY A$ COUNT TYPE CR B$ COUNT TYPE WAIT SCROLLTEST NEXT, \ return to Camel99 Forth END. \ patch TARGET variable that holds end of memory COMPILER THERE 4 + [ TARGET MDP ] HOST ! TARGET Next I need to write the number printing words. machforth-screen-test.mp4 3 Quote Link to comment Share on other sites More sharing options...
+TheBF Posted February 27, 2022 Author Share Posted February 27, 2022 Maybe I should listen to this guy. https://write.as/loke/dont-write-a-programming-language Nah! 1 Quote Link to comment Share on other sites More sharing options...
+TheBF Posted March 2, 2022 Author Share Posted March 2, 2022 Once you have SCREEN I/O words working it's time to make number printing routines. I chose to try and port the standard Forth number formatting words so that MachForth would have 16 bit and 32 bit output capability. I am beginning think that I am just not quite as smart as Chuck Moore ( ya figure?) I don't think I can cope with the limited set of 'IF' statements that he uses in his Machine Forth. I had to create some new words here to make this code work. It's not as pretty as I like so I suspect I will be going back to something that works the way Forth Assembler works on the 9900. For now I will live what I have. Note: This machine forth is weird enough that it takes some learning to use it. I am thinking I will be morphing it into something that looks more like standard Forth. For now I will make it work, then make it better Different Tradeoffs versus ITC I changed the standard 'dot' word that prints a signed integer. It uses ROT which Machine Forth did not have. There are very different tradeoffs to be made when you use "nest-able" sub-routines on the 9900. They are terribly pregnant taking 2 instructions to enter and 2 instructions to exit. In the Forth universe where one virtual instruction can be only one machine instruction that is just not acceptable. Most of the "intrinsic instructions" in MachForth are therefore inlined as was done in the original "OK" version of Machine Forth. Building the 'dot' word with ROT took 16 extra bytes versus using some optimized words DUP>R and RDROP along with R@. DUP>R and RDROP are both inline instructions on the 9900 MachForth. (I could have made ROT inline but that just seemed wrong) \ conventional Forth version : ROT ( n1 n2 n3 -- n2 n3 n1) [ 2 (SP) R1 MOV, *SP 2 (SP) MOV, TOS *SP MOV, R1 TOS MOV, ] ; : . ( n -- ) DUP ABS 0 <# #S ROT SIGN #> TYPE SPACE -; Smaller MachForth version : . ( n -- ) DUP>R ABS 0 <# #S R@ SIGN #> RDROP TYPE SPACE -; Here are the newly hatched number formatting library. Spoiler \ NUMBER OUTPUT lib for MachForth Mar 3 2022 Brian Fox COMPILER \ constants declared in compiler space CHAR 0 CONSTANT '0' CHAR - CONSTANT '-' HEX 3FFF CONSTANT #BUFF \ top end of low RAM used as digit buffer \ REQUIRES DSK2.SCREENIO OPT-ON TARGET VARIABLE BASE VARIABLE HP : UM/MOD ( ud u1 -- u2 u3 ) \ numerator(32bits), divisor -- rem,quot TOS R0 MOV, \ divisor->R0 *SP+ TOS MOV, \ POP high word into TOS *SP R5 MOV, \ MOVE low word to r5 R0 TOS DIV, \ perform unsigned division R5 *SP MOV, \ push remainder ; DECIMAL \ : >DIGIT 9 >IF DROP 7 + THEN DROP '0' + ; \ does not work ?? : >DIGIT ( n -- c) [ TOS 9 CI, HI IF, \ if n>9 TOS 7 AI, \ number is not base 10, add 7 ENDIF, TOS '0' AI, ] \ add ASCII 0 to TOS create char value ; \ : HOLD ( char -- ) HP DUP 1-! @ C! ; \ 11 instructions : HOLD ( char -- ) \ 5 instructions [ HP @@ DEC, HP @@ R1 MOV, TOS SWPB, TOS R1 ** MOVB, ] DROP ; \ [ BASE #@ ] does symbolic addressing to fetch a variable : # ( u -- ud2 ) 0 [ BASE #@ ] UM/MOD >R \ compute & save high side of 32 bit int. [ BASE #@ ] UM/MOD SWAP \ compute low side, swap quotient & remainder >DIGIT HOLD R> \ high side to TOS ; : <# ( --) #BUFF -> HP ; \ TOS->symbolic store. Smaller & faster \ '==' is non-destructive comparison of TOS & NOS ( C R4,*SP ) : #S ( ud1 -- ud2) BEGIN # == -UNTIL ; : #> ( ud1 -- c-addr u) DROP DROP HP @ #BUFF OVER - ; : SIGN ( n -- ) 0 <IF '-' HOLD THEN DROP ; : UD. ( d -- ) <# #S #> TYPE SPACE -; : U. ( u -- ) 0 UD. -; : . ( n -- ) DUP>R ABS 0 <# #S R@ SIGN #> RDROP TYPE SPACE -; Here is the test program Spoiler \ MachForth number output test program Mar 2 2022 Brian Fox COMPILER \ preamble to set up target image NEW. HEX 2000 ORIGIN. \ constants declared in compiler space CHAR 0 CONSTANT '0' CHAR - CONSTANT '-' 3F00 CONSTANT #BUFF INCLUDE DSK2.SCREENIO INCLUDE DSK2.NUMBERS CREATE TITLE$ S" Signed Un-signed" S, OPT-OFF PROG: MAIN 10 BASE ! PAGE 8 9 AT-XY TITLE$ COUNT TYPE 32000 \ first number in count 2001 FOR 9 10 AT-XY DUP . SPACE DUP U. 1+ NEXT NEXT, END. TARGET Weird syntax Reminders: [ enables the interpreter and is used to put Assembler code into a colon definition. Also can be used to "interpret" a variable, which returns the data address. Used with SYMBOLIC fetch and store: #@ #! ] turns the compiler back on. Used when you go back to compiling Forth code after ALC. -> emits register to symbolic code to load a variable from top of Forth stack -; Tail-call optimizer. Replace a sub-routine call with a branch instruction. Only if the last word is called (not inlined) MachForthNumbers.mp4 2 Quote Link to comment Share on other sites More sharing options...
+TheBF Posted March 3, 2022 Author Share Posted March 3, 2022 Symbolic Addressing makes a Big Difference Now that the MachForth compiler is getting more stable, I am cleaning up a set of test programs that provide examples of how to do things in MachForth. I am slowly getting something ready on Github for those brave souls that might want to play with this thing. (It's a toy at the moment because the library code is sparse but it's been a great education in Assembly language for me) This program shows how using symbolic addressing reduces code size and improves speed when using variables. The native Forth way of storing to a variable is: (Top of stack is cached in a register here so it's slower than using TOS in memory) Push the TOS cache register to free it for a new use (2 instructions) LI the variable's address into TOS register ( 1 instruction) Then store : MOV *SP,*R4 ( 1 instruction) and refill TOS: MOV *SP+,R4 ( 1 instruction) The program code has a table showing the effects of the different operators with and without the optimizer. I also tested the equivalent test program on Camel99 Forth. It's 5X slower. Humbling. \ MachFORTH DEMO #5: Using variables and constants \ Compiler Preamble COMPILER NEW. HEX 2000 ORIGIN. \ declare a constant in COMPILER space. -3 CONSTANT K OPT-OFF \ Operators OPT-ON Bytes OPT-OFF Bytes \ ------------ --------- ----- | ------- ----- \ Normal ! and +! 12.30 secs 82 | 15.56 94 \ Symbolic -> and ->+ 9.31 secs 70 | 12.56 82 \ Camel99 Forth 47.70 secs TARGET VARIABLE X VARIABLE Y VARIABLE Z PROG: DEMO5 TRUE \ put -1 (>FFFF) on data stack BEGIN 1- WHILE K ->+ X \ add K to symbolic address \ K X +! ( normal Forth syntax) Y 1+! X @ Y @ + -> Z \ symbolic address store \ X @ Y @ + Z ! ( normal Forth syntax) REPEAT DROP NEXT, \ return to Camel99 Forth END. \ ELAPSE DEMO5 RUN 4 Quote Link to comment Share on other sites More sharing options...
+TheBF Posted March 4, 2022 Author Share Posted March 4, 2022 OK so it's not where I want it to be but if anybody has nothing better to do here is the first cut of MachForth (alpha alpha) bfox9900/MachForth: Native code compiler for 9900 modeled on machine Forth by Charles Moore with liberties taken. (github.com) Browse to the TI-99 folder and make a DSK2 for yourself on Classic99 with the files in that folder. Follow the instructions in the readme. All the other Demo programs are in the DEMO.SRC folder. They are PC text files that you can past into MACHFORTH. The command NEW. does not clean up the compiler dictionary at this time so after pasting a program into the compiler a few times you can blow it up. For now restart it when that happens. I have added a STDLIB file that pulls in VDPLIB, SCREENIO, NUMBERS and BYE. With that you can write you own MachForth program. DOT QUOTE ( ." ) is not implemented yet so look at the DEMOs if you want to make strings. The video shows the operation for the simplest HELLO23 program with no libraries used. I have a lot of documents to make but the compiler source code is there if anybody is curious. MachForth-in-action.mp4 2 1 Quote Link to comment Share on other sites More sharing options...
+TheBF Posted July 25, 2022 Author Share Posted July 25, 2022 After I found an error in the SAVESYS word in Camel99 Forth I recompiled MACHFORTH and re-saved it. It's amazing how much more stable programs are when the compiler binary is not corrupted in random places! Anyway I have been slowly improving Machforth. It is way harder than I imagined but it is much more stable now and pretty fast. For a test I compiled the Tursi Sprite bench mark in MachForth using the Assembler only to turn off interrupts. The video shows the build process and running the program, end to end. The syntax is now getting much closer to standard Forth. Extra directives are required to deal with compiling to an executable. The final program size is 964 bytes. (The VDPLIB is 208 bytes ) With a runtime time of about 15 seconds this program matches the un-optimized GCC version of this benchmark. Using scratchpad RAM for the workspace and Forth's stacks helps. The best way to speed this up now is to replace the inner loop code in the four FOR/NEXT loops with one line of Forth Assembler, or write a word that writes a byte to VDPWD. We shall see. I have to package this thing up better for anybody who would want to play around so that's on the stack. Spoiler \ Tursi's benchmark in simple machine Forth 2022 Brian Fox COMPILER NEW. HEX A000 ORIGIN. \ constants take no space in the program unless used. 380 CONSTANT CTAB \ colour table VDP address 800 CONSTANT PDT \ "pattern descriptor table" VDP address 300 CONSTANT $300 \ sprite0 Y position 301 CONSTANT $301 \ sprite0 X poisition INCLUDE DSK2.VDPLIB INCLUDE DSK2.BYE TARGET VARIABLE C/L VARIABLE C/SCR VARIABLE VMODE HEX : GRAPHICS 0 CTAB 0 VFILL 0E0 DUP 83D4 C! 1 VWTR 0 2 VWTR \ set VDP screen page 0E 3 VWTR 01 4 VWTR 06 5 VWTR 01 6 VWTR CTAB 10 10 VFILL \ charset colors 27 7 VWTR \ screen color 20 C/L ! 300 C/SCR ! 1 VMODE ! 0 300 BL VFILL -; \ tail call optimizer used HEX : MAGNIFY ( mag-factor -- ) 83D4 C@ 0FC AND + DUP 1 VWTR 83D4 C! ; : SPRITE0 ( char colr x y -- ) \ setup SPRITE #0 300 VC! \ set Y position 301 VC! \ set X position 303 VC! \ set the sprite color 302 VC! \ set the character pattern to use ; PROG: TURSI \ setup Forth machine in scratchpad RAM [ 0 LIMI, 8300 WORKSPACE 83BE RSTACK 83FE DSTACK ] \ benchmark begins here DECIMAL GRAPHICS 42 4 0 0 SPRITE0 1 MAGNIFY [ 0 LIMI, ] 100 FOR \ using TOS for up counting 0 239 FOR DUP $301 VC! 1+ NEXT DROP 0 175 FOR DUP $300 VC! 1+ NEXT DROP \ for/next index is a down-counter 239 FOR I@ $301 VC! NEXT 175 FOR I@ $300 VC! NEXT NEXT BYE END. COMPILER SAVE DSK2.TURSI MACHFORTH-TURSI-BENCHMARK.mp4 2 Quote Link to comment Share on other sites More sharing options...
+TheBF Posted November 3, 2022 Author Share Posted November 3, 2022 (edited) I finally got back to tackling a few big bugs with MachForth. NEW. directive was not resetting the MFORTH name space back to the initial compiler word set. (kind of important to not destroy the compiler when you reset) Push/pop optimizer logic at this time doesn't work in perfectly in a loop. Removed from the I@ keyword the FOR NEXT index word. Not happy with previous syntax for invoking symbolic addressing mode or immediate instructions NEW. is fixed. I@ won't crash your code with the OPT-ON directive. I think I have a workable syntax now to generate symbolic addressing with variables or literal addresses that is not too strange and The problem in a nutshell is that when compiling a statement with a variable with this system, like: 994A X ! Code is generated that pushes the top-of-stack (TOS) cache register and then code is generated to load the address into TOS. For symbolic addressing we want that variables to be handled differently. Since Forth is using POSTFIX notation there is no natural "prefix" to indicate that we want to treat this variable differently. It would be great if we could just turn off the compiler and interpret the variable and get its address... Oh ya! You can do that in Forth. The command to turn off the Forth compiler is just square bracket. ( [ ) So I made a set of duplicate operators in this compiler that end with closed square bracket. So to invoke symbolic addressing in the latest version you would simply use this statement. 994A [ X !] Immediate operand instructions can use this too. Below is the normal syntax 1 1 + ( this performs A *SP+,TOS ) This will use the AI instruction. 1 [ 1 +] ( AI TOS,1 ) How much difference does it make? Quite a bit. Here is a minimal hello world with optimizer off and conventional syntax. 146 bytes. Spoiler \ tiny hello world in machine Forth Demo Sept 23 2021 Fox \ uses more common forth syntax, otpimizer off, compiles to 146 bytes COMPILER \ Use compiler wordlist (for interpreted words) NEW. HEX A000 ORIGIN. OPT-OFF \ equates and constants must be defined in COMPILER space \ They don't generate code until they are used HEX 8C02 CONSTANT VDPWA \ Write Address port 8C00 CONSTANT VDPWD \ Write Data port TARGET \ Use TARGET wordlist (to compile code) CREATE TXT S" Hello World!" S, HEX PROG: MAIN [ 0 LIMI, \ disable interrupts 8300 WORKSPACE \ Fast ram for registers 83BE RSTACK \ and return stack 83FE DSTACK ] \ and Data stack 0 VDPWA C! \ store VDP address LSB 4000 VDPWA C! \ store VDP address MSB + "write" bit TXT COUNT 1- FOR COUNT VDPWD C! NEXT DROP BEGIN AGAIN \ loop forever END. COMPILER SAVE DSK2.HELLO3 Same program with push/pop optimizer on. 134 bytes Spoiler \ tiny hello world in machine Forth Demo Nov 2 2022 Fox \ uses more common forth syntax, optimizer ON, compiles to 134 bytes COMPILER \ Use compiler wordlist (for interpreted words) NEW. \ clear the memory spaces HEX A000 ORIGIN. OPT-ON \ constants must be defined in COMPILER space \ They don't generate code until they are used HEX 8C02 CONSTANT VDPWA \ Write Address port HEX 8C00 CONSTANT VDPWD \ Write Data port TARGET \ Use TARGET wordlist (to compile code) CREATE TXT S" Hello World!" S, HEX PROG: MAIN [ 0 LIMI, \ disable interrupts 8300 WORKSPACE \ Fast ram for registers 83BE RSTACK \ and return stack 83FE DSTACK ] \ and Data stack 0000 VDPWA C! \ store VDP address LSB 4000 VDPWA C! \ store VDP address MSB + "write" bit TXT COUNT 1- FOR COUNT VDPWD C! NEXT DROP BEGIN AGAIN \ loop forever END. COMPILER SAVE DSK2.HELLO3A Optimized version now with symbolic addressing. 110 bytes! Spoiler \ tiny hello world in machine Forth Demo Nov 2 2022 Fox \ uses SYMBOLIC addresssing syntax, otpimizer ON, compiles to 110 bytes COMPILER \ Use compiler wordlist (for interpreted words) NEW. \ clear the memory spaces HEX A000 ORIGIN. OPT-ON \ constants must be defined in COMPILER space \ They don't generate code until they are used HEX 8C02 CONSTANT VDPWA \ Write Address port HEX 8C00 CONSTANT VDPWD \ Write Data port TARGET \ Use TARGET wordlist (to compile code) CREATE TXT S" Hello World!" S, HEX PROG: MAIN [ 0 LIMI, \ disable interrupts 8300 WORKSPACE \ Fast ram for registers 83BE RSTACK \ and return stack 83FE DSTACK ] \ and Data stack 0000 [ VDPWA C!] \ store VDP address LSB 4000 [ VDPWA C!] \ store VDP address MSB + "write" bit TXT COUNT 1- FOR COUNT [ VDPWD C!] NEXT DROP BEGIN AGAIN \ loop forever END. COMPILER SAVE DSK2.HELLO3B Edited November 3, 2022 by TheBF fixed typos 4 Quote Link to comment Share on other sites More sharing options...
Tuxon86 Posted November 3, 2022 Share Posted November 3, 2022 First, I'd like to give a big thank you for the work you're doing. Following your progress is really educational. If you ever need a new challenge after working on this Forth project, you could always port Brainfuck to the TI 🙂 1 Quote Link to comment Share on other sites More sharing options...
+TheBF Posted November 3, 2022 Author Share Posted November 3, 2022 (edited) Thanks. It keeps an old brain busy. I am trying to get to the stage where MachForth and the libraries are reliable so someone else could play the home game. Maybe I should call my version BRIANFUCK Actually @Willsy already made a compiler for it in a few lines of Forth and I just ported it to Camel99 Forth. Check it out CAMEL99-ITC/BRAINF_K.FTH at master · bfox9900/CAMEL99-ITC · GitHub Edited November 3, 2022 by TheBF fixed typos 1 1 Quote Link to comment Share on other sites More sharing options...
Tuxon86 Posted November 3, 2022 Share Posted November 3, 2022 39 minutes ago, TheBF said: Thanks. It keeps an old brain busy. I am trying to get to the stage where MachForth and the libraries are reliable so someone else could play the home game. Maybe I should call my version BRIANFUCK Actually @Willsy already made a compiler for it in a few lines of Forth and I just ported it to Camel99 Forth. Check it out CAMEL99-ITC/BRAINF_K.FTH at master · bfox9900/CAMEL99-ITC · GitHub Well, I’ve seen it all now. I can die in peace! 1 1 Quote Link to comment Share on other sites More sharing options...
+TheBF Posted November 4, 2022 Author Share Posted November 4, 2022 4 hours ago, Tuxon86 said: Well, I’ve seen it all now. I can die in peace! Please don't die. We're a small enough group already. I just noticed you are also in Canada. Bienvenue! Quote Link to comment Share on other sites More sharing options...
+TheBF Posted November 4, 2022 Author Share Posted November 4, 2022 I just aligned my memory-2-memory store to this newer syntax and did a test. Wow. I am wasting so much code doing things with standard Forth syntax I am thinking that I need to re-think the fundamental compiler operation. Or maybe these syntax enhancements are the simplest answer. That is more inline with Chuck's machine Forth for his CPUs. He used what worked for them and the 9900 is very different than the F21 or SHBOOM chips. The complications happen when you want to index into an array and you need the address in a register so you can add the index. Hmmm... Maybe for that I just need syntax that compiles to indexed-addressing... Look at these two examples and the resulting code. (-UNTIL is Chuck's machine Forth word for NOT UNTIL) Spoiler \ MACHFORTH DEMO #2: different ways to assign to variables \ Updated to MACHFORTH 2022 COMPILER NEW. HEX 2000 ORIGIN. \ * \ * NOTE: Optimizer breaks some loop exit conditions by removing instructions \ * OPT-OFF TARGET VARIABLE Z VARIABLE Y PROG: DEMO2 \ STANDARD SYNTAX 10 seconds FFFF Y ! BEGIN Y @ Z ! -1 Y +! -UNTIL \ 201C 0646 dect R6 (14) \ 201E C584 mov R4,*R6 (30) \ 2020 0204 li R4,>2006 (20) \ 2024 C114 mov *R4,R4 (26) \ 2026 0646 dect R6 (14) \ 2028 C584 mov R4,*R6 (30) \ 202A 0204 li R4,>2004 (20) \ 202E C536 mov *R6+,*R4 (42) \ 2030 C136 mov *R6+,R4 (30) \ 2032 0646 dect R6 \ 2034 C584 mov R4,*R6 \ 2036 0204 li R4,>ffff \ 203A 0646 dect R6 \ 203C C584 mov R4,*R6 \ 203E 0204 li R4,>2006 \ 2042 A536 a *R6+,*R4 \ 2044 C136 mov *R6+,R4 \ 2046 18EA joc >201c \ SYMBOLIC ADDRESSING: store to variable, memory to memory transfer, decrment \ 3 seconds FFFF [ Y !] \ TOS load to Y BEGIN [ Y Z ]! \ mem2mem move: Y @@ Z @@ MOV, [ Y 1-!] -UNTIL NEXT, \ return to Forth END. \ 2056 C820 mov @>2006,@>2004 (54) \ 205C 0620 dec @>2006 (34) \ 2060 18FA joc >2056 (14) 2 Quote Link to comment Share on other sites More sharing options...
+TheBF Posted November 4, 2022 Author Share Posted November 4, 2022 For a behind the curtain view here are the "compilers" that do this new syntax. Notice the use of ] to turn the compiler back on since [ has turned it off. \ immediate mode instructions : OR] ( n [n] --n) ] TOS SWAP ORI, ; : +] ( n [n] --) ] TOS SWAP AI, DROP ; \ (option 2) TOS + literal number : AND] ( n [n] --n) ] TOS SWAP ANDI, ; \ and TOS with literal number \ Symbolic addressing operators : @] ( addr -- n) ] SMARTDUP, ( addr) @@ TOS MOV, ; : !] ( n [var] --) ] TOS SWAP @@ MOV, DROP ; : +!] ( n [var] --) ] TOS SWAP @@ ADD, DROP ; : C!] ( c [var] --) ] TOS SWPB, TOS SWAP @@ MOVB, DROP ; : C@] ( [var] -- c) ] DUP @@ TOS MOVB, TOS 8 SRL, ; : 1+!] ( [var] --) ] @@ INC, ; : 1-!] ( [var] --) ] @@ DEC, ; \ mem2mem symbolic store and +! \ ** DO NOT CONFUSE WITH !] and +!] *** \ X @ Y ! is 10 instructions, 14 bytes \ [ X Y ]! is 1 instruction, 6 bytes : ]! ( addr1 addr2) SWAP @@ ROT @@ ] MOV, ; \ mem2mem store : ]+! ( addr1 addr2) SWAP @@ ROT @@ ] ADD, ; \ mem2mem plus-store 2 Quote Link to comment Share on other sites More sharing options...
Tuxon86 Posted November 4, 2022 Share Posted November 4, 2022 9 hours ago, TheBF said: Please don't die. We're a small enough group already. I just noticed you are also in Canada. Bienvenue! Merci! Quote Link to comment Share on other sites More sharing options...
+TheBF Posted November 4, 2022 Author Share Posted November 4, 2022 Adding indexed addressing to this thing was simpler than I realized. Of course, you need one for fetching and one for storing. I included the '+' sign in the syntax because that is the effective action. Q below is assumed to be an address. \ 12 [ Q +]@ : +]@ ( i addr -- n ) (TOS) TOS MOV, ; \ BEEF 13 [ Q +]! : +]! ( n i addr -- ) *SP+ SWAP (TOS) MOV, DROP ; 1 Quote Link to comment Share on other sites More sharing options...
+TheBF Posted November 6, 2022 Author Share Posted November 6, 2022 Making some progress with this compiler now that I have landed on final optimizing syntax for symbolic addressing. The whole thing is starting to feel stable. With the new syntax I changed statements in the SCREENIO lib that used variables and this reduced the size by a great deal. A side effect of the SCREENIO is that you need access to a buffer for scrolling. I chose to use the space above the program for the buffer so that it doesn't need to be part of the saved program image. This means you need a HERE word that returns the end of the code space. That's easy enough but you need to set the memory pointer to the end of the program before you save the program image. In the SCREENIO file I make a compiler directive called CONCUR. (with a period at the end) So after the program compiles and after the END. directive, you invoke CONCUR. if you are using SCREENIO and the program's MEM variable is set the same address and the compiler. The hardest part for users to get used to with Forth cross-compilers is that they work by changing the "name-space" so that the correct words are invoked. MachForth has the following name-spaces. Each name space sets the order that different vocabularies are searched. HOST synonym for FORTH COMPILER compiler directives are here. Some Forth words also work normally, others are changed to "compile" stuff into target memory TARGET Words in the target program, compiler directives. HOST Forth is cut-off. So when you see the code below, [CC] is a shortform for COMPILER and [TC] is shortform for TARGET Here is the SCREENIO lib written entirely in MachForth. It's important to note that the first version used @ , ! and +! in normal Forth syntax. That works fine. But symbolic addressing is a big win on the 9900 so the new syntax improves code size and run-time performance. Spoiler \ MACHFORTH SCREENIO library Nov 6 2022 Brian Fox TARGET NEEDS VWTR FROM DSK2.VDPLIB [CC] DECIMAL TARGET VARIABLE ROW VARIABLE COL VARIABLE C/L \ chars per line VARIABLE C/SCR \ chars per screen VARIABLE VPG \ VDP page VARIABLE MEM \ pointer to end of program memory VARIABLE VMODE OPT-OFF : HERE [ MEM @] ; \ points to empty memory above program : AT-XY ( col row --) [ ROW !] [ COL !] ; : VPOS ( -- Vaddr) [ ROW @] [ C/L @] * [ COL @] + ; : PAGE ( -- ) 0 0 AT-XY VPOS [ C/SCR @] BL VFILL -; \ tail call optimize [CC] HEX [TC] : SCROLL ( Vaddr -- Vaddr) [ VPG @] DUP>R [ C/L @] + HERE [ C/SCR @] VREAD \ get 2nd line & below HERE R> [ C/SCR @] [ C/L @] - VWRITE \ write to 1st line 0 17 AT-XY VPOS [ C/L @] BL VFILL -; \ tail call [CC] DECIMAL [TC] : CR ( -- ) COL OFF ROW DUP 1+! @ 23 >IF SCROLL THEN DROP ; : EMIT ( c --) COL DUP 1+! @ [ C/L @] >IF CR THEN DROP \ needs DROP for >IF VPOS VC! -; \ tail call : SPACE ( -- ) BL EMIT -; \ tail call : TYPE ( addr len --) 1- FOR COUNT EMIT NEXT DROP ; : SPACES ( n -- ) 1- FOR SPACE NEXT ; \ FAST type, no scrolling \ : VTYPE ( addr len --) DUP>R VPOS SWAP VWRITE R> [ COL +!] ; [CC] HEX TARGET : SCREEN ( n -- ) 7 VWTR -; : OUTPUT ( n -- ) 2 VWTR -; : TEXT ( -- ) 0F0 DUP 1 VWTR [ 83D4 C!] 0 OUTPUT 17 SCREEN 28 [ C/L !] 3C0 [ C/SCR !] 2 [ VMODE !] \ 2=ID for 40 column "TEXT" mode ; \ 771 bytes with VDPLIB \ ================================= \ *COMPILER DIRECTIVE to set MEM variable MUST be used at end of program COMPILER ALSO TARGETS H: CONCUR. ( -- ) THERE MEM T! ;H \ set program mem variable to compiler \ ================================= Here is the demo program that runs in the video. This version is used for interactive testing. It uses the same workspace as the HOST Forth and returns to the Forth console with NEXT, word. Spoiler \ MFORTH DEMO SCREENIO COMPILER \ preamble to set up target image NEW. HEX 2000 ORIGIN. \ load libraries here TARGET NEEDS TYPE FROM DSK2.SCREENIO CREATE A$ S" This is line 1" S, CREATE B$ S" 2nd line of text" S, [CC] DECIMAL [TC] PROG: MAIN TEXT \ init the screen variables PAGE 100 FOR A$ COUNT TYPE CR B$ COUNT TYPE CR CR NEXT NEXT, END. CONCUR. This version creates a stand alone binary program that you load and run with E/A option 5. Notice is puts the workspace and both stacks in scratchpad RAM. Interrupts are disabled. Inline assembler is available by using the [ ] operators. Spoiler \ MFORTH DEMO SCREENIO COMPILER \ preamble to set up target image NEW. HEX 2000 ORIGIN. \ load libraries here TARGET NEEDS TYPE FROM DSK2.SCREENIO CREATE A$ S" This is line 1" S, CREATE B$ S" 2nd line of text" S, [CC] HEX [TC] PROG: MAIN [ 0 LIMI, 8300 WORKSPACE 83BE RSTACK 83FE DSTACK DECIMAL ] \ turn on the compiler TEXT \ init the screen variables PAGE 100 FOR A$ COUNT TYPE CR B$ COUNT TYPE CR CR NEXT [ 0000 @@ BLWP, ] ( TI99 Title page ) END. CONCUR. COMPILER SAVE DSK2.SCRTEST MACHFORTH-SCRIO.mp4 1 Quote Link to comment Share on other sites More sharing options...
+TheBF Posted November 8, 2022 Author Share Posted November 8, 2022 (edited) While going through some old files I found a file that compiled with GCC for TI-99. It also had the assembler output file. Since I am still refining this @!#$! compiler I thought I would see what the C program would look like in MachForth. There is a lot more detail required in MachForth because it is lower level than C but by factoring things into words gets you to a higher level quickly. C version #define VDP_READ_DATA_REG (*(volatile char*)0x8800) #define VDP_WRITE_DATA_REG (*(volatile char*)0x8C00) #define VDP_ADDRESS_REG (*(volatile char*)0x8C02) #define VDP_READ_FLAG 0x00 #define VDP_WRITE_FLAG 0x40 #define VDP_REG_FLAG 0x80 static void vdp_copy_from_sys(int index, char* src, int size) { volatile char* end = src + size; VDP_ADDRESS_REG = index | VDP_WRITE_FLAG; VDP_ADDRESS_REG = (char)(index >> ; while(src != end) VDP_WRITE_DATA_REG = *src++; } void main() { // 12345678901234 vdp_copy_from_sys(0, "HELLO WORLD!",12); vdp_copy_from_sys(32, "THIS IS LINE 2", 14); while(1); } And a working MachForth version that compiles to 238 bytes. Notice how instead of using more data args in the write to screen function, we have factored them out into separate words. This is the Forth appropriate way to code, so things are re-usable. The calling overhead is pretty low, since there is no stack frame required but nested sub-routines eat up a lot of space on 9900. (8 bytes per sub-routine def + 4 bytes to call it) (By contrast Chuck's CPU designs used one bit in an address to indicate it was a sub-routine call) At the moment I don't have a way to compile strings inline. I either need a DATA segment or I need to compile code to jump past data in the code, which seems like a waste. \ MachForth Translation COMPILER HEX A000 ORIGIN. NEW. 8800 CONSTANT vdp_read_data_reg 8C00 CONSTANT vdp_write_data_reg 8C02 CONSTANT vdp_address_reg 0000 CONSTANT vdp_read_flag 4000 CONSTANT vdp_write_flag 8000 CONSTANT vdp_reg_flag TARGET : AT ( VDP_addr -- ) vdp_write_flag OR DUP vdp_address_reg C! >< vdp_address_reg C! ; : EMIT ( char -- ) vdp_write_data_reg C! ; : TYPE ( addr size -- ) FOR COUNT EMIT NEXT DROP ; CREATE A$ S" HELLO WORLD!" S, CREATE B$ S" THIS IS LINE 2" S, PROG: MAIN \ preamble is explicitly stated [ 0 LIMI, 8300 WORKSPACE 83BE RSTACK 83FE DSTACK DECIMAL ] A$ COUNT 0 AT TYPE B$ COUNT 32 AT TYPE BEGIN AGAIN END. SAVE DSK2.C2FORTH \ 238 BYTES And if I use the new symbolic addressing syntax the program is only 208 bytes. Spoiler \ MachForth Translation using explicit symbolic addressing COMPILER HEX A000 ORIGIN. NEW. 8800 CONSTANT vdp_read_data_reg 8C00 CONSTANT vdp_write_data_reg 8C02 CONSTANT vdp_address_reg 0000 CONSTANT vdp_read_flag 4000 CONSTANT vdp_write_flag 8000 CONSTANT vdp_reg_flag TARGET : AT ( VDP_addr -- ) [ vdp_write_flag OR] DUP [ vdp_address_reg C!] >< [ vdp_address_reg C!] ; : EMIT ( char -- ) [ vdp_write_data_reg C!] ; : TYPE ( addr size -- ) FOR COUNT EMIT NEXT DROP ; CREATE A$ S" HELLO WORLD!" S, CREATE B$ S" THIS IS LINE 2" S, PROG: MAIN \ preamble is explicitly stated [ 0 LIMI, 8300 WORKSPACE 83BE RSTACK 83FE DSTACK DECIMAL ] A$ COUNT 0 AT TYPE B$ COUNT 32 AT TYPE BEGIN AGAIN END. Edited November 8, 2022 by TheBF typo 2 Quote Link to comment Share on other sites More sharing options...
+TheBF Posted November 15, 2022 Author Share Posted November 15, 2022 "And still, I am learning" Michelangelo Smarter Fetch and Store So after inventing the symbolic syntax for variables I realized that I had been down this road before when studying Tom Almy's native code compiler. Tom used something he called a "literal stack". The literal stack held literal numbers and addresses and the compiler looked at that stack before compiling code. If there is something on the literal stack the compiler compiles better code instead of using the top-of-stack as a parameter. I had memories of it being complicated and without seeing an example it still seems like it won't work. Now that the compiler is stable(er), I revisited this idea, and it is dead simple if I just use the data stack for variable addresses. Here is all it takes to make the compiler do the "right" thing with variables when we used @ ! C@ C!. I will extend the idea wherever it fits. This moves the project away from Chuck Moore's Machine Forth, but for mere mortals I think this is the way to go. So far it seems to compile the correct code. This works because I have a separate stack for control structures, so the DATA stack is clean when compiling. I could make it more robust I suppose with a stack for variable addresses, but it takes extra space so for now this it. EDIT: Duh! You have to make room on the stack to fetch something if you use symbolic addressing. Added TOS PUSH, to @ and C@ : @ ( addr -- n) DEPTH ( address on the DATA stack?) IF TOS PUSH, @@ TOS MOV, ( compile symbolic code ) ELSE *TOS TOS MOV, ( compile indirect addressing ) THEN ; : C@ ( addr -- c) DEPTH IF TOS PUSH, @@ TOS MOVB, ELSE *TOS TOS MOVB, THEN TOS 8 SRL, ; : ! ( n addr --) DEPTH IF TOS SWAP @@ MOV, ELSE *SP+ *TOS MOV, THEN TOS POP, ; \ refill the TOS cache register : +! ( n addr -- ) DEPTH IF TOS SWAP @@ ADD, ELSE *SP+ *TOS ADD, THEN TOS POP, ; : C! ( c addr -- ) DEPTH IF TOS SWPB, TOS SWAP @@ MOVB, ELSE NOS SWPB, *SP+ *TOS MOVB, THEN TOS POP, ; \ inc/dec addr : 1+! ( addr -- ) DEPTH IF @@ INC, ELSE *TOS INC, TOS POP, THEN ; \ inc/dec addr : 1-! ( addr -- ) DEPTH IF @@ DEC, ELSE *TOS DEC, TOS POP, THEN ; 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.