Jump to content
IGNORED

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


Lee Stewart

Recommended Posts

Because this first cartridge version is about making it as easy on myself as possible, I suppose I could put the two fbForth resident vocabulary words, FORTH and ASSEMBLER , into the A000h+ space to avoid modifying them at this time. The reason for a future mod is that they harbor space that gets modified.

 

...lee

Link to comment
Share on other sites

I found something on another part of Atari Age that you Forth guys might like. It was originally posted by: ThumpNugget

on August of 2010 the entire issue of Byte magazine - The Forth Language

...

 

Thanks! I will definitely take a look.

 

The cover art is the same as what appeared the next year on the cover of R. G. Loeliger's Threaded Interpretive Languages, published by BYTE Books and hyped on p. 134 of this issue of BYTE. I bought the book in 1983 or 1984 after getting TI Forth from the Washington, DC Area Users Group.

 

...lee

Link to comment
Share on other sites

fbForth minutiae—

 

I am attempting to make the ALC of the resident dictionary more readable and easier to maintain. I have continued to use the original TI Forth format, but I'm thinking of changing it for the reasons stated. Here is the current code snippet for EMPTY-BUFFERS , one of the longest word names in fbForth:

*
*** EMPTY-BUFFERS ***
       DATA L10FC
L10FE  DATA >8D45,>4D50,>5459,>2D42,>5546,>4645,>52D3
EMPTYB DATA DOCOL,FIRST,LIMIT,OVER,SUB,ERASE,FLUSH
       DATA FIRST,USE,STORE,FIRST,PREV,STORE,SEMIS

The name field (labeled L10FE for the above ALC) is complicated by bit markers that are part of the leading and trailing bytes, which makes it difficult to use ALC's TEXT directive in a clear manner. I think the following may be as clear as I can get:

*
*** EMPTY-BUFFERS ***
       DATA L10FC
L10FE  DATA >8000->2000+>0D00+' E','MP','TY','-B','UF','FE','RS'+>0080
EMPTYB DATA DOCOL,FIRST,LIMIT,OVER,SUB,ERASE,FLUSH
       DATA FIRST,USE,STORE,FIRST,PREV,STORE,SEMIS

The first DATA word of the name field has 3 additional terms, viz., markers (in this case, +>8000), ->2000 (corrects space in first byte to null) and +>0x00 (x = character count always in first byte). The last DATA word of the name field has only the >0080 marker. This format is a little messy, but it does allow for fair readability of the source code.

 

Anybody have a better idea?

 

...lee

Link to comment
Share on other sites

Yeah! Junk the format and change FIND et al to use the new format!

TF's format is okay I think. Here's the guts of an old datasheet that I put together back in 2009 describing it:

 

The dictionary in TurboForth is a linked list, with the first entry in a
dictionary entry being a pointer to the *previous* entry.
The dictionary is searched (by FIND) from the most recent entry to the first
entry, i.e the dictionary is searched in reverse order.
The first entry in a dictionary entry is a pointer to the previous dictionary
entry. It is a 16-bit word.
The next 16-bit word in the dictionary entry contains:
    * Length of the name of the word (bits 12 to 15)
    * Block number that the word is defined in (bits 2 to 11)
    * Hidden flag (bit 1)
    * Immediate flag (bit 0)
   
    as seen below:
 0                              15
 MSB                           LSB
  x x x x x x x x x x x x x x x x
  | | ~~~~~~~~~~~~~~~~~~~ ~~~~~~~
  | |          |             |
  | |     block number     length
  | hidden
  immediate
