Jump to content
IGNORED

Camel99 Forth Information goes here


TheBF

Recommended Posts

 

28649 (>6FE9) is, indeed, prime, but >6FE5 (28645) is not. It is the latter that TI used. The number (>7AB9) TI used to add to the product of >6FE5 and the value at >83C0 is not prime either. They do not have any primes in common, however. I always thought the bit pattern of the two numbers might be important, but I am not sure.

 

It is kind of interesting that the second number is π to 5 places with 1 added to the last place—as though to insure the lowest bit is 1. In fact the lowest bit of both numbers is 1.

 

...lee

 

 

Good catch. Moving too fast this morning. I was looking for a close prime number to 6FE5 for the experiment.

 

So the evidence is point to the fact the choosing these internal numbers is important.

Edited by TheBF
Link to comment
Share on other sites

Here is a much faster version of your PRNG-testing program that avoids copying the screen from VRAM to CRAM every iteration:

\ Random Screen Fill that avoids using SCAN and whole-screen copying.

VARIABLE BLANK_CNT
VARIABLE ITERATIONS
VARIABLE DUPLICATES 

: WAIT-KEY   BEGIN KEY? UNTIL ;

: UNTILFULL ( -- )
      PAGE
      C/SCR @ BLANK_CNT !     \ initialize to screenfull of blanks
      DUPLICATES OFF
      ITERATIONS OFF
      BEGIN
          C/SCR @ RND DUP VC@
          DUP BL =
          IF
               DROP
               [CHAR] A SWAP VC!
               -1 BLANK_CNT +!      \ decrement blank count
          ELSE
               1+ SWAP VC!
               1 DUPLICATES +!
          THEN
         1 ITERATIONS +!
         BLANK_CNT @ 0=          \ did we hit all the blanks?
      UNTIL
      BEEP
      WAIT-KEY
      PAGE ." Random Screen Fill"
      CR
      CR ITERATIONS @ U. ." iterations"
      CR DUPLICATES @ U. ." duplicates"
;

and here it is converted to fbForth:

 

 

\ Random Screen Fill converted from CAMEL99 to fbForth 2.0---
\ ...avoids using SCAN and whole-screen copying

0 VARIABLE BLANK_CNT
0 VARIABLE ITERATIONS
0 VARIABLE DUPLICATES 

: WAIT-KEY   BEGIN ?KEY UNTIL ;

: UNTILFULL ( -- )
      PAGE
      SCRN_END @ BLANK_CNT !     \ initialize to screenfull of blanks
      0 DUPLICATES !
      0 ITERATIONS !
      BEGIN
          SCRN_END @ RND DUP VSBR
          DUP BL =               
          IF                     
               DROP              
               ASCII A SWAP VSBW 
               -1 BLANK_CNT +!      \ decrement blank count
          ELSE                   
               1+ SWAP VSBW      
               1 DUPLICATES +!
          THEN
         1 ITERATIONS +!
         BLANK_CNT @ 0=          \ did we hit all the blanks?
      UNTIL
      BEEP
      WAIT-KEY
      PAGE ." Random Screen Fill"
      CR
      CR ITERATIONS @ U. ." iterations"
      CR DUPLICATES @ U. ." duplicates"
; 

 

 

 

...lee

 

