# How the NanoCore CPU Implements the Fetch-Decode-Execute Cycle

> Explore the fetch-decode-execute cycle in the NanoCore CPU. Learn how this 8-bit processor reads, decodes, and executes instructions efficiently using pattern matching and updates the program counter.

- Repository: [Afaan Bilal/nanocore](https://github.com/afaanbilal/nanocore)
- Tags: internals
- Published: 2026-02-23

---

**The NanoCore CPU implements a classic 8-bit fetch-decode-execute pipeline where each clock tick reads three bytes from memory, decodes the opcode into an `Op` enum with `Operands`, executes the instruction via pattern matching, and updates the program counter unless overridden by control flow.**

The afaanbilal/nanocore repository provides a Rust-based emulator that models a complete 8-bit CPU architecture. Understanding its fetch-decode-execute cycle requires examining how the `NanoCore` struct orchestrates instruction processing through memory access, opcode translation, and state mutation.

## The Cycle Orchestration in [`src/nanocore.rs`](https://github.com/afaanbilal/nanocore/blob/main/src/nanocore.rs)

The entry point for every clock tick is the `cycle()` method, which coordinates the three pipeline stages and manages program counter advancement.

```rust
pub fn cycle(&mut self) -> crate::EmulatorResult<()> {
    let (op, operands) = self.fetch_decode();          // ← FETCH & DECODE
    let pc_override = self.execute(op, operands)?;     // ← EXECUTE
    // ...
    if !pc_override {
        self.cpu.pc = self.cpu.pc.wrapping_add(op.instruction_len());
    }
    self.cycle += 1;
    Ok(())
}

```

This implementation in [`src/nanocore.rs`](https://github.com/afaanbilal/nanocore/blob/main/src/nanocore.rs) (lines 71-104) demonstrates the classic pipeline: **fetch and decode** happen atomically in `fetch_decode()`, while **execute** processes the instruction and returns a boolean indicating whether the instruction manually modified the program counter.

## Stage 1: Fetch and Decode

The `fetch_decode()` method handles both memory retrieval and opcode interpretation in a single operation.

### Reading Bytes from Memory

The fetch stage reads three consecutive bytes starting at the current program counter (`PC`):

```rust
let opcode = self.cpu.memory[self.cpu.pc as usize];
let byte_2 = self.cpu.memory[self.cpu.pc.wrapping_add(1) as usize];
let byte_3 = self.cpu.memory[self.cpu.pc.wrapping_add(2) as usize];

```

### Decoding to Operation Enums

The decode stage translates the raw `u8` opcode into type-safe enums. The `Op` enum represents the operation type, while `Operands` captures the instruction format:

```rust
let op: Op = opcode.into();
let operands = match op {
    Op::HLT | Op::NOP | Op::RET => Operands::None,
    Op::LDI | Op::ADDI | ... => Operands::RegImm(byte_2, byte_3),
    Op::LDA | Op::STORE => Operands::RegAddr(byte_2, byte_3),
    Op::PUSH | Op::POP | ... => Operands::Reg(byte_2),
    Op::LDR | Op::MOV | ... => Operands::RegReg((byte_2 >> 4) & 0x0F, byte_2 & 0x0F),
    Op::JMP | Op::JZ | Op::JNZ | Op::CALL => Operands::Addr(byte_2),
};

```

This logic resides at `src/nanocore.rs#L40-L53` and supports multiple addressing modes including register-immediate, register-address, and single-register formats.

## Stage 2: Execute

The `execute()` method contains a large pattern match on the `Op` enum, with each arm implementing specific semantics for arithmetic, memory access, or control flow.

### Arithmetic Operations

Arithmetic instructions read register values, compute results, and update status flags:

```rust
Op::ADD => {
    let Operands::RegReg(rd, rs) = operands else { ... };
    let v1 = self.cpu.registers[rd as usize];
    let v2 = self.cpu.registers[rs as usize];
    let (result, carry) = self.execute_arithmetic(op, v1, v2)?;
    self.cpu.registers[rd as usize] = result;
    self.cpu.update_zn_flags(result);
    if carry { self.cpu.set_flag(CPU::FLAG_C); } else { self.cpu.clear_flag(CPU::FLAG_C); }
}

```

### Control Flow Handling

Branching instructions override the default PC advancement by returning `true`:

```rust
Op::JMP => {
    let Operands::Addr(a) = operands else { ... };
    self.cpu.pc = a;                 // PC set directly
    pc_override = true;              // Prevent auto-increment
}

```

The full execution logic spans `src/nanocore.rs#L84-L188` and handles all 8-bit operations including jumps, calls, returns, and halts.

## CPU State Architecture

The underlying hardware state resides in [`src/cpu.rs`](https://github.com/afaanbilal/nanocore/blob/main/src/cpu.rs), defining the 8-bit architecture's registers and memory:

```rust
pub struct CPU {
    pub registers: [u8; 16],
    pub pc: u8,
    pub sp: u8,
    pub memory: [u8; 256],
    pub flags: u8,
    pub is_halted: bool,
}

```

This struct maintains sixteen general-purpose registers, an 8-bit program counter addressing 256 bytes of memory, a stack pointer, and flag bits. Helper methods for flag manipulation appear in `src/cpu.rs#L21-L49`.

## Practical Example: Running a Program

The following example demonstrates three complete fetch-decode-execute cycles:

```rust
use nanocore::nanocore::NanoCore;

let program = vec![
    0x02, 0x00, 0x05,  // LDI R0, 5
    0x02, 0x01, 0x03,  // LDI R1, 3
    0x08, 0x00, 0x01,  // ADD R0, R1
    0x00,              // HLT
];

let mut emu = NanoCore::new();
emu.load_program(&program, 0x00).unwrap();
emu.run().unwrap();

assert_eq!(emu.cpu.registers[0], 8);   // 5 + 3 = 8

```

For debugging, you can step through individual cycles:

```rust
let mut emu = NanoCore::new();
emu.load_program(&[0x02, 0x00, 0x0A, 0x00], 0x00).unwrap();

emu.cycle().unwrap();  // Fetch, decode, execute LDI
assert_eq!(emu.cpu.registers[0], 10);

emu.cycle().unwrap();  // Execute HLT
assert!(emu.cpu.is_halted);

```

## Summary

- **Fetch**: The `fetch_decode()` method reads three bytes from `memory[PC]` in [`src/nanocore.rs`](https://github.com/afaanbilal/nanocore/blob/main/src/nanocore.rs).
- **Decode**: Raw opcodes convert to the `Op` enum and `Operands` variants based on instruction format.
- **Execute**: A comprehensive `match` statement in `execute()` performs operations, updates registers, and sets flags.
- **PC Management**: Most instructions advance the program counter by their length, while control-flow instructions set `pc_override` to handle jumps and calls manually.
- **State**: The `CPU` struct in [`src/cpu.rs`](https://github.com/afaanbilal/nanocore/blob/main/src/cpu.rs) holds all registers, memory, and status flags for the 8-bit architecture.

## Frequently Asked Questions

### How does NanoCore handle instruction operands of different sizes?

NanoCore reads three bytes for every instruction regardless of actual length. The `Operands` enum variant selected during decode determines which bytes are significant—`Operands::None` ignores both extra bytes, while `Operands::RegImm` uses both as register index and immediate value.

### What happens when the program counter reaches the end of memory?

The `PC` is an 8-bit value (`u8`) that wraps around using `wrapping_add()` for auto-increment operations. Since memory is exactly 256 bytes, the PC naturally wraps from 0xFF to 0x00, creating a circular address space unless control-flow instructions redirect execution.

### How does the emulator distinguish between arithmetic and control-flow instructions?

Both types execute within the same `match` arms in [`src/nanocore.rs`](https://github.com/afaanbilal/nanocore/blob/main/src/nanocore.rs), but control-flow instructions (like `JMP`, `CALL`, `RET`) return `true` to set `pc_override`, preventing the automatic PC increment in the `cycle()` method. Arithmetic instructions return `false`, allowing the standard PC advancement by `op.instruction_len()`.

### Where is the stack implemented in the NanoCore CPU?

The stack uses the `sp` field in the `CPU` struct defined in [`src/cpu.rs`](https://github.com/afaanbilal/nanocore/blob/main/src/cpu.rs). Stack operations like `PUSH` and `POP` manipulate this 8-bit pointer and interact with the same 256-byte memory array used for program storage, creating a unified memory architecture typical of 8-bit microprocessors.