The block number is encoded into the word by HEADER during LOADing of a block.
This allows the word to be easily located (if it has been loaded) by use of the
word WHERE - e.g. WHERE WILLS
Note that since the name of the word can only be 4 bits, the maximum length of
a word is 15 characters.
The next entry in the dictionary entry is the actual text of the word name.
One byte per character, in normal ASCII. The word is padded with a space or zero
if the word is an odd length.
The next entry is the CFA (Code Field Address). This is a pointer to the machine
code that will be executed when the word is executed. For primitive words, the
value of the word will be address of the word plus 2 (i.e it points to the word
immediately following it). For Forth (i.e. high level) words it points to DOCOL.
If the following words were entered at the keyboard immediately after booting:
(i.e RAM is empty)
: MARK ;
: WILLS ;
The dictionary entries would look like this:
   Address  Value
   --------------
 +-> A302   7F04   * POINTER TO PREVIOUS WORD IN CART ROM
 |   A004   0004   * LENGTH OF THE NAME 'MARK'
 |   A006   4D     M
 |   A007   41     A
 ^   A008   52     R
 |   A009   4B     K
 |   A00A   8320   * POINTER TO DOCOL IN PAD RAM
 |   A00C   832E   * POINTER TO EXIT IN PAD RAM
 |
 +-< A00E   A302   * POINTER TO PREVIOUS DICTIONARY ENTRY
     A010   0005   * LENGTH OF WORD 'WILLS'
  A012   57     W
  A013   49     I
  A014   4C     L
  A015   4C     L
  A016   53     S
  A017   00     * PAD TO EVEN ADDRESS
  A018   8320   * POINTER TO DOCOL IN PAD RAM
  A01A   832E   * POINTER TO EXIT IN PAD RAM
Edited by Willsy
Link to comment
Share on other sites

I have some equates set up in the TF source for immediate and hidden words, which makes things quite readable. Here's the word --> which is immediate:

 

 

; --> ( -- )
; loads the next block
nblkh   data blkh,immed+3
        text '--> '
nblk    data docol
        data lit,lstblk,fetch,plus1,block,tib_,store,in_,store0
        data exit
;]     
Link to comment
Share on other sites

 

Yeah! Junk the format and change FIND et al to use the new format!

 

TF's format is okay I think. Here's the guts of an old datasheet that I put together back in 2009 describing it:

 

Thanks for the suggestion; but, I really cannot modify the format of the fbForth dictionary. That part I really do need to keep compatible with TI Forth. Your suggestions would be too big a change, I'm afraid.

 

In fbForth/TI-Forth, there is a terminator bit (80h) at the beginning of the length byte and at the beginning of the last name byte (the last character of the name or a space to pad to an even number of bytes).

 

So, there are four differences between the dictionaries of fbForth and TurboForth:

  1. fbForth does not track the originating block except at LOAD time for error assistance.
  2. There are terminator bits (80h) in the first and last name bytes.
  3. The length byte is, well, one byte versus TurboForth's one word (2 bytes). Consequently, with 3 marker bits (terminator, precedence [immediate], smudge [hidden]), names can be 31 characters long.
  4. The label field points to the previous word's name field instead of the previous word's label field as with TurboForth.

I was, rather, looking for a neater way to present the name field to a reader of the source code. I fear I have found it. |:)

 

...lee

Link to comment
Share on other sites

Here's my last attempt with the slight improvement of mnemonics for the marker bits and space correction:

TERMHI EQU >8000       ; terminator bit for high byte of word
SMDGHI EQU >2000       ; smudge bit for high byte of word (same as space character)
TERMLO EQU >0080       ; terminator bit for low byte of word

*** EMPTY-BUFFERS ***
       DATA L10FC
L10FE  DATA TERMHI-SMDGHI+>0D00+' E','MP','TY','-B','UF','FE','RS'+TERMLO
EMPTYB DATA DOCOL,FIRST,LIMIT,OVER,SUB,ERASE,FLUSH
       DATA FIRST,USE,STORE,FIRST,PREV,STORE,SEMIS

Note that the space correction has the same value as the smudge bit.

 

Using the same mnemonics as above, here is another possibility:

*** EMPTY-BUFFERS ***
       DATA L10FC
L10FE  BYTE TERMLO+13         ; <--makes character count easier to read
       TEXT 'EMPTY-BUFFER'
       BYTE 'S'+TERMLO
