Overview

Last November(NOV-2022), I came across a YouTube post by Ben Eater, promoting a Black Friday sale of his 8-bit breadboard CPU kit. Having followed his YouTube channel for a while and considering him one of the best educators on the platform, I seized the opportunity and purchased a kit.

The CPU is based off the SAP-1 (Simple as Possible 1) architecture, as outlined in “Digital Computer Electronics” by Albert Malvino and Jerald Brown. Throughout Spring 2023, I dedicated my spare time to assembling the kit. I built it, got to the display module, took it apart, and even made a slightly extended version with more RAM before taking that apart too. I have then started an expanded version, documented in this GitHub repository, which I am going to share in this series of posts.

evolution
Build Evolution


That being said, I have to pause here and give a massive shoutout to Ben Eater. Consider this another angle to view the SAP architectures, rooted in the foundation that Ben Eater laid.

Also, a notable mention goes to Der_ULF1. In his own 8-bit CPU series, his interrupt handling, IO, and especially his PS2 module design, greatly influenced my design choices. It was exactly what I had in mind(Beside the port selector and SPI module), but executed even better. Check out his GitHub here and YouTube channel here.

While assembling my build, I frequently found myself scrolling through the non-official Ben Eater subreddit r/beneater. It’s a community where enthusiasts and learners share their individual builds, exchange insights, and offer support. If you’re building your own CPU and or are looking for advice and feedback on your build, I highly recommend checking out this subreddit.

Digital Computer Electronics outlines three simplified Von Neumann architectures: SAP-1, SAP-2, and SAP-3, each progressively more complex and capable. My version leans towards the SAP-3, with an 8-bit data bus and 16-bit system/address bus, and adds some additional functionalities. The SAP-1, while accessible, only has a 4-bit program counter and 16 bytes of RAM, limiting the complexity of programs it can run.

This limitation is what pushed me to modify and improve the design, aiming for a CPU capable of handling more power-demanding and interesting programs. Plus I hate to see unused areas on my breadboards, and I came to the conclusion that if I was going to spend a considerable amount of time building the CPU, I could have as well made it look nice and more permanent.

Fully assembled CPU
Fully assembled CPU


My build has the following notable features:

  • Programmable clock speed(Chosen from 8 different clock speeds)
  • 16-bit Program Counter
  • 16-bit Stack Pointer
  • Transfer/Bridge register between data and address bus
  • 16-bit numerical output register
  • PS2 keyboard decoder
  • 5-digit digital 14-segment display(signed and unsigned 16-bit integers for now)
  • SPI BUS (Connected to an Adafruit Bluetooth receiver and an SD card slot)
  • 128 x 64 monochrome OLED display
  • 48-K-byte of RAM(Remaining 16-K-byte address space is reserved for a boot loader and stack memory)
  • ALU with Shift (LSR LSR), AND, ADD, SUB, OR, XOR
  • 7 flags: Four ALU flags: (Z-O-N-C) in writable 4-bit register; and two interrupt flags.
  • 5 general purpose registers(GPRs): A, B, C, D, and E(partial GPR)
  • 8-bit I register hardwired to lower byte of interrupt code address
  • 4-bit microcode step counter(With dynamic micro-steps reset)
Build’s Architecture
Build’s Architecture


Below is a layout view showing the contents of every breadboard.

Breadboard Layout
Breadboard Layout


ICs Family

Taking into account the considerable number of chips used in my build(A little over a hundred), I opted for the 74HCT(a special version of 74HC) chips family. Ben Eater’s original kit consists of 74LS(Low-power Schottky) TTL chips; and while they are known for their speed, they are also power-intensive. In comparison, the 74HCT family of chips offers a balance between the speed characteristic of TTL and the low power consumption of CMOS circuitry. This was particularly essential given that my build, even with the 74HCT chips, draws about 1.2 amps peak in idle. Such a power draw, although substantial, would have been significantly higher had I used exclusively 74LS chips. Additionally, the compatibility of 74HCT with 74LS and 74HC(High-speed CMOS ) inputs allowed a mix of these chips in the system without any operational concerns.

Tools

  • Plier
  • Oscilloscope: I’ve had this portable oscilloscope since my undergraduate years and it has been very handy for this project.
  • Multimeter
  • The power supply that came with the SAP-1 works fine, but I prefer the control offered by benchtop power supplies. You can find the one I use here.
  • Soldering iron
  • Wire straightener: This was EXTREMELY handy! I got it here.

Regarding wiring materials, I think these wires are the perfect size(20AWG) for breadboarding.

Bypass and Filter Capacitors

It is good practice to always have a small bypass capacitor across the power and ground pin of every IC, and as close as possible to the chip to filter out electrical noise and voltage spikes.

I have a 0.1uf decoupling capacitor across most of my sequential ICs.

