FiveForths is a tiny Forth written in hand-coded RISC-V assembly, initially designed to run on the 32-bit Longan Nano (GD32VF103) microcontroller.
This document provides more detailed information on build, use, and write code for this microcontroller.
- Building for other boards
- Rebuilding the firmware
- Debug with JTAG
- Defining words (Forth)
- Toggle an LED
- Adding primitives (Assembly)
There are currently 2 support boards:
To build the lite version of Longan Nano, which is limited to 64K Flash, 20K RAM, type the following:
make build BOARD=longan-nano-lite
To build the regular Longan Nano, which has 128K Flash, 32K RAM, type the following:
make build BOARD=longan-nano
To rebuild the firmware:
make build -B
Or first clean, then build:
make clean
make build
JTAG debugging is necessary when modifying the firmware and to inspect memory and registers while the CPU is halted.
make openocd &
If openocd doesn't work, it is necessary to install it to /opt/riscv/ and it may be necessary to modify the adapter config in ft232r.cfg to match your adapter.
make debug
Once GDB is loaded, the firmware can be uploaded quickly with:
load
Registers can be inspected with (for example):
info registers a0 a1 t0 t1 sp pc
or:
info all-registers
Accessing FiveForths through the terminal should look similar to this:
$ pyserial-miniterm --eol LF /dev/ttyUSB0 115200
--- Miniterm on /dev/ttyUSB0 115200,8,N,1 ---
--- Quit: Ctrl+] | Menu: Ctrl+T | Help: Ctrl+T followed by Ctrl+H ---
FiveForths v0.5, Copyright (c) 2021~ Alexander Williams, https://a1w.ca
Some basic words can then be defined (borrowed from sectorforth hello-world and planckforth bootstrap):
: dup sp@ @ ;
: invert -1 nand ;
: negate invert 1 + ;
: - negate + ;
: drop dup - + ;
: over sp@ 4 + @ ;
: swap over over sp@ 12 + ! sp@ 4 + ! ;
: nip swap drop ;
: 2dup over over ;
: 2drop drop drop ;
: and nand invert ;
: or invert swap invert nand ;
: = - 0= ;
: <> = invert ;
: , here @ ! here @ 4 + here ! ;
: immediate latest @ 4 + dup @ 0x80000000 or swap ! ;
: [ 0 state ! ; immediate
: ] 1 state ! ;
: branch rp@ @ dup @ + rp@ ! ;
Of course, it is possible to define many other words to suit your needs.
The following code can be used to turn on the green and blue LEDs on GPIOA pins 1 and 2:
: green_led_on 0x40010800 @ 0xFFFFFF0F and 0x00000030 or 0x40010800 ! ;
: blue_led_on 0x40010800 @ 0xFFFFF0FF and 0x00000300 or 0x40010800 ! ;
green_led_on
blue_led_on
And to turn off the same LEDs:
: green_led_off 0x40010800 @ 0xFFFFFF0F and 0x00000040 or 0x40010800 ! ;
: blue_led_off 0x40010800 @ 0xFFFFF0FF and 0x00000400 or 0x40010800 ! ;
green_led_off
blue_led_off
This requires the above defined words: invert, over, swap, and, or.
To explain the values:
0x40010800: GPIOA base address with offset0x00forCTL0pins 0-7 (would beCTL1with offset0x04for pins 8-15).0xFFFFF0FF: mask to clear GPIO pin 2 (would be the same for GPIO pin 10, while GPIO pin 1 would be0xFFFFFF0Fand GPIO pin 8 would be0xFFFFFFF0).0x00000030: GPIO pin 1 setting0b0011which ispush-pull output, max speed 50MHz.0x00000040: GPIO pin 1 setting0b0100which isfloating input.0x00000300: GPIO pin 2 setting0b0011which ispush-pull output, max speed 50MHz.0x00000400: GPIO pin 2 setting0b0100which isfloating input.
The code above uses those pre-calculated values to read the existing GPIOA config from a memory address (with @), apply a mask (with and), apply the new config (with or), then store it back to the memory address (with !), thus writing the new GPIOA which toggles the pins low/high (active-low, therefore low turns on the LED, high turns it off).
New primitives can be written in RISC-V Assembly. It is recommended to add them to a new file and then include the file at the end of fiveforths.s:
# fiveforths.s
.include "my-primitives.s"
In my-primitives.s, each new primitive should start with a call to the defcode macro, and they should end with the NEXT macro. For example:
# exit ( r:addr -- ) Resume execution at address at the top of the return stack
defcode "exit", 0x04967e3f, EXIT, LIT
POPRSP s1 # pop RSP into IP
NEXT
The macro arguments are:
- primitive: used in the interactive Forth terminal. (ex:
exit) - djb2_hash: the hash of the primitive with the first 3 bits (from the MSB) reserved for the flags (immediate, hidden, user), the next 5 bits reserved for the size (1-32). (ex:
0x04967e3f) - name of the primitive: uppercase name used by the interpreter to find the label of the word. (ex:
EXIT) - name of the previous primitive: uppercase name of the previous primitive this one links to, used for dictionary lookups (linked list). (ex:
LIT)
Existing primitives can be used for inspiration, they are located in src/08-forth-primitives.s.
First, compile the djb2 hash program:
make djb2
Then run the binary with the name of the primitive, example:
./djb2 exit
djb2_hash: 0x04967e3f
The hash 0x04967e3f will be printed, which can be used in the defcode declaration.
Now that you've completed the howto, you're ready to read the other documents below:
- TUTORIALS: a quick guide to get started
- EXPLAIN: learn the story behind FiveForths
- REFERENCE: learn the technical details, what's under the hood
FiveForths documentation and source code copyright © 2021~ Alexander Williams and licensed under the permissive open source MIT license.