957 lines
27 KiB
Hare
957 lines
27 KiB
Hare
use fmt;
|
|
use os;
|
|
use fs;
|
|
use io;
|
|
use bufio;
|
|
use bytes;
|
|
use time;
|
|
use time::date;
|
|
use strings;
|
|
|
|
export type uxn = struct {
|
|
pc: u16,
|
|
console_vector: u16,
|
|
screen_vector: u16,
|
|
mouse_vector: u16,
|
|
controller_vector: u16,
|
|
ram: [BANKS_CAP] 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,
|
|
files: [2] uxnFile,
|
|
};
|
|
|
|
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);
|
|
|
|
export type uxnFile = struct {
|
|
file: io::handle,
|
|
filepath: str,
|
|
state: filestate,
|
|
dir: bool,
|
|
exists: bool,
|
|
fappend: bool,
|
|
|
|
};
|
|
export type filestate = enum {
|
|
IDLE,
|
|
FILE_READ,
|
|
FILE_WRITE,
|
|
DIR_READ,
|
|
DIR_WRITE
|
|
};
|
|
|
|
|
|
def NUMBANKS: u16 = 0x10;
|
|
def BANKSIZE: u32 = 0x10000;
|
|
def BANKS_CAP = NUMBANKS * BANKSIZE;
|
|
|
|
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,
|
|
controller_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...],
|
|
files = [uxnFile {
|
|
file = io::empty,
|
|
filepath = "",
|
|
state = filestate::IDLE,
|
|
dir = false,
|
|
exists = false,
|
|
fappend = false,
|
|
}...],
|
|
})!;
|
|
};
|
|
|
|
|
|
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 0xc0 =>
|
|
const date = time::date::localnow();
|
|
const year = time::date::year(&date);
|
|
return ((year) >> 8): u8;
|
|
case 0xc1 =>
|
|
const date = time::date::localnow();
|
|
const year = time::date::year(&date);
|
|
return year: u8;
|
|
case 0xc2 =>
|
|
const date = time::date::localnow();
|
|
return time::date::month(&date): u8 - 1;
|
|
case 0xc3 =>
|
|
const date = time::date::localnow();
|
|
return time::date::day(&date): u8;
|
|
case 0xc4 =>
|
|
const date = time::date::localnow();
|
|
return time::date::hour(&date): u8;
|
|
case 0xc5 =>
|
|
const date = time::date::localnow();
|
|
return time::date::minute(&date): u8;
|
|
case 0xc6 =>
|
|
const date = time::date::localnow();
|
|
return time::date::second(&date): u8;
|
|
case 0xc7 =>
|
|
const date = time::date::localnow();
|
|
return (time::date::weekday(&date): u8 + 1) % 7;
|
|
case 0xc8 =>
|
|
const date = time::date::localnow();
|
|
const day = time::date::yearday(&date) - 1;
|
|
return ((day) >> 8): u8;
|
|
case 0xc9 =>
|
|
const date = time::date::localnow();
|
|
const day = time::date::yearday(&date) - 1;
|
|
return day: u8;
|
|
case 0xca =>
|
|
const date = time::date::localnow();
|
|
const zone = time::date::zone(&date);
|
|
return if(zone.dst) {yield 1;} else {yield 0;}; //TODO this isn't tested
|
|
case =>
|
|
return state.dev[port];
|
|
};
|
|
};
|
|
|
|
|
|
export fn emu_deo(port: u8, value: u8, state: *uxn) void = {
|
|
state.dev[port] = value;
|
|
switch(port) {
|
|
case 0x03 =>
|
|
const high = state.dev[0x02];
|
|
const low = value;
|
|
const eaddr = short_from_bytes(high,low);
|
|
deo_expansion(eaddr,state);
|
|
case 0x04 =>
|
|
state.ptr[0] = value;
|
|
case 0x05 =>
|
|
state.ptr[1] = value;
|
|
case 0x0e =>
|
|
print_stack_debug(state);
|
|
case 0x11 =>
|
|
const high = state.dev[0x10];
|
|
const low = value;
|
|
state.console_vector = short_from_bytes(high,low);
|
|
case 0x18 =>
|
|
fmt::print(value: rune)!;
|
|
case 0x19 =>
|
|
fmt::error(value)!;
|
|
case 0x21 =>
|
|
const high = state.dev[0x20];
|
|
const low = value;
|
|
state.screen_vector = short_from_bytes(high,low);
|
|
case 0x2e =>
|
|
draw_pixel(value,state);
|
|
case 0x2f =>
|
|
draw_sprite(value,state);
|
|
case 0x81 =>
|
|
const high = state.dev[0x80];
|
|
const low = value;
|
|
state.controller_vector = short_from_bytes(high,low);
|
|
case 0x91 =>
|
|
const high = state.dev[0x90];
|
|
const low = value;
|
|
state.mouse_vector = short_from_bytes(high,low);
|
|
case 0xa5 => // File/stat [0]
|
|
filestat_to_addr(0,state);
|
|
case 0xa6 => // File/delete [0]
|
|
delete_file(0,state);
|
|
case 0xa9 => // File/name [0]
|
|
set_file_name(0,state);
|
|
case 0xad => // File/read [0]
|
|
read_file_to_addr(0,state);
|
|
case 0xaf => //File/write [0]
|
|
write_file_from_addr(0,state);
|
|
case 0xb5 => // File/stat [1]
|
|
filestat_to_addr(1,state);
|
|
case 0xb6 => // File/delete [1]
|
|
delete_file(1,state);
|
|
case 0xb9 => // File/name [1]
|
|
set_file_name(1,state);
|
|
case 0xbd => // File/read [1]
|
|
read_file_to_addr(1,state);
|
|
case 0xbf => //File/write [1]
|
|
write_file_from_addr(1,state);
|
|
|
|
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);
|
|
|
|
};
|
|
|
|
};
|
|
};
|
|
|
|
|
|
fn deo_expansion(addr: u16, state: *uxn) void = {
|
|
const op = state.ram[addr];
|
|
const length = short_from_bytes(state.ram[addr+1],state.ram[addr+2]);
|
|
switch(op) {
|
|
case 0x00 => //fill
|
|
const bank = short_from_bytes(state.ram[addr+3],state.ram[addr+4]): u32;
|
|
const dstaddr = short_from_bytes(state.ram[addr+5],state.ram[addr+6]): u32;
|
|
const value = state.ram[addr+7];
|
|
// fmt::printfln("expansion fill: bank: {:x} addr: {:x} value: {:x} | length: {:x}",bank,dstaddr,value,length)!;
|
|
if(bank < NUMBANKS) for(let i: u16 =0; i < length; i+=1){
|
|
state.ram[bank * BANKSIZE + dstaddr + i] = value;
|
|
};
|
|
case 0x01 => //cpyl
|
|
const srcbank = short_from_bytes(state.ram[addr+3],state.ram[addr+4]): u32;
|
|
const srcaddr = short_from_bytes(state.ram[addr+5],state.ram[addr+6]): u32;
|
|
const dstbank = short_from_bytes(state.ram[addr+7],state.ram[addr+8]): u32;
|
|
const dstaddr = short_from_bytes(state.ram[addr+9],state.ram[addr+10]): u32;
|
|
// fmt::printfln("Cpyl: src: {:x} <-> {:x} dst: {:x} <-> {:x} | length: {:x}",srcbank,srcaddr,dstbank,dstaddr,length)!;
|
|
if(srcbank < NUMBANKS && dstbank < NUMBANKS) for(let i: u16 =0; i < length; i+=1){
|
|
const readval = state.ram[srcbank * BANKSIZE + srcaddr + i];
|
|
state.ram[dstbank * BANKSIZE + dstaddr + i] = readval;
|
|
};
|
|
case 0x02 => //cpr TODO are these actually different?? Either way need to not use for loop
|
|
const srcbank = short_from_bytes(state.ram[addr+3],state.ram[addr+4]): u32;
|
|
const srcaddr = short_from_bytes(state.ram[addr+5],state.ram[addr+6]): u32;
|
|
const dstbank = short_from_bytes(state.ram[addr+7],state.ram[addr+8]): u32;
|
|
const dstaddr = short_from_bytes(state.ram[addr+9],state.ram[addr+10]): u32;
|
|
// fmt::printfln("Cpyr: src: {:x} <-> {:x} dst: {:x} <-> {:x} | length: {:x}",srcbank,srcaddr,dstbank,dstaddr,length)!;
|
|
if(srcbank < NUMBANKS && dstbank < NUMBANKS) for(let i: u16 =1; i <= length; i+=1){
|
|
const readval = state.ram[srcbank * BANKSIZE + srcaddr + length - i];
|
|
state.ram[dstbank * BANKSIZE + dstaddr + length - i] = readval;
|
|
};
|
|
case =>
|
|
fmt::fatalf("Unknown expansion op: 0x{:x}",op);
|
|
};
|
|
|
|
};
|
|
|
|
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;
|
|
};
|
|
};
|
|
|
|
export fn print_stack_debug(state: *uxn) void = {
|
|
//TODO follow standard form
|
|
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);
|
|
};
|
|
|
|
fn read_from_addr(short: bool, addr: u16, state: *uxn) (u8 | u16) = {
|
|
switch(short){
|
|
case true =>
|
|
const high = state.ram[addr];
|
|
const low = state.ram[addr+1];
|
|
return short_from_bytes(high,low);
|
|
|
|
case false =>
|
|
return state.ram[addr];
|
|
};
|
|
};
|
|
|
|
fn write_to_addr(value: (u8 | u16), addr: u16, state: *uxn) void = {
|
|
match(value){
|
|
case let s: u16 =>
|
|
const high = (s >> 8 ): u8;
|
|
const low = s: u8;
|
|
state.ram[addr] = high;
|
|
state.ram[addr+1] = low;
|
|
|
|
case let b: u8 =>
|
|
state.ram[addr] = b;
|
|
};
|
|
};
|
|
|
|
fn read_from_zaddr(short: bool, zaddr: u8, state: *uxn) (u8 | u16) = {
|
|
switch(short){
|
|
case true =>
|
|
const high = state.ram[zaddr: u16];
|
|
const low = state.ram[(zaddr+1): u16];
|
|
return short_from_bytes(high,low);
|
|
case false =>
|
|
return state.ram[zaddr: u16];
|
|
};
|
|
};
|
|
fn write_to_zaddr(value: (u8 | u16), zaddr: u8, state: *uxn) void = {
|
|
match(value){
|
|
case let s: u16 =>
|
|
const high = (s >> 8 ): u8;
|
|
const low = s: u8;
|
|
state.ram[zaddr: u16] = high;
|
|
state.ram[(zaddr+1): u16] = low;
|
|
case let b: u8 =>
|
|
state.ram[zaddr: u16] = b;
|
|
};
|
|
};
|
|
|
|
fn push_to_stack(ret: bool, val: (u8 | u16), state: *uxn) void = {
|
|
const 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. offset depends on short or byte
|
|
fn get_stack_val(ret: bool, short: bool, off: u8, state: *uxn) (u8 | u16) = {
|
|
const 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);
|
|
};
|
|
};
|
|
|
|
//peek offset is always byte offset
|
|
fn peek(ret: bool, short: bool, off: u8, state: *uxn) (u8 | u16 ) = {
|
|
const 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];
|
|
let high: u8 = state.stk[stack][state.ptr[stack] - 2 - off];
|
|
return short_from_bytes(high,low);
|
|
};
|
|
|
|
};
|
|
|
|
fn increment_stack(ret: bool, short: bool, state: *uxn) void = {
|
|
const 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 = {
|
|
const 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) = {
|
|
const 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);
|
|
};
|
|
};
|
|
//Offset is always byte aligned in peek
|
|
fn pop_or_peek(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 peek(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)!;
|
|
const readval = state.ram[state.pc];
|
|
const inst: instruction = get_instruction(readval)?;
|
|
state.pc += 1; //TODO verify all pc changes
|
|
|
|
match(inst) {
|
|
case BRK =>
|
|
// fmt::println("Break!")!;
|
|
state.running = false;
|
|
return done;
|
|
case JCI =>
|
|
const 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;
|
|
};
|
|
case JMI =>
|
|
state.pc = state.pc + 2 + short_from_bytes(state.ram[state.pc],state.ram[state.pc+1]); //TODO is this signed?
|
|
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?
|
|
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;
|
|
};
|
|
case let inc_inst: INC =>
|
|
const 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);
|
|
case let pop_inst: POP =>
|
|
if(!pop_inst.keep){
|
|
pop_from_stack(false,pop_inst.ret,pop_inst.short,state);
|
|
};
|
|
case let nip_inst: NIP =>
|
|
const top = pop_or_get(nip_inst,0,state);
|
|
const bot = pop_or_get(nip_inst,1,state);
|
|
push_to_stack(nip_inst.ret, top, state);
|
|
case let swp_inst: SWP =>
|
|
const top = pop_or_get(swp_inst,0,state);
|
|
const bot = pop_or_get(swp_inst,1,state);
|
|
|
|
push_to_stack(swp_inst.ret, top, state);
|
|
push_to_stack(swp_inst.ret, bot, state);
|
|
case let rot_inst: ROT =>
|
|
const top = pop_or_get(rot_inst,0,state);
|
|
const mid = pop_or_get(rot_inst,1,state);
|
|
const 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);
|
|
case let dup_inst: DUP =>
|
|
const 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);
|
|
case let ovr_inst: OVR =>
|
|
const top = pop_or_get(ovr_inst,0,state);
|
|
const 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);
|
|
case let equ_inst: EQU =>
|
|
const top = pop_or_get(equ_inst,0,state);
|
|
const bot = pop_or_get(equ_inst,1,state);
|
|
|
|
//TODO, I think this works, but it feels unclean with the casting
|
|
const val: u8 = if(top: u16 == bot: u16){
|
|
yield 1;
|
|
} else {
|
|
yield 0;
|
|
};
|
|
push_to_stack(equ_inst.ret, val, state);
|
|
case let neq_inst: NEQ =>
|
|
const top = pop_or_get(neq_inst,0,state);
|
|
const bot = pop_or_get(neq_inst,1,state);
|
|
|
|
//TODO, I think this works, but it feels unclean with the casting
|
|
const val: u8 = if(top: u16 != bot: u16){
|
|
yield 1;
|
|
} else {
|
|
yield 0;
|
|
};
|
|
push_to_stack(neq_inst.ret, val, state);
|
|
case let gth_inst: GTH =>
|
|
const top = pop_or_get(gth_inst,0,state);
|
|
const bot = pop_or_get(gth_inst,1,state);
|
|
|
|
//TODO, I think this works, but it feels unclean with the casting
|
|
const val: u8 = if(bot: u16 > top: u16){
|
|
yield 1;
|
|
} else {
|
|
yield 0;
|
|
};
|
|
push_to_stack(gth_inst.ret, val, state);
|
|
case let lth_inst: LTH =>
|
|
const top = pop_or_get(lth_inst,0,state);
|
|
const bot = pop_or_get(lth_inst,1,state);
|
|
|
|
//TODO, I think this works, but it feels unclean with the casting
|
|
const val: u8 = if(bot: u16 < top: u16){
|
|
yield 1;
|
|
} else {
|
|
yield 0;
|
|
};
|
|
push_to_stack(lth_inst.ret, val, state);
|
|
case let jmp_inst: JMP =>
|
|
const addr = pop_or_get(jmp_inst,0,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;
|
|
};
|
|
case let jcn_inst: JCN =>
|
|
const addr = pop_or_get(jcn_inst,0,state);
|
|
const cond: u8 = pop_or_peek(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;
|
|
};
|
|
};
|
|
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;
|
|
};
|
|
|
|
case let sth_inst: STH =>
|
|
const val = pop_from_stack(sth_inst.keep,sth_inst.ret,sth_inst.short,state);
|
|
push_to_stack(!sth_inst.ret, val, state);
|
|
case let ldz_inst: LDZ =>
|
|
const addr: u8 = pop_from_stack(ldz_inst.keep, ldz_inst.ret, false, state): u8;
|
|
const val = read_from_zaddr(ldz_inst.short,addr,state);
|
|
push_to_stack(ldz_inst.ret, val, state);
|
|
case let stz_inst: STZ =>
|
|
const addr: u8 = pop_or_peek(normal_op{short = false, keep = stz_inst.keep, ret = stz_inst.ret}, 0, state): u8;
|
|
const val = pop_or_peek(stz_inst,1,state);
|
|
write_to_zaddr(val,addr,state);
|
|
case let ldr_inst: LDR =>
|
|
const reladdr = (pop_from_stack(ldr_inst.keep, ldr_inst.ret, false, state): u16): i8;
|
|
const addr: u16 = (state.pc: u32: i32 + reladdr): u16;
|
|
const val = read_from_addr(ldr_inst.short,addr,state);
|
|
push_to_stack(ldr_inst.ret, val, state);
|
|
case let str_inst: STR =>
|
|
const reladdr = (pop_or_peek(normal_op{short = false, keep = str_inst.keep, ret = str_inst.ret}, 0, state): u16 & 0x00FF): u8: i8;
|
|
const addr: u16 = (state.pc: u32: i32 + reladdr): u16;
|
|
const val = pop_or_peek(str_inst,1,state);
|
|
write_to_addr(val,addr,state);
|
|
case let lda_inst: LDA =>
|
|
const addr: u16 = pop_from_stack(lda_inst.keep, lda_inst.ret, true, state): u16;
|
|
const val = read_from_addr(lda_inst.short,addr,state);
|
|
push_to_stack(lda_inst.ret, val, state);
|
|
case let sta_inst: STA =>
|
|
const addr: u16 = pop_or_peek(normal_op{short = true, keep = sta_inst.keep, ret = sta_inst.ret}, 0, state): u16 ;
|
|
const val = pop_or_peek(sta_inst,2,state);
|
|
write_to_addr(val,addr,state);
|
|
case let dei_inst: DEI =>
|
|
const port: u8 = pop_from_stack(dei_inst.keep, dei_inst.ret, false, state): u8;
|
|
const 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);
|
|
case let deo_inst: DEO =>
|
|
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);
|
|
};
|
|
case let add_inst: ADD =>
|
|
const top = pop_or_get(add_inst,0,state);
|
|
const bot = pop_or_get(add_inst,1,state);
|
|
const res = if(!add_inst.short){
|
|
yield (top: u8 + bot: u8);
|
|
} else {
|
|
yield (top: u16 + bot: u16);
|
|
};
|
|
push_to_stack(add_inst.ret, res, state);
|
|
case let sub_inst: SUB =>
|
|
const top = pop_or_get(sub_inst,0,state);
|
|
const bot = pop_or_get(sub_inst,1,state);
|
|
const res = if(!sub_inst.short){
|
|
yield (bot: u8 - top: u8);
|
|
} else {
|
|
yield (bot: u16 - top: u16);
|
|
};
|
|
push_to_stack(sub_inst.ret, res, state);
|
|
case let mul_inst: MUL =>
|
|
const top = pop_or_get(mul_inst,0,state);
|
|
const bot = pop_or_get(mul_inst,1,state);
|
|
const res = if(!mul_inst.short){
|
|
yield (bot: u8 * top: u8);
|
|
} else {
|
|
yield (bot: u16 * top: u16);
|
|
};
|
|
push_to_stack(mul_inst.ret, res, state);
|
|
case let div_inst: DIV =>
|
|
const top = pop_or_get(div_inst,0,state);
|
|
const bot = pop_or_get(div_inst,1,state);
|
|
const 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;
|
|
};
|
|
push_to_stack(div_inst.ret, res, state);
|
|
case let and_inst: AND =>
|
|
const top = pop_or_get(and_inst,0,state);
|
|
const bot = pop_or_get(and_inst,1,state);
|
|
const res = if(!and_inst.short){
|
|
yield (bot: u8 & top: u8);
|
|
} else {
|
|
yield (bot: u16 & top: u16);
|
|
};
|
|
push_to_stack(and_inst.ret, res, state);
|
|
case let ora_inst: ORA =>
|
|
const top = pop_or_get(ora_inst,0,state);
|
|
const bot = pop_or_get(ora_inst,1,state);
|
|
const res = if(!ora_inst.short){
|
|
yield (bot: u8 | top: u8);
|
|
} else {
|
|
yield (bot: u16 | top: u16);
|
|
};
|
|
push_to_stack(ora_inst.ret, res, state);
|
|
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);
|
|
case let sft_inst: SFT =>
|
|
const shifts: u8 = pop_or_peek(normal_op{short = false, keep = sft_inst.keep, ret = sft_inst.ret}, 0, state): u8;
|
|
const rightshifts = shifts & 0b00001111;
|
|
const leftshifts = ((shifts & 0b11110000) >> 4) & 0b00001111;
|
|
const val = pop_or_peek(sft_inst,1,state);
|
|
const res = match(val) {
|
|
case let s: u16 =>
|
|
yield (s >> rightshifts ) << leftshifts;
|
|
case let b: u8 =>
|
|
yield (b >> rightshifts ) << leftshifts;
|
|
};
|
|
push_to_stack(sft_inst.ret, res, state);
|
|
|
|
case =>
|
|
return readval: unhandled;
|
|
};
|
|
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 ) = {
|
|
const 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));
|
|
};
|
|
const state: *uxn = initialize_uxn_mem();
|
|
//Copy rom into ram
|
|
const bytesread = match (io::read(romfile,state.ram[0x100..(BANKS_CAP - 0x100)])){
|
|
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)!;
|
|
};
|