8-Bit Computer Arduino Programmer

8-bit computer in an FPGA Arduino controller and programmer
Share:

About

This post is about the Arduino programmer I made for the 8-Bit Computer in an FPGA. I will show how to use it, connect it, and briefly explain how it works. If you are interested in the source code you can get it all here on Github. This programmer should also be compatible with a normal breadboard computer like the one Ben Eater made in his video series.

Simple Version

This is the first version of the programmer I made. It’s made from an Arduino nano. The limitation of this design is that you can only have a 4 bit memory address and 8 bit values(otherwise you run out of pins on the nano). The Arduino also has to be reprogrammed with every change to the machine code. 

The two extra circuit boards are logic level converters. The FPGA I used has 3.3V IOs meanwhile the Arduino IO is 5V. If you built a breadboard computer with CMOS chips you can connect them straight up to the Arduino.

Connections to a breadboard computer:

In the image below, you can see how you would connect the programmer to the 8-bit breadboard computer Ben Eater made.

Arduino code:

void setup() {
  //Set pins.
  pinMode(0, OUTPUT);
  pinMode(1, OUTPUT);
  pinMode(2, OUTPUT);
  pinMode(3, OUTPUT);
  pinMode(4, OUTPUT);
  pinMode(5, OUTPUT);
  pinMode(6, OUTPUT);
  pinMode(7, OUTPUT);
  pinMode(8, OUTPUT);
  pinMode(9, OUTPUT);
  pinMode(10, OUTPUT);
  pinMode(11, OUTPUT);
  pinMode(12, OUTPUT);
  pinMode(13, OUTPUT);

  //Array containing your machine code.
  int dataArray [16][12]{
        //LSB                 MSB     MEM. ADDR.          
        { 1, 0, 0, 0,  0, 1, 1, 1,    0, 0, 0, 0},  //1
        { 0, 1, 0, 0,  1, 1, 1, 1,    1, 0, 0, 0},  //2
        { 1, 1, 0, 0,  0, 0, 0, 0,    0, 1, 0, 0},  //3
        { 0, 0, 1, 0,  0, 0, 0, 0,    1, 1, 0, 0},  //4
        { 0, 0, 0, 0,  0, 0, 0, 0,    0, 0, 1, 0},  //5
        { 0, 0, 0, 0,  0, 0, 0, 0,    1, 0, 1, 0},  //6
        { 0, 0, 0, 0,  0, 0, 0, 0,    0, 1, 1, 0},  //7
        { 0, 0, 0, 0,  0, 0, 0, 0,    1, 1, 1, 0},  //8
        { 0, 0, 0, 0,  0, 0, 0, 0,    0, 0, 0, 1},  //9
        { 0, 0, 0, 0,  0, 0, 0, 0,    1, 0, 0, 1},  //10
        { 0, 0, 0, 0,  0, 0, 0, 0,    0, 1, 0, 1},  //11
        { 0, 0, 0, 0,  0, 0, 0, 0,    1, 1, 0, 1},  //12
        { 0, 0, 0, 0,  0, 0, 0, 0,    0, 0, 1, 1},  //13
        { 0, 0, 0, 0,  0, 0, 0, 0,    1, 0, 1, 1},  //14
        { 0, 0, 1, 0,  0, 0, 0, 0,    0, 1, 1, 1},  //15
        { 0, 1, 0, 0,  0, 0, 0, 0,    1, 1, 1, 1},  //16
  };

  delay(1000);
  writeToRAM(dataArray);
  resetLines();
}

void loop() {

}

static void resetLines(){
  //Bus
  digitalWrite(0, LOW);
  digitalWrite(1, LOW);
  digitalWrite(2, LOW);
  digitalWrite(3, LOW);
  digitalWrite(4, LOW);
  digitalWrite(5, LOW);
  digitalWrite(6, LOW);
  digitalWrite(7, LOW);

  //RAM Adress
  digitalWrite(8, LOW);
  digitalWrite(9, LOW);
  digitalWrite(10, LOW);
  digitalWrite(11, LOW);

  //CLK
  digitalWrite(12, LOW);

  //RAM RE
  digitalWrite(13, LOW);
}

