From f5b5f7697948e5c2f95c07d370e0709253f479d0 Mon Sep 17 00:00:00 2001 From: JJ Bliss Date: Tue, 5 May 2026 10:27:47 -0400 Subject: [PATCH] SDL Errors don't always crash the emulator. Fixed JMP relative address handling. --- src/main.ha | 12 ++- uxn/uxn.ha | 263 ++++++++++++++++++++-------------------------------- 2 files changed, 109 insertions(+), 166 deletions(-) diff --git a/src/main.ha b/src/main.ha index cf97281..f5b6171 100644 --- a/src/main.ha +++ b/src/main.ha @@ -285,6 +285,11 @@ export fn main() void = { const mods = ev.key.mod; const sym = sdl3::GetKeyFromScancode(scancode,mods,false); uxn::set_key_down(sym: u8,state); + // Handle Debug + if(key == sdl3::K_F2){ + fmt::println("Debug Print:")!; + uxn::print_stack_debug(state); + }; //Handle keyboard controller keys // Ctrl -> A 0x01 if(key == sdl3::K_LCTRL || key == sdl3::K_RCTRL){ @@ -407,6 +412,11 @@ export fn main() void = { // sdl3::Delay(1000 / 60); - sdl3::get_error()!; + match(sdl3::get_error()){ + case void => + yield; + case let err: sdl3::error => + fmt::printfln("SDL Error: {}", sdl3::strerror(err))!; + }; }; }; diff --git a/uxn/uxn.ha b/uxn/uxn.ha index 7d661ab..f1bd2f7 100644 --- a/uxn/uxn.ha +++ b/uxn/uxn.ha @@ -421,6 +421,27 @@ fn get_instruction(byte: u8) (instruction | error) = { }; }; +export fn print_stack_debug(state: *uxn) void = { + fmt::println("Working Stack:")!; + print_stack(false,state); + + fmt::println("Return Stack:")!; + print_stack(true,state); +}; + +fn print_stack(ret: bool, state: *uxn) void = { + const p = switch(ret){ + case true => + yield 1; + case false => + yield 0; + }; + for(let i = state.ptr[p] - 8; i != state.ptr[p]; i+=1){ + fmt::printf("{:x} ",state.stk[p][i])!; + }; + fmt::printf("\n")!; +}; + fn short_from_bytes(high: u8, low: u8) u16 = { return (high: u16) << 8 | (low: u16); }; @@ -688,24 +709,24 @@ export fn uxn_step(state: *uxn) (done | error) = { let bot = pop_or_get(equ_inst,1,state); //TODO, I think this works, but it feels unclean with the casting - // let val: u8 = if(top: u16 == bot: u16){ - // yield 1; - // } else { - // yield 0; - // }; - let val: u8 = if(!equ_inst.short){ - yield if(bot: u8 == top: u8){ - yield 1; - } else { - yield 0; - }; + let val: u8 = if(top: u16 == bot: u16){ + yield 1; } else { - yield if(bot: u16 == top: u16){ - yield 1; - } else { - yield 0; - }; + yield 0; }; + // let val: u8 = if(!equ_inst.short){ + // yield if(bot: u8 == top: u8){ + // yield 1; + // } else { + // yield 0; + // }; + // } else { + // yield if(bot: u16 == top: u16){ + // yield 1; + // } else { + // yield 0; + // }; + // }; push_to_stack(equ_inst.ret, val, state); // /* NEQ */ OPC(0x09,POx(a,d) POx(b,d),PUx(b != a,0,r)) case let neq_inst: NEQ => @@ -713,24 +734,24 @@ export fn uxn_step(state: *uxn) (done | error) = { let bot = pop_or_get(neq_inst,1,state); //TODO, I think this works, but it feels unclean with the casting - // let val: u8 = if(top: u16 != bot: u16){ - // yield 1; - // } else { - // yield 0; - // }; - let val: u8 = if(!neq_inst.short){ - yield if(bot: u8 != top: u8){ - yield 1; - } else { - yield 0; - }; + let val: u8 = if(top: u16 != bot: u16){ + yield 1; } else { - yield if(bot: u16 != top: u16){ - yield 1; - } else { - yield 0; - }; + yield 0; }; + // let val: u8 = if(!neq_inst.short){ + // yield if(bot: u8 != top: u8){ + // yield 1; + // } else { + // yield 0; + // }; + // } else { + // yield if(bot: u16 != top: u16){ + // yield 1; + // } else { + // yield 0; + // }; + // }; push_to_stack(neq_inst.ret, val, state); // /* GTH */ OPC(0x0a,POx(a,d) POx(b,d),PUx(b > a,0,r)) case let gth_inst: GTH => @@ -738,24 +759,24 @@ export fn uxn_step(state: *uxn) (done | error) = { let bot = pop_or_get(gth_inst,1,state); //TODO, I think this works, but it feels unclean with the casting - // let val: u8 = if(bot: u16 > top: u16){ - // yield 1; - // } else { - // yield 0; - // }; - let val: u8 = if(!gth_inst.short){ - yield if(bot: u8 > top: u8){ - yield 1; - } else { - yield 0; - }; + let val: u8 = if(bot: u16 > top: u16){ + yield 1; } else { - yield if(bot: u16 > top: u16){ - yield 1; - } else { - yield 0; - }; + yield 0; }; + // let val: u8 = if(!gth_inst.short){ + // yield if(bot: u8 > top: u8){ + // yield 1; + // } else { + // yield 0; + // }; + // } else { + // yield if(bot: u16 > top: u16){ + // yield 1; + // } else { + // yield 0; + // }; + // }; push_to_stack(gth_inst.ret, val, state); // /* LTH */ OPC(0x0b,POx(a,d) POx(b,d),PUx(b < a,0,r)) case let lth_inst: LTH => @@ -763,31 +784,33 @@ export fn uxn_step(state: *uxn) (done | error) = { let bot = pop_or_get(lth_inst,1,state); //TODO, I think this works, but it feels unclean with the casting - // let val: u8 = if(bot: u16 < top: u16){ - // yield 1; - // } else { - // yield 0; - // }; - let val: u8 = if(!lth_inst.short){ - yield if(bot: u8 < top: u8){ - yield 1; - } else { - yield 0; - }; + let val: u8 = if(bot: u16 < top: u16){ + yield 1; } else { - yield if(bot: u16 < top: u16){ - yield 1; - } else { - yield 0; - }; + yield 0; }; + // let val: u8 = if(!lth_inst.short){ + // yield if(bot: u8 < top: u8){ + // yield 1; + // } else { + // yield 0; + // }; + // } else { + // yield if(bot: u16 < top: u16){ + // yield 1; + // } else { + // yield 0; + // }; + // }; push_to_stack(lth_inst.ret, val, state); // /* JMP */ OPC(0x0c,POx(a,d),MOV) case let jmp_inst: JMP => let addr = pop_or_get(jmp_inst,0,state); match(addr) { case let b: u8 => - state.pc = state.pc + b: u16; //TODO verify if pc changes are right + let off = b: i8: i32; + state.pc = (state.pc: i32 + off): u16; //TODO verify if pc changes are right + case let s: u16 => state.pc = s; }; @@ -868,15 +891,16 @@ export fn uxn_step(state: *uxn) (done | error) = { push_to_stack(dei_inst.ret,val,state); // /* DEO */ OPC(0x17,POx(a,0) GOT(y),DEO(a, y)) case let deo_inst: DEO => - let port: u8 = pop_or_get(normal_op{short = false, keep = deo_inst.keep, ret = deo_inst.ret}, 0, state): u8; - if(deo_inst.short){ - let low = pop_or_get(normal_op{short = false, keep = deo_inst.keep, ret = deo_inst.ret}, 1, state): u8; - let high = pop_or_get(normal_op{short = false, keep = deo_inst.keep, ret = deo_inst.ret}, 2, state): u8; - emu_deo(port,high,state); - emu_deo(port+1,low,state); - }else{ - let val = pop_or_get(deo_inst, 1, state): u8; - emu_deo(port,val,state); + const port: u8 = pop_or_get(normal_op{short = false, keep = deo_inst.keep, ret = deo_inst.ret}, 0, state): u8; + const val = pop_or_peek(deo_inst,1,state); + match(val){ + case let s: u16 => + const high: u8 = (s >> 8): u8; + const low: u8 = s: u8; + emu_deo(port,high,state); + emu_deo(port+1,low,state); + case let b: u8 => + emu_deo(port,b,state); }; // /* ADD */ OPC(0x18,POx(a,d) POx(b,d),PUx(b + a, d,r)) case let add_inst: ADD => @@ -1026,94 +1050,3 @@ export fn uxn_reset(state: *uxn) void = { state.running = true; uxn_eval(reset_vector,state)!; }; -// export fn uxnrun() void = { -// if(len(os::args) < 2){ -// fmt::printf("usage: %s file.rom [args..]\n")!; -// return; -// }; -// let path = os::args[1]; -// //Open rom file -// let romfile = match (os::open(path,fs::flag::RDONLY)) { -// case let file: io::file => -// yield file; -// case let err: fs::error => -// fmt::fatalf("Error opening {}: {}", path, fs::strerror(err)); -// }; -// let state: *uxn = initialize_uxn_mem(); -// //Copy rom into ram -// let bytesread = match (io::read(romfile,state.ram[0x100..0xff00])){ -// case let bytes: size => -// yield bytes; -// case let eof: io::EOF => -// fmt::fatalf("Empty Rom"); -// case let err: io::error => -// fmt::fatalf("Error reading: {}", io::strerror(err)); -// }; -// io::close(romfile)!; //TODO, isn't there some kind of oversized roms? -// // fread(&ram[0x100], 0xff00, 1, f), fclose(f); -// let hasargs = len(os::args) > 2; -// if(hasargs){ -// state.dev[0x17] = 1; -// }; -// // dev[0x17] = argc > 2; - -// let eval = match (uxn_eval(0x100, state)) { -// case done => -// yield done; -// case let val: u8 => -// fmt::fatalf("Unhandled Opcode: {:x}", val); -// }; -// //TODO see if the above needs to not run if console vector not set -// if(state.console_vector != 0){ -// let args = os::args[2..]; -// let i: u8 = 0; -// let argcount = len(args): u8; -// for (let arg .. args){ -// // fmt::println("Console input args")!; -// for(let char: u8 .. strings::toutf8(arg)){ -// // fmt::printfln("Console input arg char: {:x}", char)!; -// match (console_input(char,2,state)) { -// case done => -// yield done; -// case let val: u8 => -// fmt::fatalf("Unhandled Opcode: {:x}", val); -// }; -// }; -// let ctype: u8 = if(i == (argcount - 1)){ -// yield 4; -// } else { -// yield 3; -// }; -// match (console_input('\n',ctype,state)) { -// case done => -// yield done; -// case let val: u8 => -// fmt::fatalf("Unhandled Opcode: {:x}", val); -// }; -// i+=1; //TODO using i here seems inelegant -// }; -// for( state.dev[0x0f] == 0; i+=1 ){ -// let buf: [1]u8 = [0]; -// let count = io::read(os::stdin, buf)!; -// // fmt::println("Read input")!; -// if(count != 0) { -// match (console_input(buf[0],1,state)) { -// case done => -// yield done; -// case let val: u8 => -// fmt::fatalf("Unhandled Opcode: {:x}", val); -// }; -// // fmt::println("Retuned from console_input")!; -// }; -// }; -// // match (console_input('\n',4,state)) { //TODO should this run? -// // case done => -// // yield done; -// // case let val: u8 => -// // fmt::fatalf("Unhandled Opcode: {:x}", val); -// // }; -// }; -// fmt::println("Done with uxn")!; - -// return; -// };