EMPTYB DATA DOCOL,FIRST,LIMIT,OVER,SUB,ERASE,FLUSH
       DATA FIRST,USE,STORE,FIRST,PREV,STORE,SEMIS

I still think the first one is easier to read, even with the aggravating punctuation, because the second one will always have the last character of any odd-length name on the next line, unless I can figure out a way to modify the last byte during assembly.

 

...lee

Link to comment
Share on other sites

Oops! The length of the name field line with the DATA directive in my last post is 13 characters too long for assembly with the TI Editor/Assembler. Does anybody know the limit for Asm994a? @Willsy? @Tursi? @InsaneMultitasker? @matthew180? Anyone else?

 

...lee

 

It works fine in Asm994a except that Asm994a does not like the first expression AT ALL! :-o If I make the ' E' the first term, it works!? Though this is a puzzle I would like answered, I actually stumbled upon a better solution that does not need the leading space coupled with its awkward removal:

TERMBT EQU  >80       terminator bit
PRECBT EQU  >40       precedence (immediate) bit
SMDGBT EQU  >20       smudge (hidden) bit
LSHFT8 EQU  >100      multiplier for left shift of 8 bits (1 byte) 
*
*
*** EMPTY-BUFFERS ***
       DATA L10FC
L10FE  DATA 13+TERMBT*LSHFT8+'E','MP','TY','-B','UF','FE','RS'+TERMBT
EMPTYB DATA DOCOL,FIRST,LIMIT,OVER,SUB,ERASE,FLUSH
       DATA FIRST,USE,STORE,FIRST,PREV,STORE,SEMIS

The first expression now has the decimal character count for the name as the first entry followed by the addition of any marker bits, all of which get shifted into the left byte with "*LSHFT8" just before the addition of the first character of the name. As far as I can tell, EMPTY-BUFFERS at 13 characters is, indeed, the longest word in fbForth, so all of the high-level Forth words in the resident dictionary's ALC source file will assemble successfully with the name field on one reasonably readable line. At the very least, it makes the ALC eminently easier to maintain!

 

...lee

  • Like 1
Link to comment
Share on other sites

I think that's the best you'll do with that dictionary format. It's more readable than before!

 

You'll still need to use comments to separate your code, as you have above:

 

*** EMPTY-BUFFERS ***

 

Purely so that you can search for the bloody things using the search function in your text editor! :D

  • Like 1
Link to comment
Share on other sites

fbForth/TI-Forth compatibility issue—

 

I am currently puzzling over how to manage the fbForth 40/80-column and 64-column editors in cartridge space. For the most part, I plan to re-write them in ALC because I want to run them from cartridge ROM and leaving them coded in high-level Forth won't let me do that with the limited space in the dictionary bank. The reason I say "puzzling over..." is that there are a handful of words used only by the 64-column editor as fbForth is supplied; but, they are detailed in the manual, which makes them visible to users and thus available for user programming. I am inclined to recode them in pure ALC, which would make them unavailable to users and, consequently, would break existing code using them. I've never seen any TI Forth programs that use them; but, that doesn't mean they don't exist. I suspect this is a non-issue given the less than overwhelming presence of extant fbForth/TI-Forth programmers. Unless someone can disabuse me of my conclusion, I think I will recode them. The relevant words are

TCHAR      <--array of tiny 3x7 characters in a 4x8 matrix
SMASH      <--formats a line of tiny characters
CLINE      <--displays a line of tiny characters
CLIST      <--displays an entire Forth block as tiny characters

@Willsy? @jacquesg? @JonnyBritish? @Vorticon? @Rod Van Orden? Anyone?

 

...lee

Edited by Lee Stewart
Link to comment
Share on other sites

