May the Forth Be With You

It's programming Jim, but not as we know it

Chris Wilson
By Chris Wilson
May 04, 2023

If you took your favorite programming language and then started pulling things out of it, where could you stop? Could you remove variables? Operator precedence? Braces, brackets, parentheses? Function arguments and return values?

First off, this seems like a weird thing to think of, even weirder to want. All those things: syntax, statements, functions, etc. all seem to contribute the essential character to a programming language. It’s been noted that no science fiction author was able to guess that we’d form tribes around the languages we use to speak to computers. I’d posit that there’s something to the fact that there are conferences for Ruby, Rust, Java, & etc. but not x86 assembly. We like a programming language to have panache!

But let’s run with it. What would this mutant language even look like? Could you warp your brain into programming in this way?

"Sickos" meme

Hello Uxn!

If you follow this idea through the twists and turns needed to make something implementable, you arrive in the land of Forth. Forth is a family of languages comprising specific implementations of varying features. They all share a lot of DNA, but it can be difficult to speak in generalities because any particular Forth may differ. I’ve even heard that part of the point of Forth is to implement your own, maybe on your own hardware.

In the interest of being specific though, I’m going to discuss one lovely system as being emblematic of the others. So I introduce Uxn!

Inside the Uxn machine

Uxn mascot

Uxn is a virtual machine, which is to say that it’s a program which simulates what would happen if you evaluate the bytes according to the design.

Some of the key factoids about Uxn:

  • A stack machine (2 stacks, main & return)
  • byte addressable
  • 64kb memory (0xffff)
  • 16 devices (or peripherals)
  • 32 aforementioned opcodes with 3 modes

It runs it’s own byte code stored in ROM files. If you imagine this as a fantasy 80s-era video game console, you’re not far off. In fact, Uxn feels like a computer from an alternate universe where we perfected personal computing in the 80s.

I’m not going to delve too much into it here, but the philosophy of the project is fascinating in its own right. It touches on topics such as permacomputing, personalization, and creativity.

Varvara computer

Uxn and Varvara mascots

A little note on naming. Technically Uxn is the CPU while Varvara is the personal computer system that sits atop it. As the project says itself: “Uxn is to Varvara, what the 6502 [CPU] is to the Classic Nintendo.”

There are graphical programs, mouse/keyboard support, audio, and console I/O. It’s the core CPU plus all the devices that surround and support it.

Implementation first

Uxn was created with the goal of balancing implementation with usage. Ideally, something that’s not too complicated to write the VM for while still being nice and comfy to write programs in. It ought to be a weekend project to code the VM and the assembler. You can write down a cheatsheet covering the opcodes without fuss:

LIT ~ [PC]     JCI ~ (c8){PC+=[PC]}   JMI ~ {PC+=[PC]}   JSI ~ {R.PC PC+=[PC]}

BRK ~          EQU a b==c             LDZ a b [c8]       ADD a b+c
INC a b c+1    NEQ a b!=c             STZ a {[c8]=b}     SUB a b-c
POP a b        GTH a b>c              LDR a b [PC+c8]    MUL a b*c
NIP a c        LTH a b<c              STR a {[PC+c8]=b}  DIV a b/c
SWP a c b      JMP a b {PC+=c}        LDA a b [c16]      AND a b&c
ROT b c a      JCN a (b8){PC+=c}      STA a {[c16]=b}    ORA a b|c
DUP a b c c    JSR a b {R.PC PC+=c}   DEI a b {[D+c8]}   EOR a b^c
OVR a b c b    STH a b {R.c}          DEO a {[D+c8]=b}   SFT a b>>c8l<<c8h

from Uxntal Opcodes

There’s bits in there having to do with how any VM or CPU would work. It’s helpful to jog your memory if you already know Uxn and are writing some code. But to understand this, we’ll have to talk a just little about stacks, CPUs, and architecture. Mostly, I want to talk about the forth-y bits, but some surrounding context will be helpful.

Forth

