BOX-256
| Paradigm(s) | imperative |
|---|---|
| Designed by | Juha Kiili |
| Appeared in | 2016 |
| Computational class | Finite state automaton |
| Reference implementation | BOX-256 |
| Influenced by | assembly code |
BOX-256 is a programming game and language created in 2016 by Juha Kiili. The objective is to write programs for drawing given images on a 16×16 16 color screen.
Virtual machine
The BOX-256 machine has 256 bytes main memory. It supports threads. Each thread has a single byte instruction pointer that is stored at the end of memory (at 0xFF for the first thread, at 0xFE for the second thread and so on).
Execution starts with a single thread with its instruction pointer set to 0.
Instructions
Instructions are 4 bytes in length, encoding an opcode (with addressing mode) and up to three operands.
Instructions do not have to be aligned to a multiple of 4, and can wrap around at the end of memory. However, the interface only recognizes mnemonics at addresses divisible by 4, so working with misaligned instructions requires the use of numerical opcodes.
There are 3 addressing modes: Immediate (prefix: 0; also - for negative immediates), direct memory (prefix: @), or indirect memory (prefix: *).
Addresses are absolute, with the exception of jump and thread targets where immediates are offsets relative to the current instructions.
The operations are:
| Mnemonic | Description |
|---|---|
| MOV A B C | Copy a block of C bytes from A to B; if A is an immediate, replicate that C times. |
| PIX A B | Set pixel A to color B. |
| JMP A | Jump to A (0: relative, @: absolute, *: indirect) |
| JEQ A B C | Jump to C if A = B |
| JGR A B C | Jump to C if A > B |
| JNE A B C | Jump to C if A ≠ B |
| FLP A B C | Swap memory blocks of C bytes at A and B. |
| THR A | Start a new thread at A (allocates a new instruction pointer) |
| ADD A B C | C = A + B |
| SUB A B C | C = A - B |
| MUL A B C | C = A * B |
| DIV A B C | C = A / B (or 0 if B = 0) |
| MOD A B C | C = A % B (or 0 if B = 0) |
Opcodes
| Range | Mnemonic | Modes in ABC order | Note |
|---|---|---|---|
| 00 | - | - | no operation |
| 01-12 | MOV | 0@0, 0*0, @@0, @*0, *@0, **0, 0@@, 0*@, @@@, @*@, *@@, **@, 0@*, 0**, @@*, @**, *@*, *** | |
| 13-1C | ADD | @0@, *0@, @@@, *@@, **@, @0*, *0*, @@*, *@*, *** | |
| 1D-2C | SUB | @0@, 0@@, @@@, *0@, 0*@, *@@, @*@, **@, @0*, 0@*, @@*, *0*, 0**, *@*, @**, *** | |
| 2D-3B | JEQ | @00, @@0, *00, *@0, **0, @0@, @@@, *0@, *@@, **@, @0*, @@*, *0*, *@*, *** | |
| 3C-45 | MUL | @0@, @@@, *0@, *@@, **@, @0*, @@*, *0*, *@*, *** | |
| 46-53 | DIV | @0@, 0@@, @@@, *0@, *@@, @*@, **@, @0*, 0@*, @@*, *0*, *@*, @**, *** | |
| 54-56 | JMP | 0, @, * | |
| 57-6B | JGR | 0@0, @00, @@0, @*0, *00, *@0, **0, 0@@, @0@, @@@, @*@, *0@, *@@, **@, 0@*, @0*, @@*, @**, *0*, *@*, *** | |
| 6C-74 | PIX | 00, 0@, 0*, @0, @@, @*, *0, *@, ** | |
| 75-7D | FLP | @@0, *@0, **0, @@@, *@@, **@, @@*, *@*, *** | |
| 7E-80 | THR | 0, @, * | |
| 81-90 | MOD | @0@, 0@@, @@@, *0@, 0*@, *@@, @*@, **@, @0*, 0@*, @@*, *0*, 0**, *@*, @**, *** | since 1.1 |
| 91-9F | JNE | @00, @@0, *00, *@0, **0, @0@, @@@, *0@, *@@, **@, @0*, @@*, *0*, *@*, *** | since 1.1 |
| A0-A1 | DIV | 0*@, 0** | since 1.1 |
Unassigned opcodes have no effect, but could be assigned later.
The assembler supports some combinations that are not listed by mapping them to existing ones.
For example, ADD 012 @34 @56 gets translated as ADD @34 012 @56 and ADD 012 034 @56 becomes MOV 046 @56 001.
There are a few bugs as well (as of 1.2):
The assembler does not recognize the 0** versions of DIV and MOD, nor the 0*@ version of MOD.
Furthermore, in the provided documentation, opcodes 17-1B are wrong, flipping * and @ for the first argument.
Interestingly, there are no 0*0, 0*@, or 0** variants of JGR.
Execution model
In each cycle, the threads execute one instruction each, in order.
- read and increment the instruction pointer
- decode the instruction
- execute the instruction; when reading memory, the state of the memory at the start of the cycle is used
Notably, decoding instructions sees changes made by earlier threads during the cycle, making self-modifying code quite powerful in multi-threaded programs.
There is no way to terminate a thread; however, the game will stop execution when the target picture is completed at the end of some cycle.
Example
The following example fills the whole screen with color 7 (white). The value provided is 0xA7 but the top nybble is ignored.
Since there are no registers (except for the number of threads), self-modifying code is used to increment the address in the PIX instruction.
PIX 000 0A7 -00 ADD @01 001 @01 JMP @00 000 000