├── .gitignore ├── LICENSE ├── README.md ├── cpu.h ├── example.c ├── flags.h ├── instructions.h ├── tests.c └── types.h /.gitignore: -------------------------------------------------------------------------------- 1 | a.out 2 | *.bat 3 | bin/* -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2017, pbohun 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 10 | * Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | 14 | * Neither the name of fasm-tutorials nor the names of its 15 | contributors may be used to endorse or promote products derived from 16 | this software without specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 22 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 24 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 25 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 26 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Basic-CPU 2 | This is a very simple, compact, virtual cpu written in c. 3 | It's a project for fun and learning, so feel free to play around with it to try out different ideas. Don't be surprised if you find some bugs. 4 | 5 | You can find a video walkthrough of the cpu here: 6 | 7 | https://www.youtube.com/watch?v=cfPDeso3XwI 8 | 9 | ## Prerequisites 10 | You'll need a c compiler that compiles to the c99 standard, and git is recommended. 11 | 12 | ## Get basic-cpu 13 | Use the following command to get the repository: 14 | 15 | `git clone https://github.com/pbohun/basic-cpu.git` 16 | 17 | ## Usage 18 | Include the `cpu.h` file in your program and you're ready to go. You have to use the `new_cpu()` function to create a new cpu and `free_cpu()` function at the end. 19 | 20 | ## Runing the tests 21 | To compile and run the tests use the following commands: 22 | 23 | `gcc -std=c99 -o tests tests.c` 24 | `./tests` 25 | 26 | ## Trying the example 27 | To compile the example program use the following command: 28 | 29 | `gcc -std=c99 -o example example.c` 30 | 31 | The example program calculates 5 factorial and stores the answer in the register 0 (R0). 32 | 33 | ## Writing Programs 34 | Note that this is just a cpu. There are no mechanisms for input, output, monitors, long term storage, etc. You have to create memory for the cpu and fill it with machine code. An example of this is found in the `example.c` file. 35 | 36 | Everything in the cpu is 64 bit, defined as i64 and f64 for 64 bit floating point. Most instructions involve registers. Here's an example of what a move instruction would look like in an array of memory. Note that it's designed to look a lot like assembly language. 37 | 38 | `MOV, R0, R1` 39 | 40 | Take a look at the `example.c` file and the `tests.c` file to see how to use the `cpu.h` library. 41 | -------------------------------------------------------------------------------- /cpu.h: -------------------------------------------------------------------------------- 1 | // copyright 2017 Philip Bohun 2 | #ifndef CPU_H 3 | #define CPU_H 4 | 5 | #include "types.h" 6 | #include "instructions.h" 7 | #include "flags.h" 8 | 9 | void execute(cpu *c); 10 | void fetch(cpu *c); 11 | 12 | cpu *new_cpu(i64 *memory, i64 mem_size) { 13 | cpu *c = malloc(sizeof(cpu)); 14 | c->mem = memory; 15 | c->sp = mem_size - 1; 16 | c->max_mem = mem_size; 17 | c->pc = -1; 18 | c->inst = 0; 19 | return c; 20 | } 21 | 22 | void free_cpu(cpu *c) { 23 | free(c); 24 | } 25 | 26 | void run_cpu(cpu *c) { 27 | while (c->inst != HLT) { 28 | fetch(c); 29 | execute(c); 30 | } 31 | } 32 | 33 | void fetch(cpu *c) { 34 | c->pc++; 35 | c->inst = c->mem[c->pc] & FIRST_BYTE; 36 | c->dest = c->mem[c->pc+1]; 37 | c->src = c->mem[c->pc+2]; 38 | } 39 | 40 | void execute(cpu *c) { 41 | switch (c->inst) { 42 | case CLF: 43 | clear_flags(c); 44 | break; 45 | case CMP: 46 | set_flags(c, c->r[c->dest], c->r[c->src]); 47 | c->pc += 2; 48 | break; 49 | case CMPI: 50 | set_flags(c, c->r[c->dest], c->src); 51 | c->pc += 2; 52 | break; 53 | case CMPF: 54 | fset_flags(c, c->fr[c->dest - 8], (f64)c->fr[c->src - 8]); 55 | c->pc += 2; 56 | break; 57 | case CMPFI: 58 | fset_flags(c, c->fr[c->dest - 8], (f64)c->src); 59 | c->pc += 2; 60 | break; 61 | case MOV: 62 | c->r[c->dest] = c->r[c->src]; 63 | c->pc += 2; 64 | break; 65 | case MOVF: 66 | c->fr[c->dest - 8] = c->fr[c->src - 8]; 67 | c->pc += 2; 68 | break; 69 | case STI: 70 | c->mem[c->dest] = c->r[c->src]; 71 | c->pc += 2; 72 | break; 73 | case STF: 74 | c->mem[c->dest] = (i64)c->fr[c->src - 8]; 75 | c->pc += 2; 76 | break; 77 | case LDI: 78 | c->r[c->dest] = c->mem[c->src]; 79 | c->pc += 2; 80 | break; 81 | case LDF: 82 | c->fr[c->dest - 8] = (f64)c->mem[c->src]; 83 | c->pc += 2; 84 | break; 85 | case LII: 86 | c->r[c->dest] = c->src; 87 | c->pc += 2; 88 | break; 89 | case LIF: 90 | c->fr[c->dest - 8] = (f64)c->src; 91 | c->pc += 2; 92 | break; 93 | case PSH: 94 | c->mem[--c->sp] = c->r[c->mem[++c->pc]]; 95 | break; 96 | case PSHF: 97 | c->mem[--c->sp] = (i64)c->fr[c->mem[++c->pc]-8]; 98 | break; 99 | case POP: 100 | c->r[c->mem[++c->pc]] = c->mem[c->sp++]; 101 | break; 102 | case POPF: 103 | c->fr[c->mem[++c->pc]-8] = (f64)c->mem[c->sp++]; 104 | break; 105 | case INC: 106 | c->r[c->dest]++; 107 | c->pc++; 108 | break; 109 | case DEC: 110 | c->r[c->dest]--; 111 | c->pc++; 112 | break; 113 | case ADD: 114 | c->r[c->dest] += c->r[c->src]; 115 | c->pc += 2; 116 | break; 117 | case SUB: 118 | c->r[c->dest] -= c->r[c->src]; 119 | c->pc += 2; 120 | break; 121 | case MUL: 122 | c->r[c->dest] *= c->r[c->src]; 123 | c->pc += 2; 124 | break; 125 | case DIV: 126 | c->r[c->dest] /= c->r[c->src]; 127 | c->pc += 2; 128 | break; 129 | case ADDF: 130 | c->fr[c->dest - 8] += c->fr[c->src - 8]; 131 | c->pc +=2; 132 | break; 133 | case SUBF: 134 | c->fr[c->dest - 8] -= c->fr[c->src - 8]; 135 | c->pc +=2; 136 | break; 137 | case MULF: 138 | c->fr[c->dest - 8] *= c->fr[c->src - 8]; 139 | c->pc +=2; 140 | break; 141 | case DIVF: 142 | c->fr[c->dest - 8] /= c->fr[c->src - 8]; 143 | c->pc +=2; 144 | break; 145 | case JLZ: 146 | if (c->ltz) c->pc = c->mem[++(c->pc)]; 147 | else c->pc++; 148 | break; 149 | case JGZ: 150 | if (c->gtz) c->pc = c->mem[++(c->pc)]; 151 | else c->pc++; 152 | break; 153 | case JEZ: 154 | if (c->zero) c->pc = c->mem[++(c->pc)]; 155 | else c->pc++; 156 | break; 157 | case JNZ: 158 | if (!c->zero) c->pc = c->mem[++(c->pc)]; 159 | else c->pc++; 160 | break; 161 | case JMP: 162 | c->pc = c->mem[++(c->pc)]; 163 | break; 164 | case SHL: 165 | c->r[c->dest] <<= c->r[c->src]; 166 | c->pc += 2; 167 | break; 168 | case SHR: 169 | c->r[c->dest] >>= c->r[c->src]; 170 | c->pc += 2; 171 | break; 172 | case BAND: 173 | c->r[c->dest] &= c->r[c->src]; 174 | c->pc += 2; 175 | break; 176 | case BOR: 177 | c->r[c->dest] |= c->r[c->src]; 178 | c->pc += 2; 179 | break; 180 | case BNOT: 181 | c->r[c->dest] = ~c->r[c->dest]; 182 | c->pc++; 183 | break; 184 | case BXOR: 185 | c->r[c->dest] ^= c->r[c->src]; 186 | c->pc += 2; 187 | break; 188 | case LAND: 189 | c->r[c->dest] = c->r[c->dest] && c->r[c->src]; 190 | c->pc += 2; 191 | break; 192 | case LOR: 193 | c->r[c->dest] = c->r[c->dest] || c->r[c->src]; 194 | c->pc += 2; 195 | break; 196 | case LNOT: 197 | c->r[c->dest] = !c->r[c->dest]; 198 | c->pc++; 199 | break; 200 | } 201 | } 202 | 203 | #endif // CPU_H 204 | -------------------------------------------------------------------------------- /example.c: -------------------------------------------------------------------------------- 1 | // copyright 2017 Philip Bohun 2 | // To compile use the following command: 3 | // gcc -std=c99 -o example example.c 4 | #include 5 | #include 6 | #include "cpu.h" 7 | 8 | void print_list(i64 *lst, int size) { 9 | for (int i = 0; i < size - 1; i++) { 10 | printf("%llu,", lst[i]); 11 | } 12 | printf("%llu\n", lst[size-1]); 13 | } 14 | 15 | void print_registers(cpu *c) { 16 | for (int i = 0; i < 7; i++) { 17 | printf("%llu,", c->r[i]); 18 | } 19 | printf("%llu\n", c->r[7]); 20 | } 21 | 22 | void print_fregisters(cpu *c) { 23 | for (int i = 0; i < 7; i++) { 24 | printf("%f,", c->fr[i]); 25 | } 26 | printf("%f\n", c->fr[7]); 27 | } 28 | 29 | int main() { 30 | i64 b[] = { 31 | LII, R0, 1, 32 | LII, R1, 5, 33 | LII, R2, 1, 34 | MUL, R0, R1, 35 | SUB, R1, R2, 36 | JNZ, 8, 37 | HLT 38 | }; 39 | 40 | printf("instructions:\n"); 41 | print_list(b, 18); 42 | 43 | // create new cpu with the given i64 array for memory 44 | cpu *c = new_cpu(b, 18); 45 | 46 | run_cpu(c); 47 | 48 | printf("registers:\n"); 49 | print_registers(c); 50 | printf("floating registers:\n"); 51 | print_fregisters(c); 52 | 53 | free_cpu(c); 54 | 55 | return 0; 56 | } 57 | -------------------------------------------------------------------------------- /flags.h: -------------------------------------------------------------------------------- 1 | // copyright 2017 Philip Bohun 2 | #ifndef FLAGS_H 3 | #define FLAGS_H 4 | 5 | void clear_flags(cpu *c) { 6 | c->zero = 0; 7 | c->ltz = 0; 8 | c->gtz = 0; 9 | } 10 | 11 | void set_flags(cpu *c, i64 a, i64 b) { 12 | i64 res = a - b; 13 | c->zero = (res == 0); 14 | c->ltz = (res < 0); 15 | c->gtz = (res > 0); 16 | } 17 | 18 | void fset_flags(cpu *c, f64 a, f64 b) { 19 | f64 res = a - b; 20 | c->zero = (res == 0); 21 | c->ltz = (res < 0); 22 | c->gtz = (res > 0); 23 | } 24 | 25 | #endif // FLAGS_H 26 | -------------------------------------------------------------------------------- /instructions.h: -------------------------------------------------------------------------------- 1 | // copyright 2017 Philip Bohun 2 | #ifndef INSTRUCTIONS_H 3 | #define INSTRUCTIONS_H 4 | 5 | enum instr { 6 | CLF, 7 | CMP, CMPI, CMPF, CMPFI, 8 | MOV, MOVF, 9 | STI, STF, LDI, LDF, 10 | LII, LIF, 11 | PSH, POP, 12 | PSHF, POPF, 13 | INC, DEC, 14 | ADD, SUB, MUL, DIV, 15 | ADDF, SUBF, MULF, DIVF, 16 | JLZ, JGZ, JEZ, JNZ, JMP, 17 | SHL, SHR, 18 | BAND, BOR, BNOT, BXOR, 19 | LAND, LOR, LNOT, 20 | HLT, 21 | NUM_INSTRUCTIONS 22 | }; 23 | 24 | #endif // INSTRUCTIONS_H 25 | -------------------------------------------------------------------------------- /tests.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "cpu.h" 5 | 6 | void print_registers(cpu *c) { 7 | for (int i = 0; i < 7; i++) { 8 | printf("%llu,", c->r[i]); 9 | } 10 | printf("%llu\n", c->r[7]); 11 | } 12 | 13 | void print_float_registers(cpu *c) { 14 | for (int i = 0; i < 7; i++) { 15 | printf("%f,", c->fr[i]); 16 | } 17 | printf("%f\n", c->fr[7]); 18 | } 19 | 20 | void run_integer_tests(); 21 | void run_float_tests(); 22 | void run_bitwise_tests(); 23 | void run_logic_tests(); 24 | void run_jump_tests(); 25 | 26 | int main() { 27 | printf("Begin running tests\n"); 28 | run_integer_tests(); 29 | run_float_tests(); 30 | run_bitwise_tests(); 31 | run_logic_tests(); 32 | run_jump_tests(); 33 | printf("All tests passed\n"); 34 | 35 | return 0; 36 | } 37 | 38 | void run_integer_tests() { 39 | printf("Running tests on integer instructions\n"); 40 | 41 | i64 mem[] = { 42 | LII, R0, 1, 43 | LII, R1, 5, 44 | MOV, R2, R0, 45 | MUL, R0, R1, 46 | SUB, R1, R2, 47 | CMPI, R1, 0, 48 | JNZ, 8, 49 | PSH, R0, 50 | POP, R7, 51 | LII, R6, 2, 52 | DIV, R7, R6, 53 | STI, 42, R7, 54 | LDI, R5, 42, 55 | ADD, R7, R2, 56 | HLT, 57 | 0, 0, 0, 58 | 0, 0, 0 59 | }; 60 | 61 | cpu *c = new_cpu(mem, 46); 62 | run_cpu(c); 63 | print_registers(c); 64 | assert(c->r[0] == 120); 65 | assert(c->r[7] == 61); 66 | assert(c->mem[42] == 60); 67 | assert(c->r[5] == 60); 68 | 69 | free_cpu(c); 70 | printf("All integer tests passed\n"); 71 | } 72 | 73 | void run_float_tests() { 74 | printf("Running tests on float instructions\n"); 75 | 76 | i64 mem[] = { 77 | LIF, F0, 1.0, 78 | LIF, F1, 5.0, 79 | MOVF, F2, F0, 80 | MULF, F0, F1, 81 | SUBF, F1, F2, 82 | CMPFI, F1, 0, 83 | JNZ, 8, 84 | PSHF, F0, 85 | POPF, F7, 86 | LIF, F6, 2.0, 87 | DIVF, F7, F6, 88 | STF, 42, F7, // 27, 28, 29 89 | LDF, F5, 42, // 30, 31, 32 90 | ADDF, F7, F2, 91 | HLT, 92 | 0, 0, 0, 93 | 0, 0, 0 94 | }; 95 | 96 | cpu *c = new_cpu(mem, 46); 97 | run_cpu(c); 98 | print_float_registers(c); 99 | assert(c->fr[0] == 120.0); 100 | assert(c->fr[7] == 61.0); 101 | assert((f64)c->mem[42] == 60.0); 102 | assert(c->fr[5] == 60.0); 103 | 104 | free_cpu(c); 105 | printf("All integer tests passed\n"); 106 | } 107 | 108 | void run_bitwise_tests() { 109 | printf("Running tests on bitwise instructions\n"); 110 | 111 | i64 mem[] = { 112 | LII, R0, 5, 113 | LII, R1, 3, 114 | LII, R2, 6, 115 | BOR, R0, R1, 116 | MOV, R3, R0, 117 | SUB, R3, R2, 118 | BXOR, R1, R2, 119 | LII, R4, 7, 120 | BNOT, R4, 121 | LII, R5, 5, 122 | LII, R6, 3, 123 | BAND, R5, R6, 124 | LII, R7, 1, 125 | SHL, R7, 2, 126 | SHR, R7, 1, 127 | HLT 128 | }; 129 | 130 | cpu *c = new_cpu(mem, 45); 131 | run_cpu(c); 132 | print_registers(c); 133 | 134 | assert(c->r[0] == 7); 135 | assert(c->r[1] == 5); 136 | assert(c->r[4] == ~(i64)7); 137 | assert(c->r[5] == 1); 138 | assert(c->r[7] == 2); 139 | 140 | free_cpu(c); 141 | printf("All bitwise tests passed\n"); 142 | } 143 | 144 | void run_logic_tests() { 145 | printf("Running tests on logic instructions\n"); 146 | 147 | i64 mem[] = { 148 | LII, R0, 1, 149 | LII, R1, 0, 150 | LII, R2, 1, 151 | LII, R3, 1, 152 | LAND, R0, R1, 153 | LOR, R1, R2, 154 | LNOT, R3, 155 | HLT 156 | }; 157 | 158 | cpu *c = new_cpu(mem, 21); 159 | run_cpu(c); 160 | print_registers(c); 161 | 162 | assert(c->r[0] == 0); 163 | assert(c->r[1] == 1); 164 | assert(c->r[3] == 0); 165 | 166 | free_cpu(c); 167 | printf("All logic tests passed\n"); 168 | } 169 | 170 | void run_jump_tests() { 171 | printf("Running tests on jump instructions\n"); 172 | 173 | i64 mem[] = { 174 | LII, R0, 0, 175 | LII, R1, 1, 176 | INC, R1, 177 | DEC, R1, 178 | LII, R2, 2, 179 | LII, R3, 1, 180 | CMP, R0, R1, 181 | JLZ, 30, 182 | CMP, R1, R3, 183 | JEZ, 32, 184 | CMP, R1, R0, 185 | JGZ, 34, 186 | JMP, 20, 187 | JMP, 25, 188 | HLT 189 | }; 190 | 191 | cpu *c = new_cpu(mem, 34); 192 | run_cpu(c); 193 | print_registers(c); 194 | 195 | free_cpu(c); 196 | printf("All jump tests passed\n"); 197 | } 198 | -------------------------------------------------------------------------------- /types.h: -------------------------------------------------------------------------------- 1 | // copyright 2017 Philip Bohun 2 | #ifndef TYPES_H 3 | #define TYPES_H 4 | 5 | #define FIRST_BYTE 0x00000000000000FF 6 | #define byte unsigned char 7 | #define u64 unsigned long long 8 | #define i64 long long 9 | #define f64 double 10 | 11 | enum registers { 12 | R0, R1, R2, R3, R4, R5, R6, R7, 13 | F0, F1, F2, F3, F4, F5, F6, F7, 14 | NUM_REGISTERS 15 | }; 16 | 17 | typedef struct { 18 | i64 *mem; 19 | i64 max_mem; 20 | 21 | // registers 22 | i64 pc; 23 | i64 sp; 24 | i64 r[8]; 25 | f64 fr[8]; 26 | 27 | // instruction parts 28 | i64 inst; 29 | i64 dest; 30 | i64 src; 31 | 32 | // flags 33 | i64 zero; 34 | i64 ltz; 35 | i64 gtz; 36 | } cpu; 37 | 38 | #endif // TYPES_H 39 | --------------------------------------------------------------------------------