2025-04-23 18:59:44 +03:00
2025-04-23 10:12:08 +03:00
2025-04-23 18:57:33 +03:00
2025-04-22 20:02:27 +03:00
2025-04-22 20:02:27 +03:00
2025-04-23 18:59:44 +03:00

mu808: an even more ultralight numeric VM designed to run everywhere

mu808 (always lowercase, pronounced as micro-bob) is a minimalist virtual machine specification that is based on the 1V0 and 808UL ideas but has an even leaner instruction set and is even easier to implement in any modern or even old programming language.

Overview

mu808 is a Harvard-architecture virtual machine with separate memory space for programs and data. The only available data type is a floating point number, although some implementations may use fixed point numbers instead.

Unlike 808UL, mu808 strictly defines support of up to 16384 data cells and up to 16383 program steps, with the program step 0 being unavailable and the data cell 0 always returning 0.

On the machine start, both memory areas are initialized with zeroes and the interactive mode is entered which accepts instructions from the platform's standard input.

Overall features

  • 16 runtime instructions (from 0 to 15, including NOP as instruction 0);
  • ability to enter integer commands (positive and negative) into program memory;
  • ability to enter floating (or fixed) point numbers (positive and negative) at program runtime;
  • immutability of the data address 0 (must always return 0 when accessed).

The following things are implementation-dependent:

  • instruction and data internal representation;
  • tracing capabilities;
  • runlimit (the amount of program steps before forcefully halting the program).

mu808 instruction set

Unlike 808UL, mu808 only has a core instruction set. Also, regardless of the instruction number, all instructions strictly contain five numbers separated by a whitespace:

  • [lno] - instruction number,
  • [cmd] - command,
  • [arg1] - argument 1,
  • [arg2] - argument 2,
  • [arg3] - argument 3.

Any mu808 implementation must not distinguish between a whitespace and a newline character: both of them, if supported, must be used to delimit individual parts of an instruction and not instructions as a whole. Instructions as a whole, however, are separated naturally by reading five delimited numbers at a time.

This rule applies to the plaintext format of the machine code. See the section about mu808 file formats to find out about the binary machine code format.

Instruction number semantics

  • Any positive integer: the instruction is written into the program memory under this address (any previous instruction at this address is overwritten).
  • 0: the instruction is NOT written into the program memory and is immediately executed instead. If the instruction is a jump instruction that jumps into a valid positive address, program execution in the program memory begins until the program memory is exhausted.
  • -1: displays a range of instructions from address [cmd] to [arg1] (incl).
  • -2: if [cmd] is 0, clears a range of instructions from address [arg1] to [arg2] (inclusively), otherwise clears a range of data from address [cmd] to [arg2] (inclusively).
  • -3: if supported, turns off execution tracing output if the [cmd] value is equal to 0, otherwise turns it on.
  • -4: if supported, changes the program runlimit to the value of [cmd].
  • -5: if supported, the VM exits to the OS environment, otherwise resets to its initial state (both memory areas filled with zeroes).

For the data entry and display, a runtime command is required instead (see below for details).

Runtime command set

The following commands are required for all mu808 ports and implementations.

The following notation is in place for this list:

  • A1 - address passed as the first command argument;
  • A2 - address passed as the second command argument;
  • A3 - address passed as the third command argument;
  • V1 - numeric value stored at the address A1;
  • V2 - numeric value stored at the address A2;
  • V3 - numeric value stored at the address A3.

