add labels, improve documentation, add step debug
This commit is contained in:
parent
2fb5bbaa8d
commit
8d479202b0
41
README.md
41
README.md
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,8 @@
|
||||||
|
mov b, 3
|
||||||
|
mov a, 1
|
||||||
|
|
||||||
|
loop:
|
||||||
|
sub b, a
|
||||||
|
jz loop
|
||||||
|
|
||||||
|
hlt
|
||||||
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
18
src/cpu.rs
18
src/cpu.rs
|
|
@ -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();
|
||||||
|
|
|
||||||
|
|
@ -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",
|
||||||
|
_ => "???",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue