diff options
author | WlodekM <[email protected]> | 2025-04-02 12:24:10 +0300 |
---|---|---|
committer | WlodekM <[email protected]> | 2025-04-02 12:24:10 +0300 |
commit | b9cd32d22d20f3fd5e44dd738d7e1bdbdeabc9e0 (patch) | |
tree | af89604aa85adbde67ba844686e035416446f7c8 | |
parent | 1027b91f357eaaa4793b9d16673b0ab662b37c7e (diff) |
big update
-rw-r--r-- | .gitignore | 1 | ||||
-rw-r--r-- | 65c02.ts | 220 | ||||
-rw-r--r-- | instructions.ts | 190 | ||||
-rw-r--r-- | instructions/ADC.ts | 9 | ||||
-rw-r--r-- | instructions/BRK.ts | 22 | ||||
-rw-r--r-- | instructions/CLC.ts | 13 | ||||
-rw-r--r-- | instructions/INC.ts | 71 | ||||
-rw-r--r-- | instructions/INX.ts | 15 | ||||
-rw-r--r-- | instructions/INY.ts | 15 | ||||
-rw-r--r-- | instructions/LDA.ts | 9 | ||||
-rw-r--r-- | opcode_matrix.json | 606 | ||||
-rw-r--r-- | runtime.ts | 79 | ||||
-rw-r--r-- | thing.s | 2 |
13 files changed, 1247 insertions, 5 deletions
diff --git a/.gitignore b/.gitignore index c73d5a5..36c5889 100644 --- a/.gitignore +++ b/.gitignore @@ -9,3 +9,4 @@ code.o thefile riscv pc-thingg +a.out diff --git a/65c02.ts b/65c02.ts index a7660b4..96207a3 100644 --- a/65c02.ts +++ b/65c02.ts @@ -1,8 +1,13 @@ -class BitField { +import opcodeMatrix from "./opcode_matrix.json" with { type: "json" }; + +export class BitField { bits: boolean[]; flip(bit: number) { this.bits[bit] = !this.bits[bit]; } + bit(bit: number) { + return this.bits[bit] + } setBit(bit: number, value: boolean) { this.bits[bit] = value; } @@ -26,6 +31,217 @@ class BitField { } } +export class Register<T=number> extends BitField { + get value(): number { + return this.num() + } + set value(value: number) { + this.set(value) + } + increment(): number { + this.set(this.num() + 1); + return this.num() + } + decrement(): number { + this.set(this.num() - 1); + return this.num() + } + constructor(bits: number) { + super(bits); + } +} + +export class Pin { + high: boolean = false; + HI() { + this.high = true + } + LO() { + this.high = false + } + constructor(init = false) { + this.high = init + } +} + +export class IO { + data: BitField = new BitField(8); + address: BitField = new BitField(16); + busEnable: Pin = new Pin(); + NMI: Pin = new Pin(); + ready: Pin = new Pin(true); + reset: Pin = new Pin(true); + readWrite: Pin = new Pin(); + interruptRequest: Pin = new Pin(); +} + +/** + * the shittiest 65c02 emulator ever + * + * not clock cycle accurate because fuck you + */ export default class The65c02 { - data: BitField = new BitField(8) + io: IO = new IO(); + //SECTION - register + programCounter: Register<16> = new Register(16); + regA: Register<8> = new Register(8); + regX: Register<8> = new Register(8); + regY: Register<8> = new Register(8); + + stackPointer: Register<8> = new Register(8); + status: Register<8> = new Register(8); + //!SECTION + //SECTION - status reg bits + get carry(): boolean {return this.status.bit(0)} + set carry(value:boolean) {this.status.setBit(0, value)} + get zero(): boolean {return this.status.bit(1)} + set zero(value:boolean) {this.status.setBit(1, value)} + get IRQBDisable(): boolean {return this.status.bit(2)} + set IRQBDisable(value:boolean) {this.status.setBit(2, value)} + get decimalMode(): boolean {return this.status.bit(3)} + set decimalMode(value:boolean) {this.status.setBit(3, value)} + get BRK(): boolean {return this.status.bit(4)} + set BRK(value:boolean) {this.status.setBit(4, value)} + // ...1... // + get overflow(): boolean {return this.status.bit(6)} + set overflow(value:boolean) {this.status.setBit(6, value)} + get negative(): boolean {return this.status.bit(7)} + set negative(value:boolean) {this.status.setBit(7, value)} + //!SECTION + read: () => void; + write: () => void; + readPC(): BitField { + this.io.address = this.programCounter; + this.read() + return this.io.data; + } + flagZN(num: number) { + this.negative = (num & 0x80) != 0; + this.zero = num == 0; + } + flagZCN(num: number) { + this.carry = num > 0xFF + this.negative = (num & 0x80) != 0; + this.zero = num == 0; + } + instructions: Record<string, (mode: string) => void> = {}; + cycle() { + if(!this.io.reset.high) { + // reset register thingies + this.IRQBDisable = true; + this.decimalMode = false; + this.BRK = true; + // get reset vector + let resetVector = 0; + this.io.address.set(0xFFFC); + this.read() + resetVector |= this.io.data.num(); + this.io.address.set(0xFFFD); + this.read() + resetVector |= this.io.data.num() << 8; + // move PC to RV + this.programCounter.set(resetVector) + } + this.io.address.set(this.programCounter.num()); + this.read(); + const instruction = this.io.data + .num() + .toString(16) + .padStart(2, '0') + .toLowerCase(); + const opm: Record<string, { mnemonic: string, mode: string }> = opcodeMatrix; + if (!opm[instruction]) + throw `not found ${instruction}` + if (!this.instructions[opm[instruction].mnemonic]) + throw `not implement, sowwy (${opm[instruction].mnemonic}.ts not found)`; + console.debug(opm[instruction].mnemonic, opm[instruction].mode) + this.instructions[opm[instruction].mnemonic].call(this, opm[instruction].mode); + } + //SECTION - utils + getZPAddr(): number { + this.programCounter.increment() + const zp = this.readPC().num() + return zp + } + getZPXAddr(): number { + this.programCounter.increment() + const zp = this.readPC().num() + return (this.regX.num() + zp) & 0xFF + } + getAbsoluteAddr(): number { + this.programCounter.increment() + const lo_abit = this.readPC().num() + this.programCounter.increment() + const hi_abit = this.readPC().num() + return (hi_abit << 8) | lo_abit + } + getAbsoluteXAddr(): number { + this.programCounter.increment() + const lo_abit = this.readPC().num() + this.programCounter.increment() + const hi_abit = this.readPC().num() + return this.regX.num() + ((hi_abit << 8) | lo_abit) + } + getAbsoluteYAddr(): number { + this.programCounter.increment() + const lo_abit = this.readPC().num() + this.programCounter.increment() + const hi_abit = this.readPC().num() + return this.regX.num() + ((hi_abit << 8) | lo_abit) + } + getIndirectXAddr(): number { + this.programCounter.increment() + const addr = this.readPC().num() + this.io.address.set((this.regX.num() + addr) & 0xFF) + const lo_abit = this.readPC().num() + this.io.address.set((this.regX.num() + addr + 1) & 0xFF) + const hi_abit = this.readPC().num() + return ((hi_abit << 8) | lo_abit) + } + getIndirectYAddr(): number { + this.programCounter.increment() + const addr = this.readPC().num() + this.io.address.set((addr) & 0xFF) + const lo_abit = this.readPC().num() + this.io.address.set((addr + 1) & 0xFF) + const hi_abit = this.readPC().num() + return ((hi_abit << 8) | lo_abit) + this.regY.num(); + } + getAddr(mode: string): number { + switch (mode) { + case 'immediate': + this.programCounter.increment() + this.programCounter.increment() + return this.programCounter.num() - 1 + case 'zero-page': + return this.getZPAddr() + case 'zero-page, X-indexed': + return this.getZPXAddr() + case 'absolute': + return this.getAbsoluteAddr() + case 'absolute, X-indexed': + return this.getAbsoluteXAddr() + case 'absolute, Y-indexed': + return this.getAbsoluteYAddr() + case 'indirect, Y-indexed': + return this.getIndirectYAddr() + case 'X-indexed, indirect': + return this.getIndirectXAddr() + + default: + throw 'unknown mode '+mode + } + } + //!SECTION + constructor (read: () => void, write: () => void) { + this.read = read; + this.write = write; + } + async loadInstructions() { + const dir = Deno.readDirSync('instructions') + for (const entry of dir) { + this.instructions[entry.name.replace('.ts', '')] + = (await import(`./instructions/${entry.name}`)).default + } + } } \ No newline at end of file diff --git a/instructions.ts b/instructions.ts index 4d23d23..9f30620 100644 --- a/instructions.ts +++ b/instructions.ts @@ -1,3 +1,187 @@ -export const instructions: Record<string, number> = { - '': 0x01 -} \ No newline at end of file +export const instructions: Record<number, string> = { + 1: "ADC", // ADd memory to accumulator with Carry + 2: "AND", // "AND" memory with accumulator + 3: "ASL", // Arithmetic Shift one bit Left, memory or accumulator + // 4. BBR Branch on Bit Reset + // 5. BBS Branch of Bit Set + 6: "BCC", // Branch on Carry Clear (Pc=0) + 7: "BCS", // Branch on Carry Set (Pc=1) + 8: "BEQ", // Branch if EQual (Pz=1) + 9: "BIT", // BIt Test + 10: "BMI", // Branch if result MInus (Pn=1) + 11: "BNE", // Branch if Not Equal (Pz=0) + 12: "BPL", // Branch if result PLus (Pn=0) + // 13. BRA // BRanch Always + 14: "BRK", // BReaK instruction + 15: "BVC", // Branch on oVerflow Clear (Pv=0) + 16: "BVS", // Branch on oVerflow Set (Pv=1) + 17: "CLC", // CLear Cary flag + 18: "CLD", // CLear Decimal mode + 19: "CLI", // CLear Interrupt disable bit + 20: "CLV", // CLear oVerflow flag + 21: "CMP", // CoMPare memory and accumulator + 22: "CPX", // ComPare memory and X register + 23: "CPY", // ComPare memory and Y register + 24: "DEC", // DECrement memory or accumulate by one + 25: "DEX", // DEcrement X by one + 26: "DEY", // DEcrement Y by one + 27: "EOR", // "Exclusive OR" memory with accumulate + 28: "INC", // INCrement memory or accumulate by one + 29: "INX", // INcrement X register by one + 30: "INY", // INcrement Y register by one + 31: "JMP", // JuMP to new location + 32: "JSR", // Jump to new location Saving Return (Jump to SubRoutine) + 33: "LDA", // LoaD Accumulator with memory + 34: "LDX", // LoaD the X register with memory + 35: "LDY", // LoaD the Y register with memory + 36: "LSR", // Logical Shift one bit Right memory or accumulator + 37: "NOP", // No OPeration + 38: "ORA", // "OR" memory with Accumulator + 39: "PHA", // PusH Accumulator on stack + 40: "PHP", // PusH Processor status on stack + // 41. PHX PusH X register on stack + // 42. PHY PusH Y register on stack + 43: "PLA", // PuLl Accumulator from stack + 44: "PLP", // PuLl Processor status from stack + // 45. PLX PuLl X register from stack + // 46. PLY PuLl Y register from stack + // 47. RMB Reset Memory Bit + 48: "ROL", // ROtate one bit Left memory or accumulator + 49: "ROR", // ROtate one bit Right memory or accumulator + 50: "RTI", // ReTurn from Interrupt + 51: "RTS", // ReTurn from Subroutine + 52: "SBC", // SuBtract memory from accumulator with borrow (Carry bit) + 53: "SEC", // SEt Carry + 54: "SED", // SEt Decimal mode + 55: "SEI", // SEt Interrupt disable status + // 56. SMB Set Memory Bit + 57: "STA", // STore Accumulator in memory + // 58. STP SToP mode + 59: "STX", // STore the X register in memory + 60: "STY", // STore the Y register in memory + // 61. STZ STore Zero in memory + 62: "TAX", // Transfer the Accumulator to the X register + 63: "TAY", // Transfer the Accumulator to the Y register + // 64. TRB Test and Reset memory Bit + // 65. TSB Test and Set memory Bit + 66: "TSX", // Transfer the Stack pointer to the X register + 67: "TXA", // Transfer the X register to the Accumulator + 68: "TXS", // Transfer the X register to the Stack pointer register + 69: "TYA", // Transfer Y register to the Accumulator + // 70. WAI WAit for Interrupt +} + +export const instructionIds = { + /** ADd memory to accumulator with Carry */ + "ADC": 1, + /** "AND" memory with accumulator */ + "AND": 2, + /** Arithmetic Shift one bit Left, memory or accumulator */ + "ASL": 3, + /** Branch on Carry Clear (Pc=0) */ + "BCC": 6, + /** Branch on Carry Set (Pc=1) */ + "BCS": 7, + /** Branch if EQual (Pz=1) */ + "BEQ": 8, + /** BIt Test */ + "BIT": 9, + /** Branch if result MInus (Pn=1) */ + "BMI": 10, + /** Branch if Not Equal (Pz=0) */ + "BNE": 11, + /** Branch if result PLus (Pn=0) */ + "BPL": 12, + /** BReaK instruction */ + "BRK": 14, + /** Branch on oVerflow Clear (Pv=0) */ + "BVC": 15, + /** Branch on oVerflow Set (Pv=1) */ + "BVS": 16, + /** CLear Cary flag */ + "CLC": 17, + /** CLear Decimal mode */ + "CLD": 18, + /** CLear Interrupt disable bit */ + "CLI": 19, + /** CLear oVerflow flag */ + "CLV": 20, + /** CoMPare memory and accumulator */ + "CMP": 21, + /** ComPare memory and X register */ + "CPX": 22, + /** ComPare memory and Y register */ + "CPY": 23, + /** DECrement memory or accumulate by one */ + "DEC": 24, + /** DEcrement X by one */ + "DEX": 25, + /** DEcrement Y by one */ + "DEY": 26, + /** "Exclusive OR" memory with accumulate */ + "EOR": 27, + /** INCrement memory or accumulate by one */ + "INC": 28, + /** INcrement X register by one */ + "INX": 29, + /** INcrement Y register by one */ + "INY": 30, + /** JuMP to new location */ + "JMP": 31, + /** Jump to new location Saving Return (Jump to SubRoutine) */ + "JSR": 32, + /** LoaD Accumulator with memory */ + "LDA": 33, + /** LoaD the X register with memory */ + "LDX": 34, + /** LoaD the Y register with memory */ + "LDY": 35, + /** Logical Shift one bit Right memory or accumulator */ + "LSR": 36, + /** No OPeration */ + "NOP": 37, + /** "OR" memory with Accumulator */ + "ORA": 38, + /** PusH Accumulator on stack */ + "PHA": 39, + /** PusH Processor status on stack */ + "PHP": 40, + /** PuLl Accumulator from stack */ + "PLA": 43, + /** PuLl Processor status from stack */ + "PLP": 44, + /** ROtate one bit Left memory or accumulator */ + "ROL": 48, + /** ROtate one bit Right memory or accumulator */ + "ROR": 49, + /** ReTurn from Interrupt */ + "RTI": 50, + /** ReTurn from Subroutine */ + "RTS": 51, + /** SuBtract memory from accumulator with borrow (Carry bit) */ + "SBC": 52, + /** SEt Carry */ + "SEC": 53, + /** SEt Decimal mode */ + "SED": 54, + /** SEt Interrupt disable status */ + "SEI": 55, + /** STore Accumulator in memory */ + "STA": 57, + /** STore the X register in memory */ + "STX": 59, + /** STore the Y register in memory */ + "STY": 60, + /** Transfer the Accumulator to the X register */ + "TAX": 62, + /** Transfer the Accumulator to the Y register */ + "TAY": 63, + /** Transfer the Stack pointer to the X register */ + "TSX": 66, + /** Transfer the X register to the Accumulator */ + "TXA": 67, + /** Transfer the X register to the Stack pointer register */ + "TXS": 68, + /** Transfer Y register to the Accumulator */ + "TYA": 69, +} diff --git a/instructions/ADC.ts b/instructions/ADC.ts new file mode 100644 index 0000000..c8163d0 --- /dev/null +++ b/instructions/ADC.ts @@ -0,0 +1,9 @@ +import type The65c02 from "../65c02.ts"; + +export default function (this: The65c02, mode: string) { + const addr = this.getAddr(mode); + this.io.address.set(addr); + this.read(); + this.flagZN(this.io.data.num()) + this.regA.set(this.io.data.num()) +} \ No newline at end of file diff --git a/instructions/BRK.ts b/instructions/BRK.ts new file mode 100644 index 0000000..99e4072 --- /dev/null +++ b/instructions/BRK.ts @@ -0,0 +1,22 @@ +import type The65c02 from "../65c02.ts"; + +export default function (this: The65c02, mode: string) { + switch (mode) { + case 'implied': + this.io.interruptRequest.HI() + //TODO: push shit onto stack + this.io.data.set(this.programCounter.num() & 0x00FF) + this.io.address.set(0xFFFF) + this.write() + this.io.data.set(this.programCounter.num() & 0xFF00) + this.io.address.set(0xFFFE) + this.write() + this.BRK = true + this.programCounter.increment(); + this.programCounter.increment(); + break; + + default: + throw 'wha'; + } +} \ No newline at end of file diff --git a/instructions/CLC.ts b/instructions/CLC.ts new file mode 100644 index 0000000..d14b2f4 --- /dev/null +++ b/instructions/CLC.ts @@ -0,0 +1,13 @@ +import type The65c02 from "../65c02.ts"; + +export default function (this: The65c02, mode: string) { + switch (mode) { + case 'implied': + this.carry = false; + this.programCounter.increment(); + break; + + default: + throw 'wha'; + } +} \ No newline at end of file diff --git a/instructions/INC.ts b/instructions/INC.ts new file mode 100644 index 0000000..971abd3 --- /dev/null +++ b/instructions/INC.ts @@ -0,0 +1,71 @@ +// deno-lint-ignore-file no-case-declarations +import type The65c02 from "../65c02.ts"; + +export default function (this: The65c02, mode: string) { + switch (mode) { + case 'zero-page': + // set PC to where the ZP address is + this.programCounter.increment() + const zpa = this.readPC() + this.io.address.set(zpa.num()); + this.read(); + let v = this.io.data.num(); + v++ + v &= 0xFF + this.io.data.set(v) + this.write() + this.flagZN(v) + this.programCounter.increment() + break; + + case 'zero-page, X-indexed': + // set PC to where the ZP address is + this.programCounter.increment() + const zpax = this.readPC() + this.io.address.set(zpax.num() + this.regX.num()); + this.read(); + let vzx = this.io.data.num(); + vzx++ + vzx &= 0xFF + this.io.data.set(vzx) + this.write() + this.flagZN(vzx) + this.programCounter.increment() + break; + + case 'absolute': + // skip over the opcode + this.programCounter.increment() + const lo_abit = this.readPC().num() + this.programCounter.increment() + const hi_abit = this.readPC().num() + this.io.address.set((hi_abit << 8) | lo_abit) + this.read() + let va = this.io.data.num(); + va++ + va &= 0xFF + this.io.data.set(va) + this.write() + this.programCounter.increment() + break; + + case 'absolute, X-indexed': + // skip over the opcode + this.programCounter.increment() + const lo_axbit = this.readPC().num() + this.programCounter.increment() + const hi_axbit = this.readPC().num() + this.io.address.set(((hi_axbit << 8) | lo_axbit) + this.regX.num()) + this.read() + let vax = this.io.data.num(); + vax++ + vax &= 0xFF + this.io.data.set(vax) + this.write() + this.programCounter.increment() + break; + + default: + throw 'wha'; + } +} \ No newline at end of file diff --git a/instructions/INX.ts b/instructions/INX.ts new file mode 100644 index 0000000..9c1cc1b --- /dev/null +++ b/instructions/INX.ts @@ -0,0 +1,15 @@ +import type The65c02 from "../65c02.ts"; + +export default function (this: The65c02, mode: string) { + switch (mode) { + case 'implied': + this.regX.increment() + this.negative = this.regX.bit(7); + this.zero = this.regX.num() == 0; + this.programCounter.increment(); + break; + + default: + throw 'wha'; + } +} \ No newline at end of file diff --git a/instructions/INY.ts b/instructions/INY.ts new file mode 100644 index 0000000..566c6d4 --- /dev/null +++ b/instructions/INY.ts @@ -0,0 +1,15 @@ +import type The65c02 from "../65c02.ts"; + +export default function (this: The65c02, mode: string) { + switch (mode) { + case 'implied': + this.regY.increment() + this.negative = this.regY.bit(7); + this.zero = this.regY.num() == 0; + this.programCounter.increment(); + break; + + default: + throw 'wha'; + } +} \ No newline at end of file diff --git a/instructions/LDA.ts b/instructions/LDA.ts new file mode 100644 index 0000000..c8163d0 --- /dev/null +++ b/instructions/LDA.ts @@ -0,0 +1,9 @@ +import type The65c02 from "../65c02.ts"; + +export default function (this: The65c02, mode: string) { + const addr = this.getAddr(mode); + this.io.address.set(addr); + this.read(); + this.flagZN(this.io.data.num()) + this.regA.set(this.io.data.num()) +} \ No newline at end of file diff --git a/opcode_matrix.json b/opcode_matrix.json new file mode 100644 index 0000000..be27986 --- /dev/null +++ b/opcode_matrix.json @@ -0,0 +1,606 @@ +{ + "10": { + "mnemonic": "BPL", + "mode": "relative" + }, + "11": { + "mnemonic": "ORA", + "mode": "indirect, Y-indexed" + }, + "15": { + "mnemonic": "ORA", + "mode": "zero-page, X-indexed" + }, + "16": { + "mnemonic": "ASL", + "mode": "zero-page, X-indexed" + }, + "18": { + "mnemonic": "CLC", + "mode": "implied" + }, + "19": { + "mnemonic": "ORA", + "mode": "absolute, Y-indexed" + }, + "20": { + "mnemonic": "JSR", + "mode": "absolute" + }, + "21": { + "mnemonic": "AND", + "mode": "X-indexed, indirect" + }, + "24": { + "mnemonic": "BIT", + "mode": "zero-page" + }, + "25": { + "mnemonic": "AND", + "mode": "zero-page" + }, + "26": { + "mnemonic": "ROL", + "mode": "zero-page" + }, + "28": { + "mnemonic": "PLP", + "mode": "implied" + }, + "29": { + "mnemonic": "AND", + "mode": "immediate" + }, + "30": { + "mnemonic": "BMI", + "mode": "relative" + }, + "31": { + "mnemonic": "AND", + "mode": "indirect, Y-indexed" + }, + "35": { + "mnemonic": "AND", + "mode": "zero-page, X-indexed" + }, + "36": { + "mnemonic": "ROL", + "mode": "zero-page, X-indexed" + }, + "38": { + "mnemonic": "SEC", + "mode": "implied" + }, + "39": { + "mnemonic": "AND", + "mode": "absolute, Y-indexed" + }, + "40": { + "mnemonic": "RTI", + "mode": "implied" + }, + "41": { + "mnemonic": "EOR", + "mode": "X-indexed, indirect" + }, + "45": { + "mnemonic": "EOR", + "mode": "zero-page" + }, + "46": { + "mnemonic": "LSR", + "mode": "zero-page" + }, + "48": { + "mnemonic": "PHA", + "mode": "implied" + }, + "49": { + "mnemonic": "EOR", + "mode": "immediate" + }, + "50": { + "mnemonic": "BVC", + "mode": "relative" + }, + "51": { + "mnemonic": "EOR", + "mode": "indirect, Y-indexed" + }, + "55": { + "mnemonic": "EOR", + "mode": "zero-page, X-indexed" + }, + "56": { + "mnemonic": "LSR", + "mode": "zero-page, X-indexed" + }, + "58": { + "mnemonic": "CLI", + "mode": "implied" + }, + "59": { + "mnemonic": "EOR", + "mode": "absolute, Y-indexed" + }, + "60": { + "mnemonic": "RTS", + "mode": "implied" + }, + "61": { + "mnemonic": "ADC", + "mode": "X-indexed, indirect" + }, + "65": { + "mnemonic": "ADC", + "mode": "zero-page" + }, + "66": { + "mnemonic": "ROR", + "mode": "zero-page" + }, + "68": { + "mnemonic": "PLA", + "mode": "implied" + }, + "69": { + "mnemonic": "ADC", + "mode": "immediate" + }, + "70": { + "mnemonic": "BVS", + "mode": "relative" + }, + "71": { + "mnemonic": "ADC", + "mode": "indirect, Y-indexed" + }, + "75": { + "mnemonic": "ADC", + "mode": "zero-page, X-indexed" + }, + "76": { + "mnemonic": "ROR", + "mode": "zero-page, X-indexed" + }, + "78": { + "mnemonic": "SEI", + "mode": "implied" + }, + "79": { + "mnemonic": "ADC", + "mode": "absolute, Y-indexed" + }, + "81": { + "mnemonic": "STA", + "mode": "X-indexed, indirect" + }, + "84": { + "mnemonic": "STY", + "mode": "zero-page" + }, + "85": { + "mnemonic": "STA", + "mode": "zero-page" + }, + "86": { + "mnemonic": "STX", + "mode": "zero-page" + }, + "88": { + "mnemonic": "DEY", + "mode": "implied" + }, + "90": { + "mnemonic": "BCC", + "mode": "relative" + }, + "91": { + "mnemonic": "STA", + "mode": "indirect, Y-indexed" + }, + "94": { + "mnemonic": "STY", + "mode": "zero-page, X-indexed" + }, + "95": { + "mnemonic": "STA; InsItr", + "mode": "zero-page, X-indexed" + }, + "96": { + "mnemonic": "STX", + "mode": "zero-page, Y-indexed" + }, + "98": { + "mnemonic": "TYA", + "mode": "implied" + }, + "99": { + "mnemonic": "STA", + "mode": "absolute, Y-indexed" + }, + "00": { + "mnemonic": "BRK", + "mode": "implied" + }, + "01": { + "mnemonic": "ORA", + "mode": "X-indexed, indirect" + }, + "05": { + "mnemonic": "ORA", + "mode": "zero-page" + }, + "06": { + "mnemonic": "ASL", + "mode": "zero-page" + }, + "08": { + "mnemonic": "PHP", + "mode": "implied" + }, + "09": { + "mnemonic": "ORA", + "mode": "immediate" + }, + "0a": { + "mnemonic": "ASL", + "mode": "accumulator (implied)" + }, + "0d": { + "mnemonic": "ORA", + "mode": "absolute" + }, + "0e": { + "mnemonic": "ASL", + "mode": "absolute" + }, + "1d": { + "mnemonic": "ORA", + "mode": "absolute, X-indexed" + }, + "1e": { + "mnemonic": "ASL", + "mode": "absolute, X-indexed" + }, + "2a": { + "mnemonic": "ROL", + "mode": "accumulator (implied)" + }, + "2c": { + "mnemonic": "BIT", + "mode": "absolute" + }, + "2d": { + "mnemonic": "AND", + "mode": "absolute" + }, + "2e": { + "mnemonic": "ROL", + "mode": "absolute" + }, + "3d": { + "mnemonic": "AND", + "mode": "absolute, X-indexed" + }, + "3e": { + "mnemonic": "ROL", + "mode": "absolute, X-indexed" + }, + "4a": { + "mnemonic": "LSR", + "mode": "accumulator (implied)" + }, + "4c": { + "mnemonic": "JMP", + "mode": "absolute" + }, + "4d": { + "mnemonic": "EOR", + "mode": "absolute" + }, + "4e": { + "mnemonic": "LSR", + "mode": "absolute" + }, + "5d": { + "mnemonic": "EOR", + "mode": "absolute, X-indexed" + }, + "5e": { + "mnemonic": "LSR", + "mode": "absolute, X-indexed" + }, + "6a": { + "mnemonic": "ROR", + "mode": "accumulator (implied)" + }, + "6c": { + "mnemonic": "JMP", + "mode": "indirect" + }, + "6d": { + "mnemonic": "ADC", + "mode": "absolute" + }, + "6e": { + "mnemonic": "ROR", + "mode": "absolute" + }, + "7d": { + "mnemonic": "ADC", + "mode": "absolute, X-indexed" + }, + "7e": { + "mnemonic": "ROR", + "mode": "absolute, X-indexed" + }, + "8a": { + "mnemonic": "TXA", + "mode": "implied" + }, + "8c": { + "mnemonic": "STY", + "mode": "absolute" + }, + "8d": { + "mnemonic": "STA", + "mode": "absolute" + }, + "8e": { + "mnemonic": "STX", + "mode": "absolute" + }, + "9a": { + "mnemonic": "TXS", + "mode": "implied" + }, + "9d": { + "mnemonic": "STA", + "mode": "absolute, X-indexed" + }, + "a0": { + "mnemonic": "LDY", + "mode": "immediate" + }, + "a1": { + "mnemonic": "LDA", + "mode": "X-indexed, indirect" + }, + "a2": { + "mnemonic": "LDX", + "mode": "immediate" + }, + "a4": { + "mnemonic": "LDY", + "mode": "zero-page" + }, + "a5": { + "mnemonic": "LDA", + "mode": "zero-page" + }, + "a6": { + "mnemonic": "LDX", + "mode": "zero-page" + }, + "a8": { + "mnemonic": "TAY", + "mode": "implied" + }, + "a9": { + "mnemonic": "LDA", + "mode": "immediate" + }, + "aa": { + "mnemonic": "TAX", + "mode": "implied" + }, + "ac": { + "mnemonic": "LDY", + "mode": "absolute" + }, + "ad": { + "mnemonic": "LDA", + "mode": "absolute" + }, + "ae": { + "mnemonic": "LDX", + "mode": "absolute" + }, + "b0": { + "mnemonic": "BCS", + "mode": "relative" + }, + "b1": { + "mnemonic": "LDA", + "mode": "indirect, Y-indexed" + }, + "b4": { + "mnemonic": "LDY", + "mode": "zero-page, X-indexed" + }, + "b5": { + "mnemonic": "LDA", + "mode": "zero-page, X-indexed" + }, + "b6": { + "mnemonic": "LDX", + "mode": "zero-page, Y-indexed" + }, + "b8": { + "mnemonic": "CLV", + "mode": "implied" + }, + "b9": { + "mnemonic": "LDA", + "mode": "absolute, Y-indexed" + }, + "ba": { + "mnemonic": "TSX", + "mode": "implied" + }, + "bc": { + "mnemonic": "LDY", + "mode": "absolute, X-indexed" + }, + "bd": { + "mnemonic": "LDA", + "mode": "absolute, X-indexed" + }, + "be": { + "mnemonic": "LDX", + "mode": "absolute, Y-indexed" + }, + "c0": { + "mnemonic": "CPY", + "mode": "immediate" + }, + "c1": { + "mnemonic": "CMP", + "mode": "X-indexed, indirect" + }, + "c4": { + "mnemonic": "CPY", + "mode": "zero-page" + }, + "c5": { + "mnemonic": "CMP", + "mode": "zero-page" + }, + "c6": { + "mnemonic": "DEC", + "mode": "zero-page" + }, + "c8": { + "mnemonic": "INY", + "mode": "implied" + }, + "c9": { + "mnemonic": "CMP", + "mode": "immediate" + }, + "ca": { + "mnemonic": "DEX", + "mode": "implied" + }, + "cc": { + "mnemonic": "CPY", + "mode": "absolute" + }, + "cd": { + "mnemonic": "CMP", + "mode": "absolute" + }, + "ce": { + "mnemonic": "DEC", + "mode": "absolute" + }, + "d0": { + "mnemonic": "BNE", + "mode": "relative" + }, + "d1": { + "mnemonic": "CMP", + "mode": "indirect, Y-indexed" + }, + "d5": { + "mnemonic": "CMP", + "mode": "zero-page, X-indexed" + }, + "d6": { + "mnemonic": "DEC", + "mode": "zero-page, X-indexed" + }, + "d8": { + "mnemonic": "CLD", + "mode": "implied" + }, + "d9": { + "mnemonic": "CMP", + "mode": "absolute, Y-indexed" + }, + "dd": { + "mnemonic": "CMP", + "mode": "absolute, X-indexed" + }, + "de": { + "mnemonic": "DEC", + "mode": "absolute, X-indexed" + }, + "e0": { + "mnemonic": "CPX", + "mode": "immediate" + }, + "e1": { + "mnemonic": "SBC", + "mode": "X-indexed, indirect" + }, + "e4": { + "mnemonic": "CPX", + "mode": "zero-page" + }, + "e5": { + "mnemonic": "SBC", + "mode": "zero-page" + }, + "e6": { + "mnemonic": "INC", + "mode": "zero-page" + }, + "e8": { + "mnemonic": "INX", + "mode": "implied" + }, + "e9": { + "mnemonic": "SBC", + "mode": "immediate" + }, + "ea": { + "mnemonic": "NOP", + "mode": "implied" + }, + "ec": { + "mnemonic": "CPX", + "mode": "absolute" + }, + "ed": { + "mnemonic": "SBC", + "mode": "absolute" + }, + "ee": { + "mnemonic": "INC", + "mode": "absolute" + }, + "f0": { + "mnemonic": "BEQ", + "mode": "relative" + }, + "f1": { + "mnemonic": "SBC", + "mode": "indirect, Y-indexed" + }, + "f5": { + "mnemonic": "SBC", + "mode": "zero-page, X-indexed" + }, + "f6": { + "mnemonic": "INC", + "mode": "zero-page, X-indexed" + }, + "f8": { + "mnemonic": "SED", + "mode": "implied" + }, + "f9": { + "mnemonic": "SBC", + "mode": "absolute, Y-indexed" + }, + "fd": { + "mnemonic": "SBC", + "mode": "absolute, X-indexed" + }, + "fe": { + "mnemonic": "INC", + "mode": "absolute, X-indexed" + } +} \ No newline at end of file diff --git a/runtime.ts b/runtime.ts new file mode 100644 index 0000000..4f6435d --- /dev/null +++ b/runtime.ts @@ -0,0 +1,79 @@ +import The65c02, { BitField, Pin } from "./65c02.ts"; +// import { instructionIds } from "./instructions.ts"; +import opcodeMatrix from "./opcode_matrix.json" with { type: "json" }; + +function instruction(name: string | TemplateStringsArray, mode: string = 'implied'): number { + const goog = (Object.entries(opcodeMatrix).find(([_, v]) => + v.mnemonic == name && v.mode == mode) ?? [])[0]; + if (!goog) + throw `unknown instruction (${name}-${mode})` + return parseInt(goog, 16) +} + +// the thing used for ram +const ram = new Uint8Array(2**16); + +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) { + // write + ram[this.io.address.num()] = this.io.data.num() +}) + +await cpu.loadInstructions() + +// mem address $0000 +ram[0xFFFC] = 0x00 +ram[0xFFFD] = 0x00 + +//TODO: read code from file +// const code = [ +// // nop +// instruction`CLC`, +// // instruction`CLD`, +// instruction('INC', 'zero-page'), +// 0x21, +// instruction`BRK`, +// ] + +const code = Deno.readFileSync('a.out') + +// write code to ram before execution +for (let offset = 0; offset < code.length; offset++) { + const byte = code[offset]; + ram[offset] = byte; +} + +// pull RESB low to reset the 65c02 +cpu.io.reset.LO() +cpu.cycle() + +//the cpu reset, pull RESB high and start execution! +cpu.io.reset.HI() + +while (!cpu.io.interruptRequest.high) { + cpu.cycle() +} + +console.log('end of execution\n\nIO 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('\nregisters:') +console.log(` A: ${cpu.regA.bits.map(k=>+k).join('')} (0x${cpu.regA.num().toString(16)})`) +console.log(` X: ${cpu.regX.bits.map(k=>+k).join('')} (0x${cpu.regX.num().toString(16)})`) +console.log(` Y: ${cpu.regY.bits.map(k=>+k).join('')} (0x${cpu.regY.num().toString(16)})`) + +console.debug('\nRAM:') +Deno.writeFileSync('ram.bin', ram) +new Deno.Command('hexdump', { + args: ['-C', 'ram.bin'] +}).spawn() diff --git a/thing.s b/thing.s new file mode 100644 index 0000000..d2e3f9d --- /dev/null +++ b/thing.s @@ -0,0 +1,2 @@ + lda #$ff + brk \ No newline at end of file |