6502.org Forum  Projects  Code  Documents  Tools  Forum
It is currently Thu Nov 21, 2024 12:07 pm

All times are UTC




Post new topic Reply to topic  [ 354 posts ]  Go to page Previous  1 ... 7, 8, 9, 10, 11, 12, 13 ... 24  Next
Author Message
PostPosted: Mon Dec 07, 2020 11:16 pm 
Offline

Joined: Fri May 05, 2017 9:27 pm
Posts: 895

Fleet Forth has a word, ?MEM , to check if the amount of memory to ALLOT is available. At one point I defined ?MEM like this:
Code:
: ?MEM  ( N -- N )
   MRU OVER PAD + U< 0EXIT
   ABORT" NO MEMORY" ;

0EXIT was used to exit the word immediately, if there was no error, to avoid the overhead of (ABORT") .
This was the source for (ABORT")
Code:
: (ABORT")  ( F -- )
   IF
      WHERE CR R@ S?
      ABORT
   THEN
   R> COUNT + >R ;

Including docolon, that is seven passes through NEXT just to check for an error and keep going. Rather than using code like the above code for ?MEM to avoid that overhead in some places, I decided to make (ABORT") faster. Here is the source for the new (ABORT") .
Code:
CODE (ABORT")  ( F -- )
   0 ,X LDA,  1 ,X ORA,
   0= IF,
      IP )Y LDA,  SEC,
      IP ADC,  IP STA,
      CS IF,  IP 1+ INC,  THEN,
      POP JMP,
   THEN,
   >FORTH
   WHERE CR R@ S?
   ABORT ;
   -2 ALLOT

The new version of (ABORT") is only nine bytes larger than the older version.


Top
 Profile  
Reply with quote  
PostPosted: Mon Dec 07, 2020 11:34 pm 
Offline
User avatar

Joined: Fri Aug 30, 2002 1:09 am
Posts: 8543
Location: Southern California
UNUSED is pretty standard for your ?MEM, and is even in ANS Forth. It can be defined as:

Code:
: UNUSED        ( -- #bytes )   \                                   ANS_CORE_EXT
   LIMIT  HERE  -       ;       \ Leaves room for a max-length comment line from
                                \ TIB , HLD , and a max-length string in PAD .

I did it as a primitive in my '816 Forth, this way:
Code:
        HEADER "UNUSED", NOT_IMMEDIATE   ; ( -- bytes )
UNUSED: PRIMITIVE           ; Tells how much RAM is left to expand the dictionary
        LDA     #LIMITdata  ; I omitted CLC since 1 byte either way won't matter.
        SBC     DPdata
        JMP     PUSH


_________________
http://WilsonMinesCo.com/ lots of 6502 resources
The "second front page" is http://wilsonminesco.com/links.html .
What's an additional VIA among friends, anyhow?


Top
 Profile  
Reply with quote  
PostPosted: Tue Dec 08, 2020 12:36 am 
Offline

Joined: Fri May 05, 2017 9:27 pm
Posts: 895

?MEM is different. It does not return the amount of bytes free. Given a number of bytes, it performs the test to see if there is enough memory available. It takes the number of bytes and if that number is available, it returns that same number. This is why, in a previous post, I called it "transparent".
For example, here is the source for Fleet Forth's ALLOT .
Code:
: ALLOT  ( N -- )
   ?MEM  DP +! ;

ALLOT was previously defined without memory checking:
Code:
: ALLOT  ( N -- )
   DP +! ;

This seems more convenient to me than picking a "reasonable" amount of memory that needs to be available in CREATE or the need to check memory if I need to ALLOT some. If there is not enough memory, HERE does not change and the system aborts.
So far ?MEM is only used in two other places:
" places a string at PAD while interpreting or compiles (") and inlines the string while compiling.
FBUF in the editor.
The new and improved version of (ABORT") wasn't just to remove 0EXIT from ?MEM , but to make all uses of (ABORT") faster when there is no error to report.


Top
 Profile  
Reply with quote  
PostPosted: Tue Dec 08, 2020 1:31 am 
Offline
User avatar

Joined: Fri Aug 30, 2002 1:09 am
Posts: 8543
Location: Southern California
My UNUSED is also used in , (comma). My ALLOT uses it too, but in looking at it again now, I think I could simplify it.

_________________
http://WilsonMinesCo.com/ lots of 6502 resources
The "second front page" is http://wilsonminesco.com/links.html .
What's an additional VIA among friends, anyhow?


Top
 Profile  
Reply with quote  
PostPosted: Tue Dec 08, 2020 1:44 am 
Offline

Joined: Fri May 05, 2017 9:27 pm
Posts: 895
GARTHWILSON wrote:
My UNUSED is also used in , (comma). My ALLOT uses it too, but in looking at it again now, I think I could simplify it.


Wouldn't your , (comma) use ALLOT ?
Code:
: ,  ( N -- )  HERE 2 ALLOT ! ;
: C,  ( C -- )  HERE 1 ALLOT C! ;

It's funny. I intended my post to be about (ABORT") with ?MEM as an example.


Top
 Profile  
Reply with quote  
PostPosted: Tue Dec 08, 2020 2:59 am 
Offline
User avatar

Joined: Fri Aug 30, 2002 1:09 am
Posts: 8543
Location: Southern California
, uses 2 DP +! instead of ALLOT. Yeah, 2 ALLOT would be more efficient.

_________________
http://WilsonMinesCo.com/ lots of 6502 resources
The "second front page" is http://wilsonminesco.com/links.html .
What's an additional VIA among friends, anyhow?


Top
 Profile  
Reply with quote  
PostPosted: Sat Dec 12, 2020 1:20 am 
Offline

Joined: Fri May 05, 2017 9:27 pm
Posts: 895

I've talked about Fleet Forth's words BLOCK and BUFFER in other posts and I thought it would be good to go into them in a bit more depth here.
First is a quick look at R/W , Fleet Forth's block read/write word. Depending on the block number requested, R/W will either access a disk drive (for lower numbered blocks) or access RAM (for higher numbered blocks). R/W is not a deferred word, but the word to access the disk, DR/W , and the word to access RAM, RR/W , are.
R/W , and therefore DR/W and RR/W , take the address of a buffer, a block number, the read/write flag, and a count. This differs from Blazin' Forth. Blazin' Forth's R/W only takes three parameters, the address of a buffer, a block number, and the read/write flag. Fleet Forth's R/W was written to require a count to support a general version of INDEX . INDEX is a handy utility word that, given a starting block number and an ending block number, displays the first line for each block in that range. Ideally, INDEX only reads in one line as well (64 bytes). Fleet Forth's INDEX does so it only needs 64 bytes of memory at PAD. Since it uses R/W directly, it will work with different drive access methods, for different drive types, just like BLOCK and BUFFER .

Next is a look at the buffer table. The inspiration for the table came from Blazin' Forth. Fleet Forth, like Blazin' Forth, can support a variable number of blocks, configurable on the fly. There should never be less than one buffer, unless there will be no block access. There can be as many buffers as will fit in memory. The table consists of one entry per buffer plus one, the phantom entry. The entries are arranged in order from the phantom entry then the most recently used to the least recently used.
There are two system values used to manage the table, MRU and LRU. MRU points to the phantom entry. LRU points to the entry for the least recently used block. LRU is also headerless, since it is only used in the Forth kernel. Each entry consists of six cells: the block number, the buffer address, and an update flag. R/W does not update the order of the entries in the table. BLOCK and BUFFER handle that. The following shows four blocks with the resulting 5 table entries.
Code:
MRU:   5 | $C800 | FLAG
       5 | $C800 | FLAG
      42 | $C000 | FLAG
     137 | $C400 | FLAG
LRU:   1 | $CC00 | FLAG

A quick reminder that Fleet Forth is an ITC Forth for the Commodore 64 and when a CODE word is executed, the Y-register is set to zero. BLOCK starts out as a CODE word.
Code:
CODE BLOCK  ( BLK -- ADR )
   DEY,
   BLK/BUF STY,
   ' MRU >BODY    LDA,  N    STA,
   ' MRU >BODY 1+ LDA,  N 1+ STA,
   6 # LDY,  N )Y LDA,  0 ,X CMP,
   0= IF,
      INY,  N )Y LDA,  1 ,X CMP,
      0= IF,
         INY,  N )Y LDA,  PHA,
         INY,  N )Y LDA,
         PUT JMP,
      THEN,
   THEN,

The first thing BLOCK does is decrement the Y-register and store it's value for (possible) later use. BUFFER is a bodiless CODE word with a code field that points one byte into the body of BLOCK.
BLOCK then checks to see if the requested block is the most recently used. If so, it replaces the block number with the address of the buffer used for that block. Up to this point BLOCK behaves like a CODE word and jumps back to NEXT by way of PUT and we're done.

If the requested block is not the most recently used, the jump to PUT is branched over.
Code:
   >FORTH
   #BUF 1+ 2
   ?DO
      DUP I >BT @ =
      IF
         DROP I UNLOOP
         AHEAD CS>A
      THEN
   LOOP

>FORTH causes a transition to high level code. The ?DO LOOP checks if the requested block is in the table. If it is, the index into the table is saved on the stack (not to be confused with Forth's word INDEX ), the loop parameters are discarded and AHEAD branches out of the loop to elsewhere in BLOCK .
Code:
      LRU 2+ 2+ @
      IF
         LRU 2@ 0 B/BUF R/W
         LRU 2+ 2+ OFF
      THEN

If the requested block is not in the table, the least recently used block is written to disk (or ram) if its update flag is true. The update flag is not cleared until after the block is written in case the write fails.
Code:
      BLK/BUF C@
      IF
         LRU ON
         LRU 2+ @ OVER 1 B/BUF R/W
      THEN
      LRU ! #BUF

The value of the Y-register that was saved earlier is fetched to see if this code was called as BLOCK or BUFFER . If this is BLOCK , if the read fails, the contents of the buffer will not be valid (most likely) so the entry for the block number of the least recently used block is set to TRUE (-1) to indicate an unused buffer. The new block is read in to the least recently used buffer.
Both BLOCK and BUFFER then store the requested block number in the block number entry for the least recently used block/buffer and leave the index to the entry for the least recently used block/buffer on the stack.
Code:
   A>CS THEN
   DUP >BT MRU 6 CMOVE
   MRU 1 >BT ROT 6 * CMOVE>
   MRU 2+ @ ;

The branch out of the ?DO LOOP branches to this section of code. At this point one of two index values will be on the data stack.
If the requested block number was found in the table the index to the table entry with the desired block number is on the data stack.
If it was not found in the table the index of the least recently used block/buffer is on the data stack.
This table entry is copied to the phantom entry. The phantom entry and all entries above the one with its index on the stack are now slid down by one entry.
If a block that was not in the table, such as 123, was requested, this is what it would look like:
Code:
MRU: 123 | $CC00 | 0
       5 | $C800 | FLAG
      42 | $C000 | FLAG
     137 | $C400 | FLAG
LRU: 123 | $CC00 | 0

Code:
MRU: 123 | $CC00 | 0
     123 | $CC00 | 0
       5 | $C800 | FLAG
      42 | $C000 | FLAG
LRU: 137 | $C400 | FLAG

If however a block that was in the table, such as 42, was requested, this is the result:
Code:
MRU:  42 | $C000 | FLAG
       5 | $C800 | FLAG
      42 | $C000 | FLAG
     137 | $C400 | FLAG
LRU:   1 | $CC00 | FLAG

Code:
MRU:  42 | $C000 | FLAG
      42 | $C000 | FLAG
       5 | $C800 | FLAG
     137 | $C400 | FLAG
LRU:   1 | $CC00 | FLAG




Top
 Profile  
Reply with quote  
PostPosted: Sat Dec 12, 2020 2:50 am 
Offline

Joined: Sun Apr 26, 2020 3:08 am
Posts: 357
Thanks for delving into BLOCK and BUFFER for Fleet Forth.

I was in the process of deciphering what they do and how they work in ProForth. ProForth is a Forth for the Prodos OS on an Apple II. ProForth also has 2 buffers laid out as blocks. A ProForth block is 1024 bytes which happens to also be 16 lines of 64 bytes. The same as Fleet Forth's line limit.

Because of the great similarities between ProForth and FleetForth, I have taken a great interest in a lot of your improvements for FleetForth, and hope to adapt many of them to ProForth.

Just a couple of quick questions.

How does FleetForth set a device # to know which device to read from or write to before using BUFFER and BLOCK?

Under ProDOS, the BLOCK parameter list contains a device #. So far this has been hard coded into ProForth. I would like to change that so I can load any text file from any slot/drive combination. Just seeing what else is out there for word primitives that may already exist. DR0 and DR1 were only meant to signify drives and were originally programmed for floppies. Under ProForth, they are not used at all but are still included in word definitions. What does the C64 have in the way of device types. I believe there was an add-on SD card for the C64 port? That would mean you would need a word under FleetForth to identify which Port to save to other than the floppy drive, would you not?

Is FleetForth reading text files as a sequential text file (one line at a time) or as a chunk of 1024 byte screen files? Proforth is set up to load 16 screens all at the same time and the text file is a fixed 16384 ($4000) = 16 screens of 1024 bytes. Then the screens are accessed with the LIST word from 0-15 and a screen is loaded using the LOAD word.

I hate this method and want to get away from it. Would simply like to use OPEN "textfile" and LOAD to load it.


Top
 Profile  
Reply with quote  
PostPosted: Mon Dec 14, 2020 11:13 pm 
Offline

Joined: Fri May 05, 2017 9:27 pm
Posts: 895

I do not recall an SD card for the C64. I do know that there were Ram Expansion Modules available for the C64 and C128. The module plugged into the expansion port (cartridge port). As for the disk drives, they were external units interfaced by a serial cable, a serial version of IEEE-488. The drives had two connectors for the serial cable so multiple units could be 'daisy chained' together. Device numbers 8 and up were typically for disk drives.

I think I've mentioned this elsewhere, Fleet Forth determines which drive to use for block access based on block number. Originally, only five drives were supported but it takes less code to support eight drives. Here a table showing which block numbers go to which drive.
Code:
Actual        | C64    | device
block range   | device | block range
              |        |
    0 - $0FFF |    8   | 0 - $0FFF
$1000 - $1FFF |    9   | 0 - $0FFF
$2000 - $2FFF |   10   | 0 - $0FFF
$3000 - $3FFF |   11   | 0 - $0FFF
$4000 - $4FFF |   12   | 0 - $0FFF
$5000 - $5FFF |   13   | 0 - $0FFF
$6000 - $6FFF |   14   | 0 - $0FFF
$7000 - $7FFF |   15   | 0 - $0FFF
$8000 - $FFFF |  REU   | 0 - $7FFF

Block numbers are unsigned. The code word R/W determines which device is used.
Code:
HEX
CODE R/W  ( ADR BLK# R/WF CNT -- )
   BEGIN,
      SEC,
      5 ,X LDA,  10 # SBC,
   CS WHILE,
      5 ,X STA,
      INY,  8 # CPY,
   0= UNTIL,
      ' RR/W SPLIT SWAP
      # LDA,  # LDY,
      (EXECUTE) 0= NOT BRAN,
   THEN,
   DRB STY,
   ' DR/W SPLIT SWAP
   # LDA,  # LDY,
   (EXECUTE) 0= NOT BRAN,
END-CODE

(EXECUTE) is a metacompiler label that points into EXECUTE . The two branches into the body of EXECUTE are always taken. A branch always instruction would have been nice but the C64 has the 6510 processor. No CMOS version.
Code:
HEX
CODE EXECUTE  ( ADR -- )
   0 ,X LDA,
   1 ,X LDY,
   INX,  INX,
   LABEL (EXECUTE)
   W 1+ STY,  W STA,
   0 # LDY,
   W 1- JMP,
END-CODE

Although the system will choose a specific drive for each range of 4096 blocks, not all blocks will be available.
For example, if device 8 is a Commodore 1541 drive, only 166 blocks are available. This means that the range of available blocks is not continuous. The range selected for the drives is a design consideration I made to facilitate copying blocks from one device to another while allowing a larger capacity drive to have more blocks available than what is possible with the 1541 drive.
The maximum REU size that is compatible with other C64 REU's is 16 megabytes or 16384 blocks. The 1764 REU had a capacity of 256 blocks and the 1750 REU had a capacity of 512 blocks.

For block numbers less than $8000, the deferred word DR/W is called otherwise the deferred word RR/W is called.

Fleet Forth has the helper words DR+ and RAM to help hide this, or at least make it so the exact numbers do not need to be known. RAM just adds $8000 to the number on the stack. One use for blocks in the REU is virtual memory.

DR+ takes two parameters, the block number and the drive to use. If I want to see an INDEX of the first 10 blocks on drive 9 I would type this:
Code:
0 9 DR+ 10 9 DR+ INDEX

Note: For drive 8, DR+ is not needed. This:
Code:
5 8 DR+ BLOCK

Is effectively the same as this:
Code:
5 BLOCK

I placed Fleet Forth's system loader in drive 9 and got an index while the base was 16 and then set it to decimal and got the index again. This is the log of the session. Both what I typed and the computer's responses are shown.
Code:
 OK
0 9 DR+ 10 9 DR+ INDEX
1000 ***********************************
1001 // LOAD BLOCK
1002 // 2ND LOAD BLOCK -- EMPTY
1003 // DONE? THRU
1004 // SYSTEM CONSTANTS
1005 // OPCODE OFFSET TABLE AND MODES
1006 // BOT SEC RP)
1007 // CPU0 & IMPLIED ADDRESSING
1008 // CPU1
1009 // W/OUT IMPLIED ADDRESSING MODE
100A // W/OUT IMPLIED ADDRESSING MODE
100B // ?EXEC NOT OFFSET BRANCHES
100C // THEN,
100D // AHEAD, IF, ELSE, WHILE, ELIF,
100E // BEGIN, AGAIN, UNTIL, REPEAT, BRAN,
100F // SUBR CODE ;CODE END-CODE
1010 // >FORTH >ASSEM
 OK
DECIMAL  OK
0 9 DR+ 16 9 DR+ INDEX
 4096 ***********************************
 4097 // LOAD BLOCK
 4098 // 2ND LOAD BLOCK -- EMPTY
 4099 // DONE? THRU
 4100 // SYSTEM CONSTANTS
 4101 // OPCODE OFFSET TABLE AND MODES
 4102 // BOT SEC RP)
 4103 // CPU0 & IMPLIED ADDRESSING
 4104 // CPU1
 4105 // W/OUT IMPLIED ADDRESSING MODE
 4106 // W/OUT IMPLIED ADDRESSING MODE
 4107 // ?EXEC NOT OFFSET BRANCHES
 4108 // THEN,
 4109 // AHEAD, IF, ELSE, WHILE, ELIF,
 4110 // BEGIN, AGAIN, UNTIL, REPEAT, BRAN,
 4111 // SUBR CODE ;CODE END-CODE
 4112 // >FORTH >ASSEM
 OK
CONSOLE

Fleet Forth does not store blocks in sequential files. Commodore sequential files are not random access. Fleet Forth, like Blazin' Forth, uses direct access to read four 256 byte disk sectors per Forth block. The sector mapping for the 1541 drive is compatible with Blazin' Forth. In other words, Fleet Forth can read blocks from a 1541 disk used by Blazin' Forth and vice versa.
I know that C64 Forth by Tom Zimmer used Commodore relative files for blocks and I may experiment with that approach one day.

To be able to access blocks on a disk, the drive must be opened with the word DOPEN . This word takes no parameters and leaves no parameters. It opens the disk on the current drive. The current drive is 8 upon startup or a cold start but can be changed with the word DRIVE . For example, if I wanted to access blocks on drives 8 and 9, I could do the following:
Code:
8 DRIVE DOPEN 9 DRIVE DOPEN

If I know that the current drive is drive 8, I could leave off the phrase '8 DRIVE'.
Before removing a disk from a drive, the drive must be closed with the word DCLOSE . DCLOSE also takes no parameters and leaves no parameters. As with opening a drive for block access, if I want to close a drive for block access, it needs to be the current drive. After the previous example drive 9 is the current drive.
Code:
8 DRIVE DCLOSE

Now 8 is the current drive again.

Fleet Forth maintains one or more buffers for blocks. I usually use four, but I sometimes use more when manipulating blocks. The buffers are right up against the memory limit specified by the system value LIMIT .
LIMIT can be lowered if the need arises. The buffer access table sits right up against the buffers. Although Fleet Forth reads blocks using direct access to read four Commodore disk sectors, it reads each block as a one kilobyte chunk. Fleet Forth only reads one block at a time and only writes one block at a time.
The following will change the number of block buffers.
Code:
n TO #BUF CONFIGURE

Since DR/W is a deferred word, it can be re-vectored to a new word to allow access to different hardware. It could even be re-vectored to a word that used Commodore relative files for blocks.

Since RR/W is also a deferred word, it can also be re-vectored. Instead of using a Commodore Ram Expansion Unit, it could be re-vectored to a word that uses a hard drive for the blocks in the range $8000 to $FFFF.
I hope this helps.


Top
 Profile  
Reply with quote  
PostPosted: Tue Dec 15, 2020 9:22 pm 
Offline

Joined: Sun Apr 26, 2020 3:08 am
Posts: 357
JimBoyd wrote:
[color=#000000]
I do not recall an SD card for the C64.


you can find it here.
https://www.thefuturewas8bit.com/shop/c ... w1EALw_wcB


Quote:
I think I've mentioned this elsewhere, Fleet Forth determines which drive to use for block access based on block number. Originally, only five drives were supported but it takes less code to support eight drives. Here a table showing which block numbers go to which drive.


This is a big thread and haven't read it from start to finish yet. But yes, I can see how that works.

Quote:
Although the system will choose a specific drive for each range of 4096 blocks, not all blocks will be available.
For example, if device 8 is a Commodore 1541 drive, only 166 blocks are available. This means that the range of available blocks is not continuous. The range selected for the drives is a design consideration I made to facilitate copying blocks from one device to another while allowing a larger capacity drive to have more blocks available than what is possible with the 1541 drive.
The maximum REU size that is compatible with other C64 REU's is 16 megabytes or 16384 blocks. The 1764 REU had a capacity of 256 blocks and the 1750 REU had a capacity of 512 blocks.


I have to think this through some more. This BLK, SCRN, LOAD and LINE method still seems quite cumbersome.


Quote:
To be able to access blocks on a disk, the drive must be opened with the word DOPEN . This word takes no parameters and leaves no parameters. It opens the disk on the current drive. The current drive is 8 upon startup or a cold start but can be changed with the word DRIVE . For example, if I wanted to access blocks on drives 8 and 9, I could do the following:
Code:
8 DRIVE DOPEN 9 DRIVE DOPEN



I would like to convert that to
OPEN filename


Quote:
Fleet Forth maintains one or more buffers for blocks. I usually use four, but I sometimes use more when manipulating blocks. The buffers are right up against the memory limit specified by the system value LIMIT .

LIMIT can be lowered if the need arises. The buffer access table sits right up against the buffers. Although Fleet Forth reads blocks using direct access to read four Commodore disk sectors, it reads each block as a one kilobyte chunk. Fleet Forth only reads one block at a time and only writes one block at a time.


I think four buffers is almost the minimum. I will need 2 buffers to OPEN files and another 2 buffers for working on those files. I see now I can't get away from all those buffers. The first buffer houses the error messages, but since am working on a 128kb machine, I have some room to spare and might make the errors part of the system.



Fleet Forth is quite large and a C64 only has 64 kb memory? Is memory constraints so tight that it would be difficult to use graphics or write large applications with Fleet Forth?


Top
 Profile  
Reply with quote  
PostPosted: Wed Dec 16, 2020 6:48 pm 
Offline

Joined: Sun May 13, 2018 5:49 pm
Posts: 255
IamRob wrote:
I think four buffers is almost the minimum. I will need 2 buffers to OPEN files and another 2 buffers for working on those files. I see now I can't get away from all those buffers. The first buffer houses the error messages, but since am working on a 128kb machine, I have some room to spare and might make the errors part of the system.
It's possible to do more than you think with a single buffer. I was also trying to figure out what the minimum number of buffers was for TaliForth2, and I ended up trying to make it work with a single buffer and it actually worked! I can load a screen that has other LOAD or THRU commands in it. The trick is to put back whatever was in the buffer if it was in use before (and you can check that by looking in BLK). This is horribly inefficient time-wise, but makes it so only 1K is needed.

The single buffer actually made some of the coding easier because I didn't have to deal with a buffer that was being used for interpretation being re-loaded into a different location (which might happen if the code being interpreted touched enough different blocks to cause a round-robin or LRU-type replacement algorithm to reuse the buffer the code was in, and then reload it in a different buffer when it was time to resume processing). As long as the original screen being interpreted is put back into memory and the pointers/indices are pointing at the next word to process, you can just resume interpreting as if the buffer contents were always there.

More buffers, of course, makes it so the system doesn't have to thrash around trying to restore what used to be in a buffer if it still has a copy. Tali2 is designed for systems with 32K or less of RAM and some folks won't use the block words at all, so it made sense to have it use as little RAM as possible.

I use the block words for storing forth code and data on an I2C EEPROM and it works very well for that. Emacs has a special forth mode to save the code in block format that I use.


Top
 Profile  
Reply with quote  
PostPosted: Wed Dec 16, 2020 11:47 pm 
Offline

Joined: Fri May 05, 2017 9:27 pm
Posts: 895

IamRob wrote:
JimBoyd wrote:
I do not recall an SD card for the C64.

you can find it here.
https://www.thefuturewas8bit.com/shop/c ... w1EALw_wcB

I was not aware of that, nor was I aware of it back in the nineties when I was first using Forth on a Commodore 64. The word for disk read/write of a block, DR/W , is deferred so new functionality supporting new hardware can be added. Likewise, the word for REU read/write of a block, RR/W , is also deferred so new functionality can be added.
Quote:
Quote:
Although the system will choose a specific drive for each range of 4096 blocks, not all blocks will be available.
For example, if device 8 is a Commodore 1541 drive, only 166 blocks are available. This means that the range of available blocks is not continuous. The range selected for the drives is a design consideration I made to facilitate copying blocks from one device to another while allowing a larger capacity drive to have more blocks available than what is possible with the 1541 drive.
The maximum REU size that is compatible with other C64 REU's is 16 megabytes or 16384 blocks. The 1764 REU had a capacity of 256 blocks and the 1750 REU had a capacity of 512 blocks.

I have to think this through some more. This BLK, SCRN, LOAD and LINE method still seems quite cumbersome.

It's not too bad. To load a range of blocks, I use THRU . For example, if I want to load blocks 4, 5, 6, 7, 8, and 9 I would type this:
Code:
4 9 THRU

Quote:
Quote:
To be able to access blocks on a disk, the drive must be opened with the word DOPEN . This word takes no parameters and leaves no parameters. It opens the disk on the current drive. The current drive is 8 upon startup or a cold start but can be changed with the word DRIVE . For example, if I wanted to access blocks on drives 8 and 9, I could do the following:
Code:
8 DRIVE DOPEN 9 DRIVE DOPEN


I would like to convert that to
OPEN filename

Do you mean having blocks in a named file or Forth source in a text file?
Quote:
Quote:
Fleet Forth maintains one or more buffers for blocks. I usually use four, but I sometimes use more when manipulating blocks. The buffers are right up against the memory limit specified by the system value LIMIT .

LIMIT can be lowered if the need arises. The buffer access table sits right up against the buffers. Although Fleet Forth reads blocks using direct access to read four Commodore disk sectors, it reads each block as a one kilobyte chunk. Fleet Forth only reads one block at a time and only writes one block at a time.

I think four buffers is almost the minimum. I will need 2 buffers to OPEN files and another 2 buffers for working on those files. I see now I can't get away from all those buffers. The first buffer houses the error messages, but since am working on a 128kb machine, I have some room to spare and might make the errors part of the system.

Fleet Forth's errors are part of the system. I added up the memory used by the error messages in the kernel and it is 161 bytes. The error message strings for the error codes returned by the C64's kernal take up another 157 bytes.
I can use one buffer just fine. The extra buffers are to minimize "buffer thrashing". This is especially useful when editing source in a block. If I make a change in one block and it affects words in other blocks, then I edit those blocks and find that there is a better solution...
All that editing back and forth goes a lot smoother if there are enough buffers to hold all the blocks that I'm editing as I refine a design.
When I use the metacompiler, I need at least two buffers. One would work... slowly. The metacompiler uses some of the blocks in the C64's REU as virtual memory while it builds the new kernel. With only one buffer, every fetch or store involving virtual memory would flush the buffer with the source back out to disk.
Quote:
Fleet Forth is quite large and a C64 only has 64 kb memory?

I've tried streamlining Fleet Forth since releasing my initial prototype.
The Fleet Forth kernel is under ten k (about nine and three quarter). It is a complete Forth-83 system without an assembler or editor so I suppose the name 'kernel' is a bit of a misnomer. Part of the cold start routine switches out BASIC ROM because it is not needed.
The kernel plus system loader is just under 15.5 k. The system loader includes the assembler, the Starting Forth editor, the screen editor, printer and logger words, the auxiliary stack words, and some other useful words.
Loading the utilities increases the size to just under 20 k. This includes the decompiler, block manipulation tools, and a few more utilities.
Quote:
Is memory constraints so tight that it would be difficult to use graphics

I have successfully ported Blazin' Forth's graphics words to Fleet Forth. The first time I tried loading it was a disaster because Blazin' Forth's line drawing routine overwrote Fleet Forth's IP and W virtual registers in zero page! Yes, this locked up the Commodore, necessitating a reset.
The C64 video chip needs 8000 bytes for the graphics screen. For graphics, Blazin Forth sets the video chip to look at the last 16k of memory and uses the RAM under the kernal ROM. Since only 8000 bytes are needed, that leaves the last 192 bytes untouched. Blazin' Forth has a clever way to allow normal interrupt processing while the kernal ROM is switched out.
Blazin' Forth's graphics utilities add turtle graphics to Forth.
Quote:
or write large applications with Fleet Forth?

The last 'application' I wrote in Forth on the C64 was "A Virtual Nondeterministic Machine in Forth". If memory is tight, the extras can be left out. DUMP and SEE are, in my opinion, for diagnostics when something goes wrong. That is why SEE is so big. It also decompiles CODE words and handles the transition from high level to CODE and back.
An application is unlikely to need either.
I suppose the metacompiler could be considered an application in Forth. In that case, the metacompiler was the last application I wrote in Forth on a C64.


Top
 Profile  
Reply with quote  
PostPosted: Thu Dec 17, 2020 12:17 am 
Offline

Joined: Fri May 05, 2017 9:27 pm
Posts: 895
SamCoVT wrote:
IamRob wrote:
I think four buffers is almost the minimum. I will need 2 buffers to OPEN files and another 2 buffers for working on those files. I see now I can't get away from all those buffers. The first buffer houses the error messages, but since am working on a 128kb machine, I have some room to spare and might make the errors part of the system.
It's possible to do more than you think with a single buffer. I was also trying to figure out what the minimum number of buffers was for TaliForth2, and I ended up trying to make it work with a single buffer and it actually worked! I can load a screen that has other LOAD or THRU commands in it. The trick is to put back whatever was in the buffer if it was in use before (and you can check that by looking in BLK). This is horribly inefficient time-wise, but makes it so only 1K is needed.

The single buffer actually made some of the coding easier because I didn't have to deal with a buffer that was being used for interpretation being re-loaded into a different location (which might happen if the code being interpreted touched enough different blocks to cause a round-robin or LRU-type replacement algorithm to reuse the buffer the code was in, and then reload it in a different buffer when it was time to resume processing). As long as the original screen being interpreted is put back into memory and the pointers/indices are pointing at the next word to process, you can just resume interpreting as if the buffer contents were always there.


The easiest way I've found when designing a Forth-83 system is to have LOAD save BLK and >IN on the return stack and restore them after.
Fleet Forth's parsing word, WORD uses 'STREAM . If BLK is not zero, 'STREAM calls BLOCK so it doesn't matter if a block load is interrupted by another block access and the original block is read into a different buffer. Every time WORD executes, BLOCK is executed. This is why Fleet Forth's BLOCK (and BUFFER ) starts out as a CODE word. If the requested block is the most recently used, BLOCK is screaming fast.
Quote:
More buffers, of course, makes it so the system doesn't have to thrash around trying to restore what used to be in a buffer if it still has a copy. Tali2 is designed for systems with 32K or less of RAM and some folks won't use the block words at all, so it made sense to have it use as little RAM as possible.

By allowing the number of buffers to be altered on the fly, it is possible to set the number of buffers to zero in Fleet Forth with:
Code:
0 TO #BUF CONFIGURE

Just don't try to use blocks with zero buffers!
Quote:
I use the block words for storing forth code and data on an I2C EEPROM and it works very well for that. Emacs has a special forth mode to save the code in block format that I use.

Yes, blocks are not just for source code. When metacompiling, I use some blocks in the C64's REU (ram expansion unit) for virtual memory.


Top
 Profile  
Reply with quote  
PostPosted: Thu Dec 17, 2020 4:48 am 
Offline

Joined: Sun Apr 26, 2020 3:08 am
Posts: 357
Quote:
It's possible to do more than you think with a single buffer. I was also trying to figure out what the minimum number of buffers was for TaliForth2, and I ended up trying to make it work with a single buffer and it actually worked! I can load a screen that has other LOAD or THRU commands in it. The trick is to put back whatever was in the buffer if it was in use before (and you can check that by looking in BLK). This is horribly inefficient time-wise, but makes it so only 1K is needed.



Under Prodos, the minimum would be 2 buffers. The OPEN file command needs 1 buffer reserved for information about the file. The 2nd buffer is needed for the READ command which reads the actual data of the file. I can read 1024 bytes at a time and compile a line at a time that ends in a CR delimiter.

With a text file, each line gets moved into PAD and the CR gets converted to a zero before compiling. When the end of the buffer is reached, then the next block is read in and compiling continues until the end of the file is encountered.

This idea will be similar in use to the SCRN# LOAD and SCRN# LIST words, but won't need the "-->" at the end of each 1024 byte block. The new word definition for loading text file getting compiled is with "LOAD filename" (without the quotes). This way I can have as many text file applications, utilities and games all on hard drive all at the same time and swap between them easily with LOAD TEXTFILE and FORGET TASK.


Top
 Profile  
Reply with quote  
PostPosted: Thu Dec 17, 2020 5:54 am 
Offline

Joined: Sun Apr 26, 2020 3:08 am
Posts: 357
Quote:
Do you mean having blocks in a named file or Forth source in a text file?


Both. But blocks are not padded with spaces like in screens. The idea is to load a text file which requites 2 buffers. One for the OPEN command and one for the READ command. Prodos requires the OPEN file buffer for information about the file. The READ buffer is for the actual data. A block (1024 bytes) of text gets read in at a time. Each line delimited by a CR is moved to PAD and the CR converted to a zero. That line gets compiled, then the next line gets read in. If the end of the buffer is reached and a line is cut off when being moved to PAD, then the next block gets read in. This continues until the end of the file is reached. The RUN word launches the last word that gets loaded and launches the application. This makes it easy to have all the applications, games, and utilities stored in text files and can be swapped easily between them with LOAD TEXTFILE and FORGET TASK.


Quote:
Fleet Forth maintains one or more buffers for blocks. I usually use four, but I sometimes use more when manipulating blocks. The buffers are right up against the memory limit specified by the system value LIMIT .


I need a minimum 2 buffers for Prodos. The first one is reserved for information about the file and used by the Machine Interface and the second one is used to load the actual data.


Quote:
LIMIT can be lowered if the need arises.


In ProForth, LIMIT is pointing at the USER area with the buffers just underneath. FIRST is used to designate the start of the buffers. But I would prefer to have LIMIT designated to be the upper limit of free memory.


Quote:
Fleet Forth's errors are part of the system.


ProForth errors are actually part of a screen file. The screen file is a file on disk that houses 16 screens of 1024 bytes and takes up 1 constant buffer. But there is a lot of waste with the screens as every line is padded with spaces. Thus I want to make the errors part of the system and convert everything else to text file delimited by a CR to eliminate all the wasted space.


Quote:
I've tried streamlining Fleet Forth since releasing my initial prototype.
The Fleet Forth kernel is under ten k (about nine and three quarter). It is a complete Forth-83 system without an assembler or editor so I suppose the name 'kernel' is a bit of a misnomer. Part of the cold start routine switches out BASIC ROM because it is not needed.


I am trying to weed the kernel down to being just the primitives required to compile a colon definition and code words. Everything else can be added through text file loading as required.


Quote:
The kernel plus system loader is just under 15.5 k.


If all goes well, I believe I can get the bare kernel down to under 5 K. The system loader will move all the actual names and their CFA's of the words to Auxiliary memory along with a sorting algorithm. This sorter will cut compiling time down into a fraction of the time and also eliminates a lot of words when they are with their description. LFA, NFA, PFA, TRAVERSE, COUNT are no longer needed. And the dictionary of words can be listed in alphabetical order or just the words that are part of a VOCABULARY, which makes words easier to find in the list. I can also just list words that start with the first letter. Really nice not to have the entire word library listed every time.


Quote:
The system loader includes the assembler, the Starting Forth editor, the screen editor


I already have very nice system applications of these three, even though I have to leave FORTH. But I am not ready to re-invent the wheel right now.



Quote:
I have successfully ported Blazin' Forth's graphics words to Fleet Forth.


Graphics is on my to-do list


Quote:
Blazin' Forth's graphics utilities add turtle graphics to Forth.


I also have turtle graphics in an ML program. It wouldn't take much to separate the code and make word definitions for each turtle command.



Quote:
Quote:
or write large applications with Fleet Forth?

The last 'application' I wrote in Forth on the C64 was "A Virtual Nondeterministic Machine in Forth". If memory is tight, the extras can be left out.


This is where my theory of loading text files for an application can benefit. It can load larger programs in memory than can fit when creating them, as the editor and assembler are also in memory when creating applications. I will have the ability to clear the first words create and then append extra words to a text file.


Quote:
That is why SEE is so big. It also decompiles CODE words and handles the transition from high level to CODE and back.


I call my word decompiler DECODE and it does a pretty good job at decoding everything. The one problem I have to rectify yet is that -FIND returns the PFA of a word. The problem with that is that some words do not have a PFA. The CFA points somewhere else in memory. Then comes the big crash to the monitor.

Can your SEE decode BRANCH and 0BRANCH back into IF-ELSE-THEN or BEGIN-UNTIL (REPEAT or AGAIN) statements? I think I have it figured, but just when I think I have it figured out, I question my logic.


Top
 Profile  
Reply with quote  
Display posts from previous:  Sort by  
Post new topic Reply to topic  [ 354 posts ]  Go to page Previous  1 ... 7, 8, 9, 10, 11, 12, 13 ... 24  Next

All times are UTC


Who is online

Users browsing this forum: No registered users and 18 guests


You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot post attachments in this forum

Search for:
Jump to: