Files
n808/README.md
T
2025-05-04 11:56:58 +03:00

24 KiB

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 14 instructions 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 p3 if v2 equals to zero;
  • 1: jump to the address p3 if v2 is above zero;
  • 2: jump to the address p3 if v2 is below zero;
  • 3: jump to the address p3 if v2 is above or equals to zero;
  • 4: jump to the address p3 if v2 is below or equals to zero;
  • 5: jump to the address p3 if v2 does not equal to zero;
  • 6: jump to the address p3 unconditionally;
  • 7: jump to the address v3 if v2 equals to zero;
  • 8: jump to the address v3 if v2 is above zero;
  • 9: jump to the address v3 if v2 is below zero;
  • 10: jump to the address v3 if v2 is above or equals to zero;
  • 11: jump to the address v3 if v2 is below or equals to zero;
  • 12: jump to the address v3 if v2 does not equal to zero;
  • 13: jump to the address v3 unconditionally;
  • 14: save the next instruction pointer to the cell 125 and jump to the address p3 unconditionally.

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 p3 to p2;
  • 1: set the memory cell p3 to v2;
  • 2: set the memory cell v3 (converted to integer) to p2;
  • 3: set the memory cell v3 (converted to integer) to v2;
  • 4: set the memory cell v3 (converted to integer) to the value at address v2 (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 p3 to v2 + v3;
  • 1: set the memory cell p3 to v2 - v3;
  • 2: set the memory cell p3 to v2 * v3;
  • 3: set the memory cell p3 to v2 / v3 if v3 is not zero, otherwise set it to zero;
  • 4: set the memory cell p3 to v2 mod v3 if v3 is not zero, otherwise set it to the integer part of v2.
  • 5: set the memory cell p3 to |v2| (absolute value of v2);
  • 6: set the memory cell p3 to the square root of |v2|;
  • 7: set the memory cell p3 to the natural exponent of v2 (e ** v2);
  • 8: set the memory cell p3 to ln |v2|;
  • 9: set the memory cell p3 to sin v2 (v2 given in radians);
  • 10: set the memory cell p3 to cos v2 (v2 given in radians);
  • 11: set the memory cell p3 to arctg 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 address p1 to p2 (incl.);
  • 3 [p1] [p2]: clear a range of data from address p1 to p2 (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:

  1. Remove all comments (starting with ; until the end of the line).
  2. Replace all jump/function shortcuts according to the above mnemonics.
  3. Record the current line number N (not counting completely empty lines), starting with 0.
  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 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).
  6. 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.
  7. 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.
  8. Write the result as a new line into the intermediate text file.
  9. 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.
  10. 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:

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 .n8 and .n8b files, 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.

Is n808 compatible with DTMF input?

Yes. It is enough to use the * (asterisk) key to enter a minus sign at the beginning of input or a decimal point elsewhere, and the # (hash/pound) key for number entry confirmation. The VM itself never distinguishes between the newline, space or other separators. To it, here are just numbers and a delimiter between them.

Credits

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