The Time Machine Project- Emulating Historical Computers with Rust

A beginner's guide to building a simple 8-bit computer emulator, exploring the basics of system programming and computer architecture with a touch of nostalgia.


Introduction

Welcome aboard, time travelers! Today, we're not just learning Rust; we're resurrecting the ghosts of computing past. We'll build an emulator for a simple, hypothetical 8-bit computer, which we'll call "Nostalgi-8". This journey will teach us about CPU design, memory handling, and how we can make Rust dance to the oldies.

Step 1: Setting Up Your Development Environment

Before we start, make sure you have Rust installed:

rustup update
cargo --version

Step 2: Designing Our Nostalgi-8

Nostalgi-8 Specs:

  • 8-bit CPU
  • 256 bytes of RAM
  • Simple instruction set (LOAD, STORE, ADD, JUMP, HALT)

Step 3: Structuring the Emulator

Let's define our emulator structure:

struct Nostalgi8 {
    memory: [u8; 256],
    registers: [u8; 2],  // A and B
    pc: u8,              // Program Counter
    running: bool,
}
 
impl Nostalgi8 {
    fn new() -> Self {
        Nostalgi8 {
            memory: [0; 256],
            registers: [0; 2],
            pc: 0,
            running: false,
        }
    }
 
    fn load_program(&mut self, program: &[u8]) {
        self.memory[..program.len()].copy_from_slice(program);
        self.pc = 0;
    }
 
    fn step(&mut self) {
        if !self.running { return; }
        // Fetch, decode, and execute instruction
        // Implementation will be in the next steps
    }
}

Step 4: Instruction Set Implementation

Now, let's implement some basic instructions:

impl Nostalgi8 {
    // ... previous methods ...
 
    fn execute(&mut self, opcode: u8) {
        match opcode {
            0x00 => self.running = false, // HALT
            0x01 => { // LOAD A, [addr]
                let addr = self.memory[self.pc as usize + 1];
                self.registers[0] = self.memory[addr as usize];
                self.pc += 2;
            },
            0x02 => { // STORE [addr], A
                let addr = self.memory[self.pc as usize + 1];
                self.memory[addr as usize] = self.registers[0];
                self.pc += 2;
            },
            0x03 => { // ADD A, B
                self.registers[0] = self.registers[0].wrapping_add(self.registers[1]);
                self.pc += 1;
            },
            0x04 => { // JUMP addr
                self.pc = self.memory[self.pc as usize + 1];
            },
            _ => println!("Unknown opcode: {:X}", opcode),
        }
    }
 
    fn step(&mut self) {
        if !self.running { return; }
        let opcode = self.memory[self.pc as usize];
        self.execute(opcode);
    }
}

Step 5: The Main Loop

Create a main function to run our emulator:

fn main() {
    let mut machine = Nostalgi8::new();
    let program = vec![0x01, 0x00, // LOAD A, [0]
                       0x04, 0x03, // JUMP 3
                       0x03,       // ADD A, B (this will be skipped initially)
                       0x00];      // HALT
    machine.load_program(&program);
    machine.running = true;
 
    while machine.running {
        machine.step();
        println!("A: {}, B: {}, PC: {}", machine.registers[0], machine.registers[1], machine.pc);
    }
}

Step 6: Debugging and Logging

Add some debugging features:

impl Nostalgi8 {
    // ... previous code ...
 
    fn debug(&self) {
        println!("PC: {}, Registers: A={} B={}, Mem[0]: {}", 
                 self.pc, 
                 self.registers[0], 
                 self.registers[1], 
                 self.memory[0]);
    }
}

Step 7: Extending and Experimenting

  • Add More Instructions: Expand the instruction set. Maybe add a SUB, MULT, or even a simple I/O instruction to simulate input/output.
  • Error Handling: Implement proper error handling for invalid memory access or unknown opcodes.
  • UI: Create a simple GUI or CLI to interact with your emulator.

Conclusion

You've now built a basic emulator in Rust! This project isn't just about nostalgia; it's a deep dive into how computers work at a fundamental level.

Further Reading:

  • Rust documentation for in-depth language features.
  • Books on computer architecture like "But How Do It Know?" by J. Clark Scott.

Remember, every emulator starts with a single step (or instruction). Happy coding, and may your journey through time be bug-free!


This guide provides a framework. You'd need to flesh out each part, add error checking, possibly expand the instruction set, and maybe even include how to handle input/output for a complete emulator tutorial. The code provided here is basic and serves as a starting point for beginners.