colorForth Instructions

Here are the 33 instructions for the 18-bit core. There are words about each and examples of their use. There are some tricky things you can do, particularly with or and -.

The names of instructions are easily changed and continually debated. These are the opcodes and my current preference for names:

Jumps

Memory

Arithmetic

Register

00

;

08

@p

10

+*

18

dup

01

ex

09

@+

11

2*

19

pop

02

jump

0a

@b

12

2/

1a

over

03

call

0b

@

13

-

1b

a

04

unext

0c

!p

14

+

1c

.

05

next

0d

!+

15

and

1d

push

06

if

0e

!b

16

or

1e

b!

07

-if

0f

!

17

drop

1f

a!

Registers are:

  • R - top of return stack
  • A - address/scratch
  • B - address
  • P - program counter
  • I - instruction word
  • T - top of data stack
  • S - second number on data stack

Jump Instructions

Jump to address in register P, I or R

00 ;

Jump thru R (destructive)
The word ; is Forth syntax to end a definition. Here it doesn't necessarily mean end, but return
  • Return from subroutine (return to caller):
    ;
  • Computed jump - address in T:
    push ;
    Useful for a vectored jump into a table

01 ex

Jump thru R, save P in R
Execute. Sometimes called co-routine jump, it returns to caller while saving the current address. This can be repeated to
  • Interleave 2 threads
  • Computed call - address in T:
    push ex

02 jump

Jump thru I
The opcode is not used explicitly. The compiler generates it
The address is in the instruction, either 10, 8 or 3 bits depending on slot
  • Tail recursion:
    The compiler will turn a call followed by ; into a jump to save time and space
  • Implement else:
    if . . else . . . then
  • Construct a table of jumps
    Often more efficient than conditional jumps

03 call

Jump thru I, push current address to R
The opcode is not used explicitly. Referencing any defined word generates a call
  • Subroutine call:
    word . . . ;
    word

04 next

This instruction has 2 forms:
  • If the low-order bit is 0 it's called unext:
    If R is non-zero, jump to slot 0 and decrement R. Otherwise pop R and execute the next slot.
    Discards the address left by for. Very efficient loop since no instruction fetchs required.
    • Delay loop:
      for unext
    • Multiply:
      for +* unext
    • Shift:
      for 2* unext
      for 2/ unext
    • Move data to/from ports
      for @+ !b unext
      for @b !+ unext
      for @p !+ unext
      for @+ !p unext
  • If the low-order bit is 1:
    If R is non-zero, jump thru I and decrement R. Otherwise pop R
    For expects loop-count (-1) on stack and is defined as:
    push begin
    Begin fills the current instruction word with . so that the loop begins in the next word. Leaves address on compiler stack for next to use
    • Count-down loop:
      for . . . . next
      Prefer unext if only 3 instructions in loop
    • Search for match:
      for @+ over or if drop swap next no match then match
      Swap is executed at compile time to access the address for next
    • /mod for begin over over . + -if drop 2* swap next ; then over or or - 2* - next ;
      Divide operation: trial subtract and shift in either 0 or 1

06 if

Jump thru I if T is zero
If leaves its address on compiler stack for then to complete. Then may abort if address won't fit in slot
  • Test for non-zero (true):
    if . . . then
  • Test for unequal:
    or if . . . then

07 -if

Jump thru I if T is positive
  • Absolute value:
    abs -if - 1 . + then ;
  • Maximum:
    max - over . + - -if drop ; then + ;
  • Minimum:
    min - over . + - -if + ; then drop ;

Memory Instructions

Fetch or store thru registers A, B, P.
@ and ! are Forth abbreviations for 'fetch' and 'store'. Fetch pushes the data stack; store pops it

08 @p

Fetch thru P, increment P
Increment suppressed when executing from port
  • Fetch a number:
    123
    Uses 1 slot plus 1 word
  • Fetch 4 numbers:
    100 200 300 400
    Uses 4 slots plus 4 words
  • Name a constant:
    pi 314 ;
    Uses 2 slots plus 1 word
  • Add a number:
    1+ 1 . + ;
    Uses 4 slots plus 1 word

09 @+

Fetch thru A, increment A
Increment suppressed when referencing port
  • Check-sum memory:
    0 63 for @+ . + unext

0a @b

Fetch thru B
  • Read status:
    io b! @b

0b @

Fetch thru A
  • Read status:
    io a! @

0c !p

Store thru P, increment P
  • Send status to port from which you're executing instructions:
    @b !p
  • Make a constant (x) into a variable:
    x! @p drop !p ;
    x 100 ;
    Avoids using an address register

0d !+

