system/a
A slightly-less modern computing system.

Table of Contents

An image showing the system/a logo with the text 'general computing platform' to the right and a core dump of a system/a program below it.

Introduction

This was, in essence, the thing that made me realize the beauty of computer science. Yes, it looks incredibly ugly and doesn't work right now, but when it did work it was beyond beautiful. Why? Because in learning how a basic memory-to-memory virtual machine was implemented I realized how elegant and simple computer science is.

Yes, there are instruction pipelines, yes, there are other complex things going on, but even with the added complexity the simplicity and beauty of the interfaces stood out. With a continuous block of memory and simple rules you could simulate anything and it was interesting to experience and discover how programming languages worked as well.

You can see the fancy manual I made for it here.

Please note that all of this code is terrible and possibly non-functional. I have it here simply because I have fond memories about it.

Source Code

main.cpp

#include <iostream>
#include <string>
#include "assembler.h"
#include "systema.h"
#include "instructions.h"

/*
  system/a

  this is the program that contains the code for simulating a
  system/a computer and includes a translator for
  output, a complete system/a instruction set and a full
  assembler for converting ASM to machine code.
*/

int main()
{
  assembleFile("test.saa", "test.sam");
  runSystemAProgram("test.sam");
  std::cin.get();
  return 0;
}

systema.h

#pragma once

#include <string>
#include <iostream>
#include <fstream>
#include <sstream>
#include <cmath>
#include "instructions.h"

void readFileIntoMemory(const std::string inputFile, bool(&memory)[256][32]);
void coreDumpToConsole(bool memory[256][32], int programCounter);
void processCycle(int &programCounter, bool(inputBuffer)[32], bool(&outputBuffer)[32], bool(&memory)[256][32]);
int toAddr(const int addrPos, const int programCounter, bool memory[256][32]);
int addrToDec(const int addr, bool memory[256][32]);

void readFileIntoMemory(const std::string inputFile, bool(&memory)[256][32])
{
  const int ASCIIOffset = 48;
  int lineNum = 0;
  std::ifstream machineCode(inputFile);
  std::string line;

  for (int lineNum= 0; lineNum < 256; lineNum++)
    {
      std::getline(machineCode, line);
      for (int CharcterNum = 0; CharcterNum < line.length(); CharcterNum++)
        {
          int numChar = line[CharcterNum];
          numChar = numChar - ASCIIOffset;
          bool slot = numChar;
          memory [lineNum][CharcterNum] = slot;
        }
    }
}


//this prints the state of the machine to the console
void coreDumpToConsole(bool memory[256][32], int programCounter)
{
  std::cout << "CURRENT INSTRUCTION ADDR :" << programCounter << std::endl;
  std::cout << "CORE DUMP OUTPUT : " << std::endl;
  for (int rowNum = 0; rowNum < 16; rowNum++)
    {
      for (int columnNum = 0; columnNum < 32; columnNum++)
        {
          int slot = memory[rowNum][columnNum];
          std::cout << slot;
        }
      std::cout << std::endl;
    }
}


void printOutBuf(bool outputBuffer[32])
{
  long long value = 0;
  long long positionVal = 0;
  for (int bit = 31; bit > 0; bit--)
    {
      value += outputBuffer[bit] ? pow(2, positionVal) : 0;
      positionVal++;
    }
  std::cout << value;
}

void runSystemAProgram(const std::string inputFile)
{
  bool inputBuffer [32];
  bool outputBuffer [32];
  bool memory [256][32];

  bool STP = false;
  int programCounter = 0;

  readFileIntoMemory(inputFile, memory);

  while (STP == false)
    {
      processCycle(programCounter, inputBuffer, outputBuffer, memory);
      if (programCounter < 0)
        STP = true;
      coreDumpToConsole(memory, programCounter);        std::cin.get();
    }
  printOutBuf(outputBuffer);
}


