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.
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.
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)
Below is a layout view showing the contents of every breadboard.
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.
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
Name | Description |
---|---|
RST | Soft Reset |
STC | Set Carry Flag |
CLC | Clear Carry Flag |
NOP | No Operation |
HLT | Halt |
Total Control Instructions: 5
Special Instructions
Name | Description |
---|---|
SII | Set Interrupt |
CII | Clear Interrupt |
ITR | Interrupt: Triggers an interrupt process. |
RTI | Return from Interrupt |
MOV $CLK, # | Set Clock Speed to Immediate Value |
MOV $CLK, $E | Set 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.
Name | Description |
---|---|
LSL | Logical Shift Left |
LSR | Logical Shift Right |
MOV $SP, $CD | Moves content of $CD into Stack pointer in one cycle |
MOV $CD, $SP | Moves content of Stack pointer into $CD in one cycle |
Total Shift Instructions: 14
Stack Operations
Name | Description |
---|---|
PSH ${src} | Push to Stack Implemented for all GPRs. |
PUL ${src} | Pull from Stack Implemented for all GPRs. |
PSF | Push flag register’s content to stack |
PLF | Pull flag register’s content from the stack. |
Total Stack Instructions: 10
I/O Instructions
Displays
Name | Description |
---|---|
SDL | Sets lower byte of the output register from value in a register (Implemented for $A and $B) |
SDH | Sets higher byte of the output register from value in a register (Implemented for $A and $B) |
OLR | OLED Reset |
OLD | OLED Write Data (Implemented for #, $A, $B) |
OLC | OLED Write Command(Implemented for #, $A, $B) |
Total Display Instructions: 11
Port Selector
Instruction | Description |
---|---|
OUT #, RA | Writes 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, RA | Writes the content of RA to a port specified by the content of RB. |
INP RA, RB | Writes content from a port, specified by the content of RB, to RA. |
Total Port Selector Instructions: 4
Total I/O Instructions: 15
Branching Instructions
Name | Description |
---|---|
JMP | Jump: Unconditionally transfers control to another location(Implemented for [@] and [$CD]) |
JSR | Jump to Subroutine: Jumps to a subroutine and saves the return address Unconditionally transfers control to another location(Implemented for [@] and [$CD]) |
RTS | Return from Subroutine: Returns control to the calling routine. |
JZ | Jump if Zero: Jumps if the zero flag is set. |
JO | Jump if Overflow: Jumps if the overflow flag is set. |
JN | Jump if Negative: Jumps if the negative flag is set. |
JC | Jump if Carry: Jumps if the carry flag is set. |
JNZ | Jump if Not Zero: Jumps if the zero flag is not set. |
JNO | Jump if No Overflow: Jumps if the overflow flag is not set. |
JP | Jump if Positive(or zero): Jumps if the negative flag is not set. |
JNC | Jump if No Carry: Jumps if the carry flag is not set. |
JGZ | Jump if Greater Than Zero: Jumps if a greater-than-zero condition is met. |
Total Branching Instructions: 14
Next Post: Nomenclatures and Notes »»»»»»»»»»
Updated: