Files
mu808/README.md
T

495 lines
24 KiB
Markdown
Raw Normal View History

2025-04-22 20:02:27 +03:00
mu808: an even more ultralight numeric VM designed to run everywhere
====================================================================
mu808 (always lowercase, pronounced as _micro-bob_) is a minimalist virtual
2025-04-23 18:59:44 +03:00
machine specification that is based on the
[1V0](https://github.com/KedalionDaimon/1V0)
and [808UL](https://codeberg.org/luxferre/808UL) ideas but has an even
leaner instruction set and is even easier to implement in any modern or even old
programming language.
2025-04-22 20:02:27 +03:00
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.
2025-04-30 10:22:53 +03:00
### Shortcut mnemonics
When writing MU8A assembly (see below), programmers are allowed to use
some shortcut mnemonics for easier code readability. Those exist for all jump
operations and several other opcodes.
2025-04-30 08:15:48 +03:00
The shortcut mnemonics for the jump operations are as follows:
* `jeq` is a shortcut to `jmp 0` (direct jump if equals to zero)
* `jgt` is a shortcut to `jmp 1` (direct jump if greater than zero)
* `jlt` is a shortcut to `jmp 2` (direct jump if less than zero)
* `jge` is a shortcut to `jmp 3` (direct jump if greater than or equals to zero)
* `jle` is a shortcut to `jmp 4` (direct jump if less than or equals to zero)
* `jne` is a shortcut to `jmp 5` (direct jump if not equals to zero)
* `juc` is a shortcut to `jmp 6 0` (direct unconditional jump)
* `ieq` is a shortcut to `jmp 7` (indirect jump if equals to zero)
* `igt` is a shortcut to `jmp 8` (indirect jump if greater than zero)
* `ilt` is a shortcut to `jmp 9` (indirect jump if less than zero)
* `ige` is a shortcut to `jmp 10` (indirect jump if greater than or equals to zero)
* `ile` is a shortcut to `jmp 11` (indirect jump if less than or equals to zero)
* `ine` is a shortcut to `jmp 12` (indirect jump if not equals to zero)
* `iuc` is a shortcut to `jmp 13 0` (indirect unconditional jump)
2025-04-30 10:22:53 +03:00
The shortcut mnemonics for other operations are as follows:
* `dia` is a shortcut to `cpy 0` (direct integer assignment)
* `dva` is a shortcut to `cpy 1` (direct value assignment)
* `iva` is a shortcut to `cpy 2` (indirect value assignment)
* `exp` is a shortcut to `nel 0` (natural exponent)
* `log` is a shortcut to `nel 1` (natural logarithm)
* `sin` is a shortcut to `tri 0` (sine)
* `cos` is a shortcut to `tri 1` (cosine)
* `atn` is a shortcut to `tri 2` (arctangent)
2025-04-22 20:02:27 +03:00
### 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.
File and data formats related to mu808
--------------------------------------
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).
2025-04-30 10:22:53 +03:00
2. Replace all jump/function shortcuts according to the above mnemonics.
2025-04-30 08:15:48 +03:00
3. Record the current line number N (not counting completely empty lines),
2025-04-22 20:02:27 +03:00
starting with 1.
2025-04-30 08:15:48 +03:00
4. Split the line into space-delimited fields (1-based numbering as well).
5. Check if the first field starts with `:`. If so, mark the mapping between
2025-04-22 20:02:27 +03:00
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).
2025-04-30 08:15:48 +03:00
6. If the field 1 is not already a number, replace the field 1 mnemonic text
2025-04-22 20:02:27 +03:00
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.
2025-04-30 08:15:48 +03:00
7. Prepend N and the space to the current line and write the result into the
2025-04-22 20:02:27 +03:00
target file.
2025-04-30 08:15:48 +03:00
8. After the steps 1 to 7 are complete for every line, replace every label
2025-04-22 20:02:27 +03:00
occurrence in the mapping with the corresponding number in the target file.
Examples
--------
The [examples](examples/) subdirectory contains several MU8A source code file
2025-04-24 13:35:22 +03:00
examples for mu808 (some of which are ports of the same 808UL example programs),
namely:
2025-04-22 20:02:27 +03:00
* [Compound interest calculator](examples/compound.mu8a),
* [Linear regression calculator](examples/linreg.mu8a),
2025-04-24 13:42:33 +03:00
* [Hellorld!](examples/hellorld.mu8a) (a tribute to @UsagiElectric)
(requires I/O port 1 support),
* [FizzBuzz classic program](examples/fizzbuzz.mu8a)
(requires I/O port 1 support),
* [A simple 10-character echo test](examples/echo.mu8a) (requires both I/O
port 1 and port 2 support),
2025-04-22 20:02:27 +03:00
* [Bulls and Cows game](examples/moo.mu8a),
2025-04-30 09:31:12 +03:00
* [Lunar Lander game](examples/lunar.mu8a),
* [NumberJack](examples/numjack.mu8a) port of a Blackjack game, utilizing some
advanced techniques (see the comments in the beginning on how to play it).
2025-04-22 20:02:27 +03:00
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
2025-04-30 09:31:12 +03:00
replacing mnemonics with corresponding opcodes).
2025-04-22 20:02:27 +03:00
If you just want to test an implementation, assembled MU8 machine code files
(in the plaintext format) are stored in the `examples/assembled` subdirectory.
2025-04-23 15:05:00 +03:00
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.
2025-04-22 20:02:27 +03:00
Reference implementations
-------------------------
### mu808 VM reference implementations
* [Python 3 implementation](mu808.py): 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](mu808.c): 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`
2025-04-23 15:05:00 +03:00
* [POSIX AWK implementation](mu808.awk): 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.
2025-04-23 18:57:33 +03:00
* [Web (HTML5/JS) implementation](https://mu808.luxferre.top) 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](mu8web/) directory of this repo.
2025-04-22 20:02:27 +03:00
### Assembler reference implementations
* [Python 3](mu808asm.py): 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. Additionally, this assembler
implementation provides the `t74` mode to convert plaintext MU8 machine code
into the format suitable for entering into TI-74 BASIC for running on the
TI-74 port of mu808 (see below).
2025-04-22 20:02:27 +03:00
These lists are going to be expanded as soon as new reference implementations
appear.
2025-04-23 18:57:33 +03:00
### 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.
Retro/minimal platform implementations
--------------------------------------
Here's the list of known/existing mu808 implementations that adhere to the
specification to most extent and run compatible machine code but cannot
provide full operating environment functionality due to the limits of the
platform itself.
### TI-74 BASIC
The [mu808.b74](mu808.b74) file contains a BASIC port of mu808 for the Texas
Instruments TI-74 portable computer (tested on the TI-74S variant). Due to
the resource constraints, the following limitations apply:
* only 149 program steps and 256 data cells supported (adjustable at line 110),
* no interactive mode (the `RUN` command directly executes the predefined
program in the VM),
* the program itself is entered into the DATA statements in the so-called
MU74 format (see below),
* no support for tracing or intruction dumping (I/O ports 1-2 supported though),
* no boundary checks for the addresses inside the program.
Also, since the VM runs on top of a BASIC interpreter, program execution is
extremely slow most of the time. Keep in mind, this is more of a proof of
concept than a viable solution, and using the "native" TI BASIC is preferred
for any serious computing on that machine.
In order to store your programs on the device to run via this mu808 VM version,
you need to enter them in DATA statements in two-number pairs starting from
`DATA 0,0` at the BASIC line 5000. The last `DATA` line of the program must be
`DATA -1,0`. In between these two, every program instruction `lno cmd a1 a2 a3`
must be compressed into the form:
```
[5000+lno] DATA [cmd*10000 + a1], [a2 * 10000 + a3]
```
This storage format is called MU74. For example, the compound interest
calculator from the official mu808 examples will transform into:
```
5000 DATA 0,0
5001 DATA 50100,1
5002 DATA 40002,30000
5003 DATA 90002,10004
5004 DATA 50001,5
5005 DATA 70005,40005
5006 DATA 130001,50005
5007 DATA 70000,30005
5008 DATA 130000,50005
5009 DATA 30005,50000
5010 DATA -1,0
```
To make the conversion easier, the official mu808 assembler, `mu808asm.py`, now
supports the `t74` mode that accepts a plain MU8 machine code file and outputs
the same program in the MU74 format.
2025-04-22 20:02:27 +03:00
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.
2025-04-23 10:12:08 +03:00
### 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.
2025-04-22 20:02:27 +03:00
Credits
-------
Created by Luxferre in 2025, released into public domain with no warranties.