/**
 * AVR 8 CPU data structures
 * Part of AVR8js
 *
 * Copyright (C) 2019, Uri Shaked
 */ import { AVRIOPort } from '../peripherals/gpio' ; import { u32 , u16 , u8 , i16 } from '../types' ; import { avrInterrupt } from './interrupt' ; const registerSpace = 0x100 ; export type CPUMemoryHook = ( value : u8 , oldValue : u8 , addr : u16 , mask : u8 ) => boolean | void ; export interface CPUMemoryHooks { [ key ] : CPUMemoryHook ; } export type CPUMemoryReadHook = ( addr : u16 ) => u8 ; export interface CPUMemoryReadHooks { [ key ] : CPUMemoryReadHook ; } export interface AVRInterruptConfig { address : u8 ; enableRegister : u16 ; enableMask : u8 ; flagRegister : u16 ; flagMask : u8 ; constant ? : boolean ; inverseFlag ? : boolean ; } export type AVRClockEventCallback = ( ) => void ; interface AVRClockEventEntry { cycles : number ; callback : AVRClockEventCallback ; next : AVRClockEventEntry | null ; } export class CPU { readonly data : Uint8Array = new Uint8Array ( this . sramBytes + registerSpace ) ; readonly data16 = new Uint16Array ( this . data . buffer ) ; readonly dataView = new DataView ( this . data . buffer ) ; readonly progBytes = new Uint8Array ( this . progMem . buffer ) ; readonly readHooks : CPUMemoryReadHooks = [ ] ; readonly writeHooks : CPUMemoryHooks = [ ] ; private readonly pendingInterrupts : AVRInterruptConfig [ ] = [ ] ; private nextClockEvent : AVRClockEventEntry | null = null ; private readonly clockEventPool : AVRClockEventEntry [ ] = [ ] ; /**
     * Whether the program counter (PC) can address 22 bits (the default is 16)
     */ readonly pc22Bits = this . progBytes . length > 0x20000 ; readonly gpioPorts = new Set < AVRIOPort > ( ) ; readonly gpioByPort : AVRIOPort [ ] = [ ] ; /**
     * This function is called by the WDR instruction. The Watchdog peripheral attaches
     * to it to listen for WDR (watchdog reset).
     */ onWatchdogReset = ( ) => { } ; /**
     * Program counter
     */ pc : u32 = 0 ; /**
     * Clock cycle counter
     */ cycles = 0 ; nextInterrupt : i16 = - 1 ; constructor ( public progMem , private sramBytes = 8192 ) { this . reset ( ) ; } reset ( ) { this . data . fill ( 0 ) ; this . SP = this . data . length - 1 ; this . pc = 0 ; this . pendingInterrupts . splice ( 0 , this . pendingInterrupts . length ) ; this . nextInterrupt = - 1 ; this . nextClockEvent = null ; } readData ( addr ) { if ( addr >= 32 && this . readHooks [ addr ] ) { return this . readHooks [ addr ] ( addr ) ; } return this . data [ addr ] ; } writeData ( addr , value , mask = 0xff ) { const hook = this . writeHooks [ addr ] ; if ( hook ) { if ( hook ( value , this . data [ addr ] , addr , mask ) ) { return ; } } this . data [ addr ] = value ; } get SP ( ) { return this . dataView . getUint16 ( 93 , true ) ; } set SP ( value ) { this . dataView . setUint16 ( 93 , value , true ) ; } get SREG ( ) { return this . data [ 95 ] ; } get interruptsEnabled ( ) { return this . SREG & 0x80 ? true : false ; } private updateNextInterrupt ( ) { this . nextInterrupt = this . pendingInterrupts . findIndex ( ( item ) => ! ! item ) ; } setInterruptFlag ( interrupt ) { const { flagRegister , flagMask , enableRegister , enableMask } = interrupt ; if ( interrupt . inverseFlag ) { this . data [ flagRegister ] &= ~ flagMask ; } else { this . data [ flagRegister ] |= flagMask ; } if ( this . data [ enableRegister ] & enableMask ) { this . queueInterrupt ( interrupt ) ; } } updateInterruptEnable ( interrupt , registerValue ) { const { enableMask , flagRegister , flagMask , inverseFlag } = interrupt ; if ( registerValue & enableMask ) { const bitSet = this . data [ flagRegister ] & flagMask ; if ( inverseFlag ? ! bitSet : bitSet ) { this . queueInterrupt ( interrupt ) ; } } else { this . clearInterrupt ( interrupt , false ) ; } } queueInterrupt ( interrupt ) { this . pendingInterrupts [ interrupt . address ] = interrupt ; this . updateNextInterrupt ( ) ; } clearInterrupt ( { address , flagRegister , flagMask } , clearFlag = true ) { delete this . pendingInterrupts [ address ] ; if ( clearFlag ) { this . data [ flagRegister ] &= ~ flagMask ; } this . updateNextInterrupt ( ) ; } clearInterruptByFlag ( interrupt , registerValue ) { const { flagRegister , flagMask } = interrupt ; if ( registerValue & flagMask ) { this . data [ flagRegister ] &= ~ flagMask ; this . clearInterrupt ( interrupt ) ; } } addClockEvent ( callback , cycles ) { const { clockEventPool } = this ; cycles = this . cycles + Math . max ( 1 , cycles ) ; const maybeEntry = clockEventPool . pop ( ) ; const entry = maybeEntry ?? { cycles , callback , next : null } ; entry . cycles = cycles ; entry . callback = callback ; let { nextClockEvent : clockEvent } = this ; let lastItem = null ; while ( clockEvent && clockEvent . cycles < cycles ) { lastItem = clockEvent ; clockEvent = clockEvent . next ; } if ( lastItem ) { lastItem . next = entry ; entry . next = clockEvent ; } else { this . nextClockEvent = entry ; entry . next = clockEvent ; } return callback ; } updateClockEvent ( callback , cycles ) { if ( this . clearClockEvent ( callback ) ) { this . addClockEvent ( callback , cycles ) ; return true ; } return false ; } clearClockEvent ( callback ) { let { nextClockEvent : clockEvent } = this ; if ( ! clockEvent ) { return false ; } const { clockEventPool } = this ; let lastItem = null ; while ( clockEvent ) { if ( clockEvent . callback === callback ) { if ( lastItem ) { lastItem . next = clockEvent . next ; } else { this . nextClockEvent = clockEvent . next ; } if ( clockEventPool . length < 10 ) { clockEventPool . push ( clockEvent ) ; } return true ; } lastItem = clockEvent ; clockEvent = clockEvent . next ; } return false ; } tick ( ) { const { nextClockEvent } = this ; if ( nextClockEvent && nextClockEvent . cycles <= this . cycles ) { nextClockEvent . callback ( ) ; this . nextClockEvent = nextClockEvent . next ; if ( this . clockEventPool . length < 10 ) { this . clockEventPool . push ( nextClockEvent ) ; } } const { nextInterrupt } = this ; if ( this . interruptsEnabled && nextInterrupt >= 0 ) { const interrupt = this . pendingInterrupts [ nextInterrupt ] ; avrInterrupt ( this , interrupt . address ) ; if ( ! interrupt . constant ) { this . clearInterrupt ( interrupt ) ; } } } }