n808 VM specification
n808 (always lowercase, pronounced nano-bob) is a Harvard-type numeric-only virtual machine based on an elaborate effort of further simplification of the mu808 VM specification. The n808 VM comes with its own assembly language, N8A, and strict plaintext and binary machine code format definitions.
Features
- Data memory: 128 cells (125 of which are mutable, see below)
- Program memory: 128 steps
- Data cell type: floating point (or fixed point where floats are unsupported)
The n808 data memory contains several special addresses that cannot be used for writing custom data into them:
- 0: read-only, always returns 0 when accessed;
- 125: read-write but gets overwritten by
jmp 14instructions to store the return instruction address; - 126: read-only, always returns -1 when accessed;
- 127: read-only, always returns 1 when accessed.
Instruction format
Every n808 instruction [opcode] [p1] [p2] [p3] is 24 bits (3 bytes) long:
- 3 bits for opcode;
- 7 bits for parameter 1;
- 7 bits for parameter 2;
- 7 bits for parameter 3.
Implementations may accept both text and binary representations, but every
instruction can only be entered as a single non-negative integer number.
The value of this number is: opcode * 2097152 + p1 * 16384 + p2 * 128 + p3.
In case an implementation is only expecting the binary format, the byte order
of the number must be big-endian, from the most to the least significant byte.
In case of plain text representation, instructions can be separated with any
non-digit characters, but only the support for whitespaces and newlines as the
delimiters is absolutely required.
If an implementation supports the host filesystem, it is recommended to store
the plain text machine code with the .n8 file suffix, and the binary machine
code with the .n8b file suffix. Since a program can at most contain 128 n808
instructions that are 24 bits each, the maximum .n8b file size is 384 bytes.
Note: contrary to 1V0, 808UL and mu808, instruction numbers are no longer part of the instructions. Every instruction is numbered sequentially, starting from the step 0 (unlike 808UL and mu808, step number 0 is not reserved for immediate execution).
Instruction set
This description assumes that every instruction accepts address parameters
p1, p2 and p3, and v1, v2 and v3 refer to the actual contents
of data memory cells at those addresses.
Some instruction descriptions also contain shortcut mnemonics. These mnemonics
are just for convenience, as they, just like the main mnemonics, are converted
into real numeric instructions by the N8A assembler. Shortcuts always take less
parameters than real instructions. Some shortcuts, like nnn or ret, do not
take any parameters at all.
Note that mnemonics and shortcuts are a feature of the N8A assembly language and not n808 per se. The VM itself only operates on numbers in both program and data memory areas.
0 NOP: no operation
Ignore all parameters and do nothing.
Shortcut: nnn = nop 0 0 0
1 JMP: jump
The logic depends on the value of p1:
- 0: jump to the address
p3ifv2equals to zero; - 1: jump to the address
p3ifv2is above zero; - 2: jump to the address
p3ifv2is below zero; - 3: jump to the address
p3ifv2is above or equals to zero; - 4: jump to the address
p3ifv2is below or equals to zero; - 5: jump to the address
p3ifv2does not equal to zero; - 6: jump to the address
p3unconditionally; - 7: jump to the address
v3ifv2equals to zero; - 8: jump to the address
v3ifv2is above zero; - 9: jump to the address
v3ifv2is below zero; - 10: jump to the address
v3ifv2is above or equals to zero; - 11: jump to the address
v3ifv2is below or equals to zero; - 12: jump to the address
v3ifv2does not equal to zero; - 13: jump to the address
v3unconditionally; - 14: save the next instruction pointer to the cell 125 and jump to
the address
p3unconditionally.
In case of jumping to the address v3, it is converted to an integer first.
Shortcuts:
jeq=jmp 0(direct jump if equals to zero)jgt=jmp 1(direct jump if greater than zero)jlt=jmp 2(direct jump if less than zero)jge=jmp 3(direct jump if greater than or equals to zero)jle=jmp 4(direct jump if less than or equals to zero)jne=jmp 5(direct jump if not equals to zero)juc=jmp 6 0(direct unconditional jump)ieq=jmp 7(indirect jump if equals to zero)igt=jmp 8(indirect jump if greater than zero)ilt=jmp 9(indirect jump if less than zero)ige=jmp 10(indirect jump if greater than or equals to zero)ile=jmp 11(indirect jump if less than or equals to zero)ine=jmp 12(indirect jump if not equals to zero)iuc=jmp 13 0(indirect unconditional jump)jpr=jmp 14 0(jump to a procedure)ret=jmp 13 0 125(return from a procedure)
2 IAT: indirect addressing toggle
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.
Shortcuts: none
3 INO: port input/output
This instruction combines input and output depending on the port number in p1.
Generally, even ports are related to output and odd ports are related to input:
- 0: standard (numeric) output;
- 1: standard (numeric) input;
- 2: character output (if supported);
- 3: character input (if supported).
The p2 and p3 parameters define the range of addresses to output the data from
or input the data into.
Shortcuts:
out=ino 0(numeric output)inp=ino 1(numeric input)ouc=ino 2(character output)ipc=ino 3(character input)
4 CPY: copying/assignment
The logic depends on the value of p1:
- 0: set the memory cell
p3top2; - 1: set the memory cell
p3tov2; - 2: set the memory cell
v3(converted to integer) top2; - 3: set the memory cell
v3(converted to integer) tov2; - 4: set the memory cell
v3(converted to integer) to the value at addressv2(converted to integer).
Shortcuts:
dca=cpy 0(direct constant assignment)dva=cpy 1(direct value assignment)ica=cpy 2(indirect constant assignment)iva=cpy 3(indirect value assignment)ivc=cpy 4(indirect value copy)
5 SET: large value assignment
Set the memory cell p3 to the value of p1 * 100 + p2 + v3 / 100.
Shortcuts: none
6 MAT: mathematical operations
The logic depends on the value of p1:
- 0: set the memory cell
p3tov2 + v3; - 1: set the memory cell
p3tov2 - v3; - 2: set the memory cell
p3tov2 * v3; - 3: set the memory cell
p3tov2 / v3ifv3is not zero, otherwise set it to zero; - 4: set the memory cell
p3tov2 mod v3ifv3is not zero, otherwise set it to the integer part ofv2. - 5: set the memory cell
p3to|v2|(absolute value ofv2); - 6: set the memory cell
p3to the square root of|v2|; - 7: set the memory cell
p3to the natural exponent ofv2(e ** v2); - 8: set the memory cell
p3toln |v2|; - 9: set the memory cell
p3tosin v2(v2given in radians); - 10: set the memory cell
p3tocos v2(v2given in radians); - 11: set the memory cell
p3toarctg v2.
Shortcuts:
add=mat 0(addition)sub=mat 1(subtraction)mul=mat 2(multiplication)div=mat 3(division)mdf=mat 4(modulo/floor)inc=mat 0 127(increment)dec=mat 0 126(decrement)neg=mat 1 0(negation)inv=mat 3 127(inverse/reciprocal)abs=mat 5(absolute value)sqr=mat 6(square root)exp=mat 7(natural exponent)log=mat 8(natural logarithm)sin=mat 9(sine)cos=mat 10(cosine)atn=mat 11(arctangent)
7 RND: random number generator
Set the memory cell p3 to a random integer number between v1 and v2
(inclusively).
Shortcuts: none
Interactive mode
Unlike 1V0/808UL/mu808, n808 only accepts the following command parameters in the interactive mode:
0 [step no] 0: run the currently loaded program starting at a particular step;1 [step no] [instr]: enter an instruction into the program memory (the previous instruction at that step will be overwritten);2 [p1] [p2]: clear a range of instructions from addressp1top2(incl.);3 [p1] [p2]: clear a range of data from addressp1top2(incl.);4 0 0: exit to the OS or reset the VM if the exit is not supported.
Assembly source code file format (N8A)
In addition to direct machine code in the plain text or N8B formats, the n808 VM also allows using an assembly-like language to write programs using labels and the above mnemonics (case-insensitive). The recommended file suffix is .n8a.
An assembly line looks like this (the optional parts are enclosed in square
brackets): [:lbl] MNEMONIC p1 p2 p3 [;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 and the
shortcut list for each opcode. In the second case, shortcuts accept less
instruction parameters than the opcode they refer to.
Besides normal assembly lines, N8A also supports alias definition lines that
start with # and have the following format: #number alias. In the rest of
your code, you can recall any alias with the @alias form. For instance, if you
have defined #21 counter (use the cell 21 as counter), you can then write
inc @counter as opposed to inc 21. The alias feature allows you to replace
any constant numbers with easily remembered words within your N8A assembly.
Every alias must be defined on a separate line of code.
Here, the exact assembly algorithm is specified step-by-step for each line in the N8A assembly file to convert it into a plain text based machine code representation:
- Remove all comments (starting with
;until the end of the line). - Replace all jump/function shortcuts according to the above mnemonics.
- Record the current line number N (not counting completely empty lines), starting with 0.
- Split the line into space-delimited fields (1-based numbering as well).
- 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). - Check if the first field starts with
#. If so, mark the mapping between the text of the field 2 and the rest of the field 1 into the label mapping, prepending@to the text of the field 2. - 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.
- Write the result as a new line into the intermediate text file.
- After the steps 1 to 8 are complete for every line, replace every label occurrence in the mapping with the corresponding number in the intermediate text file.
- For every line in the intermediate text file, convert the four numbers on
that line (
opcode,p1,p2,p3) into a single number according to this formula:instruction = opcode * 2097152 + p1 * 16384 + p2 * 128 + p3. Write the result as a (space-delimited) field into the target code file.
Examples
The examples subdirectory contains several N8A source code file examples for n808 (some of which are ports of the same mu808 example programs), namely:
- Compound interest calculator,
- Linear regression calculator,
- Hellorld! (a tribute to @UsagiElectric) (requires I/O port 2 support),
- FizzBuzz classic program (requires I/O port 2 support),
- A simple 10-character echo test (requires both I/O port 2 and port 3 support),
- Bulls and Cows game,
- Lunar Lander game,
- NumberJack port of a Blackjack game, utilizing some advanced techniques (see the comments in the beginning on how to play it).
You can assemble them using any of the reference assemblers provided within the repository, or even by hand (by numbering lines, resolving the labels/shortcuts and replacing mnemonics with corresponding opcodes).
If you just want to test an implementation, assembled N8 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 0 0 sequence.
Reference implementations
n808 VM implementations
- ANSI C implementation (C89 standard): the primary version where all
development is being done. Supports the entire specification but only preloads
the
.n8(text-based format) machine code files. Compile the source with:cc -std=c89 -O2 -s -lm -o n808 n808.c - Python 3/MicroPython implementation: supports the entire n808 spec
and runs in any Python 3 environment. Only preloads the
.n8-type code files. - POSIX AWK implementation: supports the entire specification except
the I/O port 3 (character input). Otherwise, it is a line-to-line port of the
C89 and Python 3 versions. Only preloads the
.n8-type code files, can be run as follows:LC_ALL=C awk -f n808.awk [- input_program.n8]
N8A assembler implementations
- n8asm.py: the reference assembler/disassembler for the N8A
language. Supports all real and shortcut mnemonics mentioned in this README.
Besides assembling and disassembling
.n8and.n8bfiles, also supports in-place conversion between these two formats and exporting N8 text-based machine code into the N74 format for usage in the TI-74 and other similar BASIC-based n808 VM implementations.
Other implementations (VMs, assemblers, helper tools)
Texas Instruments TI-74 portable computer
The n808.b74 file contains a BASIC port of n808 for the Texas Instruments TI-74 portable computer (tested on the TI-74S variant). Due to the resource constraints, the following limitations apply:
- 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 N74 format (see below),
- 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 for execution, you must store the N8 instruction
values in the DATA statements, starting from the BASIC line number 1000. The
last data entry of the program must be -1. You can fit as many data entries on
a line as the machine allows (usually up to 7, given the instruction number
length in the decimal form). To ease the program entry process, you can start it
with the NUM 1000,1 command, and use FN N key combo to enter the DATA
keyword.
For instance, the compound interest calculator example looks like this in N74:
1000 DATA 8401409,6308099,12632321,12599169,12714113,12616065,12697729
1001 DATA 6291585,-1
To make the conversion easier, the official n808 assembler, n8asm.py, supports
the t74 mode that accepts a plain N8 machine code file and outputs the same
program in the N74 format.
Casio fx-3400P programmable scientific calculator
Unfortunately, the fx-3400P's program memory is too small to be able to fit in
any full-featured n808 VM or assembler, but here's a couple of helper keystroke
programs for converting the four numeric instruction parts into a machine code
instruction and vice versa. The encoding and decoding process is done according
to the formula ins = opcode * 2097152 + p1 * 16384 + p2 * 128 + p3.
These programs will help you with hand-assembling n808 code in case you don't
have a PC or any modern Web-enabled device to do it on.
The complete encoding/decoding suite consisting of both routines to be saved in
the P1 and P2 areas is presented here along with the sequences to enter them
(the ENT key is the same as the RUN key in the program entry mode):
128 Kin 6
MODE EXP SHIFT PCL MODE 1 DEC P1
x Kout 6 + ENT = x Kout 6 + ENT = x Kout 6 + ENT =
SHIFT P2
Kin 1 / Kout 6 = Kin 2 * Kout 6 - Kout 1 = +/- SHIFT HLT Kout 2 SHIFT x>0
MODE .
Note that you need to keep the value 128 in the register 6 at all times for both routines to work correctly.
To encode an instruction, first enter the opcode and press the P1 key, then
enter parameter 1 and press RUN, then enter parameter 2 and press RUN, then
enter parameter 3 and press RUN The program will output the resulting machine
instruction as a single decimal number. In case you're directly hand-assembling
a binary machine code file (N8B) file, you can view the hexadecimal
representation of the number by pressing the MODE 1 HEX sequence (press
MODE 0 to return to the normal mode).
Example: suppose the instruction is out 16 18, which translates to
ino 0 16 18, meaning 3 0 16 18. As expected, after entering each parameter
and pressing the RUN key, the program will output the final result 6293522
to be entered into the VM as the machine code.
To decode an instruction, enter the instruction value and press SHIFT P2. The
program will output the instruction parameters in the reverse order: parameter
3, parameter 2, parameter 1 and then the opcode. Continue pressing the RUN key
until you get all four parameters, press it once more to finish the program.
E.g. if we enter the instruction value 6293522 and press SHIFT P2, the program
will first output 18, then 16, then 0, then 3.
Note that the whole suite takes exactly 29 steps (the entire program memory in
the Casio fx-3400P calculator), so it doesn't clear the mode after finishing.
Once you don't want to stay in the integer calculation mode, press MODE 0 to
return to the normal mode.
Citizen SRP-145 and other programmable calculators using Sharp LI3301A chip
One of the first cheap programmable calculator architectures of the past was the Sharp LI3301A chip that never made it into Sharp's own calculators. I happen to have a Citizen SRP-145T-II based on the same hardware.
Similarly to Casio fx-3400P, this calculator only has enough (40-step) program memory for instruction encoding/decoding helper routines. However, they must be entered separately as the SRP-145T only has a single program storage area. It also has much less register memory, no flow control or integer calculation mode.
Here's what an n808 instruction encoding routine looks like in SRP-145:
SHIFT PGM
x 128 + SHIFT [x] = x 128 + SHIFT [x] = x 128 + SHIFT [x] =
SHIFT PGM
To encode an instruction, first enter the opcode and press the RUN key, then
enter parameter 1 and press RUN, then enter parameter 2 and press RUN, then
enter parameter 3 and press RUN The program will output the resulting machine
instruction as a single decimal number.
Since there's no integer conversion in LI3301A, the instruction decoding routine
relies on a DMS precision exhaustion hack (the DMS is the SHIFT / key to
convert decimal degrees into degrees/minutes/seconds, where the minutes and the
seconds are shown after the decimal point):
SHIFT PGM
MR SHIFT Ka 128 SHIFT 1/x Ka
SHIFT / (23 times)
X->M x 128 - 1 Ka = +/-
SHIFT PGM
To run the routine, enter the instruction value and then press X->M RUN.
The program will output the parameters in the reverse order (first parameter 3,
then parameter 2, then parameter 1, then the opcode). Continue pressing RUN
until you get all four parameters. Clear the memory register(s) when finished.
Note: due to the hackiness of the method, you may get non-integer outputs. Just round the results to the nearest integer if you get a fractional part.
Sharp EL-506P scientific calculator and its clones
These calculators are non-programmable but are the cheapest scientific calcs in the world, and can help you with n808 instruction encoding and decoding.
Since this architecture has algebraic input, encoding is straightforward and based on the initial formula:
[opcode] x 2097152 + [p1] x 16384 + [p2] x 128 + [p3] =
Decoding can be done based on the fact that conversion to hexadecimal and back only leaves the integer part. One of the most optimal sequences is mostly using hexadecimal flow:
[instruction] X->M 2ndf HEX / 80 * 80 - RM = +/- 2ndf DEC # display parameter 3
RM 2ndf HEX / 80 = X->M / 80 * 80 - RM = +/- 2ndf DEC # display parameter 2
RM 2ndf HEX / 80 = X->M / 80 * 80 - RM = +/- 2ndf DEC # display parameter 1
RM 2ndf HEX / 80 = 2ndf DEC # display the opcode
Generic 8-digit four-function calculators
Four-function calculators mostly have their architecture stemming from early Sharp LCD models such as EL-211 and EL-330. They are famous for their extremely low prices, limited precision and non-algebraic input. Because of this, an optimal keystroke sequence for n808 instruction encoding using them would be:
[opcode] x 128 + [p1] x 128 + [p2] x 128 + [p3] =
On the other hand, instruction decoding on such calculators is generally not possible in a fully automated fashion, so the following algorithm is suggested instead:
[instruction] / 2097152 - # note the integer part as the [opcode] value
[opcode] x 128 - # note the integer part as the [p1] value
[p1] x 128 - # note the integer part as the [p2] value
[p2] x 128 = # round to the nearest integer as the [p3] value
FAQ
Why another ultralight VM? Isn't mu808 enough?
While mu808 already is compact enough, it still has some room for optimization when it comes to the instruction format and port-based I/O. n808 aims to be an architecture that can run on the devices where even mu808 would struggle.
Besides, n808 can also serve as a demo platform for the enthusiasts to try and fit as much useful code as possible into the space as tight as 128 program steps and 124 data cells (not counting cells 0, 125, 126 and 127). For instance, the "Examples" section of this document contains several games that could be rather difficult to fit into such space.
Does n808 deprecate mu808 in the same way that mu808 deprecated 808UL?
No. These two VMs are being actively maintained in parallel. Moreover, there are some plans to upgrade mu808's assembly language (MU8A) based on the innovations introduced in the N8A language.
The author is currently more focused on the n808 implementations because it is a more interesting challenge both to port the n808 VM itself and to write useful software for it given the space constraints.
When to choose mu808 and when to choose n808?
Choose mu808 when:
- resource constraints are not a significant factor;
- you need to retain the ability to compose programs in a human-readable machine language (seeing every instruction component as opposed to a single number) in addition to assembly;
- you rely on the interactive mode more than on preloaded programs.
Choose n808 when:
- your target environment is really tight on memory and CPU performance;
- you need or just want to have the machine code as compact as possible;
- you primarily run programs by preloading them and not entering via console.
Credits
Created by Luxferre in 2025, released into public domain with no warranties.