Have you considered just copying pre-compiled Forth code from ROM (any bank, it doesn't matter) to RAM at HERE and then just executing that code?

 

TurboForth V1.1 does exactly that with the word WORDS. I couldn't fit it in bank 0 (where all the Forth coded stuff lives) so I just put it in bank 1. In bank 0, I have a dictionary entry for WORDS which branches into bank 1. In bank 1, the Forth code (in bank 1) is copied into RAM at HERE and then it returns to bank 0. The remainder of the code in bank 0 just branches to the code that was just copied into RAM. HERE isn't changed, so the next code to be compiled gets compiled over it, which is no problem at all. If you subsequently execute WORDS again it gets copied to RAM again, at the new address of HERE.

 

You can see it in action in TF V1.1. It's built in to Classic99 so: Open the debugger. Start TF and hold down the ENTER key to bypass disk boot. Now set the debug CPU window to A2EA and type WORDS. You'll see the Forth code get dumped into RAM.

 

It sounds counter-intuitive to put Forth code into bank 1, but you can do it just fine. Recall that the compiled Forth code is just a list of addresses. We write Forth code as DATA statements:

 

DATA drop,swap,nip,plus1 etc

 

These are just symbolic addresses. All defined in bank 0. Nothing at all stopping you from putting them in bank 1. You just can't execute them from bank 1 (because all the Forth CFAs would have been paged out of memory).

 

HTH

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

Have you considered just copying pre-compiled Forth code from ROM (any bank, it doesn't matter) to RAM at HERE and then just executing that code?

...

 

No, I hadn't thought of that. Wow! This is the kind of idea I need. :thumbsup: Thanks.!

 

If I use HERE, I will need to be careful that none of the words in the address list uses HERE for temporary operations! I will probably hoist fbForth's low-level system support out of low RAM and put system variables there (much less space than system support). The end of that space would make a good spot for your suggestion. It is exactly analogous to HERE in fbForth in that there is nothing permanent between HERE and the parameter stack and the return stack grows toward it from high low-RAM memory just as the parameter stack grows toward HERE in high RAM. We could call it RHERE, perhaps!

 

BTW, you're talking about TurboForth v1.2, are you not?

 

...lee

Link to comment
Share on other sites

Hi Lee

 

No. TF V1.1 copies WORDS into RAM (at HERE) to execute it, but V1.2 does not (I managed to make quite a few enhancements in V1.2 and remove redundancy, so much so that I had room to put WORDS back where it really belonged).

 

Glad you like the idea. Sounds like you've got a solution bubbling away. You could also use PAD or PAD plus some offset, though strictly speaking (at least in ANS and Forth-83, don't know about '79 and FIG) a system may not avail itself of PAD; it's reserved for user code. Nothing stopping you using PAD plus some offset though. Although your suggestion to use to place it somewhere near the end of low RAM sounds perfectly fine to me. Of course, where the execution of a word takes an unusual course of action, such as copying itself to RAM and running from there, it should be documented accordingly to alert programmers of potential pitfalls. YOur standard of documentation being what it is though, I don't anticipate a problem there!

Link to comment
Share on other sites

No. TF V1.1 copies WORDS into RAM (at HERE) to execute it, but V1.2 does not (I managed to make quite a few enhancements in V1.2 and remove redundancy, so much so that I had room to put WORDS back where it really belonged).

 

Aha! Still a good idea and I will definitely use it if I have to, especially for words like VLIST (fbForth's WORDS ) where the extra time for copying is not onerous.

 

Glad you like the idea. Sounds like you've got a solution bubbling away. You could also use PAD or PAD plus some offset, though strictly speaking (at least in ANS and Forth-83, don't know about '79 and FIG) a system may not avail itself of PAD; it's reserved for user code.

 

fbForth/TI-Forth words in the resident dictionary use PAD for ID. , <# , # , #> , and MESSAGE and, in addition, fbForth uses PAD for BFLNAME , DEFBF , MKBFL and USEBFL . However, none of the non-high-level-Forth-based system ALC uses PAD.

 

Of course, where the execution of a word takes an unusual course of action, such as copying itself to RAM and running from there, it should be documented accordingly to alert programmers of potential pitfalls.

 

If I take this course of action (likely), I may tell the user how to modify RHERE to reserve a small space that the system won't clobber. It all depends on how much space actually winds up available there when I'm done. I certainly don't want to leave the return stack with too little room!

 

Your standard of documentation being what it is though, I don't anticipate a problem there!

 

You're too kind. Thanks.

 

...lee

Link to comment
Share on other sites

Back to what to do with the 64-column editor—

 

I expect to code all of the 64-column editor into 1 bank without any use of high-level Forth within that bank except to get back to bank0 when done. If I become convinced that I need to retain the Forth definitions of TCHAR , SMASH , CLINE and CLIST , I will need to code stubs that will get to code in the 64-column editor bank that will return to bank0 immediately. but will not interfere with their use within the editor. I guess I could use a system variable to manage that without too much additional code. After all, I could commit an entire bank to one or both editors because I plan to use a 32KB cartridge (4 banks) or even a 64KB cartridge, if necessary!

 

...lee

  • Like 1
Link to comment
Share on other sites

My head hurts! I think I need to devise a small bank-switching test harness for all the different ideas I'm dreaming up for handling bank-switching etc. for my cartridge version of fbForth. I am thinking of ways to manage bank-switching faster than I can test them. I'm afraid I'll forget what I want to do. In some circumstances (like the 64-column editor!), I was hoping to avoid switching to invoking high-level Forth; but, I keep running into the need to do it. I probably shouldn't try to rewrite words in ALC every time I need one—mainly because they call other words! The rewrites start to get rather complicated.

 

I'm rambling; but, any suggestions are certainly welcome.

 

...lee

Link to comment
Share on other sites

Bank-switching code for 4 banks—

 

Here is my first attempt at 2 ALC routines for running routines in other banks and returning to the calling bank as well as 2 routines for running the payload of high-level Forth words in other banks, "returning" to bank0 and executing the next CFA (code field address) via B *NEXT:

 

 

 

********************************************************************
* General bank branching routine (32KB ROM, i.e., 4 banks) for a
* branch that is expected to return (not high-level Forth)---
*   --put in scratchpad or low RAM
*   --called by
*       BL    @BLBANK
*       DATA  dst_addr - >6000 + bank# in left 2 bits
*
BLBANK DECT R               ;reserve space on return stack (R14)
       MOV  @2(LINK),*R     ;push return address
       DECT R               ;reserve space on return stack
       MOV  @MYBANK,*R      ;push return bank (leftmost 2 bits)
       MOV  *LINK,LINK      ;copy destination bank address to R11
       MOV  LINK,CRU        ;copy it to R12
       ANDI LINK,>1FFF      ;mask off leftmost 3 bits to reveal address - >6000
       AI   LINK,>6000      ;make it a real address
       SRL  CRU,13          ;shift bank# into bits 1-2 of R12
       AI   CRU,>6000       ;make it a real bank-switch address
       CLR  *CRU            ;switch to destination bank
       B    *LINK           ;branch to destination address
*
********************************************************************
* General bank return routine (32KB ROM, i.e., 4 banks)---
*   --put in scratchpad or low RAM
*   --called by
*       B   @RTBANK
*
RTBANK MOV  *R+,CRU         ;pop return bank# from return stack to R12
       SRL  CRU,13          ;shift bank# into bits 1-2 of R12
       AI   CRU,>6000       ;make it a real bank-switch address
       MOV  *R+,LINK        ;pop return address from return stack
       CLR  *CRU            ;switch to destination bank
       B    *LINK           ;branch to return address
*
********************************************************************
* High-level-Forth bank branching routine (32KB ROM, i.e., 4 banks)
* for a branch that is not expected to return---
*   --put in scratchpad or low RAM
*   --called by
*       BL    @BLFRTH
*       DATA  dst_addr - >6000 + bank# in left 2 bits
*
BLFRTH MOV  *LINK,LINK      ;copy destination bank address to R11
       MOV  LINK,CRU        ;copy it to R12
       ANDI LINK,>1FFF      ;mask off leftmost 3 bits to reveal address - >6000
       AI   LINK,>6000      ;make it a real address
       SRL  CRU,13          ;shift bank# into bits 1-2 of R12
       AI   CRU,>6000       ;make it a real bank-switch address
       CLR  *CRU            ;switch to destination bank
       B    *LINK           ;branch to destination address
*
********************************************************************
* High-level-Forth bank "return" routine (32KB ROM, i.e., 4 banks)---
*   --put in scratchpad or low RAM
*   --called by
*       B   @RTNEXT
*
RTNEXT CLR  @6006           ;switch to bank0
       B    *NEXT           ;branch to next CFA (in R15) 

 

MYBANK in line 12 needs some explanation. It will be at the same address in all 4 banks and will have the inverted bank# in the leftmost 2 bits. The following table shows the value of MYBANK for each bank:

 

Bank Select MYBANK

---- ------ ------

0 >6006 >C000

1 >6004 >8000

2 >6002 >4000

3 >6000 >0000

 

When MYBANK is actually used to return to the calling bank, it is shifted into bits 1-2 instead of 0-1 so the result will be 6, 4, 2 or 0 instead of 3, 2, 1 or 0—well, 0 doesn't change, of course. >6000 is added before the bank switch is effected. Hopefully, there is enough information in the code above that will allow you to understand how the routines work so you can evaluate them to make suggestions for better code.

 

The DATA directive following the BL *... is composed of the destination bank# as per MYBANK and the destination address offset from >6000 to leave bit 13 as 0. The address will be corrected before the branch is taken. This device also allows for 8 banks (64KB ROM) because bits 13-15 can all three be used without interfering with the address offset.

 

I need to proceed very carefully so as not to disturb how fbForth's interrupt processing works—especially, if I execute the low-level system support code from ROM, which I'm hoping to do. I will be putting a few critical routines in low RAM along with all system variables, buffers and workspaces that I can't put in scratchpad RAM.

 

...lee

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

Looks good, with nice clean concise code. No suggestions (currently) in terms of improving the code. I'm left puzzled by the routine BLFRTH, though. In what context is it used?

 

Thanks. BLFRTH is an elaboration of the BANK1 routine in your TurboForth that will allow high-level Forth code stubs in bank 0 to call payload code in more than one bank. Obviously, I will not need anything other than BANK1 code if I can fit all of the payload guts of all words into one bank.

 

...lee

  • Like 1
Link to comment
Share on other sites

Well—if I figured this out correctly, SWAP takes 162 clock cycles and the >R ... R> combo takes 200. Just in case anyone cares to check my calculations, the relevant ALC for the three words follows:

*** SWAP ***
       MOV  *R9,R1
       MOV  @2(R9),*R9
       MOV  R1,@2(R9)
       B    *R15

*** >R ***
       DECT R14
       MOV  *R9+,*R14
       B    *R15

*** R> ***
       DECT R9
       MOV  *R14+,*R9
       B    *R15

That would make SWAP about 20 % faster. So, SWAP it is! At least, in cases where the two constructs are effectively equivalent.

 

...lee

Link to comment
Share on other sites

Not to mention the fact that >R and R> *each* incur a round-trip through the inner interpreter.

 

...which is three additional instructions for "B *R15", albeit in 16-bit RAM. That brings up another point, making it worse than I thought, viz., the formula for instruction timing in the TMS9900 Microprocessor Data Manual does not calculate the effect on timing caused by the CPU's fetching the next 2-byte word of memory on the 8-bit bus or the same circumstance for source and destination memory accesses! All of the register accesses in this instance are 16-bit; but, the indirect memory accesses through those registers are not. In another thread on this forum, @Tursi et al. discussed how to include those timing hits in the timing calculation, but I forget where. Now, my head really hurts!

 

...lee

Edited by Lee Stewart
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...