// this simulates a operating cycle of system/a 
void processCycle(int &programCounter, bool (inputBuffer)[32], bool (&outputBuffer)[32], bool (&memory)[256][32])
{
  const bool iP0 = memory[programCounter][4];
  const bool iP1 = memory[programCounter][5];
  const bool iP2 = memory[programCounter][6];
  const bool iP3 = memory[programCounter][7];

  const int argAddr1 = toAddr(0, programCounter, memory);
  const int argAddr2 = toAddr(1, programCounter, memory);
  const int tgtAddr = toAddr(2, programCounter, memory);

  std::cout << "ProgCount : " << programCounter << " AA1 : " << argAddr1 << " AA2 : " << argAddr2 << " TGA : " << tgtAddr << " iP0 : " << iP0 << " iP1 : " << iP1 << " iP2 : " << iP2 << " iP3 : " << iP3 << std::endl;

  // FLP 00001101
  if (iP0 && iP1 && !iP2 && iP3)
    FLP(argAddr1, programCounter, memory);

  // ADD 00000010
  else if (!iP0 && !iP1 && iP2 && !iP3)
    ADD(argAddr1, argAddr2, tgtAddr, programCounter, memory);

  // JMP 00001010
  else if (iP0 && !iP1 && iP2 && !iP3)
    JMP(tgtAddr, programCounter, memory);

  // JIE 00000101
  else if (!iP0 && iP1 && !iP2 && iP3)
    JIE(argAddr1, argAddr2, tgtAddr, programCounter, memory);

  // WRT 00000011
  else if (!iP0 && !iP1 && iP2 && iP3)
    WRT(tgtAddr, outputBuffer, programCounter, memory);

  // RED 00001100
  else if (iP0 && iP1 && !iP2 && !iP3)
    RED(tgtAddr, inputBuffer, programCounter, memory);

  // MOV 00000100 -> this moves the 
  else if (!iP0 && iP1 && !iP2 && !iP3)
    MOV(argAddr1, tgtAddr, programCounter, memory);

  // STP 00001111 -> this stops the machine
  else if (iP0 && iP1 && iP2 && iP3) {
    STP(programCounter);
  }

  // if given illegal opcode the system ignores it and increments the program counter
  else {
    programCounter++;
  }
}


int toAddr(const int addrPos, const int programCounter, bool memory[256][32])
{
  const int addrPosF = (addrPos * 8) + 8;

  const int p0 = (memory[programCounter][addrPosF + 0]) ? 128 : 0;
  const int p1 = (memory[programCounter][addrPosF + 1]) ? 64 : 0;
  const int p2 = (memory[programCounter][addrPosF + 2]) ? 32 : 0;
  const int p3 = (memory[programCounter][addrPosF + 3]) ? 16 : 0;

  const int p4 = (memory[programCounter][addrPosF + 4]) ? 8 : 0;
  const int p5 = (memory[programCounter][addrPosF + 5]) ? 4 : 0;
  const int p6 = (memory[programCounter][addrPosF + 6]) ? 2 : 0;
  const int p7 = (memory[programCounter][addrPosF + 7]) ? 1 : 0;

  return p0 + p1 + p2 + p3 + p4 + p5 + p6 + p7;
}


int addrToDec(const int addr, bool memory[256][32])
{
  long long value = 0;
  for (int bit = 31; bit > 0; bit--)
    {
      value += memory[addr][bit] ? pow(2, bit) : 0;
    }
  return value;
}

assembler.h

#pragma once

#include <string>
#include <fstream>
#include <sstream>
#include <bitset>
#include <map>



int assembleFile(const std::string inputFile, const std::string outputFile);
std::string assembleLine(std::string asmLine);
int hexToDec(const std::string hexNum);


// this assembles a system/a assembly file and converts it into a system/a machine code file
int assembleFile(const std::string inputFile, const std::string outputFile)
{
        std::string machineLine, machineCode;

        std::ifstream assembly(inputFile);
        for (std::string line; std::getline(assembly, line);) {
                machineLine = assembleLine(line);
                machineCode += (machineLine + "\n");
        }
        std::ofstream machineCodeFile;
        machineCodeFile.open(outputFile);
        machineCodeFile << machineCode;
        machineCodeFile.close();
        return 0;
}


// this reads lines of fixed-width assembly and converts them into binary machine code
std::string assembleLine(std::string asmLine)
{
        int position = 0;
        std::stringstream asmStream(asmLine);
        std::string token;
        std::string machineLine;
        bool NUMMarker = false;

        std::map<const std::string, const std::string> instructions = {
                { "FLP", "00001101" },{ "ADD", "00000010" },{ "JMP", "000010100000000000000000" },{ "JIE" , "00000101" },
                { "WRT" , "000000110000000000000000" },{ "RED" , "000011000000000000000000" },
                { "MOV", "00000100" },{ "STP", "00001111000000000000000000000000" } };

        while (getline(asmStream, token, ' ') && (machineLine.length() < 32) && (token != ";"))
        {

                if (NUMMarker == true) {
                        std::bitset<32> Binary(hexToDec(token));
                        machineLine += Binary.to_string();
                }

                else if (instructions.find(token) != instructions.end())
                        machineLine += instructions.find(token)->second;

                else if (token == "NUM")
                        NUMMarker = true;

                else if (token != "") {
                        std::bitset<8> Binary(hexToDec(token));
                        machineLine += Binary.to_string();
                }

        }

        while (machineLine.length() < 32)
        {
                machineLine += "00000000";
        }
        return machineLine;
} 


