Link here

Chapter 14 - Mass Memory Interface


QED-Forth provides a standard FORTH-style block buffer interface to facilitate the use of mass memory in the form of a disk drive or a "ram disk".

FORTH divides mass memory into "blocks". A "block" contains 1K (1024 bytes) of data or source code. The block buffer interface enables the entry of source code or other text into a block by typing the text from the terminal. The system can also load blocks from mass memory into a RAM buffer where the data or source code may be displayed, edited, interpreted, or saved to the disk. Display of a block is arranged, by convention, as 16 lines of 64 characters per line that fits on your screen. A block of data is sometimes referred to as a "screen" when it is being displayed.

The blocks interface is a "virtual memory" scheme: a buffer in RAM is used as a medium by which mass memory is accessed, so the disk memory is made to act like normal RAM memory. Typically, two 1 Kbyte RAM buffers are used to interface to the mass memory: 1 buffer contains the block that is currently being accessed, and the other contains the previously accessed block. More RAM buffers can be used, but 2 is adequate for the job.

Because the QED Board does not come with a standard disk drive, a "ram disk" facility is included in the kernel. This allows you to designate a certain area of RAM as mass memory. You can edit source code into the blocks in the ram disk, LIST them to display them on the terminal, and LOAD them to interpret the contents just as if the code in the blocks were being entered from the terminal.

 

How to Use the Mass Memory Interface

Let's start by designating a free area of RAM as a ram disk which is to be treated as mass memory. For this example we'll initialize the memory map by doing a cold restart as

COLD

then executing

4 USE.PAGE

to set up the memory map in page 4. Turn DIP switches #1 and #2 OFF to ensure that pages 4 through 7 are not write-protected. To designate the first 16K of page 6 as the ram disk, execute

0 6 16 IS.RAMDISK

IS.RAMDISK expects the starting xaddress under the number of 1K blocks on the stack; it initializes the headerless variables RAMDISK.START and #RAMDISK.BLOCKS appropriately, and also sets OFFSET (explained below) to 0\0. The ram disk must occupy contiguous memory, and may cross one or more page boundaries. To fill the ram disk with ascii blanks, execute

0 6 1024 16 * BLANK

Next we'll designate two 1 Kbyte block buffers starting immediately after the RAMDISK at 4000H on page 6 by executing

HEX
4000 6 2 BLOCK.BUFFERS

BLOCK.BUFFERS expects the starting buffer xaddress under the number of buffers; the buffers may cross page boundaries. Two block buffers are sufficient. Each buffer requires 1K plus 4 bytes, or 404H bytes. The 4 extra bytes hold a 32-bit disk block number to keep track of which block the buffer contains. BLOCK.BUFFERS initializes the user variables FIRST, LIMIT, PREV, and USE which are described below. To initialize the block buffers, execute

EMPTY.BUFFERS

which unassigns and erases the buffers without writing their contents to the disk.

Now we're ready to edit some code into the blocks. Block 0 is reserved for comments and cannot be LOADed (that is, its contents can not be interpreted by QED-Forth). Block 1 is typically a "load block" that commands the loading of other blocks. The contents of the user variable SCR (for "screen") specify the current block for editing. To edit block 2, execute

2 SCR !

LISTing a block writes its contents to the terminal as 16 lines of 64 characters each. The command LIST also sets SCR to the specified block number, so the command

2 LIST

shows the current contents of block 2 and also makes block 2 the current block for editing.

To enter a line of source code into the current block, type a line number between 0 and 15 followed by the command >L followed by the text to be entered on the line. A maximum of 64 characters can be accommodated on a line. If more than 64 characters are specified after the >L command, the excess characters are ignored. Try

0 >L \ this block contains some print commands
3 >L : SAY.HI CR ." Hi there!" ;
6 >L SAY.HI
7 >L SAY.HI

To list a particular line, use the .L command. For example, to display line 3, execute

3 .L

To display the entire block execute

2 LIST

To interpret and execute this source code, execute

2 LOAD

and note that the greeting "Hi there!" is printed twice because SAY.HI is invoked twice in block 2.

Let's enter some code that performs some simple matrix operations:

5 LIST
0 >L ANEW MATH.CODE    \ matrix creation and initialization
1 >L  MATRIX: MY.MATRIX
2 >L  MATRIX: DESTINATION
4 >L  2 3 ' MY.MATRIX DIMMED
6 >L  MATRIX MY.MATRIX = 1. 2. 3. 4. 5. 6.
 
