commit 5810c2f98586d8039cd625b66fe940bccd5c135a Author: Luxferre Date: Sun May 4 11:46:54 2025 +0300 initial upload diff --git a/README.md b/README.md new file mode 100644 index 0000000..7bdb6f9 --- /dev/null +++ b/README.md @@ -0,0 +1,538 @@ +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. diff --git a/examples/assembled/compound.n8 b/examples/assembled/compound.n8 new file mode 100644 index 0000000..4cc3f1e --- /dev/null +++ b/examples/assembled/compound.n8 @@ -0,0 +1,8 @@ +8401409 +6308099 +12632321 +12599169 +12714113 +12616065 +12697729 +6291585 diff --git a/examples/assembled/echo.n8 b/examples/assembled/echo.n8 new file mode 100644 index 0000000..ccdf79b --- /dev/null +++ b/examples/assembled/echo.n8 @@ -0,0 +1,3 @@ +8389899 +6340746 +6324363 diff --git a/examples/assembled/fizzbuzz.n8 b/examples/assembled/fizzbuzz.n8 new file mode 100644 index 0000000..a4b8df2 --- /dev/null +++ b/examples/assembled/fizzbuzz.n8 @@ -0,0 +1,30 @@ +8397628 +8402109 +8397118 +8403647 +8404288 +8389953 +8388609 +12599169 +8388994 +12648578 +8389251 +12648579 +8388741 +2179346 +6331965 +6332480 +6332480 +8388613 +2179479 +6332223 +6332480 +6332480 +8388613 +2097818 +6291585 +2195483 +6332609 +8401412 +12599428 +2130439 diff --git a/examples/assembled/hellorld.n8 b/examples/assembled/hellorld.n8 new file mode 100644 index 0000000..f11eca4 --- /dev/null +++ b/examples/assembled/hellorld.n8 @@ -0,0 +1,11 @@ +8397825 +8401538 +8402435 +8402436 +8402821 +8403206 +8402439 +8401416 +8392841 +8389898 +6324362 diff --git a/examples/assembled/linreg.n8 b/examples/assembled/linreg.n8 new file mode 100644 index 0000000..e519047 --- /dev/null +++ b/examples/assembled/linreg.n8 @@ -0,0 +1,49 @@ +8388618 +8388619 +8388620 +8388621 +8388622 +8388623 +6307970 +8405123 +12616067 +8405252 +12616196 +8405253 +12615813 +12583050 +12583179 +12583308 +12583437 +12583566 +12599183 +12583427 +2113926 +12599055 +8406918 +12617478 +8406407 +12616967 +12600071 +8406918 +12617222 +8406280 +12616712 +12600072 +8406033 +12632977 +8406278 +12617862 +12600710 +8406928 +12632848 +8406406 +12617094 +8406913 +12617345 +12599430 +8406017 +12616449 +12681362 +12632978 +6293522 diff --git a/examples/assembled/lunar.n8 b/examples/assembled/lunar.n8 new file mode 100644 index 0000000..35afb11 --- /dev/null +++ b/examples/assembled/lunar.n8 @@ -0,0 +1,79 @@ +8395905 +8421377 +12599041 +2113665 +8389940 +8391861 +10487861 +11708470 +8394423 +10486199 +10960952 +10800936 +8401465 +12623016 +10749097 +11673130 +11212318 +11393951 +11575584 +11757217 +11938850 +8397220 +10489124 +8397989 +10486949 +8394406 +10486310 +8394407 +10485799 +6296618 +8388610 +2168098 +6308098 +12622722 +8410371 +12589827 +8405252 +12599684 +12632452 +12714497 +12622849 +12604545 +12589697 +8405161 +12622337 +12604417 +8405160 +12604674 +8405290 +2118963 +8388650 +2118709 +8388648 +8411649 +12604417 +2113693 +6296618 +8409985 +12604545 +2113726 +6295842 +2195534 +8409857 +12604545 +2113731 +6295713 +2195534 +8409729 +12604545 +2113736 +6295584 +2195534 +8409601 +12604545 +2113741 +6295455 +2195534 +6295326 +0 diff --git a/examples/assembled/moo.n8 b/examples/assembled/moo.n8 new file mode 100644 index 0000000..a3cffb7 --- /dev/null +++ b/examples/assembled/moo.n8 @@ -0,0 +1,66 @@ +8389898 +8388668 +8388797 +8388926 +8389055 +8389184 +8389313 +8389442 +8389571 +8389700 +8389829 +8396358 +8398919 +8389902 +10485774 +8389131 +8389761 +14680193 +12591873 +8388866 +8454274 +8389891 +12599555 +2097552 +8422657 +8406401 +12592001 +8388867 +8454529 +12599051 +2114960 +8389515 +6319582 +8400198 +8388628 +8389132 +8389133 +8406529 +8414082 +12583042 +8406657 +8413955 +12583043 +8388737 +8454401 +8389124 +8454532 +12599809 +2179255 +8406658 +12600834 +2179382 +12599188 +2195511 +12584724 +12599053 +2115237 +12599052 +2115108 +6294036 +8389121 +12601857 +2097345 +12599051 +2114976 +6301908 diff --git a/examples/assembled/numjack.n8 b/examples/assembled/numjack.n8 new file mode 100644 index 0000000..0badbf8 --- /dev/null +++ b/examples/assembled/numjack.n8 @@ -0,0 +1,117 @@ +8389299 +8390324 +8393270 +8401461 +8388668 +10649660 +2195491 +16767489 +8389890 +12632194 +8388611 +12648707 +12615555 +12615811 +2113937 +8389891 +2310269 +12599043 +2179476 +12589699 +12599171 +2310269 +8402946 +12599426 +8405251 +12589571 +12616066 +12665091 +12632450 +12599042 +12622210 +12599426 +8411779 +12648707 +2310269 +6331190 +6299196 +2170484 +6315709 +8412801 +12606977 +2130164 +8405180 +2326535 +8405438 +8405380 +2326535 +12583358 +8412929 +2326550 +8391297 +12599681 +2097315 +2326535 +8405439 +2326535 +12583359 +8402817 +12607361 +2097390 +6291972 +8388672 +8388673 +6299583 +6308485 +2097873 +12599045 +2097869 +12599045 +2097863 +2195539 +2187347 +8412801 +12606977 +8405180 +12590781 +8388801 +2326535 +12583359 +12599232 +2195539 +8388801 +12599232 +8413057 +2326550 +8391297 +12599681 +2162906 +6299583 +2195491 +2105535 +8405383 +8412929 +2326550 +8405382 +8390657 +12599681 +2113765 +2326535 +12583358 +2195548 +6299455 +8405889 +12600065 +2097394 +2130161 +8391297 +12600065 +2113777 +2195491 +8388865 +12639873 +12583100 +12590780 +12590780 +2195491 +0 diff --git a/examples/compound.n8a b/examples/compound.n8a new file mode 100644 index 0000000..c0cc540 --- /dev/null +++ b/examples/compound.n8a @@ -0,0 +1,12 @@ +; A simple compound interest calculator in N8A for n808 VM +; Prompts for the percentage and then for the period, outputs the resulting multiplier +; Created by Luxferre in 2025, released into public domain + +dca 100 1 ; store the constant 100 at loc 1 +inp 2 3 ; prompt for the percentage into loc 2 and the period into loc 3 +div 2 1 ; divide the percentage value at loc 2 by the constant at loc 1 into loc 1 +inc 1 ; increment loc 1 +log 1 1 ; replace loc 1 with its ln +mul 3 1 ; replace loc 1 with loc 3 * ln loc 1 +exp 1 1 ; replace loc 1 with its nexp +out 1 1 ; output the resulting value diff --git a/examples/echo.n8a b/examples/echo.n8a new file mode 100644 index 0000000..4e59691 --- /dev/null +++ b/examples/echo.n8a @@ -0,0 +1,5 @@ +; Simple 10-character echo test for n808 VM + +dca 10 11 ; set the newline character to loc 11 +ipc 1 10 ; input characters from loc 1 to loc 10 +ouc 1 11 ; output them right away along with the newline diff --git a/examples/fizzbuzz.n8a b/examples/fizzbuzz.n8a new file mode 100644 index 0000000..0d13da7 --- /dev/null +++ b/examples/fizzbuzz.n8a @@ -0,0 +1,39 @@ +; FizzBuzz classical challenge in N8A for n808 VM +; Outputs first 100 FizzBuzz numbers +; Created by Luxferre in 2025, released into public domain + +dca 70 60 ; store Fi into loc 60-61 +dca 105 61 +dca 66 62 ; store Buz + LF into loc 62-65 +dca 117 63 +dca 122 64 +dca 10 65 +#1 cntr ; counter in loc 1 +#2 cmod3 ; variable for counter mod 3 +#3 cmod5 ; variable for counter mod 5 +#4 chk ; variable for loop checks +#5 oflag ; normal output flag variable +dca 0 @cntr ; set counter to 0 +:lp inc @cntr ; loop start, increment the counter +dca 3 @cmod3 ; store constant 3 +mdf @cntr @cmod3 ; store counter mod 3 +dca 5 @cmod5 ; store constant 5 +mdf @cntr @cmod5 ; store counter mod 5 +dca 1 @oflag ; set normal output flag +jne @cmod3 :bu ; jump next if not divisible by 3 +ouc 60 61 ; output Fizz sequence +ouc 64 64 +ouc 64 64 +dca 0 @oflag ; unset normal output flag +:bu jne @cmod5 :no ; jump next if not divisible by 5 +ouc 62 63 ; output Buzz sequence +ouc 64 64 +ouc 64 64 +dca 0 @oflag ; unset normal output flag +:no jeq @oflag :nl ; jump next if normal output flag is off +out @cntr @cntr ; normal counter output +juc :le ; jump to the end of the loop +:nl ouc 65 65 ; output a newline +:le dca 100 @chk ; store the constant 100 into checkvar +sub @cntr @chk ; save the difference into checkvar +jlt @chk :lp ; go back in the loop if not every number is displayed yet diff --git a/examples/hellorld.n8a b/examples/hellorld.n8a new file mode 100644 index 0000000..26401b7 --- /dev/null +++ b/examples/hellorld.n8a @@ -0,0 +1,11 @@ +dca 72 1 ; fill in the data bytes from 1 to 10 +dca 101 2 +dca 108 3 +dca 108 4 +dca 111 5 +dca 114 6 +dca 108 7 +dca 100 8 +dca 33 9 +dca 10 10 ; end the string with an LF character for newline +ouc 1 10 ; output the range as ASCII to port 2 diff --git a/examples/linreg.n8a b/examples/linreg.n8a new file mode 100644 index 0000000..5a93a01 --- /dev/null +++ b/examples/linreg.n8a @@ -0,0 +1,77 @@ +; Linear regression calculator in N8A for n808 VM +; Enter the pairs number by number, end with 0,0 pair +; The program will then output A and B parameters of A + Bx +; linear function and then the correlation coefficient r +; Created by Luxferre in 2025, released into public domain + +; variable/constant definitions +#1 xi ; data x component +#2 yi ; data y component +#3 xs ; xi squared +#4 ys ; yi squared +#5 xy ; xy +#6 buf ; buffer +#7 r1 ; r-coefficient buffer 1 +#8 r2 ; r-coefficient buffer 2 +#10 Sxi ; x sum +#11 Syi ; y sum +#12 Sxx ; x squared sum +#13 Syy ; y squared sum +#14 Sxy ; xy sum +#15 n ; data element counter +#16 A ; coefficient A +#17 B ; coefficient B +#18 RC ; coefficient R +; zero out all sums +dca 0 @Sxi +dca 0 @Syi +dca 0 @Sxx +dca 0 @Syy +dca 0 @Sxy +dca 0 @n +; data input loop +:lp inp @xi @yi ; loop start, input xi and yi pair +dva @xi @xs ; prepare x +mul @xs @xs ; square x +dva @yi @ys ; prepare y +mul @ys @ys ; square y +dva @yi @xy ; prepare y +mul @xi @xy ; save xy +add @xi @Sxi ; update x sum +add @yi @Syi ; update y sum +add @xs @Sxx ; update x squared sum +add @ys @Syy ; update y squared sum +add @xy @Sxy ; update xy sum +inc @n ; increment element count +add @ys @xs ; add y-squared to x-squared +jgt @xs :lp ; loop back if the square sum is over zero +dec @n ; decrement last n to omit the (0,0) input +; processing and output part +dva @n @buf ; n => buffer +mul @Sxy @buf ; n * Sxy => buffer +dva @Syi @r1 ; Syi => r-buffer 1 +mul @Sxi @r1 ; Sxi * Syi => r-buffer 1 +sub @buf @r1 ; n * Sxy - Sxi * Syi => r-buffer 1 (to be stored) +dva @n @buf ; n => buffer +mul @Sxx @buf ; n * Sxx => buffer +dva @Sxi @r2 ; Sxi => r-buffer 2 +mul @r2 @r2 ; Sxi squared => r-buffer 2 +sub @buf @r2 ; n * Sxx - Sx^2 => r-buffer 2 (to be stored) +dva @r2 @B ; prepare coefficient B +div @r1 @B ; store coefficient B +dva @Sxi @buf ; copy Sxi to buffer +mul @B @buf ; B * Sxi => buffer +sub @Syi @buf ; Syi - B * Sxi => buffer +dva @n @A ; prepare coefficient A +div @buf @A ; calculate coefficient A +dva @Syi @buf ; Syi => buffer +mul @Syi @buf ; Syi squared => buffer +dva @n @xi ; reuse xi for the second buffer +mul @Syy @xi ; n * Syy => second buffer +sub @xi @buf ; n * Syy - Sy^2 => buffer +dva @r2 @xi ; r-buffer 2 to xi +mul @buf @xi ; buffer * xi => xi +sqr @xi @RC ; sqrt(xi) => prepare RC +div @r1 @RC ; calculate correlation coefficient +out @A @RC ; output all three resulting numbers + diff --git a/examples/lunar.n8a b/examples/lunar.n8a new file mode 100644 index 0000000..f23ad20 --- /dev/null +++ b/examples/lunar.n8a @@ -0,0 +1,131 @@ +; Lunar Lander game in N8A for n808 VM +; On each turn, the following parameters are displayed in this order: +; altitude (meters), speed (m/s) and remaining fuel (kg) +; Your goal is to apply (or not apply) thrust +; (values that make any sense are 0 to 2000) every 10 seconds of flight +; and get the lunar module to land safely without running out of fuel. +; At the end, the game shows one of the following statuses: +; 4444 is a disaster landing with no survivors, +; 5555 is a crash landing with the crew surviving the impact, +; 6666 is a hard landing with some damage to the pod, +; 7777 is a good landing, +; 8888 is a perfect landing. +; Created by Luxferre in 2025, released into public domain + +; constant/variable area + +#30 code_dis ; disaster code +#31 code_crsh ; crash landing code +#32 code_dmg ; damage landing code +#33 code_good ; good landing code +#34 code_perf ; perfect landing code +#36 crit_crsh ; criterion for crash landing +#37 crit_dmg ; criterion for damage landing +#38 crit_good ; criterion for good landing +#39 crit_perf ; criterion for perfect landing +#40 alt ; module altitude +#41 speed ; module fall speed (m/s) +#42 fuel ; fuel (in kg) +#52 athr ; altitude threshold AND time period +#53 ffsd ; freefall speed delta +#54 capw ; capsule weight (in kg) +#55 burnrate ; fuel burn rate +#56 exvel ; exhaust velocity +#57 c100 ; constant 100 +#1 buf ; buffer variable +#2 floss ; fuel loss value +#3 m0 ; m0 variable in the equation +#4 m1 ; m1 variable in the equation + +; memory initialization part + +; zero out the memory (first 57 cells) +dca 57 1 ; set the counter to 57 +:clp ica 0 1 ; assign 0 to the cell from the counter +dec 1 +jgt 1 :clp + +; set constants and initial variable values + +dca 10 @athr ; altitude threshold / time period +dca 25 @ffsd ; set 16.25 as freefall speed delta +set 0 16 @ffsd +set 74 80 @capw ; set 7480 as capsule weight +dca 45 @burnrate ; set 3.45 as fuel burn rate +set 0 3 @burnrate +set 29 0 @exvel ; set 2900 as exhaust velocity +set 19 30 @alt ; set 1930 as starting altitude +dca 100 @c100 ; set constant 100 +mul @c100 @alt ; multiply this altitude value by 100 +set 16 9 @speed ; set 1609 as starting speed +set 72 60 @fuel ; set 7260 as fuel +set 44 44 @code_dis ; set 4444 as disaster code +set 55 55 @code_crsh ; set 5555 as crash landing code +set 66 66 @code_dmg ; set 6666 as damage landing code +set 77 77 @code_good ; set 7777 as good landing code +set 88 88 @code_perf ; set 8888 as perfect landing code +dca 67 @crit_crsh ; set 26.67 as the criterion for crash landing +set 0 26 @crit_crsh +dca 73 @crit_dmg ; set 9.73 as the criterion for damage landing +set 0 9 @crit_dmg +dca 45 @crit_good ; set 4.45 as the criterion for good landing +set 0 4 @crit_good +dca 45 @crit_perf ; set 0.45 as the criterion for perfect landing +set 0 0 @crit_perf + +; main action/logic part + +:lp out @alt @fuel ; loop start; print altitude, speed and fuel +dca 0 @floss ; set fuel loss to 0 +jle @fuel :cnt ; skip prompting for thrust if already out of fuel +inp @floss @floss ; prompt for thrust into the fuel loss location +mul @burnrate @floss ; multiply thrust by fuel burn rate to get fuel loss +:cnt dva @fuel @m0 ; copy fuel weight into m0 +add @capw @m0 ; add capsule weight and fuel weight to get m0 +dva @floss @m1 ; prepare m1 +sub @m0 @m1 ; m0 - floss => m1 +div @m0 @m1 ; m0 / m1 => m1 +log @m1 @buf ; ln (m0 / (m0 - floss)) => buf +mul @exvel @buf ; multiply the result by the exhaust velocity to get thrust speed delta +sub @speed @buf ; subtract the thrust speed delta from the current speed +add @ffsd @buf ; add the freefall speed delta to the current speed +dva @buf @speed ; copy the resulting speed value back to the holding variable +mul @athr @buf ; multiply speed by time period into the buffer variable +sub @alt @buf ; decrease the altitude by the result of this operation +dva @buf @alt ; restore the altitude variable +sub @fuel @floss ; decrease the amount of fuel by the fuel loss value +dva @floss @fuel ; restore the fuel variable +jgt @fuel :flc ; skip the next instruction if the amount of fuel is positive +dca 0 @fuel ; just set the amount of fuel to zero if it's negative +:flc jgt @alt :al ; do the same for altitude +dca 0 @alt ; set it to zero if negative +:al dva @athr @buf ; copy altitude threshold to the buffer +sub @alt @buf ; subtract the threshold from the altitude +jgt @buf :lp ; go to the loop start if the altitude is above the threshold + +; game finalization/scoring part + +out @alt @fuel ; print the final altitude/speed/fuel +dva @crit_perf @buf ; buffer the perfect speed +sub @speed @buf ; subtract the perfect speed +jgt @buf :good ; skip if > 0 +out @code_perf @code_perf ; output the perfect score +juc :end ; go to end +:good dva @crit_good @buf ; buffer the good speed +sub @speed @buf ; subtract the good speed +jgt @buf :dmg ; skip if > 0 +out @code_good @code_good ; output the good score +juc :end ; go to end +:dmg dva @crit_dmg @buf ; buffer the damage speed +sub @speed @buf ; subtract the damage speed +jgt @buf :crsh ; skip if > 0 +out @code_dmg @code_dmg ; output the damage score +juc :end ; go to end +:crsh dva @crit_crsh @buf ; buffer the crash speed +sub @speed @buf ; subtract the crash speed +jgt @buf :disa ; skip if > 0 +out @code_crsh @code_crsh ; output the crash score +juc :end ; go to end +:disa out @code_dis @code_dis ; output the disaster score +:end nnn ; program end label + diff --git a/examples/moo.n8a b/examples/moo.n8a new file mode 100644 index 0000000..d07d5ee --- /dev/null +++ b/examples/moo.n8a @@ -0,0 +1,108 @@ +; Bulls and Cows game in N8A for n808 VM +; Enter your guesses digit by digit, you have 7 attempts +; After each guess, the game replies with bulls.cows +; (if you have 4.0, you win) +; On victory or after running out of attempts, the game +; displays the target digits and halts +; Created by Luxferre in 2025, released into public domain + +; constants/variables section +#1 va +#2 vb +#3 vc +#4 vd +#10 c10 +#11 counter +#12 octr +#13 ictr +#14 frac +#20 bcctr +#60 dig_0 +#61 dig_1 +#62 dig_2 +#63 dig_3 +#64 dig_4 +#65 dig_5 +#66 dig_6 +#67 dig_7 +#68 dig_8 +#69 dig_9 +#70 src_base ; source base address +#71 trg_base ; target base address + +; constant/variable assignments +dca 10 @c10 ; constant 10 +dca 0 @dig_0 ; assign digits from 0 to 9 +dca 1 @dig_1 +dca 2 @dig_2 +dca 3 @dig_3 +dca 4 @dig_4 +dca 5 @dig_5 +dca 6 @dig_6 +dca 7 @dig_7 +dca 8 @dig_8 +dca 9 @dig_9 +dca 60 @src_base ; set the source base address (60) +dca 80 @trg_base ; set the target base address (80) +dca 10 @frac ; prepare the @frac variable +set 0 0 @frac ; set 0.1 to @frac + +; main logic + +; random unique 4-digit generator (into the addresses 81..84) + +dca 4 @counter ; set the counter to 4 +:dsl dca 9 @va ; digit selection loop start, set the upper boundary to @va +rnd 0 @va @va ; select a random digit from 0 to 9 inclusively into @va +add @src_base @va ; add the source base address to @va +dca @vb @vb ; init @vb with its own address +ivc @va @vb ; copy the value at address in @va into @vb +dca 10 @vc ; init @vc with the constant 10 +sub @vb @vc ; @vb - 10 => @vc +jeq @vc :dsl ; jump back to the digit selection if the value at @vc is 0 +ica 10 @va ; set the value at the address in @va to 10 +dva @counter @va ; copy the counter to @va +add @trg_base @va ; add the target base address to the counter in @va +dca @vb @vc ; copy the address of @vb into @vc +ivc @vc @va ; copy the value from @vb (address stored at @vc) to the cell address at @va +dec @counter ; decrement the counter +jgt @counter :dsl ; jump back to digit selection if it still is above zero + +; player guess loop + +dca 7 @counter ; set the attempt count to 7 +:prm inp 91 94 ; input the guess digit by digit into loc 91..94 +dca 90 @src_base ; set 90 as the new source base address +dca 0 @bcctr ; init bull/cow counter +dca 4 @octr ; init outer loop counter +:olp dca 4 @ictr ; start of the outer loop, init inner loop counter +:ilp dva @octr @va ; start of the inner loop, copy the outer counter +dva @trg_base @vb ; fetch the target base address +add @va @vb ; get the address of the target digit in @vb +dva @ictr @va ; copy the inner counter +dva @src_base @vc ; fetch the source base address +add @va @vc ; get the address of the entered digit in @vc +dca @va @va ; init @va with its own address +ivc @vb @va ; copy the target digit into @va +dca @vd @vd ; init @vd with its own address +ivc @vc @vd ; copy the entered digit into @vd +sub @vd @va ; save the digits difference into @va +jne @va :ei ; jump to the next comparator if the digits don't match +dva @ictr @vb ; load the inner counter into @vb +sub @octr @vb ; save the _counters_ difference into @vb +jne @vb :cc ; jump to the cow counter if the indices don't match +inc @bcctr ; increment the bull counter if they do +juc :ei ; skip the next instruction +:cc add @frac @bcctr ; increase the cow counter if they don't +:ei dec @ictr ; decrement the inner loop counter +jgt @ictr :ilp ; jump to the start of the inner loop if still > 0 +dec @octr ; decrement the outer loop counter +jgt @octr :olp ; jump to the start of the outer loop if still > 0 +out @bcctr @bcctr ; output the match result +dca 4 @va ; store the constant 4 into @va +sub @bcctr @va ; get the difference between bull/cow counter and 4 +jeq @va :end ; jump to the last instruction if they match +dec @counter ; decrement the attempt counter +jgt @counter :prm ; jump to guess prompt if the counter is above 0 +:end out 81 84 ; output the target number before halting + diff --git a/examples/numjack.n8a b/examples/numjack.n8a new file mode 100644 index 0000000..29e2678 --- /dev/null +++ b/examples/numjack.n8a @@ -0,0 +1,166 @@ +; A Blackjack port in N8A assembly for n808 VM +; How to play: +; * you start with a $1000 balance +; * on each round, enter your bet +; (the game will quit if the bet is above your balance) +; * if you hit a blackjack, your balance will increase immediately +; * if the dealer hits a blackjack, your balance will decrease immediately +; * the first card of the dealer's hand will be shown +; (card values are: ace is 101, 2 to 9 are "as is", 10 is 10 to K) +; * on the first turn, select 0 (stand), 1 (hit) or 2 (double) +; * on each next turn, select 0 (stand) or 1 (hit) +; * as a result of the round, the dealer's final hand will be shown +; first and then yours +; * the dealer must draw on 16 and stand on any 17 +; * player's blackjack pays 3 to 2 +; Created by Luxferre in 2025, released into public domain + +; constant/variable space +#1 va +#2 vb +#3 vc +#4 vd +#5 action +#6 dscore +#7 pscore +#127 c1 +#51 c5 +#52 c_13 +#53 c_hund +#54 c_ds +#60 balance +#61 bet +#62 dhand +#63 phand +#64 round +#65 stand + +; constant assignments +dca 5 @c5 +dca 13 @c_13 +dca 36 @c_ds +dca 100 @c_hund +dca 0 @balance +set 10 0 @balance + +juc :main ; jump to the main code after initialization + +; card retrieval procedure +; the resulting card is in the @vc cell +:gcard rnd @c1 @c_13 @va ; get a random number from 1 to 13 incl +dca 10 @vb ; assign 10 to the second buffer +div @va @vb ; va / 10 => vb +dca 0 @vc ; 0 => vc +mdf @vb @vc ; floor(va/10) => vc +sub @c1 @vc ; subtract it from 1 +mul @va @vc ; multiply it by the random choice itself +jgt @vc :gnext ; skip the next part if > 0 +dca 10 @vc ; return 10 +ret +:gnext dec @vc ; decrement +jne @vc :gnr ; skip the next part if == 0 +add @c_hund @vc ; add 100 +:gnr inc @vc ; increment back +ret + +; scoring procedure, parameter is in the @va cell +; the result is in the @vc cell +:score dca 112 @vb ; set vb to 112 +sub @va @vb ; set vb to va - 112 +dva @vb @vc ; copy vb value into vc +add @c_13 @vc ; add 13 to vc +mul @vc @vb ; vb * vc => vb +abs @vb @vc ; abs(vb) => vc +div @vc @vb ; abs(vb) / vb => vb +dec @vb ; vb - 1 => vb +mul @c5 @vb ; vb * 5 => vb +sub @va @vb ; va - vb => vb +dva @c_hund @vc ; store 100 into vc +mdf @vb @vc ; store vb mod 100 into vc +ret + +; main code part + +:main ouc @c_ds @c_ds ; output a dollar sign if supported +out @balance @balance ; output the current balance +jle @balance :end ; game over if zero or less +inp @bet @bet ; input your bet +dva @bet @va ; buffer the bet +sub @balance @va ; va = balance - bet +jlt @va :end ; game over if the bet is invalid +dva @va @balance ; restore the balance value from va +jpr :gcard ; call the card generation procedure +dva @vc @dhand ; copy the result as the dealer's hand value +dva @vc @vd ; copy the first dealer hand card into vd +jpr :gcard ; call the card generation procedure again +add @vc @dhand ; complete the dealer's hand +dva @dhand @va ; copy the dealer's hand as the va param +jpr :score ; run the scoring procedure (result in @vc) +dca 21 @va ; set the constant 21 to @va +sub @vc @va ; compare the procedure result with 21 +jeq @va :main ; loop back if we have the dealer's blackjack +jpr :gcard ; call the card generation procedure +dva @vc @phand ; save the first card into the player's hand +jpr :gcard ; call the card generation procedure +add @vc @phand ; add the second card into the player's hand +dca 111 @va ; prepare constant 111 +sub @phand @va ; compare player's hand to 111 +jeq @va :bjk ; jump to blackjack condition on player's blackjack +out @vd @vd ; display the start of the dealer's hand +dca 0 @round ; set the round index to 0 +dca 0 @stand ; set the stand flag to 0 +:rnl out @phand @phand ; start of the inner player loop, display the player's hand +inp @action @action ; input the action value +jeq @action :std ; jump to stand action if 0 +dec @action ; check if 1 +jeq @action :hit ; jump to hit action if 1 +dec @action ; check if 2 +jeq @action :dbl ; jump to double action if 2 +juc :skp ; skip otherwise +:dbl jne @round :skp ; skip double if not the first round +dva @bet @va ; buffer the bet +sub @balance @va ; subtract more balance +dva @va @balance ; restore the variable +add @bet @bet ; double the bet +dca 1 @stand ; set the stand flag, proceed to the hit section +:hit jpr :gcard ; generate a new card in @vc +add @vc @phand ; add it to the player's hand +inc @round ; increment the round index +juc :skp ; jump to skip the rest +:std dca 1 @stand ; just set the stand flag +inc @round ; increment the round index +:skp dva @phand @va ; set the player's hand as a parameter to @va +jpr :score ; call the scoring procedure (result in @vc) +dca 21 @va ; set 21 to va +sub @vc @va ; subtract 21 from the result +jle @va :nob ; reloop to beginning if the player is bust +out @phand @phand ; output the player's hand +juc :main ; reloop +:nob jeq @stand :rnl ; repeat the inner loop if the stand flag is 0 +dva @vc @pscore ; at this point, player score is now in @vc +:rdl dva @dhand @va ; start the inner dealer loop, set the dealer hand param +jpr :score ; call the scoring procedure (result in @vc) +dva @vc @dscore ; save the dealer's score +dca 16 @va ; compare @vc with 16 +sub @vc @va ; the difference is in @va +jgt @va :dbrk ; go to stand if the difference is over 16 +jpr :gcard ; call the card generation procedure +add @vc @dhand ; add the result to dealer's hand +juc :rdl ; repeat the inner dealer loop +:dbrk out @dhand @phand ; output both hands (dealer's first) +dva @pscore @va ; buffer the player's score +sub @dscore @va ; subtract it from the dealer's score +jeq @va :push ; push condition +jlt @va :win ; player win condition +dca 21 @va ; prepare constant 21 +sub @dscore @va ; compare dealer's score with 21 +jgt @va :win ; player win condition +juc :main ; reloop to beginning +:bjk dca 2 @va ; set 2 to @va +div @bet @va ; halve the bet +add @va @balance ; add half the bet to the balance +:win add @bet @balance ; add the bet the first time +:push add @bet @balance ; add the bet the second time +juc :main ; reloop to beginning +:end nnn ; program end label + diff --git a/n808.awk b/n808.awk new file mode 100644 index 0000000..eb37537 --- /dev/null +++ b/n808.awk @@ -0,0 +1,113 @@ +#!/usr/bin/env awk -f +# n808 VM reference implementation in POSIX AWK +# Run with: LC_ALL=C awk -f n808.awk [- input_program.n8] +# Supports the entire n808 spec except the I/O port 3 +# See the README.md file for all documentation +# Created by Luxferre in 2025, released into public domain + +# absolute value function +function fabs(v) {return (v < 0) ? -v : v} + +# port I/O function (all ports supported except 3) +function portio(port, data) { + if(port == 0) printf("%f\n", data) # standard numeric output + else if(port == 1) getline data # standard numeric input + else if(port == 2) printf("%c", int(data) % 256) # character output + return +data +} + +# main instruction execution logic +function iexec(lno, halt, data_override, cmd, p1, p2, p3, i, v1, v2, v3) { + halt = data_override = 0 + while(halt == 0) { # decode and execute the current instruction + cmd = int(PMEM[lno] / 2097152) % 8 # command opcode + p1 = (data_override ? int(v1) : int(PMEM[lno] / 16384)) % 128 # parameter 1 + p2 = (data_override ? int(v2) : int(PMEM[lno] / 128)) % 128 # parameter 2 + p3 = (data_override ? int(v3) : PMEM[lno]) % 128 # parameter 3 + DMEM[0] = data_override = 0 # enforce the 0 at the location 0 + DMEM[127] = 1 # enforce the 1 at the location 127 + DMEM[126] = -1 # enforce the -1 at the location 126 + v1 = DMEM[p1]; v2 = DMEM[p2]; v3 = DMEM[p3] # prefetch the values + if(cmd == 1) { # JMP + if(p1 == 14) DMEM[125] = lno + 1 + if((v2 == 0 && p1 == 0) || (v2 > 0 && p1 == 1) || (v2 < 0 && p1 == 2) \ + || (v2 >= 0 && p1 == 3) || (v2 <= 0 && p1 == 4) || (v2 != 0 && p1 == 5) \ + || p1 == 6 || p1 == 14) lno = p3 - 1 + else if((v2 == 0 && p1 == 7) || (v2 > 0 && p1 == 8) || (v2 < 0 && p1 == 9) \ + || (v2 >= 0 && p1 == 10) || (v2 <= 0 && p1 == 11) || (v2 != 0 && p1 == 12) \ + || p1 == 13) lno = int(v3) - 1 + } else if(cmd == 2) data_override = 1 # IAT + else if(cmd == 3) for(i=p2;i<=p3;i++) DMEM[i] = portio(p1, DMEM[i]) # INO + else if(cmd == 4) { # CPY + if(p1 == 0) DMEM[p3] = p2 + else if(p1 == 1) DMEM[p3] = v2 + else if(p1 == 2) DMEM[int(v3)] = p2 + else if(p1 == 3) DMEM[int(v3)] = v2 + else if(p1 == 4) DMEM[int(v3)] = DMEM[int(v2)] + } else if(cmd == 5) DMEM[p3] = p1 * 100 + p2 + v3 / 100.0 # SET + else if(cmd == 6) { # MAT + if(p1 == 0) DMEM[p3] = v2 + v3 + else if(p1 == 1) DMEM[p3] = v2 - v3 + else if(p1 == 2) DMEM[p3] = v2 * v3 + else if(p1 == 3) DMEM[p3] = (v3 == 0) ? 0 : (v2 / v3) + else if(p1 == 4) DMEM[p3] = (v3 == 0) ? int(v2) : (v2 % v3) + else if(p1 == 5) DMEM[p3] = fabs(v2) + else if(p1 == 6) DMEM[p3] = sqrt(fabs(v2)) + else if(p1 == 7) DMEM[p3] = exp(v2) + else if(p1 == 8) DMEM[p3] = (v2 == 0) ? 0 : log(fabs(v2)) + else if(p1 == 9) DMEM[p3] = sin(v2) + else if(p1 == 10) DMEM[p3] = cos(v2) + else if(p1 == 11) DMEM[p3] = atan(v2) + } else if(cmd == 7) # RND + DMEM[p3] = int(v1) + int(rand() * (int(v2) - int(v1) + 1)) + lno++ # increment the program counter + if(lno >= MEMLIMIT) halt = 1 + } +} + +# interactive mode entry function +function intermode(cmd, p1, p2) { + if(cmd == 0 && p1 < MEMLIMIT) iexec(p1) # run from the step + else if(cmd == 1) PMEM[p1] = p2 # enter the instruction into PMEM + else if(cmd == 2 && p1 < MEMLIMIT && p2 < MEMLIMIT) # clear instructions + for(cmd=p1;cmd<=p2;cmd++) PMEM[cmd] = 0 + else if(cmd == 3 && p1 < MEMLIMIT && p2 < MEMLIMIT) # clear data + for(cmd=p1;cmd<=p2;cmd++) DMEM[cmd] = 0.0 + else if(cmd == 4) {print("Bye!"); exit(0)} +} + +BEGIN { # VM entry point + MEMLIMIT = 128 + for(i=0;i 1) { # preload the input program + fname = ARGV[ARGC-1] + iindex = 0 + while(getline < fname) { # iterate over the file lines + csize = split($0, icache) + for(i=0;i ") + iindex = cmd = p1 = p2 = 0 + while(getline) { # main REPL + csize = split($0, icache) + for(i=0;i ") + } +} diff --git a/n808.b74 b/n808.b74 new file mode 100644 index 0000000..dbaddac --- /dev/null +++ b/n808.b74 @@ -0,0 +1,69 @@ +100 RANDOMIZE:RAD +110 DIM PMEM(129),DMEM(128) +120 RESTORE 1000 +130 I=0 +140 READ PMEM(I) +150 I=I+1 +160 IF PMEM(I-1)>-1 THEN 140 +170 L,IAT,V1,V2,V3=0 +180 A=PMEM(L):L=L+1 +190 IF A<0 THEN 770 +200 CMD=INT(A/2097152) +210 IF IAT=1 THEN 220 ELSE 230 +220 P1=INT(V1):P2=INT(V2):P3=INT(V3):IAT=0:GOTO 250 +230 A=A-CMD*2097152:P1=INT(A/16384):A=A-P1*16384 +240 P2=INT(A/128):P3=A-P2*128 +250 DMEM(0)=0:DMEM(127)=1:DMEM(126)=-1 +260 V1=DMEM(P1):V2=DMEM(P2):V3=DMEM(P3) +270 IF CMD=1 THEN 280 ELSE 430 +280 IF P1=0 AND V2=0 THEN L=P3:GOTO 180 +290 IF P1=1 AND V2>0 THEN L=P3:GOTO 180 +300 IF P1=2 AND V2<0 THEN L=P3:GOTO 180 +310 IF P1=3 AND V2>=0 THEN L=P3:GOTO 180 +320 IF P1=4 AND V2<=0 THEN L=P3:GOTO 180 +330 IF P1=5 AND V2<>0 THEN L=P3:GOTO 180 +340 IF P1=6 THEN L=P3:GOTO 180 +350 IF P1=7 AND V2=0 THEN L=INT(V3):GOTO 180 +360 IF P1=8 AND V2>0 THEN L=INT(V3):GOTO 180 +370 IF P1=9 AND V2<0 THEN L=INT(V3):GOTO 180 +380 IF P1=10 AND V2>=0 THEN L=INT(V3):GOTO 180 +390 IF P1=11 AND V2<=0 THEN L=INT(V3):GOTO 180 +400 IF P1=12 AND V2<>0 THEN L=INT(V3):GOTO 180 +410 IF P1=13 THEN L=INT(V3):GOTO 180 +420 IF P1=14 THEN DMEM(125)=L:L=P3:GOTO 180 +430 IF CMD=2 THEN IAT=1:GOTO 180 +440 IF CMD=3 THEN 450 ELSE 520 +450 FOR I=P2 TO P3 +460 IF P1=0 THEN PRINT(DMEM(I)):PAUSE +470 IF P1=1 THEN INPUT DMEM(I) +480 IF P1=2 THEN 490 ELSE 500 +490 IF DMEM(I)=10 THEN PAUSE ELSE PRINT(CHR$(INT(DMEM(I)))); +500 IF P1=3 THEN DMEM(I)=ASC(KEY$) +510 NEXT I:GOTO 180 +520 IF CMD=4 THEN 530 ELSE 580 +530 IF P1=0 THEN DMEM(P3)=P2:GOTO 180 +540 IF P1=1 THEN DMEM(P3)=V2:GOTO 180 +550 IF P1=2 THEN DMEM(INT(V3))=P2:GOTO 180 +560 IF P1=3 THEN DMEM(INT(V3))=V2:GOTO 180 +570 IF P1=4 THEN DMEM(INT(V3))=DMEM(INT(V2)):GOTO 180 +580 IF CMD=5 THEN DMEM(P3)=P1*100+P2+V3/100:GOTO 180 +590 IF CMD=6 THEN 600 ELSE 720 +600 IF P1=0 THEN DMEM(P3)=V2+V3:GOTO 180 +610 IF P1=1 THEN DMEM(P3)=V2-V3:GOTO 180 +620 IF P1=2 THEN DMEM(P3)=V2*V3:GOTO 180 +630 IF P1=3 THEN 640 ELSE 650 +640 IF V3=0 THEN DMEM(P3)=0:GOTO 180 ELSE DMEM(P3)=V2/V3:GOTO 180 +650 IF P1=4 THEN 660 ELSE 680 +660 IF V3=0 THEN DMEM(P3)=INT(V2):GOTO 180 ELSE 670 +670 DMEM(P3)=V2-V3*INT(V2/V3):GOTO 180 +680 IF P1=5 THEN DMEM(P3)=ABS(V2):GOTO 180 +690 IF P1=6 THEN DMEM(P3)=SQR(ABS(V2)):GOTO 180 +700 IF P1=7 THEN DMEM(P3)=EXP(V2):GOTO 180 +710 IF P1=8 THEN DMEM(P3)=LN(ABS(V2)):GOTO 180 +720 IF P1=9 THEN DMEM(P3)=SIN(V2):GOTO 180 +730 IF P1=10 THEN DMEM(P3)=COS(V2):GOTO 180 +740 IF P1=11 THEN DMEM(P3)=ATN(V2):GOTO 180 +750 IF CMD=7 THEN DMEM(P3)=INT(V1)+INT(RND*(INT(V2)-INT(V1)+1)) +760 GOTO 180 +770 END + diff --git a/n808.c b/n808.c new file mode 100644 index 0000000..83ae846 --- /dev/null +++ b/n808.c @@ -0,0 +1,144 @@ +/** + * n808: an ultralight numeric-only VM + * ANSI C89 reference implementation + * Compile with: cc -std=c89 -O2 -s -lm -o n808 n808.c + * See the README.md file for all documentation + * Created by Luxferre in 2025, released into public domain + */ + +#include +#include +#include +#include + +/* POSIX-specific terminal stuff for unbuffered input for I/O port 2 */ +#if defined (__unix__) || (defined (__APPLE__) && defined (__MACH__)) +#define NLC "\n" +#include +#include +struct termios orig_termios; +void disable_raw_mode() { + tcsetattr(STDIN_FILENO, TCSAFLUSH, &orig_termios); +} +void enable_raw_mode() { + tcgetattr(STDIN_FILENO, &orig_termios); + atexit(disable_raw_mode); + struct termios raw = orig_termios; + raw.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON); + raw.c_oflag &= ~(OPOST); + raw.c_cflag |= (CS8); + raw.c_lflag &= ~(ECHO | ICANON | IEXTEN | ISIG); + tcsetattr(STDIN_FILENO, TCSAFLUSH, &raw); +} +#else +#define NLC "\r\n" +void disable_raw_mode() {} +void enable_raw_mode() {} +#endif + +/* core memory */ +#define MEMLIMIT 128 +static unsigned int PMEM[MEMLIMIT] = {0}; +static double DMEM[MEMLIMIT] = {0.0}; +#define uchar unsigned char + +/* port input/output function */ +void portio(uchar port, double *data) { + if(port == 0) printf("%f" NLC, *data); /* standard numeric output */ + else if(port == 1) scanf("%lf", data); /* standard numeric input */ + else if(port == 2) fputc(((int)floor(*data)) & 255, stdout); /* char output */ + else if(port == 3) { /* character input */ + enable_raw_mode(); + *data = (double) (fgetc(stdin) & 255); + disable_raw_mode(); + } +} + +/* main instruction execution logic */ +void iexec(unsigned short lno) { + uchar halt = 0, data_override = 0, cmd, p1, p2, p3, i; + double v1, v2, v3; + while(halt == 0) { /* decode and execute the current instruction */ + cmd = (PMEM[lno] >> 21) & 7; /* command opcode */ + p1 = (data_override ? ((int)floor(v1)) : (PMEM[lno] >> 14)) & 127; /* parameter 1 */ + p2 = (data_override ? ((int)floor(v2)) : (PMEM[lno] >> 7)) & 127; /* parameter 2 */ + p3 = (data_override ? ((int)floor(v3)) : PMEM[lno]) & 127; /* parameter 3 */ + DMEM[0] = data_override = 0; /* enforce the 0 at the location 0 */ + DMEM[127] = 1; /* enforce the 1 at the location 127 */ + DMEM[126] = -1; /* enforce the -1 at the location 126 */ + v1 = DMEM[p1]; v2 = DMEM[p2]; v3 = DMEM[p3]; /* prefetch the values */ + if(cmd == 1) { /* JMP */ + if(p1 == 14) DMEM[125] = lno + 1; + if((v2 == 0 && p1 == 0) || (v2 > 0 && p1 == 1) || (v2 < 0 && p1 == 2) \ + || (v2 >= 0 && p1 == 3) || (v2 <= 0 && p1 == 4) || (v2 != 0 && p1 == 5) \ + || p1 == 6 || p1 == 14) lno = p3 - 1; + else if((v2 == 0 && p1 == 7) || (v2 > 0 && p1 == 8) || (v2 < 0 && p1 == 9) \ + || (v2 >= 0 && p1 == 10) || (v2 <= 0 && p1 == 11) || (v2 != 0 && p1 == 12) \ + || p1 == 13) lno = (int)floor(v3) - 1; + } else if(cmd == 2) data_override = 1; /* IAT */ + else if(cmd == 3) for(i=p2;i<=p3;i++) portio(p1, &DMEM[i]); /* INO */ + else if(cmd == 4) { /* CPY */ + if(p1 == 0) DMEM[p3] = p2; + else if(p1 == 1) DMEM[p3] = v2; + else if(p1 == 2) DMEM[(int)v3] = p2; + else if(p1 == 3) DMEM[(int)v3] = v2; + else if(p1 == 4) DMEM[(int)v3] = DMEM[(int)v2]; + } else if(cmd == 5) DMEM[p3] = p1 * 100 + p2 + v3 / 100.0; /* SET */ + else if(cmd == 6) { /* MAT */ + if(p1 == 0) DMEM[p3] = v2 + v3; + else if(p1 == 1) DMEM[p3] = v2 - v3; + else if(p1 == 2) DMEM[p3] = v2 * v3; + else if(p1 == 3) DMEM[p3] = (v3 == 0) ? 0 : (v2 / v3); + else if(p1 == 4) DMEM[p3] = (v3 == 0) ? floor(v2) : fmod(v2, v3); + else if(p1 == 5) DMEM[p3] = fabs(v2); + else if(p1 == 6) DMEM[p3] = sqrt(fabs(v2)); + else if(p1 == 7) DMEM[p3] = exp(v2); + else if(p1 == 8) DMEM[p3] = (v2 == 0) ? 0 : log(fabs(v2)); + else if(p1 == 9) DMEM[p3] = sin(v2); + else if(p1 == 10) DMEM[p3] = cos(v2); + else if(p1 == 11) DMEM[p3] = atan(v2); + } else if(cmd == 7) { /* RND */ + v1 = floor(v1); + DMEM[p3] = v1 + (rand() % (int)(floor(v2) + 1 - v1)); + } + lno++; /* increment the program counter */ + if(lno >= MEMLIMIT) halt = 1; + } +} + +/* interactive mode entry function */ +void intermode(unsigned int cmd, unsigned int p1, unsigned int p2) { + unsigned int i; + if(cmd == 0 && p1 < MEMLIMIT) iexec(p1); /* run from the step */ + else if(cmd == 1) PMEM[p1] = p2; /* enter the instruction into PMEM */ + else if(cmd == 2 && p1 < MEMLIMIT && p2 < MEMLIMIT) /* clear instructions */ + for(i=p1;i<=p2;i++) PMEM[i] = 0; + else if(cmd == 3 && p1 < MEMLIMIT && p2 < MEMLIMIT) /* clear data */ + for(i=p1;i<=p2;i++) DMEM[i] = 0.0; + else if(cmd == 4) {puts("Bye!"); exit(0);} +} + +/* entry point to the VM REPL */ +void main(int argc, char* argv[]) { + srand(time(NULL)); + unsigned int cmd, p1, p2, instr, lno = 0; /* commands and their parameters */ + if(argc > 1) { /* preload the file from a command line parameter */ + FILE *fd = fopen(argv[1], "r"); + if(fd != NULL) { /* opened successfully */ + while(!feof(fd)) { + if(fscanf(fd, "%u", &instr) == 1) { /* read the instruction number */ + intermode(1, lno, instr); /* run the entry routine */ + lno++; + } + } + fclose(fd); + } else puts("Warning: no file could be preloaded!"); + } + while(1) { /* main interactive loop */ + printf("> "); + cmd = p1 = p2 = 0; + scanf("%u %u %u", &cmd, &p1, &p2); /* read the three numbers */ + while(getchar() != '\n'); /* ignore the rest of the line */ + intermode(cmd, p1, p2); /* run the entry routine */ + } +} diff --git a/n808.py b/n808.py new file mode 100755 index 0000000..5c27bb9 --- /dev/null +++ b/n808.py @@ -0,0 +1,132 @@ +#!/usr/bin/env python3 +# n808 VM reference implementation in Python 3/MicroPython +# Supports the entire n808 spec +# See README.md for all documentation +# Created by Luxferre in 2025, released into public domain + +import sys, math, random, re + +advterm = False +try: + import tty, termios # for the character input port + advterm = True +except: + pass + +MEMLIMIT:int = 128 +PMEM = [0 for i in range(0, MEMLIMIT)] # program memory +DMEM = [0.0 for i in range(0, MEMLIMIT)] # data memory + +# port I/O function +def portio(port:int, data:float): + if port == 0: print(data) # standard numeric output + elif port == 1: # standard numeric input + try: data = float(input()) + except ValueError: data = 0 + elif port == 2: # character output + sys.stdout.write(chr(int(data)&255)) + sys.stdout.flush() + elif port == 3: # character input + ch = '\0' + if advterm: # normal OS with termios + fd = sys.stdin.fileno() + old_settings = termios.tcgetattr(fd) + try: + tty.setraw(fd) + ch = sys.stdin.read(1) + finally: + termios.tcsetattr(fd, termios.TCSADRAIN, old_settings) + else: # crippled OS without termios + ch = sys.stdin.read(1) + data = float(ord(ch)) + return data + +# main instruction execution logic +def iexec(lno:int): + global PMEM, DMEM + data_override = 0 + while True: # decode and execute the current instruction + cmd = (PMEM[lno] >> 21) & 7 # command opcode + p1 = (int(v1) if data_override else (PMEM[lno] >> 14)) & 127 # parameter 1 + p2 = (int(v2) if data_override else (PMEM[lno] >> 7)) & 127 # parameter 2 + p3 = (int(v3) if data_override else PMEM[lno]) & 127 # parameter 3 + DMEM[0] = data_override = 0 # enforce the 0 at the location 0 + DMEM[127] = 1 # enforce the 1 at the location 127 + DMEM[126] = -1 # enforce the -1 at the location 126 + v1 = DMEM[p1]; v2 = DMEM[p2]; v3 = DMEM[p3] # prefetch the values + if cmd == 1: # JMP + if p1 == 14: DMEM[125] = lno + 1 + if (v2 == 0 and p1 == 0) or (v2 > 0 and p1 == 1) or (v2 < 0 and p1 == 2) \ + or (v2 >= 0 and p1 == 3) or (v2 <= 0 and p1 == 4) or (v2 != 0 and p1 == 5) \ + or p1 == 6 or p1 == 14: lno = p3 - 1 + elif (v2 == 0 and p1 == 7) or (v2 > 0 and p1 == 8) or (v2 < 0 and p1 == 9) \ + or (v2 >= 0 and p1 == 10) or (v2 <= 0 and p1 == 11) or (v2 != 0 and p1 == 12) \ + or p1 == 13: lno = int(v3) - 1 + elif cmd == 2: data_override = 1 # IAT + elif cmd == 3: # INO + for i in range(p2,p3+1): DMEM[i] = portio(p1, DMEM[i]) + elif cmd == 4: # CPY + if p1 == 0: DMEM[p3] = p2 + elif p1 == 1: DMEM[p3] = v2 + elif p1 == 2: DMEM[int(v3)] = p2 + elif p1 == 3: DMEM[int(v3)] = v2 + elif p1 == 4: DMEM[int(v3)] = DMEM[int(v2)] + elif cmd == 5: DMEM[p3] = p1 * 100 + p2 + v3 / 100.0 # SET + elif cmd == 6: # MAT + if p1 == 0: DMEM[p3] = v2 + v3 + elif p1 == 1: DMEM[p3] = v2 - v3 + elif p1 == 2: DMEM[p3] = v2 * v3 + elif p1 == 3: DMEM[p3] = 0 if v3 == 0 else (v2 / v3) + elif p1 == 4: DMEM[p3] = math.floor(v2) if v3 == 0 else (v2 % v3) + elif p1 == 5: DMEM[p3] = math.fabs(v2) + elif p1 == 6: DMEM[p3] = math.sqrt(math.fabs(v2)) + elif p1 == 7: DMEM[p3] = math.exp(v2) + elif p1 == 8: DMEM[p3] = 0 if v2 == 0 else math.log(math.fabs(v2)) + elif p1 == 9: DMEM[p3] = math.sin(v2) + elif p1 == 10: DMEM[p3] = math.cos(v2) + elif p1 == 11: DMEM[p3] = math.atan(v2) + elif cmd == 7: DMEM[p3] = float(random.randint(int(v1), int(v2))) # RND + lno += 1 # increment the program counter + if lno >= MEMLIMIT: break + +# interactive mode entry function +def intermode(cmd:int, p1:int, p2:int): + if cmd == 0 and p1 < MEMLIMIT: iexec(p1) # run from the step + elif cmd == 1 and p1 < MEMLIMIT: PMEM[p1] = p2 # enter the instruction into PMEM + elif cmd == 2 and p1 < MEMLIMIT and p2 < MEMLIMIT: # clear instructions + for cmd in range(p1, p2+1): PMEM[cmd] = 0 + elif cmd == 3 and p1 < MEMLIMIT and p2 < MEMLIMIT: # clear data + for cmd in range(p1, p2+1): DMEM[cmd] = 0.0 + elif cmd == 4: + print("Bye!") + sys.exit(0) + +# main REPL environment +if __name__ == '__main__': + vreg = re.compile(r'[^-\d]+') + if len(sys.argv) > 1: # preload the file contents + fc = '' + try: + fd = open(sys.argv[1], 'r') + fc = fd.read() + close(fd) + except: + pass + instr = [] # current instruction cache + iindex = 0 + for instrpart in vreg.split(fc): + if len(instrpart) > 0: + intermode(1, iindex, int(instrpart)) + instr.append(int(instrpart)) + iindex += 1 + instr = [] # current instruction cache + while True: # main interactive loop + rinput = input('> ') + if len(rinput) > 0: + for v in vreg.split(rinput): # loop over the current input + if len(v) > 0: + instr.append(v) + if len(instr) == 3: # full instruction registered + intermode(int(instr[0]), int(instr[1]), int(instr[2])) + instr = [] + diff --git a/n8asm.py b/n8asm.py new file mode 100755 index 0000000..a0d05a2 --- /dev/null +++ b/n8asm.py @@ -0,0 +1,238 @@ +#!/usr/bin/env python3 +# a simple assembler/disassembler for the n808 VM +# supports both text and binary program formats +# usage: n8asm [at|ab|dt|db|t2b|b2t|t74] file outfile +# modes: +# at: assemble to plaintext (N8) machine code +# ab: assemble to binary (N8B) machine code +# dt: disassemble plaintext (N8) machine code +# db: disassemble binary (N8B) machine code +# t2b: convert from plaintext N8 to binary N8B +# b2t: convert from binary N8B to plaintext N8 +# t74: convert from plaintext N8 to the TI-74 representation +# Created by Luxferre in 2025, released into public domain + +import sys, re, struct + +# n808 mnemonics for assembly and disasssembly +mnemos = ['nop', 'jmp', 'iat', 'ino', 'cpy', 'set', 'mat', 'rnd'] + +# n808 jump/function shortcuts (arranged from longest to shortest) +jmpfunc_shorts = { + 'ret': 'jmp 13 0 125', # return from a procedure + 'nnn': 'nop 0 0 0', # alias for nop + 'inc': 'mat 0 127', # increment + 'dec': 'mat 0 126', # decrement + 'inv': 'mat 3 127', # inverse/reciprocal + 'iuc': 'jmp 13 0', # indirect unconditional jump + 'jpr': 'jmp 14 0', # jump to a procedure + 'neg': 'mat 1 0', # negation + 'juc': 'jmp 6 0', # direct unconditional jump + '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 + 'cos': 'mat 10', # cosine + 'atn': 'mat 11', # arctangent + '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 + '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 + 'out': 'ino 0', # normal numeric output + 'inp': 'ino 1', # normal numeric input + 'ouc': 'ino 2', # character output + 'ipc': 'ino 3', # character input + '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 + 'add': 'mat 0', # addition + 'sub': 'mat 1', # subtraction + 'mul': 'mat 2', # multiplication + 'div': 'mat 3', # division + 'mdf': 'mat 4', # modulo/floor + 'abs': 'mat 5', # absolute value + 'sqr': 'mat 6', # square root + 'exp': 'mat 7', # natural exponent + 'log': 'mat 8', # natural logarithm + 'sin': 'mat 9' # sine +} + +# converts the N8A source code to the plaintext machine code representation +def assemble(source): + labels = {} + out = '' + lno = 0 + vreg = re.compile(r'\s+') + for shrt, meaning in jmpfunc_shorts.items(): # replace the shortcuts + source = source.replace(shrt, meaning) + for line in source.split('\n'): # parse the main code + fields = [] + line = line.split(';')[0].strip() + if len(line) > 0: # actual line to be counted on + fields = vreg.split(line)[:5] # split it into fields + if fields[0].startswith(':'): # this is a label + labels[fields[0]] = lno # remember the label + fields = fields[1:] # remove the label from fields + elif fields[0].startswith('#'): # this is an alias + labels['@' + fields[1].strip()] = int(fields[0][1:]) + continue # do not update the instruction number + try: + cmd = mnemos.index(fields[0].lower()) + except ValueError: + try: + cmd = int(cmd) + except: + print('Assembly error - unknown mnemonic at line', lno) + sys.exit(1) + try: + out += ' '.join([str(cmd), fields[1], fields[2], fields[3]]) + '\n' + except IndexError: + print('Assembly error - not enough operands at line', lno) + sys.exit(1) + lno += 1 # update the instruction number + for lbl, lineno in labels.items(): + out = out.replace(lbl, str(lineno)) + final = '' # prepare the final text + for line in out.split('\n'): + if len(line) > 0: + fields = vreg.split(line)[:4] # split it into fields + instr = int(fields[0]) * 2097152 + int(fields[1]) * 16384 + instr += int(fields[2]) * 128 + int(fields[3]) + final += str(instr) + '\n' + return final + +# converts the plaintext machine code representation to N8A source code +def disassemble(machcode): + vreg = re.compile(r'\s+') + iindex = 0 + instrs = [] + for instrnum in vreg.split(machcode): + if len(instrnum) > 0: + instrnum = int(instrnum) + opcode = (instrnum >> 21) & 7 + p1 = (instrnum >> 14) & 127 + p2 = (instrnum >> 7) & 127 + p3 = instrnum & 127 + instrs.append([iindex, opcode, p1, p2, p3]) + iindex += 1 + # now, instrs contains 5-number groups + assembly = [None for i in range(0, 128)] + labels = {} + for instr in instrs: # iterate over each instruction + lno, cmd, x, y, z = instr + try: + mnemo = mnemos[cmd] + except IndexError: + print('Disassembly error - unknown opcode at line', lno) + sys.exit(1) + if cmd == 1: # save the label for jump instructions + labels[z] = ':lbl_' + str(z) + assembly[lno] = [mnemo, str(x), str(y), labels[z]] + else: + assembly[lno] = [mnemo, str(x), str(y), str(z)] + out = '' + lno = 0 + for asline in assembly: + if lno in labels: + out += labels[lno] + ' ' + if asline is not None: + out += ' '.join(asline) + '\n' + lno += 1 + for shrt, meaning in jmpfunc_shorts.items(): # replace the shortcuts + out = out.replace(meaning, shrt) + return out + +# converts the plaintext machine code representation to binary representation +def tobinary(txtrep): + instrs = [] # instruction list to store here + vreg = re.compile(r'\s+') + out = b'' + for instr in vreg.split(txtrep): + if len(instr) > 0: + instr = int(instr) + out += struct.pack('BBB', (instr >> 16) & 255, (instr >> 8) & 255, instr & 255) + return out + +# converts the binary machine code representation to plaintext representation +def totext(binrep): + out = '' + while len(binrep) > 0: + chunk = binrep[0:3] + binrep = binrep[3:] + b1, b2, b3 = struct.unpack('BBB', chunk) + instr = (b1 << 16) | (b2 << 8) | b3 + out += str(instr) + '\n' + return out + +# converts the N8 plaintext machine code to the TI-74 DATA statements (N74) +def ti74data(txtrep:str, baseaddr:int=1000): + instrs = [] # instruction list to store here + vreg = re.compile(r'\s+') + out = '' + iindex = 0 + for instr in vreg.split(txtrep): + if len(instr) > 0: + instrs.append(str(int(instr))) + instrs.append(str(-1)) # add the terminating instruction + while len(instrs) > 0: + dchunk = instrs[:7] + instrs = instrs[7:] + out += f'{baseaddr + iindex} DATA {','.join(dchunk)}\n' + iindex += 1 + return out + +def main(): + if len(sys.argv) < 4: + print("Usage: n8asm [at|ab|dt|db|t2b|b2t|t74] file outfile") + return + mode = sys.argv[1] + srcfile = sys.argv[2] + targetfile = sys.argv[3] + srctext = '' + inpmode = 'r' + if mode == 'b2t' or mode == 'db': + inpmode = 'rb' + try: + fd = open(srcfile, inpmode) + srctext = fd.read() + close(fd) + except: + pass + if len(srctext) > 0: + outmode = 'w' + output = '' + if mode == 'b2t': # binary-to-text + output = totext(srctext) + elif mode == 't2b': # text-to-binary + output = tobinary(srctext) + outmode = 'wb' + elif mode == 'dt': # disassemble text + output = disassemble(srctext) + elif mode == 'db': # disassemble binary + srctext = totext(srctext) + output = disassemble(srctext) + elif mode == 't74': # convert to TI-74 DATA statements + output = ti74data(srctext) + else: # assemble + output = assemble(srctext) # default mode + if mode == 'ab': + output = tobinary(output) + outmode = 'wb' + # write the output file + try: + fd = open(targetfile, outmode) + fd.write(output) + close(fd) + except: + pass + else: + print('Nothing to process!') + +if __name__ == '__main__': + main()