Not only is it faster, it seems to be accurate. Back to people management I go. :_(

Thanks Lee.

Link to comment
Share on other sites

Here is the little program I use to check the repetition frequency of the PRNG under test.

It uses a 32bit counter because I didn't want to have an overflow.

CREATE DCNT   0 , 0 ,   \ 32bit integer variable, 4 bytes

: DCNT++    ( -- ) DCNT 2@ 1 M+ DCNT 2! ;     \ incr. 32 bit counter
: CLRCNT    ( -- ) 0 0 DCNT 2! ;              \ clear counter
: UD.       ( d -- )  <# #S #>  TYPE SPACE ;  \ print double, unsigned
: .DCNT     ( -- )  DCNT 2@ UD. ;             \ print counter

: NEXTREP    \ find next repetition of Random number
        CLRCNT
        CR ." Searching for:" RNDW DUP  U.
        CR        
         BEGIN
            DCNT++
            RNDW
            OVER =
        UNTIL
        CR  ." Duplicate after "  .DCNT  ." random no.s"  ;

If this is working as I expect, which under Lee's watchful eye am learning is not always the case, :)

the TI Forth PRNG repeats after 20,000 or so and the GForth method shows 65,536 before a duplicate.

I think using the circular shift limits the TI Forth method, but again, I don't have the math to prove my intuition.

 

Comments welcome.

 

I would never have suspected the intricacies of a PRNG were so great.

I should probably have looked up the current state of the art on testing... oh well, it's a hobby after all.

Link to comment
Share on other sites

Handle Base File Access Change

 

A while back I discovered a problem with my ANS/ISO File system not selecting properly from one file handle to another file handle.

This is required for example if you wanted to copy data from one file to another file.

 

The files opened correctly, the code was selecting the correct PAB for each open file, but I was not changing the O/S address >8356

(which is called DSRNAM from what I can discover)

 

I did a kludge fix that involved string processing (yuk!) but the real fix meant adding a new field to the standard PAB.

 

Per the E/A Manual the standard PAB in CAMEL99 Forth is:

 

byte : opcode

byte : flag/status

cell : data buffer address

byte : record length

byte : character count (read or written)

cell : record number

byte : screen offset (unused)

string: filename (byte counted string) MAX length in CAMEL99=32 bytes

 

CAMEL99 now has this new field

cell: DSRNAM

 

The magic number that the O/S needs in DRSNAM to "select" a file, is the VDP address of the filename string after the '.' character.

I was doing that correctly when I created the PAB, but the number was not stored anywhere for fast retrieval.

 

In version 2.0.23 I have added a DSRNAM field to the PAB after the file name buffer.

When selecting via file handle it is now very simple.

The word SELECT takes the handle and "selects" the correct VDP PAB address from an array of PABs.

Now it also reaches into the PAB to retrieve the DSR name and put it into >8356

 

Here is the new code for SELECT with extra comments.

: SELECT  ( hndl -- )
           DUP ?FILES                                   \ test if hndl<= max files
           ]PTAB @ DUP 0= ABORT" Cannot select hndl 0"  \ ]PTAB is array of PAB addresses
           ^PAB !                                       \ store correct PAB in pab pointer 
           [PAB DSRNAM] V@  DSRNAM !  ;                 \ fetch dsrnam from VDP ram, store in CPU ram 

Once I made the decision to alter the PAB structure the rest went quickly.

 

It will get up on GITHUB this weekend.

  • Like 1
Link to comment
Share on other sites

The Ultimate Cure for Your Insomnia

 

The latest version of the manual for Camel99 Forth is up on GitHub.

 

https://github.com/bfox9900/CAMEL99-V2/tree/master/DOCS

 

(Edit: UPDATED link to show current doc)

 

The improvements include

  • Reorganizing sections for clarity
  • Expanded discussion on programming tools and disk utilities
  • Expanded discussion on using Forth Assembler including the use of BLWP from within Forth
  • Expanded discussion on multi-tasking

And oh so much more!

 

It's now 158 pages of fun fun fun! ;-)

 

That is all.

Edited by TheBF
  • Like 3
Link to comment
Share on other sites

30 Tasks + the Console Running on Camel99 Forth

I have wanted to trying this for a while on this little system. I always envisioned the multi-tasker to be able to "spawn" a new task. That means I need to be able to allocate some memory from somewhere and then setup a new task in that memory block. In version 2 of the CAMEL99 I wanted a low overhead way to use the low-memory block on the expansion RAM so I did what any Forther would do and turned the memory into a manually controlled stack of memory. :)

 

Anytime you need some memory you say " n MALLOC" where n is the number of bytes. Later you can say "n MFREE" to give it back. It's very crude. You can't remove a memory block from the middle of the stack alhough you could re-used it. Anyway, armed with that I realized I could create as many tasks as the low-memory could hold, up until it smashed into the Forth stacks at the top of low memory.

I can get a maximum of 32.

 

Each task needs 192 bytes and that is called the USER Area. The constant USIZE returns 192

  • FORK is routine that simply copies the current USER area into a new USER area and sets the 2 stack pointers to the new space.
  • ASSIGN give the task the address of a program to run
  • WAKE sets a flag in the task that it should run.

 

Using the simple MALLOC the routine to SPAWN a new task, with a program passed to it to run, is very simple.

\ create a task in heap, fork it, assign Execution token and run it
: SPAWN  ( xt -- )  
         USIZE MALLOC ( pid) 
         DUP >R FORK
         R@ ASSIGN  
         R> WAKE ;

The spoiler has the demo code which just puts the numbers >DEAD >BEEF on the local stack of each task and increments a global variable called X.

 

The 30tasks video shows the me typing at the console starting everything up and killing it all and doing it again.

You can see how much the console slows down with 30 jobs running.

Each task is set to behave "NICE"ly and not hogging the system too much. (NICE is a UNIX command that nobody uses. You lookup why) ;)

 

The lowmemtasks video just shows the CLASSIC99 debugger window with all the DEAD BEEF numbers going onto the local stacks of each task as I scroll through a few task blocks.

 

It's a useable thing now.

 

 

 

\ mtask spawn tasks demo

