With only 32 5 bit opcodes obviously some operations will require more than a single instruction. The following examples show how to accomplish some simple operations that are not primitives in a Green Arrays chip.
Swap is not a primitive, though over is. The easy way to swap the top two items on the stack is to perform 'over' and abandon the item left in the third position. Often this is the simplest and best solution. For those times when you can't afford to abandon the third item, here are some alternatives:
Use over to swap the top two items, then use drop later when the unwanted item is uncovered.
Use the a register to temporarily hold one item. The downside is that you've corrupted the a register, but sometimes you don't care about preserving the a register.
over ... drop
Use the return stack to temporarily hold both items while you drop the third. The downside is that you've used two return stack positions.
push a! pop a
Use an extra position on the data stack to temporarily hold an item. The downside is that you've used another data stack position.
over push push drop pop pop
over push over or or pop
Nip means to drop the second item on the stack. The first thing you should think is "can I simply abandon this item, or do I really need to drop it?" If it really needs to be dropped, can you leave it there until it naturally reaches the top of the stack, and then drop it? If it really needs to be dropped immediately, there are two ways to go about it.
The first consumes an extra return stack location. The second consumes an extra data stack location. If you're only going to use it once then there is no need for the definition. Save memory by coding it inline where it's needed.
push drop pop ;
over or or ;
Instead of using the literal 0, which takes 5 instruction slots, put a 0 on the stack with three instructions:
If you don't need to preserve what was already on top of the stack, use only two slots:
dup dup or
There is an add instruction but no subtract.
An obvious way to perform 2's complement subtract is to negate the top number and add. This requires getting the literal 1 onto the stack, consuming both time and memory.
If you can arrange for the two numbers to be swapped such that you're subtracting the second from the first you can avoid putting the literal on the stack and adding it.
- 1 . + . +
Best of all, if you're subtracting a constant, simply negate the number at edit time. Add a negative number, -2 in this example.
- . + -
-2 . +
It's easy to make a computed goto operator called jump. Its function is to execute the nth word in a list following the word jump. Each word following jump (except maybe the last one) must be a jump itself. The programmer must take care not to allow the index to exceed the length of the list.
. is not required before + in this case because we don't care about carry propagation in the high bits when the P register is only 10 bits wide.
In the code below feeding a 0 to example would cause zero to be executed. A 1 would cause one and a 2 would cause two to be executed.
pop + push ;
jump zero ; one ; two ;
Min and Max
If you only need one or the other then use one of the following:
If you need both min and max in the same node the following works and saves some memory:
- over . + - -if + ; then drop ;
- over . + - -if drop ; then + ;
- over . + - -if begin + ;
- over . + - -until then drop ;
Multiply by a Constant
The 2* instruction multiplies by two in a single cycle by shifting the top of the stack left once. Here are some words that multiply by other small constants in only a few cycles.
dup 2* . + ;
dup 2* 2* . + ;
2* 5* ;
If you reserve a location in memory to use as a variable you'll need to use an address register, a or b, to fetch and store the variable. A more efficient way is to create a value, as follows, for example:
If you're really clever, you might let the value word (the word x above) do more than just leave a number on the stack, for example:
@p drop !p ;
The default delay value will be 100, but that value can easily be changed by !delay without using the a or b registers.
@p drop !p ;
100 for . . unext ;
Interacting with a neighbor
The default state of each non-boot node is a multiport jump to each of its neighbors, quietly waiting for instructions from one of them. To feed instructions to a neighbor you just need to point to the neighbor using either the a or b register and then store instruction words via that register. The following code uses a the right neighbor just for its memory:
In the words above, the phrases between single quote comments are each placed on the stack by a @p instruction and sent to the right neighbor by a ! instruction.
dup a! ! ;
@p ! ! ..
@p a! @ !p
@p ! ! ..
@p a! @p ..
! @p ! ;
dup dup or @p
and dup put get
. + end
Let's look at this short program from the top down first, the word
The first phrase is
dup dup or @p. This simply puts a zero on the stack using only one word of RAM, then fetches the next word in RAM onto the stack. That next word will be a call to the right port,
r---. The source code is surrounded by white single quotes. This is a convention used to make code stand out when it is intended to be sent to and executed by a neighbor.
Next we have a call to
focus. Note that the code for focus,
dup a! ! ; only occupies a single word. It would save a bit of memory to simply code it inline instead of as a call. Making
focus a separate definition is a matter of style in this case. It makes the code easier to read and understand. If you're trying to squeeze this code into a tight space and every word counts then you might want to code the focus inline to save space. What
focus does is to call the right port. Now instruction will only be recieved from the right neighbor, and more importantly, data will be written back only to that neighbor via
!p rather than to all neighbors, some of whom may be listening!
Now we begin a loop of sending and receiving data to and from the right neighbor. There is a zero on the stack already.
and ensures that this number riding on the stack will always be a reasonable address in RAM for reading or writing. We
dup put the word out to the neighbor as both address and data, then
get it back, increment it by one, and repeat the process endlessly.
get occupies three words of RAM. The first instruction word is
@p ! ! .. which fetches the following word of RAM onto the stack, sends it to the right port, then sends the word already on the stack to the right port as well. The next word, compiled by the phrase
@p a! @ !p is sent to and executed on the right neighbor. The neighbor fetches a word of data from the right port onto its own stack, stores that into a to be used as an address, fetches data from the address stored in a, and sends that word back to the original node. The last instruction word in get is
@ ; which fetches that piece of data back from the right neighbor onto the stack and returns back to test.
Finally the word
put begins with the same phrase as the word
@p ! ! .. fetching an instruction word and sending it along with an address to the right neighbor. There is too much work to be done for it to fit into a single instruction word, so first we send
@p a! @p .. in order to tell the neighbor to fetch both the address and the data to be stored. The following instruction word
! @p ! ; sends the data to the neighbor and then fetches and sends another instruction word before returning. That final instruction word
! .. causes the neighbor to store that data word to the address contained in a.
Since this is an endless loop, we never tell the neighbor to return from the call to
focus. If the loop were to end, the last instruction word send to the right neighbor would end with
; so that the neighbor could return to its previous state.