Homebrew CPU Emulator

2023/03/08

Tags: software

Link to my UMI 8 toolchain repository:
https://github.com/FlyingFish800/UMI8

After watching videos by Ben Eater and SLU-4 on their custom 8-bit CPUs implemented in 7400 series TTL logic chips, I became interested in CPU architecture design and decided to make my own. I started with designing the architecture and instruction set, and I set the following goals in mind:

  1. The hardware should be as simple as possible
  2. It must be achievable with TTL Logic gates
  3. It should have stack/subroutine operations

My first revision of this architecture was a 16 bit design using a full 16 bit data bus, a write only memory offset register to hack in a virtual address space, and a similarly hacky interrupt system. It also had many different ALU instructions, with a continuously increasing number of control lines as feature creep overcame the design. After several months of working on this design, and a full Java emulator and assembler later, I made the hard decision to restart the project with everything I had learned, and the previous goals in mind.

The new design only uses one EEPROM for microcode to prevent feature creep, and to help me get creative with the design. These 8 control lines then go to two 3 bit demultiplexers for the source and destination register, and one two bit demultiplexer for the ALU configuration register. The microcode ROM is fed by 5 bits from the instruction, the Zero, Negative, and Carry flag bits, and a 5 bit microstep counter. Sidenote, I really want to reduce the 5 bit counter to a 4 bit one since the last half of the microsteps are only utilized by two of the stack operations, and my CSE 12 computer systems class has given me some inspiration on how to do that, but I haven’t had the time yet. Since only 5 bits of the instruction byte go to the microcode, I have used the remaining 3 to enable AND and OR operations for the entire duration of the instruction, and also to switch out the active register.

This CPU only has 2 general purpose registers, and only one can be used per instruction. The microcode is set up to only use one general purpose register, as there weren’t enough control lines left on the multiplexer to have them both be accessible during the same instruction. The 64s place bit is used to decide which register’s chip select is high during that instruction. These registers are named A and C, which should help remind the programmer that they are very separate and not usable together. The B register is not accessible to the programmer and is a scratch register that also contains a logic unit to handle logic operations. Any time the B register reads from the bus, the bits can be ANDed or ORed with the active register as a hacky way to fit logic operations into this simple processor. These control lines are active for the duration of the instruction since their control lines are tied to the first two bits of the instruction, as mentioned previously.

There are currently 24 instructions this processor is capable of, which are represented in octal due to the previous version of the processor being capable of 64 instructions. The instructions are:

0o00: NOP
0o01: CALL <imm>
0o02: RET
0o03: PSH <reg>
0o04: POP <reg>
0o10: LD <reg>, <imm>
0o11: LD <reg>, <mem:abs>
0o12: LD <mem:abs>, <reg>
0o13: LD <reg>, <mem:rel>
0o14: LD <mem:rel>, <reg>
0o20: JP <imm>
0o21: JPZ <imm>
0o22: JNZ <imm>
0o23: JPC <imm>
0o24: JNC <imm>
0o25: JPN <imm>
0o26: JNN <imm>
0o30: ADD <reg>, <imm>
0o31: ADDC <reg>, <imm>
0o32: INC <reg>
0o33: ZRO <reg>
0o34: SUB <reg>, <imm>
0o35: CMP <reg>, <imm>
0o37: LOGIC <reg>, <imm>

Key: <reg> accesses a register, A or C
        <imm> is an immediate value
        <mem:abs> is an absolute memory address
        <mem:rel> is a relative memory address

The main unique instructions to this instruction set is ZRO which sets the register to zero, and LOG which performs a logic operation of that immediate value and the active register. The specific logic instruction is determined by which of the first two instruction bits are set, and are set by pseudo instructions in the assembler.

The assembler and emulator for this architecture were both written in C and can be found here, along with a version 1 which had architectural bugs due to a lack of a dedicated scratch register. The assembler supports most but not all of the instructions as of writing. The emulator has support for memory mapped IO devices, and a recently added mode where the processor runs in a separate thread and can interface with IO to/from the user in the main thread.

Overall this has been one of the most fun and educational projects I have worked on.

>> Home