FPGA logo large

Meet the Firth of Forth

I am taking a short break from FPGA topics because I want to introduce you to Firth. Firth is an experimental programming language heavily inspired by Forth. Its design goals are to be small, fast and efficient, portable and embeddable.

Forth was designed by Charles Moore in 1968 to be a procedural, stack-oriented programming language and interactive environment. Forth is well-suited to running on small devices, which is why this is ultimately relevant to our interest in FPGA topics.

If you are new to Forth, you can learn a lot of the basics at Easy Forth. I also highly recommend Starting Forth by Leo Brodie of Forth, Inc.

Firth was created initially as an exercise in learning Forth. And to have an environment for learning through experimentation. The original plan was to create a simple Forth system, written in C++. However, along the way it became clear that there were some aspects of Forth that I wanted to change. Mainly small things to modernize the syntax to make it a little bit easier for beginners.

Rather than create a version of Forth that might not be compatible with existing Forth code, it seemed a better idea to create a language heavily influenced by, and largely compatible with Forth. While still retaining the option to break basic Forth compatibility when and where that made sense. That said, at this time Firth remains largely Forth compatible – what could be called an enhanced subset of the complete ANS Forth language.

If you are already familiar with Forth you may find the notion of creating a new Forth-like language to be strange. After all, a key feature of Forth is the ability to alter the compiler and completely redefine existing behavior.

Basics of the Firth language

In Firth code, as in Forth, there are two key building blocks. There are Numbers and there are Words. In Firth Words and Numbers are separated by spaces. and, by default, Firth is case insensitive. Firth, also like Forth, normally presents the user with an interactive command-line environment, commonly referred to as a REPL, or Read-Evaluate-Print-Loop.

Numbers

Everything that is not a Word is a Number. Numbers largely live on the data stack. When you type in a number, Firth pushes it onto the stack. Parameters (or arguments) to a word are usually passed on the stack. The result of a word is usually placed onto the stack.

Arithmetic in Firth, as in Forth, uses Reverse Polish Notation (RPN). To add two numbers together in Firth you would write:

Firth> 1 2 +
 ok

This code pushes the number 1 and then the number 2 onto the stack. And then it adds those two numbers together, replacing them with the result on the stack. To view what is currently on the stack there is a word named .S. This word prints out, without modifying, the contents of the stack.

Firth> .S
Top -> [ 3 ]
 ok

Words

Words are really just another name for functions. When you type in an existing Word it is executed. New words are composed from existing words and numbers.

It is very easy to create new words. Firth word definitions start with the word func and end with the semi-colon (;) word. To see how this works let’s create a new word for addition as follows:

Firth> func add + ;
  ok

Firth> 1 2 add
  ok

Firth> PRINT
3 ok

This code creates a new word named add that calls the existing word + to add the top two stack entries and put the result back onto the stack. The Firth word PRINT then removes and prints the top entry on the stack.

More Firth Examples

Below are a few examples of Firth in action. First, the canonical ‘hello world’ program.

Firth> func hello ." Hello World! " ;
  ok

Firth> hello
Hello World!
  ok

This code creates a new word named hello that uses the built-in word .” (dot-quote) to print out a string. Note that the spaces after dot-quote are required. The space before the trailing quote is optional. Typing in our new word hello causes it to be executed, displaying our message.

Next, we define a new word called fibonacci that computes the Fibonacci sequence for the number currently on the stack.

Firth> func fibonacci DUP
    0<> IF DUP 1 
            <> IF
                0 1 ROT 1- 0 FOR DUP ROT + LOOP NIP
            ENDIF
        ENDIF
;
  ok

Firth> 9 fibonacci PRINT
34  ok

The above example illustrates a number of new words, the Word reference section below explains a bit about each word.

From Forth to Firth

You may be asking, where does Firth diverge from Forth? In nearly all cases changes involved adding “syntactic sugar” to modernize the feel of the code. These are implemented as word synonyms. Because of that, you can continue to use Forth syntax if you prefer. Or you can migrate to the more modern Firth syntax when and as you wish. Here are the word synonyms that Firth offers:

