initial upload

This commit is contained in:
Luxferre
2025-05-04 11:46:54 +03:00
commit 5810c2f985
22 changed files with 2146 additions and 0 deletions
+538
View File
@@ -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.
+8
View File
@@ -0,0 +1,8 @@
8401409
6308099
12632321
12599169
12714113
12616065
12697729
6291585
+3
View File
@@ -0,0 +1,3 @@
8389899
6340746
6324363
+30
View File
@@ -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
+11
View File
@@ -0,0 +1,11 @@
8397825
8401538
8402435
8402436
8402821
8403206
8402439
8401416
8392841
8389898
6324362
+49
View File
@@ -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
+79
View File
@@ -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
+66
View File
@@ -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
+117
View File
@@ -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
+12
View File
@@ -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
+5
View File
@@ -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
+39
View File
@@ -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
+11
View File
@@ -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
+77
View File
@@ -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
+131
View File
@@ -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
+108
View File
@@ -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
+166
View File
@@ -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
+113
View File
@@ -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("> ")
}
}
+69
View File
@@ -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
+144
View File
@@ -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 */
}
}
Executable
+132
View File
@@ -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 = []
Executable
+238
View File
@@ -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()