Forth Execution (Advanced Topic)

Introduction

This section describes how FIGnition Executes Forth.

Byte Codes

FIGnition's Forth programs consist of both 8-bit Byte Codes and 16-bit Direct Threaded Addresses. In FIGnition Forth all instructions are of one of three basic forms:

This allows FIGnition Forth to be more compact than typical Forths and because the Serial RAM is relatively slow, actually increases the performance, because full addresses don't have to be fetched for the most common operations. This execution model simplifies instruction execution, because threaded code is implied: the Code Field Address of a Colon Definition simply points to the code to be executed and neither an indirect JSR to push IP nor an indirect execution address are needed.

The disadvantage is that only 40Kb can be used for code, the bottom 20Kb can only be used for data.

Registery

The Forth model uses a number of internal registers which are mapped to AVR registers.

Byte Code List

The byte code list corresponds to the primary built-in definitions.

Note: @TODO Check the parameters for double-number operations.

Inner Interpreter

In FIGnition, Forth programs consist of byte codes and execution addresses and these are all executed using an inner interpreter. In principle the interpreter is very simple and is equivalent to the following 'C' code, but in AVR assembler:

tmp=GetByte(gIP++);

if(tmp<64) { // It's a normal byte token.

  switch(tmp) {

  case kFigNext: // Nop does nothing.

    break;

  case kFigLit: // A 16-bit big-endian literal follows.

    Push(gTos); // save the old top of stack value.

    tmp=GetByte(gIP++)<<8; // read the upper byte

    gTos=tmp+GetByte(gIP); // combine with LSB.

    break;

  case kFigExecute:

    ... etc.

  }

}

else { // It's an executable address.

  Push(gIP); // save the return address.

  gIP=(tmp<<8)+GetByte(gIP); // jump to the next instruction.

}

However, all of this is complicated by the way FIGnition accesses memory and the need for performance enhancements. The first issue is that RAM execution requires the use of the built-in SPI peripheral on the AVR, but ROM execution is taken directly from the AVR's Flash memory. So GetByte is therefore:

byte GetByte(ushort addr)

{

  if((short)addr<0) { // it's in SRAM.

    return MicrochipSramRead(addr&0x7fff);

  }

  else { // it's in Flash.

    return pgm_read_byte((byte*)addr);

  }

}

However, all this is very inefficient since it effectively takes 2µs to read a sequential byte, though the SRAM chip could be set to start reading the next byte before it's needed so that it will be ready for when the next instruction is executed (which is the common case). In fact the inner interpreter makes a number of optimizations in order to improve program execution; as well as inlining the whole of GetByte and SRAM reads in most cases to reduce the call / return overhead.

As you look into this you'll realize there's no way for Forth execution to start executing from RAM, since execution starts in ROM and there's no route from there into executing RAM code. In reality, the Forth ROM executes user code (in SRAM) by pushing its address on the data stack and then executing kFigExec; for example as a result of executing a command line.

The Forth ROM is written almost entirely in Forth itself. As stated before, FIGnition Forth was originally based on Andrew Holme's Mark 1 Forth Computer's ROM, which was written entirely in Forth. However, since then there have been a number of changes, including 32 additional byte codes for things like access to the AVR's internal memory; built-in byte codes for cmove and fill; optimizations or variable and constant fetching and also the ability to execute AVR assembler from the Forth ROM using kFigNative.

The Forth ROM is currently encoded as word and byte definitions using the AVR assembler. For example: cr is:

_Colon FigCr, "cr", 2

    .byte kFigLit

    .word 13

    .byte kFigEmit,kFigExit

( _Colon is an assembler macro which manually builds Forth colon definition headers).

The ROM is currently being rewritten directly in Forth along with a translator to convert it to the original word and byte definitions. This will make it much easier to modify the ROM for future firmware updates.