Here’s some examples from the library of common functions. Some overall syntax notes to keep in mind about Forth and the Uxntal assembly language itself:

  • Whitespace is a separator but is otherwise insignificant
  • Parentheses are comments, they extend up to the closing paren.
  • Numbers prefixed with a hash are hex literals. As an example #9a or #9abc are a single byte and a short (2 bytes) respectively.
  • %, percent, is a macro definition.
  • An @ is a label, it stands for the address of current location in the code. These are used as targets for jumps elsewhere in the code.

    • The ampersand, & is a sub-label. It when using that as a target you refer it by prefixing it with the name of the last label
  • The 32 Forth “words”/Opcodes also have 3 modes. They’re named by suffixing an opcode, (e.g. DIV with the given suffix -k to get DIVk)

    • 2 means that the opcode operates on 2-byte values, aka shorts.
    • r means that the opcode affects the other stack, the return stack.
    • k means that the opcode keeps the operand on the stack where it would otherwise discard it.

Modulo macro

To get some Forth flavor, let’s look at the modulo operation (remainder after integer division) macro:

%MOD { DIVk MUL SUB }

Elsewhere in the code, when we use MOD, it’ll be expanded out to the series of words enclosed in curly braces.

Arguments are implicit, but we know that DIV (and by extension DIVk) take 2 bytes off the stack and divide them. I’ll show what this would look like if used MOD with #09 and #02. In the following examples the value in parens is what would be on the stack after we did the operation.

#09 #02 DIV ( 4 )

The value deeper on the stack is divided by the shallower, more recently pushed item. This means that division (and subtraction) work in the conventional way, left to right.

In the above case though, we’re keeping the stack values and so:

#09 #02 DIVk ( 9 2 4 )

Having dealt with division, next we multiply:

#09 #02 #04 MUL ( 9 8 )

This time we pop two bytes 2 and 4, and then push the result 8. Finally, we do the subtraction with the two values on the stack:

#09 #08 SUB ( 1 )

And that’s our result. Notice that arguments and returns are both implicit and we didn’t assign any variables along the way.

This is a neat benefit of working with Forth. In fact, I have a calculator that works in this way:

HP 15C Calculator

The key presses for part of that calculation would be: 9 [ENTER] 2 [ENTER] 4 [*] [-] (this calculator doesn’t have a DIVk button). The 9 just waits patiently on the stack until we need it!

Leap year calculation

Let’s look at that old chestnut of determining if a year is a leap year or not (pesky solar system!)

@is-leap-year ( year* -- bool )

	( leap year if perfectly divisible by 400 )
	DUP2 #0190 ( MOD2 ) DIV2k MUL2 SUB2 #0000 EQU2 ,&leap JCN
	( not leap year if divisible by 100 but not divisible by 400 )
	DUP2 #0064 ( MOD2 ) DIV2k MUL2 SUB2 #0000 EQU2 ,&not-leap JCN
	( leap year if not divisible by 100 but divisible by 4 )
	DUP2 #0003 AND2 #0000 EQU2 ,&leap JCN
	( all other years are not leap years )
	&not-leap
	POP2 #00

JMP2r
	&leap POP2 #01 JMP2r

We expect that there’s a 2-byte year on the stack when we start. I’m going to use my favorite futuristic leap year, 2000. First we duplicate the year, and push the constant 400 (in hex):

#07d0 DUP2 #0190 ( 2000 2000 400 )

The next part we know, because we just did it! The following DIV2k MUL2 SUB2 is just the two-byte version of MOD we worked through. Then we’ll just check to see if the result is equal to zero (it is), meaning that the year was divisible by 400 and so yes leap year:

#07d0 #0000 #0000 EQU2 ( 2000 1 )

Now we end up with 1/true value on the top of the stack:

#07d0 #01 ,&leap JCN

JCN is a conditinal jump. Since the result here is true, we take the branch and jump to the sublabel &leap.

Now there’s a little cleanup, we still have an extra date sitting on the stack and we still need to return the bool result:

&leap POP2 #01 ( 1 )

The last bit, JMP2r, is basically a “return” statement.

Lastly…

I hope that gives you a taste of Uxn and more generally Forth. I’m finding working with the both of them to be a truly mind-expanding experience. It’s fun to try and create teensy-tiny ROMS that are nonetheless useful and fun. Take a breather from the always-on connected world and dip your toes into something that’s offline, hand-made, and refreshing.


Further Reading

If you’re looking for a team to help you discover the right thing to build and help you build it, get in touch.