import { Addressing , OpType , kInstTable } from './inst' ; import { IBus } from './ibus' ; import { Util } from '../../util/util' ; import { Address , Byte , Word } from '../types' ; export const enum IrqType { APU , EXTERNAL , FDS } const CARRY_BIT = 0 ; const ZERO_BIT = 1 ; const IRQBLK_BIT = 2 ; const DECIMAL_BIT = 3 ; const BREAK_BIT = 4 ; const RESERVED_BIT = 5 ; const OVERFLOW_BIT = 6 ; const NEGATIVE_BIT = 7 ; const BREAK_FLAG = 1 << BREAK_BIT ; const RESERVED_FLAG = 1 << RESERVED_BIT ; const VEC_NMI = 0xfffa ; const VEC_RESET = 0xfffc ; const VEC_IRQ = 0xfffe ; type Bit = 0 | 1 ; function bit ( x , shift ) { return ( ( x >> shift ) & 1 ) as Bit ; } interface Regs { a : Byte ; x : Byte ; y : Byte ; s : Byte ; p : Byte ; pc : Address ; } export class Cpu { private a : Byte ; private x : Byte ; private y : Byte ; private s : Byte ; private negative : Bit = 0 ; private overflow : Bit = 0 ; private reservedFlag : Bit = 0 ; private breakmode : Bit = 0 ; private decimal : Bit = 0 ; private irqBlocked : Bit = 0 ; private zero : Bit = 0 ; private carry : Bit = 0 ; private pc : Address ; private irqRequest = 0 ; private breakPoints = new Set < Address | string > ( ) ; private watchRead = new Set < Address > ( ) ; private watchWrite = new Set < Address > ( ) ; private paused = false ; constructor ( private bus ) { this . a = this . x = this . y = this . s = 0 ; this . pc = 0 ; this . negative = this . overflow = this . decimal = this . zero = this . carry = 0 ; this . irqBlocked = this . breakmode = this . reservedFlag = 1 ; } public reset ( ) { this . s = ( this . s - 3 ) & 0xff ; this . pc = this . read16 ( VEC_RESET ) ; this . irqBlocked = 1 ; } public getRegs ( ) { return { a : this . a , x : this . x , y : this . y , s : this . s , p : this . getStatusReg ( ) , pc : this . pc , } ; } public save ( ) { return this . getRegs ( ) ; } public load ( saveData ) { this . a = saveData . a ; this . x = saveData . x ; this . y = saveData . y ; this . s = saveData . s ; this . pc = saveData . pc ; this . setStatusReg ( saveData . p ) ; } public deleteAllBreakPoints ( ) { this . breakPoints . clear ( ) ; this . watchRead . clear ( ) ; this . watchWrite . clear ( ) ; } public pause ( value ) { this . paused = value ; } public isPaused ( ) { return this . paused ; } public nmi ( ) { const vector = this . read16 ( VEC_NMI ) ; if ( this . breakPoints . has ( 'nmi' ) ) { this . paused = true ; console . warn ( `paused because NMI: ${ Util . hex ( this . pc , 4 ) }, ${ Util . hex ( vector , 4 ) }` ) ; } this . push16 ( this . pc ) ; this . push ( this . getStatusReg ( ) & ~ BREAK_FLAG ) ; this . pc = vector ; this . irqBlocked = 1 ; } public requestIrq ( type ) { this . irqRequest |= 1 << type ; } public clearIrqRequest ( type ) { this . irqRequest &= ~ ( 1 << type ) ; } public step ( ) { if ( this . irqRequest !== 0 && this . irqBlocked === 0 ) { this . irqRequest = 0 ; this . handleIrq ( ) ; } let pc = this . pc ; const op = this . read8 ( pc ++ ) ; const inst = kInstTable [ op ] ; if ( inst . opType === OpType . UNKNOWN ) { console . error ( `Unknonwn OPCODE, ${ Util . hex ( this . pc - 1 , 4 ) }: ${ Util . hex ( op , 2 ) }` ) ; this . paused = true ; return 0 ; } this . pc += inst . bytes ; const adr = this . getAdr ( pc , inst . addressing ) ; let cycle = inst . cycle ; switch ( inst . opType ) { default : break ; case OpType . NOP : break ; case OpType . LDA : this . a = this . read8 ( adr ) ; this . setNZFlag ( this . a ) ; break ; case OpType . STA : this . write8 ( adr , this . a ) ; break ; case OpType . LDX : this . x = this . read8 ( adr ) ; this . setNZFlag ( this . x ) ; break ; case OpType . STX : this . write8 ( adr , this . x ) ; break ; case OpType . LDY : this . y = this . read8 ( adr ) ; this . setNZFlag ( this . y ) ; break ; case OpType . STY : this . write8 ( adr , this . y ) ; break ; case OpType . TAX : this . x = this . a ; this . setNZFlag ( this . x ) ; break ; case OpType . TAY : this . y = this . a ; this . setNZFlag ( this . y ) ; break ; case OpType . TXA : this . a = this . x ; this . setNZFlag ( this . a ) ; break ; case OpType . TYA : this . a = this . y ; this . setNZFlag ( this . a ) ; break ; case OpType . TXS : this . s = this . x ; break ; case OpType . TSX : this . x = this . s ; this . setNZFlag ( this . x ) ; break ; case OpType . ADC : { const carry = this . carry ; const operand = this . read8 ( adr ) ; const result = this . a + operand + carry ; const overflow = ( ( this . a ^ result ) & ( operand ^ result ) & 0x80 ) !== 0 ; this . a = result & 0xff ; this . setNZCFlag ( this . a , result >= 0x0100 ) ; this . setOverFlow ( overflow ) ; } break ; case OpType . SBC : { const carry = this . carry ; const operand = 255 - this . read8 ( adr ) ; const result = this . a + operand + carry ; const overflow = ( ( this . a ^ result ) & ( operand ^ result ) & 0x80 ) !== 0 ; this . a = result & 0xff ; this . setNZCFlag ( this . a , result >= 0x0100 ) ; this . setOverFlow ( overflow ) ; } break ; case OpType . INX : this . x = ( this . x + 1 ) & 0xff ; this . setNZFlag ( this . x ) ; break ; case OpType . INY : this . y = ( this . y + 1 ) & 0xff ; this . setNZFlag ( this . y ) ; break ; case OpType . INC : { const value = ( this . read8 ( adr ) + 1 ) & 0xff ; this . write8 ( adr , value ) ; this . setNZFlag ( value ) ; } break ; case OpType . DEX : this . x = ( this . x - 1 ) & 0xff ; this . setNZFlag ( this . x ) ; break ; case OpType . DEY : this . y = ( this . y - 1 ) & 0xff ; this . setNZFlag ( this . y ) ; break ; case OpType . DEC : { const value = ( this . read8 ( adr ) - 1 ) & 0xff ; this . write8 ( adr , value ) ; this . setNZFlag ( value ) ; } break ; case OpType . AND : { const value = this . read8 ( adr ) ; this . a &= value ; this . setNZFlag ( this . a ) ; } break ; case OpType . ORA : { const value = this . read8 ( adr ) ; this . a |= value ; this . setNZFlag ( this . a ) ; } break ; case OpType . EOR : { const value = this . read8 ( adr ) ; this . a ^= value ; this . setNZFlag ( this . a ) ; } break ; case OpType . ROL : { const isAcc = inst . addressing === Addressing . ACCUMULATOR ; const value = isAcc ? this . a : this . read8 ( adr ) ; const oldCarry = this . carry ; const newCarry = value >= 0x80 ; const newValue = ( ( value << 1 ) | oldCarry ) & 0xff ; if ( isAcc ) this . a = newValue ; else this . write8 ( adr , newValue ) ; this . setNZCFlag ( newValue , newCarry ) ; } break ; case OpType . ROR : { const isAcc = inst . addressing === Addressing . ACCUMULATOR ; const value = isAcc ? this . a : this . read8 ( adr ) ; const oldCarry = this . carry ; const newCarry = ( value & 0x01 ) !== 0 ; const newValue = ( value >> 1 ) | ( oldCarry << 7 ) ; if ( isAcc ) this . a = newValue ; else this . write8 ( adr , newValue ) ; this . setNZCFlag ( newValue , newCarry ) ; } break ; case OpType . ASL : { const isAcc = inst . addressing === Addressing . ACCUMULATOR ; const value = isAcc ? this . a : this . read8 ( adr ) ; const newCarry = value >= 0x80 ; const newValue = ( value << 1 ) & 0xff ; if ( isAcc ) this . a = newValue ; else this . write8 ( adr , newValue ) ; this . setNZCFlag ( newValue , newCarry ) ; } break ; case OpType . LSR : { const isAcc = inst . addressing === Addressing . ACCUMULATOR ; const value = isAcc ? this . a : this . read8 ( adr ) ; const newCarry = ( value & 0x01 ) !== 0 ; const newValue = ( value >> 1 ) & 0xff ; if ( isAcc ) this . a = newValue ; else this . write8 ( adr , newValue ) ; this . setNZCFlag ( newValue , newCarry ) ; } break ; case OpType . BIT : { const value = this . read8 ( adr ) ; const result = this . a & value ; this . zero = result === 0 ? 1 : 0 ; this . negative = bit ( value , NEGATIVE_BIT ) ; this . overflow = bit ( value , OVERFLOW_BIT ) ; } break ; case OpType . CMP : { const value = this . read8 ( adr ) ; const result = this . a - value ; this . setNZCFlag ( result & 255 , result >= 0 ) ; } break ; case OpType . CPX : { const value = this . read8 ( adr ) ; const result = this . x - value ; this . setNZCFlag ( result & 255 , result >= 0 ) ; } break ; case OpType . CPY : { const value = this . read8 ( adr ) ; const result = this . y - value ; this . setNZCFlag ( result & 255 , result >= 0 ) ; } break ; case OpType . JMP : this . pc = adr ; break ; case OpType . JSR : this . push16 ( this . pc - 1 ) ; this . pc = adr ; break ; case OpType . RTS : this . pc = this . pop16 ( ) + 1 ; break ; case OpType . RTI : this . setStatusReg ( this . pop ( ) | RESERVED_FLAG ) ; this . pc = this . pop16 ( ) ; break ; case OpType . BCC : cycle += this . branch ( adr , this . carry === 0 ) ; break ; case OpType . BCS : cycle += this . branch ( adr , this . carry !== 0 ) ; break ; case OpType . BPL : cycle += this . branch ( adr , this . negative === 0 ) ; break ; case OpType . BMI : cycle += this . branch ( adr , this . negative !== 0 ) ; break ; case OpType . BNE : cycle += this . branch ( adr , this . zero === 0 ) ; break ; case OpType . BEQ : cycle += this . branch ( adr , this . zero !== 0 ) ; break ; case OpType . BVC : cycle += this . branch ( adr , this . overflow === 0 ) ; break ; case OpType . BVS : cycle += this . branch ( adr , this . overflow !== 0 ) ; break ; case OpType . PHA : this . push ( this . a ) ; break ; case OpType . PHP : this . push ( this . getStatusReg ( ) | BREAK_FLAG ) ; break ; case OpType . PLA : this . a = this . pop ( ) ; this . setNZFlag ( this . a ) ; break ; case OpType . PLP : this . setStatusReg ( this . pop ( ) | RESERVED_FLAG ) ; break ; case OpType . CLC : this . carry = 0 ; break ; case OpType . SEC : this . carry = 1 ; break ; case OpType . SEI : this . irqBlocked = 1 ; break ; case OpType . CLI : this . irqBlocked = 0 ; break ; case OpType . CLV : this . overflow = 0 ; break ; case OpType . SED : this . decimal = 1 ; break ; case OpType . CLD : this . decimal = 0 ; break ; case OpType . BRK : this . push16 ( this . pc + 1 ) ; this . push ( this . getStatusReg ( ) | BREAK_FLAG ) ; this . pc = this . read16 ( VEC_IRQ ) ; this . irqBlocked = 1 ; break ; case OpType . LAX : this . a = this . x = this . read8 ( adr ) ; this . setNZFlag ( this . a ) ; break ; case OpType . SAX : this . write8 ( adr , this . a & this . x ) ; break ; case OpType . ISB : { const value = ( this . read8 ( adr ) + 1 ) & 0xff ; this . write8 ( adr , value ) ; const carry = this . carry ; const operand = 255 - value ; const result = this . a + operand + carry ; const overflow = ( ( this . a ^ result ) & ( operand ^ result ) & 0x80 ) !== 0 ; this . a = result & 0xff ; this . setNZCFlag ( this . a , result >= 0x0100 ) ; this . setOverFlow ( overflow ) ; } break ; case OpType . DCP : { const value = ( this . read8 ( adr ) - 1 ) & 0xff ; this . write8 ( adr , value ) ; const result = this . a - value ; this . setNZCFlag ( result & 255 , result >= 0 ) ; } break ; case OpType . RLA : { const value = this . read8 ( adr ) ; const oldCarry = this . carry ; const newCarry = value >= 0x80 ; const newValue = ( ( value << 1 ) | oldCarry ) & 0xff ; this . write8 ( adr , newValue ) ; this . a &= newValue ; this . setNZCFlag ( this . a , newCarry ) ; } break ; case OpType . RRA : { const value = this . read8 ( adr ) ; const oldCarry = this . carry ; const newCarry = ( value & 0x01 ) !== 0 ; const newValue = ( value >> 1 ) | ( oldCarry << 7 ) ; this . write8 ( adr , newValue ) ; const carry = newCarry ? 1 : 0 ; const operand = newValue ; const result = this . a + operand + carry ; const overflow = ( ( this . a ^ result ) & ( operand ^ result ) & 0x80 ) !== 0 ; this . a = result & 0xff ; this . setNZCFlag ( this . a , result >= 0x0100 ) ; this . setOverFlow ( overflow ) ; } break ; case OpType . SLO : { const value = this . read8 ( adr ) ; const newCarry = value >= 0x80 ; const newValue = ( value << 1 ) & 0xff ; this . write8 ( adr , newValue ) ; this . a |= newValue ; this . setNZCFlag ( this . a , newCarry ) ; } break ; case OpType . SRE : { const value = this . read8 ( adr ) ; const newCarry = ( value & 0x01 ) !== 0 ; const newValue = ( value >> 1 ) & 0xff ; this . write8 ( adr , newValue ) ; this . a ^= newValue ; this . setNZCFlag ( this . a , newCarry ) ; } break ; } if ( this . breakPoints . has ( this . pc ) ) { this . paused = true ; console . warn ( `paused because PC matched break point: ${ Util . hex ( this . pc , 4 ) }` ) ; } return cycle ; } private getStatusReg ( ) { return ( ( this . negative << NEGATIVE_BIT ) | ( this . overflow << OVERFLOW_BIT ) | ( this . reservedFlag << RESERVED_BIT ) | ( this . breakmode << BREAK_BIT ) | ( this . decimal << DECIMAL_BIT ) | ( this . irqBlocked << IRQBLK_BIT ) | ( this . zero << ZERO_BIT ) | ( this . carry << CARRY_BIT ) ) ; } private setStatusReg ( p ) { this . negative = bit ( p , NEGATIVE_BIT ) ; this . overflow = bit ( p , OVERFLOW_BIT ) ; this . reservedFlag = bit ( p , RESERVED_BIT ) ; this . breakmode = bit ( p , BREAK_BIT ) ; this . decimal = bit ( p , DECIMAL_BIT ) ; this . irqBlocked = bit ( p , IRQBLK_BIT ) ; this . zero = bit ( p , ZERO_BIT ) ; this . carry = bit ( p , CARRY_BIT ) ; } private read8 ( adr ) { const value = this . bus . read8 ( adr ) ; if ( this . watchRead . has ( adr ) ) { this . paused = true ; console . warn ( `Break because watched point read: adr=${ Util . hex ( adr , 4 ) }, value=${ Util . hex ( value , 2 ) }` ) ; } return value ; } private read16 ( adr ) { const lo = this . read8 ( adr ) ; const hi = this . read8 ( adr + 1 ) ; return ( hi << 8 ) | lo ; } private read16Indirect ( adr ) { const lo = this . read8 ( adr ) ; const hi = this . read8 ( ( adr & 0xff00 ) + ( ( adr + 1 ) & 0xff ) ) ; return ( hi << 8 ) | lo ; } private write8 ( adr , value ) { this . bus . write8 ( adr , value ) ; if ( this . watchWrite . has ( adr ) ) { this . paused = true ; console . warn ( `Break because watched point write: adr=${ Util . hex ( adr , 4 ) }, value=${ Util . hex ( value , 2 ) }` ) ; } } private push ( value ) { this . write8 ( 0x0100 + this . s , value ) ; this . s = ( this . s - 1 ) & 0xff ; } private push16 ( value ) { let s = this . s ; this . write8 ( 0x0100 + s , value >> 8 ) ; s = ( s - 1 ) & 0xff ; this . write8 ( 0x0100 + s , value & 0xff ) ; this . s = ( s - 1 ) & 0xff ; } private pop ( ) { this . s = ( this . s + 1 ) & 0xff ; return this . read8 ( 0x0100 + this . s ) ; } private pop16 ( ) { let s = this . s ; s = ( s + 1 ) & 0xff ; const l = this . read8 ( 0x0100 + s ) ; s = ( s + 1 ) & 0xff ; const h = this . read8 ( 0x0100 + s ) ; this . s = s ; return ( h << 8 ) | l ; } private setNZFlag ( nz ) { this . zero = nz === 0 ? 1 : 0 ; this . negative = nz >= 0x80 ? 1 : 0 ; } private setNZCFlag ( nz , carry ) { this . zero = nz === 0 ? 1 : 0 ; this . negative = nz >= 0x80 ? 1 : 0 ; this . carry = carry ? 1 : 0 ; } private setOverFlow ( value ) { this . overflow = value ? 1 : 0 ; } private getAdr ( pc , addressing ) { switch ( addressing ) { case Addressing . ACCUMULATOR : case Addressing . IMPLIED : return 0 ; case Addressing . IMMEDIATE : case Addressing . RELATIVE : return pc ; case Addressing . ZEROPAGE : return this . read8 ( pc ) ; case Addressing . ZEROPAGE_X : return ( this . read8 ( pc ) + this . x ) & 0xff ; case Addressing . ZEROPAGE_Y : return ( this . read8 ( pc ) + this . y ) & 0xff ; case Addressing . ABSOLUTE : return this . read16 ( pc ) ; case Addressing . ABSOLUTE_X : return ( this . read16 ( pc ) + this . x ) & 0xffff ; case Addressing . ABSOLUTE_Y : return ( this . read16 ( pc ) + this . y ) & 0xffff ; case Addressing . INDIRECT_X : { const zeroPageAdr = this . read8 ( pc ) ; return this . read16Indirect ( ( zeroPageAdr + this . x ) & 0xff ) ; } case Addressing . INDIRECT_Y : { const zeroPageAdr = this . read8 ( pc ) ; const base = this . read16Indirect ( zeroPageAdr ) ; return ( base + this . y ) & 0xffff ; } case Addressing . INDIRECT : { const adr = this . read16 ( pc ) ; return this . read16Indirect ( adr ) ; } default : console . error ( `Illegal addressing: ${ addressing }, pc=${ Util . hex ( pc , 4 ) }` ) ; this . paused = true ; return 0 ; } } private branch ( adr , cond ) { if ( ! cond ) return 0 ; const pc = this . pc ; const offset = this . read8 ( adr ) ; const newPc = ( pc + ( offset < 0x80 ? offset : offset - 0x100 ) ) & 0xffff ; this . pc = newPc ; return ( ( pc ^ newPc ) & 0x0100 ) > 0 ? 2 : 1 ; } private handleIrq ( ) { this . push16 ( this . pc ) ; this . push ( this . getStatusReg ( ) & ~ BREAK_FLAG ) ; this . pc = this . read16 ( VEC_IRQ ) ; this . irqBlocked = 1 ; } }