diff --git a/ls8/cpu.c b/ls8/cpu.c index bdb1f506..15df25da 100644 --- a/ls8/cpu.c +++ b/ls8/cpu.c @@ -1,3 +1,7 @@ +#include +#include +#include + #include "cpu.h" #define DATA_LEN 6 @@ -5,23 +9,53 @@ /** * Load the binary bytes from a .ls8 source file into a RAM array */ -void cpu_load(struct cpu *cpu) +void cpu_load(struct cpu *cpu, char *filename) { - char data[DATA_LEN] = { - // From print8.ls8 - 0b10000010, // LDI R0,8 - 0b00000000, - 0b00001000, - 0b01000111, // PRN R0 - 0b00000000, - 0b00000001 // HLT - }; + //open file + FILE *fptr = fopen(filename, "r"); + + if (fptr == NULL) + { + printf("File %s is not recognized.\n", filename); + exit(2); + } + //read line by line until returns null, ignore blank lines and comments + char line[1000]; int address = 0; - for (int i = 0; i < DATA_LEN; i++) { - cpu->ram[address++] = data[i]; + while(fgets(line, sizeof line, fptr) != NULL) + { + //convert string to a number. if get an error + char *endpointer; + unsigned char value = strtoul(line, &endpointer, 2); //needs pointer to a pointer so need & as address of pointer + //ignore lines where no numbers read + if (endpointer == line) + { + continue; + } + //save data into ram, have to convert binary strings to int values to store in ram with strtoul + //cpu_ram_write(cpu, address++, value); + cpu->ram[address++] = value; } + fclose(fptr); + + //original hard code we're refactoring + // char data[DATA_LEN] = { + // // From print8.ls8 + // 0b10000010, // LDI R0,8 + // 0b00000000, + // 0b00001000, + // 0b01000111, // PRN R0 + // 0b00000000, + // 0b00000001 // HLT + // }; + + // int address = 0; + + // for (int i = 0; i < DATA_LEN; i++) { + // cpu->ram[address++] = data[i]; + // } // TODO: Replace this with something less hard-coded } @@ -31,15 +65,89 @@ void cpu_load(struct cpu *cpu) */ void alu(struct cpu *cpu, enum alu_op op, unsigned char regA, unsigned char regB) { + unsigned char valA = cpu->reg[regA]; + unsigned char valB = cpu->reg[regB]; + unsigned char flag; switch (op) { case ALU_MUL: // TODO + cpu->reg[regA] = cpu->reg[regA]*cpu->reg[regB]; //reg[regA] *= valB?? break; - // TODO: implement more ALU ops + case ALU_ADD: + cpu->reg[regA] = cpu->reg[regA]+cpu->reg[regB]; + break; + case ALU_CMP: //compare values in two registers w limited subtraction. + if (valA < valB) //if less than flag to 1 + { + flag = 0b00000100; + } + else if (valA > valB) //if greater than flag to 1 + { + flag = 0b00000010; + } + else if (valA == valB) //if equal flag to 1 + { + flag = 0b00000001; + } + else + { + flag = 0b00000000; //otherwise set to 0 + } + cpu->fl = flag; + break; } } +unsigned char cpu_ram_read(struct cpu *cpu, unsigned char mar) +{ + return cpu->ram[mar]; +} + +//mar is index in ram so set to new value +void cpu_ram_write(struct cpu *cpu, unsigned char mar, unsigned char mdr) +{ + cpu->ram[mar] = mdr; +} + +void push(struct cpu *cpu, unsigned char val) +{ + //push the value in the given register to top of the stack + //decrement the sp + cpu->reg[7]--; + //copy the value in the given register to the address pointed to by the sp + //another variable? add sp to struct? + cpu_ram_write(cpu, cpu->reg[7], val); +} + +unsigned char pop(struct cpu *cpu) +{ + //pop the value at the top of the stack into given register + //copy the value from the address pointed to by sp to given register + //put value in register take it out of ram (by reading) get address reg7 and store it in given register + //unsigned char pop_val = cpu_ram_read(cpu, cpu->reg[7]); + unsigned char val = cpu->ram[cpu->reg[7]]; + //increment sp + cpu->reg[7]++; + return val; +} + +void call(struct cpu *cpu, unsigned char registerAddress) +{ + //push address of instruction on to stack so we can return to where we left off after subroutine executes + unsigned char address = cpu->reg[registerAddress]; + push(cpu, cpu->pc+2); + //set program counter to location of subroutine + cpu->pc = address; + //pc is set to address stored in given register. jump to that location and execute the instruction in the subroutine. +} + +void ret(struct cpu *cpu) +{ + //return from subroutine. pop value from top of stack and store in pc. + cpu->pc = pop(cpu); +} + /** * Run the CPU */ @@ -50,11 +158,87 @@ void cpu_run(struct cpu *cpu) while (running) { // TODO // 1. Get the value of the current instruction (in address PC). + unsigned char IR = cpu_ram_read(cpu, cpu->pc); // 2. Figure out how many operands this next instruction requires // 3. Get the appropriate value(s) of the operands following this instruction + unsigned char operandA = cpu_ram_read(cpu, cpu->pc+1); + unsigned char operandB = cpu_ram_read(cpu, cpu->pc+2); + printf("TRACE: %02X: %02X %02X %02X \n", cpu->pc, IR, operandA, operandB); // 4. switch() over it to decide on a course of action. // 5. Do whatever the instruction should do according to the spec. + //int instruction_set_pc = (IR >> 4); + switch(IR) + { + case LDI: + //sets value in given register to value specified + cpu->reg[operandA] = operandB; + //increment by 3 since 3 arguments (CP+2) + cpu->pc += 3; + break; + case PRN: + //print out numerically in given register + printf("%d\n", cpu->reg[operandA]); + cpu->pc += 2; + break; + case MUL: + alu(cpu, ALU_MUL, operandA, operandB); + cpu->pc += 3; + break; + case ADD: + alu(cpu, ALU_ADD, operandA, operandB); + cpu->pc += 3; + break; + case PUSH: + push(cpu, cpu->reg[operandA]); + cpu->pc += 2; + break; + case POP: + cpu->reg[operandA] = pop(cpu); + cpu->pc += 2; + break; + case CALL: + //call(cpu, operandA); + push(cpu, cpu->pc + 2); + cpu->pc = cpu->reg[operandA]; + //printf("TRACE: 0x%02X\n", cpu->pc); + break; + case RET: + //ret(cpu); + cpu->pc = pop(cpu); + break; + case CMP: + alu(cpu, ALU_CMP, operandA, operandB); + break; + case JMP: + //jump to address stored in given register. set pc to address stored in given register. + cpu->pc = cpu->reg[operandA]; + break; + case JEQ: + //if equal flag set to true jump to address stored at given register. + if (cpu->fl == 1) + { + cpu->pc = cpu->reg[operandA]; + } + break; + case JNE: + //if equal flag is clear/false/0 jump to address stored at given register. + if ((cpu->fl & 0b00000001) == 0) + { + cpu->pc = cpu->reg[operandA]; + } + break; + case HLT: + running = 0; //set running to false + break; + default: //program stops if gets instruction it doesnt know + printf("unexpected instruction: 0x%0X2 at 0x%0X2 \n", IR, cpu->pc); + exit(1); + } // 6. Move the PC to the next instruction. + // if (!instruction_set_pc) + // { + // cpu->pc+= ((IR >> 6) & 0x3) + 1; + // } } } @@ -64,4 +248,14 @@ void cpu_run(struct cpu *cpu) void cpu_init(struct cpu *cpu) { // TODO: Initialize the PC and other special registers + cpu->fl = 0; + cpu->pc = 0; + memset(cpu->reg, 0, sizeof cpu->reg); //R7?????? + cpu->reg[7] = 0xf4; + memset(cpu->ram, 0, sizeof cpu->ram); } +// When the LS-8 is booted, the following steps occur: +// * `R0`-`R6` are cleared to `0`. +// * `R7` is set to `0xF4`. +// * `PC` and `FL` registers are cleared to `0`. +// * RAM is cleared to `0`. \ No newline at end of file diff --git a/ls8/cpu.h b/ls8/cpu.h index 46e49c44..594e8d1a 100644 --- a/ls8/cpu.h +++ b/ls8/cpu.h @@ -2,20 +2,32 @@ #define _CPU_H_ // Holds all information about the CPU -struct cpu { +typedef struct cpu { // TODO // PC + unsigned char pc; + unsigned char fl; // registers (array) + unsigned char reg[8]; // ram (array) -}; + unsigned char ram[256]; +} cpu; + +//add flag somewhere else below? instructions? // ALU operations enum alu_op { - ALU_MUL + ALU_MUL, + ALU_ADD, + ALU_CMP, // Add more here }; // Instructions +//memory locations !!!!!!!!!!!!!!!!! +#define ADD_EMPTY_STACK 0xf4 +#define ADD_PROGRAM_ENTRY 0x00 + // These use binary literals. If these aren't available with your compiler, hex // literals should be used. @@ -24,10 +36,20 @@ enum alu_op { #define HLT 0b00000001 #define PRN 0b01000111 // TODO: more instructions here. These can be used in cpu_run(). +#define MUL 0b10100010 +#define ADD 0b10100000 +#define PUSH 0b01000101 +#define POP 0b01000110 +#define CALL 0b01010000 +#define RET 0b00010001 +#define CMP 0b10100111 +#define JMP 0b01010100 +#define JEQ 0b01010101 +#define JNE 0b01010110 // Function declarations -extern void cpu_load(struct cpu *cpu); +extern void cpu_load(struct cpu *cpu, char *filename); extern void cpu_init(struct cpu *cpu); extern void cpu_run(struct cpu *cpu); diff --git a/ls8/examples/tests.ls8 b/ls8/examples/tests.ls8 new file mode 100644 index 00000000..720390cb --- /dev/null +++ b/ls8/examples/tests.ls8 @@ -0,0 +1,79 @@ +10000010 # LDI R0,10 +00000000 +00001010 +10000010 # LDI R1,20 +00000001 +00010100 +10000010 # LDI R2,TEST1 +00000010 +00010011 +10100111 # CMP R0,R1 +00000000 +00000001 +01010101 # JEQ R2 +00000010 +10000010 # LDI R3,1 +00000011 +00000001 +01000111 # PRN R3 +00000011 +# TEST1 (address 19): +10000010 # LDI R2,TEST2 +00000010 +00100000 +10100111 # CMP R0,R1 +00000000 +00000001 +01010110 # JNE R2 +00000010 +10000010 # LDI R3,2 +00000011 +00000010 +01000111 # PRN R3 +00000011 +# TEST2 (address 32): +10000010 # LDI R1,10 +00000001 +00001010 +10000010 # LDI R2,TEST3 +00000010 +00110000 +10100111 # CMP R0,R1 +00000000 +00000001 +01010101 # JEQ R2 +00000010 +10000010 # LDI R3,3 +00000011 +00000011 +01000111 # PRN R3 +00000011 +# TEST3 (address 48): +10000010 # LDI R2,TEST4 +00000010 +00111101 +10100111 # CMP R0,R1 +00000000 +00000001 +01010110 # JNE R2 +00000010 +10000010 # LDI R3,4 +00000011 +00000100 +01000111 # PRN R3 +00000011 +# TEST4 (address 61): +10000010 # LDI R3,5 +00000011 +00000101 +01000111 # PRN R3 +00000011 +10000010 # LDI R2,TEST5 +00000010 +01001001 +01010100 # JMP R2 +00000010 +01000111 # PRN R3 +00000011 +# TEST5 (address 73): +00000001 # HLT \ No newline at end of file diff --git a/ls8/ls8.c b/ls8/ls8.c index d24578ee..1553d4d2 100644 --- a/ls8/ls8.c +++ b/ls8/ls8.c @@ -4,13 +4,26 @@ /** * Main */ -int main(void) +//the way this is set up shows us the order that this should be built out +int main(int argc, char *argv[]) //changed from void for command line processing. arg count and array of strings that hold indiv args. { struct cpu cpu; + if (argc != 2) + { + fprintf(stderr, "This function requires two arguments."); + return 1; //same as exit(1) b/c normally return 0 + } + + char *filename = argv[1]; + cpu_init(&cpu); - cpu_load(&cpu); + cpu_load(&cpu, filename); //also update prototype to include char pointer cpu_run(&cpu); return 0; -} \ No newline at end of file +} + + +//argv[0] == "./ls8" +//argv[1] == "examples/mult.ls8" \ No newline at end of file