Jump to content
IGNORED

fbForth—TI Forth with File-based Block I/O [Post #1 UPDATED: 06/09/2023]


Lee Stewart

Recommended Posts

Almost there! The blocks file I/O works—phew! Now I need to work my way through the graphics mode changes I made to the system blocks file, FBLOCKS. I may need to put the Sprite Attribute Table back where it was for bitmap mode. I'm hoping the bugs are elsewhere. I guess it'll be a few days for that.

 

...lee

Link to comment
Share on other sites

I should be able to post fbForth v0.92 later today! :) The Sprite Attribute Table is right where I want it for bitmap mode! :-D It's amazing that one bug I found had not shown up before in TI Forth—it was a disaster just waiting to happen. The SAT was never explicitly initialized and, when I moved it, it happened to be filled with FFh; whereas before, it happened to be filled with 0. This is significant because the cursor for the 64-column editor is a sprite and the left nybble (early clock) of the fourth byte is apparently left alone by the word SPRITE . With Fh in the left nybble, the cursor was screwed up. Right now, I am initializing SAT with 0; but, I may change the definition of SPRITE to zero that nybble. Any suggestions?

 

...lee

Link to comment
Share on other sites

... the left nybble (early clock) of the fourth byte is apparently left alone by the word SPRITE . With Fh in the left nybble, the cursor was screwed up. Right now, I am initializing SAT with 0; but, I may change the definition of SPRITE to zero that nybble. Any suggestions?

 

...lee

 

H-m-m-m—SPRITE calls SPRCOL , which goes out of its way to preserve that left nybble; so, I really do need to initialize the SAT with 0s. I suppose I could also create a word ( SPRCLK ? ) to toggle or set the early clock bit. Does anyone know whether the other three bits of the early-clock nybble mean anything? The E/A Manual is silent on that score.

 

...lee

Link to comment
Share on other sites

Lee

 

Have downloaded it. Will take it out for a spin tomorrow and report back!

 

Mark

 

Here is an updated set of notes—probably more than you care to know: :P fbForth092Readme.txt <---This replaces the ReadMe file of the same name included in the ZIP file in post #329 above.

 

...lee

Link to comment
Share on other sites

As I'm working my way through the fbForth manual, I'm picking up a few minor (so far) errors I made in my 2nd edition of the TI Forth manual. In the process, I also noticed a bug in TI Forth I don't believe was ever reported. It is in SMOVE on TI Forth system screen/block #39, line 11, where " OVER OVER " should be removed. If the clause they are in is ever executed, there will be 2 extra numbers on the stack when SMOVE is finished. I will eventually post this information in the TI Forth Instruction Manual in PDF Format (edited & expanded) thread; but, right now, I'm focusing on getting the fbForth manual finished.

 

