fignition‎ > ‎Documentation‎ > ‎Understand It‎ > ‎

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.


 Name Register Purpose
 gIP 28 The Instruction Pointer - contains the address of the next instruction to be executed.
 gTos 16 The value on the top of the stack, in a register for performance reasons.
 gDP 26 The Data Pointer - contains the address of the top of the data stack. It pushes up.
 gILoop 12 The Loop Counter - contains the value of the current do ... loop counter, the value returned by i.
 gLoopLim 10 The Loop Limit - the first number supplied to a do ... loop; the value returned by i'
 tmp0..3 18 .. 24
 Temporary Registers used to supply parameters to routines written using the gcc libc convention.
 RP 0x3d The Return stack pointer, equivalent to the AVR's SP stack pointer.

Byte Code List

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

 Code Name Action
 0 kFigNext A No-operation
 1 kFigLit Pushes a (big-endian) 16-bit Literal, which follows kFigLit, onto the stack.
 2 kFigExecute Executes the Forth definition at gTos; if gTos<256, executes the corresponding byte code instead.
 3 kFigDrop Pops the top item from the data stack.
 4 kFigOBranch If gTos==0, then performs a branch specified by the (big-endian) 16-bit address that follows. Pops gTos.
 5 kFigBranch Performs a branch specified by the 16-bit address that follows.
 6 kFigLoop Performs loop. It increments gILoop and if it's < gLoopLim, branches to the 16-bit address that follows.
 7 kFigPlusLoop Performs +loop. It adds gTos to gILoop; pops gTos and if gILoop didn't cross the boundary given by gLoopLim, branches to the 16-bit address that follows.
 8 kFigDo Performs the do part of a do .. loop. Pushes the old gLoopLim, then gILoop on the return stack; pops gTos into gILoop and the next on the stack to gLoopLim.
 9 kFigUMult Performs u*, multiplying gTos and the next item on the stack to give an unsigned 32-bit result; storing the high 16-bits in the next item on the stack and the low 16-bits in gTos.
 10 kFigPortMod Performs an atomic port modification returning the value of the port before the modification: andMask orMask Address >port> oldValue. The new value stored is: (Mem[Address]&andMask) | orMask.
 11 kFigUDivMod Performs an unsigned 32-bit / 16-bit division and modulus operation returning the result in gTos and the modulus in the next item on the stack.
 12 kFigOpAnd Performs: gTos &= nos; popping nos.
 13 kFigOpOr Performs: gTos |= nos; popping nos.
 14 kFigOpXor Performs: gTos ^= nos; popping nos.
 15 kFigLeave Sets gILoop=gLoopLim so that the next loop will terminate.
 16 kFigDoes Performs (does) , the action part of a Definer definition. kFigDoes is equivalent to r> since a definer definition merely consists of a pointer to the does> part of a definer; which when executed will contain the Parameter Field Address on the top of the return stack.
 17 kFigRFrom Performs r> . Pops the top item from the return stack, pushing onto the data stack.
 18 kFigRFetch Copies the top item from the return stack, pushing onto the data stack.
 19 kFigToR Pops the top item from the data stack onto the return stack.
 20 kFigZeroEqIf gTos==0 then sets gTos to -1 otherwise, it sets it to 0.
 21 kFigZeroLt If gTos<0 then sets gTos to -1 otherwise, it sets it to 0.
 22 kFigPlus Adds nos to gTos storing the result in gTos; pops nos.
 23 kFigDPlus Double Plus. Builds a 32-bit number from second and third items on the stack and adds it to the 32-bit number formed from the first item on the stack and gTos; storing the result in gTos:nos. The second and third items on the stack are popped.
 24 kFigMinus Negates gTos, storing the result in gTos.
 25 kFigDMinus Double Negate. Builds a 32-bit number from nos and gTos; negates it and stores the result in gTos:nos.
 26 kFigOver Copies nos; pushes gTos onto the stack and stores the copied nos into gTos.
 27 kFigSwap Swaps nos and gTos
 28 kFigDup Pushes gTos onto the stack, but doesn't change.
 29 kFigFetch Fetches the 16-bit value at memory location gTos and stores it in gTos. (Fetch only reads from SRAM or Firmware).
 30 kFigCFetch Fetches the 8-bit value at memory location gTos and stores it in gTos. (CFetch only reads from SRAM or Firmware).
 31 kFigCPling Stores the 8-bit value in nos at memory location gTos. (Pling only writes to SRAM).
 32 kFigPling Stores the 16-bit value in nos at memory location gTos. (Pling only writes to SRAM).
 33 kFigGetI Pushes gTos and sets gTos to the value of gILoop.
 34 kFigInc Adds 1 to gTos.
 35 kFigNative Jumps to native AVR code at (IP+2)&0xfffe (that is, the following instruction, word aligned).
 36 kFigIntCFetch Fetches the 8-bit value at internal memory location gTos and stores it in gTos. (CFetch only reads from internal RAM).
 37 kFigIntCStore Stores the 8-bit value in nos at internal memory location gTos. (Pling only writes to internal RAM).
 38 kFigIntFetch Fetches the 16-bit value at internal memory location gTos and stores it in gTos. (CFetch only reads from internal RAM).
 39 kFigIntStore Stores the 16-bit value in nos at internal memory location gTos. (Pling only writes to internal RAM).
 40 kFigEmit Displays the current character in gTos on the screen; pops gTos.
 41 kFigSFGet Returns the value of IntMem[sf+postByte]
 42 kFigDotHex Displays the value of gTos as a hexadecimal number. Note: Low-level debugging tool; likely to be deprecated.
 43 kFigZero Pushes value 0 onto the stack.
 44 kFigLitC Followed by a byte value in the range 0 to 255. Pushes the byte value to the data stack.
 45 kFigAt Places the cursor at nos, gTos on the screen.
 46 kFigExit Returns from a definition.
 47 kFigDec Subtracts 1 from gTos.
 48 kFigFill Used as in: addr len value fill. Fills an area of SRAM or internal RAM with a particular value. The second item on the stack is the address; the first item is the number of bytes and gTos is the fill value.
 49 kFigSfPut Stores gTos in IntMem[gSf+postByte]
 50 kFigLsr Calculates gTos = (unsigned) nos shifted right by gTos bits.
 51 kFigLsl Calculates gTos = (unsigned) nos shifted left by gTos bits.
 52 kFigDskRd Given a virtual block number in gTos reads the corresponding virtual block and returns its physical block number.
 53 kFigDskWr Given a virtual block number in the second item on the stack; the physical block number in the first item on the stack and an address in gTos; writes the block at the address to the physical block number; mapping it to the given virtual block number. Note: kFigDskWr can cause a Flash disk purge if no more physical blocks are available.
 54 kFigCMove Given a source address in the second item on the stack; the destination address in the first item and the length in gTos; cmove moves length number of bytes from the source memory location to the destination. The source location can be in internal Ram, ROM or SRAM and the destination location must be in either RAM or SRAM. All three parameters are popped from the stack.
 55 kFigPlot Given an x coordinate in nos and a y coordinate in gTos; plots the pixel(x,y) in the current pen mode.
 56 kFigSpi A basic SPI driver.
 57 kFigTrace Takes FIGnition into Trace mode, Deprecated. You can test it though, by creating a word: : tTrace [ 58 c, ] ; .
 58 kFigDumpDict Dumps the current Dictionary, Deprecated.
 59 kFigVarDoes Optimises fetching normal var variables. Pushes gTos; pops the top item from the return stack and stores it in gTos.
 60 kFigConstDoes Optimises fetching normal const variables. Pushes gTos; pops the top item from the return stack; fetches the 16-bit value at that location and stores the result in gTos.
 61 kFigTile Copies a bitmap in SRAM to tile space in internal RAM.
 62 kFigBlt Xor Blits a bitmap in tile space to the frame buffer.
 63 kFig2Blt Xor Blits two bitmaps in tile space to the frame buffer in succession.
 64 kFigBlts Copy Blits a bitmap in tile space repeatedly over a region on the Frame buffer.
 65 kFigClip Defines the clipping area for the frame buffer.
 66 kFigOpSub Performs: gTos = nos-gTos; popping nos.

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.

Comments