An open API service indexing awesome lists of open source software.

https://github.com/AfaanBilal/NanoCore

NanoCore 8-bit CPU
https://github.com/AfaanBilal/NanoCore

8-bit cpu emulator rust

Last synced: 6 months ago
JSON representation

NanoCore 8-bit CPU

Awesome Lists containing this project

README

          

# NanoCore: An 8-bit CPU Emulator

## ๐ŸŒŸ Introduction

`NanoCore` is a meticulously crafted emulator for a custom 8-bit CPU.

DeepWiki: [AfaanBilal/NanoCore](https://deepwiki.com/AfaanBilal/NanoCore)

Designed with extreme minimalism in mind, this CPU operates within a strict 256-byte memory space, with all registers, the Program Counter (PC), and the Stack Pointer (SP) being 8-bit.

This project serves as an educational exercise in understanding the fundamental principles of computer architecture, low-level instruction set design, memory management under severe constraints, and assembly language programming.

NanoCore TUI

## โœจ Key Features

* **True 8-bit Architecture:** All general-purpose registers (R0-R15), Program Counter (PC), and Stack Pointer (SP) are 8-bit.
* **256-byte Memory:** The entire addressable memory space is limited to 256 bytes (`0x00` to `0xFF`), making it a challenge to write highly optimized and compact code.
* **Variable-Length Instruction Set:** Supports both 1-byte, 2-byte and 3-byte instructions to maximize opcode efficiency and flexibility within the limited address space.
* **Modular Design:** CPU cycle is broken down into distinct Fetch, Decode, and Execute phases for clarity.
* **Inbuilt Two-Pass Assembler:** The NanoCore Assembler makes it easier to program it by writing NanoCore Assembly instead of direct machine code.
* **Terminal User Interface:** Fully function TUI as seen above with breakpoints for easy debugging.

## ๐Ÿงฎ Instruction Set Architecture (ISA)

> See `programs/` for example programs and compiled binaries.

NanoCore features a small but functional instruction set designed for its 8-bit constraints.

### Instruction Format

* **1-byte instructions:** One 8-bit opcode.
* **2-byte instructions:** An 8-bit opcode followed by an 8-bit operand (e.g., an immediate value or an address).
* **3-byte instructions:** An 8-bit opcode followed by two 8-bit operands (e.g., an immediate value or an address).

### Implemented Instructions

| Opcode | Bytes | Mnemonic | Description | Encoding Example |
| :----- | ----: | :--------------- | :-------------------------------------- | :--------------- |
| `0x00` | 1 | `HLT` | Halts CPU execution | `0x00` |
| `0x01` | 1 | `NOP` | No operation | `0x01` |
| `0x02` | 3 | `LDI Reg val` | Load immediate `val` into `Reg` | `0x02 0x00 0xAB` |
| `0x03` | 3 | `LDA Reg addr` | Load from mem address `addr` into `Reg` | `0x03 0x00 0xCC` |
| `0x04` | 2 | `LDR Rd Rs` | Load from mem address in `Rs` into `Rd` | `0x04 0x00 0x01` |
| `0x05` | 2 | `MOV Rd Rs` | Copy value from `Rs` into `Rd` | `0x05 0x01 0x02` |
| `0x06` | 3 | `STORE Reg addr` | Store `Reg` into mem address `addr` | `0x05 0x01 0xAA` |
| `0x07` | 2 | `PUSH Reg` | Push `Reg` value into stack. | `0x05 0x01` |
| `0x08` | 2 | `POP Reg` | Pop from stack into `Reg` | `0x05 0x01` |
| `0x09` | 2 | `ADD Rd Rs` | Add value of `Rs` to `Rd` | `0x05 0x01 0x02` |
| `0x0A` | 3 | `ADDI Reg val` | Add immediate `val` to `Reg` | `0x05 0x01 0xAB` |
| `0x0B` | 2 | `SUB Rd Rs` | Subtract value of `Rd` from `Rs` | `0x05 0x01 0x02` |
| `0x0C` | 3 | `SUBI Reg val` | Subtract immediate `val` from `Reg` | `0x05 0x01 0xAB` |
| `0x0D` | 2 | `INC Reg` | Increment `Reg` | `0x05 0x01` |
| `0x0E` | 2 | `DEC Reg` | Decrement `Reg` | `0x05 0x01` |
| `0x0F` | 2 | `AND Rd Rs` | Set `Rd` to `Rd & Rs` | `0x05 0x01 0x02` |
| `0x10` | 2 | `OR Rd Rs` | Set `Rd` to `Rd \| Rs` | `0x05 0x01 0x02` |
| `0x11` | 2 | `XOR Rd Rs` | Set `Rd` to `Rd ^ Rs` | `0x05 0x01 0x02` |
| `0x12` | 2 | `NOT Reg` | Set `Reg` to `!Reg` | `0x05 0x01` |
| `0x13` | 2 | `CMP Rd Rs` | Set `Z` flag if `Rd == Rs` | `0x05 0x01 0x02` |
| `0x14` | 2 | `SHL Reg` | Shift left in `Reg` by 1 (`<< 1`) | `0x05 0x01` |
| `0x15` | 2 | `SHR Reg` | Shift right in `Reg` by 1 (`>> 1`) | `0x05 0x01` |
| `0x22` | 2 | `ROL Reg` | Rotate left in `Reg` by 1 | `0x22 0x01` |
| `0x23` | 2 | `ROR Reg` | Rotate right in `Reg` by 1 | `0x23 0x01` |
| `0x16` | 2 | `JMP addr` | Unconditional jump to `addr` | `0x05 0xAA` |
| `0x17` | 2 | `JZ addr` | Jump to `addr` if `Z` flag is set | `0x05 0xAA` |
| `0x18` | 2 | `JNZ addr` | Jump to `addr` if `Z` flag is not set | `0x05 0xAA` |
| `0x24` | 2 | `IN Reg` | Read char from stdin into `Reg` | `0x24 0x01` |
| `0x19` | 2 | `PRINT Reg` | Print `Reg` as an ASCII character | `0x19 0x01` |
| `0x1A` | 2 | `MUL Rd Rs` | Multiply value of `Rs` to `Rd` | `0x1A 0x01 0x02` |
| `0x1B` | 3 | `MULI Reg val` | Multiply immediate `val` to `Reg` | `0x1B 0x01 0xAB` |
| `0x1C` | 2 | `DIV Rd Rs` | Divide value of `Rs` by `Rd` | `0x1C 0x01 0x02` |
| `0x1D` | 3 | `DIVI Reg val` | Divide `Reg` by immediate `val` | `0x1D 0x01 0xAB` |
| `0x1E` | 2 | `MOD Rd Rs` | Modulus value of `Rs` by `Rd` | `0x1E 0x01 0x02` |
| `0x1F` | 3 | `MODI Reg val` | Modulus `Reg` by immediate `val` | `0x1F 0x01 0xAB` |
| `0x20` | 2 | `CALL addr` | CALL `addr` or function label. | `0x20 0xAA` |
| `0x21` | 1 | `RET` | Return from `CALL`ed function. | `0x21` |
| `0x25` | 2 | `JMPR Reg` | Unconditional jump to address in `Reg` | `0x25 0x01` |
| `0x26` | 2 | `CALLR Reg` | CALL address in `Reg` | `0x26 0x01` |
| `0x27` | 2 | `STR Rd Rs` | Store `Rd` to addr in `Rs` | `0x27 0x01 0x02` |

> - `val` = `Immediate value`
> - `addr` = `Memory address`
> - `Reg` = `Register`
> - `Rs` = `Source register`
> - `Rd` = `Destination register`
> - `Z` flag = Zero Flag
> - `R0` = `0x00`, `R1` = `0x01` ..., `R15` = `0x0F`
> - All addition, subtraction, increment and decrement is wrapping.

## ๐Ÿš€ Getting Started

To run the NanoCore emulator, you'll need to setup Rust locally.

1. **Clone the repository:**
```bash
git clone https://github.com/AfaanBilal/nanocore.git
cd nanocore
```
2. **Run the example program:**
The `programs/test.ncb` file contains a small, assembled program that demonstrates the CPU's basic functionality.
```bash
cargo run -- programs/test.ncb
```
You should see the emulator's debug output and the program's output to your console. The source assembly file is `programs/test.nca`.

## ๐Ÿ› ๏ธ Assembling

To assemble a program (say `example.nca`), run the NanoCore Assembler (`nca`):
```bash
cargo r --bin nca -- -i example.nca -o example.ncb
```
This should assemble the `example.nca` (NanoCore Assembly) to `example.ncb` (NanoCore Binary).

### Constants & Data

You can define constants using the `.CONST` directive, and embed data using `.DB` and `.STRING`:
```assembly
.CONST MAX_VAL 10
.CONST ADDR 0x10

.DB 0x01 0x02 10 ; Embed bytes 0x01, 0x02, 0x0A
.STRING "Hello" ; Embed ASCII bytes for "Hello"

LDI R0 MAX_VAL
JMP ADDR
```

## โš™๏ธ Running

To run this assembled binary, run:
```bash
cargo r -- example.ncb
```

## ๐Ÿช„ Assemble and Run

To assemble and run without saving a binary:
```bash
cargo r -- example.nca
```
> Note that the filename MUST end with the `.nca` extension to be considered a NanoCore Assembly file which will be automatically assembled before running.

## ๐Ÿ“Ÿ Terminal Interface

To run the TUI visualizer:
```bash
cargo r --bin tui -- programs/counter.nca
```

## ๐Ÿงช Testing

To run the test suite, including assembler verification:
```bash
cargo test
```

## ๐Ÿ“‚ Code Structure

* `CPU (cpu.rs)`: Defines the CPU's internal state, including registers, program counter, stack pointer, memory, and flag bits.
* `NanoCore (nanocore.rs)`: The main emulator struct, responsible for loading programs, running cycles, and managing the `CPU`.
* `NanoCore::new()`: Initializes a fresh computer state.
* `NanoCore::load_program()`: Places machine code into the simulated memory.
* `NanoCore::run()`: Executes the CPU cycle loop until halted.
* `NanoCore::cycle()`: Performs a single CPU cycle (Fetch, Decode, Execute).
* `NanoCore::fetch_decode()`: Reads the instruction byte(s) from memory and determines its type and operands.
* `NanoCore::execute_instruction()`: Performs the operation defined by the decoded instruction, updating the CPU state.
* `Assembler (assembler.rs)`: The NanoCore assembler core.
* `Assembler bin (bin/nca.rs)`: The NanoCore assembler binary.
* `TUI bin (bin/tui.rs)`: The NanoCore Terminal UI.

---

## Example Program `Fibonacci Sequence`

```assembly
; Print the fibonacci sequence (two-digit)
start:
LDI R0 0
LDI R1 1
LDI R2 12
LDI R12 32
loop:
JMP print_digits

post_print:
MOV R3 R1
ADD R1 R0
MOV R0 R3
DEC R2

JNZ loop
end:
HLT

print_digits:
PUSH R10
PUSH R11

MOV R10 R0
DIVI R10 10

JZ unit_digit

ADDI R10 48
PRINT R10

unit_digit:
MOV R11 R0
MODI R11 10
ADDI R11 48
PRINT R11

print_space:
PRINT R12

POP R11
POP R10
JMP post_print
```

## ๐Ÿค Contributing

All contributions are welcome. Please create an issue first for any feature request
or bug. Then fork the repository, create a branch and make any changes to fix the bug
or add the feature and create a pull request. That's it!
Thanks!

---

## ๐Ÿ“„ License

**NanoCore** is released under the MIT License.
Check out the full license [here](LICENSE).