summary refs log tree commit diff
path: root/runtime-py65.ts
diff options
context:
space:
mode:
Diffstat (limited to 'runtime-py65.ts')
-rw-r--r--runtime-py65.ts264
1 files changed, 264 insertions, 0 deletions
diff --git a/runtime-py65.ts b/runtime-py65.ts
new file mode 100644
index 0000000..c6fbd51
--- /dev/null
+++ b/runtime-py65.ts
@@ -0,0 +1,264 @@
+import The65c02, { BitField, Pin } from "./65c02.ts";
+import matrix from "./opcode_matrix.json" with { type: "json" };
+import { parseArgs } from "jsr:@std/cli/parse-args";
+
+const debug = Deno.args.includes('-d')
+
+// the thing used for ram
+const ram = new Uint8Array(2**16);
+
+// initiate the emulator
+const cpu = new The65c02(function (this: The65c02) {
+    // since this is controlled by the user it's easy
+    // to map things into address space
+    this.io.data.set(ram[this.io.address.num()] ?? 0)
+}, function (this: The65c02) {
+    if (this.io.address.num() == 0x5000) {
+        if (debug)
+            return console.log('CHROUT', `0x${this.io.data.num().toString(16)}`, String.fromCharCode(this.io.data.num()))
+        return Deno.stdout.write(new Uint8Array([this.io.data.num()]))
+    }
+    // write
+    ram[this.io.address.num()] = this.io.data.num()
+})
+
+await cpu.loadInstructions()
+
+// test
+cpu.stackPointer.set(0xFF)
+
+const args = parseArgs(Deno.args)
+
+const binStart = parseInt(args.b ?? args.binStart ?? '8000', 16)
+const resVec = parseInt(args.s ?? args.start ?? binStart.toString(16), 16)
+
+if (Number.isNaN(binStart))
+    throw 'binStart is NaN!'
+
+// read code from file
+const code = Deno.readFileSync(args._.toString() || 'msbasic/tmp/eater.bin')
+
+// write code to ram before execution
+for (let offset = 0; offset < code.length; offset++) {
+    const byte = code[offset];
+    ram[binStart  + offset] = byte;
+}
+
+// mem address $0000
+ram[0xFFFC] = resVec & 0x00FF
+ram[0xFFFD] = (resVec & 0xFF00) >> 8
+
+// pull RESB low to reset the 65c02
+cpu.io.reset.LO()
+cpu.cycle()
+// cpu.programCounter.set(0x13)
+
+//the cpu reset, pull RESB high and start execution!
+cpu.io.reset.HI()
+
+function inspect() {
+    // console.log('IO status:')
+    // console.log(Object.entries(cpu.io)
+    //     .map(([name, io]) => {
+    //         if (io instanceof Pin) {
+    //             return ` ${name}: ${' '.repeat(20 - name.length)}${io.high ? "HI" : "LO"}`
+    //         } else if (io instanceof BitField) {
+    //             return ` ${name}: ${' '.repeat(20 - name.length)}${io.bits.map(k=>+k).join('')} (0x${io.num().toString(16)})`
+    //         }
+    //     }).join('\n'));
+    
+    console.log(`\
+       PC  AC XR YR SP NV-BDIZC
+6502: ${cpu.programCounter.num().toString(16).padStart(4, '0')} ${cpu.regA.num().toString(16).padStart(2, '0')} ${cpu.regX.num().toString(16).padStart(2, '0')} ${cpu.regY.num().toString(16).padStart(2, '0')} ${cpu.stackPointer.num().toString(16).padStart(2, '0')} ${cpu.stackPointer.num().toString(2).padStart(8, '0')}`)
+    // console.log('\nregisters:')
+    // console.log(` A:  ${cpu.regA.bits.reverse().map(k=>+k).join('')} (0x${cpu.regA.num().toString(16)})`)
+    // console.log(` X:  ${cpu.regX.bits.reverse().map(k=>+k).join('')} (0x${cpu.regX.num().toString(16)})`)
+    // console.log(` Y:  ${cpu.regY.bits.reverse().map(k=>+k).join('')} (0x${cpu.regY.num().toString(16)})`)
+    // console.log(` SP: ${cpu.stackPointer.bits.reverse().map(k=>+k).join('')} (0x${cpu.stackPointer.num().toString(16)})`)
+    // console.log(` PC: ${cpu.programCounter.bits.reverse().map(k=>+k).join('')} (0x${cpu.programCounter.num().toString(16)})`)
+}
+
+let skip = 0;
+let breakpoints: number[] = []
+let instBreakpoints: string[] = []
+let memBreakpoints: number[] = []
+
+if (debug) {
+    console.info('NOTE; the instructions are executed after input')
+}
+
+const lengths: Record<string, number> = {
+    'implicit': 1,
+    'implied': 1,
+    'immediate': 2,
+    'zero-page': 2,
+    'zero-page, X-indexed': 2,
+    'zero-page, Y-indexed': 2,
+    'relative': 2,
+    'absolute': 3,
+    'absolute, X-indexed': 3,
+    'absolute, Y-indexed': 3,
+    'indirect': 3,
+    'absolute indexed indirect': 3,
+    'absolute indirect indexed': 3
+}
+
+// repeat until the cpu requests an interrupt
+mainloop:
+while (!cpu.io.interruptRequest.high) {
+    const instId = ram[cpu.programCounter.num()]
+        .toString(16)
+        .padStart(2, '0');
+    const goog = (matrix as Record<string, { mnemonic: string, mode: string }>)[instId];
+    if (!goog) {
+        console.log('uh', instId, 'unknown')
+        break;
+    }
+    let addr: number | undefined;
+    if (goog.mode != 'implied' && goog.mode != 'implicit') {
+        const pastPC = cpu.programCounter.num()
+        addr = cpu.getAddr(goog.mode);
+        cpu.programCounter.set(pastPC)
+    }
+    const instr = goog;
+    if (debug)
+
+    // debug step-by-step mode
+    dbg: if (debug) {
+        const instLen = lengths[goog.mode]
+        const instrData = [...ram.slice(cpu.programCounter.num(), cpu.programCounter.num() + instLen)]
+        const thingy: string = instrData.map<string>(k => k.toString(16).padStart(2, '0')).join(' ')
+        console.debug(`$${cpu.programCounter.num().toString(16).padStart(4, '0')}  ${thingy.padEnd(10)}${instr.mnemonic}`)
+        inspect()
+        if (instBreakpoints.includes(instr.mnemonic)) {
+            instBreakpoints = instBreakpoints.filter(k => k != instr.mnemonic)
+            skip = 0;
+            console.log('hit instruction breakpoint on', cpu.programCounter.num().toString(16))
+        }
+        if (breakpoints.includes(cpu.programCounter.num())) {
+            breakpoints = breakpoints.filter(k => k != cpu.programCounter.num())
+            skip = 0;
+            console.log('hit breakpoint on', cpu.programCounter.num().toString(16))
+        }
+        if (addr && memBreakpoints.includes(addr)) {
+            memBreakpoints = memBreakpoints.filter(k => k != addr)
+            skip = 0;
+            console.log('hit breakpoint on', addr.toString(16))
+        }
+        if (skip != 0) {
+            skip--
+            break dbg;
+        }
+        dbgl:
+        while (true) {
+            const i = new Uint8Array(16);
+            Deno.stdout.write(Uint8Array.from(['.'.charCodeAt(0)]))
+            await Deno.stdin.read(i);
+            if (i[0] == 'b'.charCodeAt(0)) {
+                console.log('BREAK!!')
+                break mainloop;
+            } else if (i[0] == 'i'.charCodeAt(0)) {
+                inspect()
+                continue;
+            } else if (i[0] == 's'.charCodeAt(0)) {
+                console.log('stack:')
+                for (let i = 0; i < cpu.stackPointer.num(); i++) {
+                    console.log(` ${i.toString(16).padStart(2, '0')} ${ram[0x01FF - i].toString(2).padStart(8, '0')} (0x${ram[0x01FF - i].toString(16).padStart(4, '0')} ${ram[0x01FF - i].toString().padStart(3)})`)
+                }
+                continue;
+            } else if (i[0] == 'k'.charCodeAt(0)) {
+                const num = +(new TextDecoder().decode(i.slice(1, 7)).replaceAll('\0', ''));
+                if (Number.isNaN(num)) {
+                    console.log('NaN')
+                    break dbg;
+                }
+                skip = num;
+                console.log(`skipping ${num} cycles`)
+                break dbgl;
+            } else if (i[0] == 'r'.charCodeAt(0)) {
+                const num = i[2] ? parseInt(new TextDecoder().decode(i.slice(1, 7)).replace('\n', '').replaceAll('\0', ''), 16) : cpu.programCounter.num();
+                console.log(`set breakpoint on`, num.toString(16))
+                breakpoints.push(num)
+                continue;
+            } else if (i[0] == '?'.charCodeAt(0)) {
+                console.log(`b - break, exit
+i - inspect
+s - inspect stack
+c - continue
+k[NUM] - skip
+r[ADR] - breakpoint
+g[ADR] - goto, change PC
+I[INS] - breakpoint instruction
+:[ADDR]=[VAL] - set memory
+\\[ADDR] - get value
+m[ADDR] - set breakpoint on accessing that address`);
+                continue;
+            } else if (i[0] == 'g'.charCodeAt(0)) {
+                const num = i[2] ? parseInt(new TextDecoder().decode(i.slice(1, 7)).replace('\n', '').replaceAll('\0', ''), 16) : cpu.programCounter.num();
+                console.log(`PC set to`, num.toString(16))
+                cpu.programCounter.set(num)
+                continue;
+            } else if (i[0] == 'I'.charCodeAt(0)) {
+                const instr = new TextDecoder().decode(i.slice(1, 7)).replace('\n', '').replaceAll('\0', '')
+                console.log(`instruction breakpoint set on`, instr)
+                instBreakpoints.push(instr)
+                continue;
+            } else if (i[0] == 'c'.charCodeAt(0)) {
+                skip = -1
+                console.log(`continuing execution`)
+            } else if (i[0] == ':'.charCodeAt(0)) {
+                const instr = new TextDecoder().decode(i.slice(1, 15)).replace('\n', '').replaceAll('\0', '')
+                const match = [...instr.matchAll(/^([a-fA-F0-9]{1,4})=([a-fA-F0-9]{1,2})$/g)];
+                if (!match || !match[0]) {
+                    console.log('not matched, uhoh')
+                    continue;
+                }
+                const [_, addr, data] = match[0]
+                console.log(`set $${addr} to 0x${data}`)
+                ram[parseInt(addr, 16)] = parseInt(data, 16)
+                continue;
+            } else if (i[0] == '\\'.charCodeAt(0)) {
+                const num = parseInt(new TextDecoder().decode(i.slice(1, 7)).replace('\n', '').replaceAll('\0', ''), 16);
+                if (Number.isNaN(num))
+                    continue;
+                console.log(`${num.toString(16).padStart(4, '0')}: ${ram[num].toString(16).padStart(2, '0')}`)
+                continue;
+            } else if (i[0] == '\''.charCodeAt(0)) {
+                if (i[1] == '\n'.charCodeAt(0)) {
+                    ram[0x5000] = 0;
+                    ram[0x5001] = 0;
+                    console.log('reset')
+                    continue;
+                }
+                const num = parseInt(new TextDecoder().decode(i.slice(1, 3)).replace('\n', '').replaceAll('\0', ''), 16);
+                if (Number.isNaN(num))
+                    continue;
+                ram[0x5000] = num;
+                ram[0x5001] = 0x08;
+                console.log(`set $5000 to 0x${num.toString(16).padStart(2, '0')} and $5001 to 08`)
+                console.log(`set breakpoint to accessing address 5000`)
+                memBreakpoints.push(0x5000)
+            } else if (i[0] == 'm'.charCodeAt(0)) {
+                const num = parseInt(new TextDecoder().decode(i.slice(1, 7)).replace('\n', '').replaceAll('\0', ''), 16);
+                if (Number.isNaN(num)) {
+                    console.log('NaN')
+                    break dbg;
+                }
+                console.log(`set breakpoint to accessing address`, num.toString(16))
+                memBreakpoints.push(num)
+                continue;
+            }
+            break;
+        }
+    }
+    cpu.cycle();
+}
+
+console.log('end of execution\n\n')
+inspect()
+
+// console.debug('\nRAM:')
+Deno.writeFileSync('ram.bin', ram)
+// new Deno.Command('hexdump', {
+//     args: ['-C', 'ram.bin']
+// }).spawn()