aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorkrolxon <krolyxon@tutanota.com>2026-01-05 12:12:57 +0530
committerkrolxon <krolyxon@tutanota.com>2026-01-05 12:12:57 +0530
commit8d479202b0c9dbe13bb95ad572a060b69642ec26 (patch)
tree5a8f8c015c47bef497e4a54808da667db857fbfd
parent2fb5bbaa8d97a390b3175026040709c762cb93ec (diff)
add labels, improve documentation, add step debug
-rw-r--r--README.md41
-rw-r--r--examples/loops.asm8
-rw-r--r--src/assembler.rs56
-rw-r--r--src/cpu.rs18
-rw-r--r--src/instructions.rs15
-rw-r--r--src/main.rs7
6 files changed, 136 insertions, 9 deletions
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<String> {
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<String, u16> {
+ 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<u8> {
+ 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<u8> {
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();
}
}