Jump to content
IGNORED

Camel99 Forth Information goes here


TheBF

Recommended Posts

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, 

 

Link to comment
Share on other sites

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.

 

  • Like 1
Link to comment
Share on other sites

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.

 

  • Like 2
Link to comment
Share on other sites

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, ;

 

  • Like 2
Link to comment
Share on other sites

"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.

 

 

  • Like 4
Link to comment
Share on other sites

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:

  1. The compiler has a literal address ready to give to the instruction so we can compile with direct addressing  ( MYVAR @@ TOS MOV, )
  2. 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.

 

  • Like 3
Link to comment
Share on other sites

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

Link to comment
Share on other sites

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. :) 

 

Link to comment
Share on other sites

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.

 

 

  • Like 1
Link to comment
Share on other sites

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.

 

 

  • Like 1
Link to comment
Share on other sites

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,  

 

 

 

 

Link to comment
Share on other sites

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

  • Like 1
Link to comment
Share on other sites

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?

Link to comment
Share on other sites

@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!

 

 

 

  • Like 1
Link to comment
Share on other sites

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.

Link to comment
Share on other sites

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 ;

 

 

Link to comment
Share on other sites

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

Link to comment
Share on other sites

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.

Link to comment
Share on other sites

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

 

 

 

 

 

  • Like 2
  • Thanks 1
Link to comment
Share on other sites

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

 

  • Like 1
Link to comment
Share on other sites

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.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

Loading...
  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...