From 0a0f953f09781922edd5e6e1cbbc37689057ca07 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bruno=20Ryb=C3=A1rsky?= Date: Sun, 2 Feb 2025 21:48:35 +0100 Subject: [PATCH] Do some work on the CPU, assembler still needs update --- .idea/.name | 1 + .idea/discord.xml | 7 + .idea/modules.xml | 1 + CMakeLists.txt | 14 +- PublicPixel.ttf | Bin 0 -> 128684 bytes assembler/assembler.c | 525 ++++++++++++++++++++++++++++++++++++++++++ assembler/assembler.h | 87 +++++++ cpu/core.c | 463 +++++++++++++++++++++++++++++++++++++ cpu/core.h | 132 +++++++++++ cpu/memory.c | 62 +++++ cpu/memory.h | 24 ++ main.c | 156 +++++++------ rasterthingy.ttf | Bin 26936 -> 0 bytes util/font.c | 49 ++++ util/font.h | 23 ++ 15 files changed, 1473 insertions(+), 71 deletions(-) create mode 100644 .idea/.name create mode 100644 .idea/discord.xml create mode 100644 PublicPixel.ttf create mode 100644 assembler/assembler.c create mode 100644 assembler/assembler.h create mode 100644 cpu/core.c create mode 100644 cpu/core.h create mode 100644 cpu/memory.c create mode 100644 cpu/memory.h delete mode 100644 rasterthingy.ttf create mode 100644 util/font.c create mode 100644 util/font.h 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 0000000000000000000000000000000000000000..f6c69fe2deca742d9119fbd56a510e3ea3547d76 GIT binary patch literal 128684 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 z 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 Vj^>ciG%_%91Fs?mpSi}s007%t&949e literal 0 HcmV?d00001 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 8d809828d433c14ec12f9a6c9e9632ee1b89cdc5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 26936 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 Q0jj_eJSYov7ZHRB0Fbf}NdN!< 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