6 SCR !
0 >L     \ matrix transposition and printout
2 >L  ' MY.MATRIX M..
4 >L  ' MY.MATRIX ' DESTINATION TRANSPOSED
5 >L  ' DESTINATION   M..

Blocks 5 and 6 can be loaded with the command

5 6 THRU

which executes the source code and prints the contents of MY.MATRIX and its transpose. The kernel word THRU expects a starting block number under an ending block number and LOADs all of the specified blocks.

The commands LOAD and THRU can themselves be edited into a block to control the loading of a file. Block 1 is commonly used as a "load block" that holds various LOAD and THRU commands. Editing block 1 is then an easy way to control the compilation of portions of the source code. For example,

1 SCR ! \ set block 1 as current block for editing
0 >L \ load block
2 >L 2 LOAD \ print statements
4 >L 5 6 THRU \ matrix definition and transposition

Executing

1 LOAD

causes blocks 1, 2, 5 and 6 to be interpreted.

Several user variables control the operation of the mass memory interface. SCR has already been introduced. It holds the number of the most recently listed block which is the current block for editing purposes.

BLK contains the block number of the block currently being interpreted; it is used by WORD to specify the location of the next word in the input stream. If BLK is 0, the input stream is from the serial line and is buffered in the TIB (this is why block#0 of a file can't be LOADed).

The user variable OFFSET contains a 32-bit number equal to the difference between the physical block number on the disk and the file block number to which BLK refers. The block numbers used in the above example were all 16-bit file block numbers. Each file starts with block#0. When the file is saved to disk, file block#2 may end up as, say, the 2002nd physical block on the disk. In this case, OFFSET would equal 2000. OFFSET is a 32-bit number so that large disk drives can be handled (a 16-bit OFFSET would limit the addressable mass memory to 32 Megabytes which is smaller than some available hard disk drives).

FIRST holds the 32-bit xaddress of the first byte of the first block buffer. LIMIT holds the 32-bit xaddress of the last byte+1 in the last block buffer. The ram block buffers must be in contiguous memory, and may cross page boundaries.

USE holds the 32-bit base xaddress of the next block buffer to use, and PREV holds the 32-bit base xaddress of the most recently accessed block buffer.

The word UPDATE marks the current buffer pointed to by PREV as modified. Other words check the update status to decide whether a given block needs to be written to the disk.

The word BLOCK expects a 16-bit file block number on the stack and returns the xaddress of the buffer in which the block resides. If the block is not already in a buffer, it is read in from the disk into a buffer. If the previous contents of the buffer had been marked as UPDATEd, they are written to the disk before the new block is read in. In short, BLOCK takes care of getting a specified block into a ram buffer where it can be easily accessed.

BUFFER expects a 16-bit file block number on the stack, and returns the xaddress of the buffer assigned to the specified block. BUFFER is similar to BLOCK, except that it does not read in the contents from disk. That is, BUFFER assigns a buffer to the specified block and writes the prior contents to the disk if necessary (if they had been UPDATEd), but the contents of the assigned block are unspecified.

SAVE.BUFFERS transfers the contents of all UPDATEd buffers to the disk and marks all buffers as unmodified. FLUSH unassigns all block buffers, writing to the disk any UPDATEd blocks. EMPTY.BUFFERS unassigns and erases all block buffers and does not write to the disk.

To interface a floppy or hard disk drive to QED-Forth, a driver routine capable of transferring bytes from the block buffers to specified locations on the disk must be coded. For the ram disk this word is called

READ/WRITE ( xaddr\u\flag -- )

where the xaddr is the extended block buffer address, u is a file block number, and the flag is true to read from the disk and false to write to the disk. The xcfa of this driver routine must be stored in the 32-bit user variable UREAD/WRITE. The words BLOCK and BUFFER execute the routine referenced by this user variable whenever disk I/O operations are required.

 
This page is about: Forth Language Mass Memory Interface – QED Forth provides standard FORTH style block buffer interface to facilitate use of mass memory in form of disk drive or ram disk. FORTH divides mass memory into blocks. A block contains 1K (1024 bytes) of data or source code. The block buffer interface …
 
 
Navigation