The reason I bring this up here is that, in fbForth, I am replacing SCOPY and SMOVE with CPYBLK (pretty much lifted from @Willsy's TurboForth) and, in writing that part of the fbForth manual, I realized that SMOVE had been written to make copying a range of blocks to an overlapping range a safe process regardless of whether the destination starting block was higher or lower than the source starting block. It's safe to write from high to low, but not the other way round unless you reverse the writing order to start with the end block, which is exactly what SMOVE does. I was going to do the same with CPYBLK ; but, it would result in a lot more code in fbForth because I would have to compare the source and destination blocks file names to see whether they were the same before proceeding with the overlap tests. I'm not sure it's worth the effort. If I were to make CPYBLK overlap safe, it would probably behoove me to make MOVE , CMOVE and VMOVE also overlap safe to maintain consistency. I'm inclined to just inform the user that copying to a range that overlaps the source is not safe when copying to a higher location. Anybody care?

 

...lee

  • Like 1
Link to comment
Share on other sites

To answer your question: If it's possible to do it within the constraints that you have, or have you set yourself (is your CPYBLK loaded in from disk when needed like mine?) then you should do it. A caveat in the manual is okay, but, what if they *do* want to copy some blocks with an overlapping range? They are basically stuffed - well, stuffed in the sense that they can't use CPYBLK and will need to invent their own routine to do it, which may be beyond the abilities or desires of the user...

Link to comment
Share on other sites

To answer your question: If it's possible to do it within the constraints that you have, or have you set yourself (is your CPYBLK loaded in from disk when needed like mine?) then you should do it. A caveat in the manual is okay, but, what if they *do* want to copy some blocks with an overlapping range? They are basically stuffed - well, stuffed in the sense that they can't use CPYBLK and will need to invent their own routine to do it, which may be beyond the abilities or desires of the user...

 

You don't make it easy! :P

 

Logic to do it is easy enough. I am sure, as is my wont, that I will use much more code than is necessary to accomplish the task.

 

H-m-m-m—I suppose I could write a word that would require the source starting address, destination starting address, copy count; determine overlap and copy direction; and leave a flag. The stack signature would be "( src dst cntflag )". A value of 1 for flag would indicate a need to change the direction of the copy. I could then use that word in all four copy operations. Of course, MOVE would require me to double the count for the test word because its copy unit is 2 bytes. I will post my code as I go to get suggestions for making it more compact.

 

...lee

Link to comment
Share on other sites

OK, first, I'll post my existing code for the copy operations. Please pay no attention to the text coloring in the code blocks. I'm using the "none" format in the AA editor's "Code" option, which, at least, does not remove spaces; but, it is still doing some irritating formatting. :mad:

 

Here is the TMS9900 ALC for CMOVE , MOVE and VMOVE :

 

 

*   {SP   = R9 :  stack pointer                            }
*   {NEXT = R15:  pointer to next instruction fetch routine}
*
*** CMOVE ***   move cnt bytes from src RAM to dst RAM
*       ( src dst cnt --- )
*
       DATA BF007
L1015  DATA >8543,>4D4F,>56C5
CMOVE  DATA $+2
       MOV  *SP+,R1
       MOV  *SP+,R2
       MOV  *SP+,R3
       MOV  R1,R1
       JEQ  CMOVE2
CMOVE1 MOVB *R3+,*R2+
       DEC  R1
       JNE  CMOVE1
CMOVE2 B    *NEXT
*
*
*** MOVE ***   move cnt cells from src RAM to dst RAM
*       ( src dst cnt --- )
*
       DATA L1015
A1000  DATA >844D,>4F56,>45A0
MOVE   DATA $+2
       MOV  *SP+,R1
       MOV  *SP+,R2
       MOV  *SP+,R3
       MOV  R1,R1
       JEQ  MOVE2
MOVE1  MOV  *R3+,*R2+
       DEC  R1
       JNE  MOVE1
MOVE2  B    *NEXT
*
*
*** VMOVE ***   move multiple bytes from one VDP location to another
*       ( vsrc vdst cnt --- )
*
       DATA S1004
S1004X DATA >8556,>4D4F,>56C5
VMOVE  DATA $+2
       MOV  *SP+,R1             pop cnt to R1
       MOV  *SP+,R3             pop vdst to R3
       ORI  R3,>4000            prepare for VDP write
       MOV  *SP+,R2             pop vsrc to R2
*
** copy cnt bytes from vsrc to vdst
*     {MAINWS = address of fbForth's workspace and, hence, the address of it's R0}
*
VMVMOR MOVB @MAINWS+5,@VDPWA    write LSB of VDP read address
       MOVB R2,@VDPWA           write MSB of VDP read address
       INC  R2                  next VDP read address
       MOVB @VDPRD,R0           read VDP byte
       MOVB @MAINWS+7,@VDPWA    write LSB of VDP write address
       MOVB R3,@VDPWA           write MSB of VDP write address
       INC  R3                  next VDP write address
       MOVB R0,@VDPWD           write VDP byte
       DEC  R1                  decrement count
       JNE  VMVMOR              repeat if not done
       B    *NEXT
 

 

 

 

Here is the high-level fbForth code for CPYBLK :

 

 

( CPYBLK definition...)

DECIMAL 
0 VARIABLE SFL 0 VARIABLE DFL   ( pointers to source and destination filename strings)           
: GNUM BL WORD HERE NUMBER DROP ; ( get number from terminal)
( GBFL gets HERE to stack; stores filename at HERE; establishes new HERE;
  store string address in variable pass on stack)
: GBFL ( addrvar -- )  HERE 0 BFLNAM SWAP !  ;  
: CPYBLK    ( should we FLUSH before we start?)
    HERE    ( save address where we'll copy current blocks filename; maybe use R)
    BPB BPOFF @ + 9 +     ( get address of blocks filename's char-count byte)
    DUP VSBR 1+         ( get count byte and increment it for copy count)
    HERE SWAP DUP =CELLS ALLOT VMBR  ( get current blocks name to HERE and move HERE past it)
    GNUM >R GNUM 1+ >R  ( move start & end block #s to return stack)        
    SFL GBFL            ( get source filename to HERE and store address, moving HERE)        
    GNUM                ( get destination start block to stack)        
    DFL GBFL            ( get destination filename to HERE and store address, moving HERE)                                
    R> R>               ( get the end & start block #s from return stack for loop)        
    DO DUP              ( DUP destination block #)
        SFL @ (UB)      ( open source blocks file)
        I BLOCK         ( load next block)
        2- !            ( decrement block address and store destination block #)
        DFL @ (UB)      ( open destination blocks file)
        UPDATE FLUSH    ( write block to destination blocks file)
        1+              ( increment destination block #)
    LOOP                ( continue loop)
    DROP        ( DROP excess block #)    
    DUP         ( DUP old HERE)
    (UB)        ( restore original blocks file to 'current' status)
    DP !        ( restore dictionary pointer)
;   

 

 

 

Now, for how TI Forth does an overlap-safe range copy ( SMOVE )—

Remember that TI Forth is doing disk-sector I/O. In the code below, OFFSET , MINUS and, perhaps, R merit explanation:

  • OFFSET is a Forth screen offset to disk drives not relevant in fbForth. It is usually 0, which means that screen copying is relative to drive 0 (DSK1).
  • MINUS is the same as TurboForth's NEGATE , i.e., 4 MINUS replaces 4 on the stack with -4.
  • R copies the top of the return stack (without destroying it) to the parameter stack. It is equivalent to TurboForth's R@ .

 

 

0 CONSTANT AD  ( +1|-1 added to get next screen)
: SCOPY ( src dst --- )  ( copy a single screen)
    OFFSET @ + SWAP BLOCK 2- ! UPDATE FLUSH ;
: SMOVE  ( src dst cnt --- )  ( copy a range of screens)
    >R              ( cnt to return stack)
    OVER OVER       ( dup src & dst)
    - DUP           ( src-dst & dup it)
    0< SWAP R MINUS > + 2 =  ( only overlapping low to high need to copy in reverse direction)
    IF      ( we're going from the top down)      
        SWAP R + 1- SWAP R + 1-  ( get highest src & dst screens)
        -1 ' AD !   ( we're decrementing the next screens)
    ELSE    ( we're going from the bottom up)
        1 ' AD !    ( we're incrementing the next screens)
    ENDIF 
    R> 0 DO     ( cnt from return stack so we're doing " cnt 0 DO ")
        SCOPY                   ( do the block copy from src to dst)
        AD + SWAP AD + SWAP     ( get next src & dst screens)
    LOOP 
    DROP DROP ( drop the leftover src & dst)
;
 

 

 

 

...lee

Link to comment
Share on other sites

Well—I ran into trouble trying to make CMOVE , MOVE and VMOVE overlap-safe. I tried to BL to another routine to pop the stack and set up the copy. It worked fine from the terminal line (my typing it in); but, it failed miserably when embedded in another word—at least, a word defined in the kernel. I don't know who out there can help me with this problem except for @Willsy, but here is the code snippet from the kernel:

*** determine parameters for CMOVE , MOVE and VMOVE
*   ...pop cnt src dst and set up direction of copy
*       R0 will contain 0 or 1 depending on byte or word move
*       pass values in R1--R4 to the copy routines
*
MVPMS  MOV  *SP+,TEMP1      pop count (cnt)
       MOV  *SP+,TEMP2      pop destination (dst)
       MOV  *SP+,TEMP3      pop source (src)
       LI   TEMP4,1         assume positive direction
       C    TEMP2,TEMP3     dst > src?
       JLE  MVPMS1          no...return to caller
       MOV  TEMP2,TEMP5     yes...check for overlap and adjust if necessary
       S    TEMP3,TEMP5     get positive difference
       C    TEMP1,TEMP5     cnt > difference
       JLE  MVPMS1          no...return to caller
       LI   TEMP4,-1        change direction of copy
MVPMS2 A    TEMP1,TEMP2     add cnt to dst
       DEC  TEMP2           adjust to highest dst location
       A    TEMP1,TEMP3     add cnt to src
       DEC  TEMP3           adjust to highest src location
       DEC  TEMP0           this will result in 0 only if it came in as 1
       JEQ  MVPMS2          if not word copy, repeat this dance
MVPMS1 B    *LINK
*
*** CMOVE ***   move cnt bytes from src RAM to dst RAM
*       ( src dst cnt --- )
*
       DATA BF007                                      <---LABEL field for interpreter
L1015  DATA >8543,>4D4F,>56C5                          <---NAME field for interpreter
CMOVE  DATA $+2                                        <---PARAMETER field for interpreter (points to code field)
       CLR  TEMP0      tell MVPMS this is a byte copy  <---CODE field for interpreter
       BL   @MVPMS          get parms
       MOV  TEMP1,TEMP1     check for cnt = 0
       JEQ  CMOVE2          nothing to copy; we're outta here
CMOVE1 MOVB *TEMP3,*TEMP2   copy a byte
       A    TEMP4,TEMP2     next dst
       A    TEMP4,TEMP3     next src
       DEC  TEMP1
       JNE  CMOVE1
CMOVE2 B    *NEXT

 

 

The code from MVPMS to MVPMS1 is the helper ALC. It is not part of the threaded code. It is called by CMOVE , which is my first test on my way to including MOVE and VMOVE . The fbForth workspace registers are as follows:

  • TEMP0 – TEMP7 are R0 – R7
  • SP = R9 and is the stack pointer
  • LINK = R11
  • NEXT = R15

I suspect that all of the words in the kernel that invoke CMOVE fail; but, I don't know this for a fact. All I know is that as soon as I replace the self-contained word for CMOVE with the above code, the system goes off into the weeds when it comes up.

 

Below is FILL , one of the words that uses CMOVE followed by its high-level text equivalent:

 

 

*** FILL ***
       DATA L109F
L10A0  DATA >8446,>494C,>4CA0
FILL   DATA DOCOL,SWAP,TOR,OVER,CSTORE,DUP,ONEP
       DATA FROMR,ONEM,CMOVE,SEMIS
*
: FILL  ( addr cnt b --- )
    SWAP >R OVER C! DUP 1+ R> 1- CMOVE
;

 

 

I am guessing that somehow LINK (R11) is getting trashed; but, I can't see how. At this point, I don't see it as worth the trouble to track it down; but, I just might.

 

Upon startup, COLD references it twice. Other words in the kernel that use it are WORD , ID. , FILL and BLOAD .

 

...lee

 

Link to comment
Share on other sites

Hoo-Boy—it was fortuitous I chose FILL as an example of a kernel word using CMOVE . If you look at the code for FILL and play computer with it, you can see that FILL actually relies on the overlap-UNsafe behavior of CMOVE ! :-o It puts the fill character into the first byte of the range and calls CMOVE with

   address address+1 count-1 CMOVE 

to finish the copy. CMOVE dutifully copies into the next byte what was just copied into the previous byte until it gets to the end of the range.

 

I suppose this behavior could be part of the reason for the failure reported in my last post, and I still may try to track it down; but, I should probably abandon the effort to make CMOVE , MOVE and VMOVE overlap safe because of this side effect. I will, however, pursue it with CPYBLK . I just need to devise a clever way to check for the equality of two strings.

 

...lee

Link to comment
Share on other sites

How much user RAM will be available with fbForth on a 48K system? I ran into significant memory constraints with Core War using TI Forth. One of the advantages of Turbo Forth is that one has access to the entire RAM expansion for a program.

 

It's a very similar problem with fbForth as with TI Forth until I hoist the code into a cartridge, which won't be easy, but is my plan if I live long enough! :P fbForth 0.92 has 16066 bytes in high memory between HERE and the bottom of the stack, with the minimal system loaded. There is a 682-byte segment in low memory from the end of the fbForth block buffers and utilities to the bottom of the return stack. You need to be careful encroaching on the return stack. All of the system synonyms and some other basic stuff are part of the resident dictionary, now. There is a 9338-byte chunk of VRAM free and the capability of 3 open files (including the blocks file) in all text/graphics modes except for bitmap mode. Bitmap mode has VRAM pretty much stuffed with the capability of having 1 open file besides the blocks file. You can have more files than 3 open at once in non-bitmap modes, but each additional file grabs 518 bytes of upper VRAM—just check 8370h for the highest VRAM location (byte) available.

 

I plan to take a look at the graphics primitives sometime to see if I can condense them. They grab quite a lot of space when they are loaded.

 

One thing that I won't be able to change with a cartridge is the buffer space in low RAM reserved for block buffers. Each block takes 1028 bytes and there are 5 for a total of 5140 bytes. This number is actually user-controllable in both fbForth and TI Forth with the user variable, LIMIT$. You can subtract as many multiples of 1028 from the contents of LIMIT$ as you want, as long as you leave some space. I don't think I would go lower than 2 buffers. More is better with disk systems. Once your program code is loaded, however, you could co-opt all of the Forth buffer space in low RAM, as long as you don't need any more blocks from disk. Your first disk access for a Forth block after running your program, however, will grab the buffer space back that exists between FIRST and LIMIT . FIRST and LIMIT are constants that put on the stack the contents of FIRST$ and LIMIT$ , respectively.

 

...lee

Link to comment
Share on other sites

I coded a string comparison word, SCMP , to compare 2 strings and leave a flag for the result of the comparison:

  • -1, first string is less;
  • 0, the strings are exactly equal;
  • +1, second string is less.

I only needed to discover whether the strings are equivalent for CPYBLK ; but, I figured I might as well make it more useful. SCMP requires two string addresses on the stack. The byte-count of the string must be in the first byte. It takes up 98 bytes of dictionary space. I suppose I could call it STRCMP as in C; but, that might be misleading because it's a little different from its C cousin. With that name, it would consume an even 100 bytes. But, I digress. Here's the code for anyone who wants to help me make it more compact (@Willsy? @Vorticon? ):

*

: SCMP   ( straddr1 straddr2 --- -1|0|+1 )  
    OVER C@ OVER C@ OVER OVER - >R   ( dup addresses, get strlens and dup; take diff and push to return stack
    MIN 1+ 0 SWAP 1 DO  ( get min strlen; increment for limit; 0 flag start; swap with limit; start at 1
        DROP            ( drop 0 flag)
        OVER I + C@     ( get next char of str1)
        OVER I + C@ -   ( get next char of str2 and take diff)
        DUP IF LEAVE THEN  ( dup it and leave loop if not equal)
    LOOP
    R> OVER 0=      ( pop cnt diff from return stack; copy loop result; see if 0
    IF              ( it's 0, so OR with cnt diff for final answer)
        OR
    ELSE            ( it's not 0, so drop cnt diff )
        DROP
    THEN
    SWAP DROP SWAP DROP  ( get rid of leftover str1 and str2 pointers, leaving only comparison flag)
;

*

...lee

Link to comment
Share on other sites

I had multiple cracks at it, but I couldn't improve on 98 bytes.

However, upon trying your code, on TF, the output doesn't match the stack signature. In the event that the strings are not equal, I get the *difference* between the characters.

Tested thus:



: str1 ( --addr) s" hello" drop 1- ;

: str2 ( --addr) s" hellO" drop 1- ;
: SCMP   ( straddr1 straddr2 --- -1|0|+1 )

    OVER C@ OVER C@ OVER OVER - >R

    MIN 1+ 0 SWAP 1 DO

        DROP

        OVER I + C@

        OVER I + C@ -

        DUP IF LEAVE THEN

    LOOP

    R> OVER 0= IF

        OR

    ELSE

        DROP

    THEN

    SWAP DROP SWAP DROP

;

 

str1 str2 SCMP .

If you have COUNT defined as a system word you may be able to tidy the start of the definition up.

COUNT is:

: COUNT ( addr -- addr+1 len)

DUP 1+ SWAP C@ ;
Link to comment
Share on other sites

 

I had multiple cracks at it, but I couldn't improve on 98 bytes.

 

However, upon trying your code, on TF, the output doesn't match the stack signature. In the event that the strings are not equal, I get the *difference* between the characters.

 

Tested thus:

...

 

 

Yeah, I didn't check it as thoroughly as I thought. Maybe I should just indicate the stack signature this way: ( straddr1 straddr2 --- f<0|f=0|f>0 ). That is, however, not so neat and smacks of expediency, doesn't it?—but, it is shorter than the alternative.

 

 

 

If you have COUNT defined as a system word you may be able to tidy the start of the definition up.

...

 

It is. I'll have a look.

 

...lee

Link to comment
Share on other sites

 

 

Yeah, I didn't check it as thoroughly as I thought. Maybe I should just indicate the stack signature this way: ( straddr1 straddr2 --- f<0|f=0|f>0 ). That is, however, not so neat and smacks of expediency, doesn't it?—but, it is shorter than the alternative.

 

 

You want to add SGN at the end to get the sign of the value, ala TI BASIC. Not sure if fbForth has that as a built-in word. TF doesn't.

 

I define it as follows:

: SGN ( n -- -1|0|+1) dup 0< SWAP 0> - ;
Edited by Willsy
  • 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...