// this converts intigers to decimal values
int hexToDec(const std::string hexNum)
{
        std::stringstream numStream;
        numStream << std::hex << hexNum;
        int decimalNum;
        numStream >> decimalNum;
        return decimalNum;
}

instructions.h

once


// this flips the bits of a memory address
void FLP(int argAddr1, int &programCounter, bool (&memory)[256][32])
{
  for (int bit = 0; bit < 32; bit++)
    {
      memory[argAddr1][bit] = !memory[argAddr1][bit];
    }
  programCounter++;
}


// this adds the number in argAddr1 to argAddr2 and places it in the tgtAddr
void ADD(int argAddr1, int argAddr2, int tgtAddr, int &programCounter, bool(&memory)[256][32])
{
  bool carry = false;
  bool contArgAddr1;
  bool contArgAddr2;


  for (int bit = 31; bit > 0; bit--)
    {
      contArgAddr1 = memory[argAddr1][bit];
      contArgAddr2 = memory[argAddr2][bit];

      if (contArgAddr1 && contArgAddr2 && carry)
        {
          memory[tgtAddr][bit] = 1;
          carry = 1;
        }
      else if (((contArgAddr1 && contArgAddr2) && !carry) || ((contArgAddr1 && carry) && !contArgAddr2) || ((contArgAddr2 && carry) && !contArgAddr1))
        {
          memory[tgtAddr][bit] = 0;
          carry = 1;
        }
      else if ((contArgAddr1 && !contArgAddr2 && !carry) || (!contArgAddr1 && contArgAddr2 && !carry) || (!contArgAddr1 && !contArgAddr2 && carry))
        {
          memory[tgtAddr][bit] = 1;
          carry = 0;
        }
      else
        {
          memory[tgtAddr][bit] = 0;
          carry = 0;
        }
    }
  programCounter++;
}


// this unconditionally sets the program counter to the target address
void JMP(int tgtAddr, int &programCounter, bool(&memory)[256][32])
{
  programCounter = tgtAddr;
}


// this sets the program counter to the target address if argAddr1 and argAddr2 is equal
void JIE(int argAddr1, int argAddr2, int tgtAddr, int &programCounter, bool(&memory)[256][32])
{
  bool equal = true;
  int bit = 0;
  while ((bit < 32) && equal)
    {
      if (memory[argAddr1][bit] != memory[argAddr2][bit])
        equal = false;
      bit++;
    }
  if (equal)
    programCounter = tgtAddr;
  else
    programCounter++;
}


// this writes the contents of tgtAddr to the output buffer
void WRT(int tgtAddr, bool (&outputBuffer)[32], int &programCounter, bool(&memory)[256][32])
{
  for (int bit = 0; bit < 32; bit++)
    {
      outputBuffer[bit] = memory[tgtAddr] [bit];
    }
  programCounter++;
}


// this writes the contents of the input buffer to tgtAddr
void RED(int tgtAddr, bool (inputBuffer)[32], int &programCounter, bool(&memory)[256][32])
{
  for (int bit = 0; bit < 32; bit++)
    {
      memory[tgtAddr][bit] = inputBuffer[bit];
      int bitVal = memory[tgtAddr][bit];
      memory[tgtAddr][bit] = 0;
    }
  programCounter++;
}


// this copies the value contained in argAddr1 to tgtAddr
void MOV(int argAddr1, int tgtAddr, int &programCounter, bool(&memory)[256][32])
{
  for (int bit = 0; bit < 32; bit++)
    {
      memory[tgtAddr][bit] = memory[argAddr1][bit];
    }
  programCounter++;
}


// this sets the program counter to an illegal value, causing the computer to stop and return the STP code
void STP(int &programCounter)
{
  programCounter = -1;
}

Last Modified: 2021-W52-2 00:21

Generated Using: Emacs 27.2 (Org mode 9.4.6)

Except where otherwise noted content on cons.dev is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License.