diff --git a/README.md b/README.md index e81f54cf..8b256ead 100644 --- a/README.md +++ b/README.md @@ -2,11 +2,12 @@ ## Project -* [Implement the LS-8 Emulator](ls8/) +- [Implement the LS-8 Emulator](ls8/) ## Task List: add this to the first comment of your Pull Request ### Day 1: Get `print8.ls8` running + - [ ] Inventory what is here - [ ] Implement `struct cpu` in `cpu.h` - [ ] Add RAM functions `cpu_ram_read` and `cpu_ram_write` @@ -17,15 +18,18 @@ - [ ] Add the `PRN` instruction ### Day 2: Add the ability to load files dynamically, get `mult.ls8` and `stack.ls8` running + - [ ] Un-hardcode the machine code - [ ] Implement the `cpu_load` function to load an `.ls8` file given the filename passed in as an argument - [ ] Implement a Multiply instruction and Print the result (run `mult8.ls8`) - [ ] Implement the System Stack and be able to run the `stack.ls8` program ### Day 3: Get `call.ls8` running + - [ ] Implement the CALL and RET instructions - [ ] Implement Subroutine Calls and be able to run the `call.ls8` program ### Stretch + - [ ] Add the timer interrupt to the LS-8 emulator - [ ] Add the keyboard interrupt to the LS-8 emulator diff --git a/asm/test.asm b/asm/test.asm new file mode 100644 index 00000000..849a7e97 --- /dev/null +++ b/asm/test.asm @@ -0,0 +1,15 @@ +LDI R0, 8 +LDI R1, 1 + +LDI R2, 16 +LDI R3, 2 + +SHR R0, R1 +SHR R2, R3 + +ADDI R0, R1, 8 + +PRN R0 +PRN R2 + +HLT \ No newline at end of file diff --git a/ls8/cpu.c b/ls8/cpu.c index bdb1f506..76f310f4 100644 --- a/ls8/cpu.c +++ b/ls8/cpu.c @@ -1,40 +1,247 @@ #include "cpu.h" +#include +#include +#include +#include -#define DATA_LEN 6 +// ================== RAM functions =================== + +/** Read from the CPU's ram at the specifc location + */ +unsigned char cpu_ram_read(struct cpu *cpu, unsigned char MAR) +{ + return cpu->ram[MAR]; +} + +/** Writes at index specified for the CPU's ram + */ +void cpu_ram_write(struct cpu *cpu, unsigned char MAR, unsigned char MDR) +{ + cpu->ram[MAR] = MDR; +} + +// ================== Instrcution Handlers =================== + +// LDI function: Set the value of a register to an integer +void handle_LDI(struct cpu *cpu, unsigned char MAR, unsigned char MDR) +{ + cpu->registers[MAR] = MDR; +}; + +// PRN function: Print numeric value stored in the given register +void handle_PRN(struct cpu *cpu, unsigned char MAR) +{ + printf("%d\n", cpu->registers[MAR]); +} + +// HLT function: Halt the CPU (and exit the emulator) +void handle_HLT() +{ + exit(0); +} + +// ST function: Store value in registerB in the address stored in registerA +// This opcode writes to memory +void handle_ST(struct cpu *cpu, unsigned char regA, unsigned char regB) +{ + unsigned char MAR = cpu->registers[regA]; + unsigned char MDR = cpu->registers[regB]; + cpu_ram_write(cpu, MAR, MDR); +} + +// JMP function: Jump to the address stored in the given register +void handle_JMP(struct cpu *cpu, unsigned char regA) +{ + unsigned char MAR = cpu->registers[regA]; + cpu->pc = MAR; +} + +void handle_PRA(struct cpu *cpu, unsigned char MAR) +{ + printf("%c\n", cpu->registers[MAR]); +} + +// JEQ function: If equal flag is set (true), jump to the address stored in the given register +void handle_JEQ(struct cpu *cpu, unsigned char regA, unsigned char nextOpcode) +{ + unsigned char MAR = cpu->registers[regA]; + + if ((0b00000001 & cpu->fl)) + { + cpu->pc = MAR; + } + else + { + cpu->pc += nextOpcode; + } +} + +// JNE function: If E flag is clear (false, 0), jump to the address stored in the given register. +void handle_JNE(struct cpu *cpu, unsigned char regA, unsigned char nextOpcode) +{ + unsigned char MAR = cpu->registers[regA]; + + if (!(0b00000001 & cpu->fl)) + { + cpu->pc = MAR; + } + else + { + cpu->pc += nextOpcode; + } +} + +// ================= Stack functions ================== + +void handle_PUSH(struct cpu *cpu, unsigned char MDR) +{ + cpu_ram_write(cpu, --cpu->registers[7], MDR); +} + +void handle_POP(struct cpu *cpu, unsigned char regA) +{ + unsigned char value = cpu_ram_read(cpu, cpu->registers[7]++); + cpu->registers[regA] = value; +} + +// ================= Interrupt Handle functions ================== +void handle_interupt(struct cpu *cpu, int location) +{ + cpu->registers[6] = 0; + + cpu_ram_write(cpu, --cpu->registers[7], cpu->pc); + cpu_ram_write(cpu, --cpu->registers[7], cpu->fl); + for (int i = 0; i < 7; i++) + { + handle_PUSH(cpu, i); + } + cpu->pc = cpu->ram[0xF8 + location]; + cpu->registers[5] = 0; +} + +void handle_IRET(struct cpu *cpu) +{ + for (int i = 6; i >= 0; i--) + { + handle_POP(cpu, i); + } + cpu->fl = cpu_ram_read(cpu, cpu->registers[7]++); + cpu->pc = cpu_ram_read(cpu, cpu->registers[7]++); + cpu->registers[5] = 1; +} + +// ================= SubRoutine functions ================== +void handle_CALL(struct cpu *cpu, char unsigned MAR, char unsigned nextPC) +{ + // Saves next PC at the correct spot + handle_PUSH(cpu, nextPC); + cpu->pc = cpu->registers[MAR]; +} + +void handle_RET(struct cpu *cpu) +{ + cpu->pc = cpu_ram_read(cpu, cpu->registers[7]++); +} + +void handle_CMP(struct cpu *cpu, unsigned char regA, unsigned char regB) +{ + if (cpu->registers[regA] == cpu->registers[regB]) + { + cpu->fl = 0b00000001; + } + else if (cpu->registers[regA] > cpu->registers[regB]) + { + cpu->fl = 0b00000010; + } + else + { + cpu->fl = 0b00000100; + } +} + +// ================== CPU functions =================== /** * Load the binary bytes from a .ls8 source file into a RAM array */ -void cpu_load(struct cpu *cpu) -{ - char data[DATA_LEN] = { - // From print8.ls8 - 0b10000010, // LDI R0,8 - 0b00000000, - 0b00001000, - 0b01000111, // PRN R0 - 0b00000000, - 0b00000001 // HLT - }; +void cpu_load(struct cpu *cpu, char *argv) +{ + FILE *ptr = fopen(argv, "r"); + char c[100]; + unsigned char ret; int address = 0; - - for (int i = 0; i < DATA_LEN; i++) { - cpu->ram[address++] = data[i]; + while (fgets(c, 100, ptr) != NULL) + { + char *endptr; + ret = strtoul(c, &endptr, 2); + if (endptr == c) + { + continue; + } + cpu_ram_write(cpu, address++, ret); } + fclose(ptr); + // TODO: Replace this with something less hard-coded } /** * ALU */ -void alu(struct cpu *cpu, enum alu_op op, unsigned char regA, unsigned char regB) +void alu(struct cpu *cpu, enum alu_op op) { - switch (op) { - case ALU_MUL: - // TODO - break; + unsigned char regA = cpu_ram_read(cpu, (cpu->pc + 1)); + unsigned char regB = cpu_ram_read(cpu, (cpu->pc + 2)); + unsigned char addiVal = cpu_ram_read(cpu, (cpu->pc + 3)); + switch (op) + { + case ALU_MUL: + cpu->registers[regA] = cpu->registers[regA] * cpu->registers[regB]; + break; + case ALU_ADD: + cpu->registers[regA] = cpu->registers[regA] + cpu->registers[regB]; + break; + case ALU_SUB: + cpu->registers[regA] = cpu->registers[regA] - cpu->registers[regB]; + break; + case ALU_MOD: + cpu->registers[regA] = cpu->registers[regA] % cpu->registers[regB]; + break; + case ALU_DIV: + cpu->registers[regA] = cpu->registers[regA] / cpu->registers[regB]; + break; + case ALU_INC: + cpu->registers[regA]++; + break; + case ALU_DEC: + cpu->registers[regA]--; + break; + case ALU_CMP: + handle_CMP(cpu, regA, regB); + break; + case ALU_NOT: + cpu->registers[regA] = ~cpu->registers[regA]; + break; + case ALU_AND: + cpu->registers[regA] = cpu->registers[regA] & cpu->registers[regB]; + break; + case ALU_OR: + cpu->registers[regA] = cpu->registers[regA] | cpu->registers[regB]; + break; + case ALU_XOR: + cpu->registers[regA] = cpu->registers[regA] ^ cpu->registers[regB]; + break; + case ALU_SHL: + cpu->registers[regA] = cpu->registers[regA] >> cpu->registers[regB]; + break; + case ALU_SHR: + cpu->registers[regA] = cpu->registers[regA] << cpu->registers[regB]; + break; + case ALU_ADDI: + cpu->registers[regA] = cpu->registers[regB] + cpu->registers[addiVal]; + break; // TODO: implement more ALU ops } @@ -45,9 +252,12 @@ void alu(struct cpu *cpu, enum alu_op op, unsigned char regA, unsigned char regB */ void cpu_run(struct cpu *cpu) { + int running = 1; // True until we get a HLT instruction + time_t timePast = 0; - while (running) { + while (running) + { // TODO // 1. Get the value of the current instruction (in address PC). // 2. Figure out how many operands this next instruction requires @@ -55,6 +265,98 @@ void cpu_run(struct cpu *cpu) // 4. switch() over it to decide on a course of action. // 5. Do whatever the instruction should do according to the spec. // 6. Move the PC to the next instruction. + struct timeval tv; + time_t second; + + gettimeofday(&tv, NULL); + second = tv.tv_sec; + if (second != timePast) + { + cpu->registers[6] = 0b00000001; + timePast = second; + } + + unsigned char interrupts = cpu->registers[5] & cpu->registers[6]; + if (interrupts) + { + for (int i = 0; i < 8; i++) + { + int interrupt_happened = ((interrupts >> i) & 1) == 1; + if (interrupt_happened) + { + handle_interupt(cpu, i); + break; + } + } + } + + unsigned char IR = cpu_ram_read(cpu, (cpu->pc)); + unsigned char operandA = cpu_ram_read(cpu, (cpu->pc + 1)); + unsigned char operandB = cpu_ram_read(cpu, (cpu->pc + 2)); + int nextOpcode = ((0b11000000 & IR) >> 6) + 1; + + // printf("ALU find IR: %d\n", (IR)); + unsigned char ALUNUM = (0b00100000 & IR) >> 5; + + if (ALUNUM == 1) + { + alu(cpu, IR); + } + else + { + + switch (IR) + { + case LDI: + handle_LDI(cpu, operandA, operandB); + break; + case PRN: + handle_PRN(cpu, operandA); + break; + case HLT: + handle_HLT(); + break; + case ST: + handle_ST(cpu, operandA, operandB); + break; + case JMP: + handle_JMP(cpu, operandA); + continue; + case PRA: + handle_PRA(cpu, operandA); + break; + case JEQ: + handle_JEQ(cpu, operandA, nextOpcode); + continue; + case JNE: + handle_JNE(cpu, operandA, nextOpcode); + continue; + case IRET: + handle_IRET(cpu); + continue; + + // Subroutine instructions + case CALL: + handle_CALL(cpu, operandA, (cpu->pc + nextOpcode)); + continue; + case RET: + handle_RET(cpu); + continue; + + // Stack Instructions + case PUSH: + handle_PUSH(cpu, cpu->registers[operandA]); + break; + case POP: + handle_POP(cpu, operandA); + break; + default: + printf("unexpected instruction 0x%02X at 0x%02X\n", IR, cpu->pc); + exit(1); + } + } + + cpu->pc += nextOpcode; } } @@ -64,4 +366,9 @@ void cpu_run(struct cpu *cpu) void cpu_init(struct cpu *cpu) { // TODO: Initialize the PC and other special registers + cpu->pc = 0; + cpu->fl = 0; + memset(cpu->registers, 0, sizeof(cpu->registers)); + cpu->registers[7] = 0xF4; + memset(cpu->ram, 0, sizeof(cpu->ram)); } diff --git a/ls8/cpu.h b/ls8/cpu.h index 46e49c44..54684926 100644 --- a/ls8/cpu.h +++ b/ls8/cpu.h @@ -2,17 +2,40 @@ #define _CPU_H_ // Holds all information about the CPU -struct cpu { +struct cpu +{ // TODO // PC + unsigned char pc; + // FLAG + unsigned char fl; // registers (array) + unsigned char registers[8]; // ram (array) + unsigned char ram[256]; }; // ALU operations -enum alu_op { - ALU_MUL - // Add more here +enum alu_op +{ + // Mathmatical operations + ALU_MUL = 0b10100010, + ALU_ADD = 0b10100000, + ALU_SUB = 0b10100001, + ALU_MOD = 0b10100100, + ALU_DIV = 0b10100011, + ALU_INC = 0b01100101, + ALU_DEC = 0b01100110, + ALU_CMP = 0b10100111, + // Logic operations + ALU_NOT = 0b01101001, + ALU_AND = 0b10101000, + ALU_OR = 0b10101010, + ALU_XOR = 0b10101011, + // Shifts operations + ALU_SHL = 0b10101100, + ALU_SHR = 0b10101101, + ALU_ADDI = 0b11101000 }; // Instructions @@ -20,14 +43,25 @@ enum alu_op { // These use binary literals. If these aren't available with your compiler, hex // literals should be used. -#define LDI 0b10000010 -#define HLT 0b00000001 -#define PRN 0b01000111 +#define LDI 0b10000010 +#define HLT 0b00000001 +#define PRN 0b01000111 +#define POP 0b01000110 +#define PUSH 0b01000101 +#define CALL 0b01010000 +#define RET 0b00010001 +#define ST 0b10000100 +#define JMP 0b01010100 +#define PRA 0b01001000 +#define IRET 0b00010011 +#define JEQ 0b01010101 +#define JNE 0b01010110 + // TODO: more instructions here. These can be used in cpu_run(). // Function declarations -extern void cpu_load(struct cpu *cpu); +extern void cpu_load(struct cpu *cpu, char *argv); extern void cpu_init(struct cpu *cpu); extern void cpu_run(struct cpu *cpu); diff --git a/ls8/examples/sctest.ls8 b/ls8/examples/sctest.ls8 new file mode 100644 index 00000000..8f86bba7 --- /dev/null +++ b/ls8/examples/sctest.ls8 @@ -0,0 +1,86 @@ +# Code to test the Sprint Challenge +# +# Expected output: +# 1 +# 4 +# 5 + +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 diff --git a/ls8/examples/test.ls8 b/ls8/examples/test.ls8 new file mode 100644 index 00000000..1fa8814e --- /dev/null +++ b/ls8/examples/test.ls8 @@ -0,0 +1,21 @@ +10000010 # LDI R0,8 +00000000 +00001000 +10000010 # LDI R1,1 +00000001 +00000001 +10000010 # LDI R2,16 +00000010 +00010000 +10000010 # LDI R3,2 +00000011 +00000010 +11101000 # ADD R0, R1, 1 +00000000 +00000001 +00000001 +01000111 # PRN R0 +00000000 +01000111 # PRN R2 +00000010 +00000001 # HLT \ No newline at end of file diff --git a/ls8/ls8.c b/ls8/ls8.c index d24578ee..6322317c 100644 --- a/ls8/ls8.c +++ b/ls8/ls8.c @@ -1,16 +1,30 @@ #include +#include #include "cpu.h" /** * Main */ -int main(void) +int main(int argc, char *argv[]) { + if (argc < 2) + { + fprintf(stderr, "Not enough arguments.\nUsage: ./ls8 {file path from current directory}\n"); + exit(1); + } + + FILE *fptr; + if ((fptr = fopen(argv[1], "r")) == NULL) + { + printf("No such file.\n"); + exit(1); + } + fclose(fptr); struct cpu cpu; cpu_init(&cpu); - cpu_load(&cpu); + cpu_load(&cpu, argv[1]); cpu_run(&cpu); return 0; -} \ No newline at end of file +}