bench518d47920
add labels, improve documentation, add step debug
+136 −9
6 files changed
- M README.md +34 −7
- A examples/loops.asm +8 −0
- M src/assembler.rs +55 −1
- M src/cpu.rs +18 −0
- M src/instructions.rs +15 −0
- M src/main.rs +6 −1
| @@ -1,13 +1,40 @@ | |||
| 1 | 1 | # 8-Bit CPU Emulator | |
| 2 | 2 | ||
| 3 | + | ## CPU Architecture | |
| 4 | + | - Word Size | |
| 5 | + | - **Data Width:** 8 bits | |
| 6 | + | - **Address width:** 16 bits | |
| 7 | + | - **Address space:** 64 KB (0x0000- 0xFFFF) | |
| 8 | + | ||
| 3 | 9 | ## Supported Instructions | |
| 4 | − | 1. MOV | |
| 5 | − | 2. ADD | |
| 6 | − | 3. SUB | |
| 7 | − | 4. JMP (Jump) | |
| 8 | − | 5. JZ (Jump if zero) | |
| 9 | − | 5. JZ (Jump if not zero) | |
| 10 | − | 6. HLT (Halt) | |
| 10 | + | ||
| 11 | + | | Instruction | Syntax | | |
| 12 | + | | ----------- | ------------ | | |
| 13 | + | | MOV | mov reg, imm | | |
| 14 | + | | ADD | add r1, r2 | | |
| 15 | + | | SUB | sub r1, r2 | | |
| 16 | + | | JMP | jmp addr | | |
| 17 | + | | JZ | jz addr | | |
| 18 | + | | JNZ | jnz addr | | |
| 19 | + | | HLT (Halt) | hlt | | |
| 20 | + | ||
| 21 | + | ||
| 22 | + | ## Registers | |
| 23 | + | | Register | Size | Description | | |
| 24 | + | | -------- | ------ | ------------------------------ | | |
| 25 | + | | A | 8-bit | General | | |
| 26 | + | | B | 8-bit | General | | |
| 27 | + | | C | 8-bit | General | | |
| 28 | + | | D | 8-bit | General | | |
| 29 | + | | PC | 16-bit | Program Counter | | |
| 30 | + | | SP | 16-bit | Stack pointer (unused for now) | | |
| 31 | + | ||
| 32 | + | ## Flags | |
| 33 | + | | Flag | Description | | |
| 34 | + | | ----- | ------------ | | |
| 35 | + | | Z | Zero Flag | | |
| 36 | + | | C | Carry/Borrow | | |
| 37 | + | ||
| 11 | 38 | ||
| 12 | 39 | # Usage | |
| 13 | 40 | ```bash | |
| @@ -0,0 +1,8 @@ | |||
| 1 | + | mov b, 3 | |
| 2 | + | mov a, 1 | |
| 3 | + | ||
| 4 | + | loop: | |
| 5 | + | sub b, a | |
| 6 | + | jz loop | |
| 7 | + | ||
| 8 | + | hlt | |
| @@ -1,4 +1,5 @@ | |||
| 1 | 1 | use crate::instructions::Instruction; | |
| 2 | + | use std::collections::HashMap; | |
| 2 | 3 | ||
| 3 | 4 | fn tokenize(line: &str) -> Vec<String> { | |
| 4 | 5 | line.split(|c| c == ' ' || c == ',' || c == '\t') | |
| @@ -17,14 +18,51 @@ fn parse_reg(s: &str) -> u8 { | |||
| 17 | 18 | } | |
| 18 | 19 | } | |
| 19 | 20 | ||
| 21 | + | fn instr_size(tokens: &[String]) -> u16 { | |
| 22 | + | match tokens[0].as_str() { | |
| 23 | + | "mov" | "add" | "sub" | "jmp" | "jz" | "jnz" => 3, | |
| 24 | + | "hlt" => 1, | |
| 25 | + | _ => panic!("Unknown instruction {}", tokens[0]) | |
| 26 | + | ||
| 27 | + | } | |
| 28 | + | } | |
| 29 | + | ||
| 30 | + | ||
| 31 | + | fn first_pass(source: &str) -> HashMap<String, u16> { | |
| 32 | + | let mut symbols = HashMap::new(); | |
| 33 | + | let mut addr: u16 = 0; | |
| 34 | + | ||
| 35 | + | for line in source.lines() { | |
| 36 | + | let line = line.trim(); | |
| 37 | + | ||
| 38 | + | // Ignoring comments and empty lines | |
| 39 | + | if line.is_empty() || line.starts_with(";") { | |
| 40 | + | continue; | |
| 41 | + | } | |
| 42 | + | ||
| 43 | + | // Labels (ends with ":") | |
| 44 | + | if line.ends_with(":") { | |
| 45 | + | let label = line.trim_end_matches(":").to_string(); | |
| 46 | + | symbols.insert(label, addr); | |
| 47 | + | continue; | |
| 48 | + | } | |
| 49 | + | ||
| 50 | + | let tokens = tokenize(line); | |
| 51 | + | addr += instr_size(&tokens); | |
| 52 | + | } | |
| 53 | + | ||
| 54 | + | symbols | |
| 55 | + | } | |
| 56 | + | ||
| 20 | 57 | pub fn assembler(source: &str) -> Vec<u8> { | |
| 58 | + | let symbols = first_pass(source); | |
| 21 | 59 | let mut bytes = Vec::new(); | |
| 22 | 60 | ||
| 23 | 61 | for (line_no, line) in source.lines().enumerate() { | |
| 24 | 62 | let line = line.trim(); | |
| 25 | 63 | ||
| 26 | 64 | // Comments in assembly start with ";" | |
| 27 | − | if line.is_empty() || line.starts_with(';') { | |
| 65 | + | if line.is_empty() || line.starts_with(';') || line.ends_with(":") { | |
| 28 | 66 | continue; | |
| 29 | 67 | } | |
| 30 | 68 | ||
| @@ -61,6 +99,22 @@ pub fn assembler(source: &str) -> Vec<u8> { | |||
| 61 | 99 | bytes.push(r2); | |
| 62 | 100 | } | |
| 63 | 101 | ||
| 102 | + | "jmp" | "jz" | "jnz" => { | |
| 103 | + | let opcode = match tokens[0].as_str() { | |
| 104 | + | "jmp" => Instruction::JMP, | |
| 105 | + | "jz" => Instruction::JZ, | |
| 106 | + | "jnz" => Instruction::JNZ, | |
| 107 | + | _ => unreachable!() | |
| 108 | + | }; | |
| 109 | + | ||
| 110 | + | let label = &tokens[1]; | |
| 111 | + | let addr = *symbols.get(label).expect("Uknown label"); | |
| 112 | + | ||
| 113 | + | bytes.push(opcode as u8); | |
| 114 | + | bytes.push((addr & 0xFF) as u8); // low | |
| 115 | + | bytes.push((addr >> 8) as u8); // high | |
| 116 | + | } | |
| 117 | + | ||
| 64 | 118 | "hlt" => { | |
| 65 | 119 | bytes.push(Instruction::HLT as u8); | |
| 66 | 120 | } | |
| @@ -2,6 +2,7 @@ use crate::instructions::Instruction; | |||
| 2 | 2 | use crate::memory::Memory; | |
| 3 | 3 | ||
| 4 | 4 | #[derive(Default, Debug)] | |
| 5 | + | #[allow(dead_code)] | |
| 5 | 6 | pub struct CPU { | |
| 6 | 7 | pub a: u8, | |
| 7 | 8 | pub b: u8, | |
| @@ -18,6 +19,23 @@ pub struct CPU { | |||
| 18 | 19 | } | |
| 19 | 20 | ||
| 20 | 21 | impl CPU { | |
| 22 | + | ||
| 23 | + | pub fn debug_instr(&self, mem: &Memory) { | |
| 24 | + | let opcode = mem.read(self.pc); | |
| 25 | + | ||
| 26 | + | println!( | |
| 27 | + | "PC={:04X} {:<3} | A={:02X} B={:02X} C={:02X} D={:02X} | Z={} C={}", | |
| 28 | + | self.pc, | |
| 29 | + | Instruction::opcode_name(opcode), | |
| 30 | + | self.a, | |
| 31 | + | self.b, | |
| 32 | + | self.c, | |
| 33 | + | self.d, | |
| 34 | + | self.zero as u8, | |
| 35 | + | self.carry as u8 | |
| 36 | + | ); | |
| 37 | + | } | |
| 38 | + | ||
| 21 | 39 | pub fn step(&mut self, mem: &mut Memory) { | |
| 22 | 40 | let opcode = mem.read(self.pc); | |
| 23 | 41 | self.inc_pc(); | |
| @@ -8,3 +8,18 @@ pub enum Instruction { | |||
| 8 | 8 | JNZ = 0x06, | |
| 9 | 9 | HLT = 0xFF, | |
| 10 | 10 | } | |
| 11 | + | ||
| 12 | + | impl Instruction { | |
| 13 | + | pub fn opcode_name(op: u8) -> &'static str{ | |
| 14 | + | match op { | |
| 15 | + | 0x01 => "MOV", | |
| 16 | + | 0x02 => "ADD", | |
| 17 | + | 0x03 => "SUB", | |
| 18 | + | 0x04 => "JMP", | |
| 19 | + | 0x05 => "JZ", | |
| 20 | + | 0x06 => "JNZ", | |
| 21 | + | 0xFF => "HLT", | |
| 22 | + | _ => "???", | |
| 23 | + | } | |
| 24 | + | } | |
| 25 | + | } | |
| @@ -3,6 +3,8 @@ mod cpu; | |||
| 3 | 3 | mod instructions; | |
| 4 | 4 | mod memory; | |
| 5 | 5 | ||
| 6 | + | use std::io; | |
| 7 | + | ||
| 6 | 8 | use cpu::CPU; | |
| 7 | 9 | use memory::Memory; | |
| 8 | 10 | use clap::Parser; | |
| @@ -30,7 +32,10 @@ fn main() { | |||
| 30 | 32 | } | |
| 31 | 33 | ||
| 32 | 34 | while !cpu.halted { | |
| 35 | + | cpu.debug_instr(&mem); | |
| 33 | 36 | cpu.step(&mut mem); | |
| 34 | − | println!("{:?}", cpu); | |
| 37 | + | ||
| 38 | + | let mut buf = String::new(); | |
| 39 | + | io::stdin().read_line(&mut buf).unwrap(); | |
| 35 | 40 | } | |
| 36 | 41 | } | |