Sunday, February 13, 2011

Forth Parameter Stack, Named Parameters and Local Variables

I've been meaning to write an article about local variables for while.

Parameter Stack

So in the Forth programming language, normally most working data is kept on the stack - both parameters passed into functions and also local working data (which would be kept in local 'automatic' variables in C). You can also use named global variables for state data outside of this. (I'll ignore Forth object-oriented extensions for this discussion).

Apart from getting used to remembering the stack positions (and writing 'stack diagrams' - more in a bit) this works much better than it sounds. And allows you to pass data to working functions ('words' in Forth terminology) automatically. For instance:

  : print_num ( number -- ) . ;
: print_5_numbers ( n1 n2 n3 n4 n5 -- ) 5 0 DO print_num LOOP ;


It would be used like this:

  1 2 3 4 5 print_5_numbers


And would display:

  5 4 3 2 1    ok


'ok' (or 'OK') is the standard reply when things go well from Forth.

The word (function) 'print_num' is a bit of a silly example, but hopefully you get the idea.

The things in brackets are comments in Forth, and you must remember to follow the leading open bracket '(' with a space. All words in Forth are separated by spaces.

The comments in this examples are called stack diagrams. It shows you what parameters get passed into a word and, after the double dash ('--'), what gets returned. It is typical for Forth words to consume their parameters, but not mandatory. So print_num consumes one parameter (a number) and prints that number with the dot word ('.'). The word print_5_numbers has a loop in it iterates 5 times and then prints each number. The loop control variables are kept separately (on a separate stack called the return stack) so don't interfere with the parameters (which are on the parameter stack).

The stack comment for the built-in word dot ('.') operator would be:

  ( number -- ) 


More Stack Diagrams

Here is the stack diagram for the build-in (core) word 'dup' (which duplicates the element on the top of the stack):

 ( n -- n n ) 


Here is the stack diagram for the core word 'over', that gets a copy of x1 to the top of the stack:

 ( x1 x2 -- x1 x2 x1 ) 



Considerations regarding Named Parameters and Local Variables

Good Forth programming style says keep Forth words very short. In fact usually the inner interpreter of Forth is designed with this in mind so that calling Forth words is very low overhead - as close to none as possible.

Compare this with the C programming language where local variables need to be saved before the call and parameters set up. Then, once you call the C function, the stack frame (with local variables) must be set up. Upon return the opposite process must be followed. This means in speed critical code, calling lots of C functions (either in depth or sequentially) is usually not efficient - although it could be argued in practice this is a tiny fraction of code. I've certainly seen it more than once. (Some modern RISC machines have leaf routines that minimise this to compensate - although this only usually works one level down. Additionally 'inline functions' can help). In Forth, however, this can all add up because Forth words are everywhere - even at a low level, except on a native compiling Forth.

Short word definitions also means you can debug them as you go and also has the side effect the stack manipulation tends to be easier.

Practical Named Parameters and Local Variables in Forth

However, sometimes stack based parameters are a pain. And this is where local variables come in. I think it's fair to say that a significant fraction of Forth programmers are not keen on local variables. One reason might be that it encourages beginners from other languages to use them everywhere and not understand the stack. Another reason is that they are (a lot) less efficient (speed-wise) than the parameter stack.

I've found when you have a word that needs them, their benefits in clarity far outweighs any penalty in terms of speed or inelegance.

So what do they look like?

Well, slightly annoyingly, this was one area the 1994 Forth Language standard couldn't agree on a syntax. They did define a base set of words that allow you to implement whatever local variables you want (partially leveraging on Forth's extendibility). However, I'm going to use the syntax in pForth and ByVac Forth.

For named parameters, you basically use curly braces (brackets) like '{' and '}' instead of the ( ) style brackets in a stack diagram. The names before the '--' become the parameter names, and the names afterwards are treated as a comment.

  : test { n m -- x } n m + ; 


The x is a comment here that tells you there will be one return variable. Let's compare that with the same example without named parameters:

  : test ( n m - x ) + ;


You could argue in this case it's more complicated with the named parameters!

Let's have an example that I think does warrant it - from the display board Forth code:

: (testports) { testing_func_xt portnum -- }
portnum . ." - Test? Y/N" KEY CR
dup [CHAR] y =
IF
drop
portnum testing_func_xt EXECUTE
ELSE
[CHAR] n <>
IF
." Unknown Key - Aborting"
ABORT
THEN
THEN
;


This hardware test word is called from another Forth language word that passes in an output port number to test and the address of a different Forth word to call ('execute') that tests that a particular type of port. The details aren't overly important.

But this routine was much quicker to write with named parameters.

Non-Parameter Local Variable

You can also define local variables that are different to global variables and have a scope (visibility) of just the function. Again this is different between different Forth's - even between pForth and ByVac Forth. This is a rather trivial example, with pForth syntax. The local variables in pForth are after the bar ('|').


: test2 { n | m -- } ( n is the parameter passed in. m is the local variable after the bar )
3 -> m ( set m to 3 )
n m + ( fetch n and m and add them together )
. (print the number)
;


Using this as follows:
  2 test2


Gives:
  5    ok


Conclusion

If you read this far, congratulations!

There are more details about local variables in most Forths I haven't covered. But hopefully you can see it's all rather trivial.

Additionally, implementing different syntaxes is not too hard because of the extendibility of Forth - so if you can implement your favourite syntax in most Forths.

Saturday, January 22, 2011

Bauble displayed

One of Claire's graphics

It works!! Christmas tree display

This is animated with twinkling lights, but that's obviously not apparent from this still shot.

Controller board mounted

With 20 row and 24 column wires connected.

