This page details the instruction set of the Cord virtual machine project.
Instruction formats
All instructions are 16-bit words, little-endian and naturally aligned. Addresses within instructions are effectively 15 bits: they are encoded with an implicit 0 in the low bit.
p is the program counter.
| Name | Binary encoding | Notes |
|---|---|---|
imm8, opcode |
000yyyyy xxxxxxxx |
Push 8-bit signed immediate x to data stack, then execute opcode y. |
opcode, opcode |
001yyyyy xxxxxxxx |
Execute opcode x and then opcode y. |
call |
01xxxxxx xxxxxxxx |
Push p to return stack and set p to x. |
jmp0 |
10xxxxxx xxxxxxxx |
Pop data stack, then set p to x if the value was 0. |
jmp |
11xxxxxx xxxxxxxx |
Set p to x. |
Note that in the opcode, opcode format x can range from [0,
255]. As we only specify 32 opcodes, this leaves 224 for an optional
extensions system (to be specified).
Opcode table
io |
. |
dup |
drop |
swap |
over |
push |
pop |
+ |
- |
* |
/mod |
u/mod |
<< |
>> |
u>> |
and |
or |
xor |
not |
< |
u< |
ex |
; |
w@ |
s@ |
c@ |
w! |
s! |
c! |
w@p |
s@p |
Opcode details
p is the program counter. x and y represent arbitrary 32-bit
integers on the data stack.
By design there is no undefined behavior in the instruction set.
| Name | Inputs | Outputs | Notes |
|---|---|---|---|
io |
varies | varies | Initiate I/O. 0 io (encoded $0000) is a halt instruction: thus, executing uninitialized memory will halt. |
. |
No-op. | ||
dup |
x |
x x |
|
drop |
x |
Discard value. | |
swap |
x y |
y x |
|
over |
x y |
x y x |
|
push |
x |
Push value to return stack. | |
pop |
x |
Pop value from return stack. | |
+ |
x y |
x+y |
|
- |
x y |
x-y |
|
* |
x y |
x*y |
Non-widening multiply: works for either unsigned or signed inputs. |
/mod |
x y |
x%y x/y |
Signed, truncated modulo and division. Division by 0 results in x -1. Dividing the most negative integer $80000000 by -1 results in 0 $80000000. |
u/mod |
x y |
x%y x/y |
Unsigned, truncated modulo and division. Division by 0 results in x -1. Unsigned division cannot overflow. |
<< |
x y |
x<<y |
Left shift. If y >= 32, results in 0. (y is treated as an unsigned value for all shift opcodes.) |
>> |
x y |
x>>y |
Signed right shift. If y >= 32, results in -1 for negative x and 0 otherwise. |
u>> |
x y |
x>>y |
Unsigned right shift. If y >= 32, results in 0. |
and |
x y |
x&y |
Bitwise and. |
or |
x y |
x∣y |
Bitwise or. |
xor |
x y |
x^y |
Bitwise xor. |
not |
x |
~x |
Bitwise not. |
< |
x y |
x<y |
If x < y, push -1 to data stack. Otherwise, push 0. x and y are signed. |
u< |
x y |
x<y |
If x < y, push -1 to data stack. Otherwise, push 0. x and y are unsigned. |
ex |
The value at the top of the return stack is swapped with p. A function whose address is on the data stack can be called via push ex. |
||
; |
Return from function. Pops return stack into p. |
||
w@ |
x |
[x] |
32-bit (“word”) load from address x. (Note: Cord is byte-addressed, and addresses that would be out of bounds wrap around.) |
s@ |
x |
[x] |
16-bit (“short”) sign-extending load. |
c@ |
x |
[x] |
8-bit (“char”) sign-extending load. |
w! |
x y |
32-bit store (value x into memory address y). |
|
s! |
x y |
16-bit store. | |
c! |
x y |
8-bit store. | |
w@p |
x |
32-bit signed immediate. | |
s@p |
x |
16-bit signed immediate. |
Prior art
A short and incomplete list of hardware that inspired this instruction set:
- The idea of encoding calls and jumps as tag bits on an address was inspired by J1 and various older Forth chips.
- The
exopcode and the naming scheme for various opcodes came from the GreenArrays F18A architecture. - Division behavior was taken from RISC-V. This architecture also
inspired the inclusion of the
<andu<opcodes.
Special thanks
Thanks to Ethan Black for his useful comments and suggestions. Check out his SubSky project if you’re interested in a minimalist RISC VM.
Incoming
✏ Edited