Jump to content
IGNORED

Machine Forth OMG


TheBF

Recommended Posts

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?

Link to comment
Share on other sites

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 by apersson850
  • Like 1
Link to comment
Share on other sites

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 by TheBF
fixed comment
  • Like 2
Link to comment
Share on other sites

13 hours ago, Willsy said:

IL is a 61131 language. Worth checking out, as its stack-based :thumbsup:

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.

  • Like 3
Link to comment
Share on other sites

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

 

 

  • Like 1
Link to comment
Share on other sites

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.

 

 

 

  • Like 3
Link to comment
Share on other sites

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) 

 

 

 

  • Like 2
Link to comment
Share on other sites

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

 

 

  • Like 4
Link to comment
Share on other sites

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.

 

 

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

  • 4 months later...

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

 

 

 

  • Like 2
Link to comment
Share on other sites

  • 3 months later...

I finally got back to tackling a few big bugs with MachForth.

  1. NEW.   directive was not resetting the MFORTH name space back to the initial compiler word set.
    1. (kind of important to not destroy the compiler when you reset) 
  2. 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. 
  3. 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 by TheBF
fixed typos
  • Like 4
Link to comment
Share on other sites

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 by TheBF
fixed typos
  • Like 1
  • Haha 1
Link to comment
Share on other sites

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!

  • Like 1
  • Haha 1
Link to comment
Share on other sites

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)

 

 

  • Like 2
Link to comment
Share on other sites

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

 

  • Like 2
Link to comment
Share on other sites

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 ;

 

 

 

  • Like 1
Link to comment
Share on other sites

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

 

 

  • Like 1
Link to comment
Share on other sites

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 by TheBF
typo
  • Like 2
Link to comment
Share on other sites

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

 

 

 

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