Store thru A, increment A
  • Fill memory from port:
    63 for @b !+ unext
  • Fill memory executing from port:
    63 for @p !+ unext

0e !b

Store thru B
Be careful to distinguish !b from b!
  • Set 2 I/O pins
    30003 !b
  • Reset 3 I/O pins
    2000a !b

0f !

Store thru A
  • Set first I/O pin
    30000 !

ALU Instructions

Unary and binary operations:

10 +*

Multiply step: add S to T if A0=1 then shift T and A right. The multiplicand (S) is signed; the multiplier (A) is unsigned
  • Integer multiply: 36-bit result, buried multiplicand
    * nm-nhl a! 0 17 for +* unext a ;
    unext provides the delay between +*s for carry to propagate. No delay is needed for the initial add to 0
  • Short integer multiply: 9-bits x 9-bits yields 18-bits
    * nm-p a! 0 8 for +* unext drop drop a ;
    If the multiplicand (S) was shifted 9 places left the result is right-justified

11 2*

Shift T left; shift 0 into bit 0
  • Shift 1 into bit 0:
    - 2* -
  • Multiply by 3:
    dup 2* . +
  • Multiply by 5:
    dup 2* 2* . +
  • Multiply by 6:
    2* dup 2* . +
  • Multiply by 7:
    dup 2* dup 2* . + . +
  • Test right-port read-status:
    @b 2* -if
  • Test down-port read-status (after right-port):
    2* 2* -if

12 2/

Shift T right; sign bit unchanged
  • Divide by 4:
    2/ 2/

13 -

One's complement T
  • Test for positive:
    - -if
  • Negate:
    - 1 . +
  • Subtract T from S:
    - . +
    This result is 1 too small. It can be corrected during additional arithmetic
  • Subtract S from T:
    - . + -
  • Subtract T from S:
    push - pop . + -
  • Construct -1:
    dup or -
  • Construct -2:
    dup or - 2*
  • Construct +1:
    dup or - 2* -
    Same number of slots as literal 1

14 +

Add S to T (discard S)
  • Add:
    . +
    Dot required if stack has just changed. Otherwise carry can propagate only 9 bits

14 +

Add S to T with carry
Requires bit 9 of P be set
  • Set P9:
    200 org arithmetic . . . ;
    arithmetic
    P9 reset upon return from arithmetic
  • Add:
    . +
  • Return and clear carry: 1 if carry 1; 0 if carry 0
    dup dup or dup +
  • Return and preserve carry: 0 if carry 1; -1 if carry 0
    dup dup - . +
  • Set carry:
    dup or - dup + (trashes stack)

15 and

Bit-wise and of S and T
  • Mask low-order byte:
    ff and
  • Mask sign bit:
    20000 and
  • Return zero if T was a power of 2:
    dup -1 . + and

16 or

Bit-wise exclusive-or of S and T
  • One's complement T:
    3ffff or
    Not useful since it uses 5 slots instead of 1 ( - )
  • Change sign bit:
    20000 or
  • Inclusive-or:
    over - and or
  • nip:
    over or or
    Uses a stack location
  • swap:
    over push over or or pop

17 drop

Discard T
  • Discard R:
    pop drop
  • nip (discard S):
    push drop pop
    Uses a return stack location

Stack Instructions

18 dup

Create a working copy of T
  • construct a 0 (discards T):
    dup or
  • Preserve the stack while using it destructively:
    dup dup or
  • Square a number:
    dup *
  • Cube a number:
    dup dup * *

19 pop

Fetch R (destructive)
  • Retrieve something saved
  • Retrieve a loop index:
    pop dup push
  • Discard loop index and exit loop:
    pop drop ;
  • Discard a return address. Return to the caller's caller:
    pop drop ;

1a over

Fetch S (non-destructive)
  • Fetch an increment:
    over . +
  • Working copy of two numbers:
    over over
  • Swap T and S (leaving S behind)

1b a

Fetch A (non-destructive)
  • Retrieve something saved
  • Read address for decrementing:
    a over . +

1c .

Do nothing
  • Fill unused slots
  • Delay:
    !b . !b
  • Delay before add:
    . +
  • Delay loops:
    for . unext
    for . . unext
    for . . . unext

1d push

Push T into R
  • Set loop count
  • Expose deeper stack:
    push over
  • nip:
    push drop pop
    Uses a return stack location
  • swap:
    over push push drop pop pop
    Uses 2 return stack locations
  • Decrement non-zero number:
    push here 1 + next pop
  • clip:
    push max pop min

1e b!

Store into B
Be careful to distinguish b! and !b
  • Set @b !b address

1f a!

Store into A
  • Set @ ! address
  • Save top of stack
  • swap:
    push a! pop a