#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; 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; #define biggerFont fonts[0] #define smallFont fonts[1] #define smallerFont fonts[2] #define fontCount 3 BitmapFont fonts[fontCount]; CPU cpu; SDL_Texture *cpuStatsTexture; SDL_Texture *cpuStateTexture; #define codeEditor editors[0] #define memoryViewer editors[1] #define editorCount 2 #define activeEditor editors[activeEditorIndex] int activeEditorIndex = 0; 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(); while (running) { pthread_mutex_lock(&cpu_mutex); if (!(cpu.mode & (CPU_MODE_PAUSED | CPU_MODE_HALTED | CPU_MODE_ERROR))) { step(&cpu); } 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(); } 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) { perror("Error opening file"); return NULL; } // Seek to the end of the file to determine size fseek(file, 0, SEEK_END); long filesize = ftell(file); rewind(file); // Go back to the beginning // Allocate memory for file content (+1 for null terminator) char *buffer = malloc(filesize + 1); if (buffer == NULL) { perror("Memory allocation failed"); fclose(file); return NULL; } // Read entire file into buffer fread(buffer, 1, filesize, file); buffer[filesize] = '\0'; // Null-terminate the string fclose(file); return buffer; // Caller must free the memory } void updateState(bool full) { renderVals(&cpu, &smallFont, &smallerFont, renderer, &cpuStatsTexture); renderState(&cpu, &biggerFont, renderer, &cpuStateTexture); char *dump = hexdump_to_string(cpu.memory, full ? 0 : memoryViewer.cursor_line_offset * 16, full ? MEM_SIZE : (memoryViewer.cursor_line_offset * 16) + (memoryViewer.displayLineCount * 16)); fill_editor_from_string(&memoryViewer, dump, full ? 0 : memoryViewer.cursor_line_offset, full, renderer); free(dump); } void compile(bool erase) { generate_string(&codeEditor); completePass(codeEditor.outputString, &cpu, erase); updateState(true); } int init() { //Initialize SDL 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; } //Initialize SDL_ttf if (TTF_Init() == -1) { printf("SDL_ttf could not initialize! SDL_ttf Error: %s\n", TTF_GetError()); return 1; } //Create window window = SDL_CreateWindow("RISC-B simulator", 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; } // Create OpenGL context SDL_GLContext glContext = SDL_GL_CreateContext(window); if (!glContext) { fprintf(stderr, "SDL_GL_CreateContext failed: %s\n", SDL_GetError()); exit(1); } // Use OpenGL context SDL_GL_MakeCurrent(window, glContext); // Make sure OpenGL context is current before any OpenGL rendering 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); init_seven_segment(&displayA, renderer, 480, 240, 60, 120); init_seven_segment(&displayB, renderer, 545, 240, 60, 120); init_seven_segment(&displayC, renderer, 610, 240, 60, 120); init_seven_segment(&displayD, renderer, 675, 240, 60, 120); init_seven_segment(&displayE, renderer, 480, 375, 60, 120); init_seven_segment(&displayF, renderer, 545, 375, 60, 120); init_seven_segment(&displayG, renderer, 610, 375, 60, 120); init_seven_segment(&displayH, renderer, 675, 375, 60, 120); init_switches(&switchesA, renderer, 500, 500, 100, 100); init_switches(&switchesB, renderer, 625, 500, 100, 100); init_switches(&switchesC, renderer, 500, 610, 100, 100); init_switches(&switchesD, renderer, 625, 610, 100, 100); gpuSurf = SDL_CreateRGBSurfaceWithFormat(0, 160, 160, 32, SDL_PIXELFORMAT_RGBA8888); gpuTex = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_STREAMING, 160, 160); SDL_SetRenderTarget(renderer, gpuTex); SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255); SDL_RenderClear(renderer); SDL_SetRenderTarget(renderer, NULL); SDL_Rect viewport = {0, 0, SCREEN_WIDTH, SCREEN_HEIGHT}; SDL_RenderSetViewport(renderer, &viewport); SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_BLEND); biggerFont = prepText(renderer, 16, "PublicPixel.ttf", 255, 255, 255, 255); smallFont = prepText(renderer, 12, "PublicPixel.ttf", 255, 255, 255, 255); smallerFont = prepText(renderer, 8, "PublicPixel.ttf", 255, 255, 255, 255); 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); for (int i = 0; i < editorCount; i++) { generate_string_display(&codeEditor, renderer); } init_cpu(&cpu, renderer); compile(true); return 0; } int render() { SDL_SetRenderDrawColor(renderer, 32, 32, 32, 255); SDL_RenderClear(renderer); for (int i = 0; i < editorCount; i++) { editor_render(&editors[i], renderer, &cpu, i, activeEditorIndex == i, cursor); } SDL_Rect rect2; rect2.x = 9; rect2.y = 0; rect2.w = 0; rect2.h = 0; if (cpuStatsTexture) { SDL_QueryTexture(cpuStatsTexture, NULL, NULL, &rect2.w, &rect2.h); SDL_RenderCopy(renderer, cpuStatsTexture, NULL, &rect2); } rect2.x = 100; rect2.y = 46; rect2.w = 0; rect2.h = 0; if (cpuStateTexture) { SDL_QueryTexture(cpuStateTexture, NULL, NULL, &rect2.w, &rect2.h); SDL_RenderCopy(renderer, cpuStateTexture, NULL, &rect2); } rect2.x = 530; rect2.y = 50; rect2.w = 0; rect2.h = 0; if (gpuSurfDirty) { SDL_UpdateTexture(gpuTex, NULL, gpuSurf->pixels, gpuSurf->pitch); gpuSurfDirty = false; } if (gpuTex) { SDL_QueryTexture(gpuTex, NULL, NULL, &rect2.w, &rect2.h); SDL_RenderCopy(renderer, gpuTex, NULL, &rect2); } render_seven_segment(&displayA, renderer); render_seven_segment(&displayB, renderer); render_seven_segment(&displayC, renderer); render_seven_segment(&displayD, renderer); render_seven_segment(&displayE, renderer); render_seven_segment(&displayF, renderer); render_seven_segment(&displayG, renderer); render_seven_segment(&displayH, renderer); render_switches_segment(&switchesA, renderer); render_switches_segment(&switchesB, renderer); render_switches_segment(&switchesC, renderer); render_switches_segment(&switchesD, renderer); SDL_RenderPresent(renderer); frames++; if (!(frames % 60)) { cursor = !cursor; } return 0; } SDL_Keycode ConvertKPToNonKP(SDL_Keycode keycode) { switch (keycode) { case SDLK_KP_0: return SDLK_0; case SDLK_KP_1: return SDLK_1; case SDLK_KP_2: return SDLK_2; case SDLK_KP_3: return SDLK_3; case SDLK_KP_4: return SDLK_4; case SDLK_KP_5: return SDLK_5; case SDLK_KP_6: return SDLK_6; case SDLK_KP_7: return SDLK_7; case SDLK_KP_8: return SDLK_8; case SDLK_KP_9: return SDLK_9; case SDLK_KP_PERIOD: return SDLK_PERIOD; case SDLK_KP_COMMA: return SDLK_COMMA; case SDLK_KP_DIVIDE: return SDLK_SLASH; case SDLK_KP_MULTIPLY: return SDLK_ASTERISK; case SDLK_KP_MINUS: return SDLK_MINUS; case SDLK_KP_PLUS: return SDLK_PLUS; case SDLK_KP_ENTER: return SDLK_RETURN; case SDLK_KP_EQUALS: return SDLK_EQUALS; case SDLK_KP_LEFTPAREN: return SDLK_LEFTPAREN; case SDLK_KP_RIGHTPAREN: return SDLK_RIGHTPAREN; case SDLK_KP_LEFTBRACE: return SDLK_LEFTBRACKET; case SDLK_KP_RIGHTBRACE: return SDLK_RIGHTBRACKET; case SDLK_KP_TAB: return SDLK_TAB; case SDLK_KP_BACKSPACE: return SDLK_BACKSPACE; case SDLK_KP_A: return SDLK_a; case SDLK_KP_B: return SDLK_b; case SDLK_KP_C: return SDLK_c; case SDLK_KP_D: return SDLK_d; case SDLK_KP_E: return SDLK_e; case SDLK_KP_F: return SDLK_f; case SDLK_KP_XOR: return SDLK_CARET; case SDLK_KP_PERCENT: return SDLK_PERCENT; case SDLK_KP_LESS: return SDLK_LESS; case SDLK_KP_GREATER: return SDLK_GREATER; case SDLK_KP_AMPERSAND: case SDLK_KP_DBLAMPERSAND: return SDLK_AMPERSAND; // No direct match, best alternative case SDLK_KP_VERTICALBAR: case SDLK_KP_DBLVERTICALBAR: return SDLK_BACKSLASH; // No direct match case SDLK_KP_COLON: return SDLK_COLON; case SDLK_KP_HASH: return SDLK_HASH; case SDLK_KP_SPACE: return SDLK_SPACE; case SDLK_KP_AT: return SDLK_AT; case SDLK_KP_EXCLAM: return SDLK_EXCLAIM; case SDLK_KP_MEMADD: return SDLK_PLUS; case SDLK_KP_MEMSUBTRACT: return SDLK_MINUS; case SDLK_KP_MEMMULTIPLY: return SDLK_ASTERISK; case SDLK_KP_MEMDIVIDE: return SDLK_SLASH; case SDLK_KP_MEMSTORE: case SDLK_KP_MEMRECALL: case SDLK_KP_MEMCLEAR: case SDLK_KP_PLUSMINUS: case SDLK_KP_CLEAR: case SDLK_KP_CLEARENTRY: case SDLK_KP_BINARY: case SDLK_KP_OCTAL: case SDLK_KP_DECIMAL: case SDLK_KP_HEXADECIMAL: return SDLK_UNKNOWN; default: return keycode; // If it's not a KP key, return it unchanged } } uint8_t cpuSpeedTemp = 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; 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); } else if (e.type == SDL_KEYDOWN) { int keySym = ConvertKPToNonKP(e.key.keysym.sym); int keyMod = e.key.keysym.mod; cursor = true; bool moved = false; switch (keySym) { case SDLK_UP: move_cursor_relative(&activeEditor, -1, 0, false, renderer); moved = true; break; case SDLK_PAGEUP: move_cursor_relative(&activeEditor, -activeEditor.displayLineCount, -1, true, renderer); moved = true; break; case SDLK_DOWN: move_cursor_relative(&activeEditor, 1, 0, false, renderer); moved = true; break; case SDLK_PAGEDOWN: move_cursor_relative(&activeEditor, activeEditor.displayLineCount, 0, true, renderer); moved = true; break; case SDLK_LEFT: move_cursor_relative(&activeEditor, 0, -1, false, renderer); moved = true; break; case SDLK_HOME: if (keyMod & KMOD_CTRL) { move_cursor(&activeEditor, 0, 0, false, renderer); moved = true; break; } move_cursor(&activeEditor, activeEditor.cursor_line, 0, false, renderer); moved = true; break; case SDLK_RIGHT: move_cursor_relative(&activeEditor, 0, 1, false, renderer); moved = true; break; case SDLK_END: int lineLen = strlen(activeEditor.lines[activeEditor.cursor_line].text); if (keyMod & KMOD_CTRL) { move_cursor(&activeEditor, activeEditor.maxLines - 1, lineLen, false, renderer); moved = true; break; } move_cursor(&activeEditor, activeEditor.cursor_line, lineLen, false, renderer); moved = true; break; case SDLK_d: if (keyMod & KMOD_CTRL && !activeEditor.readOnly) { insert_line_rel(&activeEditor, renderer); memset(activeEditor.lines[activeEditor.cursor_line].text, 0, activeEditor.max_line_width); strcpy(activeEditor.lines[activeEditor.cursor_line].text, activeEditor.lines[activeEditor.cursor_line - 1].text); activeEditor.lines[activeEditor.cursor_line].active = 1; move_cursor_relative(&activeEditor, 0, activeEditor.max_line_width, false, renderer); generate_string_display(&activeEditor, renderer); } break; case SDLK_F9: cpu.mode ^= CPU_MODE_LOOP; updateState(false); return 1; case SDLK_F8: 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 cpu.mode |= CPU_MODE_STEP; // Restart cycle } else { cpu.mode |= CPU_MODE_STEP; // Restart cycle } updateState(false); return 1; case SDLK_F5: compile(!(keyMod & KMOD_CTRL)); case SDLK_F7: if (cpu.mode & (CPU_MODE_HALTED | CPU_MODE_ERROR) || (keyMod & KMOD_SHIFT)) { cpu.pc = 0; } cpu.mode &= ~(CPU_MODE_HALTED | CPU_MODE_PAUSED | CPU_MODE_ERROR); updateState(false); break; case SDLK_ESCAPE: cpu.mode |= CPU_MODE_PAUSED; if (keyMod & (KMOD_CTRL | KMOD_SHIFT)) { cpu.mode |= CPU_MODE_HALTED; cpu.pc = 0; init_cpu(&cpu, renderer); } updateState(false); break; case SDLK_s: if (keyMod & KMOD_CTRL) { FILE *fptr; char fname[20]; snprintf(fname, sizeof(fname), "riscb%lu.bsm", time(NULL)); 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; case SDLK_l: if (keyMod & KMOD_CTRL) { FILE *fptr; char fname[20]; sscanf(editors[0].lines[editors[0].cursor_line].text, "%s", fname); toLowerCase(fname); strcat(fname, ".bsm"); fptr = fopen(fname, "rb"); if (fptr) { char *prog = read_file_as_string(fname); toUpperCase(prog); fill_editor_from_string(&editors[0], prog, 0, true, renderer); free(prog); fclose(fptr); } return 1; } break; case SDLK_BACKSPACE: if (!activeEditor.readOnly) { remove_character(&activeEditor, false, renderer); moved = true; } break; case SDLK_DELETE: if (!activeEditor.readOnly) { remove_character(&activeEditor, true, renderer); moved = true; } break; case SDLK_RETURN: case SDLK_RETURN2: if (keyMod & KMOD_CTRL && activeEditorIndex == 0) { compile(!(keyMod & KMOD_SHIFT)); break; } if (!activeEditor.readOnly) { insert_line_rel(&activeEditor, renderer); moved = true; } break; case SDLK_TAB: activeEditorIndex++; if (activeEditorIndex >= editorCount) { activeEditorIndex = 0; } activeEditor = editors[activeEditorIndex]; break; default: break; } if (moved && &activeEditor == &memoryViewer) { updateState(false); } } else if (e.type == SDL_TEXTINPUT) { for (int i = 0; e.text.text[i] != '\0'; i++) { // Iterate over the input string char keySym = e.text.text[i]; if (!activeEditor.readOnly && activeEditor.cursor_pos <= activeEditor.max_line_width) { if (keySym >= 32 && keySym <= 126) { // Printable ASCII range if (keySym > 0x60 && keySym < 0x7b) { // Convert lowercase to uppercase keySym -= 0x20; } if (activeEditor.cursor_pos < activeEditor.max_line_width) { insert_character(&activeEditor, keySym, renderer); } } } } } else if (e.type == SDL_MOUSEBUTTONDOWN) { SDL_Rect mouse; mouse.w = 1; mouse.h = 1; SDL_GetMouseState(&mouse.x, &mouse.y); toggle_switch(mouse, &switchesA); toggle_switch(mouse, &switchesB); toggle_switch(mouse, &switchesC); toggle_switch(mouse, &switchesD); } 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; 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); } status = render(); if (status) { return status; } if (!(cpu.mode & (CPU_MODE_HALTED | CPU_MODE_PAUSED | CPU_MODE_ERROR))) { if (cpu.mode & CPU_MODE_SECOND) { if (!(frames % 60)) { step(&cpu); updateState(false); } } else { step(&cpu); updateState(false); } if (cpu.mode & CPU_MODE_STEP) { cpu.mode |= CPU_MODE_PAUSED; updateState(false); } } pthread_mutex_unlock(&cpu_mutex); end = SDL_GetTicks64(); const unsigned long timeNeeded = end - start; if (timeNeeded < delayNeeded) { SDL_Delay(delayNeeded - timeNeeded); } } for (uint8_t i = 0; i < editorCount; i++) { destroy_editor(&editors[i]); } for (uint8_t i = 0; i < fontCount; i++) { destroyFont(&fonts[i]); } puts(SDL_GetError()); if (cpuStatsTexture) SDL_DestroyTexture(cpuStatsTexture); if (cpuStateTexture) SDL_DestroyTexture(cpuStateTexture); destroy_switches_segment(&switchesA); destroy_switches_segment(&switchesB); destroy_switches_segment(&switchesC); destroy_switches_segment(&switchesD); destroy_seven_segment(&displayA); destroy_seven_segment(&displayB); destroy_seven_segment(&displayC); destroy_seven_segment(&displayD); destroy_seven_segment(&displayE); destroy_seven_segment(&displayF); destroy_seven_segment(&displayG); destroy_seven_segment(&displayH); pthread_join(cpu_thread, NULL); if (renderer) SDL_DestroyRenderer(renderer); if (window) SDL_DestroyWindow(window); SDL_Quit(); return 0; }