ForthFirthComments
VARIABLEVARThe keyword var is common in modern languages
CONSTANTCONSTThe keyword const is common in modern languages
:FUNCA colon feels obscure for a modern function declaration
THENENDIFThe IF-ELSE-ENDIF pattern is more common than the Forth IF-ELSE-THEN construct
DOFORThe limit and index parameters make this is a FOR loop by modern standards

At the moment I prefer func as a colon synonym. It is short yet descriptive, which keeps to the spirit of Forth word naming. So that is what I’ve documented here. That said, I am still actively debating whether to use the Golang fn, Swift’s func, Javascript’s function or Python’s def as the preferred replacement for colon. For now, they all exist to see which feels better. If you have strong opinions, please let me know! You can configure the defaults by editing firth_config.h.

Embedding Firth

Firth is designed to be very easy to embed into other apps. Doing so requires integration of one .cpp and one .h file (firth.cpp and firth.h) and just a few Firth API calls. Adding in optional floating point support words involves one additional API call.

The file main.cpp demonstrates how to initialize Firth and add custom words for a constant, a variable, and a native function. The important excerpts of main.cpp are shown below.

#include "firth.h"

// custom word functions
static int isEven(Firth *pFirth)
{
	auto n = pFirth->pop();
	pFirth->push((n % 2) == 0 ? FTH_TRUE : FTH_FALSE);

	return FTH_TRUE;
}

static int isOdd(Firth *pFirth)
{
	auto n = pFirth->pop();
	pFirth->push((n % 2) ? FTH_TRUE : FTH_FALSE);

	return FTH_TRUE;
}

// register our collection of custom words
static const struct FirthWordSet myWords[] =
{
	{ "even?", isEven },
	{ "odd?", isOdd },
	{ nullptr, nullptr }
};

FirthNumber tickCount;

// examples of calling Firth from native code
void callFirth(Firth *pFirth)
{
    // do_word is a set of convenience methods to push 
    // 1, 2, or 3 parameters on stack and execute a word
    pFirth->do_word("+", 1, 2);
	
	// execute any defined word, no passed parameters
    pFirth->exec_word(".");
}

int main()
{
    Firth *pFirth = new Firth();

    // load core libraries
    pFirth->loadCore();

    // add custom words that can be called from Firth
    pFirth->register_wordset(myWords);

    // add a const and a var
    pFirth->define_word_const("APP.VER", 1);
    pFirth->define_word_var("System.Tick", &tickCount);

    // parse Firth
    int active = FTH_TRUE;
    while(active)
    (
        active = pFirth->parse();
        tickCount++;
    );

    return 0;
}

Testing Firth

I have created a small test harness to allow testing words in Firth. This framework is called test.fth and is located in the test sub-folder. You load the framework as follows:

Firth> include test\test.fth
  ok

I have also created a set of unit tests for the core Firth words. These unit tests are also found in the test sub-folder in a file called core-tests.fth. Once again you load the tests by including the file.

I found that having unit tests was critical to the development of this project. It allowed me to ensure that newly added words functioned correctly. But even more importantly, having a rich set of unit tests allowed me to re-factor and optimize code with confidence.

The core-tests.fth file both defines and runs the unit tests. If all goes well a message will be displayed saying that All tests passed! If not, Firth will halt at the test that failed.

Firth> include test\core-tests.fth
Test drop
        1 √
Test swap
        2 √
Test dup
        3 √
Test max
        4 √
        5 √
        6 √
Test min
        7 √
        8 √
        9 √
Test negate
        10 √
        11 √
        12 √
Test abs
        13 √
        14 √
        15 √
Test nip
        16 √
Test not
        17 √
        18 √
Test or
        19 √
        20 √
        21 √
        22 √
        23 √
Test xor
        24 √
        25 √
        26 √
        27 √
Test 2dup
        28 √
Test 2drop
        29 √
Test ?dup
        30 √
        31 √
Test */
        32 √
        33 √
        34 √
Test <
        35 √
        36 √
        37 √
Test >
        38 √
        39 √
        40 √
Test =
        41 √
        42 √
Test <>
        43 √
        44 √
Test 0=
        45 √
        46 √
Test 0<
        47 √
        48 √
        49 √
Test 0>
        50 √
        51 √
        52 √
Test 0<>
        53 √
        54 √
Test and
        55 √
        56 √
        57 √
        58 √
        59 √
