diff --git a/.vscode/ipch/78cb2fb1c87129d9/LS8.ipch b/.vscode/ipch/78cb2fb1c87129d9/LS8.ipch new file mode 100644 index 00000000..b0c77738 Binary files /dev/null and b/.vscode/ipch/78cb2fb1c87129d9/LS8.ipch differ diff --git a/.vscode/ipch/78cb2fb1c87129d9/mmap_address.bin b/.vscode/ipch/78cb2fb1c87129d9/mmap_address.bin new file mode 100644 index 00000000..862b8428 Binary files /dev/null and b/.vscode/ipch/78cb2fb1c87129d9/mmap_address.bin differ diff --git a/.vscode/ipch/bca32951ca4ad473/cpu.ipch b/.vscode/ipch/bca32951ca4ad473/cpu.ipch new file mode 100644 index 00000000..8981633b Binary files /dev/null and b/.vscode/ipch/bca32951ca4ad473/cpu.ipch differ diff --git a/.vscode/ipch/bca32951ca4ad473/mmap_address.bin b/.vscode/ipch/bca32951ca4ad473/mmap_address.bin new file mode 100644 index 00000000..71307aba Binary files /dev/null and b/.vscode/ipch/bca32951ca4ad473/mmap_address.bin differ diff --git a/.vscode/ipch/cc92be1e265297de/ls8.ipch b/.vscode/ipch/cc92be1e265297de/ls8.ipch new file mode 100644 index 00000000..677cf2d9 Binary files /dev/null and b/.vscode/ipch/cc92be1e265297de/ls8.ipch differ diff --git a/.vscode/ipch/cc92be1e265297de/mmap_address.bin b/.vscode/ipch/cc92be1e265297de/mmap_address.bin new file mode 100644 index 00000000..71307aba Binary files /dev/null and b/.vscode/ipch/cc92be1e265297de/mmap_address.bin differ diff --git a/.vscode/ipch/f6c20eca54484328/CPU.ipch b/.vscode/ipch/f6c20eca54484328/CPU.ipch new file mode 100644 index 00000000..a51581cb Binary files /dev/null and b/.vscode/ipch/f6c20eca54484328/CPU.ipch differ diff --git a/.vscode/ipch/f6c20eca54484328/mmap_address.bin b/.vscode/ipch/f6c20eca54484328/mmap_address.bin new file mode 100644 index 00000000..862b8428 Binary files /dev/null and b/.vscode/ipch/f6c20eca54484328/mmap_address.bin differ diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 00000000..691a8f68 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "C_Cpp.errorSquiggles": "Disabled" +} \ No newline at end of file diff --git a/ls8/README.md b/ls8/README.md index bb8d4fb4..862749ab 100644 --- a/ls8/README.md +++ b/ls8/README.md @@ -66,7 +66,7 @@ but you'll have to implement those three above instructions first! * Read this whole file. * Skim the spec. -## Step 1: Implement `struct cpu` in `cpu.h` +## ✔ Step 1: Implement `struct cpu` in `cpu.h` This structure holds information about the CPU and associated components. @@ -79,14 +79,14 @@ unsigned char x; (Weirdly in C, if you don't specific `signed` or `unsigned` with a `char`, it's up to the compiler which it uses.) -## Step 2: Add RAM functions +## ✔ Step 2: Add RAM functions In `cpu.c`, add functions `cpu_ram_read()` and `cpu_ram_write()` that access the RAM inside the `struct cpu`. We'll make use of these helper function later. -## Step 3: Implement the core of `cpu_init()` +## ✔ Step 3: Implement the core of `cpu_init()` The `cpu_init()` function takes a pointer to a `struct cpu` and initializes it as necessary. At first, the PC, registers, and RAM should be cleared to zero. @@ -123,7 +123,7 @@ the instruction opcode. See the LS-8 spec for details. Add the `HLT` instruction to `cpu.h`. In `cpu_run()` in your switch, exit the loop if a `HLT` instruction is -encountered, regardless of whether or not there are more lines of code in the LS-8 program you loaded. +encountered, regardless of whether or not there are more lines of code in the LS-8 program you loaded. We can consider `HLT` to be similar to a `return` or `exit()` in that we stop whatever we are doing, wherever we are. @@ -178,7 +178,7 @@ so you can look in `argv[1]` for the name of the file to load. > Bonus: check to make sure the user has put a command line argument where you > expect, and print an error and exit if they didn't. -In `load_cpu()`, you will now want to use those command line arguments to open a file, read in its contents line by line, and save appropriate data into RAM. +In `load_cpu()`, you will now want to use those command line arguments to open a file, read in its contents line by line, and save appropriate data into RAM. As you process lines from the file, you should be on the lookout for blank lines (ignore them), and you should ignore everything after a `#`, since that's a @@ -219,7 +219,7 @@ Check the LS-8 spec for what the `MUL` instruction does. > Note: `MUL` is the responsiblity of the ALU, so it would be nice if your code eventually called the `alu()` function with appropriate arguments to get the work done. -> +> ## Step 10: Beautify your `cpu_run()` loop, if needed @@ -312,10 +312,10 @@ a high address) and grows _downward_ as things are pushed on. The LS-8 is no exception to this. Implement a system stack per the spec. Add `PUSH` and `POP` instructions. Read - the beginning of the spec to see which register is the stack pointer. - -* Values themselves should be saved in the ***portion of RAM*** _that is allocated for the stack_. - - Use the stack pointer to modify the correct block of memory. + the beginning of the spec to see which register is the stack pointer. + +* Values themselves should be saved in the ***portion of RAM*** _that is allocated for the stack_. + - Use the stack pointer to modify the correct block of memory. - Make sure you update the stack pointer appropriately as you `PUSH` and `POP` items to and from the stack. If you run `./ls8 examples/stack.ls8` you should see the output: @@ -333,7 +333,7 @@ enable you to jump to any address with the `CALL` instruction, and then return back to where you called from with the `RET` instruction. This enables you to create reusable functions. -Subroutines have many similarities to functions in higher-level languages. Just as a function in C, JavaScript or Python will jump from the function call, to its definition, and then return back to the line of code following the call, subroutines will also allow us to execute instructions non-sequentially. +Subroutines have many similarities to functions in higher-level languages. Just as a function in C, JavaScript or Python will jump from the function call, to its definition, and then return back to the line of code following the call, subroutines will also allow us to execute instructions non-sequentially. The stack is used to hold the return address used by `RET`, so you **must** implement the stack in step 11, first. Then, add subroutine instructions `CALL` and `RET`. @@ -343,8 +343,8 @@ problem is that some instructions want to execute and move to the next instruction like normal, but others, like `CALL` and `JMP` want to go to a specific address. - > Note: `CALL` is very similar to the `JMP` instruction. However, there is one key difference between them. Can you find it in the specs? - > + > Note: `CALL` is very similar to the `JMP` instruction. However, there is one key difference between them. Can you find it in the specs? + > * In **any** case where the instruction handler sets the `PC` directly, you _don't_ want to advance the PC to the next instruction. So you'll have to set up diff --git a/ls8/cpu.c b/ls8/cpu.c index bdb1f506..e3642580 100644 --- a/ls8/cpu.c +++ b/ls8/cpu.c @@ -1,43 +1,79 @@ + +#include +#include +#include #include "cpu.h" #define DATA_LEN 6 +#define SP 7 -/** - * Load the binary bytes from a .ls8 source file into a RAM array - */ -void cpu_load(struct cpu *cpu) +unsigned char cpu_ram_read(struct cpu *cpu, unsigned char mar) { - char data[DATA_LEN] = { - // From print8.ls8 - 0b10000010, // LDI R0,8 - 0b00000000, - 0b00001000, - 0b01000111, // PRN R0 - 0b00000000, - 0b00000001 // HLT - }; + // mar = Memory Address registersister, holds the memory address + // we're reading or writing - int address = 0; + // return value from ram specified by mar + return cpu->ram[mar]; +} - for (int i = 0; i < DATA_LEN; i++) { - cpu->ram[address++] = data[i]; - } +void cpu_ram_write(struct cpu *cpu, unsigned char index, unsigned char mdr) +{ + // mdr = Memory Data registersister, holds the value + // to write or the value just read + + // write mdr in ram + cpu->ram[index] = mdr; +} + +void cpu_push(struct cpu *cpu, unsigned char val) +{ + // Decrement SP + cpu->registers[SP]--; + // Copy the value in the given reg to the address pointed to by SP; + cpu_ram_write(cpu, cpu->registers[SP], val); +} - // TODO: Replace this with something less hard-coded +unsigned char cpu_pop(struct cpu *cpu) +{ + // Read last value from stack + unsigned char val = cpu_ram_read(cpu, cpu->registers[SP]); + // Increment SP + cpu->registers[SP]++; + return val; } /** - * ALU + * Load the binary bytes from a .ls8 source file into a RAM array */ -void alu(struct cpu *cpu, enum alu_op op, unsigned char regA, unsigned char regB) +void cpu_load(struct cpu *cpu, char *filename) { - switch (op) { - case ALU_MUL: - // TODO - break; + FILE *fp = fopen(filename, "r"); + + if (fp == NULL) + { + fprintf(stderr, "ls8: error opening file: %s\n", filename); + exit(2); + } + + char line[8192]; // to hold individual lines in the file + int address = 0; + + while (fgets(line, sizeof(line), fp) != NULL) + { + char *endptr; // to keep track of non-numbers in the file + // converts str to number + unsigned char byte = strtoul(line, &endptr, 2); + + // prevents unnecessary lines being stored on ram + if (endptr == line) + { + continue; + } - // TODO: implement more ALU ops + cpu->ram[address++] = byte; } + + fclose(fp); } /** @@ -45,16 +81,62 @@ 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 - - while (running) { - // TODO - // 1. Get the value of the current instruction (in address PC). - // 2. Figure out how many operands this next instruction requires - // 3. Get the appropriate value(s) of the operands following this instruction - // 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. + // True until we get a HLT instruction + int running = 1; + unsigned char command, operand1, operand2; + + while (running) + { + command = cpu_ram_read(cpu, cpu->PC); + + operand1 = cpu_ram_read(cpu, cpu->PC + 1); + operand2 = cpu_ram_read(cpu, cpu->PC + 2); + + switch (command) + { + case LDI: + // sets value of operand1 to the registers[operand1] + cpu->registers[operand1] = operand2; + // Move the PC to the next instruction. + cpu->PC += 3; + break; + case PRN: + printf("%d\n", cpu->registers[operand1]); + // Move the PC to the next instruction. + cpu->PC += 2; + break; + case MUL: + cpu->registers[operand1] *= operand2; + cpu->PC += 3; + break; + case ADD: + cpu->registers[operand1] += operand2; + cpu->PC += 3; + break; + + case PUSH: + cpu_push(cpu, cpu->registers[operand1]); + cpu->PC += 2; + break; + + case POP: + // Copy the value from the address pointed to by SP to the given registers + cpu->registers[operand1] = cpu_pop(cpu); + cpu->PC += 2; + break; + + case HLT: + // Set running to false to stop program + running = 0; + // Move the PC to the next instruction. + cpu->PC += 1; + break; + + default: + printf("KIT: I can't do that Michael\n"); + exit(1); + break; + } } } @@ -63,5 +145,18 @@ void cpu_run(struct cpu *cpu) */ void cpu_init(struct cpu *cpu) { - // TODO: Initialize the PC and other special registers -} + // R0-R6 are cleared to 0 + for (int i = 0; i < 6; i++) + { + cpu->registers[i] = 0; + } + + // R7 is set to 0xF4 + cpu->registers[7] = 0xF4; + + // PC is cleared to 0 + cpu->PC = 0; + + // RAM is cleared to 0 + memset(cpu->ram, 0, sizeof(cpu->ram)); +} \ No newline at end of file diff --git a/ls8/cpu.h b/ls8/cpu.h index 46e49c44..6a1e7527 100644 --- a/ls8/cpu.h +++ b/ls8/cpu.h @@ -1,18 +1,38 @@ #ifndef _CPU_H_ #define _CPU_H_ -// Holds all information about the CPU -struct cpu { - // TODO - // PC - // registers (array) - // ram (array) +// DONE✔: Holds all information about the CPU +struct cpu +{ + // DONE✔: PC + unsigned int PC; + /* DONE✔: registers (array) // R0 - R7 + R5 is reserved for the interrupt mask (IM) + R6 is reserved for the interrupt status (IS) + R7 is reserved for the stack pointer (SP) + */ + unsigned int IM; + unsigned int IS; + unsigned int SP; + unsigned char registers[8]; + // DONE✔: ram (array) // 8 bit address + // that can hold up to 256 bytes of RAM total + unsigned char ram[256]; }; // ALU operations -enum alu_op { - ALU_MUL - // Add more here +enum alu_op +{ + ALU_MUL, // what is this??? + ALU_ADD, // Add more here + // not done yet, just added + // ALU_NOP, + // ALU_NOT, + // ALU_POP, + // ALU_PRA, + // ALU_PRN, + // ALU_PUSH, + // ALU_RET, }; // Instructions @@ -20,14 +40,20 @@ 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 PRN 0b01000111 +#define HLT 0b00000001 // TODO: more instructions here. These can be used in cpu_run(). +#define MUL 0b10100010 +#define PUSH 0b01000101 +//POP register - pop the value at the top of the stack into the given reg. +#define POP 0b01000110 +#define CALL 0b01010000 +#define RET 0b00010001 +#define ADD 0b10100000 // 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/ls8.c b/ls8/ls8.c index d24578ee..04a47cc7 100644 --- a/ls8/ls8.c +++ b/ls8/ls8.c @@ -1,15 +1,25 @@ #include +#include #include "cpu.h" - /** * Main */ -int main(void) +int main(int argc, char **argv) { struct cpu cpu; + // check to make sure the user has put a command line argument where you + // expect, and print an error and exit if they didn't + if (argc != 2) + { + fprintf(stderr, "usage: ls8 filename\n"); + exit(1); + } + + char *filename = argv[1]; + cpu_init(&cpu); - cpu_load(&cpu); + cpu_load(&cpu, filename); cpu_run(&cpu); return 0; diff --git a/ls8/examples/mult.ls8 b/ls8/mult.ls8 similarity index 100% rename from ls8/examples/mult.ls8 rename to ls8/mult.ls8 diff --git a/ls8/examples/stack.ls8 b/ls8/stack.ls8 similarity index 100% rename from ls8/examples/stack.ls8 rename to ls8/stack.ls8