Additionally, on each power rail, I have a 1uf ceramic capacitor, as well as a 10uf and 100uf electrolytic filter capacitor.

Capacitors Close-up
Capacitors Close-up

Proximity matters because the closer the bypass capacitor is to the IC, the more effective it is at quickly smoothing out voltage fluctuations and noise. When the capacitor is near, the electrical path is shorter, reducing inductance and resistance, which allows for faster response times to counteract any sudden changes in voltage or noise that could adversely affect the IC’s performance.

Instruction Set:

My build has a total of 256 instructions:

Notations

  • $: Register
  • #: Number
  • [@]: Memory location

Partial GPR $E

Immediate

  • MOV $E, #
  • ADD $E, #
  • SUB $E, #
  • CMP $E, #

Register Direct Mode Instructions

With $A, $B, $C, and $D:

  • MOV ${reg}, $E
  • MOV $E, ${reg}
  • CMP $E, ${reg}

Total Instructions: 16

Group (GRP) Instructions

The following seven instructions, collectively referred to as GRP, as in “group” are implemented for all General Purpose Registers : $A, $B, $C, $D.

  • MOV
  • ADD
  • SUB
  • AND
  • OR
  • XOR
  • CMP

Immediate Mode Instructions

  • GRP ${dest}, # (with 8-bit data operand)

Total Immediate Instructions: 28

Absolute Mode Instructions

  • MOV ${dest}, [@] and MOV [@], ${dest}, plus 6 other GRP ${dest}, [@]

Total Absolute Mode Instructions: 32

Register Direct Mode Instructions

  • GRP ${dest}, ${src} for each GPR combination

Total Direct Mode Instructions: 84 (excluding 4 x 7 cases where dest = src)

Register Indirect Mode Instructions

  • MOV ${dest}, [$CD], MOV [$CD], ${dest}, and GRP ${dest}, [$CD]

Total Indirect Mode Instructions: 32

Total GRP Instructions: 176

Control Instructions

NameDescription
RSTSoft Reset
STCSet Carry Flag
CLCClear Carry Flag
NOPNo Operation
HLTHalt

Total Control Instructions: 5

Special Instructions

NameDescription
SIISet Interrupt
CIIClear Interrupt
ITRInterrupt: Triggers an interrupt process.
RTIReturn from Interrupt
MOV $CLK, #Set Clock Speed to Immediate Value
MOV $CLK, $ESet Clock Speed to Value in $E

Total Special Instructions: 6

Shift Instructions

Implemented in register direct mode for all GPRs; and in absolute and register indirect mode.

NameDescription
LSLLogical Shift Left
LSRLogical Shift Right
MOV $SP, $CDMoves content of $CD into Stack pointer in one cycle
MOV $CD, $SPMoves content of Stack pointer into $CD in one cycle

Total Shift Instructions: 14

Stack Operations

NameDescription
PSH ${src}Push to Stack Implemented for all GPRs.
PUL ${src}Pull from Stack Implemented for all GPRs.
PSFPush flag register’s content to stack
PLFPull flag register’s content from the stack.

Total Stack Instructions: 10

I/O Instructions

Displays

NameDescription
SDLSets lower byte of the output register from value in a register (Implemented for $A and $B)
SDHSets higher byte of the output register from value in a register (Implemented for $A and $B)
OLROLED Reset
OLDOLED Write Data (Implemented for #, $A, $B)
OLCOLED Write Command(Implemented for #, $A, $B)

Total Display Instructions: 11

Port Selector

InstructionDescription
OUT #, RAWrites the content of RA to a port selected based on memory content.
INP RA, #Writes content from a port, selected based on memory content, to RA.
OUT RB, RAWrites the content of RA to a port specified by the content of RB.
INP RA, RBWrites content from a port, specified by the content of RB, to RA.

Total Port Selector Instructions: 4

Total I/O Instructions: 15

Branching Instructions

NameDescription
JMPJump: Unconditionally transfers control to another location(Implemented for [@] and [$CD])
JSRJump to Subroutine: Jumps to a subroutine and saves the return address Unconditionally transfers control to another location(Implemented for [@] and [$CD])
RTSReturn from Subroutine: Returns control to the calling routine.
JZJump if Zero: Jumps if the zero flag is set.
JOJump if Overflow: Jumps if the overflow flag is set.
JNJump if Negative: Jumps if the negative flag is set.
JCJump if Carry: Jumps if the carry flag is set.
JNZJump if Not Zero: Jumps if the zero flag is not set.
JNOJump if No Overflow: Jumps if the overflow flag is not set.
JPJump if Positive(or zero): Jumps if the negative flag is not set.
JNCJump if No Carry: Jumps if the carry flag is not set.
JGZJump if Greater Than Zero: Jumps if a greater-than-zero condition is met.

Total Branching Instructions: 14


Next Post: Nomenclatures and Notes »»»»»»»»»»

Updated:

back to top