The mu808 runtime command list follows along with their mnemonics.

  • 0 (NOP): No operation. The instruction is ignored regardless of parameters.
  • 1 (JMP): Jump. This is the most complicated operation in the list, see the next section ("Jump operation semantics") for the full explanation how it works.
  • 2 (IAT): Indirect addressing toggle. This instruction overrides the next instruction by providing V1, V2 and V3 as the parameters for the next instruction's command. The actual command parameters provided with the next instruction will be ignored.
  • 3 (OUT): Output. Print the range of values stored from the address A1 to the address A2 (inclusively) to the port number in A3 (0 is the platform's standard output).
  • 4 (INP): Input. Prompt the user to enter the range of values to be stored from the address A1 to the address A2 (inclusively) from the port number in A3 (0 is the platform's standard input).
  • 5 (SET): Set value at address. Interpret A1 and A2 as two direct integer values and then set the value at A3 to (A1 mod 10000) + (A2 mod 10000) / 10000.
  • 6 (CPY): Direct/indirect value copy. If A1 is 0, then set the value at A3 to A2. If A1 is 1, set the value at A3 to V2. Otherwise, set the value at V3 to the contents of the address V2 (converted to integer).
  • 7 (FMA): Fused multiply-add. Set the value at A3 to V1 + (V2 * V3).
  • 8 (SUB): Subtract. Set the value at A3 to V1 - V2.
  • 9 (DIV): Divide. Set the value at A3 to 0 if V2 is 0, otherwise set it to V1 / V2.
  • 10 (MDF): Modulo/floor. Interpret V1 and V2 as integers and set the value at A3 to V1 mod V2 if V2 is not 0, otherwise set the result to the integer part of V1.
  • 11 (ABS): Absolute value. Set the value at A3 to |V2|.
  • 12 (SQR): Square root. Set the value at A3 to the square root of |V2|.
  • 13 (NEL): Natural exponent/logarithm. If the value of A1 is 0, set the value at A3 to e ** V2, otherwise set it to ln |V2|.
  • 14 (TRI): Trigonometric function. If the value of A1 is 0, set the value at A3 to sin V2, if the value of A1 is 1, set the value at A3 to cos V2, otherwise set it to arctg V2. The parameters are given in radians.
  • 15 (RND): Random number. Set the value at A3 to a random integer number between V1 and V2 (inclusively).

Jump operation semantics

The jump operation (instruction command 1) in mu808 is almost identical to the corresponding operation in 808UL, which, in turn, was directly modeled after the corresponding operation in the 1V0 TZ IV variant. It allows for all kinds of conditional and unconditional, direct and indirect jumps based on three input parameters.

Using the same notation as above, the jump operation semantics is as follows:

  • If V2 == 0 and A1 == 0, or
  • if V2 > 0 and A1 == 1, or
  • if V2 < 0 and A1 == 2, or
  • if V2 >= 0 and A1 == 3, or
  • if V2 <= 0 and A1 == 4, or
  • if V2 != 0 and A1 == 5, or
  • if A1 == 6,

then jump to the address A3.

  • If V2 == 0 and A1 == 7, or
  • if V2 > 0 and A1 == 8, or
  • if V2 < 0 and A1 == 9, or
  • if V2 >= 0 and A1 == 10, or
  • if V2 <= 0 and A1 == 11, or
  • if V2 != 0 and A1 == 12, or
  • if A1 == 13,

then jump to the address V3 (converting it to an integer first).

Note that the order of operands has changed compared to the 1V0/808UL specifications: here, it's [selector] [value] [jumpaddr] as opposed to [value] [jumpaddr] [selector]. This has been done for more convenient jump programming by selecting the condition as the first operand.

Port input and output

For the commands 3 and 4, mu808 supports optional non-zero port numbers, where port 1 is the character output port and port 2 is the character input port respectively. Additional custom ports may be implementation-defined.

This specification also defines several file formats that can be used for mu808 programming process.

Plain text machine code format (MU8)

This is the main format to store and enter mu808 programs in. It essentially duplicates what's being entered into the mu808 REPL, with the exception of the negative instruction numbers. Unlike 808UL, it only allows specifying digits and whitespace in the file. Implementations are only required to support the space character (32, 0x20) as the delimiter, but should also support newline, tabs and other kinds of whitespace in the same way.

The recommended file suffix for mu808 plain text machine code is .mu8.

Binary machine code format (MU8B)

Unlike 808UL, mu808 also defines a MU8B binary format to store every instruction in 64 bits of data in the following order (for easier processing):

  • 4 higher bits are unused,
  • 4 bits are used for the command opcode (0 to 15),
  • 14 bits are used for the instruction number (can't be 0),
  • 14 bits are used for the command argument 1,
  • 14 bits are used for the command argument 2,
  • 14 bits are used for the command argument 3.

Hence, every instruction can only contain the number from 0 to 16383 in any of its command arguments, and from 1 to 16383 in its instruction number field.

The recommended file suffix for mu808 binaries is .mu8b or .bin. Implementors are encouraged (but not required) to provide tools for conversion between the binary and text representations of mu808 machine code.

When exporting the entire program memory to the MU8B format, NOPs may or may not be omitted. If a mu808 implementation supports MU8B file import, it must behave the same way as if loading MU8 (plain text) machine code with the same instructions or entering them manually.

Assembly source code file format (MU8A)

In addition to direct machine code in the plain text or MU8B formats, the mu808 VM also allows using an assembly-like language to write programs using labels and the above mnemonics (case-insensitive). The recommended file suffix is .mu8a.

An assembly line looks like this (the optional parts are enclosed in square brackets): [:lbl] MNEMONIC arg1 arg2 arg3 [;comment]. Labels are optional but must start with a colon (:) and be on the same line before the instruction they label. The mnemonics are specified above in the core opcode list.

Here, the exact assembly algorithm is specified step-by-step for each line in the MU8A assembly file to convert it into a plain text based machine code representation:

  1. Remove all comments (starting with ; until the end of the line).
  2. Record the current line number N (not counting completely empty lines), starting with 1.
  3. Split the line into space-delimited fields (1-based numbering as well).
  4. Check if the first field starts with :. If so, mark the mapping between the field text and the number N, then remove the field from the set (so that the field 2 becomes field 1 and so on).
  5. If the field 1 is not already a number, replace the field 1 mnemonic text with the numeric opcode if it can be found. If it's not a number and the mnemonic cannot be found, report an error and halt the process.
  6. Prepend N and the space to the current line and write the result into the target file.
  7. After the steps 1 to 6 are complete for every line, replace every label occurrence in the mapping with the corresponding number in the target file.

Examples

The examples subdirectory contains several MU8A source code file examples for mu808 which are ports of the same 808UL example programs, namely:

You can assemble them using any of the reference assemblers provided within the repository, or even by hand (by numbering lines, resolving the labels and replacing mnemonics with corresponding opcodes). The "Hellorld!" example only works on the platforms supporting I/O port 1 (character output).

If you just want to test an implementation, assembled MU8 machine code files (in the plaintext format) are stored in the examples/assembled subdirectory. After loading into the REPL, you can run each of them with the 0 1 0 0 1 or 0 1 6 0 1 sequence.

Reference implementations

mu808 VM reference implementations

  • Python 3 implementation: the first one where all prototyping and initial spec development has been done. Also compatible with MicroPython an tested on it. Supports the entire documented mu808 instruction set, including the two extended I/O ports (requires termios library to work with the character input port correctly on desktop OSes but falls back to a simple stream file read in case the library is not found). Accepts a file name to preload a MU8 (plain text) machine code program from the OS command line.
  • ISO C99 implementation: supports the entire specification the same way as the Python implementation, and also makes use of the optimized fma() call that appeared in the C99 standard. Accepts a file name to preload a MU8 machine code program from the OS command line. Compile the source with: cc -std=c99 -O2 -s -lm -o mu808 mu808.c
  • POSIX AWK implementation: supports the entire mu808 specification sans the (non-portable) I/O port 2. Run it like this: LC_ALL=C awk -f mu808.awk [- input_program.mu8], where the dash-delimited program file name is optional and used for preloading MU8 program files into VM's memory. The Busybox AWK implementation is compact but quite slow, so use it as a last resort solution when no other programming environment is available.
  • Web (HTML5/JS) implementation aka mu8web: supports the entire mu808 spec except the I/O port 2 and offers a way to load program files in the same MU8 format via a convenient UI. Uses a convoluted flow to achieve true synchronous input. Since there's no exiting to the OS environment, the -5 instruction number resets the VM instead (by zeroing out both memory areas and the counter). Entry conventions within UI are pretty much the same as for 808UL. Tested on various devices with physical keypads and touch screens. The source code can be found in the mu8web directory of this repo.

Assembler reference implementations

  • Python 3: supports assembling MU8A source files into both plain text (MU8) and binary (MU8B) machine code formats. Also supports disassembling MU8 and MU8B files into their MU8A sources and converting machine code between MU8 and MU8B formats.

These lists are going to be expanded as soon as new reference implementations appear.

Web reference implementation keyboard controls

The virtual keyboard (unavailable on the smallest screens) is duplicated with the following keys at all times in the mu808 Web reference implementation:

  • 0-9: decimal digits input;
  • * (star), comma, dot: decimal point input;
  • Space, +, /, down arrow, Call key (if present): whitespace input;
  • Backspace, Delete, left arrow, -: minus or backspace input (as the minus sign can only be entered at the beginning of the instruction or data input line);
  • # (pound, hash sign), Enter: newline input;
  • l, L, up arrow, Left Soft key (if present): open a program file loading dialogue.

12-key (DTMF) input method proposal

For hobbyist projects and other environments where all input is limited to 12 keys of the standard phone keypad, the following convention is recommended:

  • the * (star) key is inputting a minus if this is the first position in the entry, otherwise it's inputting a decimal point;
  • the # (pound) key actually confirms entering a single number field, and all instructions are required to contain exactly five numbers.

FAQ

Why another 1V0 derivative that's even code-incompatible with 808UL?

808UL has served its purpose of fulfilling the author's vision on what an 1V0-like (virtual) system should look like. However, its ISA still somewhat lacked rigidity and refinedness in terms of an optimal functionality distribution: there were a lot of instructions that didn't use all three command arguments, there were some instructions that could be parameterized better to avoid functionality duplication, and dedicated port I/O subcommands seemed like a hack on top of already available I/O routines. The mu808 ISA does a pretty good job at polishing those bits while retaining all the main features of the 808UL specification, also adding much stricter constraints on how the operating environment is to be organized.

What can be considered a minimum compatible mu808 VM implementation?

A minimum implementation must implement all runtime instruction commands (from 0 to 15 inclusive) and the interactive loop that accepts the following instruction numbers: any positive number, 0, -1, -2 and -5.

A minimum implementation is required to support floating point numbers as a data type, or, in case it's technically impossible, fixed point numbers with at least two digits after the decimal point (four or more recommended).

808UL supported sparse instructions, is this feature gone in mu808?

No, not at all. It's the MU8A assembly language that doesn't (yet) support this feature and autonumbers instructions based on their position in the source code. When writing machine code directly, you can specify any instruction numbers you like in the program.

Why combine multiplication and addition into a single instruction command?

Because it is faster on modern architectures if both of them are required, and doesn't add any complexity if only one of them is. Additionally, the FMA operation is included in IEEE 754-2008, and all reference mu808 implementations are using it in case the target platform allows them to.

How to do simple addition and simple multiplication, given only the FMA?

It might seem inconvenient at first, but the FMA operation is quite capable. First off, you already have a constant 0 always stored in 0, but it's advisable to store a constant 1 into some unused location, like 99. You can do this e.g. with the set 1 0 99 operation. Then, let's go with three cases (assuming we have stored 1 into the data memory location 99):

  1. Incrementing a memory location is as easy as fma 99 99 [loc].
  2. Adding two numbers at loc1 and loc2 into loc2: fma [loc1] 99 [loc2].
  3. Multiplying two numbers at loc1 and loc2 into loc2: fma 0 [loc1] [loc2].

The only inconvenience with the FMA operation is that you need to sacrifice one of the operand locations to store the result. If you want to fully emulate the 808UL's behavior, then you have to copy one of the operands into the target memory location first and then use it as the last parameter to the FMA command.

Why is the "Hellorld!" example much shorter than in 808UL?

Because mu808 unifies the standard and port I/O, thus allowing to specify a range of memory locations to the port output routine as well. Since the example arranges all the message bytes in order, a single call to the output command will suffice.

Credits

Created by Luxferre in 2025, released into public domain with no warranties.

S
Description
An ultralight numeric VM designed to run anywhere, now with a better ISA, its own formats and assembly
Readme
92 KiB
Languages
Python 36.4%
JavaScript 28.1%
C 16%
Awk 13.4%
CSS 3.1%
Other 3%