This commit is contained in:
Curt Spark 2025-11-06 18:50:35 +00:00
commit 9d02af574f
4 changed files with 483 additions and 0 deletions

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
main

161
README.org Normal file
View File

@ -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

310
main.c Normal file
View File

@ -0,0 +1,310 @@
#include <stdio.h>
#include <stdint.h>
#include <unistd.h>
#include <stdbool.h>
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;
}

11
run.sh Executable file
View File

@ -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