This project is a simple 8-bit virtual machine (VM) emulator written in Rust. It simulates a basic CPU with registers, memory, and a set of instructions for performing arithmetic, memory operations, and control flow.
- Registers: Five general-purpose 8-bit registers (A, B, C, D, E).
- Memory: 256 bytes of memory.
- Instruction Set:
- Arithmetic:
ADD
,SUB
,MUL
,DIV
,MOD
,MULH
- Data Movement:
MOV
,STORE
- Memory Access: Supports
[0]
,[A]
,[B]
, etc. - Control Flow:
JMP
,JZ
,JNZ
,LOOP
- Input/Output:
INPUT
,PRINT
,PRINTCH
- Program Termination:
HALT
- Screen Operations:
DRAW
,CLS
,RENDER
- Comparison:
CMP
- Random Number:
RAND
- Arithmetic:
- Zero Flag: Indicates whether the result of the last operation is zero, often used for conditional branching or logical evaluations. Comparisons evaluate to
false
(non-zero) ortrue
(zero), enabling conditional logic. - Custom Parsing: Accepts comments (
//
) and instruction separation via;
or by lines. - Character Literals: Supports character literals in instructions, e.g.,
MOV A 'p'
. Characters are internally treated as their ASCII numeric values and must fit within 8 bits (0–255), just like any other number. - Debug Mode: Optional debug mode for detailed output during execution.
- IDLE Mode: Allows direct input of instructions for testing and debugging.
- Screen Rendering: Supports drawing characters on an
80
by25
virtual screen and rendering it to the console. - Label Support: You can now use labels for control flow instructions (
JMP
,JZ
,JNZ
,LOOP
) instead of numeric instruction indices.
You can define a label by writing it at the start of a line followed by a colon, e.g. LOOP_START:
.
You can then use the label name as the target for JMP
, JZ
, JNZ
, or LOOP
instructions:
MOV A 0
MOV B 10
LOOP_START:
ADD A 1
PRINT A
CMP A B
JNZ LOOP_START
HALT
This is equivalent to using numeric indices, but is easier to read and maintain.
The files game.e8
, game2.e8
, example.e8
, example2.e8
, example3.e8
, example4.e8
, example5.e8
, example6.e8
, example7.e8
, and others contain example programs that demonstrate the use of registers, arithmetic operations, memory storage, loops, and conditional logic.
Even or Odd Example:
// EVEN OR ODD
INPUT A // get number from user input
MOD A 2 // get remainder of A divided by 2
CMP A 0 // compare A with 0, if true, zero flag set to true
JZ EVEN // EVEN (jump to label EVEN if zero)
MOV C 'O' // ODD
PRINTCH C -N
MOV C 'D'
PRINTCH C -N
PRINTCH C -N
JMP END
EVEN:
MOV C 'E' // EVEN
PRINTCH C -N
MOV C 'V'
PRINTCH C -N
MOV C 'E'
PRINTCH C -N
MOV C 'N'
PRINTCH C -N
END:
HALT // end program
Factorial Example (example.e8
):
// FACTORIAL SCRIPT
// registers
MOV A 1; // A = 1 (result)
MOV B 5; // B = 5 (number to get factorial)
LOOP_START:
MUL A B; // A *= B
SUB B 1; // B -= 1
LOOP LOOP_START B; // if B != 0 go to LOOP_START
// END
PRINT A; // shows result
HALT;
-
Install Rust: Ensure you have Rust installed. You can download it from rust-lang.org.
-
Compile the Program:
cargo build --release
-
Run the Emulator:
cargo run example.e8 -d
Replace
example.e8
with the path to your program file.If no file is specified, it will run in IDLE mode, where you can enter instructions directly.
The
-d
flag is optional and enables debug mode, which provides additional output for debugging purposes.
Programs for the emulator are written in a custom assembly-like language. Each instruction is written on a new line or separated by a semicolon and can include comments starting with //
. Refer to the example programs above for syntax.
Instruction | Description |
---|---|
MOV A 10 |
Move value 10 into register A |
MOV A 'p' |
Move character literal 'p' into register A (ASCII value) |
MOV A [0] |
Move value from memory address 0 into A |
MOV A [B] |
Move value from memory at index stored in B |
ADD A B |
A = A + B |
SUB A 1 |
A = A - 1 |
MUL A 2 |
A = A * 2 |
DIV A 2 |
A = A / 2 |
MOD A 2 |
A = A % 2 (remainder after division) |
MULH A B C |
A = high byte of (B * C) |
STORE A [0] |
Store A into memory[0] |
STORE A [B] |
Store A into memory at index in B |
INPUT A |
Read input (u8 or char) into register A |
INKEY A |
Reads a single key press (non-blocking), stores ASCII code of the key in register A, or 0 if no key was pressed. Only character keys are returned. |
JMP 10 / JMP LABEL |
Jump to instruction index 10 or to label LABEL |
JZ 5 / JZ LABEL |
Jump to index 5 or label if last result was 0 (zero flag set) |
JNZ 8 / JNZ LABEL |
Jump if last result was not zero (zero flag not set) |
LOOP 3 C / LOOP LABEL C |
Jump to index 3 or label while C != 0 |
PRINT A |
Print value of A with newline |
PRINT A -N |
Print value of A without newline |
PRINTCH A |
Print character represented by value in A |
PRINTCH A -N |
Print character without newline |
DRAW X Y C |
Draw character C at screen position (X, Y) |
CLS |
Clear the screen |
CTS |
Clear the terminal screen |
RENDER |
Render the screen to the console (80x25) |
SLP 1000 |
Pause execution for 1 second (1000 ms) |
HALT |
Stops program execution |
CMP A 10 |
Compare register A with value 10. Sets the zero flag if equal. |
RAND A |
Set register A to a random value between 0–255 |
Type | Example | Description |
---|---|---|
Register | A , B , C , D , E |
One of the five registers |
Immediate Value | 42 , 'p' |
A literal number or character between 0–255 |
Memory Address | [0] |
Direct access to memory index 0 |
Memory via Reg | [A] |
Access memory using value in register A |
Note: Square brackets (
[]
) are used to specify memory addresses. For example:
MOV A [0]
loads the value from memory address 0 into register A.STORE A [0]
stores the value of register A into memory address 0.
Instruction | Arg 1 Type | Arg 2 Type | Arg 3 Type |
---|---|---|---|
MOV |
Register | Immediate Value, Register, or Memory Address ([0] , [A] , etc.) |
- |
ADD |
Register | Immediate Value, Register, or Memory Address | - |
SUB |
Register | Immediate Value, Register, or Memory Address | - |
MUL |
Register | Immediate Value, Register, or Memory Address | - |
DIV |
Register | Immediate Value, Register, or Memory Address | - |
MOD |
Register | Immediate Value, Register, or Memory Address | - |
MULH |
Register | Register | Register |
STORE |
Register | Memory Address ([0] , [B] , etc.) |
- |
JMP |
Immediate Value | - | - |
JZ |
Immediate Value | - | - |
JNZ |
Immediate Value | - | - |
LOOP |
Immediate Value (Instruction Index) | Register | - |
PRINT |
Register | Optional: -N to suppress newline |
- |
PRINTCH |
Register | Optional: -N to suppress newline |
- |
INPUT |
Register | - | - |
DRAW |
Immediate Value, Register, or Memory Address | Immediate Value, Register, or Memory Address | Immediate Value, Register, or Memory Address |
CLS |
- | - | - |
CTS |
- | - | - |
RENDER |
- | - | - |
SLP |
Milliseconds | - | - |
HALT |
- | - | - |
CMP |
Register | Immediate Value, Register, or Memory Address | - |
RAND |
Register | - | - |
- Use the
CTS
instruction to clear the terminal screen andCLS
to clear virtual screen. - Use the
MOD
instruction to easily check for even/odd numbers or perform modular arithmetic. - The
CMP
instruction is useful for conditional branching withJZ
andJNZ
. - Use
PRINTCH
for ASCII output andPRINT
for numeric output. - The virtual screen is 80 columns by 25 rows; use
DRAW
,CLS
, andRENDER
for simple graphics.
- Add support for more instructions.
- Implement debugging tools.
- Enhance error handling for invalid programs.
This project is open-source and available under the MIT License.
INKEY A
reads a single key press (non-blocking) and stores the ASCII code of the pressed key in registerA
.- If no key was pressed,
A
is set to0
. - Only character keys are returned (e.g., letters, numbers, symbols). Special keys like arrows, F1, etc., are ignored or return 0.
- The zero flag is set if no key was pressed (
A == 0
). - Useful for real-time input in games or interactive programs.