Skip to content

Commit

Permalink
Work on solidifying cpu operation
Browse files Browse the repository at this point in the history
  • Loading branch information
commonkestrel committed Nov 18, 2023
1 parent 87d53e9 commit c7546bd
Show file tree
Hide file tree
Showing 2 changed files with 67 additions and 88 deletions.
76 changes: 54 additions & 22 deletions Arch.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,28 +10,60 @@ and is determined in 3 ROM chips inside the control circuit
based on the current instruction, the instruction immediate bit,
and the clock cycle.

### ALU opcode
The control word is split into 3 bytes,
just because I couldn't find any EEPROMs with a 24-bit word.
It is layed out like so:

The first three bits of the control word (`AOL`, `AOM`, and `AOH`) represent the ALU opcode.
| | `B7` | `B6` | `B5` | `B4` | `B3` | `B2` | `B1` | `B0` |
|-----------|------|------|------|------|------|------|------|------|
| Low Byte | `RPA` | `RBA` | `RO` | `RI` | `AO` | `AOH` | `AOM` | `AOL` |
| Mid Byte | `` | `` | `` | `` | `` | `` | `` | `` |
| High Byte | `` | `` | `` | `` | `` | `` | `` | `` |

### ALU Opcode

The first four bits of the control word (`AOL`, `AOM`, and `AOH`) represent the ALU opcode.
ALU operations based on these opcodes are shown below:

| `AOL` | `AOM` | `AOH` | Operation |
|-------|-------|-------|-----------|
| `0` | `0` | `0` | `ADD` |
| `0` | `0` | `1` | `SUB` |
| `0` | `1` | `0` | `ADC` |
| `0` | `1` | `0` | `SBC` |
| `1` | `0` | `0` | `NAND` |
| `1` | `0` | `1` | `OR` |
| `1` | `1` | `0` | `CMP` |
| `1` | `1` | `1` | `CZ` |

While most of these are pretty self explanitory,
a few need a bit more explanation.

`CMP` compares the numbers in the primary and secondary registers,
and sets the `L`, `E`, and `G` flags in the Flags Register accordingly.

`CZ` stands for Check Zero.
This operation checks if the number in the primary register is zero,
and sets the `Z` flag in the Flags Register accordingly.
| `AO` | `AO2` | `AO1` | `AO0` | Operation |
|-------|-------|-------|-------|-----------|
| `0` | `0` | `0` | `0` | `NOP` |
| `0` | `0` | `0` | `1` | `CMP` |
| `0` | `0` | `1` | `0` | `CZ` |
| `0` | `0` | `1` | `1` | `ALP` |
| `0` | `1` | `0` | `0` | `ALS` |
| `1` | `0` | `0` | `0` | `ADD` |
| `1` | `0` | `0` | `1` | `SUB` |
| `1` | `0` | `1` | `0` | `ADC` |
| `1` | `0` | `1` | `0` | `SBC` |
| `1` | `1` | `0` | `0` | `NAND` |
| `1` | `1` | `0` | `1` | `OR` |

The `AO` bit, or Arithmetic Operation bit, designates an arithmetic operation.
If `AO` is set, the output of the operation will be output to the bus.
When not set, the operation executed is a special operation,
or an operation that is not part of general integer arithmatic.
These need a bit more explanation.

- `NOP` (No Op) does nothing.
- `CMP` (Compare) compares the integers in the primary and secondary register,\
setting the `L`, `E`, and `G` bits in the Flags register respectively.
- `CZ` (Check Zero) checks if the number in the primary register is `0`,\
setting the `Z` bit in the Flags register respectively.
- `ALP` (ALU Load Primary) loads the ALU primary register from the bus.
- `ALS` (ALU Load Secondary) loads the ALU secondary register from the bus.

## Memory

There are 64kb of accessable RAM on the board,
with the top 64 addresses (`0xFFC0` - `0xFFFF`) being reserved for memory mapped peripherals.
The stack starts at `0xFFBF` and grows downward.

