Compare commits
5 Commits
61471536ca
...
356ed2408a
| Author | SHA1 | Date | |
|---|---|---|---|
| 356ed2408a | |||
| 0b5006f985 | |||
| e9cf387640 | |||
| d314ef651e | |||
| 16b97b24b0 |
Binary file not shown.
+25
-23
@@ -3,26 +3,26 @@ package simulator
|
||||
import emu "../machine"
|
||||
import rl "vendor:raylib"
|
||||
|
||||
// @ Window
|
||||
// Window
|
||||
WINDOW_WIDTH :: 1920
|
||||
WINDOW_HEIGHT :: 1080
|
||||
|
||||
// @ Layout proportions
|
||||
// Layout proportions
|
||||
// Sidebar takes 20% of screen width on each side; display takes the rest.
|
||||
// The center column splits vertically: 40% display, 60% debug panel.
|
||||
SIDEBAR_PERCENT :: f32(0.20)
|
||||
DISPLAY_V_RATIO :: f32(0.40)
|
||||
// @ Fixed heights
|
||||
// Fixed heights
|
||||
CONTROL_BAR_H :: f32(50)
|
||||
STATUS_BAR_H :: f32(30)
|
||||
// @ Layout constants
|
||||
// Layout constants
|
||||
PADDING_X :: f32(10)
|
||||
PADDING_Y :: f32(8)
|
||||
PANEL_HEADER :: f32(24)
|
||||
// @ Buttons
|
||||
// Buttons
|
||||
BUTTON_HEIGHT :: 30
|
||||
BUTTON_WIDTH :: 120
|
||||
// @ Fonts
|
||||
// Fonts
|
||||
BIG_FONT_SIZE :: 20
|
||||
KEYPAD_FONT_SIZE :: 18
|
||||
|
||||
@@ -50,7 +50,7 @@ run_gui :: proc(sim: ^Simulator) {
|
||||
sim.font = font
|
||||
|
||||
rl.GuiLoadStyleDefault()
|
||||
rl.GuiLoadStyle("./assets/raygui_styles/style_dark.rgs")
|
||||
rl.GuiLoadStyle("./assets/raygui_styles/genesis.rgs")
|
||||
|
||||
rl.GuiSetFont(font)
|
||||
rl.GuiSetStyle(.DEFAULT, i32(rl.GuiDefaultProperty.TEXT_SIZE), 18)
|
||||
@@ -67,23 +67,19 @@ run_gui :: proc(sim: ^Simulator) {
|
||||
layout := calc_layout(screen_width, screen_height)
|
||||
|
||||
rl.BeginDrawing()
|
||||
rl.ClearBackground(rl.BLACK)
|
||||
rl.ClearBackground(rl.Color{0x18, 0x18, 0x18, 0xFF})
|
||||
|
||||
if (!sim.paused) {
|
||||
// Cycle the machine to update memory etc
|
||||
emu.run_machine(sim.machine, 12)
|
||||
tick_timers(sim, beep)
|
||||
}
|
||||
|
||||
// Handle delay timer
|
||||
if sim.machine.delay_timer > 0 do sim.machine.delay_timer -= 1
|
||||
|
||||
// Current sound file is around 1s so stop
|
||||
// immediately when timer is 0
|
||||
if sim.machine.sound_timer > 0 {
|
||||
sim.machine.sound_timer -= 1
|
||||
if !rl.IsSoundPlaying(beep) do rl.PlaySound(beep)
|
||||
} else {
|
||||
rl.StopSound(beep)
|
||||
}
|
||||
if(sim.paused && sim.step) {
|
||||
// Cycle the machine to update memory etc
|
||||
emu.run_machine(sim.machine, 1)
|
||||
tick_timers(sim, beep)
|
||||
sim.step = false
|
||||
}
|
||||
|
||||
// Top
|
||||
@@ -115,12 +111,18 @@ run_gui :: proc(sim: ^Simulator) {
|
||||
rl.UnloadSound(beep)
|
||||
rl.CloseAudioDevice()
|
||||
rl.CloseWindow()
|
||||
|
||||
// Free any allocated memory for the simulator
|
||||
delete(sim.disasm)
|
||||
}
|
||||
|
||||
// @TODO: If this grows lets move it into its own file
|
||||
tick_timers :: proc(sim: ^Simulator, beep: rl.Sound) {
|
||||
if sim.machine.delay_timer > 0 do sim.machine.delay_timer -= 1
|
||||
if sim.machine.sound_timer > 0 {
|
||||
sim.machine.sound_timer -= 1
|
||||
if !rl.IsSoundPlaying(beep) do rl.PlaySound(beep)
|
||||
} else {
|
||||
rl.StopSound(beep)
|
||||
}
|
||||
}
|
||||
|
||||
calc_layout :: proc(screen_width: f32, screen_height: f32) -> Layout {
|
||||
// Control bar is a fixed height frozen at top of gui, all items start below it.
|
||||
y_pos := CONTROL_BAR_H
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package simulator
|
||||
|
||||
import "core:log"
|
||||
|
||||
import emu "../machine"
|
||||
import rl "vendor:raylib"
|
||||
|
||||
gui_control_bar :: proc(rect: rl.Rectangle, sim: ^Simulator) {
|
||||
@@ -15,7 +17,6 @@ gui_control_bar :: proc(rect: rl.Rectangle, sim: ^Simulator) {
|
||||
if btn(&cursor, rect, BUTTON_HEIGHT, BUTTON_WIDTH, PADDING_X, "RUN") {
|
||||
if sim.rom_loaded {
|
||||
sim.paused = false
|
||||
sim.running = true
|
||||
} else {
|
||||
log.info("no rom selected, can't run")
|
||||
}
|
||||
@@ -23,12 +24,26 @@ gui_control_bar :: proc(rect: rl.Rectangle, sim: ^Simulator) {
|
||||
|
||||
if btn(&cursor, rect, BUTTON_HEIGHT, BUTTON_WIDTH, PADDING_X, "PAUSE") {
|
||||
sim.paused = true
|
||||
sim.running = false
|
||||
}
|
||||
|
||||
if btn(&cursor, rect, BUTTON_HEIGHT, BUTTON_WIDTH, PADDING_X, "STEP") {
|
||||
if !sim.step do sim.step = true
|
||||
}
|
||||
|
||||
if btn(&cursor, rect, BUTTON_HEIGHT, BUTTON_WIDTH, PADDING_X, "RESET") {
|
||||
|
||||
sim.paused = true
|
||||
sim.disasm_count = 0
|
||||
emu.reset_machine(sim.machine)
|
||||
}
|
||||
|
||||
// @TODO: Get this working
|
||||
slider_rect := rl.Rectangle{
|
||||
x = rect.width - 100 - 50,
|
||||
y = rect.y + 15,
|
||||
width = 100,
|
||||
height = 15
|
||||
}
|
||||
rl.GuiSlider(slider_rect, "Speed ", nil, &sim.speed_value, 0, 100)
|
||||
}
|
||||
|
||||
btn :: proc(cursor: ^f32, rect: rl.Rectangle, h, w, gap: f32, label: cstring) -> bool {
|
||||
|
||||
@@ -7,12 +7,6 @@ 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)
|
||||
|
||||
@@ -39,7 +33,7 @@ gui_file_loader :: proc(rect: rl.Rectangle, sim: ^Simulator) {
|
||||
rom_path := string(ret)
|
||||
if rom_path == "" do return
|
||||
|
||||
// reset machine state
|
||||
// Reset machine state
|
||||
emu.reset_machine(sim.machine)
|
||||
|
||||
// load new rom
|
||||
@@ -48,12 +42,8 @@ gui_file_loader :: proc(rect: rl.Rectangle, sim: ^Simulator) {
|
||||
// @TODO: update status bar here
|
||||
panic("failed to load rom!")
|
||||
}
|
||||
|
||||
sim.rom_loaded = true
|
||||
|
||||
// Free old instructions on sim
|
||||
delete(sim.disasm)
|
||||
sim.disasm = disassemble_rom(sim.machine.memory[:], 0x200, 0x200 + u16(rom_size))
|
||||
sim.disasm_count = disassemble_rom_n(sim.machine.memory[:], 0x200, 0x200 + u16(rom_size), sim.disasm[:])
|
||||
}
|
||||
|
||||
// drop-zone occupies the panel's content area, minus space for the button
|
||||
@@ -108,12 +98,14 @@ gui_file_loader :: proc(rect: rl.Rectangle, sim: ^Simulator) {
|
||||
rl.GuiButton(bottom_btn_rect, "Open ROM Folder")
|
||||
}
|
||||
|
||||
disassemble_rom :: proc(memory: []u8, start: u16, end_addr: u16) -> []Instruction {
|
||||
entries := make([dynamic]Instruction)
|
||||
disassemble_rom_n :: proc(memory: []u8, start_addr: u16, end_addr: u16, out: []Instruction) -> int {
|
||||
count := 0
|
||||
i := start_addr
|
||||
|
||||
i := start
|
||||
for i < end_addr {
|
||||
if int(i) + 1 >= len(memory) do break
|
||||
if count >= len(out) do break
|
||||
|
||||
addr := i
|
||||
high_byte := memory[i]
|
||||
low_byte := memory[i + 1]
|
||||
@@ -130,63 +122,68 @@ disassemble_rom :: proc(memory: []u8, start: u16, end_addr: u16) -> []Instructio
|
||||
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
|
||||
|
||||
instruction := &out[count]
|
||||
instruction.address = addr
|
||||
instruction.raw = opcode
|
||||
|
||||
switch first_nibble {
|
||||
case 0x0:
|
||||
switch opcode {
|
||||
case 0x00E0: str = "CLS"
|
||||
case 0x00EE: str = "RET"
|
||||
case: str = fmt.tprintf("DATA 0x%04X", opcode)
|
||||
//case 0x00E0: str = "CLS"
|
||||
case 0x00E0: fmt.bprintf(instruction.str[:], "CLS")
|
||||
case 0x00EE: fmt.bprintf(instruction.str[:], "RET")
|
||||
case: fmt.bprintf(instruction.str[:], "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 0x1: fmt.bprintf(instruction.str[:], "JP 0x%03X", nnn)
|
||||
case 0x2: fmt.bprintf(instruction.str[:], "CALL 0x%03X", nnn)
|
||||
case 0x3: fmt.bprintf(instruction.str[:], "SE V%X, 0x%02X", vx, kk)
|
||||
case 0x4: fmt.bprintf(instruction.str[:], "SNE V%X, 0x%02X", vx, kk)
|
||||
case 0x5: fmt.bprintf(instruction.str[:], "SE V%X, V%X", vx, vy)
|
||||
case 0x6: fmt.bprintf(instruction.str[:], "LD V%X, 0x%02X", vx, kk)
|
||||
case 0x7: fmt.bprintf(instruction.str[:], "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 0x0: fmt.bprintf(instruction.str[:], "LD V%X, V%X", vx, vy)
|
||||
case 0x1: fmt.bprintf(instruction.str[:], "OR V%X, V%X", vx, vy)
|
||||
case 0x2: fmt.bprintf(instruction.str[:], "AND V%X, V%X", vx, vy)
|
||||
case 0x3: fmt.bprintf(instruction.str[:], "XOR V%X, V%X", vx, vy)
|
||||
case 0x4: fmt.bprintf(instruction.str[:], "ADD V%X, V%X", vx, vy)
|
||||
case 0x5: fmt.bprintf(instruction.str[:], "SUB V%X, V%X", vx, vy)
|
||||
case 0x6: fmt.bprintf(instruction.str[:], "SHR V%X", vx)
|
||||
case 0x7: fmt.bprintf(instruction.str[:], "SUBN V%X, V%X", vx, vy)
|
||||
case 0xE: fmt.bprintf(instruction.str[:], "SHL V%X", vx)
|
||||
case: fmt.bprintf(instruction.str[:], "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 0x9: fmt.bprintf(instruction.str[:], "SNE V%X, V%X", vx, vy)
|
||||
case 0xA: fmt.bprintf(instruction.str[:], "LD I, 0x%03X", nnn)
|
||||
case 0xB: fmt.bprintf(instruction.str[:], "JP V0, 0x%03X", nnn)
|
||||
case 0xC: fmt.bprintf(instruction.str[:], "RND V%X, 0x%02X", vx, kk)
|
||||
case 0xD: fmt.bprintf(instruction.str[:], "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 0x9E: fmt.bprintf(instruction.str[:], "SKP V%X", vx)
|
||||
case 0xA1: fmt.bprintf(instruction.str[:], "SKNP V%X", vx)
|
||||
case: fmt.bprintf(instruction.str[:], "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 0x07: fmt.bprintf(instruction.str[:], "LD V%X, DT", vx)
|
||||
case 0x0A: fmt.bprintf(instruction.str[:], "LD V%X, K", vx)
|
||||
case 0x15: fmt.bprintf(instruction.str[:], "LD DT, V%X", vx)
|
||||
case 0x18: fmt.bprintf(instruction.str[:], "LD ST, V%X", vx)
|
||||
case 0x1E: fmt.bprintf(instruction.str[:], "ADD I, V%X", vx)
|
||||
case 0x29: fmt.bprintf(instruction.str[:], "LD F, V%X", vx)
|
||||
case 0x33: fmt.bprintf(instruction.str[:], "LD B, V%X", vx)
|
||||
case 0x55: fmt.bprintf(instruction.str[:], "LD [I], V%X", vx)
|
||||
case 0x65: fmt.bprintf(instruction.str[:], "LD V%X, [I]", vx)
|
||||
case: fmt.bprintf(instruction.str[:], "DATA 0x%04X", opcode)
|
||||
}
|
||||
case: str = fmt.tprintf("DATA 0x%04X", opcode)
|
||||
case: fmt.bprintf(instruction.str[:], "DATA 0x%04X", opcode)
|
||||
}
|
||||
|
||||
append(&entries, Instruction{address = addr, raw = opcode, str = str})
|
||||
count += 1
|
||||
}
|
||||
|
||||
return entries[:]
|
||||
return count
|
||||
}
|
||||
|
||||
@@ -5,7 +5,6 @@ import rl "vendor:raylib"
|
||||
|
||||
StatusIconShape :: enum { CIRCLE, SQUARE }
|
||||
|
||||
// @TODO: render status bar text
|
||||
gui_status_bar :: proc(rect: rl.Rectangle, sim: ^Simulator) {
|
||||
// Left to right text draws
|
||||
rl.DrawRectangleLinesEx(rect, 1, rl.DARKGRAY)
|
||||
@@ -13,7 +12,7 @@ gui_status_bar :: proc(rect: rl.Rectangle, sim: ^Simulator) {
|
||||
cursor: f32 = rect.x + PADDING_X
|
||||
cy := rect.y + rect.height * 0.5
|
||||
|
||||
if sim.running && !sim.paused {
|
||||
if !sim.paused {
|
||||
status_icon(&cursor, cy, rl.GREEN, .CIRCLE, "Running", sim.font)
|
||||
} else {
|
||||
status_icon(&cursor, cy, rl.RED, .SQUARE, "Paused", sim.font)
|
||||
|
||||
@@ -12,6 +12,11 @@ gui_tab_disasm :: proc(rect: rl.Rectangle, sim: ^Simulator) {
|
||||
// Header: Address label
|
||||
rl.DrawTextEx(sim.font, "Disassembled Instructions", {rect.x + 4, rect.y + 4}, 18, 1, rl.WHITE)
|
||||
|
||||
follow_label : cstring = "Follow: ON" if sim.disasm_follow else "Follow: OFF"
|
||||
follow_rect := rl.Rectangle{rect.x + rect.width - 110, rect.y + 4, 100, 20}
|
||||
if rl.GuiButton(follow_rect, follow_label) {
|
||||
sim.disasm_follow = !sim.disasm_follow
|
||||
}
|
||||
|
||||
// Scroll panel area (below header)
|
||||
panel_rect := rl.Rectangle{
|
||||
@@ -38,7 +43,7 @@ gui_tab_disasm :: proc(rect: rl.Rectangle, sim: ^Simulator) {
|
||||
rl.BeginScissorMode(i32(view.x), i32(view.y), i32(view.width), i32(view.height))
|
||||
defer rl.EndScissorMode()
|
||||
|
||||
for entry, i in sim.disasm {
|
||||
for entry, i in sim.disasm[:sim.disasm_count] {
|
||||
y_pos := panel_rect.y + (f32(i) * LINE_HEIGHT) + sim.disasm_scroll.y
|
||||
|
||||
txt := fmt.tprintf("%d : %s", i, entry.str)
|
||||
@@ -58,4 +63,21 @@ gui_tab_disasm :: proc(rect: rl.Rectangle, sim: ^Simulator) {
|
||||
txt_color,
|
||||
)
|
||||
}
|
||||
|
||||
if sim.disasm_follow {
|
||||
for entry, i in sim.disasm[:sim.disasm_count] {
|
||||
if entry.address == sim.machine.pc {
|
||||
target_y := f32(i) * LINE_HEIGHT
|
||||
visible_height := panel_rect.height
|
||||
|
||||
// Center the active instruction in the panel
|
||||
sim.disasm_scroll.y = -(target_y - visible_height / 2)
|
||||
|
||||
// Clamp so we don't scroll past the content
|
||||
max_scroll := -(f32(sim.disasm_count) * LINE_HEIGHT - visible_height)
|
||||
sim.disasm_scroll.y = clamp(sim.disasm_scroll.y, max_scroll, 0)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,18 +3,32 @@ package simulator
|
||||
import emu "../machine"
|
||||
import rl "vendor:raylib"
|
||||
|
||||
// CHIP-8 ROM can be at most 3584 bytes (4096 - 0x200 reserved)
|
||||
// Each instruction is 2 bytes, 3584 / 2 = 1792 instructions.
|
||||
MAX_INSTRUCTIONS :: 1792
|
||||
|
||||
Instruction :: struct {
|
||||
address: u16,
|
||||
raw: u16,
|
||||
str: [32]u8
|
||||
}
|
||||
|
||||
Simulator :: struct {
|
||||
// Emulator
|
||||
machine: ^emu.System,
|
||||
rom_loaded: bool,
|
||||
running: bool,
|
||||
paused: bool,
|
||||
step: bool,
|
||||
cycles_per_second: int,
|
||||
speed_value: f32,
|
||||
// GUI
|
||||
font: rl.Font,
|
||||
active_tab: i32,
|
||||
mem_scroll: rl.Vector2,
|
||||
disasm: []Instruction,
|
||||
// disassembly props
|
||||
disasm: [MAX_INSTRUCTIONS]Instruction,
|
||||
disasm_follow: bool,
|
||||
disasm_count: int,
|
||||
disasm_scroll: rl.Vector2,
|
||||
}
|
||||
|
||||
@@ -23,9 +37,10 @@ run_simulator :: proc(s: ^emu.System) {
|
||||
sim := Simulator {
|
||||
machine = s,
|
||||
rom_loaded = false,
|
||||
running = false,
|
||||
paused = true,
|
||||
cycles_per_second = 12
|
||||
step = false,
|
||||
cycles_per_second = 12,
|
||||
disasm_follow = true
|
||||
}
|
||||
|
||||
run_gui(&sim)
|
||||
|
||||
Reference in New Issue
Block a user