INCLUDE DSK1.TOOLS.F     \ for debugging etc.
INCLUDE DSK1.MTASK99.F

INIT-MULTI


\ create a task in heap, fork it, assign Execution token and run it
: SPAWN  ( xt -- )  
         USIZE MALLOC ( pid) 
         DUP >R FORK
         R@ ASSIGN  
         R> WAKE ;
         


HEX 10 CONSTANT STKSIZE  \ each task has ONLY 20 cells for each stack

VARIABLE X   \ used to test if tasks are running

: DROPS   ( n --)  0 DO DROP LOOP ; \ drop items from the stack

: STKTHING   \ fill and clear data stack so it can be seen in debugger
          BEGIN
            PAUSE

            STKSIZE 0 DO PAUSE DEAD  LOOP
            STKSIZE DROPS
            PAUSE

            STKSIZE 0 DO PAUSE BEEF  LOOP
            STKSIZE DROPS
            PAUSE
            1 X +!
          AGAIN ;

\ create and wake n tasks.
VARIABLE #TASKS

: TASKS  ( n -- )
         DUP #TASKS !
         0 DO  ['] STKTHING SPAWN  LOOP ;

: KILLALL
            SINGLE  
            USIZE #TASKS @ *  MFREE
            INIT-MULTI  ;

 

 

 

30TASKS.mp4

LOWMEM 30TASKS.MOV

  • Like 5
Link to comment
Share on other sites

Everybody Wants to Write to the Screen

 

One of the challenges in a multi-tasking system (or a multi-user system) is how to manage screen control. In a single task system you need a ROW variable and a COL variable to keep track of the cursor position. That works great until some other process on the machine tries to write to another location than your task is using.

 

So how do we keep this straight between tasks?

 

In Forth the answer is "USER variables" . USER variables are in a special memory block called the USER area.

The USER area is unique for each task. In CAMEL99 Forth this includes the VROW and VCOL variables for VDP x,y control as well as the number BASE variable and a bunch of compiler control variables.

 

So USER variables let each task have it's own copy of where it is using the VDP screen.

That's good, but it does not solve the problem of converting digital numbers into text.

 

Forth typically has a small buffer called the HOLD buffer where numbers are converted. It is placed just past the end of the dictionary typically, in unused memory. This means if you add new words to the dictionary the HOLD buffer moves up too. All good until two tasks try to convert a number to text at the same time!

 

The solution in CAMEL99 Forth is to add a USER variable called TPAD (task's pad) that *sets the offset from the end of the dictionary where any tasks places it's hold buffer and also a general purpose buffer call PAD.

(not to be confused with the TI-99 scratch-pad memory)

 

*Credit where it's due: This idea comes from HsForth written by the late James Kalihan of Springboro Ohio

 

So for each task we must set the offset a little bigger so each task has it's own little space. This could be automated, but on a little machine like the TI-99 it's best that you know where everything is. ;-)

 

The demo code shows how that is done by creating 3 timers and "PULSE" monitor. Each timer writes to the screen but in different a radix. (base 10,16 and 2)

 

The PULSE monitor shows how long it is taking for the multi-tasker to get around the entire set of tasks.

 

Part of the reason it's slow is because the KSCAN code in ROM takes about 1.2 mS so that limits how fast we can get around the loop of tasks immediately. If we ran a program in the console things speed up nicely. Or writing a KSCAN that is properly cooperative would be the ideal solution. (remember this tasker is cooperative not preemptive so programs have to be friendly)

 

The Demo video shows you how to control the tasks.

Also notice that when we compile a source code file, the counters stop. This is by design because having multiple tasks compiling into the same dictionary would be fatal! Adding a SAMS memory page for each task would fix that but it's just an idea at the moment.

 

 

 

\ 4tasks.f  mtask demo with numeric printing

INCLUDE DSK1.TOOLS.F
INCLUDE DSK1.MTASK99.F
INCLUDE DSK1.UDOTR.F    \ Right justified number printing

INIT-MULTI

( word to  create a task in heap and FORK)
: TASK:  ( size -- )  MALLOC DUP FORK CONSTANT ;

USIZE TASK: TASK1
USIZE TASK: TASK2
USIZE TASK: TASK3
USIZE TASK: TASK4

DECIMAL
\ show the round-robin task time in uS. (TMR is 21.3 uS/tick)
: .PULSE   TMR@ PAUSE TMR@ - ABS  213 10   */  6 U.R  ." uS" ;

\ single command to stop a task and goto next task
: STOP    ( -- ) MYSELF SLEEP PAUSE ;

\ re-entrant up counter
: UPCOUNTER ( col row end start -- )
            ?DO
              2DUP AT-XY
              I 8 U.R
              PAUSE
            LOOP
            2DROP ;

\ re-entrant down counter
: DNCOUNTER ( col row end start -- )
            SWAP
            ?DO
              2DUP AT-XY
              I 8 U.R
              PAUSE
            -1 +LOOP
            2DROP ;

: COUNTER1              \ decimal counter
            DECIMAL
            132 TPAD !  \ set task's PAD offset from HERE
            0 0 1000 0 UPCOUNTER
            STOP  ;


: COUNTER2              \ hex counter
            164 TPAD !
            HEX
            9 0 [ HEX ] 1FF 0 DNCOUNTER
            STOP ;

DECIMAL
: COUNTER3              \ perpetual binary counter
            196 TPAD !
            2 BASE !
            BEGIN
              20 0 256 0 UPCOUNTER
            AGAIN ;

: PULSER                \ show round robin time
            220 TPAD !
            DECIMAL
            BEGIN
              31 0 AT-XY
              PAUSE .PULSE
              500 MS
              [ HEX ] 83D6 OFF  \ kill screen time-out
              [ DECIMAL ]
            AGAIN ;

' COUNTER1 TASK1 ASSIGN
' COUNTER2 TASK2 ASSIGN
' COUNTER3 TASK3 ASSIGN
' PULSER   TASK4 ASSIGN

: RUN
     0  C/L@ BL VFILL \ erase top line of screen
     C/L@ VTOP !     \ set top of screen to 2nd line
     PAGE 
     CR ."   MULTI-TASKING VDP I/O DEMONSTRATION"
     CR ." ---------------------------------------"
     CR ." 3 counter tasks in BASE 10,16 and 2"
     CR ." 1 pulse task, shows round robin"
     CR ." time in micro-seconds"
     CR
     CR ." Shows how tasks can write to screen"
     CR ." using standard screen words."
     CR
     CR ." Console is active."
     CR
     CR ." When counters stop use RESTART to"
     CR ." run them again. WAKE will crash!"

     MULTI
     TASK4 WAKE
     TASK1 WAKE
     TASK2 WAKE
     TASK3 WAKE ;  

 

 

SCREENIO.mp4

Edited by TheBF
  • Like 3
Link to comment
Share on other sites

How to Do Conditional Compilation in a Tiny Machine

 

I was quite happy with the progress on CAMEL99 Forth V2 once I got the file access working.

The original vision for CAMEL99 was to make an 8K kernel that could extend itself with source code files.

I thought is was quite cool to have the source code in text files rather than blocks

 

So far so good but what about dependencies?

What if you try to load a file that requires another file's words to be loaded first?

 

Standard Forth has a set of words for this called [iF] [ELSE] [THEN]. You can use these words in your source code like this:

 

( Remember Forth's 'then' means "then go on with the rest of the program" :-)

TRUE [IF]   INCLUDE SOURCE1.FTH    
    [ELSE]  INCLUDE SOURCE2.FTH    
    [THEN] 

This a very nice solution however to implement them takes quite a bit of code. Here are the official definitions

: [ELSE] ( -- )
    1 BEGIN                                          \ level
       BEGIN BL WORD COUNT DUP WHILE                  \ level adr len
         2DUP S" [IF]" COMPARE 0= IF                  \ level adr len
             2DROP 1+                                 \ level'
          ELSE                                        \ level adr len
            2DUP S" [ELSE]" COMPARE 0= IF             \ level adr len
                2DROP 1- DUP IF 1+ THEN               \ level'
            ELSE                                      \ level adr len
                S" [THEN]" COMPARE 0= IF              \ level
                   1-                                 \ level'
               THEN
             THEN
          THEN ?DUP 0= IF EXIT THEN                   \ level'
       REPEAT 2DROP                                   \ level
   REFILL 0= UNTIL                                   \ level
    DROP
; IMMEDIATE

: [IF]   ( flag -- )  0= IF POSTPONE [ELSE] THEN ; IMMEDIATE
: [THEN] ( -- ) ; IMMEDIATE

This uses string comparisons so I would need to add COMPARE to the system, and it uses REFILL, which I have not implemented per the new Forth 2012 architecture.

altogether it would consume over 100 244 bytes of precious TI-99 memory.

 

So all that to say I am going to use two non-standard words called NEEDS and FROM.

The entire code adds only 36 bytes to the system and it solves my problem nicely. In fact it adds a feature that [iF] [THEN] does not have.

You specify the significant word needed with NEEDS.

: NEED  ( -- ?)  BL WORD FIND NIP  ;

: FROM   ( ? -- ) 
         BL PARSE-WORD ROT ( addr len ? --)
         0= IF  INCLUDED
         ELSE   2DROP
         THEN ; 

With these two words loaded all you do is:

 

NEEDS HCHAR FROM DSK1.GRAFIX.F

NEEDS DUMP FROM DSK1.TOOLS.F

NEEDS RND FROM DSK1.RANDOM.F

 

The hard part now is going through the source files and putting the correct conditional information in them. :-)

Edited by TheBF
Link to comment
Share on other sites

That's really nice. You'll need a NEEDS ALL FROM ... too to make it really shine.

 

Top job!

 

 

Thanks. I wish it was that smart. :-)

