From 9d02af574f681af8ea0c4078f33ba1d728bfb751 Mon Sep 17 00:00:00 2001 From: cspark Date: Thu, 6 Nov 2025 18:50:35 +0000 Subject: [PATCH] Init --- .gitignore | 1 + README.org | 161 ++++++++++++++++++++++++++++ main.c | 310 +++++++++++++++++++++++++++++++++++++++++++++++++++++ run.sh | 11 ++ 4 files changed, 483 insertions(+) create mode 100644 .gitignore create mode 100644 README.org create mode 100644 main.c create mode 100755 run.sh diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ba2906d --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +main diff --git a/README.org b/README.org new file mode 100644 index 0000000..334de37 --- /dev/null +++ b/README.org @@ -0,0 +1,161 @@ +* Overture CPU Emulator + +This project implements an emulator for the Overture Instruction set architecture. + +This theoretical ISA is described and implemented in the steam game Turing Complete. + +** ISA Spec + +Overture has 1 byte-wide instructions. + +This contains 6 registers, of which the first 4 have designated uses. + +Additionally, it contains a single input and a single output, which together are mapped as if they were a 7th register. + + +The instructions can be split into 4 categories, which can be distinguished by the most significant two bits. + + 🔴🔴🟤🟤🟤🟤🟤🟤 Immidiate instructions. + 🔴🟢🟤🟤🟤🟤🟤🟤 Calculate instructions. + 🟢🔴🟤🟤🟤🟤🟤🟤 Copy instructions. + 🟢🟢🟤🟤🟤🟤🟤🟤 Condition instructions. + +* Immediate 🔴🔴🟤🟤🟤🟤🟤🟤 +These instructions represent a number between 0 and 63 (inclusive), which will be put into register 0 + +Assembly Mnemonics +Generally, these instructions are represented directly by using the value (a number between 0 and 63) directly, since they represent the actual number. + +* Calculate 🔴🟢🟤🟤🟤🟤🟤🟤 +This set of instructions use the 3 least significant bits to indicate which instruction to preform on the contents of Register 1 and Register 2 + + 🔴🟢🟤🟤🟤🔴🔴🔴 OR + 🔴🟢🟤🟤🟤🔴🔴🟢 NAND + 🔴🟢🟤🟤🟤🔴🟢🔴 NOR + 🔴🟢🟤🟤🟤🔴🟢🟢 AND + 🔴🟢🟤🟤🟤🟢🔴🔴 ADD + 🔴🟢🟤🟤🟤🟢🔴🟢 SUB + + +OR 🔴🟢🟤🟤🟤🔴🔴🔴 +A bitwise OR of the values stored in Register 1 and register 2, with the result being placed in Register 3 + +NAND 🔴🟢🟤🟤🟤🔴🔴🟢 +A bitwise NAND of the values stored in Register 1 and register 2, with the result being placed in Register 3 + +NOR 🔴🟢🟤🟤🟤🔴🟢🔴 +A bitwise NOR of the values stored in Register 1 and register 2, with the result being placed in Register 3 + +AND 🔴🟢🟤🟤🟤🔴🟢🟢 +A bitwise AND of the values stored in Register 1 and register 2, with the result being placed in Register 3 + +ADD 🔴🟢🟤🟤🟤🟢🔴🔴 +Mathematically adds the the values stored in Register 1 and register 2 together, (without support for a carry), with the result being placed in Register 3 + +SUB 🔴🟢🟤🟤🟤🟢🔴🟢 +Subtracts the the values stored in Register 2 from the value stored in register 1 using 2-complements logic, with the result being placed in Register 3 + +Assembly Mnemonics +The assembly mnemonics for these commands are simply these commands written on their own. + +#+BEGIN_SRC + or + nand + nor + and + add + sub + +const or 64 +const nand 65 +const nor 66 +const and 67 +const add 68 +const sub 69 +#+END_SRC + +* Copy 🟢🔴🟤🟤🟤🟤🟤🟤 +These instructions allow you to represent a copy action between a source location and a destination location. + +There are 7 possible sources to read from: + + 🟢🔴🔴🔴🔴🟤🟤🟤 Register 0 + 🟢🔴🔴🔴🟢🟤🟤🟤 Register 1 + 🟢🔴🔴🟢🔴🟤🟤🟤 Register 2 + 🟢🔴🔴🟢🟢🟤🟤🟤 Register 3 + 🟢🔴🟢🔴🔴🟤🟤🟤 Register 4 + 🟢🔴🟢🔴🟢🟤🟤🟤 Register 5 + 🟢🔴🟢🟢🔴🟤🟤🟤 Input + + +There are 7 possible destinations to write to: + + 🟢🔴🟤🟤🟤🔴🔴🔴 Register 0 + 🟢🔴🟤🟤🟤🔴🔴🟢 Register 1 + 🟢🔴🟤🟤🟤🔴🟢🔴 Register 2 + 🟢🔴🟤🟤🟤🔴🟢🟢 Register 3 + 🟢🔴🟤🟤🟤🟢🔴🔴 Register 4 + 🟢🔴🟤🟤🟤🟢🔴🟢 Register 5 + 🟢🔴🟤🟤🟤🟢🟢🔴 Output + + +Assembly Mnemonics + +A common method of representing the commands is by idividually specifying the parts of the command and adding them together using the OR operator. + +#+BEGIN_SRC +const cp 128 +const s0 0 +const s1 8 +const s2 16 +const s3 24 +const s4 32 +const s5 40 +const in 48 +const d0 0 +const d1 1 +const d2 2 +const d3 3 +const d4 4 +const d5 5 +const out 6 +#+END_SRC + +Example, which allows you to copy from Register 3 to Register 0 +cp|s3|d0 + +Off Label Usage +This section is non-normative + +Most common implementations are implemented in such way that if you specify the non-defined option as a source, a zero is read + + 🟢🔴🟢🟢🟢🟤🟤🟤 Clear Destination Register + +* Condition 🟢🟢🟤🟤🟤🟤🟤🟤 +This set of instructions allow you to jump to a certain point in your program. + +The destination of the jump is specified by the Register 0. + +The jump will only be performed if the value in Register 3 matches the condition set by the least significant 3 bits of the instruction. + + 🟢🟢🟤🟤🟤🔴🔴🔴 Never + 🟢🟢🟤🟤🟤🔴🔴🟢 Equals 0 + 🟢🟢🟤🟤🟤🔴🟢🔴 Less Than 0 + 🟢🟢🟤🟤🟤🔴🟢🟢 Less than or equals 0 + 🟢🟢🟤🟤🟤🟢🔴🔴 Always + 🟢🟢🟤🟤🟤🟢🔴🟢 Not equals 0 + 🟢🟢🟤🟤🟤🟢🟢🔴 Greater than or equals 0 + 🟢🟢🟤🟤🟤🟢🟢🟢 Greater than 0 + + +Assembly Mnemonics +#+BEGIN_SRC +const never 192 +const eq 193 +const less 194 +const less_eq 195 +const always 196 +const not_eq 197 +const greater_eq 198 +const greater 199 +#+END_SRC diff --git a/main.c b/main.c new file mode 100644 index 0000000..cb224bc --- /dev/null +++ b/main.c @@ -0,0 +1,310 @@ +#include +#include +#include +#include + +enum overture_mode { + IMMEDIATE, + CALCULATE, + COPY, + CONDITION +}; + +enum overture_calculate_mode { + OR, + NAND, + NOR, + AND, + ADD, + SUB +}; + +enum overture_condition_mode { + NEVER, + EQZ, + LTZ, + LTEQZ, + ALWAYS, + NEQZ, + GTEQZ, + GTZ +}; + +enum overture_registers { + R0, + R1, + R2, + R3, + R4, + R5, + INPUT_OUTPUT +}; + +void overture_immediate(uint8_t immediate, uint8_t* r0) { + *r0 = immediate; +} + +void overture_alu_calculate(enum overture_calculate_mode mode, uint8_t r1, uint8_t r2, uint8_t* r3) { + switch (mode) { + case OR: + *r3 = r1 | r2; + break; + case NAND: + *r3 = ~(r1 & r2); + break; + case NOR: + *r3 = ~(r1 | r2); + break; + case AND: + *r3 = r1 & r2; + break; + case ADD: + *r3 = r1 + r2; + break; + case SUB: + *r3 = r1 - r2; + break; + } +}; + +void overture_register_copy(uint8_t* source, uint8_t* destination) { + *destination = *source; +}; + +void overture_condition_jump(enum overture_condition_mode mode, uint8_t r0, uint8_t r3, uint8_t* program_counter) { + switch (mode) { + case NEVER: + return; + case EQZ: + if (r3 == 0) + *program_counter = r0; + break; + case LTZ: + if (r3 < 0) + *program_counter = r0; + break; + case LTEQZ: + if (r3 <= 0) + *program_counter = r0; + break; + case ALWAYS: + *program_counter = r0; + break; + case NEQZ: + if (r3 != 0) + *program_counter = r0; + break; + case GTEQZ: + if (r3 >= 0) + *program_counter = r0; + break; + case GTZ: + if (r3 > 0) + *program_counter = r0; + break; + }; +} + + +typedef struct overture_decoded_instruction { + uint8_t overture_mode : 2; + uint8_t left_operand : 3; + uint8_t right_operand : 3; + uint8_t combined_immediate : 6; +} overture_decoded_instruction; + +overture_decoded_instruction overture_decode_instruction(uint8_t current_instruction) { + overture_decoded_instruction instruction = { + .overture_mode = current_instruction >> 6, + .left_operand = (current_instruction << 2) >> 5, + .right_operand = (current_instruction << 5) >> 5, + .combined_immediate = (current_instruction << 2) >> 2, + }; + + return instruction; +}; + +void overture_fetch_instruction(uint8_t program_counter, uint8_t* current_instruction, uint8_t rom[256]) { + *current_instruction = rom[program_counter - 1]; +} + +typedef struct overture { + bool step_mode; + int clock_speed_hz; + uint8_t program_counter; + + uint8_t r0; + uint8_t r1; + uint8_t r2; + uint8_t r3; + uint8_t r4; + uint8_t r5; + + uint8_t input; + uint8_t output; + bool output_enable; + + uint8_t current_instruction; +} overture; + +void sleep_hz(int hz) { + usleep(1000000 / hz); +}; + +uint8_t* multiplex_source(uint8_t operand, overture* cpu) { + uint8_t* source; + + switch (operand) { + case R0: + source = &(cpu->r0); + break; + case R1: + source = &(cpu->r1); + break; + case R2: + source = &(cpu->r2); + break; + case R3: + source = &(cpu->r3); + break; + case R4: + source = &(cpu->r4); + break; + case R5: + source = &(cpu->r5); + break; + case INPUT_OUTPUT: + source = &(cpu->input); + break; + } + + return source; +} + +uint8_t* multiplex_destination(uint8_t operand, overture* cpu) { + uint8_t* destination; + + switch (operand) { + case R0: + destination = &(cpu->r0); + break; + case R1: + destination = &(cpu->r1); + break; + case R2: + destination = &(cpu->r2); + break; + case R3: + destination = &(cpu->r3); + break; + case R4: + destination = &(cpu->r4); + break; + case R5: + destination = &(cpu->r5); + break; + case INPUT_OUTPUT: + destination = &(cpu->output); + break; + } + + return destination; +} + +void overture_execute_instruction(overture_decoded_instruction instruction, overture* cpu) { + uint8_t* source_register = multiplex_source(instruction.left_operand, cpu); + uint8_t* destination_register = multiplex_destination(instruction.right_operand, cpu); + + switch (instruction.overture_mode) { + case IMMEDIATE: + overture_immediate(instruction.combined_immediate, &(cpu->r0)); + break; + case CALCULATE: + overture_alu_calculate(instruction.right_operand, cpu->r1, cpu->r2, &(cpu->r3)); + break; + case COPY: + overture_register_copy(source_register, destination_register); + + if (instruction.right_operand == INPUT_OUTPUT) { + cpu->output_enable = true; + } + break; + case CONDITION: + overture_condition_jump(instruction.right_operand, cpu->r0, cpu->r3, &(cpu->program_counter)); + break; + }; +} + +void cpu_diagnostics(overture* cpu) { + if (cpu->step_mode) { + printf("Step mode\n"); + } else { + printf("Clock speed: %dhz\n", cpu->clock_speed_hz); + } + printf("Program counter: %d\n\n", cpu->program_counter); + + printf("r0: %d\n", cpu->r0); + printf("r1: %d\n", cpu->r1); + printf("r2: %d\n", cpu->r2); + printf("r3: %d\n", cpu->r3); + printf("r4: %d\n", cpu->r4); + printf("r5: %d\n\n", cpu->r5); + + printf("input: %d\n", cpu->input); + printf("output: %d\n", cpu->output); + + printf("Current instruction: %d\n\n", cpu->current_instruction); +} + +void overture_cycle(overture* cpu, uint8_t rom[256]) { + if (cpu->program_counter == 255) { + cpu->program_counter = 0; + }; + cpu->program_counter += 1; + cpu->output_enable = false; + overture_fetch_instruction(cpu->program_counter, &(cpu->current_instruction), rom); + + overture_decoded_instruction instruction = overture_decode_instruction(cpu->current_instruction); + overture_execute_instruction(instruction, cpu); + + cpu_diagnostics(cpu); +} + +void overture_start_clock(overture* cpu, uint8_t rom[256]) { + while (true) { + if (cpu->step_mode) { + getchar(); + } else { + sleep_hz(cpu->clock_speed_hz); + } + + overture_cycle(cpu, rom); + }; +} + +int main() { + uint8_t rom[256] = { 63, 129, 7, 130, 68 }; + overture cpu = { + .step_mode = true, + .clock_speed_hz = 1, + .program_counter = 0, + + .r0 = 0, + .r1 = 0, + .r2 = 0, + .r3 = 0, + .r4 = 0, + .r5 = 0, + + .input = 0, + .output = 0, + .output_enable = true, + + .current_instruction = 0 + }; + + overture_start_clock(&cpu, rom); + + return 0; +} + diff --git a/run.sh b/run.sh new file mode 100755 index 0000000..e6889c4 --- /dev/null +++ b/run.sh @@ -0,0 +1,11 @@ +#!/bin/sh + +rm main + +# Release +#gcc -std=gnu99 -Wall -Werror main.c -o main + +# Debug +gcc -std=gnu99 -Wall -g -fsanitize=address main.c -o main + +./main