Do some work on the CPU, assembler still needs update
This commit is contained in:
1
.idea/.name
generated
Normal file
1
.idea/.name
generated
Normal file
@@ -0,0 +1 @@
|
|||||||
|
RISCB
|
7
.idea/discord.xml
generated
Normal file
7
.idea/discord.xml
generated
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="DiscordProjectSettings">
|
||||||
|
<option name="show" value="ASK" />
|
||||||
|
<option name="description" value="" />
|
||||||
|
</component>
|
||||||
|
</project>
|
1
.idea/modules.xml
generated
1
.idea/modules.xml
generated
@@ -2,6 +2,7 @@
|
|||||||
<project version="4">
|
<project version="4">
|
||||||
<component name="ProjectModuleManager">
|
<component name="ProjectModuleManager">
|
||||||
<modules>
|
<modules>
|
||||||
|
<module fileurl="file://$PROJECT_DIR$/.idea/riscb.iml" filepath="$PROJECT_DIR$/.idea/riscb.iml" />
|
||||||
<module fileurl="file://$PROJECT_DIR$/.idea/sdlzasedaco.iml" filepath="$PROJECT_DIR$/.idea/sdlzasedaco.iml" />
|
<module fileurl="file://$PROJECT_DIR$/.idea/sdlzasedaco.iml" filepath="$PROJECT_DIR$/.idea/sdlzasedaco.iml" />
|
||||||
</modules>
|
</modules>
|
||||||
</component>
|
</component>
|
||||||
|
@@ -1,11 +1,19 @@
|
|||||||
cmake_minimum_required(VERSION 3.31)
|
cmake_minimum_required(VERSION 3.31)
|
||||||
project(sdlzasedaco C)
|
project(RISCB C)
|
||||||
|
|
||||||
set(CMAKE_C_STANDARD 23)
|
set(CMAKE_C_STANDARD 23)
|
||||||
|
|
||||||
find_package(PkgConfig REQUIRED)
|
find_package(PkgConfig REQUIRED)
|
||||||
pkg_check_modules(SDL2 REQUIRED sdl2)
|
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)
|
||||||
|
BIN
PublicPixel.ttf
Normal file
BIN
PublicPixel.ttf
Normal file
Binary file not shown.
525
assembler/assembler.c
Normal file
525
assembler/assembler.c
Normal file
@@ -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);
|
||||||
|
}
|
87
assembler/assembler.h
Normal file
87
assembler/assembler.h
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
//
|
||||||
|
// Created by bruno on 1.2.2025.
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef RISCB_ASSEMBLER_H
|
||||||
|
#define RISCB_ASSEMBLER_H
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <ctype.h>
|
||||||
|
#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
|
463
cpu/core.c
Normal file
463
cpu/core.c
Normal file
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
132
cpu/core.h
Normal file
132
cpu/core.h
Normal file
@@ -0,0 +1,132 @@
|
|||||||
|
//
|
||||||
|
// Created by bruno on 2.2.2025.
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef RISCB_CORE_H
|
||||||
|
#define RISCB_CORE_H
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#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
|
62
cpu/memory.c
Normal file
62
cpu/memory.c
Normal file
@@ -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;
|
||||||
|
}
|
24
cpu/memory.h
Normal file
24
cpu/memory.h
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
//
|
||||||
|
// Created by bruno on 2.2.2025.
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef RISCB_MEMORY_H
|
||||||
|
#define RISCB_MEMORY_H
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#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
|
142
main.c
142
main.c
@@ -1,55 +1,32 @@
|
|||||||
#include <SDL2/SDL.h>
|
#include <SDL2/SDL.h>
|
||||||
#include <SDL2/SDL_ttf.h>
|
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <stdlib.h>
|
#include <threads.h>
|
||||||
|
#include "util/font.h"
|
||||||
|
#include "assembler/assembler.h"
|
||||||
|
|
||||||
//Screen dimension constants
|
//Screen dimension constants
|
||||||
const int SCREEN_WIDTH = 640;
|
const int SCREEN_WIDTH = 1280;
|
||||||
const int SCREEN_HEIGHT = 480;
|
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,
|
//The window we'll be rendering to
|
||||||
uint8_t b, uint8_t a) {
|
SDL_Window *window = NULL;
|
||||||
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);
|
|
||||||
|
|
||||||
int iW, iH;
|
//The surface contained by the window
|
||||||
SDL_QueryTexture(fontTempTex, NULL, NULL, &iW, &iH);
|
SDL_Renderer *renderer = NULL;
|
||||||
|
|
||||||
SDL_Rect srcRect;
|
SDL_Rect rect1;
|
||||||
srcRect.x = 0;
|
|
||||||
srcRect.y = 0;
|
|
||||||
srcRect.w = iW;
|
|
||||||
srcRect.h = iH;
|
|
||||||
|
|
||||||
SDL_Rect dstRect;
|
BitmapFont smallFont;
|
||||||
dstRect.x = x;
|
|
||||||
dstRect.y = y;
|
|
||||||
dstRect.w = iW;
|
|
||||||
dstRect.h = iH;
|
|
||||||
|
|
||||||
SDL_RenderCopy(renderer, fontTempTex, &srcRect, &dstRect);
|
CPU cpu;
|
||||||
|
|
||||||
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;
|
|
||||||
|
|
||||||
|
char programString[65535];
|
||||||
|
|
||||||
|
int init() {
|
||||||
//Initialize SDL
|
//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());
|
printf("SDL could not initialize! SDL_Error: %s\n", SDL_GetError());
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
@@ -60,34 +37,29 @@ int main(int argc, char *args[]) {
|
|||||||
return 1;
|
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
|
//Create window
|
||||||
window = SDL_CreateWindow("SDLko", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, SCREEN_WIDTH,
|
window = SDL_CreateWindow("SDLko", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, SCREEN_WIDTH,
|
||||||
SCREEN_HEIGHT, SDL_WINDOW_SHOWN);
|
SCREEN_HEIGHT, SDL_WINDOW_SHOWN | SDL_WINDOW_RESIZABLE);
|
||||||
if (window == NULL) {
|
if (window == NULL) {
|
||||||
printf("Window could not be created! SDL_Error: %s\n", SDL_GetError());
|
printf("Window could not be created! SDL_Error: %s\n", SDL_GetError());
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
//Get window surface
|
//Get window surface
|
||||||
renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED);
|
renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED);
|
||||||
|
if (renderer == NULL) {
|
||||||
//Hack to get window to stay up
|
printf("Renderer could not be created SDL_Error: %s\n", SDL_GetError());
|
||||||
SDL_Event e;
|
return 1;
|
||||||
bool quit = false;
|
|
||||||
Uint64 start;
|
|
||||||
Uint64 end;
|
|
||||||
while (!quit) {
|
|
||||||
start = SDL_GetTicks64();
|
|
||||||
while (SDL_PollEvent(&e)) {
|
|
||||||
if (e.type == SDL_QUIT) quit = true;
|
|
||||||
}
|
}
|
||||||
|
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_SetRenderDrawColor(renderer, 128, 0, 0, 255);
|
||||||
SDL_RenderClear(renderer);
|
SDL_RenderClear(renderer);
|
||||||
|
|
||||||
@@ -101,14 +73,62 @@ int main(int argc, char *args[]) {
|
|||||||
char textTemp[12];
|
char textTemp[12];
|
||||||
sprintf(textTemp, "%d", rect1.x);
|
sprintf(textTemp, "%d", rect1.x);
|
||||||
|
|
||||||
renderText(renderer, gFont, textTemp, 100, 100, 255, 255, 255, 255);
|
renderText(renderer, smallFont, textTemp, 100, 100);
|
||||||
|
|
||||||
SDL_RenderPresent(renderer);
|
SDL_RenderPresent(renderer);
|
||||||
end = SDL_GetTicks64();
|
return 0;
|
||||||
SDL_Delay((1000 / 60) - (end - start));
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
TTF_CloseFont(gFont);
|
//Hack to get window to stay up
|
||||||
|
SDL_Event e;
|
||||||
|
bool running = true;
|
||||||
|
Uint64 start;
|
||||||
|
Uint64 end;
|
||||||
|
while (running) {
|
||||||
|
start = SDL_GetTicks64();
|
||||||
|
while (SDL_PollEvent(&e)) {
|
||||||
|
running = processEvent(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
status = render();
|
||||||
|
if (status) {
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
end = SDL_GetTicks64();
|
||||||
|
const unsigned long timeNeeded = end - start;
|
||||||
|
if (timeNeeded < delayNeeded) {
|
||||||
|
SDL_Delay(delayNeeded - timeNeeded);
|
||||||
|
} else {
|
||||||
|
printf("%lu", timeNeeded);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t *program;
|
||||||
|
int program_size;
|
||||||
|
|
||||||
|
completePass(programString, &cpu);
|
||||||
|
|
||||||
|
step(&cpu);
|
||||||
|
|
||||||
//Destroy window
|
//Destroy window
|
||||||
SDL_DestroyWindow(window);
|
SDL_DestroyWindow(window);
|
||||||
|
BIN
rasterthingy.ttf
BIN
rasterthingy.ttf
Binary file not shown.
49
util/font.c
Normal file
49
util/font.c
Normal file
@@ -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++;
|
||||||
|
}
|
||||||
|
}
|
23
util/font.h
Normal file
23
util/font.h
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
//
|
||||||
|
// Created by bruno on 1.2.2025.
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef RISCB_FONT_H
|
||||||
|
#define RISCB_FONT_H
|
||||||
|
|
||||||
|
#include <SDL2/SDL_render.h>
|
||||||
|
#include <SDL2/SDL_ttf.h>
|
||||||
|
#include <math.h>
|
||||||
|
|
||||||
|
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
|
Reference in New Issue
Block a user