Jump to content

TheBF

+AtariAge Subscriber
  • Content Count

    2,874
  • Joined

  • Last visited

Posts posted by TheBF


  1. It's a daring project. You might want to consider having programs for your O/S run as byte code rather than machine code, with the ability to call machine code when needed.

    Byte code systems can save a lot of space fro your programs if you build an efficient virtual machine.

    GPL is an example, although not one I would recommend. No sense constraining yourself to 256 bytes of RAM.

     

    Basically you build a virtual machine that is core of everything and it just interprets program bytes very fast.

    Your virtual machine can be register based or stack based or 2 stack based like GPL, but generally is made to be simpler than the underlying machine.

     

    Any language for your O/S then just has to create byte code. P-code systems work this way, but the TI-99 version that interpreted P-code bytes with GPL byte code was a bad decision.

    • Like 1

  2. For my lectures I had to learn the MIPS architecture, and well, I have to admit, it is really impressive. I thought I could never love an architecture the way I did with the TMS9900. OK, the MIPS is a RISC 32 bit system, pretty different to what we know. But 32 registers ... operations with three registers ... fixed 32 bit command width ... no additional arguments ...

     

    Oh yes there are some beautiful instructions sets and architectures out there. MIPs is among them.

    Sun Sparc also comes to mind with a circular set of registers on chip.

     

    Forth guys also love the RTX2000 designed by Chuck Moore that performed more that 1 instruction per clock cycle, did sub-routines calls in 1 instruction and performed sub-routine returns for in 0 clock cycles. Honest. I believe the chip was specced at 12 MIPs with a 10 MHz clock. Pretty slick.

     

    You can see something similar in Jim Bowmans FPGA J1 processor written in 200 lines of Verilog code.

     

    B


  3. This would be an excellent application for robotics. For example, if a robot is executing a move forward 5 units, it is completely blind to its environment until the move command completes. With multitasking, it could still scan its sensor array while still executing the move command.

    I would love to hear from Willsy and Lee about this... Well done!

     

    Yup. That's the idea. The one problem with the current implementation is KSCAN. That darn thing takes about .7 milliseconds to run.

    I am told there are debouncing delays in the code and it they were implemented for a coopertative tasker, they would let other tasks work while waiting.

    So I found some faster "check if any key is pressed" code that I will use to speed up my KEY() function.


  4. The 9900 is cool, but every time you want to even increment a register, the CPU has to load from RAM, then perform the increment, and write the value back to RAM. That makes it a bit slower than a machine with internal registers.

    The lack of a conventional stack pointer also makes some things a bit different. To maintain a stack you have to manage the stack with a register manually. No auto increment or decrement.

    If it had a cache of the register contents, it could skip the load and write the value back to RAM when the memory bus isn't busy or you change the register file pointer.

     

     

    Ya for sure the 9900 has lots of warts. It's not just slow, it's glacial. The little MSP430 addresses most of these concerns from what I can see.

    In a Camel99 Forth I manage 2 stacks and to prevent my stupid mistakes, I just made some macros called PUSH, POP, and RPUSH, RPOP, for the return stack. In the case of popping a stack the 9900 does it with 1 instruction so that's pretty good. For pushing it takes 2 instructions.

     

    B


  5. Ok you got my attention... I'll be following this topic from here on out.

     

    LOL. Ya this to me is one of the most fun things about little Forth systems. Somehow it got lost with some of the public domain systems that came out like the Forth Interest Group (FIG) . It might have been to avoid patent infringement or something like that.

     

    But ya you can see quickly how some game stuff gets a h_ll of a lot simpler if you can give it to a little daemon and forget about it.

     

    Since you are interested here is the code with a lot of comments. I am happy to answer questions... after I get some sleep.

     

     

     

    \ TASKS99.HSF for CAMEL99                               06JAN2017 Brian Fox
    
    \ May152017  RE-wrote for use with new Kernel that includes USER variables
    
    [undefined] XASSEMBLER [IF] ."  **This is for XASM99 cross compiler"
                                cr ." Compile halted."  ABORT [THEN]
    
    \ This multi-tasker takes advantage of the unique TMS9900
    \ memory to memory architecure to create a 20uS task switcher.
    
    \ WP in the 9900 CPU points to the current WORKSPACE which is normally
    \ just the registers.  We extend the concept to include a set of
    \ 15 USER VARIABLES and space for both stacks right above the registers.
    
    \ *Therefore the WP becomes the USER POINTER (UP) of a conventional Forth multi-tasker.
    
    \ Using WP to point to the USER area also lets us use the Workspace register
    \ architecture futher. We can use registers 13,14 and 15 to link to another
    \ workspace and use the RTWP instuction to change tasks in 1 instruction!
    \ A very neat trick.
    \
    \ ALSO, the registers become user variables 0..15 of each task
    
    \        ************* WARNING ****************
    \ BLWP/RTWP R13 and R14 have been stolen by this MULTI-TASKER.
    \ If you want to write code words that use BLWP/RTWP you must
    \ save the contents of R13 and R14 before using BLWP
    
    \ The simplest way in this Forth is to use the return stack:
    \  R13 RPUSH,
    \  R14 RPUSH,
    \  WKSPX BLWP,       call your new workspace vector
    \   R13 RPOP,
    \   R14 RPOP,
    
    \ =======================================================================
    \ CAMEL99 MULTI-TASKING USER AREA
    \ -----------------------------------------------------------------------
    \   0 USER R0   LOCAL general purpose register     ( workspace begins)
    \   1 USER R1   LOCAL general purpose register
    \   2 USER R2   LOCAL general purpose register
    \   3 USER R3   LOCAL general purpose register
    \   4 USER R4   LOCAL Top of stack cache
    \   5 USER R5   LOCAL overflow for mult. & div.,       // general purpose register (used by NEXT)
    \   6 USER R6   LOCAL parameter stack pointer ('SP')
    \   7 USER R7   LOCAL return stack pointer    ('RP')
    \   8 USER R8   LOCAL Forth working register  ('W')    // general purpose register in code words
    \   9 USER R9   LOCAL Forth interpreter pointer ('IP)
    \  10 USER R10  LOCAL Forth's "NEXT" routine cached in R10
    \  11 USER R11  LOCAL 9900 sub-routine return register // general purpose register in code words
    \  12 USER R12  LOCAL 9900 CRU register                // general purpose register in code words
    \  13 USER R13  LOCAL task link
    \  14 USER R14  LOCAL Program counter: ALWAYS runs TSTAT routine
    \  15 USER R15  LOCAL Status Register
    \ ------------------------------------------------------------------------
    \  16 USER TFLAG    LOCAL task's awake/asleep flag
    \  17 USER JOB      contains XT of Forth word that runs in this task
    \  19 USER VAR3
    \  21 USER VAR4
    \  22 USER VAR5
    \  23 USER VAR6
    \  24 USER VAR7
    \  25 USER VAR8
    \  26 USER VAR9
    \  27 USER VAR10
    \  28 USER VAR11
    \  29 USER VAR12
    \  30 USER VAR13
    \ -----------------------------------------------------------------------
    \   TASK Parameter stack base address 20 cells (grows downwards)
    \   TASK Return stack base address    20 cells (grows downwards)
    \ =======================================================================
    
    \ Each task has a Process ID (PID)
    \ In this system we use the workspace address as the PID
    
    CROSS-ASSEMBLING
     CODE: MYSELF ( -- PID)         \  return my "Process ID"
                TOS PUSH,
                TOS STWP,           \ fetch the cpu WP register to Forth TOS
                NEXT,
                END-CODE
    
    8300 CONSTANT: USER0            \ user0 is the main Forth task workspace
    
    [CC] DECIMAL
    
    [CC] 15 cells         \ calc. size of task memory block (168 bytes)
         28 cells +
         20 cells +
         20 cells +
         2+               \ 1 cell extra for safety
    [TC] CONSTANT: USIZE
    
    
    TARGET-COMPILING
    \ define CPU registers as user variables
      12 user: 'SP             \ the task's Forth SP register
      14 user: 'RP             \ the task's Forth RP register
      18 user: 'IP             \ the task's Forth IP register
    
    \ these registers are used by RTWP to change context
      26 user: TLINK           \ R13 = linked task wksp
      28 user: TPC             \ R14 = linked task program counter
      30 user: TST             \ R15 = linked task status register
    
    
    \ T A S K   S W I T C H E R
    \ ========================================================================
    \ EXPLANATION OF THE MULTI-TASKER FOR TMS9900
    
    \ Forth multi-taskers create a word, YIELD, that switches from one task "context"
    \ to the next "context".  TMS9900 has a fantastic method. The Workspace.
    \ CAMEL99 initializes the workspace of each task as if it had been called 
    \ by BLWP. With at the workspaces pointing in a circle we can use the RTWP 
    \ instruction to hop from one to next to the next.
    
    \ But TMS9900 created a problem. The RTWP instruction will change context
    \ immediately given an address and program counter in R13 and R14.
    \ This is different than conventional round robin where YIELD remains in a
    \ loop testing each task in the linked list, only leaving the loop when a
    \ task is awake. (tflag<>0)
    
    \ SOLUTION : *Divide YIELD into 2 parts*
    \ Part1 : YIELD
    \         Do the Forth style context switch at appropriate code boundaries.
    \         In this case it's just one instruction. RTWP.
    
    \ Part2 : TSTAT
    \         Load R14 of each task with the address of the rest of YIELD (TSTAT)
    \         TSTAT will run after the RTWP instruction hops to the new workspace.
    \         TSTAT tests it's own TLAG variable to see if it's awake
    \         If the task is asleep TSTAT jumps back to YIELD otherwise it's runs NEXT
    \         which runs the Forth system for the awake task we just entered.
    \
    \ *Addressing the workspace registers and user variables with WP uses index# x 2
    \ example:  R2 is accessed with 4 (R1) ...
    CODE: YIELD  ( -- )
                  BEGIN,                 \ CURRENT TASK:
                     RTWP,               \ one instruction switches context        14
    \ -----------------------------------------------------------------------------------------
    l: _TSTAT        R1  STWP,           \ NEXT TASK: store NEW workspace in R1     8
                     32 (R1) R0 MOV,     \ Read local TFLAG to see if I am awake   28
                  NE UNTIL,              \ loop thru tasks until TFLAG is <> 0     10
                  NEXT,                  \ if task is awake, run next            \ 60 *.333 = 20uS
                  END-CODE
    
    \ convert labels into Forth constants for the addresses of the code
    [CC] HEX
    NEXT2 [TC] constant: NEXT2             \ convert EQU to Forth constant
    
    [CC]
    T' YIELD 2+ [TC] constant: 'YIELD      \ *NOTE* we need the code address, not the code field addr.
    
    _TSTAT    constant: 'TSTAT             \ convert code label to Forth constant
    
    [CC] DECIMAL
    TARGET-COMPILING
    \ PID = process ID.  It is the address of the tasks' user area memory block
    : LOCAL    ( PID uvar -- addr' ) MYSELF - + ;     \ usage:  TASK1 'SP LOCAL @
    : SLEEP    ( PID -- )  0 SWAP TFLAG LOCAL ! ;     \ put PID to sleep
    : WAKE     ( PID -- ) -1 SWAP TFLAG LOCAL ! ;     \ wake up PID
    
    \ turn multi-tasking on or off by changing the CODE address in PAUSE
    : SINGLE     NEXT2  T['] PAUSE ! ;   \ disable multi-tasking
    : MULTI     'YIELD  T['] PAUSE ! ;   \ enable multi-tasking
    
    ( *** YOU  M U S T  use INIT-MULTI before multi-tasking ***)
    : INIT-MULTI ( -- )
                    MYSELF tlink !              \ Set my 'link to my own WKSP
                    'TSTAT TPC !                \ set my task PC (R14) to run TSTAT
                    MYSELF WAKE   ;             \ mark myself awake
    
    : FORK ( PID -- )                           \ needs 168 bytes
           >R                                   \ copy taskaddr
           [email protected] USIZE 0 FILL                      \ erase user area
           USER0 [email protected] 60 CMOVE                    \ copy USER0 regs & vars to taskaddr
                                                \ -this sets R14 to TSTAT routine (IF init-multi was run)
                                                \  and it sets R10 to NEXT2
    
           [email protected] 100 +  [email protected] 'SP LOCAL !             \ set Rstack pointer to this user area
           [email protected] 140 +  [email protected] 'RP LOCAL !             \ set Pstack pointer to this user area
    
    \ add this task to round-robin list of task s
           tlink @                 ( -- link)   \ get the current link round-robin link
           [email protected] tlink !                           \ replace it with addr of new task
           [email protected] tlink LOCAL !                     \ store the copy from above into new task's space
    
           R> SLEEP  ;                          \ mark this new task as asleep
    
    : ASSIGN ( XT PID -- )
               dup  JOB local      ( -- xt PID addr )  \ get the address of JOB for task PID
               over 'IP local !    \ store addr of JOB in the PID's instruction pointer
               2dup JOB local ! ;  \ store the XT in the PID's JOB user var.
    
    \ : RESTART  ( PID) 2DUP JOB LOCAL SWAP 'IP LOCAL !  WAKE ;
    
    TARGET-COMPILING
    
    [CC] HEX [TC]
    
    
    

     

     


  6. I finally got around to getting my TI-99 multi-tasker working pretty much the way I wanted.

     

    Traditionally commercial Forth systems were multi-tasking multi-user systems. I am told Forth Inc. could strap 12 terminals to an IBM PC and have good response for the users.

     

    So why is the TMS9900 so cool? because it can create a new set of registers for itself anywhere in memory and change to that set of registers in 1 instruction!

     

    For those who have never thought about it a conventional multi-tasking systems usea a special program called a scheduler that decides which program is going run and how long they get to do something. At some point while program A is running, the schedule program interrupts program A and gives program B a turn and so on with all the programs that are in the "schedule". So with only 1 CPU, it's really an illusion of multiple programs running at the same time.

     

    The Chuck Moore's Forth multi-tasker was built to be very lightweight and has not got a separate program to schedule which task is going to run and when. (It's heresy, I know)

    Instead a new task gets a turn EVERY time an input or output occurs. So after outputting a character to the printer another task gets a turn. If that task reads a key stroke, it releases control to the next task and so on...

     

    This works well because most I/O takes tons of time so the CPU might as well go do something else.

     

    So to make this kind of multi-tasker on the TMS9900 you can do a switch from one program to the next program in only four, yes that's right 4 instructions. This is unheard of.

     

    Below is the code for the word YIELD, which changes control to the next task, written in a Forth Assembler language.

     

    I did something with this that might be unique. I use the RTWP, instruction to jump to the next task in a list of tasks.

    I can do that because I manually initialize all the registers in each task's workspace as if each task was already called once by BLWP.

     

    It makes the actual switch to a new program 1 instruction.

    Then the only thing we do is check a variable that is right after the workspace to see if the task is awake or asleep.

    If it's asleep we just jump to the next workspace and so on.

    I love this processor!

    CODE: YIELD  ( -- )
                  BEGIN,             \ CURRENT TASK:
                     RTWP,           \ one instruction switches context        14
    l: _TSTAT        R1  STWP,       \ NEXT TASK: store NEW workspace in R1     8
                     32 (R1) R0 MOV, \ Read local TFLAG to see if I am awake   28
                  NE UNTIL,          \ loop thru tasks until TFLAG is <> 0     10
                  NEXT,              \ if task is awake, run next            \ 60 *.333 = 20uS
                  END-CODE
    

    So when all is said and done I have a set of words that lets me create tasks and run them, stop them or assign them new programs almost like Unix system, but much tinier.

     

    Here is the DEMO program that I tested it with. I will get a Video up here shortly and the multi-tasking kernel up on git hub.

     

    This can be ported to FBForth or Turbo Forth with just a little assembler code but mostly high level Forth.

    \ CAMEL99 Forth Multi-tasking Demo
    \ paste into system with mtask99.hsf installed
    INIT-MULTI
    
    CREATE TASK1   USIZE ALLOT
    CREATE TASK2   USIZE ALLOT
    CREATE TASK3   USIZE ALLOT
    
    TASK1 FORK
    TASK2 FORK
    TASK3 FORK
    
    DECIMAL
    VARIABLE SPEED  25 SPEED !
    : JOB1
              BEGIN
                15 3
                DO
                  I SCREEN   ( change screen color)
                  SPEED @ 5 MAX MS
                LOOP
              AGAIN  ;
    
    : JOB2
              BEGIN
                90 65
                DO
                   30 1 I 47 VCHAR
                   25 MS
                LOOP
              AGAIN ;
    
    VARIABLE X
    \ run for a period of time then go to sleep
    : JOB3
              X OFF
              2000 MS
              X ON
              MYSELF SLEEP  \ easy to I am all done
              PAUSE ;
    
     ' JOB1 TASK1 ASSIGN
     ' JOB2 TASK2 ASSIGN
     ' JOB3 TASK3 ASSIGN
    

    With that code loaded, you type

    MULTI

     

    TASK1 WAKE

    TASK2 WAKE

    TASK3 WAKE

    TASK1 SLEEP etc..

     

    And the Forth console is still alive the whole time...

     

    unless you say MYSELF SLEEP. :-)

     

    Nighty night Forth.

     

    • Like 8

  7. So where are the variables stored, and how about arrays?

     

    As Lee stated, the variables are in the common memory space called the dictionary.

     

    <ADVANCED TOPIC>

    But just like in Assembler, Forth variables are just an address.

    ​ie: they return an address to the stack not the contents.

    The dictionary name is the label, but it's in a linked list of other labels.

    However you could use any address you cared to as a variable

     

    So you could create an array manually like this:

    \ purely academic example
    
    CREATE MYDATA   100 CELLS ALLOT
    
    \ now create a "calculator" to return the address of addr[x]
    : []      ( n addr -- addr[n] )   SWAP 1 CELLS * +  ;
    
    \ usage:
    
      99 7 MYDATA [] !   \ store 99 into cell 7 
      7 MYDATA [] @      \ return the contents of cell 7
    
    

    </ADVANCED TOPIC>


  8. In another post we read about a person who in interested in translating a game from BASIC to Turbo Forth

    ...

    "But I do define and use a lot of variables. So I'll be exploring ways to eliminate the need to do so."

     

    There is no law of the universe that says you cannot write Forth programs and use variables just like you would in BASIC.

    Variables in FORTH, as in BASIC are GLOBAL. Any part of the program can get at them if it needs to.

     

    So go ahead, write a Forth program using all the variables you used in BASIC. It will work but there will be side-effects:

    1. You will use some extra RAM for each variable and it's name

    2. It will be more difficult to make your program connect together like Lego blocks

     

    Variables are always needed to record something the program wants to keep a permanent record of.

    Like say, the Score in the game needs to be recorded, and maybe the highest score I have gotten in the session.

    In fact Forth systems have a large number of variables built in to keep track of internal things.

    For example here is a partial list from CAMEL99 Forth so you can see some things that need to be kept in a variable

    (these may be different in other Forth systems)

    ( also notice that any character except space, can be used to name a variable)

      TFLAG         \ TASK flag awake/asleep status
      JOB           \ Forth word that runs in a task
      DP            \ end of dictionary pointer
      HP            \ used in number to text conversion
      CSP           \ remembers Current Stack Position (error checking) 
      BASE          \ the current number base (decimal/hex/binary/octal)  
      >IN           \ interpreter pointer
      VADR          \ holds VDP RAM address of the cursor
      C/L           \ Chars per line (32 or 40 depending on VDP mode)
      VROW          \ current VDP row
      VCOL          \ current VDP column
      C/SCR         \ chars per screen >300 or 3C0
      VMODE         \ keeps track of the video mode we are in
      VTOP          \ top of video screen memory. defaults to 0
      VBOTM         \ "bottom" VDP row address for current MODE (HEX 300 or 3C0)
    

    So you can see that variables have a place in Forth.

    Then what's the stack for?

    What if all sub-routines had a secret place where they could dump their results and other sub-routines

    just knew where that secret place was and picked up what they needed from there?

     

    That's where the Forth stack is used.

     

    Example:

     

    Forth has a simple word that prints 1 character called EMIT.

    Emit knows to look on the top of the stack to get a character to print out.

    No variable required.

     

    In Forth we have a way to describe that with "stack comments".

     

    The stack comments for EMIT look like this

     

    EMIT ( char -- ) This means EMIT takes 1 char off the stack and puts nothing back.

     

    GCHAR is in most TI-99 Forths as well. The stack comments for GCHAR are:

     

    GCHAR ( y x -- char) GCHAR needs 2 inputs and gives back 1 character on the stack.

     

    Here is where the Lego blocks start to happen.

     

    if you wanted to read a character from the screen and re-print it at the cursor position you can feed the output of GCHAR directly into EMIT like this.

     

    3 5 GCHAR EMIT

     

    Using the stack lets these words plug together like lego blocks.

     

    See the attached screen capture where we used HCHAR to put characters on the screen

    and then read them and printed them at the cursor position.

     

     

     

    post-50750-0-35350500-1494943267.jpg

    • Like 2

  9. My 2 cents for Sinpahltimus is to get a text editor or word processor and try writing what the different parts of the game do in your own words.
    Try not to think in basic, but chop the game up into the different sections of code that need to do specific stuff and say it in English.

    The secret to good Forth programs is very well demonstrated by Willsy's Primer #3.
    It takes some practice to stop thinking the way that BASIC forces us to think.

    For example I re-did a version of the denile program the way I would do it and here is the main routine called RUN:

    : RUN        ( -- )
                 2 SCREEN
                 CHANGE-CHARS
                 CLEAR  .PYRAMID .STARS .MOON .RIVER
                 HONK RESTORE-CHARSET 
                 ;

    2 SCREEN is CALL SCREEN(2). So you know I changed it to a night scene.

    The Forth word '.' (dot) prints a number so some people put a DOT in front of words that put stuff on the screen.
    Now you understand what this program does pretty much right away. And it's because it was "factored" into small pieces.

    Oh and I had to add the stars and a moon in a night scene. :-)


    And the .RIVER word is actually a loop that does all the action with the water and star twinkling and exits if you hit a key.
    It looks like this:

    Hope this helps.

    : .RIVER     ( -- )
                 INIT-WAVES  
                 BEGIN
                   100 MS
                   .WAVES
                   3 TWINKLES
                   KEY?
                 UNTIL ;
    

    post-50750-0-48743700-1494895280.jpg


  10. These Forth tutorials are great, @Willsy! Just when you decided to start #2, I was in surgery for a complete right knee replacement. Today is the first time I have felt up to doing anything much online. I am sorry I was not here to participate from the start. I still may be a little slow to get back in the saddle. Again, wonderful effort. :thumbsup: :thumbsup: :thumbsup:

     

    ...lee

     

    I wondered why you were not here to correct all my mistakes. :-)

     

    So happy that you are back.

     

    Get back to 110% soon Lee.

     

    You are in my thoughts.

     

    B

    • Like 1

  11. Brian

     

    I already have a 32 bit library. It has a ton of words in it including hold and <# etc.

     

    Maybe I could extend it to provide fixed point?

     

    I think it should be pretty straightforward with all that.

     

    It really is just creating the output operators for the fixed size you want to use isn't it?

    Or am I missing something?

     

    All the maths will work out of the box.

     

    I suppose you could "tart" things up with a special vocabulary or even an infix parser.

     

    Depends what you want I guess.

     

    B


  12. Hey Mark,

     

    I think you already have a fixed point library!

     

    299.99 fixed point is still just 29999 internally.

     

    What Forth does is provides a set of words to format any number as fixed point numbers.

     

    First you need this little word set. Believe it or not this allows arbitrary number formatting in

    114 bytes of code on a 16 bit machine.

     

    ( All these routines assume a 32 bit number on the stack (ie: 2 16 bit ints) which makes

    them much more practical for money programs.

     

    From CAMEL99 source code

    : UD/MOD      ( ud1 u2 -- u3 ud4)
                  >R 0 [email protected] UM/MOD -ROT R> UM/MOD ROT ;         \ 32/16->32 divide
    
    : HOLD   ( char -- )        -1 HP +!  HP @ C! ;               \ decr. HOLD pointer HP, Store char at the address contained in HP
    \ : >DIGIT ( n -- c)          DUP 9 > 7 AND + 30 + ;          \ convert n to ascii digit c (*Moved to ASM word because it is in a loop)
    : <#     ( --)              #PAD HP ! ;                       \ initialize Hold Pointer to end of the number buffer (#pad)
    : #      ( ud1 -- ud2)      BASE @ UD/MOD ROT >DIGIT HOLD ;   \ convert 1 digit & store at HP, return remainder
    : #S     ( ud1 -- ud2)      BEGIN  # 2DUP OR 0=  UNTIL ;      \ convert all digits in ud1. ud2 will be 0 (the remainder)
    : #>     ( ud1 -- c-addr u) 2DROP HP @ #PAD OVER - ;          \ return a stack string (address, length)  of the converted number
    : SIGN   ( n -- )           0< IF [CHAR] -  HOLD THEN ;      \ if 'n'<0  add '-' char  string created in #PAD
    
    

    Then you use these routines to make your number formats.

    The simplest is below;

    : U.     ( u -- )  S>D  <#  #S  #>  TYPE SPACE ;  \ print 'u' as an un-signed integer 
    

    But here is signed dollar formatting

    \ From CAMEL Forth source code Brad Rodriguez
    : DNEGATE  ( d -- d)  SWAP INVERT SWAP INVERT 1 M+ ;
    : ?DNEGATE ( d1 n -- d2) 0< IF DNEGATE THEN ; \  negate d1 if n negative
    : DABS     ( d1 -- +d2 )  DUP ?DNEGATE ; \ absolute value dbl.prec.
    
    CHAR . CONSTANT '.'
    \ from STARTING Forth, Brodie
    : .$    ( n -- ) S>D  TUCK DABS  <#  # # '.' HOLD  #S ROT SIGN  [CHAR] $ HOLD  #>  TYPE SPACE ;
    
    
    

    Works like this:

     

    29950 .$ $299.50

     

    -30095 .$ $-300.95


  13.  

    If you create a fixed point library, one of the first decisions is how much information you want to store about each FP number. Do you store just the binary number and leave it to the user of the library to specify how many places to shift the result after a multiplication, for instance? Or do you store the number of fractional bits for each FP number and use this to shift automatically? You can also store whether a FP number is signed or not. If it's unsigned you can speed up multiplications and divisions. If you store the number of fractional bits you can report an error if you try to add or subtract numbers with different number of fractional bits, or you can scale the numbers automatically. You also need to decide how to handle overflows, do you just ignore them, or report an error, or return the maximum possible result?

     

    Many of the old Forth systems resorted to a 32 bit library for fixed point math to prevent overflow.

    I have never thought about creating a 16 bit version. I would be useful for scientific applications if you were data logging low voltages and currents I suppose.

    Interesting...

     

    The creator of Forth made a couple of operators to help with 16bit overflow in calculations.

     

    One is '*/' called "star-slash". Arguments are ( n, multiplier, divisor ) and it returns 16bit int.

    You use it to scale values that could overflow on the multiply operation because the mpy is mixed math producing a 32 bit product

     

    The other handy one is called M+ which takes 2 ints and returns a 32bit int.

     

    B


  14. Just a rough bit of code, I'm sat on the Chromebook at work, taking a slightly longer than expected break due to not much happenings ... :)

     

    Right, this one needs XB.

     

    Couple of issues with it,

    <snip>

     

    attachicon.gifINVASION.zip

    attachicon.gifTHE INVASION.txt

     

    This is very cool. Thank you.

     

    I am going to use this to debug my SPRITE CODE.

     

    BF

    • Like 1

  15. OK I promise this is the last one for Primer #2.

     

    Now we have removed all the BASIC comments to make space on the page.

    With that space we have shown you some alternative ways to format Forth code

    to make it read nicely. Forth source code is free form so you can express

    yourself in whatever way works for you.

     

    We also introduce:

     

    COLORS ( set# set# FG,BG -- )

    SET# ( char -- set$)

    VREAD ( vdp-addr cpu-addr cnt -- ) (also known as VMBR)

    VWRITE ( cpu-addr vdp-addr cnt -- ) ( a.k.a VMBW )

     

    With those words we create a way to save the all the patterns and restore them when the program ends.

    And we include a little TI BASIC "HONK" so it's official.

     

     

     

    \ DENILE #3 MORE factoring and Forth Style
    
    \ dialect: CAMEL99 Forth V 0.9 TRAINER
    \          with extensions to help TI-BASIC PROGRAMMERS
    
    \ give names to the pattern data
    \ Where BASIC uses strings, we will use HEX integers
    \ Each word drops 4 numbers onto the stack for CHARDEF
    \ to pickup
    HEX
    : RSLOPE      ( -- n1 n2 n3 n4)  0102 0709 1F24 7F92 ;
    : LSLOPE      ( -- n1 n2 n3 n4)  8040 E090 F824 FE49 ;
    : STONE       ( -- n1 n2 n3 n4)  FF92 FF24 FF92 FF49 ;
    : SAND        ( -- n1 n2 n3 n4)  AA55 4489 2002 4801 ;
    : CAMEL       ( -- n1 n2 n3 n4)  0002 1735 7CFC 44AA ;
    : LITTLE-MAN  ( -- n1 n2 n3 n4)  0008 081C 2A08 1414 ;
    
    : WAVE1       ( -- n1 n2 n3 n4)  0083 C7AE FBEF BDF7 ;
    : WAVE2       ( -- n1 n2 n3 n4)  0007 8F5D F7DF 7BEF ;
    : WAVE3       ( -- n1 n2 n3 n4)  000E 1FBA EFBF F6DF ;
    : WAVE4       ( -- n1 n2 n3 n4)  001C 3E75 DF7F EDBF ;
    : WAVE5       ( -- n1 n2 n3 n4)  0038 7CEA BFFE DB7F ;
    : WAVE6       ( -- n1 n2 n3 n4)  0070 F8D5 7FFD B7FE ;
    : WAVE7       ( -- n1 n2 n3 n4)  00E0 F1AB FEFB 6FFD ;
    : WAVE8       ( -- n1 n2 n3 n4)  00C1 E357 FDF7 DEFB ;
    
    \ ====================================================
    \ *NEW* save & restore character set
    \ we will use some low expansion RAM at address HEX 2000
    
    HEX 2000 CONSTANT CHARBUFF     \ it's just that easy
    
    \ PDT is the pattern descriptor table in VDP RAM
    \ 800 is how many bytes it contains (2K bytes)
    \ VREAD is VMBR in TI speak
    \ VWRITE is VMBW in TI speak
    
    : SAVE-CHARSET    ( -- )
             ( from) PDT ( to) CHARBUFF 800 ( bytes) VREAD ;
    
    \ *NEW* "COLORS" restores mulitple charsets wow!
    : RESTORE-CHARSET ( -- )
             ( from) CHARBUFF ( to) PDT 800 ( bytes) VWRITE
              0 16 2 1 COLORS ;
    
    DECIMAL
    \ ====================================================
    \ assign patterns to characters
    \ this is a big defintion but it is like a table of data
    \ so it is reads easily but it is CODE.
    \ *NEW* SET# calculates the set# for us
    
    : CHANGE-CHARS ( -- )
    
       SAVE-CHARSET
    \  pattern   char          set#      colors
    \ --------   ----          ----     ----------
       RSLOPE      65 CHARDEF   65 SET# 11 1 COLOR
       LSLOPE      66 CHARDEF
       STONE       67 CHARDEF
       SAND        80 CHARDEF   80 SET# 2 12 COLOR
       CAMEL       88 CHARDEF   88 SET# 2  1 COLOR
       LITTLE-MAN  89 CHARDEF
       WAVE1      104 CHARDEF  104 SET# 5  6 COLOR
       WAVE2      105 CHARDEF
       WAVE3      106 CHARDEF
       WAVE4      107 CHARDEF
       WAVE5      108 CHARDEF
       WAVE6      109 CHARDEF
       WAVE7      110 CHARDEF
       WAVE8      111 CHARDEF
    \ --------------------------------------------
    ;
    
    \ ====================================================
    \ wave control variables
    VARIABLE T
    VARIABLE Y
    
    \ pyramid building strings
    32 STRING: A$
    32 STRING: B$
    32 STRING: C$
    
    : TABS ( n -- ) 0 ?DO SPACE LOOP ;
    
    : PUSH-UPS ( n -- ) 0 ?DO CR LOOP ;
    
    \ DOT is the Forth word to print a number.
    \ These words begin with '.' to tell us they print something
    \ A Forth style you can use if you want to
    : .SAND  ( -- )  0 23 80 32 HCHAR ;
    : .CAMEL ( -- )  0 22 88 1  HCHAR ;
    : .MAN   ( -- )  1 22 89 1  HCHAR ;
    
    : .PEAK  ( -- )  CR 15 TABS ." AB" ;
    
    \ this part is still very BASIC style
    \ We don't need to use strings for this
    : .BRICKS ( -- )
          C$ " CC" &       C$ $!    \ 190 C$=C$&"CC"
         " A" C$ &  " B" & B$ $! ;  \ 200 B$="A"&C$&"B"
    
    : .WALLS ( -- )
        A$ =""  B$ =""  C$ =""
        0 13 ?DO
             .BRICKS
             CR  I 1+ TABS B$ PRINT
         -1 +LOOP ;
    
    \ ====================================================
    \ Horizontal format code reads more like human language. 
    \ Some people code Forth this way
    
    : .PYRAMID ( -- )  .PEAK .WALLS .SAND .CAMEL .MAN  3 PUSH-UPS .SAND ;
    
    : WAVECLIP   ( n -- n') DUP 111 > IF DROP 104 THEN ;  \ clip n to valid wave char
    : NEXT-T     ( -- n)  T @ 1+  WAVECLIP DUP  T ! ;    \ inc T by 1 & clip
    : NEXT-Y     ( -- n)  Y @ 2+  WAVECLIP DUP  Y ! ;    \ inc Y by 2 & clip
    
    : INIT-WAVES ( -- ) 104 DUP T !  2+ Y !  ;
    
    : .WAVES     ( -- )  0 21 NEXT-T 32 HCHAR    0 22 NEXT-Y 32 HCHAR ;
    
    : .RIVER     ( -- ) INIT-WAVES  BEGIN  .WAVES  200 MS  KEY? UNTIL ;
    
    : RUN        ( -- ) 
                 15 SCREEN
                 CHANGE-CHARS
                 CLEAR  .PYRAMID  .RIVER
                 HONK RESTORE-CHARSET ;
    

     

     

    • Like 1

  16. So I have become slightly crazy since I started comparing BASIC to FORTH.

     

    I posted a literal translation of deNile here: http://atariage.com/forums/topic/265231-in-denile/page-2

     

    Now for anyone who is interested I took the same Forth code, which is very bad Forth style and re-organized in the way the Forth programs are written.

    You can see this "factoring" of the program into small parts in Lee's code.

    The stack stuff might make your head spin, so this shows Forth style but still using some Variables and strings like the original did.

    All we did was re-organize how to think about it. That's important for programming in any language if you move from BASIC.

     

    I think the thing to know is Chuck Moore believes every Forth WORD (sub-routine) should be so simple that there was no question what it does.

    And the name should clearly let you know what it's going to do.

     

    So with those to things in mind here is deNile using more "WORDS" which is how Forth programs are made.

     

    *Look at how many lines were removed by creating a WORD to clip values and then using that clipping word to make automatic functions to control the water characters.

    And hopefully we get more clarity when we are done because we have created a little language to solve the problem.

     

    ( Let me know if this is helpful or not

    I can shut-up anytime, but with a venue to teach about Forth I can be chatterbox)

     

     

     

    \ DENILE.BAS translate using Forth style factoring
    
    \ dialect: CAMEL99 Forth V 0.9 TRAINER
    \          with extensions to help TI-BASIC PROGRAMMERS
    
                                           \ un-rolled this loop
    HEX" 0102 0709 1F24 7F92"  65 CHARDEF  \ 20 FOR L=65 TO 70 )
    HEX" 8040 E090 F824 FE49"  66 CHARDEF  \ 30 READ Q$        )
    HEX" FF92 FF24 FF92 FF49"  67 CHARDEF  \ 40 CALL CHAR(L,Q$)
    HEX" AA55 4489 2002 4801"  68 CHARDEF  \ 50 NEXT L  )
    HEX" 0002 1735 7CFC 44AA"  69 CHARDEF  \  Camel
    HEX" 0008 081C 2A08 1414"  70 CHARDEF  \  little man
    
    \ water waves
    HEX" 0083 C7AE FBEF BDF7" 104 CHARDEF  \ 70 CALL CHAR(104,"0083C7AEFBEFBDF7")
    HEX" 0007 8F5D F7DF 7BEF" 105 CHARDEF  \ 80 CALL CHAR(105,"00078F5DF7DF7BEF")
    HEX" 000E 1FBA EFBF F6DF" 106 CHARDEF  \ 90 CALL CHAR(106,"000E1FBAEFBFF6DF")
    HEX" 001C 3E75 DF7F EDBF" 107 CHARDEF  \ 100 CALL CHAR(107,"001C3E75DF7FEDBF")
    HEX" 0038 7CEA BFFE DB7F" 108 CHARDEF  \ 110 CALL CHAR(108,"00387CEABFFEDB7F")
    HEX" 0070 F8D5 7FFD B7FE" 109 CHARDEF  \ 120 CALL CHAR(109,"0070F8D57FFDB7FE")
    HEX" 00E0 F1AB FEFB 6FFD" 110 CHARDEF  \ 130 CALL CHAR(110,"00E0F1ABFEFB6FFD")
    HEX" 00C1 E357 FDF7 DEFB" 111 CHARDEF  \ 140 CALL CHAR(111,"00C1E357FDF7DEFB")
    
    \ ALL variables must be defined before they are used
    VARIABLE T
    VARIABLE Y
    
    32 STRING: A$    \ STRING: is not standard Forth. We made it to for you.
    32 STRING: B$    \ *BTW Strings can have any name
    32 STRING: C$
    
    : TABS ( n -- ) 0 ?DO SPACE LOOP ;  \ we don't have a TAB word so make one
    
    : PUSH-UPS ( n -- ) 0 ?DO CR LOOP ; \ do the same for new lines
    
    \ build the program "in our own words"
    : SAND  ( -- )  0 23 68 32 HCHAR ; \ 250 CALL HCHAR(24,1,68,32)
    : CAMEL ( -- )  0 22 69 1  HCHAR ; \ 260 CALL HCHAR(23,1,69)
    : MAN   ( -- )  1 22 70 1  HCHAR ; \ 270 CALL HCHAR(23,2,70)
    
    : PEAK  ( -- )  CR 15 TABS ." AB" ; \ 180 PRINT TAB(X+1);"AB"
    
    : BRICKS ( -- )
          C$ " CC" &       C$ $!    \ 190 C$=C$&"CC" ( " needs a space, '&' is RPN 
         " A" C$ &  " B" & B$ $! ;  \ 200 B$="A"&C$&"B"
    
    : WALLS ( -- )
        0 13 ?DO
             BRICKS
             CR  I 1+ TABS B$ PRINT \ 210 PRINT TAB(X);B$
                                    \ 220 C=C+1
                                    \ 230 X=X-1
          -1 +LOOP                  \ 240 IF C=13 THEN 250 ELSE 190
    ;
    
    : PYRAMID ( -- )
         A$ =""  B$ =""  C$ =""   \ reset the strings
                                  \ 160 X=13
                                  \ 170 C=1
        PEAK
        WALLS
        SAND  
        CAMEL MAN
        3 PUSH-UPS               \ 280 PRINT
                                 \ 290 PRINT
                                 \ 295 PRINT
        SAND                     \ 296 CALL HCHAR(24,1,68,32)
    ;
    
    : WAVECLIP ( n -- n') DUP 111 > IF DROP 104 THEN ;  \ clip n to valid wave char
    
    : NEXT-T ( -- n) T @ 1+ WAVECLIP DUP T ! ; \ inc T by 1 & clip
    : NEXT-Y ( -- n) Y @ 2+ WAVECLIP DUP Y ! ; \ inc Y by 2 & clip
    
    : INIT-WAVES  ( -- )        \ 300 T=104
        104 DUP T !  2+ Y ! ;   \ 310 Y=106
    
    : RIVER    \ flow the river loop
        INIT-WAVES
        BEGIN                    \ 320 T=T+1
                                 \ 330 IF T>111 THEN 340 ELSE 350
                                 \ 340 T=104
                                 \ 350 Y=Y+2
                                 \ 360 IF Y>111 THEN 370 ELSE 380
                                 \ 370 Y=104
           0 21  NEXT-T 32 HCHAR \ 380 CALL HCHAR(22,1,T,32)
           0 22  NEXT-Y 32 HCHAR \ 390 CALL HCHAR(23,1,Y,32)
           100 MS
        KEY?
        UNTIL                   \ 400 GOTO 320
    ;
    
    : RUN   \ Forth doesn't have RUN so we make one
       CLEAR                  \ 10 CALL CLEAR
       13 6 5 COLOR           \ 150 CALL COLOR[10,6,5]
       PYRAMID                \ 500 GOSUB PYRAMID
       RIVER                  \ 600 GOSUB RIVERFLOW
    ;
    
    

     


    • Like 2

  17. In fact I think that if that source wasn't documented it's byte-count would be less than the BASIC list I made.

     

    Comments are FREE in Forth. The compiler reads the file and throws comments away.

     

    Regarding code size I just measured CAMEL99 version at 554 bytes (all in mem. expansion)

    Of course there is 8K of run time block underneath it all but that is not so different than TI BASIC ROMS and GROMS.

    One the reasons This Forth version is smaller is because it loads the character patterns with the interpreter when the program loads.

    CHARDEF just loads the data into VDP ram in "IMMEDIATE MODE" as we say in BASIC and nothing is stored in the program. Kind of cool.

     

    In Extended BASIC the program uses: 976 bytes for program and 11 bytes of "stack memory"

    • Like 1

  18. I have been working on creating additions to CAMEL99 Forth that make some training wheels for people who want to try Forth after TI BASIC
    The latest version of the EA5 file is here: https://github.com/bfox9900/CAMEL99
    I used the DENILE program to beat up on my GRAPHICS words and it helped me find some bugs.
    I also thought it would funny/informative to write a Forth version that uses VARIABLES exactly like the BASIC
    program. The hard thing for Forth is imitating BASIC strings, but fortunately I did that work in 1987
    for TI FORTH. Willsy liked the concepts so much he completely re-wrote them his own way for Turbo Forth and I re-wrote them again
    for this job.
    So here is DENILE translated to Forth in a more literal way.
    So I guess this can be a ROSETTA stone to translate for BASIC PROGRAMERS.
    To use it start CAMEL99 in Classic99 and past it in.
    Then type RUN.
    BY THE WAY: Do not make Forth programs like this. Use Lee's example to see how it's really done. ;)

    \ LITERAL TRANSLATION OF DENILE.BAS using variables
    \ This is not how Forth normally Forth is used
    \ but it was fun to see what would happen
    \ Integer variables in Forth are like BASIC in that
    \ they are global, meaning any part of the program
    \ can get at them.
    \ dialect: CAMEL99 Forth V 0.9 TRAINER
    \          with extensions to help TI-BASIC PROGRAMMERS
                                           \ un-rolled this loop
    HEX" 0102 0709 1F24 7F92"  65 CHARDEF  \ 20 FOR L=65 TO 70 )
    HEX" 8040 E090 F824 FE49"  66 CHARDEF  \ 30 READ Q$        )
    HEX" FF92 FF24 FF92 FF49"  67 CHARDEF  \ 40 CALL CHAR(L,Q$)
    HEX" AA55 4489 2002 4801"  68 CHARDEF  \ 50 NEXT L  )
    HEX" 0002 1735 7CFC 44AA"  69 CHARDEF  \  Camel
    HEX" 0008 081C 2A08 1414"  70 CHARDEF  \  little man
    HEX" 0083 C7AE FBEF BDF7" 104 CHARDEF  \ 70 CALL CHAR(104,"0083C7AEFBEFBDF7")
    HEX" 0007 8F5D F7DF 7BEF" 105 CHARDEF  \ 80 CALL CHAR(105,"00078F5DF7DF7BEF")
    HEX" 000E 1FBA EFBF F6DF" 106 CHARDEF  \ 90 CALL CHAR(106,"000E1FBAEFBFF6DF")
    HEX" 001C 3E75 DF7F EDBF" 107 CHARDEF  \ 100 CALL CHAR(107,"001C3E75DF7FEDBF")
    HEX" 0038 7CEA BFFE DB7F" 108 CHARDEF  \ 110 CALL CHAR(108,"00387CEABFFEDB7F")
    HEX" 0070 F8D5 7FFD B7FE" 109 CHARDEF  \ 120 CALL CHAR(109,"0070F8D57FFDB7FE")
    HEX" 00E0 F1AB FEFB 6FFD" 110 CHARDEF  \ 130 CALL CHAR(110,"00E0F1ABFEFB6FFD")
    HEX" 00C1 E357 FDF7 DEFB" 111 CHARDEF  \ 140 CALL CHAR(111,"00C1E357FDF7DEFB")
    \ ALL variables must be defined first
    VARIABLE X
    VARIABLE C
    VARIABLE T 
    VARIABLE Y
    32 STRING: A$    \ STRING: is not standard Forth. We made it to for you.
    32 STRING: B$    \ *BTW Strings can have any name
    32 STRING: C$
    : TAB ( n -- ) 0 ?DO SPACE LOOP ; \ we don't have a TAB word so make one
    : PYRAMID
       A$ =""  B$ =""  C$ =""
      14 X !                     \ 160 X=13
       1 C !                     \ 170 C=1
       CR X @ 1+ TAB ." AB"      \ 180 PRINT TAB(X+1);"AB"
     BEGIN                       \ " needs a space, '&' is RPN 
          C$ " CC" &       C$ $! \ 190 C$=C$&"CC"
         " A" C$ &  " B" & B$ $! \ 200 B$="A"&C$&"B"
          CR   X @ TAB  B$ PRINT \ 210 PRINT TAB(X);B$
          1 C +!                 \ 220 C=C+1
         -1 X +!                 \ 230 X=X-1
          C @ 14 =               \ 240 IF C=13 THEN 250 ELSE 190
     UNTIL
        0 23 68 32 HCHAR         \ 250 CALL HCHAR(24,1,68,32)
        0 22 69 1  HCHAR         \ 260 CALL HCHAR(23,1,69)
        1 22 70 1  HCHAR         \ 270 CALL HCHAR(23,2,70)
        CR                       \ 280 PRINT
        CR                       \ 290 PRINT
        CR                       \ 295 PRINT
        0 23 68 32 HCHAR         \ 296 CALL HCHAR(24,1,68,32)
    ;
    : RIVER    \ flow the river loop  *EDIT* IF ELSE not needed for Forth
        104 T !               \ 300 T=104
        106 Y !               \ 310 Y=106
     BEGIN
        1 T +!                \ 320 T=T+1
        T @ 111 >             \ 330 IF T>111 THEN 340 ELSE 350
        IF  104 T ! THEN      \ 340 T=104
        2 Y +!                \ 350 Y=Y+2
        Y @ 111 >             \ 360 IF Y>111 THEN 370 ELSE 380
        IF  104 Y ! THEN      \ 370 Y=104
        0 21 T @ 32 HCHAR \ 380 CALL HCHAR(22,1,T,32)
        0 22 Y @ 32 HCHAR \ 390 CALL HCHAR(23,1,Y,32)
        100 MS
        KEY?
     UNTIL                    \ 400 GOTO 320
    ;
    
    : RUN   \ Forth doesn't have RUN so we make one
       CLEAR                  \ 10 CALL CLEAR
       4 SCREEN               \ BASIC does this, Forth only does what you say
       13 6 5 COLOR           \ 150 CALL COLOR[10,6,5]
                              \ Forth has different sets,
       PYRAMID                \ 500 GOSUB PYRAMID
       RIVER                  \ 600 GOSUB RIVERFLOW
    ;
    

    • Like 1
×
×
  • Create New...