Wooden Dividers Holding Apart Wires

Done last week - to avoid the horizontal column wires and vertical row wires from touching in the middle. They hold down the row wires and hold up the column wires.

Saturday, January 15, 2011

LED display progress

Now 479 LED's soldered onto the board. One LED must have got damaged - so I've ordered a replacement. (Claire did loads of winding and soldering - and found a location with two LEDs soldered on... what a wonderful wife!)

Additionally I've got a 'computer emulation' of the display. It uses the same high level Forth display words and a different set of low level words to display it in a text terminal window.

I used pForth on the computer. pForth a public domain Forth written by Phil Burk in C and it's very portable. I only needed to modify it for one specific thing - to add nanosleep for timing.

Although BVForth has some weird words for Forth (for, next, // commands, VSPACE$, ms) I was able to emulate all of those with little problems. The '&' preceding a hex number is a bit of a pain for a Forth engine. The normal Forth syntax is characters separated by space - so special parsing would require a new accept - not major problems - but it moves it away from standard Forth. Anyway I've limited the hex numbers to &ffffff and all other numbers are decimal. Not a major problem.

Getting closer...

Tuesday, January 04, 2011

Hundreds of LED's - text

(This was meant to be posted with the previous picture)

One hundred exactly left to do. Lots of help from Claire, Ron, Linda.

Made slightly more difficult by the fact that about 50 have resistors soldered on the leg of the LED right next to the head under plastic covering. Another good number had three or four wires soldered onto the LED which needed to be reduced to two.

However, the general process is: cut from chain, strip ends, test for cathode/anode and mark, twist wire, twist onto grid of tinned copper wires, solder. Very time consuming.

Hundred of LEDs!!

Monday, January 03, 2011

Testing a port

Here is a bit of code that I've been using to test microprocessor pins (also know as ports).

// ( port# -- )
: test_port0
// program port as an output
." Testing port 0." dup . ." ->"
dup p0_out_bit
BEGIN
dup p0set_bit ." 1"
500 ms
dup p0clear_bit ." 0"
500 ms
KEY? UNTIL
KEY drop
drop
CR
;


This word turns the selected output on and off every half a second (500 milliseconds) until a key is pressed. ('Word' is the Forth name for what other languages call functions, procedures or methods)

I won't explain all of the detail, but here are some bits worth noting.
  1. The // comment is supported on this Forth platform but not normally. The more traditional backslash (\) to end of line comments and round brackets (parentheses) are also supported.
  2. p0_out_bit, p0set_bit and p0clear_bit are words I created earlier. These do the following: p0_out_bit turns a particular pin (port) into an output, p0set_bit turns a pin high (on, 3v) and p0clear_bit turns a pin low (off, 0v).
  3. Forth syntax is words or numbers separated by spaces.
  4. Forth stores values on the stack, so 500 ms will store 500 onto the stack and the word 'ms' on this Forth system waits for that number of milliseconds.
  5. The Forth word ':' (colon) defines a new word called the name that follows it. The Forth word ';' (semicolon) ends that new word creation process.
  6. The BEGIN marks the start of the loop and the UNTIL marks the end of the loop. UNTIL will loop until it gets a true (In the above case KEY? produces a true when a key is ready).
  7. This Forth system is not case sensitive - so it doesn't matter whether the words are spelt DROP or drop.
To use this word you would type:
31 test_port0
This will print:
Testing port 0.31 ->setting31
101010101010101010
On this system, port 0.31 is the on-board LED, and hence this particular example will flash the LED. (Note: the 'setting31' is test output printed by p0_out_bit)

Update on ‘broken’ output ports and other trivia

P0.2/P0.3 (port not going high) -- 10K pull up on each port fixed problem.

P1.22 (port ok, row transistor not going high) -- solder short on output resistor on controller board. No danger to any device, short removed.

P1.28 (port being low) -- This was a bit of solder between the actual microprocessor IC pins for P1.28 and VSS_A. Moved with a scalpel. (Most likely during construction of controller board rather than a manufacturing fault on the microprocessor board).


P0.23 -- The ‘flickering’ on logic high seems to occur when my hand or the whole board is near the computer. This doesn’t happen on other ports. Logic low doesn’t seem to do this. When in circuit it seems ok (although slightly noisy under same circumstances) - so I’m going to leave it for now and see if it causes a problem on that row. The port voltage itself seems ok, and the transistor for that row isn’t turning on.

For interest, here are the port assignments used for the display.

Function -- Position -- Port

Row Select Number (Inverted) -- bit 0 (LSB) -- P0.2
Row Select Number (Inverted) -- bit 1 -- P0.3
Row Select Number (Inverted) -- bit 2 -- P0.4
Row Select Number (Inverted) -- bit 3 -- P0.5
Row Select Number (Inverted) -- bit 4 (MSB) --P0.6

Column Enable (1=on) -- 7 to 23(MSB, left-most of graphic) -- P1.16 to P1.31
Column Enable (1=on) -- 2 to 6 -- P0.25 to P0.30
Column Enable (1=on) -- 0 (LSB, right-most of graphic) to 1 -- P0.22 to P23

NOTES
  • Row Select Number 0 = top row
  • Row Select Number 19 = bottom row


The split of column enabled into three ranges is mainly due to the available ports on the microprocessor board, but this are not a problem since we operate basically in the range of milliseconds, and code to split up the graphic is trivial. (I’ll share this in a future post.)



A Note on terminology:

I use the term microprocessor and microprocessor board rather than microcontroller and microcontroller board in order to clearly separate it from the LED display ‘controller board’ in this blog post and the previous one.
Newer›  ‹Older