use fmt; use os; use fs; use io; use strings; // let console_vector: u16 = 0; // let ram: [0x10000] u8 = [0...]; // let dev: [0x100] u8 = [0...]; // let ptr: [2] u8 = [0...]; // let stk: [2][0x100] u8 = [[0...],[0...]]; export type uxn = struct { pc: u16, console_vector: u16, screen_vector: u16, mouse_vector: u16, ram: [0x10000] u8, dev: [0x100] u8, ptr: [2] u8, stk: [2][0x100] u8, running: bool, screen_size_changed: bool, screen_update: bool, screen: uxn_screen, palette: [4] u16, }; export def MAXWIDTH = 4096; export def MAXHEIGHT = 2160; export def SCREENRATE = 60; //Screen consists of two layers of 2-bit pixels export type uxn_screen = ([MAXWIDTH][MAXHEIGHT]u8, [MAXWIDTH][MAXHEIGHT]u8); const reset_vector: u16 = 0x0100; const colormodes: [4][16] u8 = [ [0, 0, 0, 0, 1, 0, 1, 1, 2, 2, 0, 2, 3, 3, 3, 0], [0, 1, 2, 3, 0, 1, 2, 3, 0, 1, 2, 3, 0, 1, 2, 3], [1, 2, 3, 1, 1, 2, 3, 1, 1, 2, 3, 1, 1, 2, 3, 1], [2, 3, 1, 2, 2, 3, 1, 2, 2, 3, 1, 2, 2, 3, 1, 2]]; fn initialize_uxn_mem() *uxn = { return alloc(uxn{ pc = reset_vector, console_vector = 0, screen_vector = 0, mouse_vector = 0, ram = [0...], dev = [0...], ptr = [0...], stk = [[0...],[0...]], running = false, screen_size_changed = false, screen_update = true, screen = ([[0...]...],[[0...]...]), palette = [0...], })!; }; export fn get_window_size(state: *uxn) struct{width: u16, height: u16} = { let width_high = state.dev[0x22]; let width_low = state.dev[0x23]; let height_high = state.dev[0x24]; let height_low = state.dev[0x25]; let result = struct {width: u16 = short_from_bytes(width_high,width_low), height: u16 = short_from_bytes(height_high,height_low) }; return result; }; export fn set_window_size(w: u16, h: u16, state: *uxn) void = { state.dev[0x22] = (w >> 8): u8; state.dev[0x23] = (w): u8; state.dev[0x24] = (h >> 8): u8; state.dev[0x25] = (h): u8; }; fn emu_dei(port: u8, state: *uxn) u8 = { switch(port) { case 0x04 => return state.ptr[0]; case 0x05 => return state.ptr[1]; case => return state.dev[port]; // fmt::printfln("Read {:x} from port {:x}", val, port)!; }; }; fn emu_deo(port: u8, value: u8, state: *uxn) void = { // fmt::printfln("Writing {:x} to port {:x}", value, port)!; state.dev[port] = value; switch(port) { case 0x04 => state.ptr[0] = value; case 0x05 => state.ptr[1] = value; case 0x11 => let high = state.dev[0x10]; let low = value; state.console_vector = short_from_bytes(high,low); // fmt::printfln("Setting console_vector to: {:x}", console_vector)!; case 0x18 => fmt::print(value: rune)!; case 0x19 => fmt::error(value)!; case 0x21 => let high = state.dev[0x20]; let low = value; state.screen_vector = short_from_bytes(high,low); case 0x2e => draw_pixel(value,state); case 0x2f => draw_sprite(value,state); case 0x91 => let high = state.dev[0x90]; let low = value; state.mouse_vector = short_from_bytes(high,low); case => if( port == 0x22 || port == 0x23 || port == 0x24 || port == 0x25 ) { state.screen_size_changed = true; }else if( port == 0x09 || //TODO maybe also trigger on high bytes port == 0x0b || port == 0x0d ){ regenerate_palettes(state); }; }; }; export fn set_mouse_motion(x: u16, y: u16, state: *uxn) void = { state.dev[0x92] = (x >> 8): u8; state.dev[0x93] = (x): u8; state.dev[0x94] = (y >> 8): u8; state.dev[0x95] = (y): u8; }; export fn set_mouse_down(button: u8, state: *uxn) void = { const cur = state.dev[0x96]; const new = cur | button; state.dev[0x96] = new; }; export fn set_mouse_up(button: u8, state: *uxn) void = { const cur = state.dev[0x96]; const invbutton = 0xff ^ button; const new = cur & invbutton; state.dev[0x96] = new; }; fn regenerate_palettes(state: *uxn) void = { const r = short_from_bytes(state.dev[0x08],state.dev[0x09]); const g = short_from_bytes(state.dev[0x0a],state.dev[0x0b]); const b = short_from_bytes(state.dev[0x0c],state.dev[0x0d]); state.palette[0] = ((r & 0xf000) >> 4) | ((g & 0xf000) >> 8) | ((b & 0xf000) >> 12); state.palette[1] = ((r & 0x0f00) >> 0) | ((g & 0x0f00) >> 4) | ((b & 0x0f00) >> 8); state.palette[2] = ((r & 0x00f0) << 4) | ((g & 0x00f0) >> 0) | ((b & 0x00f0) >> 4); state.palette[3] = ((r & 0x000f) << 8) | ((g & 0x000f) << 4) | ((b & 0x000f) >> 0); // fmt::printfln("Palette Color0: {:x} Color1: {:x} Color2: {:x} Color3: {:x}", state.palette[0], state.palette[1],state.palette[2],state.palette[3])!; }; fn draw_pixel(value: u8, state: *uxn) void = { //TODO handle fill const color = value & 0b00000011; const x = short_from_bytes(state.dev[0x28],state.dev[0x29]); const y = short_from_bytes(state.dev[0x2a],state.dev[0x2b]); const fill: bool = (value & 0b10000000) != 0; const layer1: bool = (value & 0b01000000) != 0; const flipy: bool = (value & 0b00100000) != 0; const flipx: bool = (value & 0b00010000) != 0; if(!fill){ // fmt::printfln("Pixel at x: {:x} y: {:x} color: {:x}", x, y, color)!; const auto = state.dev[0x26]; let count = ((auto & 0xf0) >> 4): i8; const auto_x = (auto & 0b00000001) != 0; const auto_y = (auto & 0b00000010) != 0; const auto_a = (auto & 0b00000100) != 0; if(!layer1){ state.screen.0[x][y] = color; } else { state.screen.1[x][y] = color; }; if(auto_x) { const new_x = x + 1; state.dev[0x28] = (new_x >> 8): u8; state.dev[0x29] = (new_x): u8; }; if(auto_y) { const new_y = y + 1; state.dev[0x2a] = (new_y >> 8): u8; state.dev[0x2b] = (new_y): u8; }; }else{ //TODO see if auto still happens with fill // fmt::println("Doing Fill")!; const dims = get_window_size(state); const startx: u16 = if(flipx) {yield 0; }else{ yield x; }; const starty: u16 = if(flipy) {yield 0; }else{ yield y; }; const endx: u16 = if(flipx) {yield x; }else{ yield dims.width; }; const endy: u16 = if(flipy) {yield y; }else{ yield dims.height; }; // fmt::printfln("Fill startx: {:x} endx: {:x} starty: {:x} endy: {:x}", startx, endx, starty, endy)!; for(let xl = startx; xl < endx; xl+=1){ for(let yl = starty; yl < endy; yl+=1){ // fmt::printfln("Filling at x: {:x} y: {:x} color: {:x}", xl, yl, color)!; if(!layer1){ state.screen.0[xl][yl] = color; } else { state.screen.1[xl][yl] = color; }; }; }; }; state.screen_update = true; }; fn draw_sprite(value: u8, state: *uxn) void = { //TODO handle fill const colors = value & 0b00001111; const auto = state.dev[0x26]; let count = ((auto & 0xf0) >> 4): u8; const auto_x = (auto & 0b00000001) != 0; const auto_y = (auto & 0b00000010) != 0; const auto_a = (auto & 0b00000100) != 0; const bpp2: bool = (value & 0b10000000) != 0; const layer1: bool = (value & 0b01000000) != 0; const flipy: bool = (value & 0b00100000) != 0; const flipx: bool = (value & 0b00010000) != 0; const x = short_from_bytes(state.dev[0x28],state.dev[0x29]); const y = short_from_bytes(state.dev[0x2a],state.dev[0x2b]); if(count == 0){ const addr = short_from_bytes(state.dev[0x2c],state.dev[0x2d]); // const x = if(flipx) { yield x - i * 8; } else { yield x + i * 8; }; copy_sprite_to_screen(bpp2,colors,x,y,addr,flipx,flipy,layer1,state); if(auto_a){ const new_addr = if(bpp2) { yield addr + 16; } else { yield addr + 8; }; state.dev[0x2c] = (new_addr >> 8): u8; state.dev[0x2d] = (new_addr): u8; }; if(auto_x) { const new_x = if(flipx) { yield x - 8; } else { yield x + 8; }; state.dev[0x28] = (new_x >> 8): u8; state.dev[0x29] = (new_x): u8; }; if(auto_y) { const new_y = if(flipy) { yield y - 8; } else { yield y + 8; }; state.dev[0x2a] = (new_y >> 8): u8; state.dev[0x2b] = (new_y): u8; }; }else { // fmt::printfln("Multi-Sprite! Auto-x: {} Auto-y: {} Flip-x: {} Flip-y: {}",auto_x,auto_y,flipx,flipy)!; for(let i: u16 = 0; i <= count; i+=1){ const addr = short_from_bytes(state.dev[0x2c],state.dev[0x2d]); let x = x; let y = y; if(auto_y) { x = if(flipx) { yield x - i * 8; } else { yield x + i * 8; }; }else if(auto_x) { y = if(flipy) { yield y - i * 8; } else { yield y + i * 8; }; }; copy_sprite_to_screen(bpp2,colors,x,y,addr,flipx,flipy,layer1,state); if(auto_a){ const new_addr = if(bpp2) { yield addr + 16; } else { yield addr + 8; }; state.dev[0x2c] = (new_addr >> 8): u8; state.dev[0x2d] = (new_addr): u8; }; }; if(auto_y) { const new_y = if(flipy){ yield y - 8; } else { yield y + 8; }; state.dev[0x2a] = (new_y >> 8): u8; state.dev[0x2b] = (new_y): u8; }; if(auto_x) { const new_x = x + 8; state.dev[0x28] = (new_x >> 8): u8; state.dev[0x29] = (new_x): u8; }; }; state.screen_update = true; }; fn copy_sprite_to_screen(bpp2: bool, colors: u8, x: u16, y: u16, addr: u16, flipx: bool, flipy: bool, layer1: bool, state: *uxn) void = { let dims = get_window_size(state); let xoff: u16 = if(flipx){ yield 7; } else { yield 0; }; let yoff: u16 = if(flipy){ yield 7; } else { yield 0; }; const pixels: []u8 = if(bpp2) { yield get_pixels_from_2bpp_sprite(addr,state); } else {yield get_pixels_from_1bpp_sprite(addr,state);}; for(let p .. pixels){ const color = colormodes[p][colors]; if((x < MAXWIDTH) && (y < MAXHEIGHT)){ if(layer1) { state.screen.1[x+xoff][y+yoff] = color; } else {state.screen.0[x+xoff][y+yoff] = color; }; }; if(flipx){ xoff -= 1; }else { xoff += 1; }; if(xoff >= 8){ xoff = if(flipx){ yield 7; } else { yield 0; }; if(flipy){ yoff -=1; }else{ yoff += 1; }; }; }; }; fn get_pixels_from_1bpp_sprite(addr: u16, state: *uxn) []u8 = { let pixels: [64]u8 = [0...]; for(let i = 0; i < 8; i+=1){ let data: u8 = state.ram[addr + i: u16]; pixels[i*8 +0] = (data & 0b10000000) >> 7; pixels[i*8 +1] = (data & 0b01000000) >> 6; pixels[i*8 +2] = (data & 0b00100000) >> 5; pixels[i*8 +3] = (data & 0b00010000) >> 4; pixels[i*8 +4] = (data & 0b00001000) >> 3; pixels[i*8 +5] = (data & 0b00000100) >> 2; pixels[i*8 +6] = (data & 0b00000010) >> 1; pixels[i*8 +7] = (data & 0b00000001) >> 0; }; return pixels; }; fn get_pixels_from_2bpp_sprite(addr: u16, state: *uxn) []u8 = { let pixels: [64]u8 = [0...]; for(let i = 0; i < 8; i+=1){ const data1: u8 = state.ram[addr + i: u16]; const data2: u8 = state.ram[addr + i:u16 + 8]; pixels[i*8 +0] = ((data2 & 0b10000000) >> 6) | ((data1 & 0b10000000) >> 7); pixels[i*8 +1] = ((data2 & 0b01000000) >> 5) | ((data1 & 0b01000000) >> 6); pixels[i*8 +2] = ((data2 & 0b00100000) >> 4) | ((data1 & 0b00100000) >> 5); pixels[i*8 +3] = ((data2 & 0b00010000) >> 3) | ((data1 & 0b00010000) >> 4); pixels[i*8 +4] = ((data2 & 0b00001000) >> 2) | ((data1 & 0b00001000) >> 3); pixels[i*8 +5] = ((data2 & 0b00000100) >> 1) | ((data1 & 0b00000100) >> 2); pixels[i*8 +6] = ((data2 & 0b00000010) >> 0) | ((data1 & 0b00000010) >> 1); pixels[i*8 +7] = ((data2 & 0b00000001) << 1) | ((data1 & 0b00000001) >> 0); }; return pixels; }; export fn get_color(x: u16, y: u16, state: *uxn) u16 = { // check layer 1 let color = state.screen.1[x][y] & 0b00000011; if(color == 0){ color = state.screen.0[x][y] & 0b00000011; }; return state.palette[color]; }; export type evalerror = !void; export type unhandled = !u8; export type quit = void; export type error = !(evalerror | unhandled); type simple_op = void; type literal_op = struct { short: bool, ret: bool}; type normal_op = struct { short: bool, keep: bool, ret: bool}; type BRK = simple_op; type JCI = simple_op; type JMI = simple_op; type JSI = simple_op; type LIT = literal_op; type INC = normal_op; type POP = normal_op; type NIP = normal_op; type SWP = normal_op; type ROT = normal_op; type DUP = normal_op; type OVR = normal_op; type EQU = normal_op; type NEQ = normal_op; type GTH = normal_op; type LTH = normal_op; type JMP = normal_op; type JCN = normal_op; type JSR = normal_op; type STH = normal_op; type LDZ = normal_op; type STZ = normal_op; type LDR = normal_op; type STR = normal_op; type LDA = normal_op; type STA = normal_op; type DEI = normal_op; type DEO = normal_op; type ADD = normal_op; type SUB = normal_op; type MUL = normal_op; type DIV = normal_op; type AND = normal_op; type ORA = normal_op; type EOR = normal_op; type SFT = normal_op; type instruction = (BRK | JCI | JMI | JSI | LIT | INC | POP | NIP | SWP | ROT | DUP | OVR | EQU | NEQ | GTH | LTH | JMP | JCN | JSR | STH | LDZ | STZ | LDR | STR | LDA | STA | DEI | DEO | ADD | SUB | MUL | DIV | AND | ORA | EOR | SFT); fn get_instruction(byte: u8) (instruction | error) = { let k: bool = (byte & (0b10000000) != 0 ); let r: bool = (byte & (0b01000000) != 0 ); let s: bool = (byte & (0b00100000) != 0 ); let opcode: u8 = byte & (0b00011111); switch(opcode) { case 0x00 => switch(byte){ case 0x00 => return void: BRK; case 0x20 => return void: JCI; case 0x40 => return void: JMI; case 0x60 => return void: JSI; case 0x80 => // LIT return LIT {short=false, ret=false}; case 0xa0 => // LIT2 return LIT {short=true, ret=false}; case 0xc0 => // LITr return LIT {short=false, ret=true}; case 0xe0 => // LIT2r return LIT {short=true, ret=true}; case => return byte: unhandled; }; case 0x01 => return INC {short = s, keep = k, ret = r}; case 0x02 => return POP {short = s, keep = k, ret = r}; case 0x03 => return NIP {short = s, keep = k, ret = r}; case 0x04 => return SWP {short = s, keep = k, ret = r}; case 0x05 => return ROT {short = s, keep = k, ret = r}; case 0x06 => return DUP {short = s, keep = k, ret = r}; case 0x07 => return OVR {short = s, keep = k, ret = r}; case 0x08 => return EQU {short = s, keep = k, ret = r}; case 0x09 => return NEQ {short = s, keep = k, ret = r}; case 0x0a => return GTH {short = s, keep = k, ret = r}; case 0x0b => return LTH {short = s, keep = k, ret = r}; case 0x0c => return JMP {short = s, keep = k, ret = r}; case 0x0d => return JCN {short = s, keep = k, ret = r}; case 0x0e => return JSR {short = s, keep = k, ret = r}; case 0x0f => return STH {short = s, keep = k, ret = r}; case 0x10 => return LDZ {short = s, keep = k, ret = r}; case 0x11 => return STZ {short = s, keep = k, ret = r}; case 0x12 => return LDR {short = s, keep = k, ret = r}; case 0x13 => return STR {short = s, keep = k, ret = r}; case 0x14 => return LDA {short = s, keep = k, ret = r}; case 0x15 => return STA {short = s, keep = k, ret = r}; case 0x16 => return DEI {short = s, keep = k, ret = r}; case 0x17 => return DEO {short = s, keep = k, ret = r}; case 0x18 => return ADD {short = s, keep = k, ret = r}; case 0x19 => return SUB {short = s, keep = k, ret = r}; case 0x1a => return MUL {short = s, keep = k, ret = r}; case 0x1b => return DIV {short = s, keep = k, ret = r}; case 0x1c => return AND {short = s, keep = k, ret = r}; case 0x1d => return ORA {short = s, keep = k, ret = r}; case 0x1e => return EOR {short = s, keep = k, ret = r}; case 0x1f => return SFT {short = s, keep = k, ret = r}; case => return byte: unhandled; }; }; fn short_from_bytes(high: u8, low: u8) u16 = { return (high: u16) << 8 | (low: u16); }; fn push_to_stack(ret: bool, val: (u8 | u16), state: *uxn) void = { let stack = switch(ret){ case true => yield 1; case false => yield 0; }; match(val) { case let bval: u8 => state.stk[stack][state.ptr[stack]] = bval; state.ptr[stack] = state.ptr[stack] + 1; case let sval: u16 => let low: u8 = (sval & 0xFF): u8; let high: u8 = (sval >> 8): u8; state.stk[stack][state.ptr[stack]] = high; state.ptr[stack] = state.ptr[stack] + 1; state.stk[stack][state.ptr[stack]] = low; state.ptr[stack] = state.ptr[stack] + 1; }; }; // Get value from stack, offset 0 is top of stack. fn get_stack_val(ret: bool, short: bool, off: u8, state: *uxn) (u8 | u16) = { let stack = switch(ret){ case true => yield 1; case false => yield 0; }; if(!short){ let val: u8 = state.stk[stack][state.ptr[stack] - 1 - off]; return val; } else { let low: u8 = state.stk[stack][state.ptr[stack]-1 - (off * 2)]; let high: u8 = state.stk[stack][state.ptr[stack]-2 - (off * 2)]; return short_from_bytes(high,low); }; }; fn increment_stack(ret: bool, short: bool, state: *uxn) void = { let stack = switch(ret){ case true => yield 1; case false => yield 0; }; if(short){ state.ptr[stack] = state.ptr[stack] + 2; } else { state.ptr[stack] = state.ptr[stack] + 1; }; }; fn decrement_stack(ret: bool, short: bool, state: *uxn) void = { let stack = switch(ret){ case true => yield 1; case false => yield 0; }; if(short){ state.ptr[stack] = state.ptr[stack] - 2; } else { state.ptr[stack] = state.ptr[stack] - 1; }; }; fn pop_from_stack(keep: bool, ret: bool, short: bool, state: *uxn) (u8 | u16) = { let val = get_stack_val(ret, short, 0, state); if(!keep) decrement_stack(ret, short, state); return val; }; fn pop_or_get(inst: normal_op, off: u8, state: *uxn) (u8 | u16) = { return switch(inst.keep) { case false => yield pop_from_stack(false,inst.ret,inst.short,state); case true => yield get_stack_val(inst.ret,inst.short, off,state); }; }; export fn uxn_eval(new_pc: u16, state: *uxn) (done | quit | error ) = { // let a: u16 = 0, b: u16 = 0, c: u16 = 0, x: [2]u16 = [0...], y: [2]u16 = [0...], z: [2]u16 = [0...]; state.pc = new_pc; state.running = true; for(state.running){ uxn_step(state)?; }; if(state.dev[0x0f] != 0) return quit; return done; }; export fn uxn_step(state: *uxn) (done | error) = { // fmt::printfln("Starting eval with pc: {:x}", pc)!; let readval = state.ram[state.pc]; let inst: instruction = get_instruction(readval)?; state.pc += 1; //TODO verify all pc changes match(inst) { // /* BRK */ case 0x00: return 1; case BRK => // fmt::println("Break!")!; state.running = false; return done; // /* JCI */ case 0x20: if(DEC(0)) { IMM state.pc += a; } else pc += 2; break; case JCI => let val: u8 = pop_from_stack(false, false, false, state): u8; if(val != 0) { state.pc += 2 + short_from_bytes(state.ram[state.pc],state.ram[state.pc+1]); //TODO, is this signed? }else{ state.pc += 2; }; // /* JMI */ case 0x40: IMM pc += a; break; case JMI => state.pc = state.pc + 2 + short_from_bytes(state.ram[state.pc],state.ram[state.pc+1]); //TODO is this signed? // /* JSI */ case 0x60: IMM PUx(pc, 1, 1) pc += a; break; case JSI => push_to_stack(true, state.pc + 2, state); state.pc = state.pc + 2 + short_from_bytes(state.ram[state.pc],state.ram[state.pc+1]); //TODO is this signed? // /* LIT */ case 0x80: INC(0) = ram[pc++]; break; case let lit_inst: LIT => push_to_stack(lit_inst.ret, state.ram[state.pc], state); state.pc = state.pc + 1; if(lit_inst.short) { push_to_stack(lit_inst.ret, state.ram[state.pc],state); state.pc = state.pc + 1; }; // /* INC */ OPC(0x01,POx(a,d),PUx(a + 1,d,r)) case let inc_inst: INC => let val = match(pop_from_stack(inc_inst.keep,inc_inst.ret,inc_inst.short,state)) { //TODO this whole match statement seems unnecessary case let byte: u8 => yield byte + 1; case let short: u16 => yield short + 1; }; push_to_stack(inc_inst.ret,val,state); // /* POP */ OPC(0x02,ptr[r] -= 1 + d;,{}) case let pop_inst: POP => if(!pop_inst.keep){ pop_from_stack(false,pop_inst.ret,pop_inst.short,state); }; // /* NIP */ OPC(0x03,GOT(x) ptr[r] -= 1 + d;,PUT(x,r)) case let nip_inst: NIP => let top = pop_or_get(nip_inst,0,state); let bot = pop_or_get(nip_inst,1,state); push_to_stack(nip_inst.ret, top, state); // /* SWP */ OPC(0x04,GOT(x) GOT(y),PUT(x,r) PUT(y,r)) case let swp_inst: SWP => let top = pop_or_get(swp_inst,0,state); let bot = pop_or_get(swp_inst,1,state); push_to_stack(swp_inst.ret, top, state); push_to_stack(swp_inst.ret, bot, state); // /* ROT */ OPC(0x05,GOT(x) GOT(y) GOT(z),PUT(y,r) PUT(x,r) PUT(z,r)) case let rot_inst: ROT => let top = pop_or_get(rot_inst,0,state); let mid = pop_or_get(rot_inst,1,state); let bot = pop_or_get(rot_inst,2,state); push_to_stack(rot_inst.ret, mid, state); push_to_stack(rot_inst.ret, top, state); push_to_stack(rot_inst.ret, bot, state); // /* DUP */ OPC(0x06,GOT(x),PUT(x,r) PUT(x,r)) case let dup_inst: DUP => let val = pop_from_stack(dup_inst.keep, dup_inst.ret, dup_inst.short, state); push_to_stack(dup_inst.ret, val, state); push_to_stack(dup_inst.ret, val, state); // /* OVR */ OPC(0x07,GOT(x) GOT(y),PUT(y,r) PUT(x,r) PUT(y,r)) case let ovr_inst: OVR => let top = pop_or_get(ovr_inst,0,state); let bot = pop_or_get(ovr_inst,1,state); push_to_stack(ovr_inst.ret, bot, state); push_to_stack(ovr_inst.ret, top, state); push_to_stack(ovr_inst.ret, bot, state); // /* EQU */ OPC(0x08,POx(a,d) POx(b,d),PUx(b == a,0,r)) case let equ_inst: EQU => let top = pop_or_get(equ_inst,0,state); 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; }; 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 => let top = pop_or_get(neq_inst,0,state); 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; }; 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 => let top = pop_or_get(gth_inst,0,state); 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; }; 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 => let top = pop_or_get(lth_inst,0,state); 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; }; 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 case let s: u16 => state.pc = s; }; // /* JCN */ OPC(0x0d,POx(a,d) POx(b,0),if(b) MOV) case let jcn_inst: JCN => let addr = pop_or_get(jcn_inst,0,state); let cond: u8 = pop_or_get(normal_op {keep = jcn_inst.keep, ret = jcn_inst.ret, short = false}, if(jcn_inst.short){ yield 2;} else { yield 1; }, state ): u8; if( cond != 0) { match(addr) { case let b: u8 => 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; }; }; // /* JSR */ OPC(0x0e,POx(a,d),PUx(pc,1,!r) MOV) case let jsr_inst: JSR => let addr = pop_or_get(jsr_inst,0,state); push_to_stack(true, state.pc, state); match(addr) { case let b: u8 => 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; }; // /* STH */ OPC(0x0f,GOT(x),PUT(x,!r)) case let sth_inst: STH => let val = pop_or_get(sth_inst,0,state); push_to_stack(!sth_inst.ret, val, state); // /* LDZ */ OPC(0x10,POx(a,0),PEK(a, x, 0xff)) case let ldz_inst: LDZ => let addr: u16 = pop_from_stack(ldz_inst.keep, ldz_inst.ret, false, state): u16 & 0x00FF; let val = if(ldz_inst.short){ yield short_from_bytes(state.ram[addr],state.ram[addr+1 & 0x00FF]); } else { yield state.ram[addr]; }; push_to_stack(ldz_inst.ret, val, state); // /* STZ */ OPC(0x11,POx(a,0) GOT(y),POK(a, y, 0xff)) case let stz_inst: STZ => let addr: u16 = pop_or_get(normal_op{short = false, keep = stz_inst.keep, ret = stz_inst.ret}, 0, state): u16 & 0x00FF; let val = if(stz_inst.short){ let low = pop_or_get(normal_op{short = false, keep = stz_inst.keep, ret = stz_inst.ret}, 1, state): u8; let high = pop_or_get(normal_op{short = false, keep = stz_inst.keep, ret = stz_inst.ret}, 2, state): u8; yield short_from_bytes(high,low); }else{ yield pop_or_get(stz_inst, 1, state): u8; }; match (val) { case let b: u8 => state.ram[addr] = b; case let s: u16 => let high = (s >> 8 ): u8; let low = s: u8; state.ram[addr] = high; state.ram[addr+1 & 0x00FF] = low; //TODO verify correct endianness }; // /* LDR */ OPC(0x12,POx(a,0),PEK(pc + (Sint8)a, x, 0xffff)) case let ldr_inst: LDR => let reladdr = (pop_from_stack(ldr_inst.keep, ldr_inst.ret, false, state): u16): i8; let addr: u16 = (state.pc: u32: i32 + reladdr): u16; let val = if(ldr_inst.short){ yield short_from_bytes(state.ram[addr],state.ram[addr+1]); } else { yield state.ram[addr]; }; push_to_stack(ldr_inst.ret, val, state); // /* STR */ OPC(0x13,POx(a,0) GOT(y),POK(pc + (Sint8)a, y, 0xffff)) case let str_inst: STR => let reladdr = (pop_from_stack(str_inst.keep, str_inst.ret, false, state): u16): i8; let addr: u16 = (state.pc: u32: i32 + reladdr): u16; let val = pop_from_stack(str_inst.keep, str_inst.ret, str_inst.short, state); match (val) { case let b: u8 => state.ram[addr] = b; case let s: u16 => let high = (s >> 8 ): u8; let low = s: u8; state.ram[addr] = high; state.ram[addr+1] = low; //TODO verify correct endianness }; // /* LDA */ OPC(0x14,POx(a,1),PEK(a, x, 0xffff)) case let lda_inst: LDA => let addr: u16 = pop_from_stack(lda_inst.keep, lda_inst.ret, true, state): u16; let val = if(lda_inst.short){ yield short_from_bytes(state.ram[addr],state.ram[addr+1]); } else { yield state.ram[addr]; }; push_to_stack(lda_inst.ret, val, state); // /* STA */ OPC(0x15,POx(a,1) GOT(y),POK(a, y, 0xffff)) case let sta_inst: STA => let addr: u16 = pop_from_stack(sta_inst.keep, sta_inst.ret, true, state): u16; let val = pop_from_stack(sta_inst.keep, sta_inst.ret, sta_inst.short, state); match (val) { case let b: u8 => state.ram[addr] = b; case let s: u16 => let high = (s >> 8 ): u8; let low = s: u8; state.ram[addr] = high; state.ram[addr+1] = low; //TODO verify correct endianness }; // /* DEI */ OPC(0x16,POx(a,0),DEI(a, x)) case let dei_inst: DEI => // fmt::println("DEI Instruction")!; let port: u8 = pop_from_stack(dei_inst.keep, dei_inst.ret, false, state): u8; let val = if(dei_inst.short){ yield short_from_bytes(emu_dei(port,state),emu_dei(port+1,state)); }else{ yield emu_dei(port,state); }; 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); }; // /* ADD */ OPC(0x18,POx(a,d) POx(b,d),PUx(b + a, d,r)) case let add_inst: ADD => let top = pop_or_get(add_inst,0,state); let bot = pop_or_get(add_inst,1,state); let res = if(!add_inst.short){ yield (top: u8 + bot: u8); } else { yield (top: u16 + bot: u16); }; push_to_stack(add_inst.ret, res, state); // /* SUB */ OPC(0x19,POx(a,d) POx(b,d),PUx(b - a, d,r)) case let sub_inst: SUB => let top = pop_or_get(sub_inst,0,state); let bot = pop_or_get(sub_inst,1,state); let res = if(!sub_inst.short){ yield (bot: u8 - top: u8); } else { yield (bot: u16 - top: u16); }; push_to_stack(sub_inst.ret, res, state); // /* MUL */ OPC(0x1a,POx(a,d) POx(b,d),PUx(b * a, d,r)) case let mul_inst: MUL => let top = pop_or_get(mul_inst,0,state); let bot = pop_or_get(mul_inst,1,state); let res = if(!mul_inst.short){ yield (bot: u8 * top: u8); } else { yield (bot: u16 * top: u16); }; push_to_stack(mul_inst.ret, res, state); // /* DIV */ OPC(0x1b,POx(a,d) POx(b,d),PUx(a ? b / a : 0, d,r)) case let div_inst: DIV => let top = pop_or_get(div_inst,0,state); let bot = pop_or_get(div_inst,1,state); let res = if(!div_inst.short){ assert(bot is u8); assert(top is u8); if(top: u8 == 0) yield top; yield (bot: u8 / top: u8): u8; } else { assert(bot is u16); assert(top is u16); if(top: u16 == 0) yield top; yield (bot: u16 / top: u16): u16; };//TODO handle rounding and edge cases with division push_to_stack(div_inst.ret, res, state); // /* AND */ OPC(0x1c,POx(a,d) POx(b,d),PUx(b & a, d,r)) case let and_inst: AND => let top = pop_or_get(and_inst,0,state); let bot = pop_or_get(and_inst,1,state); let res = if(!and_inst.short){ yield (bot: u8 & top: u8); } else { yield (bot: u16 & top: u16); }; push_to_stack(and_inst.ret, res, state); // /* ORA */ OPC(0x1d,POx(a,d) POx(b,d),PUx(b | a, d,r)) case let ora_inst: ORA => let top = pop_or_get(ora_inst,0,state); let bot = pop_or_get(ora_inst,1,state); let res = if(!ora_inst.short){ yield (bot: u8 | top: u8); } else { yield (bot: u16 | top: u16); }; push_to_stack(ora_inst.ret, res, state); // /* EOR */ OPC(0x1e,POx(a,d) POx(b,d),PUx(b ^ a, d,r)) case let eor_inst: EOR => let top = pop_or_get(eor_inst,0,state); let bot = pop_or_get(eor_inst,1,state); let res = if(!eor_inst.short){ yield (bot: u8 ^ top: u8); } else { yield (bot: u16 ^ top: u16); }; push_to_stack(eor_inst.ret, res, state); // /* SFT */ OPC(0x1f,POx(a,0) POx(b,d),PUx(b >> (a & 0xf) << (a >> 4), d,r)) case let sft_inst: SFT => let shifts: u8 = pop_or_get(normal_op{short = false, keep = sft_inst.keep, ret = sft_inst.ret}, 0, state): u8; let rightshifts = shifts & 0b00001111; let leftshifts = (shifts & 0b11110000) >> 4; if(sft_inst.short){ //TODO, this seems like it can be cleaned up a bit let low = pop_or_get(normal_op{short = false, keep = sft_inst.keep, ret = sft_inst.ret}, 1, state): u8; let high = pop_or_get(normal_op{short = false, keep = sft_inst.keep, ret = sft_inst.ret}, 2, state): u8; let val = short_from_bytes(high,low); let res = (val >> rightshifts ) << leftshifts; push_to_stack(sft_inst.ret, res, state); }else{ let val = pop_or_get(sft_inst, 1, state): u8; let res = (val >> rightshifts ) << leftshifts; push_to_stack(sft_inst.ret, res, state); }; case => return readval: unhandled; }; return done; }; export fn console_input(c: u8, ctype: u8, state: *uxn) (done | error) = { state.dev[0x12] = c; state.dev[0x17] = ctype; if(state.console_vector != 0){ // fmt::println("Evaluating Console Vector")!; uxn_eval(state.console_vector, state)?; // fmt::println("Done Evaluating Console Vector")!; }; //TODO implement eval return done; }; // Converts an error into a user-friendly string export fn strerror(err: error) str = { match (err) { case evalerror => return "Reached unexpected part of eval code"; case let val: unhandled => return fmt::asprintf("Unknown Opcode read: {:x}", val: u8)!; }; }; export fn uxn_init(path: str) ( *uxn | error ) = { 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? //Initialize default size state.dev[0x22] = 0x02; state.dev[0x23] = 0x80; state.dev[0x24] = 0x01; state.dev[0x25] = 0xe0; //Initialize default colors TODO look for .theme files state.dev[0x08] = 0xf0; state.dev[0x09] = 0x7f; state.dev[0x0a] = 0xf0; state.dev[0x0b] = 0xd6; state.dev[0x0c] = 0xf0; state.dev[0x0d] = 0xb2; regenerate_palettes(state); return state; }; 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; };