diff --git a/src/machine/rom.odin b/src/machine/rom.odin index 72ca4f3..d6c98a6 100644 --- a/src/machine/rom.odin +++ b/src/machine/rom.odin @@ -5,13 +5,13 @@ package machine import "core:log" import "core:os" -load_rom :: proc(s: ^System, file_path: string) -> os.Error { +load_rom :: proc(s: ^System, file_path: string) -> (int, os.Error) { log.info("Loading rom from file") data, read_err := os.read_entire_file(file_path, context.allocator) if read_err != os.ERROR_NONE { log.errorf("failed to read rom %v", read_err) - return read_err + return 0, read_err } defer delete(data) @@ -21,5 +21,5 @@ load_rom :: proc(s: ^System, file_path: string) -> os.Error { log.infof("First few bytes: %X %X %X %X", s.memory[0x200], s.memory[0x201], s.memory[0x202], s.memory[0x203]) - return nil + return len(data), nil } diff --git a/src/simulator/gui_bottom_tabs.odin b/src/simulator/gui_bottom_tabs.odin index 5a88b74..41ea657 100644 --- a/src/simulator/gui_bottom_tabs.odin +++ b/src/simulator/gui_bottom_tabs.odin @@ -18,16 +18,23 @@ gui_bottom_tabs :: proc(rect: rl.Rectangle, sim: ^Simulator) { // inside draw loop: rl.GuiTabBar(tab_bar_rect, &tabs[0], i32(len(tabs)), &sim.active_tab) - bounds := rl.Rectangle { + memory_view_bounds := rl.Rectangle { x = rect.x + PADDING_X, y = rect.y + PADDING_Y + tab_bar_rect.height, - width = rect.width - (PADDING_X * 2), + width = (rect.width - (PADDING_X * 2)) / 2, + height = rect.height - (PADDING_Y * 2) - tab_bar_rect.height, + } + disasm_view_bounds := rl.Rectangle { + x = rect.x + memory_view_bounds.width + PADDING_X, + y = rect.y + PADDING_Y + tab_bar_rect.height, + width = (rect.width - (PADDING_X * 2)) / 2, height = rect.height - (PADDING_Y * 2) - tab_bar_rect.height, } switch sim.active_tab { case 0: // draw registers panel - gui_tab_memory(bounds, sim) + gui_tab_memory(memory_view_bounds, sim) + gui_tab_disasm(disasm_view_bounds, sim) case 1: // draw memory panel case 2: // draw display panel } diff --git a/src/simulator/gui_file_loader.odin b/src/simulator/gui_file_loader.odin index 9a56f6f..75fe88d 100644 --- a/src/simulator/gui_file_loader.odin +++ b/src/simulator/gui_file_loader.odin @@ -1,11 +1,18 @@ package simulator +import "core:fmt" import "core:log" import rl "vendor:raylib" import emu "../machine" import tfd "../../external/tinyfiledialogs" +Instruction :: struct { + address: u16, + raw: u16, + str: string +} + gui_file_loader :: proc(rect: rl.Rectangle, sim: ^Simulator) { rl.DrawRectangleLinesEx(rect, 1, rl.GRAY) @@ -17,26 +24,11 @@ gui_file_loader :: proc(rect: rl.Rectangle, sim: ^Simulator) { } rl.GuiPanel(bounds, "Rom / File") - // drop-zone occupies the panel's content area, minus space for the button - drop_zone := rl.Rectangle { - bounds.x + PADDING_X, - bounds.y + PADDING_Y, - bounds.width - (PADDING_X * 2), - bounds.height - ((PADDING_Y * 2) + (BUTTON_HEIGHT + PADDING_Y)) - } - - // centered drop-zone text - text: cstring = "Drop a CHIP-8 ROM here" - text_width := rl.MeasureText(text, BIG_FONT_SIZE) - text_x := drop_zone.x + (drop_zone.width - f32(text_width)) / 2 - text_y := drop_zone.y + (drop_zone.height - f32(BIG_FONT_SIZE)) / 2 - rl.DrawTextEx(sim.font, text, {text_x, text_y}, BIG_FONT_SIZE, 1, rl.WHITE) - // open rom button below drop-zone btn_rect := rl.Rectangle { - drop_zone.x, - drop_zone.y + drop_zone.height + PADDING_Y, - drop_zone.width, + bounds.x, + bounds.y, + rect.width - (PADDING_X * 2), BUTTON_HEIGHT, } @@ -49,15 +41,32 @@ gui_file_loader :: proc(rect: rl.Rectangle, sim: ^Simulator) { emu.reset_machine(sim.machine) // load new rom - err := emu.load_rom(sim.machine, rom_path) + rom_size, err := emu.load_rom(sim.machine, rom_path) if err != nil { // @TODO: update status bar here panic("failed to load rom!") } sim.rom_loaded = true + sim.disasm = disassemble_rom(sim.machine.memory[:], 0x200, 0x200 + u16(rom_size)) } + // drop-zone occupies the panel's content area, minus space for the button + drop_zone := rl.Rectangle { + bounds.x + PADDING_X, + bounds.y + btn_rect.height + PADDING_Y * 2, + bounds.width - (PADDING_X * 2), + bounds.height - (btn_rect.height) - (PADDING_Y * 4) + } + rl.DrawRectangleLinesEx(drop_zone, 1, rl.LIGHTGRAY) + + // centered drop-zone text + text: cstring = "Drop a CHIP-8 ROM here" + text_width := rl.MeasureText(text, BIG_FONT_SIZE) + text_x := drop_zone.x + (drop_zone.width - f32(text_width)) / 2 + text_y := drop_zone.y + (drop_zone.height - f32(BIG_FONT_SIZE)) / 2 + rl.DrawTextEx(sim.font, text, {text_x, text_y}, BIG_FONT_SIZE, 1, rl.WHITE) + // Handle file drop if rl.IsFileDropped() { dropped_file := rl.LoadDroppedFiles() @@ -75,3 +84,111 @@ gui_file_loader :: proc(rect: rl.Rectangle, sim: ^Simulator) { rl.UnloadDroppedFiles(dropped_file) } } + +disassemble_rom :: proc(memory: []u8, start: u16, end_addr: u16) -> []Instruction { + entries := make([dynamic]Instruction) + + i := start + for i < end_addr { + if int(i) + 1 >= len(memory) do break + addr := i + high_byte := memory[i] + low_byte := memory[i + 1] + i += 2 + + // Get full opcode from the 2 bytes + opcode := (u16(high_byte) << 8) | u16(low_byte) + + // Pre-decode common components of opcodes + first_nibble := (opcode & 0xF000) >> 12 + vx := (opcode & 0x0F00) >> 8 // A 4-bit value, the lower 4 bits of the high byte of the instruction + vy := (opcode & 0x00F0) >> 4 // A 4-bit value, the upper 4 bits of the low byte of the instruction + last_nibble := (opcode & 0x000F) // A 4-bit value, the lowest 4 bits of the instruction + kk := u8(opcode & 0x00FF) // An 8-bit value, the lowest 8 bits of the instruction + nnn := (opcode & 0x0FFF) // A 12-bit value, the lowest 12 bits of the instruction + + str: string + switch first_nibble { + case 0x0: + switch opcode { + case 0x00E0: str = "CLS" + case 0x00EE: str = "RET" + case: str = fmt.tprintf("DATA 0x%04X", opcode) + } + case 0x1: str = fmt.tprintf("JP 0x%03X", nnn) + case 0x2: str = fmt.tprintf("CALL 0x%03X", nnn) + case 0x3: str = fmt.tprintf("SE V%X, 0x%02X", vx, kk) + case 0x4: str = fmt.tprintf("SNE V%X, 0x%02X", vx, kk) + case 0x5: str = fmt.tprintf("SE V%X, V%X", vx, vy) + case 0x6: str = fmt.tprintf("LD V%X, 0x%02X", vx, kk) + case 0x7: str = fmt.tprintf("ADD V%X, 0x%02X", vx, kk) + case 0x8: + switch last_nibble { + case 0x0: str = fmt.tprintf("LD V%X, V%X", vx, vy) + case 0x1: str = fmt.tprintf("OR V%X, V%X", vx, vy) + case 0x2: str = fmt.tprintf("AND V%X, V%X", vx, vy) + case 0x3: str = fmt.tprintf("XOR V%X, V%X", vx, vy) + case 0x4: str = fmt.tprintf("ADD V%X, V%X", vx, vy) + case 0x5: str = fmt.tprintf("SUB V%X, V%X", vx, vy) + case 0x6: str = fmt.tprintf("SHR V%X", vx) + case 0x7: str = fmt.tprintf("SUBN V%X, V%X", vx, vy) + case 0xE: str = fmt.tprintf("SHL V%X", vx) + case: str = fmt.tprintf("DATA 0x%04X", opcode) + } + case 0x9: str = fmt.tprintf("SNE V%X, V%X", vx, vy) + case 0xA: str = fmt.tprintf("LD I, 0x%03X", nnn) + case 0xB: str = fmt.tprintf("JP V0, 0x%03X", nnn) + case 0xC: str = fmt.tprintf("RND V%X, 0x%02X", vx, kk) + case 0xD: str = fmt.tprintf("DRW V%X, V%X, %X", vx, vy, last_nibble) + case 0xE: + switch kk { + case 0x9E: str = fmt.tprintf("SKP V%X", vx) + case 0xA1: str = fmt.tprintf("SKNP V%X", vx) + case: str = fmt.tprintf("DATA 0x%04X", opcode) + } + case 0xF: + switch kk { + case 0x07: str = fmt.tprintf("LD V%X, DT", vx) + case 0x0A: str = fmt.tprintf("LD V%X, K", vx) + case 0x15: str = fmt.tprintf("LD DT, V%X", vx) + case 0x18: str = fmt.tprintf("LD ST, V%X", vx) + case 0x1E: str = fmt.tprintf("ADD I, V%X", vx) + case 0x29: str = fmt.tprintf("LD F, V%X", vx) + case 0x33: str = fmt.tprintf("LD B, V%X", vx) + case 0x55: str = fmt.tprintf("LD [I], V%X", vx) + case 0x65: str = fmt.tprintf("LD V%X, [I]", vx) + case: str = fmt.tprintf("DATA 0x%04X", opcode) + } + case: str = fmt.tprintf("DATA 0x%04X", opcode) + } + + append(&entries, Instruction{address = addr, raw = opcode, str = str}) + } + + return entries[:] +} + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/simulator/gui_tab_disasm.odin b/src/simulator/gui_tab_disasm.odin new file mode 100644 index 0000000..0a36e24 --- /dev/null +++ b/src/simulator/gui_tab_disasm.odin @@ -0,0 +1,49 @@ +package simulator + +import "core:fmt" +import "core:strings" +import rl "vendor:raylib" + +gui_tab_disasm :: proc(rect: rl.Rectangle, sim: ^Simulator) { + rl.DrawRectangleRec(rect, rl.DARKGRAY) + rl.GuiPanel(rect, "Disassembly") + + LINE_HEIGHT :: 22 + + // Total height of all instructions + conten_height := f32(len(sim.disasm)) * LINE_HEIGHT + + content_rect := rl.Rectangle { + x = rect.x, + y = rect.y, + width = rect.width + PANEL_HEADER, + height = conten_height, + } + + view: rl.Rectangle + rl.GuiScrollPanel(rect, nil, content_rect, &sim.disasm_scroll, &view) + + rl.BeginScissorMode(i32(view.x), i32(view.y), i32(view.width), i32(view.height)) + defer rl.EndScissorMode() + + for entry, i in sim.disasm { + y_pos := rect.y + PANEL_HEADER + (f32(i) * LINE_HEIGHT) + sim.disasm_scroll.y + + txt := fmt.tprintf("%d : %s", i, entry.str) + bg_color := rl.DARKGRAY if entry.address != sim.machine.pc else rl.BLACK + txt_color := rl.WHITE + + rl.DrawRectangleV( + {rect.x + sim.disasm_scroll.x, y_pos}, + {rect.width, LINE_HEIGHT}, + bg_color, + ) + rl.DrawTextEx( + sim.font, + strings.clone_to_cstring(txt, context.temp_allocator), + {rect.x + PADDING_X + sim.disasm_scroll.x, y_pos}, + 18, 1, + txt_color, + ) + } +} diff --git a/src/simulator/simulator.odin b/src/simulator/simulator.odin index 6413512..86f6ee9 100644 --- a/src/simulator/simulator.odin +++ b/src/simulator/simulator.odin @@ -5,15 +5,17 @@ import rl "vendor:raylib" Simulator :: struct { // Emulator - machine: ^emu.System, - rom_loaded: bool, - running: bool, - paused: bool, + machine: ^emu.System, + rom_loaded: bool, + running: bool, + paused: bool, cycles_per_second: int, // GUI - font: rl.Font, - active_tab: i32, - mem_scroll : rl.Vector2, + font: rl.Font, + active_tab: i32, + mem_scroll: rl.Vector2, + disasm: []Instruction, + disasm_scroll: rl.Vector2, } // Requires an initilized emulatore System Struct