diff --git a/README.md b/README.md index 48f85e9..6e6835d 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,40 @@ # 8-Bit CPU Emulator +## CPU Architecture +- Word Size + - **Data Width:** 8 bits + - **Address width:** 16 bits + - **Address space:** 64 KB (0x0000- 0xFFFF) + ## Supported Instructions -1. MOV -2. ADD -3. SUB -4. JMP (Jump) -5. JZ (Jump if zero) -5. JZ (Jump if not zero) -6. HLT (Halt) + +| Instruction | Syntax | +| ----------- | ------------ | +| MOV | mov reg, imm | +| ADD | add r1, r2 | +| SUB | sub r1, r2 | +| JMP | jmp addr | +| JZ | jz addr | +| JNZ | jnz addr | +| HLT (Halt) | hlt | + + +## Registers +| Register | Size | Description | +| -------- | ------ | ------------------------------ | +| A | 8-bit | General | +| B | 8-bit | General | +| C | 8-bit | General | +| D | 8-bit | General | +| PC | 16-bit | Program Counter | +| SP | 16-bit | Stack pointer (unused for now) | + +## Flags +| Flag | Description | +| ----- | ------------ | +| Z | Zero Flag | +| C | Carry/Borrow | + # Usage ```bash diff --git a/examples/loops.asm b/examples/loops.asm new file mode 100644 index 0000000..5c49d2b --- /dev/null +++ b/examples/loops.asm @@ -0,0 +1,8 @@ +mov b, 3 +mov a, 1 + +loop: + sub b, a + jz loop + +hlt diff --git a/src/assembler.rs b/src/assembler.rs index 3cd2bf8..3eeec64 100644 --- a/src/assembler.rs +++ b/src/assembler.rs @@ -1,4 +1,5 @@ use crate::instructions::Instruction; +use std::collections::HashMap; fn tokenize(line: &str) -> Vec { line.split(|c| c == ' ' || c == ',' || c == '\t') @@ -17,14 +18,51 @@ fn parse_reg(s: &str) -> u8 { } } +fn instr_size(tokens: &[String]) -> u16 { + match tokens[0].as_str() { + "mov" | "add" | "sub" | "jmp" | "jz" | "jnz" => 3, + "hlt" => 1, + _ => panic!("Unknown instruction {}", tokens[0]) + + } +} + + +fn first_pass(source: &str) -> HashMap { + let mut symbols = HashMap::new(); + let mut addr: u16 = 0; + + for line in source.lines() { + let line = line.trim(); + + // Ignoring comments and empty lines + if line.is_empty() || line.starts_with(";") { + continue; + } + + // Labels (ends with ":") + if line.ends_with(":") { + let label = line.trim_end_matches(":").to_string(); + symbols.insert(label, addr); + continue; + } + + let tokens = tokenize(line); + addr += instr_size(&tokens); + } + + symbols +} + pub fn assembler(source: &str) -> Vec { + let symbols = first_pass(source); let mut bytes = Vec::new(); for (line_no, line) in source.lines().enumerate() { let line = line.trim(); // Comments in assembly start with ";" - if line.is_empty() || line.starts_with(';') { + if line.is_empty() || line.starts_with(';') || line.ends_with(":") { continue; } @@ -61,6 +99,22 @@ pub fn assembler(source: &str) -> Vec { bytes.push(r2); } + "jmp" | "jz" | "jnz" => { + let opcode = match tokens[0].as_str() { + "jmp" => Instruction::JMP, + "jz" => Instruction::JZ, + "jnz" => Instruction::JNZ, + _ => unreachable!() + }; + + let label = &tokens[1]; + let addr = *symbols.get(label).expect("Uknown label"); + + bytes.push(opcode as u8); + bytes.push((addr & 0xFF) as u8); // low + bytes.push((addr >> 8) as u8); // high + } + "hlt" => { bytes.push(Instruction::HLT as u8); } diff --git a/src/cpu.rs b/src/cpu.rs index 13d2fc8..df8b3a2 100644 --- a/src/cpu.rs +++ b/src/cpu.rs @@ -2,6 +2,7 @@ use crate::instructions::Instruction; use crate::memory::Memory; #[derive(Default, Debug)] +#[allow(dead_code)] pub struct CPU { pub a: u8, pub b: u8, @@ -18,6 +19,23 @@ pub struct CPU { } impl CPU { + + pub fn debug_instr(&self, mem: &Memory) { + let opcode = mem.read(self.pc); + + println!( + "PC={:04X} {:<3} | A={:02X} B={:02X} C={:02X} D={:02X} | Z={} C={}", + self.pc, + Instruction::opcode_name(opcode), + self.a, + self.b, + self.c, + self.d, + self.zero as u8, + self.carry as u8 + ); + } + pub fn step(&mut self, mem: &mut Memory) { let opcode = mem.read(self.pc); self.inc_pc(); diff --git a/src/instructions.rs b/src/instructions.rs index acfb8bd..ac88e94 100644 --- a/src/instructions.rs +++ b/src/instructions.rs @@ -8,3 +8,18 @@ pub enum Instruction { JNZ = 0x06, HLT = 0xFF, } + +impl Instruction { + pub fn opcode_name(op: u8) -> &'static str{ + match op { + 0x01 => "MOV", + 0x02 => "ADD", + 0x03 => "SUB", + 0x04 => "JMP", + 0x05 => "JZ", + 0x06 => "JNZ", + 0xFF => "HLT", + _ => "???", + } + } +} diff --git a/src/main.rs b/src/main.rs index 607fb34..3dc0121 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,6 +3,8 @@ mod cpu; mod instructions; mod memory; +use std::io; + use cpu::CPU; use memory::Memory; use clap::Parser; @@ -30,7 +32,10 @@ fn main() { } while !cpu.halted { + cpu.debug_instr(&mem); cpu.step(&mut mem); - println!("{:?}", cpu); + + let mut buf = String::new(); + io::stdin().read_line(&mut buf).unwrap(); } }