Krolyxon
Commit 8d47920

Branches

Tags

Refs →

bench518d47920

add labels, improve documentation, add step debug

Author
krolxon
Date
2026-01-05 12:12 +0530
Parent
2fb5bba
Commit
8d479202b0c9dbe13bb95ad572a060b69642ec26

+136 −9

6 files changed

  1. M README.md +34 −7
  2. A examples/loops.asm +8 −0
  3. M src/assembler.rs +55 −1
  4. M src/cpu.rs +18 −0
  5. M src/instructions.rs +15 −0
  6. M src/main.rs +6 −1
M README.md View
@@ -1,13 +1,40 @@
11 # 8-Bit CPU Emulator
22
3+## CPU Architecture
4+- Word Size
5+ - **Data Width:** 8 bits
6+ - **Address width:** 16 bits
7+ - **Address space:** 64 KB (0x0000- 0xFFFF)
8+
39 ## 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+
1138
1239 # Usage
1340 ```bash
A examples/loops.asm View
@@ -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
M src/assembler.rs View
@@ -1,4 +1,5 @@
11 use crate::instructions::Instruction;
2+use std::collections::HashMap;
23
34 fn tokenize(line: &str) -> Vec<String> {
45 line.split(|c| c == ' ' || c == ',' || c == '\t')
@@ -17,14 +18,51 @@ fn parse_reg(s: &str) -> u8 {
1718 }
1819 }
1920
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+
2057 pub fn assembler(source: &str) -> Vec<u8> {
58+ let symbols = first_pass(source);
2159 let mut bytes = Vec::new();
2260
2361 for (line_no, line) in source.lines().enumerate() {
2462 let line = line.trim();
2563
2664 // Comments in assembly start with ";"
27− if line.is_empty() || line.starts_with(';') {
65+ if line.is_empty() || line.starts_with(';') || line.ends_with(":") {
2866 continue;
2967 }
3068
@@ -61,6 +99,22 @@ pub fn assembler(source: &str) -> Vec<u8> {
6199 bytes.push(r2);
62100 }
63101
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+
64118 "hlt" => {
65119 bytes.push(Instruction::HLT as u8);
66120 }
M src/cpu.rs View
@@ -2,6 +2,7 @@ use crate::instructions::Instruction;
22 use crate::memory::Memory;
33
44 #[derive(Default, Debug)]
5+#[allow(dead_code)]
56 pub struct CPU {
67 pub a: u8,
78 pub b: u8,
@@ -18,6 +19,23 @@ pub struct CPU {
1819 }
1920
2021 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+
2139 pub fn step(&mut self, mem: &mut Memory) {
2240 let opcode = mem.read(self.pc);
2341 self.inc_pc();
M src/instructions.rs View
@@ -8,3 +8,18 @@ pub enum Instruction {
88 JNZ = 0x06,
99 HLT = 0xFF,
1010 }
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+}
M src/main.rs View
@@ -3,6 +3,8 @@ mod cpu;
33 mod instructions;
44 mod memory;
55
6+use std::io;
7+
68 use cpu::CPU;
79 use memory::Memory;
810 use clap::Parser;
@@ -30,7 +32,10 @@ fn main() {
3032 }
3133
3234 while !cpu.halted {
35+ cpu.debug_instr(&mem);
3336 cpu.step(&mut mem);
34− println!("{:?}", cpu);
37+
38+ let mut buf = String::new();
39+ io::stdin().read_line(&mut buf).unwrap();
3540 }
3641 }