The NEEDS word is just checking the system for one word as a proxy for the file being loaded or not.

I always liked the import directive from MODULA that let you do what you are referring to; pulling in specific words from a library.

That's a bit bigger job for Forth, but it could be done. Probably makes more sense for a cross-compiler.

Link to comment
Share on other sites

CAMEL99 Forth is Starting to Look like a Shell

 

I have decided that adding the NEEDS/FROM utility is such a game changer that the system needs a revision number. So I will be releasing V2.1.0 this week with notes on the changes.

 

One big change is the NEEDS/FROM words are loaded first in the START configuration file because it is so handy. To demonstrate what I mean I wrote a simple file copy utility. In the video you can see that when I load the COPY utility the ANSFILES code is pulled into the system, but when I load the MORE utility, to see file contents, it loads very quickly because the ANSFILES code is already compiled in the system and so only the MORE utility program is compiled.

 

The file COPY code is starting to look very ANS/ISO Forth now. An interesting thing to note is how we parse the input file names with no temporary buffer. This is done by using "stack strings" that are just the address and length of a string residing on the data stack. The word FNAME cuts the text input, delimiting on a space character using PARSE-NAME. Each time it runs, it leaves the data for the string on the data stack. Even though both file name strings are in a single terminal input buffer, it is converted into two separate "stack strings" on the data stack. Neat!

 

 

 

