
vic-sm8
The VIC-SM8 is the next CPU that is going to replace the current vic-8p. Not much in the design has been currently planned, but know that this CPU is going to be stack-based and will be relatively easy to write a Forth using it.
Unlike the VIC-8P that had a really messy and unoptimized instruction set, this one will aim to be easier to use and easier to embed in other applications than the VICERA itself.
The architecture of this CPU consists of two stacks, a return stack and a data stack, 64 KiB of memory, an interrupt table and 256 "I/O" ports which the user can communicate with through 2 instructions.
The instruction set is heavily inspired by uxn and I highly recommend to check out this project because it seems really interesting and promising.
This CPU focuses on extensibility and ease to implement applications with it because this same CPU will be used by me on other applications than the VICERA. It will be sort of a toy VM to play around with and make other projects.
Current memory bindings
Address | Size | Description |
---|---|---|
0000 | 32KB | ROM allocation |
F300 | 512B | Interrupt Table |
Currently planned features...
- stack-based CPU instruction set
- virtual I/O architecture
- 32 KiB general purpose memory
- Eventually a Forth compiler or a similar lang
- Interrupts for error handling and... simply interrupts like syscalls.
Even though the VIC-SM8 is a stack machine, it still needs a few registers to store some pointers and the flags.
-
DS
is the data stack. -
RS
is the return stack. -
PC
is the program counter. Points at instructions to execute. -
IP
is the interrupt pointer. Points to the interrupt table. -
AR
is the address register. Stores an address.
The VM has an interrupt table allowing the programmer to perform error handling or to define their own interrupt routines such as syscalls or stuff like that. Interrupts are either called by the CPU if an error occurs or explicitly called by the user. If the said interrupt points to 0x0000 in the table, the HALT flag will be set and the machine will stop working.
The I/O architecture allows up to 256 ports that are bindable using C structs. The io struct has 4 function pointers to start, stop and handle in/out opcodes. This has been made to ease the extensibility of the machine. With that, you can write any "device" in C like TTY, SDL graphics, networking, or bind it to actual I/O.
The I/O has a similar convention to the x86: It uses IN
and OUT
commands to
interact the I/O. Once the instruction executed, it will pops a byte from the
stack to know which port to go. The I/O have full access to the return stack,
the data stack, the memory and the registers.
The instruction set is quite simple. It uses reverse polish notation to operate. Just like Forth.
HF
is the HALT flag
Instruction | Opcode | Input | Output | Description |
---|---|---|---|---|
HALT | 00 | HF | Sets the halt flag | |
--- | 01 | Unused. | ||
INT | 02 | a | Performs an interrupt call | |
IN | 03 | a | Sends IN command to an IO port | |
OUT | 04 | a | Sends OUT command to an IO port | |
ROT | 05 | a b c | b c a | Moves a to the top |
SWP | 06 | a b | b a | Swaps a and b |
DUP | 07 | a | a a | Dupliates a |
OVER | 08 | a b | a b a | Duplicates a to the top |
DROP | 09 | a | Pops a | |
ADD | 0a | a b | c | a + b = c |
SUB | 0b | a b | c | a - b = c |
MUL | 0c | a b | c | a * b = c |
DIV | 0d | a b | c | a / b = c |
MOD | 0e | a b | c | a % b = c |
AND | 0f | a b | c | a AND b = c |
OR | 10 | a b | c | a OR b = c |
NOT | 11 | a | b | NOT a = b |
XOR | 12 | a b | c | a XOR b = c |
SR | 13 | a | b | a >> 1 = b |
SL | 14 | a | b | a << 1 = b |
INV | 15 | a | b | b = !a |
LD | 16 | (a b) | c | Loads from memory at (a b) |
ST | 17 | a (b c) | Stores from memory at (b c) | |
ADL | 18 | c |
Loads from AR
|
|
ADS | 19 | a |
Stores a from AR
|
|
EQU | 1a | a b | c | a == b = c |
NEQ | 1b | a b | c | a != b = c |
GRT | 1c | a b | c | a > b == c |
LRT | 1d | a b | c | a < b == c |
GEQ | 1e | a b | c | a >= b == c |
LEQ | 1f | a b | c | a <= b == c |
JMP | 20 | (a b) | Jumps to (a b) | |
JMZ | 21 | a (b c) | Jumps to (b c) if a > 0 | |
CAL | 22 | (a b) | Calls subroutine at (a b) | |
CAZ | 23 | a (b c) | Calls subroutine at (a b) if a > 0 | |
RET | 24 | Pops from return stack and jumps | ||
REZ | 25 | a | Same as RET if a > 0 | |
ADR | 26 | c |
Reads the content of AR
|
|
ADW | 27 | (a b) |
Stores (a b) into AR
|
|
ADI | 28 |
Increments AR
|
||
SADD | 29 |
signed ADD
|
||
SSUB | 2a |
signed SUB
|
||
SMUL | 2b |
signed MUL
|
||
SDIV | 2c |
signed DIV
|
||
SGRT | 2d |
signed GRT
|
||
SLRT | 2e |
signed LRT
|
||
SGEQ | 2f |
signed GEQ
|
||
SLEQ | 30 |
signed LEQ
|
Every byte are formatted this way:
7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
---|---|---|---|---|---|---|---|
+ | L | B | B | B | B | B | B |
+
is the 16-bit mode. Meaning that if this bit is set, the opcodes will be
run in 16-bit mode (basically manipulating the stack with 16-bit numbers instead
of 8-bit numbers). Please note that this is not compatible with all bytecodes,
mostly just the ones that manipulates numbers.
L
is the literal mode, allowing the programmer to push numbers in the stack.
The bytecode (B
) will then be used as the number of bytes to push.
Example: 42 ac ab fe
pushes 2 bytes into the stack, fe
will not be pushed.
B
is the space for instructions. Also used to define the number of bytes to
push in literal mode.
There is an assembler in the same repository as the VIC-SM8. It is really simple and I am not sure if I will make the final assembler in C. I hate parsing in C.
All NUM
s must be written in hexadecimal. They can be written in pairs of 2
digits like this: 0f
cafe
deadbeef
.
Command | Description |
---|---|
$varname
|
Defines a pointer to the memory, meant to be used as a variable. |
#const NUM
|
Defines a constant. NUM must be a 16-bit integer. |
:label
|
Sets a label. |
.val
|
Appends val to the bytecode with the NUM opcode. |
,NUM
|
Appends raw hexadecimals in the bytecode. |
{ comment }
|
A comment. |
"string
|
Appends a raw string in the bytecode. |
NUM
|
Appends a num in the bytecode with the NUM opcode. |
OPCODE
|
Appends an opcode. |
+OPCODE
|
Appends an opcode in 16-bit mode. |
There is also |NUM
which moves the location pointer but I couldn't fit it in
the table because vimwiki doesn't seem to do escape sequences : pain:
Here is an hello world written in sm8 assembly. It uses an I/O port that interacts with the TTY.
.main JMP :hello "Hello, ,20 "World! ,0a00 :main .hello ADW :l ADL DUP INV .end JMZ 01 OUT ADI .l JMP :end HALT
The source code is available and licensed under the MIT License. There is an example machine which is also used for testing.