SkillAgentSearch skills...

Romforth

Ultra Portable, Small, Baremetal Forth for various processors

Install / Use

/learn @romforth/Romforth

README

romforth is a small, portable, baremetal version of Forth which has been ported to various 8/16/32/64-bit microcontrollers with support for both big and little endian architectures. So far, it has been ported to the following instruction sets: x86 (16-bit, 32-bit and 64-bit), PDP11, 68000, SPARC, Z80, MSP430, ARM64, RISC-V(rv32, rv64), WASM, Padauk, 6502, 8051, 6809, IBM 1130.

So, at a superficial level, romforth is just yet another Forth which has been ported to a wide variety of CPUs and is meant to be runnable directly from the Flash/ROM of the microcontroller.

Dig deeper though, and you might see that romforth can also be considered a "porting toolkit" which enables a "stepwise porting framework" that allows you to port Forth "relatively easily" to any of your favorite CPU architectures.

The various ports that have been done already can be then be considered as proof of concept of this approach to porting Forth.

At this point you might reasonably ask: Why use Forth for microcontrollers? I'll go ahead and claim that Forth is a perfect fit for small, resource constrained systems. Perhaps elsewhere too, but opinions widely differ on that. Here's some sample code that might be used to blink an LED every second:

loop{
	green led_on
	500 millisec sleep
	green led_off
	500 millisec sleep
}loop

romforth is yet another Forth-like implementation meant to (eventually) run on most "low end" (ie ROM and RAM constrained) microcontrollers.

See PORTING for a list of architectures to which romforth has been ported as well as all the steps that are required for porting to a new architecture.

Unlike many Forth implementations which require a large number of "words" to be implemented before you can do anything with it, romforth is meant to be a "shrink to fit" implementation where the user decides how much of the Forth functionality they really need and only implement what they really require.

There are four "levels" of Forth that could be implemented in this way. For lack of better names, I'll refer to these "levels" as: 1/4 : "oneforth" (pun intended), which has only primitives and a data stack ROM : ~256 bytes, RAM : in the single digit byte range? 2/4 : "twoforth" which has definitions and a return stack (in addition to the primitives and data stack that "oneforth" has) ROM : ~256 bytes, RAM : 16 bytes (or thereabouts in two digit byte range) 3/4 : "threeforth" which has a static dictionary (in addition to the primitives and definitions and the two stacks that "twoforth" has) ROM : ~512 bytes, RAM : 16 bytes (or thereabouts in two digit byte range) 4/4 : a full fledged "fourforth"/full/regular Forth with a dynamic dictionary in addition to the rest of the bells and whistles from "threeforth". ROM : ~1024 bytes, RAM : 512 bytes or more - to hold the dictionary

These levels are further broken down into a total of ~74 individual steps which implement incremental pieces of Forth functionality. Each of these steps comes with tests that can be run to verify each additional piece of functionality as it is added, one at a time, which helps in having a working implementation at each and every step along the way.