There are 64 addresses for memory mapped I/O to allow for expansion,
but there are a few peripherals that are required:
- `0xFFFF` Status Register: This register is written to and read by the CPU without addressing,\
but programs can access it through memory operations. The contents of the Status Register are explained more in [Status Register](#status-register).

## Status Register


79 changes: 13 additions & 66 deletions src/emulator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -142,10 +142,9 @@ struct RegBank {
c: u8,
d: u8,
e: u8,
f: u8,
h: u8,
l: u8,
f: Flags,
temp: u8,
}

impl RegBank {
Expand All @@ -156,9 +155,9 @@ impl RegBank {
2 => self.c,
3 => self.d,
4 => self.e,
5 => self.h,
6 => self.l,
7 => self.f.bits(),
5 => self.f,
6 => self.h,
7 => self.l,
_ => unreachable!(),
}
}
Expand All @@ -170,9 +169,9 @@ impl RegBank {
2 => self.c = val,
3 => self.d = val,
4 => self.e = val,
5 => self.h = val,
6 => self.l = val,
7 => self.f = Flags::from_bits_retain(val),
5 => self.f = val,
6 => self.h = val,
7 => self.l = val,
_ => unreachable!(),
}
}
Expand All @@ -186,10 +185,9 @@ impl Default for RegBank {
c: 0,
d: 0,
e: 0,
f: 0,
h: 0,
l: 0,
f: Flags::empty(),
temp: 0,
}
}
}
Expand Down Expand Up @@ -321,7 +319,6 @@ struct State {
bank: RegBank,
speed: Option<Duration>,
quit: bool,
ports: [u8; 1 << 8],
mem: Box<[u8]>,
program: Box<[u8]>,
}
Expand Down Expand Up @@ -368,7 +365,6 @@ impl State {
bank: RegBank::default(),
speed: None,
quit: false,
ports: [0; 1 << 8],
mem: vec![0; 1 << 16].into_boxed_slice(),
program,
}
Expand All @@ -393,15 +389,13 @@ impl fmt::Display for State {
enum SingleCmd {
Get,
Peek,
Read,
Run,
}

#[derive(Debug, Clone, Copy, PartialEq, Hash)]
enum DoubleCmd {
Set,
Poke,
Write,
}

static STATE: OnceLock<RwLock<State>> = OnceLock::new();
Expand Down Expand Up @@ -496,8 +490,6 @@ async fn handle_input(input: String) -> Result<(), EmulatorError> {
"SET" => double_arg(DoubleCmd::Set, args).await?,
"PEEK" => single_arg(SingleCmd::Peek, args).await?,
"POKE" => double_arg(DoubleCmd::Poke, args).await?,
"READ" => single_arg(SingleCmd::Read, args).await?,
"WRITE" => double_arg(DoubleCmd::Write, args).await?,
"RUN" => single_arg(SingleCmd::Run, args).await?,
_ => eprintln!("UNRECOGNIZED COMMAND: {cmd}"),
},
Expand Down Expand Up @@ -568,8 +560,6 @@ fn help() {
GET <reg> : Gets the value in the register `reg`\n\
PEEK <addr> : Gets the value at the memory address `addr`\n\
POKE <addr>, <val> : Sets the value at the memory address `addr` to `val`\n\
READ <port> : Gets the value on the specified port `port`\n\
WRITE <port>, <val> : Writes the value `val` to the specified port `port`\n\
RUN <speed> : Starts running the CPU at the specified `speed` (in hertz)\n
If `speed` is zero, the emulator will run as fast as possible.
DUMP : Dumps the current machine state\n\
Expand Down Expand Up @@ -615,26 +605,7 @@ async fn single_arg(cmd: SingleCmd, arg: &str) -> Result<(), EmulatorError> {
.bank
.get_reg(reg)
)
}
SingleCmd::Read => {
let port = match parse_u8(arg.trim()) {
Ok(reg) => reg,
Err(_) => {
eprintln!("INVALID ARGUMENT: unable to parse register number");
return Ok(());
}
};

println!(
"PORT {port}: {:#04X}",
STATE
.get()
.ok_or(EmulatorError::OnceEmpty)?
.read()
.await
.ports[port as usize]
);
}
},
SingleCmd::Peek => {
let addr = match parse_u16(arg.trim()) {
Ok(addr) => addr,
Expand All @@ -653,7 +624,7 @@ async fn single_arg(cmd: SingleCmd, arg: &str) -> Result<(), EmulatorError> {
.await
.mem[addr as usize]
);
}
},
SingleCmd::Run => {
let speed = match parse_u32(arg.trim()) {
Ok(speed) => speed,
Expand All @@ -675,7 +646,7 @@ async fn single_arg(cmd: SingleCmd, arg: &str) -> Result<(), EmulatorError> {
.write()
.await
.speed = Some(duration);
}
},
}

Ok(())
Expand Down Expand Up @@ -729,7 +700,7 @@ async fn double_arg(cmd: DoubleCmd, args: &str) -> Result<(), EmulatorError> {
.await
.bank
.set_reg(reg, value);
}
},
DoubleCmd::Poke => {
let addr = match parse_u16(arg1.trim()) {
Ok(addr) => addr,
Expand All @@ -753,31 +724,7 @@ async fn double_arg(cmd: DoubleCmd, args: &str) -> Result<(), EmulatorError> {
.write()
.await
.mem[addr as usize] = value;
}
DoubleCmd::Write => {
let port = match parse_u8(arg1.trim()) {
Ok(reg) => reg,
Err(_) => {
eprintln!("INVALID ARGUMENT: unable to parse register number");
return Ok(());
}
};

let value = match parse_u8(arg2.trim()) {
Ok(val) => val,
Err(_) => {
eprintln!("INVALID ARGUMENT: unable to parse value");
return Ok(());
}
};

STATE
.get()
.ok_or(EmulatorError::OnceEmpty)?
.write()
.await
.ports[port as usize] = value;
}
},
}

Ok(())
Expand Down

0 comments on commit c7546bd

Please sign in to comment.