initial upload
This commit is contained in:
@@ -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.
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
8401409
|
||||||
|
6308099
|
||||||
|
12632321
|
||||||
|
12599169
|
||||||
|
12714113
|
||||||
|
12616065
|
||||||
|
12697729
|
||||||
|
6291585
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
8389899
|
||||||
|
6340746
|
||||||
|
6324363
|
||||||
@@ -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
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
8397825
|
||||||
|
8401538
|
||||||
|
8402435
|
||||||
|
8402436
|
||||||
|
8402821
|
||||||
|
8403206
|
||||||
|
8402439
|
||||||
|
8401416
|
||||||
|
8392841
|
||||||
|
8389898
|
||||||
|
6324362
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
|
|
||||||
@@ -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
|
||||||
|
|
||||||
@@ -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
|
||||||
|
|
||||||
@@ -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
|
||||||
|
|
||||||
@@ -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<MEMLIMIT;i++) DMEM[i] = PMEM[i] = 0 # initialize both areas
|
||||||
|
srand()
|
||||||
|
if(ARGC > 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<csize;i++) {
|
||||||
|
a = int(icache[i+1])
|
||||||
|
intermode(1, iindex, a) # enter nth instruction
|
||||||
|
iindex++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
close(fname)
|
||||||
|
}
|
||||||
|
printf("> ")
|
||||||
|
iindex = cmd = p1 = p2 = 0
|
||||||
|
while(getline) { # main REPL
|
||||||
|
csize = split($0, icache)
|
||||||
|
for(i=0;i<csize;i++) {
|
||||||
|
a = int(icache[i+1])
|
||||||
|
if(iindex == 0) cmd = a
|
||||||
|
else if(iindex == 1) p1 = a
|
||||||
|
else if(iindex == 2) p2 = a
|
||||||
|
iindex++
|
||||||
|
if(iindex == 3) { # pefrorm the entry
|
||||||
|
intermode(cmd, p1, p2)
|
||||||
|
iindex = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
printf("> ")
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
|
||||||
@@ -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 <stdlib.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <time.h>
|
||||||
|
#include <math.h>
|
||||||
|
|
||||||
|
/* POSIX-specific terminal stuff for unbuffered input for I/O port 2 */
|
||||||
|
#if defined (__unix__) || (defined (__APPLE__) && defined (__MACH__))
|
||||||
|
#define NLC "\n"
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <termios.h>
|
||||||
|
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 */
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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 = []
|
||||||
|
|
||||||
@@ -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()
|
||||||
Reference in New Issue
Block a user