2024-05-23 19:45:26 +02:00
|
|
|
================================================================
|
|
|
|
|
|
|
|
crc's
|
|
|
|
_ _ _
|
|
|
|
(_) | ___ ___ ___ _ __ ___ _ __ _ _| |_ ___ _ __
|
|
|
|
| | |/ _ \ / __/ _ \| '_ ` _ \| '_ \| | | | __/ _ \ '__|
|
|
|
|
| | | (_) | | (_| (_) | | | | | | |_) | |_| | || __/ |
|
|
|
|
|_|_|\___/ \___\___/|_| |_| |_| .__/ \__,_|\__\___|_|
|
|
|
|
|_|
|
|
|
|
|
|
|
|
================================================================
|
|
|
|
|
|
|
|
ilo is a virtual computer implementing a dual stack processor
|
|
|
|
with a simple instruction set and a few i/o devices. It's small
|
|
|
|
and can be easily implemented and/or extended by an individual
|
|
|
|
programmer.
|
|
|
|
|
|
|
|
# limitations
|
|
|
|
|
|
|
|
+---------------+-----------------------+
|
|
|
|
| Item | Size |
|
|
|
|
+===============+=======================+
|
|
|
|
| memory | 65,536 cells |
|
|
|
|
| data stack | 32 numbers |
|
|
|
|
| address stack | 256 numbers |
|
|
|
|
| cell | 32-bit signed integer |
|
|
|
|
| min value | -2,147,483,647 |
|
|
|
|
| max value | 2,147,483,646 |
|
|
|
|
+---------------+-----------------------+
|
|
|
|
|
|
|
|
# memory model
|
|
|
|
|
|
|
|
ilo provides a memory space consisting of 32-bit, signed
|
|
|
|
integer values. The first address is mapped to zero, with
|
|
|
|
subsequent values following in a strictly linear fashion.
|
|
|
|
|
|
|
|
Each addressable 32-bit unit is called a *cell*. There is no
|
|
|
|
support in the instruction set for accessing values larger or
|
|
|
|
smaller than a single cell.
|
|
|
|
|
|
|
|
# registers
|
|
|
|
|
|
|
|
ilo maintains an internal `instruction pointer` register. This
|
|
|
|
document will refer to it as IP. There may also be internal
|
|
|
|
stack pointers or other registers.
|
|
|
|
|
|
|
|
No registers are exposed via the instruction set.
|
|
|
|
|
|
|
|
# image file
|
|
|
|
|
|
|
|
On startup, ilo will load an *image file* ("rom"). This is a
|
|
|
|
flat, linear sequence of signed integer values. On disk images
|
|
|
|
are stored in little endian format.
|
|
|
|
|
|
|
|
The first value loaded is mapped to address zero, and subsequent
|
|
|
|
values are loaded to sequential addresses in memory.
|
|
|
|
|
|
|
|
An image file does not contain copies of the stacks or any
|
|
|
|
internal registers.
|
|
|
|
|
|
|
|
# endian
|
|
|
|
|
|
|
|
The image and block files are stored in little endian format.
|
|
|
|
|
|
|
|
# stacks
|
|
|
|
|
|
|
|
There are two stacks, one for general data and one for holding
|
|
|
|
return addresses for calls. The return (or address) stack can
|
|
|
|
be used to temporarily hold values as well.
|
|
|
|
|
|
|
|
Stacks are LIFO (last in, first out).
|
|
|
|
|
|
|
|
# instruction set
|
|
|
|
|
|
|
|
ilo has 30 instructions. In short, these are:
|
|
|
|
|
|
|
|
+----+----+--------+-------------------------------------------+
|
|
|
|
| Op | Nm | Stack | Description |
|
|
|
|
+====+====+========+===========================================+
|
|
|
|
| 00 | .. | - | non-op |
|
|
|
|
| 01 | li | -n | push value in following cell to stack |
|
|
|
|
| 02 | du | n-nn | duplicate top stack item |
|
|
|
|
| 03 | dr | n- | discard top stack item |
|
|
|
|
| 04 | sw | ab-ba | swap top two stack items |
|
|
|
|
| 05 | pu | n- | move top stack item to address stack |
|
|
|
|
| 06 | po | -n | move top address stack item to data stack |
|
|
|
|
| 07 | ju | a- | jump to an address |
|
|
|
|
| 08 | ca | a- | call a function |
|
|
|
|
| 09 | cc | fa- | call a function if the flag is non-zero |
|
|
|
|
| 10 | cj | fa- | jump to a function if the flag is non-zero|
|
|
|
|
| 11 | re | - | return from a call or conditional call |
|
|
|
|
| 12 | eq | ab-f | compare two values for equality. a == b |
|
|
|
|
| 13 | ne | ab-f | compare two values for inequality. a != b |
|
|
|
|
| 14 | lt | ab-f | compare two values for less than. a < b |
|
|
|
|
| 15 | gt | ab-f | compare two values for greater than. a > b|
|
|
|
|
| 16 | fe | a-n | fetch a stored value in memory |
|
|
|
|
| 17 | st | na- | store a value into memory |
|
|
|
|
| 18 | ad | ab-c | add two numbers. a + b |
|
|
|
|
| 19 | su | ab-c | subtract two numbers. a - b |
|
|
|
|
| 20 | mu | ab-c | multiply two numbers. a * b |
|
|
|
|
| 21 | di | ab-cd | divide and get remainder. a % b, a / b |
|
|
|
|
| 22 | an | ab-c | bitwise and |
|
|
|
|
| 23 | or | ab-c | bitwise or |
|
|
|
|
| 24 | xo | ab-c | bitwise xor |
|
|
|
|
| 25 | sl | ab-c | shift left. a << b |
|
|
|
|
| 26 | sr | ab-c | shift right. a >> b |
|
|
|
|
| 27 | cp | sdn-f | compare two memory regions |
|
|
|
|
| 28 | cy | sdn- | copy memory |
|
|
|
|
| 29 | io | n- | perform i/o operation |
|
|
|
|
+----+----+--------+-------------------------------------------+
|
|
|
|
|
|
|
|
A condensed summary table:
|
|
|
|
|
|
|
|
Opode Instruction Names Data Stack Effects
|
|
|
|
===== ================= ====================================
|
|
|
|
00-05 .. li du dr sw pu - -n n-nn n- nm-mn n-
|
|
|
|
06-11 po ju ca cc cj re -n a- a- fa- fa- -
|
|
|
|
12-17 eq ne lt gt fe st nn-f nn-f nn-f nn-f a-n na-
|
|
|
|
18-23 ad su mu di an or nn-n nn-n nn-n nn-nn nn-n nn-n
|
|
|
|
24-29 xo sl sr cp cy io nn-n nn-n nn-n nnn- nnn- n-
|
|
|
|
===== ================= ====================================
|
|
|
|
|
|
|
|
And in detail:
|
|
|
|
|
|
|
|
+--------+------+----------------------------------------------+
|
|
|
|
| Opcode | Name | Action |
|
|
|
|
+========+======+==============================================+
|
|
|
|
| 00 | .. | Non-op. Does nothing; used for padding in |
|
|
|
|
| | | instruction bundles |
|
|
|
|
+--------+------+----------------------------------------------+
|
|
|
|
| 01 | li | Push value in the following cell to the stack|
|
|
|
|
| | | |
|
|
|
|
| | | Increment IP, then fetch the value in memory |
|
|
|
|
| | | at IP and place on the stack. |
|
|
|
|
| | | |
|
|
|
|
| | | ip += 1; |
|
|
|
|
| | | push(memory[ip]); |
|
|
|
|
+--------+------+----------------------------------------------+
|
|
|
|
| 02 | du | Duplicate the top value on the stack. |
|
|
|
|
| | | |
|
|
|
|
| | | sp += 1; |
|
|
|
|
| | | data[sp] = data[sp - 1]; |
|
|
|
|
| | | |
|
|
|
|
| | | If there is not a value on the stack an |
|
|
|
|
| | | underflow occurs. If there are 32 an |
|
|
|
|
| | | overflow occurs. |
|
|
|
|
+--------+------+----------------------------------------------+
|
|
|
|
| 03 | dr | Discard the top stack item. |
|
|
|
|
| | | |
|
|
|
|
| | | sp -= 1; |
|
|
|
|
| | | |
|
|
|
|
| | | If no items are on the stack, an underflow |
|
|
|
|
| | | occurs. |
|
|
|
|
+--------+------+----------------------------------------------+
|
|
|
|
| 04 | sw | Swap the top two items on the stack. |
|
|
|
|
| | | |
|
|
|
|
| | | a = data[sp]; |
|
|
|
|
| | | b = data[sp - 1]; |
|
|
|
|
| | | data[sp - 1] = a; |
|
|
|
|
| | | data[sp] = b; |
|
|
|
|
| | | |
|
|
|
|
| | | If there are not at least two values on the |
|
|
|
|
| | | stack, and underflow occurs. |
|
|
|
|
+--------+------+----------------------------------------------+
|
|
|
|
| 05 | pu | Move the top stack item to the address stack.|
|
|
|
|
| | | |
|
|
|
|
| | | rp += 1; |
|
|
|
|
| | | addresses[rp] = data[sp]; |
|
|
|
|
| | | sp -= 1; |
|
|
|
|
+--------+------+----------------------------------------------+
|
|
|
|
| 06 | po | Move the top address stack item to the data |
|
|
|
|
| | | stack. |
|
|
|
|
| | | |
|
|
|
|
| | | sp += 1; |
|
|
|
|
| | | data[sp] = addresses[rp]; |
|
|
|
|
| | | rp -= 1; |
|
|
|
|
+--------+------+----------------------------------------------+
|
|
|
|
| 07 |ju | jump to an address |
|
|
|
|
| | | modifies the instruction pointer |
|
|
|
|
| | | |
|
|
|
|
| | | Due to how the execution cycle works |
|
|
|
|
| | | (IP is incremented at the end of the |
|
|
|
|
| | | instruction bundle processing), the |
|
|
|
|
| | | address needs to be ajusted to account |
|
|
|
|
| | | for this. |
|
|
|
|
| | | |
|
|
|
|
| | | ip = data[sp] - 1; |
|
|
|
|
| | | sp -= 1; |
|
|
|
|
+--------+------+----------------------------------------------+
|
|
|
|
| 08 | ca | call a function |
|
|
|
|
| | | modifies the instruction pointer |
|
|
|
|
| | | |
|
|
|
|
| | | A call is like the `ju`mp instruction, but |
|
|
|
|
| | | adds the original IP to the address stack. |
|
|
|
|
| | | |
|
|
|
|
| | | rp += 1; |
|
|
|
|
| | | address[rp] = ip; |
|
|
|
|
| | | ip = data[sp] - 1; |
|
|
|
|
| | | sp -= 1; |
|
|
|
|
+--------+------+----------------------------------------------+
|
|
|
|
| 09 | cc | call a function if the flag is non-zero |
|
|
|
|
| | | modifies the instruction pointer |
|
|
|
|
| | | |
|
|
|
|
| | | Conditional calls are like `ca`lls, but |
|
|
|
|
| | | factor in a flag. |
|
|
|
|
| | | |
|
|
|
|
| | | target = pop(); |
|
|
|
|
| | | if (pop() != 0) { |
|
|
|
|
| | | rp += 1; |
|
|
|
|
| | | address[rp] = ip; |
|
|
|
|
| | | ip = target - 1; |
|
|
|
|
| | | } |
|
|
|
|
+--------+------+----------------------------------------------+
|
|
|
|
| 10 | cj | jump to a function if the flag is non-zero |
|
|
|
|
| | | modifies the instruction pointer |
|
|
|
|
| | | |
|
|
|
|
| | | Conditional jumps are like `ju`mp, but |
|
|
|
|
| | | factor in a flag. |
|
|
|
|
| | | |
|
|
|
|
| | | target = pop(); |
|
|
|
|
| | | if (pop() != 0) { |
|
|
|
|
| | | ip = target - 1; |
|
|
|
|
| | | } |
|
|
|
|
+--------+------+----------------------------------------------+
|
|
|
|
| 11 | re | return from a call or conditional call |
|
|
|
|
| | | modifies the instruction pointer |
|
|
|
|
| | | |
|
|
|
|
| | | ip = address[rp]; |
|
|
|
|
| | | rp -= 1; |
|
|
|
|
+--------+------+----------------------------------------------+
|
|
|
|
| 12 | eq | compare two values for equality |
|
|
|
|
| | | |
|
|
|
|
| | | if (data[sp - 1] == data[sp]) |
|
|
|
|
| | | data[sp - 1] = -1; |
|
|
|
|
| | | else |
|
|
|
|
| | | data[sp - 1] = 0; |
|
|
|
|
| | | sp -= 1; |
|
|
|
|
+--------+------+----------------------------------------------+
|
|
|
|
| 13 | ne | compare two values for inequality. |
|
|
|
|
| | | |
|
|
|
|
| | | if (data[sp - 1] != data[sp]) |
|
|
|
|
| | | data[sp - 1] = -1; |
|
|
|
|
| | | else |
|
|
|
|
| | | data[sp - 1] = 0; |
|
|
|
|
| | | sp -= 1; |
|
|
|
|
+--------+------+----------------------------------------------+
|
|
|
|
| 14 | lt | compare two values for less than. |
|
|
|
|
| | | |
|
|
|
|
| | | if (data[sp - 1] < data[sp]) |
|
|
|
|
| | | data[sp - 1] = -1; |
|
|
|
|
| | | else |
|
|
|
|
| | | data[sp - 1] = 0; |
|
|
|
|
| | | sp -= 1; |
|
|
|
|
+--------+------+----------------------------------------------+
|
|
|
|
| 15 | gt | compare two values for greater than. |
|
|
|
|
| | | |
|
|
|
|
| | | if (data[sp - 1] > data[sp]) |
|
|
|
|
| | | data[sp - 1] = -1; |
|
|
|
|
| | | else |
|
|
|
|
| | | data[sp - 1] = 0; |
|
|
|
|
| | | sp -= 1; |
|
|
|
|
+--------+------+----------------------------------------------+
|
|
|
|
| 16 | fe | fetch a stored value in memory |
|
|
|
|
| | | |
|
|
|
|
| | | data[sp] = memory[data[sp]]; |
|
|
|
|
+--------+------+----------------------------------------------+
|
|
|
|
| 17 | st | store a value into memory |
|
|
|
|
| | | |
|
|
|
|
| | | memory[data[sp]] = data[sp - 1]; |
|
|
|
|
| | | sp -= 2; |
|
|
|
|
+--------+------+----------------------------------------------+
|
|
|
|
| 18 | ad | add two numbers |
|
|
|
|
| | | |
|
|
|
|
| | | data[sp - 1] += data[sp]; |
|
|
|
|
| | | sp -= 1; |
|
|
|
|
+--------+------+----------------------------------------------+
|
|
|
|
| 19 | su | subtract two numbers |
|
|
|
|
| | | |
|
|
|
|
| | | data[sp - 1] -= data[sp]; |
|
|
|
|
| | | sp -= 1; |
|
|
|
|
+--------+------+----------------------------------------------+
|
|
|
|
| 20 | mu | multiply two numbers |
|
|
|
|
| | | |
|
|
|
|
| | | data[sp - 1] *= data[sp]; |
|
|
|
|
| | | sp -= 1; |
|
|
|
|
+--------+------+----------------------------------------------+
|
|
|
|
| 21 | di | divide and get remainder. |
|
|
|
|
| | | |
|
|
|
|
| | | Take two values from the data stack. The top |
|
|
|
|
| | | item is the dividend, and the second item is |
|
|
|
|
| | | the divisor. Perform the division, and push |
|
|
|
|
| | | the quotient and remainder to the stack. |
|
|
|
|
| | | |
|
|
|
|
| | | After execution the quotient will be on top, |
|
|
|
|
| | | with the remainder below it. |
|
|
|
|
| | | |
|
|
|
|
| | | *Division is symmetric, not floored*. |
|
|
|
|
| | | |
|
|
|
|
| | | a = data[sp]; |
|
|
|
|
| | | b = data[sp - 1]; |
|
|
|
|
| | | data[sp] = b / a; |
|
|
|
|
| | | data[sp - 1] = b % a; |
|
|
|
|
+--------+------+----------------------------------------------+
|
|
|
|
| 22 | an | bitwise and |
|
|
|
|
| | | |
|
|
|
|
| | | data[sp - 1] = data[sp - 1] & data[sp]; |
|
|
|
|
| | | sp -= 1; |
|
|
|
|
+--------+------+----------------------------------------------+
|
|
|
|
| 23 | or | bitwise or |
|
|
|
|
| | | |
|
|
|
|
| | | data[sp - 1] = data[sp - 1] | data[sp]; |
|
|
|
|
| | | sp -= 1; |
|
|
|
|
+--------+------+----------------------------------------------+
|
|
|
|
| 24 | xo | bitwise xor |
|
|
|
|
| | | |
|
|
|
|
| | | data[sp - 1] = data[sp - 1] ^ data[sp]; |
|
|
|
|
| | | sp -= 1; |
|
|
|
|
+--------+------+----------------------------------------------+
|
|
|
|
| 25 | sl | shift bits left |
|
|
|
|
| | | |
|
|
|
|
| | | data[sp - 1] = data[sp - 1] << data[sp]; |
|
|
|
|
| | | sp -= 1; |
|
|
|
|
| | | |
|
|
|
|
| | | The results of a negative shift are |
|
|
|
|
| | | implementation specific. |
|
|
|
|
| | | |
|
|
|
|
| | | The results of large shifts are implementaion|
|
|
|
|
| | | specific. |
|
|
|
|
+--------+------+----------------------------------------------+
|
|
|
|
| 26 | sr | shift bits right |
|
|
|
|
| | | |
|
|
|
|
| | | data[sp - 1] = data[sp - 1] >> data[sp]; |
|
|
|
|
| | | sp -= 1; |
|
|
|
|
| | | |
|
|
|
|
| | | The results of a negative shift are |
|
|
|
|
| | | implementation specific. |
|
|
|
|
| | | |
|
|
|
|
| | | The results of large shifts are implementaion|
|
|
|
|
| | | specific. |
|
|
|
|
+--------+------+----------------------------------------------+
|
|
|
|
| 27 | cp | compare two memory regions |
|
|
|
|
| | | |
|
|
|
|
| | | len = data[sp]; |
|
|
|
|
| | | dest = data[sp - 1]; |
|
|
|
|
| | | src = data[sp - 2]; |
|
|
|
|
| | | flag = -1; |
|
|
|
|
| | | while (len) { |
|
|
|
|
| | | if (memory[dest] != memory[src]) flag = 0; |
|
|
|
|
| | | len -= 1; |
|
|
|
|
| | | src += 1; |
|
|
|
|
| | | dest += 1; |
|
|
|
|
| | | }; |
|
|
|
|
| | | sp -= 2; |
|
|
|
|
| | | data[sp] = flag; |
|
|
|
|
+--------+------+----------------------------------------------+
|
|
|
|
| 28 | cy | copy memory |
|
|
|
|
| | | |
|
|
|
|
| | | len = data[sp]; |
|
|
|
|
| | | dest = data[sp - 1]; |
|
|
|
|
| | | src = data[sp - 2]; |
|
|
|
|
| | | while (len) { |
|
|
|
|
| | | memory[dest] = memory[src]; |
|
|
|
|
| | | len -= 1; |
|
|
|
|
| | | src += 1; |
|
|
|
|
| | | dest += 1; |
|
|
|
|
| | | }; |
|
|
|
|
| | | sp -= 3; |
|
|
|
|
+--------+------+----------------------------------------------+
|
|
|
|
| 29 | io | perform i/o operation |
|
|
|
|
| | | |
|
|
|
|
| | | An ilo system must support a few specific |
|
|
|
|
| | | i/o devices. These are discussed in the i/o |
|
|
|
|
| | | section of this specification. |
|
|
|
|
+--------+------+----------------------------------------------+
|
|
|
|
|
|
|
|
# instruction bundling
|
|
|
|
|
|
|
|
ilo allows up to four instructions to be packed into a single
|
|
|
|
memory location. These are processed in order. Instructions that
|
|
|
|
modify the instruction pointer (other than `li`) can not be
|
|
|
|
followed by anything other than a non-op to avoid unpredictable
|
|
|
|
behavior.
|
|
|
|
|
|
|
|
Instructions are unpacked from right to left. E.g., in C, the
|
|
|
|
instruction processor can be written like:
|
|
|
|
|
|
|
|
void process_opcode_bundle(int opcode) {
|
|
|
|
process(opcode & 0xFF);
|
|
|
|
process((opcode >> 8) & 0xFF);
|
|
|
|
process((opcode >> 16) & 0xFF);
|
|
|
|
process((opcode >> 24) & 0xFF);
|
|
|
|
}
|
|
|
|
|
|
|
|
Where `process()` takes and executes a single instruction.
|
|
|
|
|
|
|
|
# instruction processing
|
|
|
|
|
|
|
|
IP is set to zero, and execution begins. For each cycle, the
|
|
|
|
opcode bundle at IP in memory is executed. At the end of each
|
|
|
|
cycle, IP is incremented. Execution ends if IP exceeds the
|
|
|
|
length of memory. In C:
|
|
|
|
|
|
|
|
while(ip < 65536) { process_opcode_bundle(memory[ip++]); }
|
|
|
|
|
|
|
|
# I/O
|
|
|
|
|
|
|
|
A standard ilo has a limited set of I/O devices. These are:
|
|
|
|
|
|
|
|
+------------+--------------------------------------+----------+
|
|
|
|
| I/O Device | Action | Required |
|
|
|
|
+============+======================================+==========+
|
|
|
|
| 0 | Display a character. Consumes a | Yes |
|
|
|
|
| | character from the stack. | |
|
|
|
|
| 1 | Read a character from the input | Yes |
|
|
|
|
| | device. The character is pushed to | |
|
|
|
|
| | the stack. | |
|
|
|
|
| 2 | Read a block into memory. | Yes |
|
|
|
|
| 3 | Write memory to a block. | Yes |
|
|
|
|
| 4 | Write all memory to an image/rom. | No |
|
|
|
|
| 5 | Reload the image/rom, and jump to | Yes |
|
|
|
|
| | address 0. Also empty all stacks. | |
|
|
|
|
| 6 | End execution. On a hosted system, | No |
|
|
|
|
| | exit ilo. If native, suspend | |
|
|
|
|
| | execution. | |
|
|
|
|
| 7 | Obtain stack depths. Pushes the data | Yes |
|
|
|
|
| | depth then the address depth. | |
|
|
|
|
+------------+-------------------------------------------------+
|
|
|
|
|
|
|
|
I/O numbers below 12 are reserved. For custom I/O extensions,
|
|
|
|
use a number 12 or above.
|
|
|
|
|
|
|
|
# Block Storage
|
|
|
|
|
|
|
|
A generic block storage device is provided.
|
|
|
|
|
|
|
|
Each block is 1,024 memory cells (4,096 bytes) in length.
|
|
|
|
|
|
|
|
Reading a block:
|
|
|
|
|
|
|
|
- push the block number
|
|
|
|
- push an address of the buffer in memory that will hold the
|
|
|
|
block contents after the read completes
|
|
|
|
- push the value 2 (read block contents) to the stack
|
|
|
|
- call I/O operation via the `io` instruction
|
|
|
|
|
|
|
|
Writing a block:
|
|
|
|
|
|
|
|
- push the block number
|
|
|
|
- push an address of the buffer in memory that holds the
|
|
|
|
data to write to the block
|
|
|
|
- push the value 3 (write block contents) to the stack
|
|
|
|
- call I/O operation via the `io` instruction
|
|
|
|
|
|
|
|
# block file format
|
|
|
|
|
2024-07-14 17:30:45 +02:00
|
|
|
An implementation is free to choose the best approach to
|
2024-05-23 19:45:26 +02:00
|
|
|
implementing the actual block storage. For the reference model,
|
|
|
|
a flat file is used, with each block being stored sequentially.
|
|
|
|
|
|
|
|
To locate a block, multiply the block number by 4096 and seek
|
|
|
|
that offset into the file. Then read or write 4096 bytes,
|
|
|
|
packing or unpacking from memory as needed.
|
|
|
|
|
|
|
|
As with the image, the block file in the reference model is
|
|
|
|
stored in little endian format.
|
2024-07-14 17:30:45 +02:00
|
|
|
|
|
|
|
This is not required. An implementation could store blocks as
|
|
|
|
individual files, using a database, or in whatever form the
|
|
|
|
implementor desires.
|