\ copy text file
NEEDS OPEN-FILE  FROM DSK1.ANSFILES.F
NEEDS VALUE      FROM DSK1.VALUES.F
NEEDS PARSE-NAME FROM DSK1.PARSNAME.F

HEX
0 VALUE #1  \ these hold the file handles
0 VALUE #2

: FNAME  ( -- addr len )  PARSE-NAME DUP ?FILE ;

: COPY ( <file1> <file2> )
        FNAME FNAME   ( -- addr len addr len )
        DV80 W/O OPEN-FILE ?FILERR TO #2
        DV80 R/O OPEN-FILE ?FILERR TO #1
        55 MALLOC >R
        LINES OFF
        BEGIN
         #1 EOF 0=
        WHILE
          R@  50   #1 READ-LINE  ?FILERR DROP  ( -- n)
          R@ SWAP  #2 WRITE-LINE ?FILERR
          LINES 1+!
        REPEAT
        R> DROP                 \ DROP buffer address from rstack
        55 MFREE                \ release the buffer memory
        #2 CLOSE-FILE ?FILERR
        #1 CLOSE-FILE ?FILERR
        BASE @ >R 
        DECIMAL
        CR ." Copy complete. " LINES @ . ." records"
        R> BASE !
;

 

 

FILECOPY.mp4

  • Like 1
Link to comment
Share on other sites

The watchword of the day is read the manual carefully.

 

I finally got CAMEL99 Forth transferred to floppy disk with the help of Karsul's SAMS card and Magic FM from Arcadeshopper.

It loaded and ran great as long as I did not try to access a file. :(

 

There is a little paragraph in the E/A Manual, on page 404, that states:

>834A - >836D Used as a stack area by the interpreter, floating point routines,
and DSR routines. Unless console routines are called by your
assembly language program, this area is available for use.

Notice the reference to DSR routines. I had protected 2 addresses in this area, but used the rest of the space.

 

Well my clever idea of placing an array of user variables in the scratch-pad RAM was probably a bad idea.

I suspect that is why my DSR routine works on Classic99 which does not emulate the DSR code, but fails on real iron.

 

I am going to re-org the user variable list to avoid >834A..>836D and see what happens.

The cross-compiler should make that not too painful. (brave words) :-)

Link to comment
Share on other sites

You should probably consider a test run on MAME when working with DSRs, since it uses the original ROMs.

 

So will Classic99 if you tell it to. It's not in the menu yet, but it's no harder than configuring MAME. Configure a disk image drive on DSK1, DSK2, or DSK3 normally, then exit the emulator. Edit Classic99.ini and change the "Type" for that drive from '2' to '3', and it will access the image using the TI DSR. I don't recommend this for general use yet only because it doesn't support the usual range of Classic99 functions yet - it runs the DSR and nothing more. (So, 180k disk image maximum, DSK1-3 only).

 