Test over
        60 √
Test pow
        61 √
        62 √
Test rot
        63 √
Test tuck
        64 √
Test +
        65 √
        66 √
        67 √
Test -
        68 √
        69 √
        70 √
Test *
        71 √
        72 √
        73 √
Test /
        74 √
        75 √
        76 √
Test mod
        77 √
        78 √
        79 √
Test /mod
        80 √
        81 √
        82 √
Test sqr
        83 √
        84 √
        85 √
        86 √
Test 1+
        87 √
        88 √
        89 √
Test 1-
        90 √
        91 √
        92 √
Test DO LOOP
        93 √
Test depth
        94 √
        95 √
        96 √
Test IF ELSE THEN
        97 √
        98 √
        99 √
        100 √
        101 √
        102 √
All tests passed!

Built-in Firth Words

There are a (growing) number of basic words that have already been defined in Firth. They are:

WordDescriptionStack effects
ABStake absolute value of TOS( n — |n| )
AGAINloop back to BEGIN( — )
ALLOTreserve n extra cells for array( n — )
ANDbitwise AND( n1 n2 — n3 )
BEGINstart an indefinite loop
BELemits a BEL char( — )
BLprints a space( — )
CELLScalculate cell count for array size( n — n )
CONSTdefine a new constant( n — )
CRprint a carriage return( — )
DOstart a definite loop( — )
DROPdiscard TOS( n — )
DUPduplicate TOS( n — n n )
ELSEstart of else clause( — )
EMITprint TOS as ASCII( n — )
EXITexit from current loop( — )
FALSEconstant representing logical false( — f )
FUNCbegin definition of new word( — )
Iput current loop index on the stack( — n )
INCLUDEload and parse the given Firth file( — )
IFstart a conditional( f — )
LFprint a line feed( — )
LOOPinc index by 1, end of definite loop( — )
+LOOPinc index by TOS, end of definite loop( n — )
LSHIFTlogical shift left n, u places( n1 u — n2 )
MAXleave greater of top two stack entries( n1 n2 — n1
MAX-INTputs largest representable int value on stack( — n )
MINleave lesser of top two stack entries( n1 n2 — n1
MIN-INTputs smallest representable int value on stack( — n )
MODcompute remainder( n1 n2 — n3 )
NEGATEchange sign of TOS( n — -n )
NIPdiscard the second entry on stack( n1 n2 — n2 )
NOTbitwise NOT( n1 n2 — n3 )
ORbitwise OR( n1 n2 — n3 )
OVERdupe the second stack entry to the top( n1 n2 — n1 n2 n1 )
POWraise x to power of y( x y — x^y )
REPEATloop back to BEGIN( — )
ROTrotate the top 3 stack entries( n1 n2 n3 — n2 n3 n1 )
RSHIFTlogical shift right n, u places( n1 u — n2 )
SWAPswap top two stack entries( n1 n2 — n2 n1 )
TABprints a tab char( — )
THENend of IF conditional( — )
TRUEconstant representing logical true( — f )
TUCKcopy the top stack item below the second stack item( n1 n2 — n2 n1 n2)
UNTILend of indefinite loop( — )
VARdefine a new variable( — )
WHILEtest whether loop condition is true( — )
WORDSlist all words in the dictionary( — )
XORbitwise XOR( n1 n2 — n3 )
2DUPduplicate top two stack entries( n1 n2 — n1 n2 n1 n2 )
?DUPduplicate TOS if it is non-zero( n1 — n1 | n1 n1 )
;end definition of new word( — )
+addition( n1 n2 — n3 )
subtraction( n1 n2 — n3 )
*multiplcation( n1 n2 — n3 )
/division( n1 n2 — n3 )
*/multiply then divide( n1 n2 n3 — n4 )
/MODremainder and quotient( n1 n2 — n3 n4 )
<less than comparison( n1 n2 — f )
>greater than comparison( n1 n2 — f )
=equivalence comparison( n1 n2 — f )
<>not equivalence comparison( n1 n2 — f )
0=true if TOS is zero( n — f )
0<true if TOS is less than zero( n — f )
0>true if TOS is greater than zero( n — f )
0<>true if TOS is not equal zero( n — f )
.print TOS( n — )
.Snon-destructively print the stack contents( — )
.”print the following ” delimited string( — )

Configuring Firth

Firth is designed to be somewhat configurable. Configuration parameters are adjusted by editing the file firth_config.h and rebuilding Firth. The configurable parameters are:

ParameterDescription
FTH_UNDEFINEDThe value placed in memory for uninitialized variables
FTH_CASE_SENSITIVEControls compiler case sensitivity
FTH_INCLUDE_FLOATControls whether floating point is compiled into Firth
FTH_MAX_WORD_NAMESets the limit for the length of a word name
FTH_DEFAULT_DATA_SEGMENT_SIZESets the default size reserved for variables
FTH_MAX_PRINTF_SIZESets the limit for the length of a firth_printf result
FTH_EPSILONDefines epsilon for floating point precision

Forth traditionally does not include support for floating point. Firth by default does include basic floating-point support, adding a floating-point number stack, float variables and load/store operations.

If memory space is at a premium and you do not need or want floating-point support, you can edit the file firth_config.h to remove it.

By also including the files firth_float.cpp and firth_float.h you can install a set of floating-point support words.

   #include "firth.h"
    #include "firth_float.h"
    ...
	FirthFloat fSimTime;

    // load (optional) floating point libraries
	firth_register_float(pFirth);

    // create a float const and var
    pFirth->define_word_fconst("PI", 3.1415926);
    pFirth->define_word_fvar("SimTime", &fSimTime);

Below is a list of the additional words that the floating-point library includes. In the listing below TOFS represents the top of float stack.

WordDescriptionStack effects
F+float addition( f: n1 n2 — n1+n2 )
F-float subtraction( f: n1 n2 — n1-n2 )
F*float multiplication( f: n1 n2 — n1*n2 )
F/float division( f: n1 n2 — n1/n2 )
F.print TOFS( f: n — )
F@fetch a float variable( s: addr — f: n )
F!store a float variable( f: n s: addr — )
F<less than comparison( f: n1 n2 — s: f )
F>greater than comparison( f: n1 n2 — s: f )
F=equivalence comparison( f: n1 n2 — s: f )
F<>not equivalence comparison( f: n1 n2 — s: f )
FABSabsolute value of TOFS( f: n — abs(n) )
FCONSTdefine a new float constant( f: n — )
FCOScosine of TOFS( f: n — cos(n) )
FDEPTHput depth of float stack on data stack( s: — n )
FDROPdrop the TOFS( f: n — )
FDUPduplicate the TOFS( f: n — n n )
FEXPexp of TOFS( f: n — exp(n) )
FLNnatural log of TOFS( f: n — log(n) )
FNIPdiscard the second entry on stack( f: n1 n2 — n2 )
FOVERcopy the second stack entry to the top( f: n1 n2 — n1 n2 n1 )
FROTrotate the top 3 stack entries( f: n1 n2 n3 — n2 n3 n1 )
FSINsine of TOFS( f: n — sin(n) )
FTANtangent of TOFS( f: n — tan(n) )
FTUCKcopy the top stack item below the second stack item( f: n1 n2 — n2 n1 n2)
FSQRTsquare root of TOFS( f: n — sqrt(n) )
FSWAPswap the top two float stack entries( f: n1 n2 — n2 n1 )
FVARdefine a new float variable( f: — )
.Fnon-destructively print the float stack contents( f: — )

Where is the Forth Syntax Highlighting

You may have noticed that the Firth/Forth code presented is not using syntax highlighting. That is because Forth is not currently a programming language supported by the WordPress plugin. As you may recall from the Syntax Highlighting Revisited post, I created a WordPress plugin for Verilog. I may have to do the same for Firth/Forth.

Finding Firth

The Firth project is available on github, you can find it here. If you find Firth to be useful, please leave a comment. Forth is designed to be self-hosting, but the current Firth project is a long way from that goal. Who knows, in the future it may be possible to make Firth portable enough to migrate to a soft-core processor running on an FPGA.

Please remember to Like and Share these posts on social media. And also remember that if you have feedback, questions or suggestions regarding this or other posts, please leave me a comment!

Discover more from FPGA Coding

Subscribe now to keep reading and get access to the full archive.

Continue reading