static void writeToRAM(int dataArray [16][12] ){
  //Iterate through the machine code array and toggle the appropriate pins.
  for(int i = 0; i < 16; i++){
      //Bus
      digitalWrite(0, getState(dataArray[i][0]));
      digitalWrite(1, getState(dataArray[i][1]));
      digitalWrite(2, getState(dataArray[i][2]));
      digitalWrite(3, getState(dataArray[i][3]));
      digitalWrite(4, getState(dataArray[i][4]));
      digitalWrite(5, getState(dataArray[i][5]));
      digitalWrite(6, getState(dataArray[i][6]));
      digitalWrite(7, getState(dataArray[i][7]));
    
      //RAM Adress
      digitalWrite(8, getState(dataArray[i][8]));
      digitalWrite(9, getState(dataArray[i][9]));
      digitalWrite(10, getState(dataArray[i][10]));
      digitalWrite(11, getState(dataArray[i][11]));


      //RAM RE
      digitalWrite(13, HIGH);
      delay(10);

      //Delay clock so the data can get to the destination before the rising edge of the clock.
      //delay(100);
      
      //CLK
      digitalWrite(12, HIGH);
      delay(100);
      digitalWrite(12, LOW);
      delay(10);
      
      //RAM RE
      digitalWrite(13, LOW);

      //Delay before next program cycle.
      delay(100);
  }
}

static bool getState(int bitValue){
    if(bitValue == 0){
      return false;
    }else{
      return true;
    }
}

From the Arduino sketch you can see that pins 0-7 are data lines, 8-11 are memory address lines, 12 is RAM read enable and 13 is the clock.

To program you computer you first need to write your program in machine code. Then you enter your machine code into the dataArray. The first 8 numbers are the memory content while the last 4 are the memory address. After you do that you have to flash the sketch to the Arduino.

Finally, you can just use the reset button on the Arduino to initiate the programming process as when the Arduino resets the program will execute.

After the computer has been programmed the Arduino programmer has to be disconnected or turned off as otherwise it will pull down the data lines. This can also be avoided if you connect the Arduino to your RAM through OR gates(this is what I did with my computer).

Programmer with an "IDE"

To make programming the 8-bit computer even easier I decided to make a simple sort of an IDE that can connect to the Arduino programmer and program the computer through it without having to reflash it with every change of the program.

The IDE has three code editors. The last window has the assembly definition where you map your custom machine code for the specific computer you made to your specific custom assembly language you made. Then you can write an assembly program in the first code editor. The machine code will automatically get generated in the middle editor. 

On the left, you have the controls column. At the very top, you can establish a COM port connection to the Arduino. 

Then you have the option to set your RAM size, RAM word length, and the memory address length. Currently, this is fixed and can’t be changed. I didn’t really need the option for a bigger memory so I skipped implementing this feature(might add it in the future). 

I won’t post the code for the IDE here as it’s too long. You can see and download the whole project from Github. For the explanation of the code read the comments.
I used an Arduino Mega because it has more IO pins than the nano. This enables future expansions for RAM size and it enables the use of control signals to start/stop/reset the computer from the IDE.
The hardware connections to the breadboard computer would be almost the same as they were in the image above with the simple version of the programmer. The exception would be that you would have to connect the control signals to your clock start/stop/step buttons, reset button. For setting the clock speed using the programmer you would however have to slightly redesign your clock module.

Arduino Code:

const byte numChars = 27;
char receivedChars[numChars]; 
int memData[16][12];
byte memCounter = 0;
boolean newData = false;
boolean dataStart = false;

//Pin assignments///////////////
const byte strtClkPin = 36;
const byte stopClkPin = 37;
const byte rsetPin = 2; 
const byte progEnPin = 3;
const byte clkStpPin = 4;
const byte clkSetPin1 = 5;
const byte clkSetPin2 = 6;
const byte clkSetPin3 = 7;
const byte clkSetPin4 = 8;
////////////////////////////////

void setup() {
  setPinModes();
  Serial.begin(115200);
  
  digitalWrite(progEnPin, LOW);
  stopClock();
  resetDevice();
  digitalWrite(clkSetPin1, LOW);
  digitalWrite(clkSetPin2, LOW);
  digitalWrite(clkSetPin3, LOW);
  digitalWrite(clkSetPin4, LOW);
}

void loop() {
    rxData(); 
    rxMemDataEnd();
}