In additional when using the default DSR, Classic99 will emit debug in the logs if you stomp on the reserved DSR memory locations, telling you which locations you stomped on and what address the code that did the stomping was at. The debugger is there to help, particularly large amounts of debug are provided for disk access.

 

The following tests are made for every disk access to the Classic99 DSR (and will debug a warning if violated):

-verifies that the workspace is GPLWS at >83E0

-verifies that the CRU base is stored at >83D0

-verifies (loosely) that the DSR entry point was stored at >83D2

-validates the PAB address is greater than >000D (otherwise there isn't room for the PAB when you backup to the start) - this one will refuse to execute the DSR as well

-validates that the DSR name length is 7 or less (this is the "DSK1" part and there are hard-coded limits)

-validates that the PAB opcode is valid

-checks several places if the PAB overruns the top of VRAM

-warns on filenames (including path) longer than 32 characters

-checks the top of VRAM pointer at >8370 for the disk buffers

-verifies the first four bytes of the VRAM buffer header are intact (except LSB of top of RAM pointer)

-verifies the number of files value is less than 9 (maximum supported by Classic99's DSR, more means corruption happened) - there is an option to breakpoint if these values are corrupt and a real TI controller card would crash on the call

 

Then AFTER the DSR call happens, it again checks the pointer at >8370, and validates the first four bytes (including the VRAM top LSB skipped above - that's a bug that won't be an issue in Classic99 yet, I was envisioning CF7 support for that one...) It also checks to see if any user code touched any of the actual buffers.

 

I don't know if that covers the case you ran into, I've been adding cases as I hit them myself. In general though, if Classic99 doesn't throw any warnings in the debug log, you have a pretty good chance of it working. If you test with the TI DSR option, then you're running the actual DSR code. That said, always good to test on multiple platforms, particularly if you are uncertain. The above options are meant to try and help you understand WHY it doesn't run.

  • Like 4
Link to comment
Share on other sites

 

So will Classic99 if you tell it to. It's not in the menu yet, but it's no harder than configuring MAME. Configure a disk image drive on DSK1, DSK2, or DSK3 normally, then exit the emulator. Edit Classic99.ini and change the "Type" for that drive from '2' to '3', and it will access the image using the TI DSR. I don't recommend this for general use yet only because it doesn't support the usual range of Classic99 functions yet - it runs the DSR and nothing more. (So, 180k disk image maximum, DSK1-3 only).

 

In additional when using the default DSR, Classic99 will emit debug in the logs if you stomp on the reserved DSR memory locations, telling you which locations you stomped on and what address the code that did the stomping was at. The debugger is there to help, particularly large amounts of debug are provided for disk access.

 

The following tests are made for every disk access to the Classic99 DSR (and will debug a warning if violated):

-verifies that the workspace is GPLWS at >83E0

-verifies that the CRU base is stored at >83D0

-verifies (loosely) that the DSR entry point was stored at >83D2

-validates the PAB address is greater than >000D (otherwise there isn't room for the PAB when you backup to the start) - this one will refuse to execute the DSR as well

-validates that the DSR name length is 7 or less (this is the "DSK1" part and there are hard-coded limits)

-validates that the PAB opcode is valid

-checks several places if the PAB overruns the top of VRAM

-warns on filenames (including path) longer than 32 characters

-checks the top of VRAM pointer at >8370 for the disk buffers

-verifies the first four bytes of the VRAM buffer header are intact (except LSB of top of RAM pointer)

-verifies the number of files value is less than 9 (maximum supported by Classic99's DSR, more means corruption happened) - there is an option to breakpoint if these values are corrupt and a real TI controller card would crash on the call

 

Then AFTER the DSR call happens, it again checks the pointer at >8370, and validates the first four bytes (including the VRAM top LSB skipped above - that's a bug that won't be an issue in Classic99 yet, I was envisioning CF7 support for that one...) It also checks to see if any user code touched any of the actual buffers.

 

I don't know if that covers the case you ran into, I've been adding cases as I hit them myself. In general though, if Classic99 doesn't throw any warnings in the debug log, you have a pretty good chance of it working. If you test with the TI DSR option, then you're running the actual DSR code. That said, always good to test on multiple platforms, particularly if you are uncertain. The above options are meant to try and help you understand WHY it doesn't run.

As always this is super help. Thanks!

 

I will make use of this. It's way simpler than loading an image on a floppy and debugging word by word in Forth, with limited tools... because I have no file support to load them.

 

B

Link to comment
Share on other sites

  • 4 weeks later...

CAMEL99 Forth is Alive on Real Iron

 

It took waaaay longer than I expected, but I guess that's normal for S/W projects.

 

Special thanks to all the 99ers on Atariage, you know who you are, who filled in my enormous knowledge gaps.

 

I will be getting these updated files to GitHub over the weekend.

 

 

 

 

CAMEL99 ON REALIRON.MOV

  • Like 2
Link to comment
Share on other sites

 

So will Classic99 if you tell it to. It's not in the menu yet, but it's no harder than configuring MAME.

 

-warns on filenames (including path) longer than 32 characters

 

Hmm the buffer path and size of EA (REA) and XB (RXB) is 39 characters including a length byte so 40 in total.

 

Almost every single Cart including DM2 and all TI originals have a 40 character buffer which includes Length byte.

 

I have no idea why you would limit it to 32? (Is this some arbitrary value you picked?)

Edited by RXB
Link to comment
Share on other sites

Hmm the buffer path and size of EA (REA) and XB (RXB) is 39 characters including a length byte so 40 in total.

 

Almost every single Cart including DM2 and all TI originals have a 40 character buffer which includes Length byte.

 

I have no idea why you would limit it to 32? (Is this some arbitrary value you picked?)

 

It's not a limit, it's just a warning. (Also, this is JUST the filename, not including the device and options). While TI software was pretty good at having reasonable length, a lot of third party software is smaller, and indeed more than 10 characters is sometimes not guaranteed. As part of my long filename specification, I recommended 32 characters be consider a compromise (power of two), and just added it to the warnings. :)

  • Like 1
Link to comment
Share on other sites

LOL yea I put up to 255 support in RXB 1000 for path names, and 1 byte for length.

 

Then discovered to my dismay 99% of the device DSR SUCK at only holding 39 and some as little as 10???

Which is as imaginative for future use as having 1 finger!

Link to comment
Share on other sites

I think I love CLASSIC99

 

In the course of writing documents for CAMEL99 Forth I needed a current version of a file in the TI system.

With the FILECOPY utility in CAMEL Forth, I simply did this:

COPY DSK1.START CLIP

And then pasted the file into MS Word.

 

Wow, how nice is that?

 

File copy looks like this:

NEEDS OPEN-FILE  FROM DSK1.ANSFILES
NEEDS VALUE      FROM DSK1.VALUES
NEEDS PARSE-NAME FROM DSK1.PARSNAME

HEX
0 VALUE #1  \ these hold the file handles
0 VALUE #2

: FNAME  ( -- addr len )  PARSE-NAME DUP ?FILE ;

: COPY ( <file1> <file2> )
        FNAME FNAME   ( -- addr len addr len )
        DV80 W/O OPEN-FILE ?FILERR TO #2
        DV80 R/O OPEN-FILE ?FILERR TO #1
        52 DUP MALLOC >R
        LINES OFF
        BEGIN
          R@  50  #1 READ-LINE  ?FILERR   ( -- #bytes eof?)
        WHILE
          R@ SWAP #2 WRITE-LINE ?FILERR
          LINES 1+!
        REPEAT
        R> DROP                 \ DROP buffer address from rstack
      ( 52) MFREE               \ release the buffer memory
        #2 CLOSE-FILE ?FILERR
        #1 CLOSE-FILE ?FILERR
        BASE @ >R 
        DECIMAL
        CR ." Copy complete. " LINES @ . ." records"
        R> BASE !
;

post-50750-0-37076600-1540750049.jpg

Edited by TheBF
  • Like 1
Link to comment
Share on other sites

Machine Forth "mini-compilers" speed up Forth code

 

In the course of reviewing a TRIG lookup table I realized it would be cool if I used the 9900 "indexed" addressing mode to read numbers from the table.

Normally this requires using the Assembler because the address of the table has to be hard coded into the instruction like this:

TABLE   BSS >1000

GETNUM  MOV TABLE@(R4),R4

The Forth Assembler is actually a set of mini "assemblers" that each know how to assemble only 1 instruction. :-)

 

Chuck Moore the inventor of Forth began using this concept to create "mini compilers" for a handful of instructions and he called this "machine Forth"

 

Think along those lines I created two "mini-compilers" like this:

\ machine Forth "compilers"
\ R4 is CAMEL99 Forth cache register for Top of stack
: 2*,   ( n -- 2(n)  A104 , ;             \ A R4,R4
: +@,   ( addr -- )  C124 , ( addr) ,     \ MOV addr@(R4),R4

The first one just multiplies R4 by two by compiling the A R4,R4 instruction into memory.

 

The 2nd one adds an address to R4 and "fetches" the value at the computed address back into R4. In Forth that is a '+' and a '@' (Fetch)

+@, "compiles" the instruction into memory and then it compiles the address into memory right after the instruction using the comma "number compiler".

 

Now we can create a CODE word as simply as doing it in hi level Forth.

DECIMAL
CODE ]SIN  ( ndx -- sin) 2*,  SINTAB +@,  NEXT, ENDCODE

   : ]SIN2 ( ndx -- sin) 2*   SINTAB + @ ;  \ FORTH equivalent

By testing a run through the entire SIN table 100 times, we find the Machine Forth version is two times faster!

DECIMAL
: MFSIN     100 0 DO   91 0 DO I ]SIN DROP  LOOP LOOP ;
: FORTHSIN  100 0 DO   91 0 DO I ]SIN2 DROP LOOP LOOP ;

