add labels, improve documentation, add step debug

This commit is contained in:
krolxon 2026-01-05 12:12:57 +05:30
parent 2fb5bbaa8d
commit 8d479202b0
6 changed files with 136 additions and 9 deletions

View File

@ -1,13 +1,40 @@
# 8-Bit CPU Emulator # 8-Bit CPU Emulator
## CPU Architecture
- Word Size
- **Data Width:** 8 bits
- **Address width:** 16 bits
- **Address space:** 64 KB (0x0000- 0xFFFF)
## Supported Instructions ## Supported Instructions
1. MOV
2. ADD | Instruction | Syntax |
3. SUB | ----------- | ------------ |
4. JMP (Jump) | MOV | mov reg, imm |
5. JZ (Jump if zero) | ADD | add r1, r2 |
5. JZ (Jump if not zero) | SUB | sub r1, r2 |
6. HLT (Halt) | 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 # Usage
```bash ```bash

8
examples/loops.asm Normal file
View File

@ -0,0 +1,8 @@
mov b, 3
mov a, 1
loop:
sub b, a
jz loop
hlt

View File

@ -1,4 +1,5 @@
use crate::instructions::Instruction; use crate::instructions::Instruction;
use std::collections::HashMap;
fn tokenize(line: &str) -> Vec<String> { fn tokenize(line: &str) -> Vec<String> {
line.split(|c| c == ' ' || c == ',' || c == '\t') 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> { pub fn assembler(source: &str) -> Vec<u8> {
let symbols = first_pass(source);
let mut bytes = Vec::new(); let mut bytes = Vec::new();
for (line_no, line) in source.lines().enumerate() { for (line_no, line) in source.lines().enumerate() {
let line = line.trim(); let line = line.trim();
// Comments in assembly start with ";" // Comments in assembly start with ";"
if line.is_empty() || line.starts_with(';') { if line.is_empty() || line.starts_with(';') || line.ends_with(":") {
continue; continue;
} }
@ -61,6 +99,22 @@ pub fn assembler(source: &str) -> Vec<u8> {
bytes.push(r2); 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" => { "hlt" => {
bytes.push(Instruction::HLT as u8); bytes.push(Instruction::HLT as u8);
} }

View File

@ -2,6 +2,7 @@ use crate::instructions::Instruction;
use crate::memory::Memory; use crate::memory::Memory;
#[derive(Default, Debug)] #[derive(Default, Debug)]
#[allow(dead_code)]
pub struct CPU { pub struct CPU {
pub a: u8, pub a: u8,
pub b: u8, pub b: u8,
@ -18,6 +19,23 @@ pub struct CPU {
} }
impl 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) { pub fn step(&mut self, mem: &mut Memory) {
let opcode = mem.read(self.pc); let opcode = mem.read(self.pc);
self.inc_pc(); self.inc_pc();

View File

@ -8,3 +8,18 @@ pub enum Instruction {
JNZ = 0x06, JNZ = 0x06,
HLT = 0xFF, 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",
_ => "???",
}
}
}

View File

@ -3,6 +3,8 @@ mod cpu;
mod instructions; mod instructions;
mod memory; mod memory;
use std::io;
use cpu::CPU; use cpu::CPU;
use memory::Memory; use memory::Memory;
use clap::Parser; use clap::Parser;
@ -30,7 +32,10 @@ fn main() {
} }
while !cpu.halted { while !cpu.halted {
cpu.debug_instr(&mem);
cpu.step(&mut mem); cpu.step(&mut mem);
println!("{:?}", cpu);
let mut buf = String::new();
io::stdin().read_line(&mut buf).unwrap();
} }
} }