void rxData() {
    static byte count = 0;
    char ch;
    
  //Check if serial data is available.
    while(Serial.available() > 0 && newData == false) {
        //Read and remove character from the serial buffer.
        ch = Serial.read();

        //Check if it's the start of the data.
        if(ch == '_'){
          dataStart = true;
        }

        if(dataStart && ch != '_'){   
          if(ch != '\n'){
              receivedChars[count] = ch;
              count++;
              if(count >= numChars){
                  count = numChars - 1;
              }
          }
          else{
              //Terminate string.
              receivedChars[count] = '\0';

              char command[5];
              
              getCommand(command);
              
              if(strcmp(command, "prog") == 0){
                getMemData(); 
              }else if(strcmp(command, "strt") == 0){
                startClock();
                Serial.print("strt done");
              }else if(strcmp(command, "stop") == 0){
                stopClock();
                Serial.print("stop done");
              }else if(strcmp(command, "step") == 0){
                clockStep();
                Serial.print("step done");
              }else if(strcmp(command, "rset") == 0){
                resetDevice();
                Serial.print("rset done");
              }else if(strcmp(command, "clks") == 0){           
                setClockSpeed();
                Serial.print("clks done");
              }else if(strcmp(command, "prst") == 0){
                programmerReset();
                Serial.print("prst done");
              }else{
                Serial.print("Bad command!");
              }
              
              count = 0;     
              newData = true;
              dataStart = false;              
            }
       }
    }   
}

static void programmerReset(){
  //Reset variables.
  memCounter = 0;
  newData = false;
  dataStart = false;

  //Empty serial buffer.
  while(Serial.available() > 0){
    Serial.read();
  }
}

//Currently not used.
static void getInfo(int *memInfo){
  char memAddressLengthStr[3];
  char memLocationLengthStr[3];
  char memLocationCountStr[6];

  for(int i = 4; i <= 12; i++){
    if(i <= 5){
      memAddressLengthStr[i-4] = receivedChars[i];
      if(i-4 == 1){
        memAddressLengthStr[i-3] = '\0';
      }
    }else if(i <= 7){
      memLocationLengthStr[i-6] =  receivedChars[i];
      if(i-6 == 1){
        memLocationLengthStr[i-5] = '\0';
      }
    }else if(i <= 12){
      memLocationCountStr[i-8] =  receivedChars[i];

      if(i-11 == 1){
        memLocationCountStr[i-7] = '\0';
      }
    }
  }

  //Convert char to 
  memInfo[0] = atoi(memAddressLengthStr);
  memInfo[1] = atoi(memLocationLengthStr);
  memInfo[2] = atoi(memLocationCountStr);

}

static void getClockSpeed(int *clkSpeedValues){
  for(int i = 4; i <= 7; i++){
    clkSpeedValues[i-4] = (receivedChars[i] - '0');
  } 
}

static void setClockSpeed(){
  int clkSpeedValues[4];
  
  getClockSpeed(clkSpeedValues);
  
  digitalWrite(clkSetPin1, getState(clkSpeedValues[0]));
  digitalWrite(clkSetPin2, getState(clkSpeedValues[1]));
  digitalWrite(clkSetPin3, getState(clkSpeedValues[2]));
  digitalWrite(clkSetPin4, getState(clkSpeedValues[3]));
}

static void setPinModes(){
  //Data pins///////////////
  pinMode(22, OUTPUT); //LSB
  pinMode(23, OUTPUT);
  pinMode(24, OUTPUT);
  pinMode(25, OUTPUT);
  
  pinMode(26, OUTPUT);
  pinMode(27, OUTPUT);
  pinMode(28, OUTPUT);
  pinMode(29, OUTPUT); //MSB
  //////////////////////////

  //Mem. Add. Pins/////////
  pinMode(30, OUTPUT);
  pinMode(31, OUTPUT);
  pinMode(32, OUTPUT);
  pinMode(33, OUTPUT);
  /////////////////////////

  //Clk
  pinMode(34, OUTPUT);
  //RAM read enable
  pinMode(35, OUTPUT);

  //Start
  pinMode(strtClkPin, OUTPUT);
  //Stop  
  pinMode(stopClkPin, OUTPUT);
  
  //Clear
  pinMode(rsetPin, OUTPUT);
  //Program enable
  pinMode(progEnPin, OUTPUT);
  //Step
  pinMode(clkStpPin, OUTPUT);

  //Clk speed selection//
  pinMode(clkSetPin1, OUTPUT);
  pinMode(clkSetPin2, OUTPUT);
  pinMode(clkSetPin3, OUTPUT);
  pinMode(clkSetPin4, OUTPUT);
  ///////////////////////
}

