+TheBF Posted November 28, 2020 Author Share Posted November 28, 2020 42 minutes ago, FarmerPotato said: This is eye-popping amazing! I had never thought outside the threaded-indirect box. Do I understand correctly that the output is a standalone machine language program? So if you compile in necessary utilities, it's all there? No runtime needed? What happens in high-level words? When the compiler finds one, does it make a BL call? (maybe that's what the code field does? compiles a BL?) If I write a word that contains only machine instructions, does it become a subroutine or does it create inline code? Lol! I don't often get this reaction. Do I understand correctly that the output is a standalone machine language program? YES So if you compile in necessary utilities, it's all there? No runtime needed? No runtime needed (unless make routines for I/O etc.) What happens in high-level words? When the compiler finds one, does it make a BL call? (maybe that's what the code field does? compiles a BL?) There is no "code field" per-se. Yes all hi level words are just 9900 sub-routines. The magic is here. : RET, ( -- ) R11 RPOP, RT, ; \ nestable return psuedo-instruction \ machine Forth colon/semi-colon (creates nestable sub-routines) : M: \ COMPILE TIME CREATE !CSP THERE , \ remember the program address in the HOST R11 RPUSH, \ compile "enter sub-routine" code \ RUN TIME (when we invoke the word) DOES> @ RELOCATE \ @ THERE from this sub-routine S" @@ BL," EVALUATE ; \ compile BL to call the routine : ;M RET, ?CSP ; \ compile exit sub-routine code If I write a word that contains only machine instructions, does it become a subroutine or does it create inline code? At the moment there are 34 Forth "instructions" that compile inline machine code. I might add more to take advantage of the 9900 instruction set. But anything that takes more than 3..4 instructions, I will make into a sub-routine. The CALL on 9900 takes 4 bytes SO it gets big quickly. If you want to make a sub-routine you just do: : MYSUB-ROUTINE DUP + ; Looks familiar huh? That would compile to : (R4 is the top of stack cache register) RP DECT, \ : R11 *RP MOV, MY-SUBROUTINE @@ BL, SP DECT, R4 *SP MOV, \ DUP *SP+ R4 ADD, \ + *RP+ R11 MOV, \ ; *R11 B, Quote Link to comment Share on other sites More sharing options...
+TheBF Posted November 28, 2020 Author Share Posted November 28, 2020 49 minutes ago, FarmerPotato said: : SETVA ( address -- address ) TOS SWPB, TOS 8C02 MOVB, TOS SWPB, TOS 8C02 MOVB, ; : VSBW ( byte address -- ) TOS 4000 ORI, SETVA DROP TOS 8C00 MOVB, DROP ; I might want to invoke SETVA as a subroutine call sometimes, but in VSBW I want to generate those 4 instructions inline, for fastest results. (Did I get the stack operations right?) Yes that would work because you can mix CODE and hi-level code together. You would put the first DROP typically in the SETVA sub-routine. It's an example where having the recurring data in the TOS register is simpler and smaller than stack code. 1 Quote Link to comment Share on other sites More sharing options...
+TheBF Posted November 28, 2020 Author Share Posted November 28, 2020 4 hours ago, GDMike said: Mr. Zimmer once said.. "Well, I describe myself as a C programmer, who is really a Forth programmer". I suppose he's right as far as making a living in programming revenue. This is a great article. Thanks!!! Quote Link to comment Share on other sites More sharing options...
+TheBF Posted November 28, 2020 Author Share Posted November 28, 2020 I found an article on an machine Forth for ARM by Jet Thomas. He realized that because ARM uses a register to store the sub-routine return address (like TMS9900) this meant the register was actually like a cache register for the TOP-OF-RETURN-STACK. With that idea I implemented another Charles Moore creation for his CPUs which is the FOR/NEXT loop... Forth style. Chuck now considers DO/LOOP to be much too much like FORTRAN. FOR/NEXT is much simpler than DO/LOOP because it works the same way Assembly language programmers make loops. Set a register to the count and decrement the register until it gets to zero. It you want your own loop counter put it on the data stack and increment it as needed with 1+ or 2+ or any increment you want. Or if the stack is really complicated make a variable and increment the variable. If you really wanted the loop index down-counter you could also make a code word like: CODE I ( -- n) DUP R11 TOS MOV, ; Here is all it takes to make this useful structure in Machine Forth using R11 and the loop index: \ FOR/NEXT loop Decrements index in R11 : FOR ( n -- ) R11 RPUSH, TOS R11 MOV, BEGIN ; : NEXT ( -- ) R11 DEC, THERE 0 JOC, <BACK R11 RPOP, ; Here is the test program: \ MFORTH DEMO #6: FOR/NEXT loop test NEEDS TARGET: FROM DSK2.MFORTH COMPILER: NEW. HEX 2000 ORIGIN. TARGET: \ Running from inside Forth we can share the same workspace :) PROG: DEMOFOR FFFF # FOR NEXT NEXT, \ return to Camel99 Forth END. Here is the code for the empty loop: 2000 0460 b @>2004 \ jump into program 2004 2004 0646 dect R6 \ index loaded to TOS 2006 C584 mov R4,*R6 2008 0204 li R4,>ffff 200C 0647 dect R7 \ FOR 200E C5CB mov R11,*R7 \ push R11 onto Rstack 2010 C2C4 mov R4,R11 \ load index into R11 2012 060B dec R11 \ NEXT decrements R11 2014 18FE joc >2012 \ test transition to zero 2016 C2F7 mov *R7+,R11 \ POP rstack back to R11 2018 045A b *R10 \ Jump to Forth inner interpreter The video shows this test program running the empty FOR/NEXT loop. The equivalent empty DO/LOOP in CAMEL99 Forth runs in 3.21 seconds. Machine Forth is about 5X faster in this case. MFORTH_FOR_NEXT.mp4 2 Quote Link to comment Share on other sites More sharing options...
+TheBF Posted December 1, 2020 Author Share Posted December 1, 2020 I have changed my mind on using R11 as the index counter. It adds complication that does not give much benefit. The new FOR/NEXT loop will use the return stack. The speed difference is minimal and the code to make it is just: : FOR ( n -- ) >R BEGIN ; : NEXT ( -- ) *RP DEC, THERE 0 JOC, <BACK RDROP ; I add the instruction RDROP because it is again only one 9900 instruction so a saving from using R> DROP which is two. R@ becomes the loop index which I can rename to 'I' and to get nested FOR/NEXT loops indices I can use: : J DUP 2 (RP) TOS MOV, ; : K DUP 4 (RP) TOS MOV, ; 2 Quote Link to comment Share on other sites More sharing options...
+TheBF Posted December 2, 2020 Author Share Posted December 2, 2020 "Use the Forth Luke" In my attempt to make a good machine Forth for 9900 I have come to a conclusion. There are some things that the 9900 just needs to do differently. Where Charles Moore used an "address" register I believe it was because he did not have a memory to memory architecture device like ours. I will still try the A register idea for auto incrementing addresses for loops and such but 9900 has other magic. So in my compiler I have decided that every Forth word that shows a stack diagram that has "addr" in it, will not convert that address to a literal value that is loaded into the address register but rather will use "symbolic" or as I prefer to call it "direct" addressing. This is the super-power of the 9900 CPU so machine Forth is mandated to use it. So the code for @ (fetch) becomes: : @ ( addr -- n) DUP @@ TOS MOV, ; Make a new TOS register with DUP and load the register from memory. Likewise ! (store) becomes: : ! ( n variable -- ) TOS SWAP @@ MOV, DROP ; And... I am even wondering if I should automate the DROP operation or leave it up to the programmer. (?) I will leave it in for now for compatibility. (In a smarter compiler we could make DUP smart so that any word that DUPs the stack looks back to see if there was a DROP previously compiled and remove them both. I might go there once this thing is "debuggered". Once we embrace the 9900 super power it becomes clear we need a Forth operator to do memory to memory movement. In IForth by Marcel Hendrix he has the word ":=" borrowed from Pascal, Modula and Nicholas Wirth's other languages. This is so simple in 9900 Assembler and is over 2X faster than Fetch and store operations used in standard Forth. The compiler code is below: : := ( var1 var2 --) >R @@ R> @@ MOV, ; \ assign var1 to var2 The rules are simple. There is no safety net. If you feed the wrong thing to an operator that expects an address parameter it will fail. Machine Forth is a just an assembler with Forthy syntax. To show you how important this is for the 9900 consider this Forth code: VARIABLE A VARIABLE B A @ B ! \ VERSUS A B := Here is the compiler output: 2008 0646 dect R6 200A C584 mov R4,*R6 \ DUP 200C C120 mov @>2004,R4 \ A @ 2010 C804 mov R4,@>2006 \ B ! 2014 C136 mov *R6+,R4 \ DROP \ 1 instruction verus 5 2016 C820 mov @>2004,@>2006 \ A B := Classic99 says: 130 clocks, 14 bytes versus 50 clocks, 6 bytes. 4 Quote Link to comment Share on other sites More sharing options...
+TheBF Posted December 3, 2020 Author Share Posted December 3, 2020 The Challenge of "@" (Fetch) in machine Forth Once I had a "working" machine Forth Compiler I started creating library code to take it for a drive. We discussed the power of 9900 direct addressing in the previous post and how powerful it is to reduce program size and even improve speed. Writing real code to use that power exposed a challenge with @ and C@. There are really two different ways these words can work in a Forth system: The compiler has a literal address ready to give to the instruction so we can compile with direct addressing ( MYVAR @@ TOS MOV, ) The address is in the top-of-stack register coming from a previous operation (DUP for example) ( *TOS TOS MOV, ) This requires that you compile different code for each condition. In a smarter compiler there are many ways to work this out but the goal of Machine Forth is to be like a portable Assembler. So what to do? I could mandate that all addresses must be pushed onto the stack before they are used, but then we loose direct addressing and get some code bloat. I took a cue from the 9900 immediate instructions which I had already dealt with by extending the concept of the literal number. In Machine Forth the command to push a literal number onto the stack is '#'. By extension I renamed the 'AI' (add immediate) instruction #+ The ANDI became #AND The ORI became #OR ... and so on. So... the compiler has two versions of @ and C@ and just as in ALC the programmer must use the correct instruction. : #@ ( addr -- n) DUP @@ TOS MOV, ; : @ ( TOSaddr -- n) *TOS TOS MOV, ; : #C@ ( addr -- c) DUP @@ TOS MOV, TOS 8 SRL, ; : C@ ( TOSaddr -- c) *TOS TOS MOVB, TOS 8 SRL, ; Although this is not as pretty as making a smarter compiler it gives us a way to solve the fetch problem as well bring in the 9900 immediate instructions in a consistent manner. It also adheres to Chuck Moore's advice: "The dictionary is a big case statement. Use it" I have not needed it yet in my code examples but I suspect I will have to do the same thing for '!' (store). At least I now have a syntax framework to follow and it's easy to extend this compiler. 3 Quote Link to comment Share on other sites More sharing options...
+Lee Stewart Posted December 3, 2020 Share Posted December 3, 2020 36 minutes ago, TheBF said: So... the compiler has two versions of @ and C@ and just as in ALC the programmer must use the correct instruction. : #@ ( addr -- n) DUP @@ TOS MOV, ; : @ ( TOSaddr -- n) *TOS TOS MOV, ; : #C@ ( addr -- c) DUP @@ TOS MOV, TOS 8 SRL, ; : C@ ( TOSaddr -- c) *TOS TOS MOVB, TOS 8 SRL, ; Would this be any quicker or does it defeat your purpose? : #@ ( addr -- n) TOS OVER @ LI, ; ...lee Quote Link to comment Share on other sites More sharing options...
+TheBF Posted December 3, 2020 Author Share Posted December 3, 2020 3 minutes ago, Lee Stewart said: Would this be any quicker or does it defeat your purpose? : #@ ( addr -- n) TOS OVER @ LI, ; ...lee Interesting. So that would be fetching the value of the variable at compile time if the '@' operator in the definition is the Host Forth version. So I don't think that would give me what I need for variables in the target program, but it does give me a way to do CONSTANT better perhaps. I will put it on the "things to test" stack. Thanks This thing is harder than I believe Chuck thinks it is, at least for mere mortals. Quote Link to comment Share on other sites More sharing options...
+TheBF Posted December 3, 2020 Author Share Posted December 3, 2020 An actual test program in Machine Forth This program compiles to 1,061 bytes. 275 bytes are taken by the VDPLIB file which has healthy set of routines. Variables are just 2 bytes because there are no labels in the final code and constants only emit code when they are used in the code. Things to note: To change radix (HEX DECIMAL) you must talk to the compiler so that must be prefixed by COMPILER: or the [CC] shortform. After that you must switch back to the TARGET: name space so code will be emitted into the target program image. Notice in COUNT how you can slip in an assembler instruction to reference the 2nd item on the stack. I don't like the look of all the # signs but it is the price paid for ultra simplicity in a compiler I suppose. Spoiler \ DEMO #16: Graphics and text mode with printed strings NEEDS MFORTH FROM DSK2.MFORTH COMPILER: NEW. HEX 2000 ORIGIN. INCLUDE DSK2.VDPLIB ( VC@ VC! V@ V! VREAD VWRITE VFILL VWTR ) [CC] DECIMAL TARGET: 32 CONSTANT BL \ space character (blank) VARIABLE C/L VARIABLE C/SCR \ chars per screen VARIABLE VROW VARIABLE VCOL VARIABLE VPG VARIABLE VMODE : (CLS) ( -- ) 0 # C/SCR #@ BL VFILL ; : PAGE ( -- ) (CLS) VROW OFF VCOL OFF ; : AT-XY ( col row --) VROW #! VCOL #! ; : VPOS ( -- Vaddr) \ smaller in ALC DUP VROW @@ R3 MOV, C/L @@ TOS MOV, TOS R3 MPY, VCOL @@ TOS ADD, ; : EMIT ( c --) VPOS VC! VCOL 1+! ; : CR ( -- ) VROW 1+! VCOL OFF ; : TYPE ( addr len --) FOR DUP C@ EMIT 1+ NEXT DROP ; : COUNT ( addr -- addr' len) DUP *SP INC, \ inc. 2nd item on stack. :-) C@ ; : SPACE BL EMIT ; : SPACES ( n -- ) FOR SPACE NEXT ; : CLEAR ( -- ) (CLS) 0 # 23 # AT-XY ; [CC] HEX TARGET: : GRAPHICS 0 # 380 # 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 380 # 10 # 10 # VFILL 17 # 7 # VWTR 20 # C/L #! 300 # C/SCR #! 1 # VMODE #! CLEAR ; : TEXT ( -- ) 0F0 # DUP 83D4 #C! 1 # VWTR 0 # 2 # VWTR \ set VDP screen page VPG OFF 12 # 7 # VWTR \ FG & BG color 28 # C/L #! 3C0 # C/SCR #! 2 # VMODE #! \ 2=ID for 40 column "TEXT" mode PAGE ; : WAIT FFFF # FOR NEXT ; CREATE A$ S" GRAPHICS MODE" S, CREATE B$ S" TEXT MODE" S, PROG: DEMO16 BEGIN GRAPHICS A$ COUNT TYPE WAIT TEXT B$ COUNT TYPE WAIT ?TERMINAL UNTIL NEXT, END. MFORTH DEMO16.mp4 1 Quote Link to comment Share on other sites More sharing options...
+FarmerPotato Posted December 4, 2020 Share Posted December 4, 2020 On 12/3/2020 at 7:57 AM, TheBF said: : #@ ( addr -- n) DUP @@ TOS MOV, ; : @ ( TOSaddr -- n) *TOS TOS MOV, ; : #C@ ( addr -- c) DUP @@ TOS MOV, TOS 8 SRL, ; : C@ ( TOSaddr -- c) *TOS TOS MOVB, TOS 8 SRL, ; Pretty sure you meant : C#@ ( addr -- c ) DUP @@ TOS MOVB, TOS 8 SRL, ; since MOV would always fetch an even address. You could have a case test for even/odd constant addresses, depending on whether the CPU was better at MOV or MOVB (16-bit or 8-bit bus resp). I still don't understand @@. I think this compiles a symbolic address. But why the DUP? What's the difference between 8800 # @@ TOS MOV, and 8800 # #@ oh I see, the first one clobbers the run-time stack So is that DUP compile time or run-time? On 12/1/2020 at 9:13 PM, TheBF said: "symbolic" or as I prefer to call it "direct" addressing. I was confused by this later on. I confused direct with register. All the TI manuals say: Ts or Td 00 WS Register 01 WS Register Indirect 10 Symbolic / Indexed when D>0 11 WS Register Indirect auto increment I guess it is clear now. I'm struggling to keep up. Looking up @@ in Wycove Forth was interesting but more confusing. 1 Quote Link to comment Share on other sites More sharing options...
+TheBF Posted December 4, 2020 Author Share Posted December 4, 2020 43 minutes ago, FarmerPotato said: Pretty sure you meant : C#@ ( addr -- c ) DUP @@ TOS MOVB, TOS 8 SRL, ; since MOV would always fetch an even address. You could have a case test for even/odd constant addresses, depending on whether the CPU was better at MOV or MOVB (16-bit or 8-bit bus resp). I still don't understand @@. I think this compiles a symbolic address. But why the DUP? What's the difference between 8800 # @@ TOS MOV, and 8800 # #@ oh I see, the first one clobbers the run-time stack So is that DUP compile time or run-time? Yes @@ is symbolic addressing. Sorry about that. I take issue with the name symbolic. That's an assembler writer's term to me. They aren't symbols they are addresses. If I am "directly" talking to memory then that is "direct addressing" vs "indirect addressing" through an address in a register. But that's just me. Yes that MOV was a mistake. Thank you. Ok so the big difference with Camel Forth is that the top of stack is cached in a register. This was something that became popular in the late 80s in Forth systems. It gets you 8% to 10% speed-up over TOS in memory. But... you have to manage that. To get a new TOS register you have to push the current register onto the stack in memory. I used to write: SP DECT, R4 *SP MOV, Then I took a look at my definition for DUP one day and it was ... CODE DUP SP DECT, R4 *SP MOV, NEXT, ENDCODE Same thing. So to @ or C@ a number I want it to end up in the TOS register. So the first DUP is just copying the TOS register onto the stack in memory so you can use the TOS register for the new number you are fetching. You can see the efficiency of the TOS in a register in all the instructions that process an input and return an output like + for example: CODE + *SP+ TOS A, And at the end of a definition with this system you have to refill the TOS register in words that leave no item on the stack. ( Like ! or C! That code looks just like DROP. *SP+ TOS MOV, Quote Link to comment Share on other sites More sharing options...
+Lee Stewart Posted December 5, 2020 Share Posted December 5, 2020 5 hours ago, TheBF said: Ok so the big difference with Camel Forth is that the top of stack is cached in a register. This was something that became popular in the late 80s in Forth systems. It gets you 8% to 10% speed-up over TOS in memory. I have thought about how I might incorporate TOS into fbForth (3.0?). I cannot afford to lose any more registers, so I thought about co-opting the register (R8=U) that points to the base of the User Variable area. It would require extensive changes that might not be possible with the possible added code, but it might be worth a shot because most of the code changes would be changing @n(U) to @n+$UVAR or similar, where n = any particular user variable’s offset from $UVAR, the usual contents of U. The sticking point is that there may be a few(?) times when U is changed (I think) to the default user variable area. If that is not the case, I might be home free. Otherwise, there might be some unacceptable code bloat. We shall see.... ...lee 1 Quote Link to comment Share on other sites More sharing options...
+TheBF Posted December 5, 2020 Author Share Posted December 5, 2020 50 minutes ago, Lee Stewart said: I have thought about how I might incorporate TOS into fbForth (3.0?). I cannot afford to lose any more registers, so I thought about co-opting the register (R8=U) that points to the base of the User Variable area. It would require extensive changes that might not be possible with the possible added code, but it might be worth a shot because most of the code changes would be changing @n(U) to @n+$UVAR or similar, where n = any particular user variable’s offset from $UVAR, the usual contents of U. The sticking point is that there may be a few(?) times when U is changed (I think) to the default user variable area. If that is not the case, I might be home free. Otherwise, there might be some unacceptable code bloat. We shall see.... ...lee As far as I can tell it nets out to about the same amount of code. (I think) But ya all those registers fill up quickly. That's why I hid my User point in the WP pointer but that can be weird when the workspace is at 8300. I can't fill the scratchpad with USER variables so I have to tread lightly... Could you experiment with a version in RAM like TI-Forth first to get a feel for how it will work? Quote Link to comment Share on other sites More sharing options...
+TheBF Posted December 5, 2020 Author Share Posted December 5, 2020 @FarmerPotato What's the difference between 8800 # @@ TOS MOV, and 8800 # #@ I didn't answer this question. This machine Forth can hurt in the head because when you are writing the compiler code it looks like application code but it's not. So # is how you take an integer <n> from the Host computer world (ie Camel99 Forth) and compile that number into the target program as a literal. (ie: TOS <n> LI, ) @@ is indeed symbolic addressing . Since Forth variables are just "symbols that return an address" this is most the efficient way to load or store a variable om 9900. (unless you are in a loop I suppose) MYVAR @@ TOS MOV, \ fetch the contents of myvar into the top of stack register (ie: R4 in Camel99 Forth) I needed a way to tell the compiler here is an address of a variable. It looks like a number but DO NOT put it on the Target program's stack like it was a number, but rather FETCH it's contents and put that into the program's TOS register I used #@ for that purpose. So the rule I am using is # specifies that the program has given me a literal number and I need to put it into the TOS register. #???? (extra stuff after #) will take a number from the program and do what that instruction is. Example: 8800 # 0F00 #AND compiles to: TOS 0F00 ANDI, (corrected: Thanks to eagle eye @Lee Stewart) Therefore #@ takes that address of a variable and works like this MYVAR #@ --> MYVAR @@ TOS MOV, *** but remember before we can use the TOS we must PUSH the current value onto the stack in memory which is just a DUP operation. (See previous post) Whew! 1 Quote Link to comment Share on other sites More sharing options...
+TheBF Posted December 5, 2020 Author Share Posted December 5, 2020 Special note This stuff about #@ vs @ etc. can be fixed if I break from Machine Forth thinking and just make a smarter cross-compiler. I know how to do that but it adds some complexity to the compiler but it will look more like normal Forth. I am leaning to go that way for a better user experience in the (near) future. I just needed to explore this concept first. Quote Link to comment Share on other sites More sharing options...
+TheBF Posted December 5, 2020 Author Share Posted December 5, 2020 VDP Screen Driver for MForth Using the VDPLIB I re-worked the Camel99 screen code to create this. I am really liking the simplicity of the one parameter FOR/NEXT loop. I have a faster version of TYPE that uses the AREG and auto-increment to ripple through the bytes of the string. It messes with R9 however which is Camel Forth's IP register so it does return to Forth politely. I think I need to change my A register to R8. The scroll is dead simple using a 1 line buffer and seems fast enough. I put the buffer in the middle of the code. I need to define HERE and PAD for this Forth to use memory after the application code for such things. But in the meantime here is what the screen code looks like. I mixed ALC and Forth as needed to do the job. Spoiler \ SCREENIO.FTH for MForth BFox Dec 3 2020 HOST: NEEDS VFILL FROM DSK2.VDPLIB [CC] DECIMAL TARGET: 32 CONSTANT BL \ space character (blank) VARIABLE C/L VARIABLE C/SCR \ chars per screen VARIABLE VROW VARIABLE VCOL VARIABLE VPG VARIABLE VMODE : (CLS) ( -- ) 0 # C/SCR #@ BL VFILL ; : PAGE ( -- ) (CLS) VROW OFF VCOL OFF ; : AT-XY ( col row --) VROW #! VCOL #! ; : CLEAR ( -- ) (CLS) 0 # 23 # AT-XY ; \ TI-99 legacy :-) [CC] HEX TARGET: : GRAPHICS 0 # 380 # 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 380 # 10 # 10 # VFILL \ charset colors 17 # 7 # VWTR \ screen color 20 # C/L #! 300 # C/SCR #! 1 # VMODE #! CLEAR ; : TEXT ( -- ) 0F0 # DUP 83D4 #C! 1 # VWTR 0 # 2 # VWTR \ set VDP screen page VPG OFF 12 # 7 # VWTR \ FG & BG color 28 # C/L #! 3C0 # C/SCR #! 2 # VMODE #! \ 2=ID for 40 column "TEXT" mode PAGE ; [CC] DECIMAL TARGET: : VPOS ( -- VDP-cursor-addr) \ smaller in ALC DUP VROW @@ R3 MOV, C/L @@ TOS MOV, TOS R3 MPY, VCOL @@ TOS ADD, ; COMPILER: CREATE SBUFF 42 ALLOT \ 1 LINE scroll buffer TARGET: : SCROLL ( -- ) \ works in Graphics or Text mode VCOL OFF 1 # VROW #! 22 # FOR VPOS SBUFF C/L #@ VREAD VROW 1-! SBUFF VPOS C/L #@ VWRITE VROW 2+! NEXT 0 # 23 # AT-XY \ cursor to last line VPOS C/L #@ BL VFILL \ erase last line ; : CR ( -- ) VCOL OFF VROW 1+! VROW @@ R5 MOV, \ avoid stack keeps it cleaner R5 23 CI, \ 23 is the last VROW on screen -IF SCROLL THEN ; : EMIT ( c --) VPOS VC! VCOL 1+! VCOL @@ C/L @@ CMP, \ compare column & max line length -IF CR THEN ; \ if VCOL > C/L do CR : TYPE ( addr len ) FOR DUP C@ EMIT 1+ NEXT DROP ; : COUNT ( addr -- addr' len) DUP *SP INC, \ inc. 2nd item on stack. :-) C@ ; : SPACE BL EMIT ; : SPACES ( n -- ) FOR SPACE NEXT ; MFORTH-SCROLL.mp4 Quote Link to comment Share on other sites More sharing options...
+Lee Stewart Posted December 5, 2020 Share Posted December 5, 2020 9 hours ago, TheBF said: Example: 8800 # 0F00 #AND compiles to: TOS 0F00 AI, Sorry for the distraction, but should that not be the following? 8800 # 0F00 #AND compiles to: TOS 0F00 ANDI, ...lee Quote Link to comment Share on other sites More sharing options...
+Lee Stewart Posted December 5, 2020 Share Posted December 5, 2020 8 hours ago, TheBF said: : VPOS ( -- VDP-cursor-addr) \ smaller in ALC DUP VROW @@ R3 MOV, C/L @@ TOS MOV, TOS R3 MPY, VCOL @@ TOS ADD, ; At first, I could not see that this line TOS R3 MPY, was correct—seein’ as how you want the result in TOS. But, then, I realized that TOS is an alias for R4—doh! ...lee Quote Link to comment Share on other sites More sharing options...
+TheBF Posted December 5, 2020 Author Share Posted December 5, 2020 1 hour ago, Lee Stewart said: Sorry for the distraction, but should that not be the following? 8800 # 0F00 #AND compiles to: TOS 0F00 ANDI, ...lee Yes. Late nights and going too fast. Quote Link to comment Share on other sites More sharing options...
+TheBF Posted December 5, 2020 Author Share Posted December 5, 2020 25 minutes ago, Lee Stewart said: At first, I could not see that this line TOS R3 MPY, was correct—seein’ as how you want the result in TOS. But, then, I realized that TOS is an alias for R4—doh! ...lee Ya when I was getting started this gave me some grief, but this seem to be the way to do it. Quote Link to comment Share on other sites More sharing options...
+TheBF Posted December 7, 2020 Author Share Posted December 7, 2020 Binary programs with MForth 1.0 I have been beating up MForth with the goal of creating a development system that can generate stand alone binary programs written in either Machine Forth , Forth Assembler or a mix of both. I had to re-work my SAV utility to make it more versatile and that is now working. With that I took one the demo files and commented it up to explain what the different sections of the program do. The spoiler has the program and the video shows everything in action. The final step is to compile MForth all together and save that in EA5 format so that MForth becomes it's own stand-alone application for TI-99. Then I have to write the manual... Spoiler \ DEMO #17: SCREEN SCROLLING NEEDS MFORTH FROM DSK2.MFORTH \ preamble purges memory and sets program Address COMPILER: HEX NEW. 2000 ORIGIN. \ Optional library files (runtime routines) INCLUDE DSK2.VDPLIB INCLUDE DSK2.SCREENIO INCLUDE DSK2.MENU \ return to E/A5 menu screen [CC] HEX \ set number has for Compiler TARGET: \ Set name space to TARGET Program CREATE A$ S" Machine Forth " S, CREATE B$ S" Created with CAMEL99 Forth! " S, : PRINT ( $addr -- ) COUNT TYPE ; \ because we can : MAIN BEGIN \ start a loop A$ PRINT B$ PRINT ?TERMINAL \ test for BREAK key UNTIL \ loop until pressed MENU \ returns to E/A menu ; \ PROG: section for initialization & sets program entry address PROG: DEMO17 8300 WORKSPACE \ set the workspace FF00 DSTACK \ Program's data stack FE00 RSTACK \ Program's return stack TEXT \ 40 column mode MAIN \ run the program END. \ test program size and entry address \ Carnal Knowledge: \ In DSK2.SCREENIO I created a variable 'DP' to mark the end of \ program memory. This allows me to put DATA in the unused memory. \ Below I must patch the DP variable to the compiler's program end address \ called THERE. COMPILER: THERE TARGET: DP HOST: ! \ Start end filename HEX 2000 THERE SAVEBIN DSK2.SCROLLDEMO MFORTH-EA5-PROG.mp4 2 1 Quote Link to comment Share on other sites More sharing options...
GDMike Posted December 7, 2020 Share Posted December 7, 2020 Very good work indeed 1 Quote Link to comment Share on other sites More sharing options...
+TheBF Posted December 11, 2020 Author Share Posted December 11, 2020 The 90/10 rule never breaks. I have been going over this compiler fixing and breaking it all week. I am in the long tail of making it better. It's getting closer but I before I release it I want it to be actually useful and easy to use. I thought I would show what happens when you invoke a DROP/DUP optimizer in this kind of system. Background: This Forth implementation converts the Forth machine into a something like an Accumulator computer because the top of the stack is cached in a register. (TOS) This means that if you want to put something on the stack you must first "push" that register onto the stack or its contents would be lost. This is just a Forth DUP operation. If a function performs some operations and then needs to clean up the stack when it is finished it needs to "refill" the TOS register before it returns. This is just DROP operation. This can lead to a situation where a function cleans up with a DROP and the next function needs the TOS register so does a DUP, even though the previous function was finished using the top-of-stack register. That's a waste of 6 bytes and lots of cycles on a 9900. What if we could detect that situation and not bother doing the DROP/DUP? Here is one way: : ~DUP ( DUP "maybe" ) THERE 2- @ 'DROP' = \ look back 2 bytes. Is it DROP? (>C136) IF -2 TALLOT \ move program counter back 2 bytes \ this will erase DROP ELSE SP DECT, \ no DROP found so we have to DUP TOS *SP MOV, THEN ; And here is the difference in the resulting code: \ POP/PUSH optimizer example with result VARIABLE A VARIABLE B OPTIMIZER OFF : TEST1 DEAD # A #! BEEF # B #! ; OPTIMIZER ON : TEST2 DEAD # A #! BEEF # B #! ; \ Generated code \ optimizer off 2008 0647 dect R7 \ enter sub-routine 200A C5CB mov R11,*R7 \ .......................... 200C 0646 dect R6 \ DUP 200E C584 mov R4,*R6 2010 0204 li R4,>dead \ constant DEAD 2014 C804 mov R4,@>2004 \ A ! 2018 C136 mov *R6+,R4 \ DROP 201A 0646 dect R6 \ DUP 201C C584 mov R4,*R6 201E 0204 li R4,>beef \ constant BEEF 2022 C804 mov R4,@>2006 \ B ! 2026 C136 mov *R6+,R4 \ DROP \ .......................... 28 BYTES 2028 C2F7 mov *R7+,R11 \ return 202A 045B b *R11 \ ________________________________________ \ optimizer on 202C 0647 dect R7 \ enter sub-routine 202E C5CB mov R11,*R7 \ ............................. 2030 0646 dect R6 \ DUP 2032 C584 mov R4,*R6 2034 0204 li R4,>dead \ constant DEAD 2038 C804 mov R4,@>2004 \ A ! 203C 0204 li R4,>beef \ constant BEEF 2040 C804 mov R4,@>2006 \ B ! 2044 C136 mov *R6+,R4 \ DROP \ ............................... 22 BYTES 2046 C2F7 mov *R7+,R11 \ return 2048 045B b *R11 1 Quote Link to comment Share on other sites More sharing options...
+Lee Stewart Posted December 11, 2020 Share Posted December 11, 2020 I forget. How does Camel99Forth know when TOS is empty (invalid)? ...lee 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.