summary refs log tree commit diff
path: root/65c02.ts
diff options
context:
space:
mode:
Diffstat (limited to '65c02.ts')
-rw-r--r--65c02.ts220
1 files changed, 218 insertions, 2 deletions
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