diff --git a/CMakeLists.txt b/CMakeLists.txt index ae03228..38d23a0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -48,6 +48,10 @@ set(SOURCE_FILES util/texteditor.c util/texteditor.h util/hexdump.c util/hexdump.h util/cpustatusui.c util/cpustatusui.h + peripherals/audio.c + peripherals/audio.h + peripherals/peripheraldata.c + peripherals/peripheraldata.h ) # Build the target executable diff --git a/assembler/assembler.c b/assembler/assembler.c index aa6aa32..11aa99b 100644 --- a/assembler/assembler.c +++ b/assembler/assembler.c @@ -90,6 +90,9 @@ void toUpperCase(char *string) { if (*string > 0x60 && *string < 0x7b) { (*string) -= 0x20; } + if (*string == '\r') { + *string = ' '; + } string++; } } @@ -117,7 +120,7 @@ int getOpcode(char *mnemonic) { else if (strcmp(mnemonic, "HLT") == 0) return HLT; 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 + return -2; // Special case: we must decide between MOV_IMM_RN, MOV_RN_RM, MOV_RN_ADDR, MOV_ADDR_RN else if (strcmp(mnemonic, "SWAP") == 0) return SWAP; else if (strcmp(mnemonic, "SWAPN") == 0) @@ -161,9 +164,13 @@ int getOpcode(char *mnemonic) { else if (strcmp(mnemonic, "JNE") == 0) return JNE; else if (strcmp(mnemonic, "JMPBS") == 0) - return -14; //Special: decide between JMP_BIT_SET_RN and JMP_BIT_SET_ADDR + return -14; //Special: decide between BIT_TS_RN and BIT_TS_ADDR else if (strcmp(mnemonic, "JMPBC") == 0) - return -15; //Special: decide between JMP_BIT_CLEAR_RN and JMP_BIT_CLEAR_ADDR + return -15; //Special: decide between BIT_TC_RN and BIT_TC_ADDR + else if (strcmp(mnemonic, "BITS") == 0) + return -16; //Special: decide between BITS_RN and BITS_ADDR + else if (strcmp(mnemonic, "BITC") == 0) + return -17; //Special: decide between BITC_RN and BITC_ADDR else if (strcmp(mnemonic, "JG") == 0) return JG; else if (strcmp(mnemonic, "JL") == 0) @@ -186,20 +193,20 @@ int getOpcode(char *mnemonic) { // 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. +// For example, "MOV Rn, 42" should choose MOV_IMM_RN, 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 + if (dest[0] == '[') return MOV_RN_ADDR; // actually, MOV [Addr], Rn expects Rn in second operand // Otherwise, dest is a register. // Now, check src: - if (src[0] == 'R' || src[0] == 'r') { + if ((dest[0] == 'R' || dest[0] == 'r') && (src[0] == 'R' || src[0] == 'r')) { return MOV_RN_RM; } else if (src[0] == '[') { - return MOV_RN_ADDR; + return MOV_ADDR_RN; } else { - return MOV_RN_IMM; + return MOV_IMM_RN; } } @@ -228,9 +235,13 @@ int resolveALU(int baseOpcode, const char *src) { case -13: return DEC_RN; case -14: - return JMP_BIT_SET_RN; + return BIT_TS_RN; case -15: - return JMP_BIT_CLEAR_RN; + return BIT_TC_RN; + case -16: + return BITS_RN; + case -17: + return BITC_RN; default: return -1; } @@ -266,9 +277,13 @@ int resolveALU(int baseOpcode, const char *src) { case -13: return DEC_ADDR; case -14: - return JMP_BIT_SET_ADDR; + return BIT_TS_ADDR; case -15: - return JMP_BIT_CLEAR_ADDR; + return BIT_TC_ADDR; + case -16: + return BITS_ADDR; + case -17: + return BITC_ADDR; default: return -1; } @@ -367,6 +382,17 @@ uint32_t completePass(const char *source, CPU *cpu, bool erase) { continue; } + // Stop at the first semicolon. + char *semicolon = strchr(line, ';'); + if (semicolon != NULL) { + *semicolon = '\0'; // Terminate the string at the semicolon + trim(line); // Trim again in case spaces remain + if (line[0] == '\0') { + lineIndex++; + continue; + } + } + // Remove any label definitions (up to the colon). char *colon = strchr(line, ':'); if (colon != NULL) { @@ -398,31 +424,20 @@ uint32_t completePass(const char *source, CPU *cpu, bool erase) { fprintf(stderr, "Error: MOV requires two operands.\n"); return 1; } - int resolvedOpcode = resolveMOV(operand1, operand2); + int resolvedOpcode = resolveMOV(operand2, operand1); cpu->memory[addr++] = resolvedOpcode; - if (resolvedOpcode == MOV_RN_IMM) { - int reg = parseRegister(operand1); - uint8_t imm = parseImmediate(operand2); - cpu->memory[addr++] = reg; + if (resolvedOpcode == MOV_IMM_RN) { + uint8_t imm = parseImmediate(operand1); + int reg = parseRegister(operand2); cpu->memory[addr++] = imm; - } else if (resolvedOpcode == MOV_RN_RM) { - int regDest = parseRegister(operand1); - int regSrc = parseRegister(operand2); - cpu->memory[addr++] = regDest; - cpu->memory[addr++] = regSrc; - } else if (resolvedOpcode == MOV_RN_ADDR) { - int reg = parseRegister(operand1); - // Assume source is written as "[address]": remove the brackets. - char addrStr[32]; - strncpy(addrStr, operand2 + 1, strlen(operand2) - 2); - addrStr[strlen(operand2) - 2] = '\0'; - uint32_t memAddr = (uint32_t) strtoul(addrStr, NULL, 0); cpu->memory[addr++] = reg; - cpu->memory[addr++] = memAddr & 0xFF; - cpu->memory[addr++] = (memAddr >> 8) & 0xFF; - cpu->memory[addr++] = (memAddr >> 16) & 0xFF; + } else if (resolvedOpcode == MOV_RN_RM) { + int regSrc = parseRegister(operand1); + int regDest = parseRegister(operand2); + cpu->memory[addr++] = regSrc; + cpu->memory[addr++] = regDest; } else if (resolvedOpcode == MOV_ADDR_RN) { - // Destination is memory (written as "[address]"). + // Assume source is written as "[address]": remove the brackets. char addrStr[32]; strncpy(addrStr, operand1 + 1, strlen(operand1) - 2); addrStr[strlen(operand1) - 2] = '\0'; @@ -432,6 +447,17 @@ uint32_t completePass(const char *source, CPU *cpu, bool erase) { cpu->memory[addr++] = (memAddr >> 8) & 0xFF; cpu->memory[addr++] = (memAddr >> 16) & 0xFF; cpu->memory[addr++] = reg; + } else if (resolvedOpcode == MOV_RN_ADDR) { + // Destination is memory (written as "[address]"). + char addrStr[32]; + strncpy(addrStr, operand2 + 1, strlen(operand2) - 2); + addrStr[strlen(operand2) - 2] = '\0'; + int reg = parseRegister(operand1); + uint32_t memAddr = (uint32_t) strtoul(addrStr, NULL, 0); + cpu->memory[addr++] = reg; + cpu->memory[addr++] = memAddr & 0xFF; + cpu->memory[addr++] = (memAddr >> 8) & 0xFF; + cpu->memory[addr++] = (memAddr >> 16) & 0xFF; } } // --- INC and DEC (baseOpcode == -12 or -13) --- @@ -465,7 +491,7 @@ uint32_t completePass(const char *source, CPU *cpu, bool erase) { fprintf(stderr, "Error: %s requires two operands.\n", mnemonic); return 1; } - int resolvedOpcode = resolveALU(baseOpcode, operand2); + int resolvedOpcode = resolveALU(baseOpcode, operand1); cpu->memory[addr++] = resolvedOpcode; int regDest = parseRegister(operand1); cpu->memory[addr++] = regDest; @@ -617,7 +643,7 @@ uint32_t completePass(const char *source, CPU *cpu, bool erase) { } const uint32_t remainingBytes = CPU_INSTRUCTION_SIZE - (addr - oldAddr); if (remainingBytes > CPU_INSTRUCTION_SIZE) { - printf("HELP, INSTRUCTION SIZE SMALLER THAN INSTRUCTION"); + printf("HELP, INSTRUCTION SIZE SMALLER THAN INSTRUCTION\n"); } cpu->addrToLineMapper[(addr - (addr % CPU_INSTRUCTION_SIZE)) / CPU_INSTRUCTION_SIZE] = lineIndex; addr += remainingBytes; diff --git a/assembler/assembler.h b/assembler/assembler.h index 9c31a43..efa45d9 100644 --- a/assembler/assembler.h +++ b/assembler/assembler.h @@ -65,7 +65,7 @@ int getOpcode(char *mnemonic); // 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. +// For example, "MOV Rn, 42" should choose MOV_IMM_RN, 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); diff --git a/cpu/core.c b/cpu/core.c index e120dbd..133ee19 100644 --- a/cpu/core.c +++ b/cpu/core.c @@ -93,10 +93,10 @@ void step(CPU *cpu) { write_mem(cpu, addrTemp, temp - 1); break; - case MOV_RN_IMM: + case MOV_IMM_RN: //Load from immediate to register - reg1 = read_reg_number(cpu); imm = read_mem(cpu, cpu->pc++); + reg1 = read_reg_number(cpu); cpu->regs[reg1] = imm; break; @@ -372,7 +372,43 @@ void step(CPU *cpu) { break; } - case JMP_BIT_SET_RN: + case BITS_RN: { + // Jump if bit in register set + reg1 = read_reg_number(cpu); + uint8_t bit = read_mem(cpu, cpu->pc++); + + temp = read_reg(cpu, reg1); + if (reg1 >= REG_COUNT) { + reg1 = REG_COUNT - 1; + } + if (bit > 7) { + bit = 7; + } + temp |= (1 << bit); + write_reg(cpu, reg1, temp); + cpu->pc += CPU_INSTRUCTION_SIZE; + break; + } + + case BITC_RN: { + // Jump if bit in register set + reg1 = read_reg_number(cpu); + uint8_t bit = read_mem(cpu, cpu->pc++); + + temp = read_reg(cpu, reg1); + if (reg1 >= REG_COUNT) { + reg1 = REG_COUNT - 1; + } + if (bit > 7) { + bit = 7; + } + temp &= ~(1 << bit); + write_reg(cpu, reg1, temp); + cpu->pc += CPU_INSTRUCTION_SIZE; + break; + } + + case BIT_TS_RN: { // Jump if bit in register set reg1 = read_reg_number(cpu); uint8_t bit = read_mem(cpu, cpu->pc++); @@ -391,13 +427,40 @@ void step(CPU *cpu) { } cpu->pc += CPU_INSTRUCTION_SIZE; break; + } - case JMP_BIT_SET_ADDR: { + case BITS_ADDR: { // Jump if bit in register set addrTemp = read_address_argument(cpu); - if (addrTemp >= MEM_SIZE) { - addrTemp = MEM_SIZE - 1; + uint8_t bit = read_mem(cpu, cpu->pc++); + temp = read_mem(cpu, addrTemp); + if (bit > 7) { + bit = 7; } + temp |= (1 << bit); + write_mem(cpu, addrTemp, temp); + cpu->pc += CPU_INSTRUCTION_SIZE; + break; + } + + case BITC_ADDR: { + // Jump if bit in register set + addrTemp = read_address_argument(cpu); + uint8_t bit = read_mem(cpu, cpu->pc++); + + temp = read_mem(cpu, addrTemp); + if (bit > 7) { + bit = 7; + } + temp &= ~(1 << bit); + write_mem(cpu, addrTemp, temp); + cpu->pc += CPU_INSTRUCTION_SIZE; + break; + } + + case BIT_TS_ADDR: { + // Jump if bit in register set + addrTemp = read_address_argument(cpu); uint8_t bit = read_mem(cpu, cpu->pc++); if (bit > 7) { bit = 7; @@ -423,7 +486,7 @@ void step(CPU *cpu) { break; } - case JMP_BIT_CLEAR_RN: { + case BIT_TC_RN: { // Jump if bit in register set reg1 = read_reg_number(cpu); temp = read_reg(cpu, reg1); @@ -440,7 +503,7 @@ void step(CPU *cpu) { break; } - case JMP_BIT_CLEAR_ADDR: { + case BIT_TC_ADDR: { // Jump if bit in register set addrTemp = read_address_argument(cpu); uint8_t bit = read_mem(cpu, cpu->pc++); diff --git a/cpu/core.h b/cpu/core.h index 69db860..7e526f4 100644 --- a/cpu/core.h +++ b/cpu/core.h @@ -22,6 +22,7 @@ #define CPU_MODE_LOOP (1 << 3) #define CPU_MODE_STEP (1 << 4) #define CPU_MODE_SECOND (1 << 5) +#define CPU_MODE_FRAME (1 << 6) #define CPU_INSTRUCTION_SIZE 8 //Biggest instruction @@ -56,7 +57,7 @@ typedef enum { HLT, - MOV_RN_IMM, // MOV Rn, Imm - Move immediate to register (Rn = Imm) + MOV_IMM_RN, // 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) @@ -114,11 +115,17 @@ typedef enum { JE, // JE Addr - Jump if equal (if zero flag set, PC = Addr) JNE, // JNE Addr - Jump if not equal (if zero flag not set, PC = Addr) - JMP_BIT_SET_RN, // jump relative if a given bit in a register is set - JMP_BIT_SET_ADDR, // jump relative if a given bit in memory is set + BIT_TS_RN, // jump relative if a given bit in a register is set + BIT_TS_ADDR, // jump relative if a given bit in memory is set - JMP_BIT_CLEAR_RN, // jump relative if a given bit in a register is not set - JMP_BIT_CLEAR_ADDR, // jump relative if a given bit in memory is not set + BIT_TC_RN, // jump relative if a given bit in a register is not set + BIT_TC_ADDR, // jump relative if a given bit in memory is not set + + BITS_RN, // jump relative if a given bit in a register is set + BITS_ADDR, // jump relative if a given bit in memory is set + + BITC_RN, // jump relative if a given bit in a register is not set + BITC_ADDR, // jump relative if a given bit in memory is not set JG, // JG Addr - Jump if greater (if greater flag set, PC = Addr) diff --git a/cpu/memory.c b/cpu/memory.c index 06d8ef2..ec12fcb 100644 --- a/cpu/memory.c +++ b/cpu/memory.c @@ -4,6 +4,8 @@ #include "memory.h" +#define SFR_OFFSET (0xDF00) + uint8_t write_mem(CPU *cpu, uint32_t addr, uint8_t value) { if (addr >= MEM_SIZE) { return 1; @@ -11,10 +13,50 @@ uint8_t write_mem(CPU *cpu, uint32_t addr, uint8_t value) { switch (addr) { + case SFR_OFFSET + 0x00: + pcm_buffer_push(&audioData.pcmVoice, value << 8 | cpu->memory[SFR_OFFSET + 0x01]); + + case SFR_OFFSET + 0x02: + audioData.synthVoices[0].volume = value; + + case SFR_OFFSET + 0x03: + audioData.synthVoices[0].waveform = value; + + case SFR_OFFSET + 0x04: + audioData.synthVoices[0].phase = value; + + case SFR_OFFSET + 0x05: + audioData.synthVoices[0].frequency = value << 8 | cpu->memory[SFR_OFFSET + 0x06]; + + case SFR_OFFSET + 0x07: + audioData.synthVoices[1].volume = value; + + case SFR_OFFSET + 0x08: + audioData.synthVoices[1].waveform = value; + + case SFR_OFFSET + 0x09: + audioData.synthVoices[1].phase = value; + + case SFR_OFFSET + 0x0A: + audioData.synthVoices[1].frequency = value << 8 | cpu->memory[SFR_OFFSET + 0x0B]; + + case SFR_OFFSET + 0x0C: + audioData.synthVoices[2].volume = value; + + case SFR_OFFSET + 0x0D: + audioData.synthVoices[2].waveform = value; + + case SFR_OFFSET + 0x0E: + audioData.synthVoices[2].phase = value; + + case SFR_OFFSET + 0x0F: + audioData.synthVoices[2].frequency = value << 8 | cpu->memory[SFR_OFFSET + 0x10]; + default: - cpu->memory[addr] = value; + break; } + cpu->memory[addr] = value; return 0; } @@ -50,6 +92,15 @@ uint32_t read_address_argument(CPU *cpu) { return out; } +uint8_t read_register_argument(CPU *cpu) { + uint8_t out = read_mem(cpu, cpu->pc); + cpu->pc += 1; + if (out >= REG_COUNT) { + out = REG_COUNT - 1; + } + return out; +} + // Push a 32-bit program counter (PC) onto the stack void write_stack(CPU *cpu) { if (cpu->pc >= MEM_SIZE) { diff --git a/cpu/memory.h b/cpu/memory.h index b955a13..0d4fd23 100644 --- a/cpu/memory.h +++ b/cpu/memory.h @@ -7,6 +7,7 @@ #include #include "../cpu/core.h" +#include "../peripherals/peripheraldata.h" uint8_t write_mem32(CPU *cpu, uint32_t addr, uint32_t value); @@ -32,5 +33,7 @@ uint8_t read_mem(CPU *cpu, uint32_t addr); uint8_t write_mem(CPU *cpu, uint32_t addr, uint8_t value); +uint8_t read_register_argument(CPU *cpu); + #endif //RISCB_MEMORY_H diff --git a/distWin/SDL2.dll b/distWin/SDL2.dll new file mode 100644 index 0000000..9d06e14 Binary files /dev/null and b/distWin/SDL2.dll differ diff --git a/distWin/SDL2_ttf.dll b/distWin/SDL2_ttf.dll new file mode 100644 index 0000000..d1dae94 Binary files /dev/null and b/distWin/SDL2_ttf.dll differ diff --git a/docs/instructions.md b/docs/instructions.md index 2fd4d64..e1076b4 100644 --- a/docs/instructions.md +++ b/docs/instructions.md @@ -10,7 +10,7 @@ `HLT` - Halt CPU execution -### **MOV_RN_IMM** +### **MOV_IMM_RN** `MOV R1, 0x10` - Move immediate value `0x10` to register `R1` @@ -178,18 +178,73 @@ `RET` - Return from subroutine -### **JMP_BIT_CLEAR_RN** +### **BIT_TC_RN** `JMPBC R1, 3, 0x4000` - Jump to address `0x4000` if bit `3` in register `R1` is **not set**. -### **JMP_BIT_CLEAR_ADDR** +### **BIT_TC_ADDR** `JMPBC 0x2000, 5, 0x5000` - Jump to address `0x5000` if bit `5` in memory at address `0x2000` is **not set**. -### **JMP_BIT_SET_RN** +### **BIT_TS_RN** `JMPBS R2, 1, 0x6000` - Jump to address `0x6000` if bit `1` in register `R2` **is set**. -### **JMP_BIT_SET_ADDR** +### **BIT_TS_ADDR** + +`JMPBS 0x3000, 7, 0x7000` - Jump to address `0x7000` if bit `7` in memory at address `0x3000` **is set**. + +### **BITS_ADDR** + +`BITS [0x2000], 3` - Set bit `3` in memory at address `0x2000` + +- Reads a memory address as an argument. +- Reads a bit index from the next byte. +- Ensures the bit index is between `0-7`. +- Sets the specified bit in the memory address. +- Increments the program counter. + +### **BITC_ADDR** + +`BITC [0x2000], 5` - Clear bit `5` in memory at address `0x2000` + +- Reads a memory address as an argument. +- Reads a bit index from the next byte. +- Ensures the bit index is between `0-7`. +- Clears the specified bit in the memory address. +- Increments the program counter. + +### **BITS_RN** + +`BITS R1, 2` - Set bit `2` in register `R1` + +- Reads a register number. +- Reads a bit index from the next byte. +- Ensures the register is valid (`0 - REG_COUNT - 1`). +- Ensures the bit index is between `0-7`. +- Sets the specified bit in the register. +- Increments the program counter. + +### **BITC_RN** + +`BITC R2, 6` - Clear bit `6` in register `R2` + +- Reads a register number. +- Reads a bit index from the next byte. +- Ensures the register is valid (`0 - REG_COUNT - 1`). +- Ensures the bit index is between `0-7`. +- Clears the specified bit in the register. +- Increments the program counter. + +### **BITS (Special Instruction)** + +- This mnemonic decides between `BITS_RN` and `BITS_ADDR` based on the operand. +- If the operand is a register, `BITS_RN` is used. +- If the operand is a memory address, `BITS_ADDR` is used. + +### **BITC (Special Instruction)** + +- This mnemonic decides between `BITC_RN` and `BITC_ADDR` based on the operand. +- If the operand is a register, `BITC_RN` is used. +- If the operand is a memory address, `BITC_ADDR` is used. -`JMPBS 0x3000, 7, 0x7000` - Jump to address `0x7000` if bit `7` in memory at address `0x3000` **is set**. \ No newline at end of file diff --git a/main.c b/main.c index e681596..b7f0516 100644 --- a/main.c +++ b/main.c @@ -1,11 +1,13 @@ #include #include #include +#include #include "util/font.h" #include "assembler/assembler.h" #include "util/texteditor.h" #include "util/hexdump.h" #include "util/cpustatusui.h" +#include "peripherals/peripheraldata.h" //Screen dimension constants const int SCREEN_WIDTH = 1280; @@ -13,8 +15,11 @@ const int SCREEN_HEIGHT = 720; const int targetFPS = 60; const int delayNeeded = 1000 / targetFPS; +#define TARGET_CYCLE_TIME_NS 15625 // 15.625 µs per cycle for 64 kHz + //The window we'll be rendering to SDL_Window *window = NULL; +volatile bool running = true; //The surface contained by the window SDL_Renderer *renderer = NULL; @@ -44,6 +49,71 @@ TextEditor editors[editorCount]; unsigned long frames = 0; bool cursor = true; +pthread_t cpu_thread; +pthread_mutex_t cpu_mutex = PTHREAD_MUTEX_INITIALIZER; + +void msleep(unsigned int milliseconds) { + struct timespec ts; + ts.tv_sec = milliseconds / 1000; + ts.tv_nsec = (milliseconds % 1000) * 1000000; + nanosleep(&ts, NULL); +} + +void *cpu_loop(void *arg) { + uint64_t last_tick = SDL_GetTicks64(); + uint64_t last_speed_check = last_tick;; + uint64_t step_count = 0; + struct timespec start, end, sleep_time; + while (running) { + pthread_mutex_lock(&cpu_mutex); + + if (!(cpu.mode & (CPU_MODE_PAUSED | CPU_MODE_HALTED | CPU_MODE_ERROR))) { + clock_gettime(CLOCK_MONOTONIC, &start); // Get start time + step(&cpu); + clock_gettime(CLOCK_MONOTONIC, &end); // Get end time + // Compute step duration in nanoseconds + uint64_t step_duration_ns = (end.tv_sec - start.tv_sec) * 1000000000ULL + + (end.tv_nsec - start.tv_nsec); + + // Compute remaining time to maintain 64 kHz + if (step_duration_ns < TARGET_CYCLE_TIME_NS) { + uint64_t remaining_ns = TARGET_CYCLE_TIME_NS - step_duration_ns; + sleep_time.tv_sec = 0; + sleep_time.tv_nsec = remaining_ns; + clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, &sleep_time, NULL); + } + + step_count++; + } else { + pthread_mutex_unlock(&cpu_mutex); + msleep(10); + } + + if (cpu.mode & CPU_MODE_SECOND) { + pthread_mutex_unlock(&cpu_mutex); + msleep(1000); + continue; + } else if (cpu.mode & CPU_MODE_FRAME) { + uint64_t frame_duration = SDL_GetTicks64() - last_tick; + if (frame_duration < delayNeeded) { + pthread_mutex_unlock(&cpu_mutex); + msleep(delayNeeded - frame_duration); + continue; + } + last_tick = SDL_GetTicks64(); + } + // Print speed every second + uint64_t now = SDL_GetTicks64(); + if (now - last_speed_check >= 1000) { + printf("CPU Max Speed: %lu Hz\n", step_count); + step_count = 0; + last_speed_check = now; + } + pthread_mutex_unlock(&cpu_mutex); + } + return NULL; +} + char *read_file_as_string(const char *filename) { FILE *file = fopen(filename, "rb"); // Open file in binary mode if (file == NULL) { @@ -91,7 +161,10 @@ void compile(bool erase) { int init() { //Initialize SDL - if (SDL_Init(SDL_INIT_VIDEO) < 0) { + SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, NULL); + SDL_SetHint(SDL_HINT_VIDEO_HIGHDPI_DISABLED, "1"); + SDL_SetHint(SDL_HINT_RENDER_DRIVER, "opengl"); + if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO) < 0) { printf("SDL could not initialize! SDL_Error: %s\n", SDL_GetError()); return 1; } @@ -116,6 +189,24 @@ int init() { return 1; } + memset(&audioData, 0, sizeof(AudioData)); + + SDL_AudioSpec spec = {0}; + spec.freq = SAMPLE_RATE; + spec.format = AUDIO_F32SYS; + spec.channels = 1; + spec.samples = 4096; + spec.callback = audio_callback; + spec.userdata = &audioData; + + SDL_AudioDeviceID dev = SDL_OpenAudioDevice(NULL, 0, &spec, NULL, 0); + if (dev == 0) { + printf("Failed to open audio: %s\n", SDL_GetError()); + SDL_Quit(); + } + + SDL_PauseAudioDevice(dev, 0); + SDL_Rect viewport = {0, 0, SCREEN_WIDTH, SCREEN_HEIGHT}; SDL_RenderSetViewport(renderer, &viewport); @@ -126,7 +217,6 @@ int init() { init_editor(&codeEditor, &smallFont, 10, 80, renderer, 35, 1000, 48, false); init_editor(&memoryViewer, &smallerFont, 738, 80, renderer, 59, MEM_SIZE / 16 + 2, 70, true); SDL_RenderSetLogicalSize(renderer, SCREEN_WIDTH, SCREEN_HEIGHT); - SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, NULL); for (int i = 0; i < editorCount; i++) { generate_string_display(&codeEditor, renderer); } @@ -150,18 +240,22 @@ int render() { rect2.w = 0; rect2.h = 0; - SDL_QueryTexture(cpuStatsTexture, NULL, NULL, &rect2.w, &rect2.h); + if (cpuStatsTexture) { + SDL_QueryTexture(cpuStatsTexture, NULL, NULL, &rect2.w, &rect2.h); - SDL_RenderCopy(renderer, cpuStatsTexture, NULL, &rect2); + SDL_RenderCopy(renderer, cpuStatsTexture, NULL, &rect2); + } rect2.x = 100; rect2.y = 46; rect2.w = 0; rect2.h = 0; - SDL_QueryTexture(cpuStateTexture, NULL, NULL, &rect2.w, &rect2.h); + if (cpuStateTexture) { + SDL_QueryTexture(cpuStateTexture, NULL, NULL, &rect2.w, &rect2.h); - SDL_RenderCopy(renderer, cpuStateTexture, NULL, &rect2); + SDL_RenderCopy(renderer, cpuStateTexture, NULL, &rect2); + } SDL_RenderPresent(renderer); frames++; @@ -289,6 +383,11 @@ int processEvent(SDL_Event e) { int newWidth = e.window.data1; int newHeight = e.window.data2; + for (int editorIndex = 0; editorIndex < editorCount; editorIndex++) { + generate_string_display(&editors[editorIndex], renderer); + } + updateState(true); + // Adjust the viewport to match the new window size; SDL_Rect viewport = {0, 0, newWidth, newHeight}; SDL_RenderSetViewport(renderer, &viewport); @@ -346,11 +445,18 @@ int processEvent(SDL_Event e) { updateState(false); return 1; case SDLK_F8: - if (++cpuSpeedTemp == 3) { - cpuSpeedTemp = 0; + if (cpu.mode & CPU_MODE_STEP) { + cpu.mode &= ~CPU_MODE_STEP; + cpu.mode |= CPU_MODE_SECOND; + } else if (cpu.mode & CPU_MODE_SECOND) { + cpu.mode &= ~CPU_MODE_SECOND; + cpu.mode |= CPU_MODE_FRAME; + } else if (cpu.mode & CPU_MODE_FRAME) { + cpu.mode &= ~CPU_MODE_FRAME; + // No specific mode set, defaults to none + } else { + cpu.mode |= CPU_MODE_STEP; // Restart cycle } - cpu.mode &= ~(CPU_MODE_SECOND | CPU_MODE_STEP); - cpu.mode |= cpuSpeedTemp << 4; updateState(false); return 1; case SDLK_F5: @@ -367,6 +473,7 @@ int processEvent(SDL_Event e) { cpu.mode |= CPU_MODE_PAUSED; if (keyMod & (KMOD_CTRL | KMOD_SHIFT)) { cpu.mode |= CPU_MODE_HALTED; + cpu.pc = 0; } updateState(false); break; @@ -376,10 +483,16 @@ int processEvent(SDL_Event e) { FILE *fptr; char fname[20]; snprintf(fname, sizeof(fname), "riscb%lu.bsm", time(NULL)); - fptr = fopen(fname, "w"); + fptr = fopen(fname, "wb"); generate_string(&editors[0]); fputs(editors[0].outputString, fptr); fclose(fptr); + if (keyMod & KMOD_SHIFT) { + snprintf(fname, sizeof(fname), "riscb%lu.mem", time(NULL)); + fptr = fopen(fname, "wb"); + fwrite(cpu.memory, sizeof(cpu.memory[0]), MEM_SIZE, fptr); + fclose(fptr); + } return 1; } break; @@ -391,7 +504,7 @@ int processEvent(SDL_Event e) { sscanf(editors[0].lines[editors[0].cursor_line].text, "%s", fname); toLowerCase(fname); strcat(fname, ".bsm"); - fptr = fopen(fname, "r"); + fptr = fopen(fname, "rb"); if (fptr) { char *prog = read_file_as_string(fname); toUpperCase(prog); @@ -465,11 +578,12 @@ int main(__attribute__((unused)) int argc, __attribute__((unused)) char *args[]) //Hack to get window to stay up SDL_Event e; - bool running = true; Uint64 start; Uint64 end; + pthread_create(&cpu_thread, NULL, cpu_loop, NULL); while (running) { start = SDL_GetTicks64(); + pthread_mutex_lock(&cpu_mutex); while (SDL_PollEvent(&e)) { running = processEvent(e); } @@ -495,12 +609,11 @@ int main(__attribute__((unused)) int argc, __attribute__((unused)) char *args[]) updateState(false); } } + pthread_mutex_unlock(&cpu_mutex); end = SDL_GetTicks64(); const unsigned long timeNeeded = end - start; if (timeNeeded < delayNeeded) { SDL_Delay(delayNeeded - timeNeeded); - } else { - printf("%lu\n", timeNeeded); } } @@ -517,6 +630,7 @@ int main(__attribute__((unused)) int argc, __attribute__((unused)) char *args[]) if (cpuStatsTexture) SDL_DestroyTexture(cpuStatsTexture); if (cpuStateTexture) SDL_DestroyTexture(cpuStateTexture); + pthread_join(cpu_thread, NULL); if (renderer) SDL_DestroyRenderer(renderer); if (window) SDL_DestroyWindow(window); diff --git a/peripherals/audio.c b/peripherals/audio.c new file mode 100644 index 0000000..bcd2583 --- /dev/null +++ b/peripherals/audio.c @@ -0,0 +1,79 @@ +/* +// Created by bruno on 16.2.2025. +*/ + +#include "audio.h" + +int pcm_buffer_empty(PcmVoice *pcm) { + return pcm->head == pcm->tail; +} + +int pcm_buffer_full(PcmVoice *pcm) { + return ((pcm->tail + 1) % PCM_BUFFER_SIZE) == pcm->head; +} + +void pcm_buffer_push(PcmVoice *pcm, uint16_t sample) { + if (!pcm_buffer_full(pcm)) { + pcm->buffer[pcm->tail] = sample; + pcm->tail = (pcm->tail + 1) % PCM_BUFFER_SIZE; + } +} + +uint16_t pcm_buffer_pop(PcmVoice *pcm) { + if (!pcm_buffer_empty(pcm)) { + uint16_t sample = pcm->buffer[pcm->head]; + pcm->head = (pcm->head + 1) % PCM_BUFFER_SIZE; + return sample; + } + return 0; +} + +void audio_callback(void *userdata, Uint8 *stream, int len) { + AudioData *audio = (AudioData *) userdata; + int samples = len / sizeof(float); + + for (int i = 0; i < samples; i++) { + float mix = 0.0f; + int activeVoices = 0; + + for (int v = 0; v < NUM_SYNTH_VOICES; v++) { + SynthVoice *voice = &audio->synthVoices[v]; + if (voice->volume == 0 || voice->frequency == 0) continue; + + float sample = 0.0f; + float t = (float) voice->phase / 255.0f * 2.0f - 1.0f; + + switch (voice->waveform) { + default: + case WAVE_SINE: + sample = sinf(voice->phase * 2.0f * M_PI / 256.0f); + break; + case WAVE_SQUARE: + sample = (t >= 0.0f) ? 1.0f : -1.0f; + break; + case WAVE_SAWTOOTH: + sample = t; + break; + case WAVE_TRIANGLE: + sample = (t < 0) ? -t : t; + break; + case WAVE_NOISE: + sample = ((float) rand() / RAND_MAX) * 2.0f - 1.0f; + break; + } + + voice->phase += (uint8_t) ((voice->frequency * 256) / SAMPLE_RATE); + mix += sample * (voice->volume / 255.0f); + activeVoices++; + } + + PcmVoice *pcm = &audio->pcmVoice; + if (pcm->volume > 0 && !pcm_buffer_empty(pcm)) { + float pcmSample = pcm_buffer_pop(pcm) / 65535.0f; + mix += pcmSample * (pcm->volume / 255.0f); + activeVoices++; + } + + ((float *) stream)[i] = (activeVoices > 0) ? mix / activeVoices : 0.0f; + } +} diff --git a/peripherals/audio.h b/peripherals/audio.h new file mode 100644 index 0000000..cd4721c --- /dev/null +++ b/peripherals/audio.h @@ -0,0 +1,55 @@ +/* +// Created by bruno on 16.2.2025. +*/ + +#ifndef RISCB_AUDIO_H +#define RISCB_AUDIO_H + +#include +#include +#include +#include + +#define SAMPLE_RATE 44100 +#define NUM_SYNTH_VOICES 3 +#define PCM_BUFFER_SIZE 4096 + +typedef enum { + WAVE_SINE, + WAVE_SQUARE, + WAVE_SAWTOOTH, + WAVE_TRIANGLE, + WAVE_NOISE +} Waveform; + +typedef struct { + uint8_t volume; + uint16_t frequency; + uint8_t phase; + Waveform waveform; +} SynthVoice; + +typedef struct { + uint8_t volume; + uint8_t upsample_factor; + uint8_t upsample_count; + uint16_t buffer[PCM_BUFFER_SIZE]; + size_t head, tail; +} PcmVoice; + +typedef struct { + SynthVoice synthVoices[NUM_SYNTH_VOICES]; + PcmVoice pcmVoice; +} AudioData; + +int pcm_buffer_empty(PcmVoice *pcm); + +int pcm_buffer_full(PcmVoice *pcm); + +void pcm_buffer_push(PcmVoice *pcm, uint16_t sample); + +uint16_t pcm_buffer_pop(PcmVoice *pcm); + +void audio_callback(void *userdata, Uint8 *stream, int len); + +#endif //RISCB_AUDIO_H diff --git a/peripherals/peripheraldata.c b/peripherals/peripheraldata.c new file mode 100644 index 0000000..0cc7036 --- /dev/null +++ b/peripherals/peripheraldata.c @@ -0,0 +1,7 @@ +// +// Created by bruno on 16.2.2025. +// + +#include "peripheraldata.h" + +AudioData audioData; \ No newline at end of file diff --git a/peripherals/peripheraldata.h b/peripherals/peripheraldata.h new file mode 100644 index 0000000..5725664 --- /dev/null +++ b/peripherals/peripheraldata.h @@ -0,0 +1,11 @@ +// +// Created by bruno on 16.2.2025. +// + +#ifndef RISCB_PERIPHERALDATA_H +#define RISCB_PERIPHERALDATA_H + +#include "audio.h" + +extern AudioData audioData; +#endif //RISCB_PERIPHERALDATA_H diff --git a/util/cpustatusui.c b/util/cpustatusui.c index d9fbcbb..86eae9c 100644 --- a/util/cpustatusui.c +++ b/util/cpustatusui.c @@ -121,7 +121,7 @@ void renderState(CPU *cpu, BitmapFont *titleFont, SDL_Renderer *renderer, SDL_Te } else if (cpu->mode & CPU_MODE_HALTED) { strcat(valueStr, "HLT "); } else if (cpu->mode & CPU_MODE_PAUSED) { - strcat(valueStr, "PAUS "); + strcat(valueStr, "PS "); } else { strcat(valueStr, "RUN "); } @@ -136,6 +136,8 @@ void renderState(CPU *cpu, BitmapFont *titleFont, SDL_Renderer *renderer, SDL_Te strcat(valueStr, "STP"); } else if (cpu->mode & CPU_MODE_SECOND) { strcat(valueStr, "SEC"); + } else if (cpu->mode & CPU_MODE_FRAME) { + strcat(valueStr, "FRM"); } else { strcat(valueStr, "BRR"); } diff --git a/util/font.c b/util/font.c index 4f8d377..b8371a4 100644 --- a/util/font.c +++ b/util/font.c @@ -13,10 +13,10 @@ prepText(SDL_Renderer *renderer, unsigned char pxSize, const char *file, uint8_t unsigned int i = 1; do { if (i == 173) { //specifically this char is 0 width (IDK why) - out.surface[i] = SDL_CreateRGBSurface(0,pxSize,pxSize,32,0,0,0,0); + out.surface[i] = SDL_CreateRGBSurface(0, pxSize, pxSize, 32, 0, 0, 0, 0); } else { - char tmpOut[2] = {i, 0}; - out.surface[i] = TTF_RenderText_Solid(gFont, tmpOut, out.color); + char tmpOut[2] = {i, 0}; + out.surface[i] = TTF_RenderText_Solid(gFont, tmpOut, out.color); } out.texture[i] = SDL_CreateTextureFromSurface(renderer, out.surface[i]); i++; @@ -51,7 +51,11 @@ void renderText(SDL_Renderer *renderer, BitmapFont font, char *string, uint16_t void destroyFont(BitmapFont *font) { for (uint16_t i = 1; i < 256; i++) { - SDL_DestroyTexture(font->texture[i]); - SDL_FreeSurface(font->surface[i]); + if (font->texture[i]) { + SDL_DestroyTexture(font->texture[i]); + } + if (font->surface[i]) { + SDL_FreeSurface(font->surface[i]); + } } } \ No newline at end of file diff --git a/util/texteditor.c b/util/texteditor.c index a953e57..38b2ca1 100644 --- a/util/texteditor.c +++ b/util/texteditor.c @@ -49,8 +49,9 @@ void init_editor(TextEditor *editor, BitmapFont *font, int x, int y, SDL_Rendere } // Allocate output and display strings. - editor->outputString = (char *) malloc(sizeof(char) * (editor->max_line_width * editor->maxLines + 1)); - editor->displayString = (char *) malloc(sizeof(char) * (editor->max_line_width * editor->displayLineCount + 1)); + editor->outputString = (char *) malloc(sizeof(char) * ((editor->max_line_width + 1) * editor->maxLines + 1) + 1); + editor->displayString = (char *) malloc( + sizeof(char) * ((editor->max_line_width + 1) * editor->displayLineCount + 1) + 1); if (!editor->outputString || !editor->displayString) { fprintf(stderr, "Failed to allocate memory for output/display strings.\n"); exit(EXIT_FAILURE); @@ -301,9 +302,9 @@ void generate_string_display(TextEditor *editor, SDL_Renderer *renderer) { charDstRect.x += charDstRect.w + 1; linePTR++; } - strcat(editor->displayString, "\n"); charDstRect.x = 4; charDstRect.y += charDstRect.h + 1; + strcat(editor->displayString, "\n"); } } SDL_SetRenderTarget(renderer, NULL);