static void getMemData(){
  for(int i = 0; i <= 11 ; i++){
    memData[memCounter][i] = (receivedChars[i+13] - '0');
  }
                
  //Next mem. position.
  memCounter++;
}

static void getCommand(char *command){
  for(int i = 0; i <= 3; i++){
    command[i] = receivedChars[i];
  }

  //Terminate string.
  command[4] = '\0';
}

static void rxMemDataEnd(){
    //If new data is available.
    if(newData == true){
      //Check if data transfer is over.
      if(memCounter == 16){
        writeToRAM();
        
        //Reset.
        memCounter = 0;

        Serial.print("prog done");
      }else{
        //Signal that the data was read.
        Serial.print("ok");
      }

      //Reset.
      newData = false;
      delay(10);
    }
}

static void clockStep(){  
  digitalWrite(clkStpPin, HIGH);
  delay(50);
  digitalWrite(clkStpPin, LOW);
}

static void resetDevice(){
  digitalWrite(rsetPin, HIGH);
  delay(50);
  digitalWrite(rsetPin, LOW);
}

static void startClock(){
  digitalWrite(strtClkPin, HIGH);
  delay(50);
  digitalWrite(strtClkPin, LOW);
}

static void stopClock(){
  digitalWrite(stopClkPin, HIGH);
  delay(50);
  digitalWrite(stopClkPin, LOW);
}

static void writeToRAM(){
  stopClock();
  resetDevice();
  digitalWrite(progEnPin, HIGH);
  delay(10);
  
  //Iterate through the machine code array and toggle the appropriate pins.
  for(int i = 0; i < 16; i++){
      //Bus
      digitalWrite(22, getState(memData[i][0]));
      digitalWrite(23, getState(memData[i][1]));
      digitalWrite(24, getState(memData[i][2]));
      digitalWrite(25, getState(memData[i][3]));
      digitalWrite(26, getState(memData[i][4]));
      digitalWrite(27, getState(memData[i][5]));
      digitalWrite(28, getState(memData[i][6]));
      digitalWrite(29, getState(memData[i][7]));
    
      //RAM Adress
      digitalWrite(30, getState(memData[i][8]));
      digitalWrite(31, getState(memData[i][9]));
      digitalWrite(32, getState(memData[i][10]));
      digitalWrite(33, getState(memData[i][11]));


      //RAM RE
      digitalWrite(35, HIGH);
      delay(10);

      //Delay clock so the data can get to the destination before the rising edge of the clock.
      //delay(100);
      
      //CLK
      digitalWrite(34, HIGH);
      delay(10);
      digitalWrite(34, LOW);
      delay(10);
      
      //RAM RE
      digitalWrite(35, LOW);

      //Delay before next program cycle.
      delay(10);
  }

  //PROG EN 
  digitalWrite(progEnPin, LOW);
}

static bool getState(int bitValue){
    if(bitValue == 0){
      return false;
    }else if(bitValue == 1){
      return true;
    }
}

Here I will briefly describe how the code works(for more detail see the code and the comments).

 The Arduino receives the values over the serial port from the App. First, it determines what command it has just received. After that, it will execute the appropriate action like start, stop, program, … The machine code data arrives to the Arduino in chunks(each mem. address is one chunk) and is then stored in an array. This keeps happening until the last chunk of data has been received. Finally, the processes to shift all the values from the array into the 8-bit computers RAM begins. This is done by toggling the IO pins high/low depending on the values in the array.

I know that storing the values in the Arduino isn’t the best solution. Ideally, the data should just be immediately written into the computers RAM without being buffer in an array. The reason things work the way they do is that I just upgraded the code from the simple version of the programmer. So if there is a need for it I might update the programmer and IDE in the future. 

Programming Demonstration

Share:

Leave a Reply

Your email address will not be published. Required fields are marked *

The following GDPR rules must be read and accepted:
This form collects your name, email and content so that we can keep track of the comments placed on the website. For more info check our privacy policy where you will get more info on where, how and why we store your data.

Advertisment ad adsense adlogger