system/b
A modern computing system.
Table of Contents
Introduction
Another project from my early days in programming 1 About three years ago now, wow! Amazing how time flies., a simple memory-to-memory virtual machine written in C++. I currently intend to upgrade it a bit eventually with interrupt codes, though maybe I will wait until I write a proper LISP machine with tagged memory / typed instructions. To compile it simply run GCC on systemBshell.cpp.
Example Programs
mult.sba
; this is a program that finds c for ; ; a * b = c ; JIE 05 07 04 ; 0x00 jumps to the stop instruction if the counter equals a ADD 06 254 254 ; 0x01 adds b to c and places it in c ADD 07 082 07 ; 0x02 increments the counter up by one JMP 00 ; 0x03 jumps to address 0x00 STP ; 0x04 stop instruction 3 ; 0x05t a 256 ; 0x06 b 0 ; 0x07 counter 1 ; 0x08 storage for 1
subEX.sba
; this is a program that finds c for ; ; a - b = c ; FLP 4 ; 0x00 this flips the value a ADD 4 5 4 ; 0x01 this adds one to a ADD 3 4 6 ; 0x02 this adds a to b, and places the ansewer c in address 0x6 0 ; 0x03 this stores the value a 0 ; 0x04 this stores the value of b 1 ; 0x05 this is where the one is stored for the add one operation ; 0x06 this is where the value c will go
Source Code
systemBshell.cpp
#include <iostream> #include <string> #include "ioStrings.h" #include "assembler.h" #include "SystemBvm.h" /* * This is the system/b "shell" allows the user to interact with it. Feel free to write your own if you want. * note : the shell is not complete and needs more work to add full functionality to system/b */ int main() { bool quit = false; SystemBvm1024 vm; std::string cmdInput, command, argument1, argument2; std::cout << welcomeText(); do { std::cin.clear(); std::cout << "system/b> "; std::cin >> command; if (command == "m") { std::cout << manualText(); std::cout << std::endl; } else if ((command == "assemble") || (command == "a")) { try { std::cout << "inFile> "; std::cin >> argument1; std::cout << "outFile> "; std::cin >> argument2; assemble(argument1, argument2); std::cout << "file assembled sucessfully " << std::endl; } catch (const std::invalid_argument& e) { std::cout << e.what() << std::endl; } std::cout << std::endl; } else if ((command == "boot") || (command == "b")) { try { std::cout << "inFile> "; std::cin >> argument1; vm.boot(argument1); std::cout << "boot completed " << std::endl; } catch (const std::invalid_argument& e) { std::cout << e.what() << std::endl; } } else if ((command == "immDump") || (command == "imm")) { try { vm.immDump(); std::cout << "imm dump completed " << std::endl; } catch (const std::invalid_argument& e) { std::cout << e.what() << std::endl; } } else if ((command == "immDump") || (command == "idr")) { try { vm.idrDump(); std::cout << "idr dump completed " << std::endl; } catch (const std::invalid_argument& e) { std::cout << e.what() << std::endl; } } else if ((command == "stepCycle") || (command == "sc")) { try { vm.stepCycle(); std::cout << "cycle step completed " << std::endl; } catch (const std::invalid_argument& e) { std::cout << e.what() << std::endl; } } else if ((command == "checkCounter") || (command == "cc")) { try { std::cout << "current instruction counter : " << vm.getinstructionCounter() << std::endl; } catch (const std::invalid_argument& e) { std::cout << e.what() << std::endl; } } else if ((command == "getOutput") || (command == "go")) { try { std::cout << "output address contains : " << vm.getOutput() << std::endl; } catch (const std::invalid_argument& e) { std::cout << e.what() << std::endl; } } else if ((command == "setInput") || (command == "si")) { try { std::cin >> argument1; vm.setInput(argument1); } catch (const std::invalid_argument& e) { std::cout << e.what() << std::endl; } } else if ((command == "quit") || (command == "q")) { quit = true; } else { std::cout << errorText(); } } while (!quit); std::cout << quitText(); return 0; }
SystemBvm.h
#pragma once #include <fstream> #include <iostream> #include <cmath> /* * This is the basic system\b vm with 1024 possible indirectly addressed locations in memory. The addresses FD and FE are used for input and output, repsectively */ const short WORD_SIZE = 32; const short IMM_ADDR_SPACE_SIZE = 256; const unsigned int IDR_ADDR_SPACE_SIZE = 1024; class SystemBvm1024 { private: bool immediateAddressSpace[IMM_ADDR_SPACE_SIZE][WORD_SIZE]; bool indirectAddressSpace[IDR_ADDR_SPACE_SIZE][WORD_SIZE]; short instructionCounter; short addressToShort(short start) { short address = 0; for (int i = 8; i > 0; i--) { if (immediateAddressSpace[instructionCounter][i + start - 1]) { address += pow(2,(8 - i)); } } return address; } unsigned int addrToInt(short addr) { unsigned int address = 0; for (int i = WORD_SIZE; i > 0; i--) { if (immediateAddressSpace[instructionCounter][i - 1]) { address += pow(2, (8 - i)); } } return address; } /* instruction : JIE binary representatioin : 0000 1010 usage : JIE [argument address] [argument address] [argument address] jumps to the third argument address if the value of the first argument address equals the second argumet address */ void JIE() { bool addrEquality = true; const short argAddr0 = addressToShort(8); const short argAddr1 = addressToShort(16); const short argAddr2 = addressToShort(24); for (int i = 0; i < WORD_SIZE; i++) { if (immediateAddressSpace[argAddr0][i] != immediateAddressSpace[argAddr1][i]) { addrEquality = false; } } if(addrEquality) { instructionCounter = argAddr2; } else { instructionCounter++; } } /* instruction : JMP binary representatioin : 0000 0101 usage : JMP [argument address] unconditionally jumps to the argument address provided */ void JMP() { instructionCounter = addressToShort(8); } /* instruction : MOV binary representatioin : 0000 1100 usage : MOV [argument address] [argument address] copies the value stored in the first argument address to the second argument address unconditionally */ void MOV() { const short argAddr0 = addressToShort(8); const short argAddr1 = addressToShort(16); for (int i = 0; i < WORD_SIZE; i++) { immediateAddressSpace[argAddr1][i] = immediateAddressSpace[argAddr0][i]; } instructionCounter++; } /* instruction : ADD binary representatioin : 0000 1011 usage : ADD [argument address] [argument address] [argument address] adds the contents of the first argument address to the second argument address and then places the result in the third argument address */ void ADD() { const short argAddr0 = addressToShort(8); const short argAddr1 = addressToShort(16); const short argAddr2 = addressToShort(24); bool carry = false, bit0 = false, bit1 = false; for (int i = WORD_SIZE; i >= 0; i--) { bit0 = immediateAddressSpace[argAddr0][i]; bit1 = immediateAddressSpace[argAddr1][i]; if ((bit0 + bit1 == 1) && (carry == 0)) { immediateAddressSpace[argAddr2][i] = 1; std::cout << "1"; } else if ((bit0 + bit1 == 0) && (carry == 1)) { immediateAddressSpace[argAddr2][i] = 1; carry = 0; std::cout << "2"; } else if (bit0 + bit1 + carry == 0) { immediateAddressSpace[argAddr2][i] = 0; carry = 0; std::cout << "3"; } else if (bit0 + bit1 + carry == 2) { immediateAddressSpace[argAddr2][i] = 0; carry = 1; std::cout << "4"; } else if (bit0 + bit1 + carry == 3) { immediateAddressSpace[argAddr2][i] = 1; carry = 1; std::cout << "5"; } } std::cout << std::endl; std::cout << argAddr0 << "::" << argAddr1 << "::" << argAddr2 << std::endl; instructionCounter++; } /* instruction : FLP binary representatioin : 0000 0100 usage : FLP [argument address] flips the bits in the argument address provided */ void FLP() { const short argAddr0 = addressToShort(8); for (int i = 0; i < WORD_SIZE; i++) { immediateAddressSpace[argAddr0][i] = !immediateAddressSpace[argAddr0][i]; } instructionCounter++; } /* instruction : RED binary representatioin : 0000 1101 usage : RED [argument address] [argument address] special instruction, reads the value located in the directly addressed space to the second argument address, the first argument address provides the pointer to the location of the data in the directly addressed space */ void RED() { const short argAddr0 = addressToShort(8); const short argAddr1 = addressToShort(16); const unsigned int idrAddr = addrToInt(argAddr0); for (int i = 0; i < WORD_SIZE; i++) { immediateAddressSpace[argAddr1][i] = indirectAddressSpace[idrAddr][i]; } instructionCounter++; } /* instruction : WRT binary representatioin : 0000 0010 usage : WRT [argument address] [argument address] special instruction, writes the value located in the second argument address to an address located in the directly addressed space, the first argument address provides the pointer to the location of the data in the directly addresssed space */ void WRT() { const short argAddr0 = addressToShort(8); const short argAddr1 = addressToShort(16); const unsigned int idrAddr = addrToInt(argAddr0); for (int i = 0; i < WORD_SIZE; i++) { indirectAddressSpace[idrAddr][i] = immediateAddressSpace[argAddr1][i]; } instructionCounter++; } /* instruction : STP binary representatioin : 0000 1111 usage : STP Stops the execution of code by system/b */ void STP() { } public: void stepCycle() { bool i0, i1, i2, i3; i0 = immediateAddressSpace[instructionCounter][4]; i1 = immediateAddressSpace[instructionCounter][5]; i2 = immediateAddressSpace[instructionCounter][6]; i3 = immediateAddressSpace[instructionCounter][7]; //JIE if (i0 && !i1 && i2 && !i3) { JIE(); } //JMP else if (!i0 && i1 && !i2 && i3) { JMP(); } //MOV else if (i0 && i1 && !i2 && !i3) { MOV(); } //ADD else if (i0 && !i1 && i2 && i3) { ADD(); } //FLP else if (!i0 && i1 && !i2 && !i3) { FLP(); } //RED else if (i0 && i1 && !i2 && i3) { RED(); } //WRT else if (!i0 && !i1 && i2 && !i3) { WRT(); } //STP else if (i0 && i1 && i2 && i3) { STP(); } else { instructionCounter++; } } void writeInput(bool input[WORD_SIZE]) { for (int i = 0; i < WORD_SIZE; i++) { input[i] = immediateAddressSpace[254][i]; } } std::string getOutput() { std::string output = ""; for (int i = 0; i < WORD_SIZE; i++) { if (immediateAddressSpace[254][i]) { output += "1"; } else { output += "0"; } } return output; } void setInput(std::string input) { for (int i = 0; i < WORD_SIZE; i++) { if (input[i] == '1') { immediateAddressSpace[253][i] = 1; } else { immediateAddressSpace[253][i] = 0; } } } short getinstructionCounter() { return instructionCounter; } /* * this is a dummy bootloader designed to clear the immediately addressable space and load the * relevant system/b machine code file into the immediately addressable file space */ void boot(std::string filename) { std::ifstream inFile(filename); char bit; instructionCounter = 0; for (int i = 0; i < IMM_ADDR_SPACE_SIZE; i++) { for (int j = 0; j < WORD_SIZE; j++) { immediateAddressSpace[i][j] = 0; } } for (int i = 0; i < IMM_ADDR_SPACE_SIZE; i++) { for (int j = 0; j < WORD_SIZE; j++) { inFile >> bit; if (inFile) { if (bit == '1') { immediateAddressSpace[i][j] = 1; } else if (bit == '0') { immediateAddressSpace[i][j] = 0; } } else { immediateAddressSpace[i][j] = 0; } } } instructionCounter = 0; } void immDump() { std::ofstream otFile("immDump.txt"); otFile << "current instruction counter : " << instructionCounter << std::endl; otFile << "Immediate Address Space State :" << std::endl; for (int i = 0; i < IMM_ADDR_SPACE_SIZE; i++) { for (int j = 0; j < WORD_SIZE; j++) { otFile << immediateAddressSpace[i][j]; } otFile << " " << i << std::endl; } otFile.close(); } void idrDump() { std::ofstream otFile("idrDump.txt"); otFile << "current instruction counter : " << instructionCounter << std::endl; otFile << "indirect address space state : " << std::endl; for (int i = 0; i < IDR_ADDR_SPACE_SIZE; i++) { for (int j = 0; j < WORD_SIZE; j++) { otFile << indirectAddressSpace[i][j]; } otFile << " " << i << std::endl; } otFile.close(); } };
ioStrings.h
This is a file that I used to store functions that would return strings. Honestly I probably should have just had a file of constant functions, but these were the old days when I didn't know better and wanted everything to be "functional" regardless of how good an idea that was. 2 Be glad I have spared you from the worst of it where I wrote several C++ programs with only recursion and constants, no loops or mutable variables. I probably gave my algorithms teacher nightmares. As a consequence I did things a bit weirdly.
#pragma once #include <string> std::string welcomeText() { return " __ __ __ \n" " _______ _______/ /____ ____ ___ _/_/ / /_ \n" " / ___/ / / / ___/ __/ _ \\/ __ `__ \\ _/_/ / __ \\\n" " (__ ) /_/ (__ ) /_/ __/ / / / / / _/_/ / /_/ /\n" "/____/\\__, /____/\\__/\\___/_/ /_/ /_/ /_/ /_.___/ \n" " /____/ \n" "a simple virtual machine\n" "\n" "\n"; } std::string errorText() { return "Invalid input, could not parse.\n" "Abort, Retry, Fail?\n" "\n"; } std::string manualText() { return "system/b manual : \n" "\n" "system/b is an evolution on the earlier system/a virtual machine archictecture using\n" "a similar 32 bit word size and word addressed memory, but now with direct addressing\n" "allowing for a signifigant expansion in the size of programs that can be run by\n" "system/b from 1024 bytes to up to 17179869184 bytes of code. This is divided between\n" "the 1024 bytes of immediately addressed space and 17179869184 bytes of directly\n" "addressed space. Direct addressing is conducted using the RED and WRT instructions. An\n" "explination of the instructions used by system/b and their use is contained in the\n" "\"instruction setsection\" of the system/b manual.\n" "\n" "sections :\n" "0 basic operation \\ shell commands\n" "1 instruction set\n" "2 debugging code\n" "3 system implmentation\n" "\n" "\n" "\n" "0 basic operation \\ shell commands :\n" "command, alias | arguments | description \n" "assemble, a | input_file(sba), output_file(sbm) | assembles the input file\n" "manual, m | null | displays this manual\n" "quit, q | null | quits the shell\n" "boot, b | input_file(sbm) | boots the systm/b vm with\n" " | | the code in the immediately\n" " | | addressed space.\n" "stepCycle, sc | null | steps the system/b vm one cycle\n" "immDump, imm | null | dumps the immediately addressed\n" " | | space to the file \"immDump.txt\"\n" "idrDump, idr | null | dumps the indirectly addressed\n" " | | space to the file \"idrDump.txt\"\n" "\n" "\n" "\n" "1 instruction set :\n" "The system/b instruction set is a fixed width instructon set containing eight\n" "instructions, MOV, ADD, FLP, JIE, JMP, RED, WRT and STP. All instructions except the\n" "special instructions RED and WRT only access data from the immediately addressed space.\n" "\n" "\n" "standard instruction format :\n" "[instruction] [argument address] [argument address] [argument address]\n" " JIE AB 10 23 \n" " 0000 1010 1010 1011 0001 0000 0010 0011 \n" "\n" "\n" "instruction : JIE\n" "binary representatioin : 0000 1010\n" "usage : \n" "JIE [argument address] [argument address] [argument address]\n" "jumps to the third argument address if the value of the first argument\n" "address equals the second argumet address\n" "\n" "\n" "instruction : JMP\n" "binary representatioin : 0000 0101\n" "usage : \n" "JMP [argument address]\n" "unconditionally jumps to the argument address provided\n" "\n" "\n" "instruction : MOV\n" "binary representatioin : 0000 1100\n" "usage : \n" "MOV [argument address] [argument address]\n" "copies the value stored in the first argument address to the second argument\n" "address unconditionally\n" "\n" "\n" "instruction : ADD\n" "binary representatioin : 0000 1011\n" "usage : \n" "ADD [argument address] [argument address] [argument address]\n" "adds the contents of the first argument address to the second argument\n" "address and then places the result in the third argument address\n" "\n" "\n" "instruction : FLP\n" "binary representatioin : 0000 0100\n" "usage : \n" "FLP [argument address]\n" "flips the bits in the argument address provided\n" "\n" "\n" "instruction : RED\n" "binary representatioin : 0000 1101\n" "usage : \n" "RED [argument address] [argument address]\n" "special instruction, reads the value located in the directly addressed\n" "space to the second argument address, the first argument address provides\n" "the pointer to the location of the data in the directly addressed space\n" "\n" "\n" "instruction : WRT\n" "binary representatioin : 0000 0010\n" "usage : \n" "WRT [argument address] [argument address]\n" "special instruction, writes the value located in the second argument\n" "address to an address located in the directly addressed space, the first\n" "argument address provides the pointer to the location of the data in the\n" "directly addresssed space\n" "\n" "\n" "instruction : STP\n" "binary representatioin : 0000 1111\n" "usage : \n" "STP\n" "Stops the execution of code by system/b\n" "\n" "\n" "\n" "debugging code :\n" "To debug system/b assembly programs, simply create a system/b instance\n" "and load a system/b machine code file into the machine. Then step through\n" "the code using the included assembler using the \"stepCycle\" command, it\n" "may be advisable to use the immDump and idrDump commands to gain a \n" "snapshot of the immediate address space and the indirect address space.\n" "Viewing these snapshots, translate the binary to the assembly yourself and\n" "use that to identify how the program is running\n" "\n" "\n" "\n" "system implmentation :\n" "system/b is a simple machine modled loosely on the Analytical Engine\n" "as created by Charles Babbage. The operation of system/b is simple to,\n" "visualize. The code is loaded into the immediately addressable space as\n" "a series of binary instructions that are read by the machine and then\n" "executed, with all non-jump instructions iterating the instruction counter\n" "by, causing the next instruction in the immediately addressed space to \n" "be executed. Code can be loaded into the directly addressed space using the\n" "special WRT instruction and read from it using the RED instruction. All numbers\n" "are stored in a little endian two's complment format."; } std::string quitText() { return "Thank you for using system/b, have a nice day.\n"; }
assembler.h
#pragma once #include <fstream> #include <bitset> void assemble(std::string inputFile, std::string outputFile) { std::ifstream inFile; std::ofstream otFile(outputFile); std::string instruction, argumentAddr1, argumentAddr2, argumentAddr3, machineLine, ignore; bool complete = false; inFile.open(inputFile); if (!inFile) { throw std::invalid_argument("infile not accessable"); } while (!complete) { inFile >> instruction; if (inFile) { /* * this checks if the instruction is actually a comment */ if (instruction == ";") { std::getline(inFile, ignore); } else { /* instruction : JIE binary representatioin : 0000 1010 usage : JIE [argument address] [argument address] [argument address] jumps to the third argument address if the value of the first argument address equals the second argumet address */ if (instruction == "JIE") { inFile >> argumentAddr1; inFile >> argumentAddr2; inFile >> argumentAddr3; machineLine = "00001010" + std::bitset<8>(stoi(argumentAddr1)).to_string() + std::bitset<8>(stoi(argumentAddr2)).to_string() + std::bitset<8>(stoi(argumentAddr3)).to_string(); } /* instruction : JMP binary representatioin : 0000 0101 usage : JMP [argument address] unconditionally jumps to the argument address provided */ else if (instruction == "JMP") { inFile >> argumentAddr1; machineLine = "00000101" + std::bitset<8>(stoi(argumentAddr1)).to_string() + "0000000000000000"; } /* instruction : MOV binary representatioin : 0000 1100 usage : MOV [argument address] [argument address] copies the value stored in the first argument address to the second argument address unconditionally */ else if (instruction == "MOV") { inFile >> argumentAddr1; inFile >> argumentAddr2; machineLine = "00001100" + std::bitset<8>(stoi(argumentAddr1)).to_string() + std::bitset<8>(stoi(argumentAddr2)).to_string() + "00000000"; } /* instruction : ADD binary representatioin : 0000 1011 usage : ADD [argument address] [argument address] [argument address] adds the contents of the first argument address to the second argument address and then places the result in the third argument address */ else if (instruction == "ADD") { inFile >> argumentAddr1; inFile >> argumentAddr2; inFile >> argumentAddr3; machineLine = "00001011" + std::bitset<8>(stoi(argumentAddr1)).to_string() + std::bitset<8>(stoi(argumentAddr2)).to_string() + std::bitset<8>(stoi(argumentAddr3)).to_string(); } /* instruction : FLP binary representatioin : 0000 0100 usage : FLP [argument address] flips the bits in the argument address provided */ else if (instruction == "FLP") { inFile >> argumentAddr1; machineLine = "00000100" + std::bitset<8>(stoi(argumentAddr1)).to_string() + "0000000000000000"; } /* instruction : RED binary representatioin : 0000 1101 usage : RED [argument address] [argument address] special instruction, reads the value located in the directly addressed space to the second argument address, the first argument address provides the pointer to the location of the data in the directly addressed space */ else if (instruction == "RED") { inFile >> argumentAddr1; inFile >> argumentAddr2; machineLine = "00001101" + std::bitset<8>(stoi(argumentAddr1)).to_string() + std::bitset<8>(stoi(argumentAddr2)).to_string() + "00000000"; } /* instruction : WRT binary representatioin : 0000 0010 usage : WRT [argument address] [argument address] special instruction, writes the value located in the second argument address to an address located in the directly addressed space, the first argument address provides the pointer to the location of the data in the directly addresssed space */ else if (instruction == "WRT") { inFile >> argumentAddr1; inFile >> argumentAddr2; machineLine = "00000010" + std::bitset<8>(stoi(argumentAddr1)).to_string() + std::bitset<8>(stoi(argumentAddr2)).to_string() + "00000000"; } /* instruction : STP binary representatioin : 0000 1111 usage : STP Stops the execution of code by system/b */ else if (instruction == "STP") { machineLine = "00001111000000000000000000000000"; } /* this ignores comments on lines */ else { machineLine = std::bitset<32>(stoi(instruction)).to_string(); } otFile << machineLine << std::endl; } } else { complete = true; } } otFile.close(); inFile.close(); }