File Type Mode Size
.gitignore file 644 10
Makefile file 644 210
README.md file 644 4139
compile.s file 644 9141
cortex-m0.ld file 644 311
data.s file 644 495
debug.gdb file 644 57
macros.inc file 644 1092
platform-nrf51822.s file 644 2099
vectors.s file 644 380
vm.s file 644 5274
words.s file 644 2880
README.md

Cortex-M0 Stack Machine

This implements a rough bare-metal stack machine that works on Cortex-M0 devices (or possibly anything that supports Thumb2, but I've only tried it on M0).

Building

Currently the only target is the micro:bit as emulated by QEMU. There's a good chance it might work on hardware but I don't have one and I haven't tried it.

If you have an ARM toolchain installed (i.e. GNU binutils that start with arm-none-eabi-), you should be able to just do:

$ make

And you'll get a vm.elf.

Running

You will need qemu-system-arm, which should be included with any reasonable copy of qemu. You can run vm.elf like so:

$ qemu-system-arm -machine microbit -serial stdio -device loader,file=vm.elf

You should get a > prompt. Control-C will kill qemu.

Theory of Operation

The data stack is native 32-bit words, stored at the top of memory using the hardware stack and hardware push/pop instructions. This means it grows downward toward the bottom of RAM.

At the bottom of RAM is the input buffer, the control stack, a pointer to the next compile location ("scratch space"), then the environment. On startup, built-in words are copied into RAM and the REPL loop begins.

The REPL input parses space-separated "words" and compiles them into native code in the scratch space. There are three kinds of words - literal values, regular words, and definitions.

A literal value is a $ followed by hex digits defining up to 32 bits of data. This value is pushed to the stack.

> $31

A regular word is any sequence of characters not starting with a $ or :. It names a function, which is called. See below for built-in words.

> $31 putch
0

A definition word is a : followed by any sequence of letters not containing a space. This collects all prior words on the input line and defines them as a new word with the given name. The collection of currently defined words is called the "environment".

> $31 putch :print_one


> print_one
1

The stack is maintained between input lines.

> $31


> putch
1

Popping too many values will cause a hardware fault accessing out-of-bounds memory and call the fault handler, which will attempt to point you at the word that failed. It will then reset the CPU.

> pop
  ^halted!

>

Unfortunately, the environment is reset on startup, so mistakes can make progress somewhat difficult. 😅

This is obviously very FORTH-inspired, as pretty much all interactive stack machines are.

Built-in words

A good stack machine has a lot more words than this. But this was enough to test with.

pop - ( v -- )

Discards the top of the stack.

dup - ( v -- v v )

Duplicates the top item on the stack.

add - ( a b -- a+b )

Adds the two values on the top of the stack and pushes the result to the top of the stack.

do - ( -- )

Pushes the location after the do onto the control stack.

while - ( v -- )

Pops the value on the top of the stack. If it is non-zero, control resumes at the location on the top of the control stack. If it is zero, the location on the top of the control stack is popped and control continues as normal.

eq - ( a b -- a==b )

Pops two values off of the stack and compares them for equality. If they are equal, a 1 is pushed to the stack. Otherwise, a 0 is pushed.

ne - ( a b -- a!=b )

Like eq but tests for inequality.

(This is actually implemented with a subtract so the "true" values it produces are non-zero, but are not definitely 1)

putch - ( v -- )

Pops the value on the top of the stack, truncates it to a byte, and writes it to the output.

getch - ( -- v )

Reads one byte from the input and pushes it onto the stack.

if - ( v -- )

Pops the value on the top of the stack. If it is zero, execution continues after the next else or endif.

endif - ( -- )

Does nothing. Serves as a location target for a prior if or else.

else - ( -- )

Continues execution after the next else or endif.

halt

Halts execution.

Clone: https://git.bytex64.net/m0-stack-machine.git