By design, the most compact implementation is limited to use just a little over ~256 bytes of native code with the rest of the functionality implemented in machine independent code. Currently, ~100 bytes of machine independent Forth related functionality is used in the x86 implementation. So the overall Flash/ROM requirement on x86 is currently about ~350 bytes. As a proof of concept, an even more minimal bootstrap mechanism that uses just a dozen bytes of x86 native code which can load in the rest via any available serial interface (just like Frank Sergeant's "3 Instruction Forth") is also available.

As mentioned earlier, there is also a regression test suite which is called at boot as part of init (which can be optionally #ifdef'ed out) and that currently takes an additional ~500 bytes of machine independent code.

romforth is meant to be extended using new "purpose built" Forth words which can be defined in terms of existing Forth words. The new definitions can be added in the machine independent "defs.4th".

The new words that have been defined can then be called directly as part of the CPU boot code/initialization by adding them to "rom.4th".

Running "make" will generate a "forth" binary which can be flashed into ROM (although in all of the implementations that have been completed so far, the testing is done using emulation, not actual hardware). I hope to run this on actual hardware real soon now.

The following (mostly) traditional Forth runtime words are available: Data Stack operators : dup drop nip dip swap over rot 2drop third fourth Data Stack get/set ops : pick stick Arithmetic : + - inc(1+) dec(1-) neg Memory access : ! @ c! c@ Comparison operators : > < >= <= 0= ~ I/O : key emit p@ p! Memory allocation : here alloc Return stack operators : >r r> Call/return : enter exit call exec Loop index : i Literals : lit Bit operators : & | ^ << >> inv Control flow : j(branch) jz(branch0) jnz(branchnz) Stack switch operators : sp@! rp@! Halt : bye Conditionals : if{ ... }else{ ... }if unconditional loop : loop{ ... }loop while loop : loop{ ... }while{ ... }loop until loop : loop{ ... }until{ ... }loop for loop : for{ ... }for

At the "oneforth" and "twoforth" levels of the implementation, compared to regular Forth, what is not available is the dictionary. We make up for that by providing an "umbilical host"ed solution using a set of scripts, which runs on the host, and will turn the Forth code into byte code which can be run on top of the ~350 byte "runtime". This "umbilical host" provides control flow structures such as conditionals and loop structures which are identical to the ones available at runtime in the dictionary at the "threeforth"/"fourforth" levels of the implementation.

So, using about ~350 bytes of "runtime", we can write architecture independent loops (while, until, for), conditionals (if, else), allocate memory, and even run threads or invoke semi/full-coroutines (if your proclivities tend that way)

It is reasonable to ask what distinguishes this Forth implementation from the zillion other implementations that already exist. I think the main distinguishing features are that it is portable, small and runs baremetal:

  1. Portable : romforth currently runs on 8/16/32/64 bit architectures as well as little and big endian CPUs. It was designed with portability in mind so as to eventually be able to run on "most" microcontrollers. The primitives that need to be implemented in native code are just a few instructions each and there are machine independent tests (in Forth) that verify the functionality of each primitive as it is added. See the table at the end of this README for a list of ports that already exist.
  2. Small : Designed for low end microcontrollers romforth is meant to be a "shrink to fit" implementation of Forth where the user decides how much of the Forth functionality they really need and only implement what they must. See the table at the end of this README to see the actual implementation sizes for various CPU architectures
  3. Baremetal : Can run directly on the hardware, doesn't require any other underlying runtime or OS support. In most of the implementations done so far, serial I/O needs to be supported if a REPL is required, otherwise an "umbilical hosted" support may be needed to flash the code. All of the existing ports have been tested using emulation (and/or on x86 Linux).

License

All of the code here is licensed under the Affero GPL 3.0 open source license. For exact details about the license please see the LICENSE file. For the thinking/intent behind the choice of AGPL, see LICENSE.README

Rationale

For a long winded explanation of why this project exists, see RATIONALE

Installation

To make a local copy: git clone https://github.com/romforth/romforth

To build/test, the following dependencies need to be installed: (Note 0: many of the toolchain dependencies required to build this repo are available in https://github.com/romforth/toolchain - clone and build that first by running make in the toolchain subdirectory and that will populate many of the binaries required for the romforth build toolchain. Many of the tools that are usually available as distro packages such as the C compiler, M4, Make, Perl, Zig and QEMU are currently not built as part of the toolchain - these need to be installed separately.) (Note 1: the version number in parentheses is the "known to work" version used in testing, older/newer versions may or may not work) (Note 2: If you are only doing a partial build for a specific processor by running make within a subdirectory, a subset of these dependencies is sufficient. For example, for the x86 port, a C compiler and the nasm x86 assembler might be more than sufficient.)

- make (GNU Make 4.1)
- perl (v5.22.1)
- nasm (version 2.15.05 compiled on Aug 28 2020)
- gcc (version 5.3.1 20160413 (Ubuntu 5.3.1-14ubuntu2))
- binutils ((GNU Binutils) 2.38)
	- i386-elf
	- x86_64-elf
	- pdp11
	- m68k
	- sparc
	- msp430
	- aarch64
	- riscv64
	- riscv32
- simh (open-simh V4.0-0 Current git commit id: 33aad325)
- zig (0.11.0)
- qemu (7.1.0, via nix)
- sdcc (4.2.0 #13081 (Linux))
- ucsim (git pulled from github.com/danieldrotos/ucsim, ucsim: 0.9.4)
	- exact commit used was e6acd12518f8c70313209b9946d9f571b0f9253b
- m4 ((GNU M4) 1.4.17)
- mspdebug(from github.com/romforth/mspdebug, until my changes
	are upstreamed)
- wasmtime (wasmtime-cli 9.0.4)

After installing all the above dependencies, running make will build each of the ports and run each of them in an emulator and should complete successfully.

To do a partial build, you can go into any individual processor specific directory and run `make

View on GitHub
GitHub Stars53
CategoryDevelopment
Updated1d ago
Forks0

Languages

Perl

Security Score

100/100

Audited on Mar 28, 2026

No findings