diff --git a/.idea/.name b/.idea/.name new file mode 100644 index 0000000..233187a --- /dev/null +++ b/.idea/.name @@ -0,0 +1 @@ +RISCB \ No newline at end of file diff --git a/.idea/discord.xml b/.idea/discord.xml new file mode 100644 index 0000000..30bab2a --- /dev/null +++ b/.idea/discord.xml @@ -0,0 +1,7 @@ + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml index 9957783..bff9a85 100644 --- a/.idea/modules.xml +++ b/.idea/modules.xml @@ -2,6 +2,7 @@ + diff --git a/CMakeLists.txt b/CMakeLists.txt index efca100..2446374 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,11 +1,19 @@ cmake_minimum_required(VERSION 3.31) -project(sdlzasedaco C) +project(RISCB C) set(CMAKE_C_STANDARD 23) find_package(PkgConfig REQUIRED) pkg_check_modules(SDL2 REQUIRED sdl2) -add_executable(sdlzasedaco main.c) # Ensure the target is defined before linking +add_executable(RISCB main.c + util/font.c + util/font.h + assembler/assembler.c + assembler/assembler.h + cpu/memory.c + cpu/memory.h + cpu/core.c + cpu/core.h) # Ensure the target is defined before linking -target_link_libraries(sdlzasedaco SDL2 SDL2_ttf) +target_link_libraries(RISCB SDL2 SDL2_ttf m) diff --git a/PublicPixel.ttf b/PublicPixel.ttf new file mode 100644 index 0000000..f6c69fe Binary files /dev/null and b/PublicPixel.ttf differ diff --git a/assembler/assembler.c b/assembler/assembler.c new file mode 100644 index 0000000..aaf1d3c --- /dev/null +++ b/assembler/assembler.c @@ -0,0 +1,525 @@ +// +// Created by bruno on 1.2.2025. +// + +#include "assembler.h" + +Label labels[MAX_LABELS]; +int labelCount = 0; + +// +// Helper functions for string manipulation +// +void trim(char *s) { + // Remove leading whitespace + while (isspace((unsigned char) *s)) s++; + + // Remove trailing whitespace + char *end = s + strlen(s) - 1; + while (end > s && isspace((unsigned char) *end)) { + *end = '\0'; + end--; + } +} + +// Look up a label by name; returns -1 if not found. +int lookupLabel(const char *name) { + for (int i = 0; i < labelCount; i++) { + if (strcmp(labels[i].name, name) == 0) + return labels[i].address; + } + return -1; +} + +// Add a label to the table +void addLabel(const char *name, int address) { + if (labelCount >= MAX_LABELS) { + fprintf(stderr, "Too many labels!\n"); + exit(1); + } + strncpy(labels[labelCount].name, name, sizeof(labels[labelCount].name)); + labels[labelCount].address = address; + labelCount++; +} + +// +// Parse a register string (e.g., "R0", "R1", etc.) and return it's number. +// Returns -1 on error. +int parseRegister(const char *token) { + if (token[0] == 'R' || token[0] == 'r') { + int reg = atoi(token + 1); + if (reg >= 0 && reg < REG_COUNT) + return reg; + } + return -1; +} + +// Parse an immediate value (supports decimal and 0x... hexadecimal) +uint8_t parseImmediate(const char *token) { + int value; + if (strlen(token) > 2 && token[0] == '0' && (token[1] == 'x' || token[1] == 'X')) + sscanf(token, "%x", &value); + else + sscanf(token, "%d", &value); + return (uint8_t) value; +} + +void toUpperCase(char *string) { + while (*string) { + if (*string > 0x60 && *string < 0x7b) { + (*string) -= 0x20; + } + } +} + +// +// Map an instruction mnemonic (string) to its opcode value and expected operand types. +// For simplicity, we will return the opcode value and then in our parser we’ll decide how many operands to expect. +// (In a full assembler you might use a more sophisticated data structure.) +// +int getOpcode(char *mnemonic) { + toUpperCase(mnemonic); + if (strcmp(mnemonic, "BRK") == 0) + return BRK; + else if (strcmp(mnemonic, "NOP") == 0) + return NOP; + else if (strcmp(mnemonic, "MOV") == 0) + return -2; // Special case: we must decide between MOV_RN_IMM, MOV_RN_RM, MOV_RN_ADDR, MOV_ADDR_RN + else if (strcmp(mnemonic, "SWAP") == 0) + return SWAP; + else if (strcmp(mnemonic, "SWAPN") == 0) + return SWAPN; + else if (strcmp(mnemonic, "ADD") == 0) + return -3; // Special: decide between ADD_RN_RM and ADD_RN_IMM + else if (strcmp(mnemonic, "SUB") == 0) + return -4; // Special: decide between SUB_RN_RM and SUB_RN_IMM + else if (strcmp(mnemonic, "MUL") == 0) + return -5; // Special: decide between MUL_RN_RM and MUL_RN_IMM + else if (strcmp(mnemonic, "DIV") == 0) + return -6; // Special: decide between DIV_RN_RM and DIV_RN_IMM + else if (strcmp(mnemonic, "MOD") == 0) + return -7; // Special: decide between MOD_RN_RM and MOD_RN_IMM + else if (strcmp(mnemonic, "NEG") == 0) + return NEG_RN; + else if (strcmp(mnemonic, "AND") == 0) + return -8; // Special: decide between AND_RN_RM and AND_RN_IMM + else if (strcmp(mnemonic, "OR") == 0) + return -9; // Special: decide between OR_RN_RM and OR_RN_IMM + else if (strcmp(mnemonic, "XOR") == 0) + return -10; // Special: decide between XOR_RN_RM and XOR_RN_IMM + else if (strcmp(mnemonic, "NOT") == 0) + return NOT_RN; + else if (strcmp(mnemonic, "SHL") == 0) + return SHL_RN_IMM; + else if (strcmp(mnemonic, "SHR") == 0) + return SHR_RN_IMM; + else if (strcmp(mnemonic, "SAR") == 0) + return SAR_RN_IMM; + else if (strcmp(mnemonic, "JMP") == 0) + return JMP; + else if (strcmp(mnemonic, "CMP") == 0) + return CMP; + else if (strcmp(mnemonic, "JE") == 0) + return JE; + else if (strcmp(mnemonic, "JNE") == 0) + return JNE; + else if (strcmp(mnemonic, "JG") == 0) + return JG; + else if (strcmp(mnemonic, "JL") == 0) + return JL; + else if (strcmp(mnemonic, "JGE") == 0) + return JGE; + else if (strcmp(mnemonic, "JLE") == 0) + return JLE; + else if (strcmp(mnemonic, "CALL") == 0) + return CALL; + else if (strcmp(mnemonic, "RET") == 0) + return RET; + else if (strcmp(mnemonic, "PUSH") == 0) + return PUSH; + else if (strcmp(mnemonic, "POP") == 0) + return POP; + else if (strcmp(mnemonic, "PUSHF") == 0) + return PUSHF; + else if (strcmp(mnemonic, "POPF") == 0) + return POPF; + else { + return -1; + } +} + +// +// In this simple assembler, some instructions share a mnemonic, and we must choose the correct opcode +// based on the type of the operand (register vs. immediate vs. memory). +// The following helper functions decide that, given two operands (as strings). +// +// For example, "MOV Rn, 42" should choose MOV_RN_IMM, while "MOV Rn, Rm" should choose MOV_RN_RM. +// We assume that memory addresses are written in square brackets, e.g. "[123]". +// +int resolveMOV(const char *dest, const char *src) { + // If dest starts with '[' then it is a memory destination. + if (dest[0] == '[') return MOV_ADDR_RN; // actually, MOV [Addr], Rn expects Rn in second operand + // Otherwise, dest is a register. + // Now, check src: + if (src[0] == 'R' || src[0] == 'r') { + return MOV_RN_RM; + } else if (src[0] == '[') { + return MOV_RN_ADDR; + } else { + return MOV_RN_IMM; + } +} + +int resolveALU(int baseOpcode, const char *src) { + // baseOpcode is one of our special negative values for ADD, SUB, etc. + if (src[0] == 'R' || src[0] == 'r') + switch (baseOpcode) { + case -3: + return ADD_RN_RM; + case -4: + return SUB_RN_RM; + case -5: + return MUL_RN_RM; + case -6: + return DIV_RN_RM; + case -7: + return MOD_RN_RM; + case -8: + return AND_RN_RM; + case -9: + return OR_RN_RM; + case -10: + return XOR_RN_RM; + default: + return -1; + } + else + switch (baseOpcode) { + case -3: + return ADD_RN_IMM; + case -4: + return SUB_RN_IMM; + case -5: + return MUL_RN_IMM; + case -6: + return DIV_RN_IMM; + case -7: + return MOD_RN_IMM; + case -8: + return AND_RN_IMM; + case -9: + return OR_RN_IMM; + case -10: + return XOR_RN_IMM; + default: + return -1; + } +} + +// Reads a single line from the source string. +const char *readLine(const char *source, char *buffer, size_t maxLen) { + size_t i = 0; + while (*source && *source != '\n' && i < maxLen - 1) { + buffer[i++] = *source++; + } + buffer[i] = '\0'; + return (*source == '\n') ? source + 1 : source; +} + +// +// The first pass scans the assembly source file to record all labels and their addresses. +// The address is simply the offset into the output machine code buffer. +// For this example, every instruction is assumed to have a fixed length (opcode plus operand bytes). +// +int firstPass(const char *source) { + char line[MAX_LINE_LENGTH]; + int addr = 0; + const char *ptr = source; + + while (*ptr) { + // Read a line from the source string + ptr = readLine(ptr, line, sizeof(line)); + trim(line); + + if (line[0] == '\0' || line[0] == ';' || line[0] == '#') + continue; // Skip empty or comment lines + + char *colon = strchr(line, ':'); + if (colon != NULL) { + *colon = '\0'; + trim(line); + addLabel(line, addr); + + char *rest = colon + 1; + trim(rest); + if (strlen(rest) == 0) + continue; + strcpy(line, rest); + } + + // For simplicity, we assume each instruction (with its operands) takes a fixed number of bytes. + // Here we calculate the number of bytes by looking at the opcode mnemonic. + // (A more robust approach would have a table for instruction sizes.) + char mnemonic[32]; + sscanf(line, "%31s", mnemonic); + + int opcode = getOpcode(mnemonic); + if (opcode == -2) { + // MOV: two operands separated by comma + // e.g. MOV R1, 42 + // We add 3 bytes: opcode, operand1, operand2. + addr += 3; + } else if (opcode == -3 || opcode == -4 || opcode == -5 || opcode == -6 || + opcode == -7 || opcode == -8 || opcode == -9 || opcode == -10) { + // ALU instructions with two operands: 3 bytes. + addr += 3; + } else if (opcode == NEG_RN || opcode == SWAPN || opcode == NOT_RN) { + // One operand: 2 bytes. + addr += 2; + } else if (opcode == SWAP || opcode == CMP) { + // Two operands: 3 bytes. + addr += 3; + } else if (opcode == SHL_RN_IMM || opcode == SHR_RN_IMM || + opcode == SAR_RN_IMM) { + addr += 3; + } else if (opcode == JMP || opcode == JE || opcode == JNE || + opcode == JG || opcode == JL || opcode == JGE || opcode == JLE || + opcode == CALL) { + // Jump or call: 2 bytes (opcode and one byte address/immediate). + addr += 2; + } else if (opcode == RET || opcode == PUSHF || opcode == POPF) { + addr += 1; + } else if (opcode == PUSH || opcode == POP) { + addr += 2; + } else { + // For other instructions, we assume 3 bytes. + addr += 3; + } + } + return addr; +} + +// +// The second pass actually translates the assembly instructions to machine code. +// The machine code is written into the provided buffer. (It must be large enough.) +// +int secondPass(const char *source, uint8_t *code) { + char line[MAX_LINE_LENGTH]; + int addr = 0; + const char *ptr = source; + + while (*ptr) { + ptr = readLine(ptr, line, sizeof(line)); + trim(line); + + if (line[0] == '\0' || line[0] == ';' || line[0] == '#') + continue; + + char *colon = strchr(line, ':'); + if (colon != NULL) { + *colon = ' '; + } + + if (strlen(line) == 0) + continue; + + char *token = strtok(line, " ,"); + if (!token) + continue; + + char mnemonic[32]; + strncpy(mnemonic, token, sizeof(mnemonic)); + int opcode = getOpcode(mnemonic); + code[addr++] = opcode; + + // Handle instructions that need operand disambiguation. + if (strcmp(mnemonic, "MOV") == 0) { + // Get first operand. + char *dest = strtok(NULL, " ,"); + char *src = strtok(NULL, " ,"); + if (!dest || !src) { + fprintf(stderr, "Error: MOV requires two operands.\n"); + exit(1); + } + int opcode2 = resolveMOV(dest, src); + code[addr++] = opcode2; + // For the MOV instructions we decide that: + // - For MOV_RN_IMM: operand bytes: [register, immediate] + // - For MOV_RN_RM: operand bytes: [dest register, src register] + // - For MOV_RN_ADDR: operand bytes: [dest register, address] + // - For MOV_ADDR_RN: operand bytes: [address, register] + if (opcode2 == MOV_RN_IMM) { + int reg = parseRegister(dest); + uint8_t imm = parseImmediate(src); + code[addr++] = reg; + code[addr++] = imm; + } else if (opcode2 == MOV_RN_RM) { + int regDest = parseRegister(dest); + int regSrc = parseRegister(src); + code[addr++] = regDest; + code[addr++] = regSrc; + } else if (opcode2 == MOV_RN_ADDR) { + // src is memory reference like "[123]" + int regDest = parseRegister(dest); + // Remove the brackets. + char addrStr[32]; + strncpy(addrStr, src + 1, strlen(src) - 2); + addrStr[strlen(src) - 2] = '\0'; + uint8_t memAddr = parseImmediate(addrStr); + code[addr++] = regDest; + code[addr++] = memAddr; + } else if (opcode2 == MOV_ADDR_RN) { + // dest is a memory reference, src is a register. + // Remove brackets from dest. + char addrStr[32]; + strncpy(addrStr, dest + 1, strlen(dest) - 2); + addrStr[strlen(dest) - 2] = '\0'; + uint8_t memAddr = parseImmediate(addrStr); + int regSrc = parseRegister(src); + code[addr++] = memAddr; + code[addr++] = regSrc; + } + } else if (strcmp(mnemonic, "ADD") == 0 || + strcmp(mnemonic, "SUB") == 0 || + strcmp(mnemonic, "MUL") == 0 || + strcmp(mnemonic, "DIV") == 0 || + strcmp(mnemonic, "MOD") == 0 || + strcmp(mnemonic, "AND") == 0 || + strcmp(mnemonic, "OR") == 0 || + strcmp(mnemonic, "XOR") == 0) { + // ALU instructions with two operands. + char *dest = strtok(NULL, " ,"); + char *src = strtok(NULL, " ,"); + if (!dest || !src) { + fprintf(stderr, "Error: %s requires two operands.\n", mnemonic); + exit(1); + } + int baseOpcode; + if (strcmp(mnemonic, "ADD") == 0) baseOpcode = -3; + else if (strcmp(mnemonic, "SUB") == 0) baseOpcode = -4; + else if (strcmp(mnemonic, "MUL") == 0) baseOpcode = -5; + else if (strcmp(mnemonic, "DIV") == 0) baseOpcode = -6; + else if (strcmp(mnemonic, "MOD") == 0) baseOpcode = -7; + else if (strcmp(mnemonic, "AND") == 0) baseOpcode = -8; + else if (strcmp(mnemonic, "OR") == 0) baseOpcode = -9; + else if (strcmp(mnemonic, "XOR") == 0) baseOpcode = -10; + else baseOpcode = -1; + int opcode3 = resolveALU(baseOpcode, src); + code[addr++] = opcode3; + int regDest = parseRegister(dest); + code[addr++] = regDest; + // For a register source, encode the register; for an immediate, encode the value. + if (src[0] == 'R' || src[0] == 'r') { + int regSrc = parseRegister(src); + code[addr++] = regSrc; + } else { + uint8_t imm = parseImmediate(src); + code[addr++] = imm; + } + } else if (strcmp(mnemonic, "NEG") == 0 || + strcmp(mnemonic, "SWAPN") == 0 || + strcmp(mnemonic, "NOT") == 0) { + // One operand instructions. + char *op = strtok(NULL, " ,"); + if (!op) { + fprintf(stderr, "Error: %s requires one operand.\n", mnemonic); + exit(1); + } + int opcode4 = getOpcode(mnemonic); + code[addr++] = opcode4; + int reg = parseRegister(op); + code[addr++] = reg; + } else if (strcmp(mnemonic, "SWAP") == 0 || strcmp(mnemonic, "CMP") == 0) { + // Two operand instructions: both registers. + char *op1 = strtok(NULL, " ,"); + char *op2 = strtok(NULL, " ,"); + if (!op1 || !op2) { + fprintf(stderr, "Error: %s requires two operands.\n", mnemonic); + exit(1); + } + int opcode5 = getOpcode(mnemonic); + code[addr++] = opcode5; + int r1 = parseRegister(op1); + int r2 = parseRegister(op2); + code[addr++] = r1; + code[addr++] = r2; + } else if (strcmp(mnemonic, "SHL") == 0 || + strcmp(mnemonic, "SHR") == 0 || + strcmp(mnemonic, "SAR") == 0 || + strcmp(mnemonic, "SHRS") == 0) { + // Shift instructions: one register operand and one immediate. + char *regToken = strtok(NULL, " ,"); + char *immToken = strtok(NULL, " ,"); + if (!regToken || !immToken) { + fprintf(stderr, "Error: %s requires two operands.\n", mnemonic); + exit(1); + } + int opcode6 = getOpcode(mnemonic); + code[addr++] = opcode6; + int reg = parseRegister(regToken); + code[addr++] = reg; + uint8_t imm = parseImmediate(immToken); + code[addr++] = imm; + } else if (strcmp(mnemonic, "JMP") == 0 || + strcmp(mnemonic, "JE") == 0 || + strcmp(mnemonic, "JNE") == 0 || + strcmp(mnemonic, "JG") == 0 || + strcmp(mnemonic, "JL") == 0 || + strcmp(mnemonic, "JGE") == 0 || + strcmp(mnemonic, "JLE") == 0 || + strcmp(mnemonic, "CALL") == 0) { + // Jump instructions: one operand which may be a label or an immediate address. + char *operand = strtok(NULL, " ,"); + if (!operand) { + fprintf(stderr, "Error: %s requires an operand.\n", mnemonic); + exit(1); + } + int opcode7 = getOpcode(mnemonic); + code[addr++] = opcode7; + // If the operand is not a number, assume it is a label. + if (!isdigit(operand[0])) { + int labelAddr = lookupLabel(operand); + if (labelAddr < 0) { + fprintf(stderr, "Error: undefined label '%s'\n", operand); + exit(1); + } + code[addr++] = (uint8_t) labelAddr; + } else { + uint8_t imm = parseImmediate(operand); + code[addr++] = imm; + } + } else if (strcmp(mnemonic, "RET") == 0 || + strcmp(mnemonic, "PUSHF") == 0 || + strcmp(mnemonic, "POPF") == 0) { + // Instructions with no operand. + int opcode8 = getOpcode(mnemonic); + code[addr++] = opcode8; + } else if (strcmp(mnemonic, "PUSH") == 0 || + strcmp(mnemonic, "POP") == 0) { + // One operand (a register) + char *regToken = strtok(NULL, " ,"); + if (!regToken) { + fprintf(stderr, "Error: %s requires a register operand.\n", mnemonic); + exit(1); + } + int opcode9 = getOpcode(mnemonic); + code[addr++] = opcode9; + int reg = parseRegister(regToken); + code[addr++] = reg; + } else { + fprintf(stderr, "Error: Unknown instruction '%s'\n", mnemonic); + exit(1); + } + } + return addr; +} + +void completePass(const char *input, CPU *cpu) { + // First pass: determine label addresses. + firstPass(input); + + memset(cpu->memory, 0, MEM_SIZE); + + // Second pass: generate machine code. + secondPass(input, cpu->memory); +} \ No newline at end of file diff --git a/assembler/assembler.h b/assembler/assembler.h new file mode 100644 index 0000000..350f934 --- /dev/null +++ b/assembler/assembler.h @@ -0,0 +1,87 @@ +// +// Created by bruno on 1.2.2025. +// + +#ifndef RISCB_ASSEMBLER_H +#define RISCB_ASSEMBLER_H + +#include +#include +#include +#include +#include +#include "../cpu/core.h" + +// +// Definitions used by the CPU and the assembler +// + +#define MAX_LINE_LENGTH 256 +#define MAX_LABELS 1024 + +// +// Label table entry +// +typedef struct { + char name[64]; + int address; // address (in the output machine code) +} Label; + +extern Label labels[MAX_LABELS]; +extern int labelCount; + +// +// Helper functions for string manipulation +// +void trim(char *s); + +// Look up a label by name; returns -1 if not found. +int lookupLabel(const char *name); + +// Add a label to the table +void addLabel(const char *name, int address); + +// +// Parse a register string (e.g., "R0", "R1", etc.) and return it's number. +// Returns -1 on error. +int parseRegister(const char *token); + +// Parse an immediate value (supports decimal and 0x... hexadecimal) +uint8_t parseImmediate(const char *token); + +// +// Map an instruction mnemonic (string) to its opcode value and expected operand types. +// For simplicity, we will return the opcode value and then in our parser we’ll decide how many operands to expect. +// (In a full assembler you might use a more sophisticated data structure.) +// +int getOpcode(char *mnemonic); + +// +// In this simple assembler, some instructions share a mnemonic, and we must choose the correct opcode +// based on the type of the operand (register vs. immediate vs. memory). +// The following helper functions decide that, given two operands (as strings). +// +// For example, "MOV Rn, 42" should choose MOV_RN_IMM, while "MOV Rn, Rm" should choose MOV_RN_RM. +// We assume that memory addresses are written in square brackets, e.g. "[123]". +// +int resolveMOV(const char *dest, const char *src); + +int resolveALU(int baseOpcode, const char *src); + +// +// The first pass scans the assembly source file to record all labels and their addresses. +// The address is simply the offset into the output machine code buffer. +// For this example, every instruction is assumed to have a fixed length (opcode plus operand bytes). +// +int firstPass(const char *source); + +// +// The second pass actually translates the assembly instructions to machine code. +// The machine code is written into the provided buffer. (It must be large enough.) +// +int secondPass(const char *source, uint8_t *code); + +void completePass(const char *input, CPU *cpu); + + +#endif //RISCB_ASSEMBLER_H diff --git a/cpu/core.c b/cpu/core.c new file mode 100644 index 0000000..8b9a048 --- /dev/null +++ b/cpu/core.c @@ -0,0 +1,463 @@ +// +// Created by bruno on 2.2.2025. +// + +#include "core.h" +#include "memory.h" +#include "string.h" + +// Initialize CPU +void init_cpu(CPU *cpu) { + memset(cpu, 0, sizeof(CPU)); + cpu->sp = MEM_SIZE - 1; // Stack grows downward +} + +// Helper function for setting flags in the CPU (here we assume bit0 is the Zero flag, +// and bit1 is the Negative flag). +static inline void set_flags(CPU *cpu, int32_t result) { + cpu->flags = 0; + if (result == 0) + cpu->flags |= 0x01; // Zero flag + if (result < 0) + cpu->flags |= 0x02; // Negative flag +} + +// Execute a program (a byte array) on the given CPU. +void step(CPU *cpu) { + if (cpu->mode < EverySecond) { + return; + } + if (cpu->pc >= MEM_SIZE) { + cpu->mode = Done; //terminate + } + uint8_t opcode = read_mem(cpu, cpu->pc++); + uint8_t reg1, reg2, imm; + uint32_t temp, newPC; + int32_t cmpResult; + switch (opcode) { + case NOP: + cpu->pc++; + break; + + case BRK: + cpu->pc++; + cpu->mode = Paused; + break; + + case INC_RN: + cpu->pc++; + reg1 = read_mem(cpu, cpu->pc++); + cpu->regs[reg1]++; + + case INC_ADDR: + cpu->pc++; + imm = read_mem(cpu, cpu->pc++); + write_mem(cpu, imm, read_mem(cpu, imm) + 1); + + case DEC_RN: + cpu->pc++; + reg1 = read_mem(cpu, cpu->pc++); + cpu->regs[reg1]--; + + case DEC_ADDR: + cpu->pc++; + imm = read_mem(cpu, cpu->pc++); + write_mem(cpu, imm, read_mem(cpu, imm) - 1); + + case MOV_RN_IMM: + reg1 = read_mem(cpu, cpu->pc++); + imm = read_mem(cpu, cpu->pc++); + cpu->regs[reg1] = imm; + break; + + case MOV_RN_RM: + reg1 = read_mem(cpu, cpu->pc++); + reg2 = read_mem(cpu, cpu->pc++); + cpu->regs[reg1] = cpu->regs[reg2]; + break; + + case MOV_RN_ADDR: + // Load from memory into register. + reg1 = read_mem(cpu, cpu->pc++); + imm = read_mem(cpu, cpu->pc++); + cpu->regs[reg1] = read_mem(cpu, imm); + break; + + case MOV_ADDR_RN: + // Store from register into memory. + imm = read_mem(cpu, cpu->pc++); + reg1 = read_mem(cpu, cpu->pc++); + write_mem(cpu, imm, cpu->regs[reg1]); + break; + + case SWAP: { + // Swap contents of two registers. + reg1 = read_mem(cpu, cpu->pc++); + reg2 = read_mem(cpu, cpu->pc++); + temp = cpu->regs[reg1]; + cpu->regs[reg1] = cpu->regs[reg2]; + cpu->regs[reg2] = temp; + break; + } + + case SWAPN: { + // Swap the nibbles of a register. + reg1 = read_mem(cpu, cpu->pc++); + uint8_t val = (uint8_t) cpu->regs[reg1]; + cpu->regs[reg1] = ((val & 0x0F) << 4) | ((val & 0xF0) >> 4); + break; + } + + case ADD_RN_RM: + reg1 = read_mem(cpu, cpu->pc++); + reg2 = read_mem(cpu, cpu->pc++); + cpu->regs[reg1] += cpu->regs[reg2]; + set_flags(cpu, cpu->regs[reg1]); + break; + + case ADD_RN_IMM: + reg1 = read_mem(cpu, cpu->pc++); + imm = read_mem(cpu, cpu->pc++); + cpu->regs[reg1] += imm; + set_flags(cpu, cpu->regs[reg1]); + break; + + case SUB_RN_RM: + reg1 = read_mem(cpu, cpu->pc++); + reg2 = read_mem(cpu, cpu->pc++); + cpu->regs[reg1] -= cpu->regs[reg2]; + set_flags(cpu, cpu->regs[reg1]); + break; + + case SUB_RN_IMM: + reg1 = read_mem(cpu, cpu->pc++); + imm = read_mem(cpu, cpu->pc++); + cpu->regs[reg1] -= imm; + set_flags(cpu, cpu->regs[reg1]); + break; + + case MUL_RN_RM: + reg1 = read_mem(cpu, cpu->pc++); + reg2 = read_mem(cpu, cpu->pc++); + cpu->regs[reg1] *= cpu->regs[reg2]; + set_flags(cpu, cpu->regs[reg1]); + break; + + case MUL_RN_IMM: + reg1 = read_mem(cpu, cpu->pc++); + imm = read_mem(cpu, cpu->pc++); + cpu->regs[reg1] *= imm; + set_flags(cpu, cpu->regs[reg1]); + break; + + case DIV_RN_RM: + reg1 = read_mem(cpu, cpu->pc++); + reg2 = read_mem(cpu, cpu->pc++); + if (cpu->regs[reg2] == 0) { + printf("Error: Division by zero!\n"); + cpu->mode = Error; + return; + } + cpu->regs[reg1] /= cpu->regs[reg2]; + set_flags(cpu, cpu->regs[reg1]); + break; + + case DIV_RN_IMM: + reg1 = read_mem(cpu, cpu->pc++); + imm = read_mem(cpu, cpu->pc++); + if (imm == 0) { + printf("Error: Division by zero!\n"); + cpu->mode = Error; + return; + } + cpu->regs[reg1] /= imm; + set_flags(cpu, cpu->regs[reg1]); + break; + + case MOD_RN_RM: + reg1 = read_mem(cpu, cpu->pc++); + reg2 = read_mem(cpu, cpu->pc++); + if (cpu->regs[reg2] == 0) { + printf("Error: Modulo by zero!\n"); + cpu->mode = Error; + return; + } + cpu->regs[reg1] %= cpu->regs[reg2]; + set_flags(cpu, cpu->regs[reg1]); + break; + + case MOD_RN_IMM: + reg1 = read_mem(cpu, cpu->pc++); + imm = read_mem(cpu, cpu->pc++); + if (imm == 0) { + printf("Error: Modulo by zero!\n"); + cpu->mode = Error; + return; + } + cpu->regs[reg1] %= imm; + set_flags(cpu, cpu->regs[reg1]); + break; + + case NEG_RN: + reg1 = read_mem(cpu, cpu->pc++); + cpu->regs[reg1] = -((int32_t) cpu->regs[reg1]); + set_flags(cpu, cpu->regs[reg1]); + break; + + case AND_RN_RM: + reg1 = read_mem(cpu, cpu->pc++); + reg2 = read_mem(cpu, cpu->pc++); + cpu->regs[reg1] &= cpu->regs[reg2]; + set_flags(cpu, cpu->regs[reg1]); + break; + + case AND_RN_IMM: + reg1 = read_mem(cpu, cpu->pc++); + imm = read_mem(cpu, cpu->pc++); + cpu->regs[reg1] &= imm; + set_flags(cpu, cpu->regs[reg1]); + break; + + case OR_RN_RM: + reg1 = read_mem(cpu, cpu->pc++); + reg2 = read_mem(cpu, cpu->pc++); + cpu->regs[reg1] |= cpu->regs[reg2]; + set_flags(cpu, cpu->regs[reg1]); + break; + + case OR_RN_IMM: + reg1 = read_mem(cpu, cpu->pc++); + imm = read_mem(cpu, cpu->pc++); + cpu->regs[reg1] |= imm; + set_flags(cpu, cpu->regs[reg1]); + break; + + case XOR_RN_RM: + reg1 = read_mem(cpu, cpu->pc++); + reg2 = read_mem(cpu, cpu->pc++); + cpu->regs[reg1] ^= cpu->regs[reg2]; + set_flags(cpu, cpu->regs[reg1]); + break; + + case XOR_RN_IMM: + reg1 = read_mem(cpu, cpu->pc++); + imm = read_mem(cpu, cpu->pc++); + cpu->regs[reg1] ^= imm; + set_flags(cpu, cpu->regs[reg1]); + break; + + case NOT_RN: + reg1 = read_mem(cpu, cpu->pc++); + cpu->regs[reg1] = ~cpu->regs[reg1]; + set_flags(cpu, cpu->regs[reg1]); + break; + + case SHL_RN_IMM: + reg1 = read_mem(cpu, cpu->pc++); + imm = read_mem(cpu, cpu->pc++); + cpu->regs[reg1] <<= imm; + set_flags(cpu, cpu->regs[reg1]); + break; + + case SHR_RN_IMM: + reg1 = read_mem(cpu, cpu->pc++); + imm = read_mem(cpu, cpu->pc++); + cpu->regs[reg1] >>= imm; // Logical right shift (assuming unsigned value) + set_flags(cpu, cpu->regs[reg1]); + break; + + case SAR_RN_IMM: + reg1 = read_mem(cpu, cpu->pc++); + imm = read_mem(cpu, cpu->pc++); + // Arithmetic right shift; cast to signed before shifting. + cpu->regs[reg1] = ((int32_t) cpu->regs[reg1]) >> imm; + set_flags(cpu, cpu->regs[reg1]); + break; + + case JMP: + newPC = read_mem32(cpu, cpu->pc); + cpu->pc += 4; + cpu->pc = newPC; + break; + + case JMP_REL: + imm = (int32_t) read_mem(cpu, cpu->pc++); + cpu->pc += imm; + break; + + case CMP: { + // Compare two registers: set flags (Zero, Negative) + reg1 = read_mem(cpu, cpu->pc++); + reg2 = read_mem(cpu, cpu->pc++); + cmpResult = (int32_t) cpu->regs[reg1] - (int32_t) cpu->regs[reg2]; + set_flags(cpu, cmpResult); + break; + } + + case JE_BIT_RN: { + // Jump if bit in register set + reg1 = read_mem(cpu, cpu->pc++); + if (reg1 >= REG_COUNT) { + reg1 = REG_COUNT - 1; + } + uint8_t bit = read_mem(cpu, cpu->pc++); + if (bit > 7) { + bit = 7; + } + newPC = read_mem32(cpu, cpu->pc); + cpu->pc += 4; + if (cpu->regs[reg1] & (1 << bit)) + cpu->pc = newPC; + break; + } + + case JE_BIT_ADDR: { + // Jump if bit in register set + temp = read_mem32(cpu, cpu->pc); + if (temp >= MEM_SIZE) { + temp = MEM_SIZE - 1; + } + uint8_t bit = read_mem(cpu, cpu->pc++); + if (bit > 7) { + bit = 7; + } + newPC = read_mem32(cpu, cpu->pc); + cpu->pc += 4; + if (cpu->memory[temp] & (1 << bit)) + cpu->pc = newPC; + break; + } + + + case JE: { + // Jump if equal (Zero flag set) + newPC = read_mem32(cpu, cpu->pc); + cpu->pc += 4; + if (cpu->flags & 0x01) + cpu->pc = newPC; + break; + } + + case JNE_BIT_RN: { + // Jump if bit in register set + reg1 = read_mem(cpu, cpu->pc++); + if (reg1 >= REG_COUNT) { + reg1 = REG_COUNT - 1; + } + uint8_t bit = read_mem(cpu, cpu->pc++); + if (bit > 7) { + bit = 7; + } + newPC = read_mem32(cpu, cpu->pc); + cpu->pc += 4; + if (!(cpu->regs[reg1] & (1 << bit))) + cpu->pc = newPC; + break; + } + + case JNE_BIT_ADDR: { + // Jump if bit in register set + temp = read_mem32(cpu, cpu->pc); + if (temp >= MEM_SIZE) { + temp = MEM_SIZE - 1; + } + uint8_t bit = read_mem(cpu, cpu->pc++); + if (bit > 7) { + bit = 7; + } + newPC = read_mem32(cpu, cpu->pc); + cpu->pc += 4; + if (!(cpu->memory[temp] & (1 << bit))) + cpu->pc = newPC; + break; + } + + case JNE: { + // Jump if not equal (Zero flag clear) + newPC = read_mem32(cpu, cpu->pc); + cpu->pc += 4; + if (!(cpu->flags & 0x01)) + cpu->pc = newPC; + break; + } + + case JG: { + // Jump if greater: not negative and not zero. + newPC = read_mem32(cpu, cpu->pc); + cpu->pc += 4; + if (((cpu->flags & 0x02) == 0) && ((cpu->flags & 0x01) == 0)) + cpu->pc = newPC; + break; + } + + case JL: { + // Jump if less: Negative flag set. + newPC = read_mem32(cpu, cpu->pc); + cpu->pc += 4; + if (cpu->flags & 0x02) + cpu->pc = newPC; + break; + } + + case JGE: { + // Jump if greater or equal: Zero flag set or negative clear. + newPC = read_mem32(cpu, cpu->pc); + cpu->pc += 4; + if ((cpu->flags & 0x01) || ((cpu->flags & 0x02) == 0)) + cpu->pc = newPC; + break; + } + + case JLE: { + // Jump if less or equal: Negative flag set or Zero flag set. + newPC = read_mem32(cpu, cpu->pc); + cpu->pc += 4; + if ((cpu->flags & 0x02) || (cpu->flags & 0x01)) + cpu->pc = newPC; + break; + } + + case CALL: { + // Push the current PC onto the stack, then jump to the address. + newPC = read_mem32(cpu, cpu->pc); + cpu->pc += 4; + // Push return address (current PC) onto the stack. + write_mem(cpu, cpu->sp--, (uint8_t) (cpu->pc & 0xFF)); + cpu->pc = newPC; + break; + } + + case RET: + // For RET, assume that the return address was stored on the stack. + cpu->pc = read_mem(cpu, ++cpu->sp); + break; + + case PUSH: { + // Push register value onto the stack. + reg1 = read_mem(cpu, cpu->pc++); + write_mem(cpu, cpu->sp--, (uint8_t) (cpu->regs[reg1] & 0xFF)); + break; + } + + case POP: { + // Pop a value from the stack into a register. + reg1 = read_mem(cpu, cpu->pc++); + cpu->regs[reg1] = read_mem(cpu, ++cpu->sp); + break; + } + + case PUSHF: + // Push the flags register. + write_mem(cpu, cpu->sp--, cpu->flags); + break; + + case POPF: + // Pop into the flags register. + cpu->flags = read_mem(cpu, ++cpu->sp); + break; + + default: + printf("Unknown opcode: %d\n", opcode); + cpu->mode = Error; + } +} \ No newline at end of file diff --git a/cpu/core.h b/cpu/core.h new file mode 100644 index 0000000..6fa30cb --- /dev/null +++ b/cpu/core.h @@ -0,0 +1,132 @@ +// +// Created by bruno on 2.2.2025. +// + +#ifndef RISCB_CORE_H +#define RISCB_CORE_H + +#include +#include "stdio.h" + +enum Mode { + Paused, + Done, + Error, + EverySecond, + EveryFrame, + MaxSpeed +}; + +#define MEM_SIZE 8192 +// Register count (register names R0 to R7) +#define REG_COUNT 32 + +// CPU state +typedef struct { + uint32_t regs[REG_COUNT]; + uint8_t memory[MEM_SIZE]; + uint32_t pc; // Program counter + uint32_t sp; // Stack pointer + uint8_t flags; // Status flags + enum Mode mode; +} CPU; + +void step(CPU *cpu); + +void init_cpu(CPU *cpu); + +// Helper function for setting flags in the CPU (here we assume bit0 is the Zero flag, +// and bit1 is the Negative flag). +static inline void set_flags(CPU *cpu, int32_t result); + +// Opcode definitions (should match the CPU’s enum) +typedef enum { + NOP, // NOP - No operation (does nothing, advances to next instruction) + + BRK, // BRK - Pause CPU (halts execution until resumed) + + INC_RN, //INC Rn - Increment register + INC_ADDR, //INC [Addr] - Increment address + + DEC_RN, //DEC Rn - Increment register + DEC_ADDR, //DEC [Addr] - Increment address + + MOV_RN_IMM, // MOV Rn, Imm - Move immediate to register (Rn = Imm) + MOV_RN_RM, // MOV Rn, Rm - Move value from one register to another (Rn = Rm) + MOV_RN_ADDR, // MOV Rn, [Addr] - Load value from memory address into register (Rn = [Addr]) + MOV_ADDR_RN, // MOV [Addr], Rn - Store register value into memory address ([Addr] = Rn) + + SWAP, // SWAP Rn, Rm - Swap values between two registers (Rn <-> Rm) + + SWAPN, // SWAPN Rn - Swap nibbles within a register (Rn high/low nibbles swapped) + + ADD_RN_RM, // ADD Rn, Rm - Add values of two registers (Rn = Rn + Rm) + ADD_RN_IMM, // ADD Rn, Imm - Add immediate value to register (Rn = Rn + Imm) + + SUB_RN_RM, // SUB Rn, Rm - Subtract one register from another (Rn = Rn - Rm) + SUB_RN_IMM, // SUB Rn, Imm - Subtract immediate value from register (Rn = Rn - Imm) + + MUL_RN_RM, // MUL Rn, Rm - Multiply two registers (Rn = Rn * Rm) + MUL_RN_IMM, // MUL Rn, Imm - Multiply register by immediate (Rn = Rn * Imm) + + DIV_RN_RM, // DIV Rn, Rm - Divide one register by another (Rn = Rn / Rm) + DIV_RN_IMM, // DIV Rn, Imm - Divide register by immediate (Rn = Rn / Imm) + + MOD_RN_RM, // MOD Rn, Rm - Compute remainder of division (Rn = Rn % Rm) + MOD_RN_IMM, // MOD Rn, Imm - Compute remainder using immediate (Rn = Rn % Imm) + + NEG_RN, // NEG Rn - Negate register value (Rn = -Rn) + + AND_RN_RM, // AND Rn, Rm - Bitwise AND two registers (Rn = Rn & Rm) + AND_RN_IMM, // AND Rn, Imm - Bitwise AND register with immediate (Rn = Rn & Imm) + + OR_RN_RM, // OR Rn, Rm - Bitwise OR two registers (Rn = Rn | Rm) + OR_RN_IMM, // OR Rn, Imm - Bitwise OR register with immediate (Rn = Rn | Imm) + + XOR_RN_RM, // XOR Rn, Rm - Bitwise XOR two registers (Rn = Rn ^ Rm) + XOR_RN_IMM, // XOR Rn, Imm - Bitwise XOR register with immediate (Rn = Rn ^ Imm) + + NOT_RN, // NOT Rn - Bitwise NOT (Rn = ~Rn) + + SHL_RN_IMM, // SHL Rn, Imm - Logical shift left (Rn = Rn << Imm) + + SHR_RN_IMM, // SHR Rn, Imm - Logical shift right (Rn = Rn >> Imm) + + SAR_RN_IMM, // SAR Rn, Imm - Arithmetic shift right (Rn = Rn >> Imm with sign extension) + + JMP, // JMP Addr - Jump to address (PC = Addr) + + JMP_REL, // JMP Offset - relative jump (offset is signed) + + CMP, // CMP Rn, Rm - Compare two registers (sets flags based on Rn - Rm) + + JE, // JE Addr - Jump if equal (if zero flag set, PC = Addr) + JE_BIT_RN, // jump relative if a given bit in a register is set + JE_BIT_ADDR, // jump relative if a given bit in memory is set + + JNE, // JNE Addr - Jump if not equal (if zero flag not set, PC = Addr) + JNE_BIT_RN, // jump relative if a given bit in a register is not set + JNE_BIT_ADDR, // jump relative if a given bit in memory is not set + + JG, // JG Addr - Jump if greater (if greater flag set, PC = Addr) + + JL, // JL Addr - Jump if less (if less flag set, PC = Addr) + + JGE, // JGE Addr - Jump if greater or equal (if greater or zero flag set, PC = Addr) + + JLE, // JLE Addr - Jump if less or equal (if less or zero flag set, PC = Addr) + + CALL, // CALL Addr - Call subroutine (push PC to stack, PC = Addr) + + RET, // RET - Return from subroutine (pop PC from stack) + + PUSH, // PUSH Rn - Push register onto stack (stack[top] = Rn) + + POP, // POP Rn - Pop value from stack into register (Rn = stack[top]) + + PUSHF, // PUSHF - Push flags onto stack (stack[top] = flags) + + POPF // POPF - Pop flags from stack (flags = stack[top]) +} Opcode; + +#endif //RISCB_CORE_H diff --git a/cpu/memory.c b/cpu/memory.c new file mode 100644 index 0000000..aed42c1 --- /dev/null +++ b/cpu/memory.c @@ -0,0 +1,62 @@ +// +// Created by bruno on 2.2.2025. +// + +#include "memory.h" + +uint8_t write_mem(CPU *cpu, uint32_t addr, uint8_t value) { + if (addr >= MEM_SIZE) { + return 1; + } + + switch (addr) { + + default: + cpu->memory[addr] = value; + } + + return 0; +} + +uint8_t read_mem(CPU *cpu, uint32_t addr) { + if (addr >= MEM_SIZE) { + return 0; + } + switch (addr) { + + default: + return cpu->memory[addr]; + } +} + +uint16_t read_mem16(CPU *cpu, uint32_t addr) { + return read_mem(cpu, addr) | (read_mem(cpu, addr + 1) << 8); +} + +uint32_t read_mem32(CPU *cpu, uint32_t addr) { + return read_mem16(cpu, addr) | (read_mem16(cpu, addr + 2) << 16); +} + +uint8_t write_mem16(CPU *cpu, uint32_t addr, uint16_t value) { + uint8_t status = write_mem(cpu, addr, value & 0xFF); + if (status) { + return status; + } + status = write_mem(cpu, addr + 1, (value >> 8) & 0xFF); + if (status) { + return status; + } + return 0; +} + +uint8_t write_mem32(CPU *cpu, uint32_t addr, uint32_t value) { + uint8_t status = write_mem16(cpu, addr, value & 0xFFFF); + if (status) { + return status; + } + status = write_mem16(cpu, addr + 2, (value >> 16) & 0xFFFF); + if (status) { + return status; + } + return 0; +} diff --git a/cpu/memory.h b/cpu/memory.h new file mode 100644 index 0000000..9655b78 --- /dev/null +++ b/cpu/memory.h @@ -0,0 +1,24 @@ +// +// Created by bruno on 2.2.2025. +// + +#ifndef RISCB_MEMORY_H +#define RISCB_MEMORY_H + +#include +#include "../cpu/core.h" + +uint8_t write_mem32(CPU *cpu, uint32_t addr, uint32_t value); + +uint8_t write_mem16(CPU *cpu, uint32_t addr, uint16_t value); + +uint32_t read_mem32(CPU *cpu, uint32_t addr); + +uint16_t read_mem16(CPU *cpu, uint32_t addr); + +uint8_t read_mem(CPU *cpu, uint32_t addr); + +uint8_t write_mem(CPU *cpu, uint32_t addr, uint8_t value); + + +#endif //RISCB_MEMORY_H diff --git a/main.c b/main.c index 3ec13a6..f779d65 100644 --- a/main.c +++ b/main.c @@ -1,55 +1,32 @@ #include -#include #include -#include +#include +#include "util/font.h" +#include "assembler/assembler.h" //Screen dimension constants -const int SCREEN_WIDTH = 640; -const int SCREEN_HEIGHT = 480; +const int SCREEN_WIDTH = 1280; +const int SCREEN_HEIGHT = 720; +const int targetFPS = 60; +const int delayNeeded = 1000 / targetFPS; -void renderText(SDL_Renderer *renderer, TTF_Font *font, char *string, uint16_t x, uint16_t y, uint8_t r, uint8_t g, - uint8_t b, uint8_t a) { - SDL_Texture *fontTempTex; - SDL_Surface *fontTempSurf; - SDL_Color color = {r, g, b, a}; - fontTempSurf = TTF_RenderText_Blended(font, string, color); - fontTempTex = SDL_CreateTextureFromSurface(renderer, fontTempSurf); +//The window we'll be rendering to +SDL_Window *window = NULL; - int iW, iH; - SDL_QueryTexture(fontTempTex, NULL, NULL, &iW, &iH); +//The surface contained by the window +SDL_Renderer *renderer = NULL; - SDL_Rect srcRect; - srcRect.x = 0; - srcRect.y = 0; - srcRect.w = iW; - srcRect.h = iH; +SDL_Rect rect1; - SDL_Rect dstRect; - dstRect.x = x; - dstRect.y = y; - dstRect.w = iW; - dstRect.h = iH; +BitmapFont smallFont; - SDL_RenderCopy(renderer, fontTempTex, &srcRect, &dstRect); - - SDL_FreeSurface(fontTempSurf); - SDL_DestroyTexture(fontTempTex); -} - -int main(int argc, char *args[]) { - //The window we'll be rendering to - SDL_Window *window = NULL; - - //The surface contained by the window - SDL_Renderer *renderer = NULL; - - SDL_Rect rect1; - - TTF_Font *gFont; +CPU cpu; +char programString[65535]; +int init() { //Initialize SDL - if (SDL_Init(SDL_INIT_VIDEO) < 0) { + if (SDL_Init(SDL_INIT_EVERYTHING) < 0) { printf("SDL could not initialize! SDL_Error: %s\n", SDL_GetError()); return 1; } @@ -60,55 +37,98 @@ int main(int argc, char *args[]) { return 1; } - gFont = TTF_OpenFont("../rasterthingy.ttf", 50); - if (gFont == NULL) { - printf("Failed to load lazy font! SDL_ttf Error: %s\n", TTF_GetError()); - return 1; - } //Create window - window = SDL_CreateWindow("SDLko", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, SCREEN_WIDTH, - SCREEN_HEIGHT, SDL_WINDOW_SHOWN); + window = SDL_CreateWindow("SDLko", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, SCREEN_WIDTH, + SCREEN_HEIGHT, SDL_WINDOW_SHOWN | SDL_WINDOW_RESIZABLE); if (window == NULL) { printf("Window could not be created! SDL_Error: %s\n", SDL_GetError()); return 1; } //Get window surface renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED); + if (renderer == NULL) { + printf("Renderer could not be created SDL_Error: %s\n", SDL_GetError()); + return 1; + } + smallFont = prepText(renderer, 10, "../PublicPixel.ttf", 255, 255, 255, 255); + SDL_RenderSetLogicalSize(renderer, SCREEN_WIDTH, SCREEN_HEIGHT); + SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, 0); + init_cpu(&cpu); + + return 0; +} + +int render() { + SDL_SetRenderDrawColor(renderer, 128, 0, 0, 255); + SDL_RenderClear(renderer); + + SDL_SetRenderDrawColor(renderer, 0, 128, 0, 255); + rect1.x = (rect1.x + 1) % 400; + rect1.y = 10; + rect1.w = 50; + rect1.h = 10; + SDL_RenderFillRect(renderer, &rect1); + + char textTemp[12]; + sprintf(textTemp, "%d", rect1.x); + + renderText(renderer, smallFont, textTemp, 100, 100); + + SDL_RenderPresent(renderer); + return 0; +} + +int processEvent(SDL_Event e) { + if (e.type == SDL_QUIT) { return 0; } + else if (e.type == SDL_WINDOWEVENT && e.window.event == SDL_WINDOWEVENT_RESIZED) { + int newWidth = e.window.data1; + int newHeight = e.window.data2; + + // Adjust the viewport to match the new window size + SDL_Rect viewport = {0, 0, newWidth, newHeight}; + SDL_RenderSetViewport(renderer, &viewport); + } + return 1; +} + +int main(__attribute__((unused)) int argc, __attribute__((unused)) char *args[]) { + int status = init(); + if (status) { + return status; + } //Hack to get window to stay up SDL_Event e; - bool quit = false; + bool running = true; Uint64 start; Uint64 end; - while (!quit) { + while (running) { start = SDL_GetTicks64(); while (SDL_PollEvent(&e)) { - if (e.type == SDL_QUIT) quit = true; + running = processEvent(e); } + status = render(); + if (status) { + return status; + } - SDL_SetRenderDrawColor(renderer, 128, 0, 0, 255); - SDL_RenderClear(renderer); - - SDL_SetRenderDrawColor(renderer, 0, 128, 0, 255); - rect1.x = (rect1.x + 1) % 400; - rect1.y = 10; - rect1.w = 50; - rect1.h = 10; - SDL_RenderFillRect(renderer, &rect1); - - char textTemp[12]; - sprintf(textTemp, "%d", rect1.x); - - renderText(renderer, gFont, textTemp, 100, 100, 255, 255, 255, 255); - - SDL_RenderPresent(renderer); end = SDL_GetTicks64(); - SDL_Delay((1000 / 60) - (end - start)); + const unsigned long timeNeeded = end - start; + if (timeNeeded < delayNeeded) { + SDL_Delay(delayNeeded - timeNeeded); + } else { + printf("%lu", timeNeeded); + } } - TTF_CloseFont(gFont); + uint8_t *program; + int program_size; + + completePass(programString, &cpu); + + step(&cpu); //Destroy window SDL_DestroyWindow(window); diff --git a/rasterthingy.ttf b/rasterthingy.ttf deleted file mode 100644 index 8d80982..0000000 Binary files a/rasterthingy.ttf and /dev/null differ diff --git a/util/font.c b/util/font.c new file mode 100644 index 0000000..1c07f55 --- /dev/null +++ b/util/font.c @@ -0,0 +1,49 @@ +// +// Created by bruno on 1.2.2025. +// + +#include "font.h" + +BitmapFont +prepText(SDL_Renderer *renderer, unsigned char pxSize, const char *file, uint8_t r, uint8_t g, uint8_t b, uint8_t a) { + const unsigned char ptSize = floor(pxSize * 0.75); + TTF_Font *gFont = TTF_OpenFont(file, ptSize); + BitmapFont out; + out.size = pxSize; + out.color = (SDL_Color) {r, g, b, a}; + unsigned char i = 0; + SDL_Surface *fontTempSurf = NULL; + do { + char tmpOut[2] = {i, 0}; + + fontTempSurf = TTF_RenderText_Solid(gFont, tmpOut, out.color); + SDL_Rect dstRect; + dstRect.x = 0; + dstRect.y = 0; + dstRect.w = pxSize; + dstRect.h = pxSize; + out.texture[i] = SDL_CreateTextureFromSurface(renderer, fontTempSurf); + i++; + } while (i < 255); + + SDL_FreeSurface(fontTempSurf); + TTF_CloseFont(gFont); + return out; +} + +void renderText(SDL_Renderer *renderer, BitmapFont font, char *string, uint16_t x, uint16_t y) { + SDL_Rect charRect; + charRect.x = 0; + charRect.y = 0; + charRect.w = font.size; + charRect.h = font.size; + SDL_Rect outRect = charRect; + outRect.x = x; + outRect.y = y; + + while (*string) { + SDL_RenderCopy(renderer, font.texture[*string], &charRect, &outRect); + outRect.x += charRect.w + 1; + string++; + } +} \ No newline at end of file diff --git a/util/font.h b/util/font.h new file mode 100644 index 0000000..93f57b1 --- /dev/null +++ b/util/font.h @@ -0,0 +1,23 @@ +// +// Created by bruno on 1.2.2025. +// + +#ifndef RISCB_FONT_H +#define RISCB_FONT_H + +#include +#include +#include + +typedef struct { + SDL_Texture *texture[256]; + uint8_t size; + SDL_Color color; +} BitmapFont; + +BitmapFont +prepText(SDL_Renderer *renderer, unsigned char pxSize, const char *file, uint8_t r, uint8_t g, uint8_t b, uint8_t a); + +void renderText(SDL_Renderer *renderer, BitmapFont font, char *string, uint16_t x, uint16_t y); + +#endif //RISCB_FONT_H