As you might know from a previous post, I built Ben Eater’s 8bit TTL CPU on breadboards in the past. The CPU is fully programmable with 16 bytes of memory and 11 Op Codes that can be used to write programs, one instruction per byte. This programming has to be done manually using DIP switches every time the device is powered on. It is a bit tedious as you have to enter instructions in binary for each memory location by hand. I have been learning PCB design and would like to convert this project to a PCB build in the near future. One of the things I would like to add is a program loader that can automatically load programs into the memory on boot. Before implementing this on a PCB, I wanted to have a working prototype that connects with my Breadboard CPU.
Pin Mapping
Arduino
D2 to Shift Register 1 Pin 14
D3 to Shift Register 2 Pin 14
D4 to Shift Register 1 Pin 12
D5 to Shift Register 2 Pin 12
D6 to Shift Register 1 Pin 11
D7 to Shift Register 2 Pin 11
A0 to Push Button input with 10K pulldown resistor
A1 to Output 0 on 74LS138 Decoder
A2 to CPU_RESET
A3 to MEM_RESET
A4 to OLED I2C SDA
A5 to OLED I2C SCL
A6 to the clock enable/disable button
A7 to the programming/run mode button
Shift Register 1
Pin 15 to Data DIP0 switch
Pin 1 to Data DIP1 switch
Pin 2 to Data DIP2 switch
Pin 3 to Data DIP3 switch
Pin 4 to Data DIP4 switch
Pin 5 to Data DIP5 switch
Pin 6 to Data DIP6 switch
Pin 7 to Data DIP7 switch
Shift Register 2
Pin 15 to Address DIP0 switch
Pin 1 to Address DIP1 switch
Pin 2 to Address DIP2 switch
Pin 3 to Address DIP3 switch
Note: Vin pin on the Arduino can optionally be connected to a +7-12 VDC input. Please ensure Vin and USB are not both connected at the same time.
You can find the most up-to-date code on my GitHub Repo. I use a 3rd party platform called PlatformIO for writing Arduino programs in VS Code. But you can simply copy the code from src/main.cpp and run that in the Arduino IDE.
The data to be programmed to the memory is stored in a byte array named code (in decimal format). 16 values are programmed in 16 memory locations, each value is converted to binary in their respective function call. The below code adds a number (saved in memory location 15 i.e. 1111) repeatedly to itself until the upper limit (255) is reached, it then repeatedly subtracts that value from itself until it reaches 0, and it keeps looping.
byte code[] = {224, 47, 116, 96, 63, 224, 128, 100, 0, 0, 0, 0, 0, 0, 0, 1};
Memory location | Array value | Binary value | Op Codes |
---|---|---|---|
00 (0000) | 224 | 1110 0000 | OUT |
01 (0001) | 47 | 0010 1111 | ADD 15 |
02 (0010) | 116 | 0111 0100 | JC 4 |
03 (0011) | 96 | 0110 0000 | JMP 0 |
04 (0100) | 63 | 0011 1111 | SUB 15 |
05 (0101) | 224 | 1110 0000 | OUT |
06 (0110) | 128 | 1000 0000 | JZ 0 |
07 (0111) | 100 | 0110 0100 | JMP 4 |
08 (1000) | |||
09 (1001) | |||
10 (1010) | |||
11 (1011) | |||
12 (1100) | |||
13 (1101) | |||
14 (1110) | |||
15 (1111) | 1 | XXXX 0001 |
#include <Arduino.h>
#include <Wire.h>
#include <SPI.h>
#include <Adafruit_SSD1306.h>
#include <Adafruit_GFX.h>
Adafruit_SSD1306 display(-1);
//control pins
#define CPU_RESET A2
#define MEM_WRITE A3
#define SR1_INPUT 2
#define SR2_INPUT 3
#define SR1_CLOCK 4
#define SR2_CLOCK 5
#define SR1_LATCH 6
#define SR2_LATCH 7
//read pins
#define CONFIRM_BUTTON A0
#define COUNTER_0 A1
#define CLOCK_STOP A6
#define PROG_MODE A7
//constants
#define PULSE_DURATION 10
//variables
int confirmButton = 0;
bool programMode = false;
//reset all modules
void resetCPU(){
//CPU reset attempt#1
//Serial.println("\nCPU Reset Signal");
display.clearDisplay();
display.setCursor(0,0);
display.println("CPU Reset Signal");
//display.display();
digitalWrite(CPU_RESET, HIGH);
delay(PULSE_DURATION);
digitalWrite(CPU_RESET, LOW);
//Check if CPU reset worked properly and repeat if necessary.
int resetCounter = 2;
while(digitalRead(COUNTER_0)){
//Serial.println("CPU Reset Attempt#"+resetCounter);
display.println("CPU Reset Attempt#"+resetCounter);
//display.display();
resetCounter++;
digitalWrite(CPU_RESET, HIGH);
delay(PULSE_DURATION);
digitalWrite(CPU_RESET, LOW);
}
//Serial.println("CPU Reset Successful");
display.println("CPU Reset Successful");
display.display();
delay(900);
}
//shift 4 bits to SR2 and latch to memory address register.
void setMemoryAddress(byte address){
digitalWrite(SR2_LATCH, LOW);
shiftOut(SR2_INPUT, SR2_CLOCK, MSBFIRST, address);
digitalWrite(SR2_LATCH, HIGH);
String outputText = "";
for (int i = 0; i < 4; i++){
if(address & 1)
outputText = "1" + outputText;
else
outputText = "0" + outputText;
address = address >> 1;
}
//Serial.print(outputText+": ");
}
//shift 8 bits to SR1 and latch to memory data.
void writeToRAM(byte data){
digitalWrite(SR1_LATCH, LOW);
shiftOut(SR1_INPUT, SR1_CLOCK, MSBFIRST, data);
digitalWrite(SR1_LATCH, HIGH);
String outputText = "";
for (int i = 0; i < 8; i++){
if(data & 1)
outputText = "1" + outputText;
else
outputText = "0" + outputText;
data = data >> 1;
}
//Serial.println(outputText);
pinMode(MEM_WRITE, OUTPUT);
digitalWrite(MEM_WRITE, LOW);
delay(PULSE_DURATION);
pinMode(MEM_WRITE, INPUT);
}
void setup() {
//initialize with the I2C addr 0x3C
display.begin(SSD1306_SWITCHCAPVCC, 0x3C);
//clear the buffer.
display.clearDisplay();
display.setTextSize(1);
display.setTextColor(WHITE);
//initalize serial connection
Serial.begin(115200);
Serial.println();
//initialize pins
digitalWrite(CPU_RESET, LOW);
digitalWrite(SR1_INPUT, LOW);
digitalWrite(SR2_INPUT, LOW);
digitalWrite(SR1_CLOCK, LOW);
digitalWrite(SR2_CLOCK, LOW);
digitalWrite(SR1_LATCH, LOW);
digitalWrite(SR2_LATCH, LOW);
pinMode(CPU_RESET, OUTPUT);
pinMode(SR1_INPUT, OUTPUT);
pinMode(SR2_INPUT, OUTPUT);
pinMode(SR1_CLOCK, OUTPUT);
pinMode(SR2_CLOCK, OUTPUT);
pinMode(SR1_LATCH, OUTPUT);
pinMode(SR2_LATCH, OUTPUT);
pinMode(CONFIRM_BUTTON, INPUT);
pinMode(MEM_WRITE, INPUT);
pinMode(COUNTER_0, INPUT);
pinMode(CLOCK_STOP, INPUT);
pinMode(PROG_MODE, INPUT);
//notify user to disable the clock and switch to programming mode.
//program execution will not continue unless these conditions are satisfied.
programMode = (analogRead(CLOCK_STOP) < 210 && analogRead(PROG_MODE) < 210) ? true : false;
if(!programMode){
//Serial.println("Please switch Clock to manual and Memory to programming mode.");
display.setCursor(0,0);
display.println("Please switch Clock\nto manual and Memory to programming mode.");
display.display();
}
while(!programMode){
programMode = (analogRead(CLOCK_STOP) < 210 && analogRead(PROG_MODE) < 210) ? true : false;
}
//wait for user to ensure all DIP switches are in the ON (HIGH) position. These switches
//are inverted in Ben's design, hence moving them to the UP position disconnects them.
//Serial.println("Ensure ALL DIP switches are in the ON (HIGH) position.\nPress button to continue.");
display.clearDisplay();
display.setCursor(0,0);
display.println("Ensure ALL DIP\nswitches are in the\nON (HIGH) position.\n\nPress button to\ncontinue.");
display.display();
//loop until the user presses the button.
confirmButton = digitalRead(CONFIRM_BUTTON);
while(confirmButton != 1){
confirmButton = digitalRead(CONFIRM_BUTTON);
}
resetCPU();
display.clearDisplay();
display.setCursor(0,0);
//disable memory address dip switch input.
//Serial.println("\nProgramming the memory");
display.println("Programming the\nmemory");
byte code[] = {224, 47, 116, 96, 63, 224, 128, 100, 0, 0, 0, 0, 0, 0, 0, 1};
for (int address = 0; address < 16; address++){
setMemoryAddress(address);
writeToRAM(code[address]);
display.print(".");
display.display();
delay(10);
}
//disable shift register latches, and reset all modules again.
digitalWrite(SR1_LATCH, LOW);
digitalWrite(SR2_LATCH, LOW);
resetCPU();
//Serial.println("\nPlease switch to run mode and enable the clock...");
programMode = false;
}
void loop() {
programMode = (analogRead(CLOCK_STOP) > 210 && analogRead(PROG_MODE) > 210) ? true : false;
if(programMode){
display.clearDisplay();
display.setCursor(40,30);
display.println("RUN MODE");
display.display();
}
else{
display.clearDisplay();
display.setCursor(0,0);
display.println("Please switch to run\nmode and enable the\nclock...");
display.display();
}
delay(100);
}