post-50750-0-83937300-1541434742.jpg

Edited by TheBF
  • Like 4
Link to comment
Share on other sites

So because I am not well behaved I avoided the issue of signed division in my Forth system, since my needs were always positive numbers.

That is until I had to work with trigonometric coordinates...

 

So Lee Stewart to the rescue! (again)

 

I found the code in FBForth to be well suited to my needs so I re-coded it in structured assembler and added a variable called FLOOR to the system that let's one switch between symmetrical and floored division.

Preliminary testing seems to show I got the logic correct but I will beat it up a bit more.

 

Edits

1. Never trust preliminary testing. (it didn't work for all cases)

2. Working with your own tools is "interesting".

CON: I found a bug in my cross-assembler with JLT instruction

PRO: I fixed it!

 

Hats off once again to Dr. Stewart.

 

For the record here is where the code ended up. I then use M/MOD as a primitive that can perform symmetrical or floored division (default).

M/MOD is a primitive that is used to derive the rest of the division family. ( */MOD, /MOD, / )

CODE: M/MOD  ( lsb msb n3 -- rem quot)
      *SP+ R1 MOV,     \ POP the high word of ud to r1
      *SP  R2 MOV,     \ move low word of ud to r2 (keep stack pos.)
       TOS R3 MOV,             \ DUP for sign of den
       R1  W  MOV,             \ DUP for sign of num
       R1  R5 MOV,             \ DUP 2nd copy of num for symmetric sign
          TOS ABS,             \ force den positive
       R1  0 CMPI,             \ check sign of num
       LT IF,                  \ if numerator<0
           R1 INV,             \ DO DABS. invert num MSB and..
           R2 NEG,             \ ..negate num LSB
           OC IF,              \ if carry=TRUE
               R1 INC,         \ increment num MSB
           ENDIF,
       ENDIF,
       TOS R1 DIV,             \ perform the division. R1=quot, R2=rem
\ * Test for negative quotient
       R3 W  XOR,              \ compare signs of den and num
       LT IF,                  \ if different
           R1  NEG,            \ negate quotient
       ENDIF,
\ check for remainder
       R2 0 CMPI,
       NE IF,                  \ if <>0
             R5  8000 ANDI,    \ test for numerator negative
             NE IF,            \ if signbit<>0
                  R2 NEG,      \ rem. takes sign of num(symmetric)
             ENDIF,
\         * Handle floored division, if necessary
            _floor @@ R0 MOV,     \ symmetric or floored division?
             NE IF,               \ if 0, it's symmetric and we're done
                   W  8000 ANDI,  \ use XOR result to check num and den signs
                   NE IF,
                         R1 DEC,    \ signs different, so floor quot
                         R3 R2 ADD, \ rem = den + rem
                   ENDIF,
            ENDIF,
       ENDIF,
       R1 TOS MOV,     \ quotient to tos
       R2 *SP MOV,     \ put remainder on open stack location
       NEXT,           \ we're outta here!
       END-CODE

Edited by TheBF
  • Like 1
Link to comment
Share on other sites

CAMEL99 Forth Stable Release V2.1.E

 

https://github.com/bfox9900/CAMEL99-V2

 

I think I have this system in place where I feel pretty good about people really giving it a try.

The new DSRLNK is stable and I have added floored division to this release as required by the ANS/ISO Forth standard.

 

A variable in the system allows you to switch between Floored and Symmetrical division like this:

FLOOR ON  ( default)

FLOOR OFF 

Here are the GitHub release notes. Beat it up! :)

 

Nov 13, 2018 V2.1.E

  • Floored division is now the default per ANS/ISO standard.
  • Symmetrical division is set using the FLOOR variable: FLOOR OFF, FLOOR ON
  • Due to the slow speed of the 9900 CPU floored division is coded in Forth Assembler. The code is a re-work of code, used by permission, from FB-Forth by Lee Stewart. ( http://fbforth.stewkitt.com/ )
  • Forth primitives are now separated into 2 files: 9900FAS2.HSF, TI99PRIMS.HSF
  • RSTPAB (reset PAB) added to QUIT for stability when using file system.
  • Improved ?TERMINAL so it waits for key release after key press.
  • Like 2
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...