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](https://codeberg.org/luxferre/mu808) 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](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](examples/compound.n8a), * [Linear regression calculator](examples/linreg.n8a), * [Hellorld!](examples/hellorld.n8a) (a tribute to @UsagiElectric) (requires I/O port 2 support), * [FizzBuzz classic program](examples/fizzbuzz.n8a) (requires I/O port 2 support), * [A simple 10-character echo test](examples/echo.n8a) (requires both I/O port 2 and port 3 support), * [Bulls and Cows game](examples/moo.n8a), * [Lunar Lander game](examples/lunar.n8a), * [NumberJack](examples/numjack.n8a) 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](n808.c) (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](n808.py): supports the entire n808 spec and runs in any Python 3 environment. Only preloads the `.n8`-type code files. * [POSIX AWK implementation](n808.awk): 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](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](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.