import { DzrpRemote } from '../dzrp/dzrpremote' ; import { Z80_REG , Z80Registers } from '../z80registers' ; import { Z80Ports } from './z80ports' ; import { Z80Cpu } from './z80cpu' ; import { Settings , ZSimType } from '../../settings' ; import { Utility } from '../../misc/utility' ; import { BREAK_REASON_NUMBER } from '../remotebase' ; import { Labels } from '../../labels/labels' ; import { MemBuffer } from '../../misc/membuffer' ; import { CodeCoverageArray } from './codecovarray' ; import { CpuHistoryClass , CpuHistory , DecodeStandardHistoryInfo } from '../cpuhistory' ; import { ZSimCpuHistory } from './zsimcpuhistory' ; import { Zx16Memory } from './zx16memory' ; import { Zx48Memory } from './zx48memory' ; import { Zx128Memory } from './zx128memory' ; import { ZxNextMemory } from './zxnextmemory' ; import { MemoryModel , Zx16MemoryModel , Zx48MemoryModel , Zx128MemoryModel , ZxNextMemoryModel , CustomMemoryModel } from '../Paging/memorymodel' ; import { SimulatedMemory } from './simmemory' ; import { SnaFile } from '../dzrp/snafile' ; import { NexFile } from '../dzrp/nexfile' ; import { CustomCode } from './customcode' ; import { BeeperBuffer , ZxBeeper } from './zxbeeper' ; import { GenericBreakpoint } from '../../genericwatchpoint' ; import { Z80RegistersStandardDecoder } from '../z80registersstandarddecoder' ; import { CustomMemory } from './custommemory' ; /**
 * The representation of a Z80 remote.
 * With options to simulate ZX Spectrum or some ZX Next features.
 */ export class ZSimRemote extends DzrpRemote { public z80Cpu : Z80Cpu ; public memory : SimulatedMemory ; public ports : Z80Ports ; protected codeCoverage : CodeCoverageArray ; protected lastBpId : number ; protected stopCpu : boolean ; protected serializeObjects : any [ ] ; protected previouslyStoredPCHistory : number ; protected tbblueRegisterSelectValue : number ; protected tbblueRegisterWriteHandler : Map < number , ( value : number ) => void > ; protected tbblueRegisterReadHandler : Map < number , ( ) => number > ; public customCode : CustomCode ; protected passedTstates : number ; protected timeStep : number ; protected nextStepTstates : number ; protected timeoutRequest : boolean ; protected ulaScreenAddress : number ; public zxBeeper : ZxBeeper ; protected zxBorderColor : number ; constructor ( ) { super ( ) ; this . ulaScreenAddress = 0x4000 ; this . timeoutRequest = false ; this . previouslyStoredPCHistory = - 1 ; this . tbblueRegisterSelectValue = 0 ; this . tbblueRegisterWriteHandler = new Map < number , ( value : number ) => void > ( ) ; this . tbblueRegisterReadHandler = new Map < number , ( ) => number > ( ) ; this . passedTstates = 0 ; this . timeStep = Settings . launch . zsim . customCode . timeStep ; this . nextStepTstates = 0 ; this . stopCpu = true ; this . lastBpId = 0 ; this . zxBorderColor = 7 ; Z80Registers . decoder = new Z80RegistersStandardDecoder ( ) ; if ( Settings . launch . history . reverseDebugInstructionCount > 0 ) { CpuHistoryClass . setCpuHistory ( new ZSimCpuHistory ( ) ) ; CpuHistory . decoder = new DecodeStandardHistoryInfo ( ) ; } if ( Settings . launch . history . codeCoverageEnabled ) this . codeCoverage = new CodeCoverageArray ( ) ; } /**
     * Is set/reset by the ZSimulatorView to request processing time.
     */ public setTimeoutRequest ( on ) { this . timeoutRequest = on ; } param port param value protected zx128BankSwitch ( port , value ) { const mem = this . memory ; const ramBank = value & 0x07 ; mem . setSlot ( 3 , ramBank ) ; const shadowScreen = value & 0b01000 ; this . ulaScreenAddress = ( shadowScreen == 0 ) ? 5 * 0x4000 : 7 * 0x4000 ; const romIndex = ( value & 0b010000 ) ? 1 : 0 ; this . memory . setSlot ( 0 , 8 + romIndex ) ; if ( value & 0b0100000 ) { this . ports . registerSpecificOutPortFunction ( 0x7FFD , undefined ) ; } } param port param value protected tbblueRegisterSelect ( port , value ) { this . tbblueRegisterSelectValue = value ; } param port param value protected tbblueRegisterWriteAccess ( port , value ) { const func = this . tbblueRegisterWriteHandler . get ( this . tbblueRegisterSelectValue ) ; if ( func ) func ( value ) ; } param port protected tbblueRegisterReadAccess ( port ) { const func = this . tbblueRegisterReadHandler . get ( this . tbblueRegisterSelectValue ) ; if ( ! func ) return 0 ; const value = func ( ) ; return value ; } param value protected tbblueMemoryManagementSlotsWrite ( value ) { const slot = this . tbblueRegisterSelectValue & 0x07 ; if ( value == 0xFF ) { if ( slot > 1 ) return ; if ( slot == 0 ) value = 0xFE ; } else if ( value > 223 ) return ; this . memory . setSlot ( slot , value ) ; } /**
     * Reads the tbblue slot/bank association for slots 0-7.
     * See https://wiki.specnext.dev/Memory_management_slot_0_bank
     * tbblueRegisterSelectValue contains the register (0x50-0x57) respectively the
     * slot.
     */ protected tbblueMemoryManagementSlotsRead ( ) { const slot = this . tbblueRegisterSelectValue & 0x07 ; let bank = this . memory . getSlots ( ) ! [ slot ] ; if ( bank == 0xFE ) bank = 0xFF ; return bank ; } param zsim protected configureMachine ( zsim ) { Z80Registers . decoder = new Z80RegistersStandardDecoder ( ) ; this . ports = new Z80Ports ( zsim . defaultPortIn ) ; const zxBeeperEnabled = zsim . zxBeeper ; const zxBorderWidth = zsim . zxBorderWidth ; this . zxBeeper = new ZxBeeper ( zsim . cpuFrequency , zsim . audioSampleRate , Settings . launch . zsim . updateFrequency ) ; if ( zxBeeperEnabled || zxBorderWidth > 0 ) { this . ports . registerGenericOutPortFunction ( ( port , value ) => { if ( port & 0x01 ) return undefined ; if ( zxBorderWidth > 0 ) { this . zxBorderColor = value & 0x07 ; } if ( zxBeeperEnabled ) { this . zxBeeper . writeBeeper ( this . passedTstates , ( value & 0b10000 ) != 0 ) ; } } ) ; } switch ( zsim . memoryModel ) { case "RAM" : { this . memoryModel = new MemoryModel ( ) ; this . memory = new SimulatedMemory ( 1 , 1 ) ; } break ; case "ZX16K" : { this . memoryModel = new Zx16MemoryModel ( ) ; this . memory = new Zx16Memory ( ) ; } break ; case "ZX48K" : { this . memoryModel = new Zx48MemoryModel ( ) ; this . memory = new Zx48Memory ( ) ; } break ; case "ZX128K" : { this . memoryModel = new Zx128MemoryModel ( ) ; this . memory = new Zx128Memory ( ) ; this . ports . registerSpecificOutPortFunction ( 0x7FFD , this . zx128BankSwitch . bind ( this ) ) ; this . ulaScreenAddress = 5 * 0x4000 ; } break ; case "ZXNEXT" : { this . memoryModel = new ZxNextMemoryModel ( ) ; this . memory = new ZxNextMemory ( ) ; for ( let tbblueRegister = 0x50 ; tbblueRegister <= 0x57 ; tbblueRegister ++ ) { this . tbblueRegisterWriteHandler . set ( tbblueRegister , this . tbblueMemoryManagementSlotsWrite . bind ( this ) ) ; this . tbblueRegisterReadHandler . set ( tbblueRegister , this . tbblueMemoryManagementSlotsRead . bind ( this ) ) ; } this . ports . registerSpecificOutPortFunction ( 0x243B , this . tbblueRegisterSelect . bind ( this ) ) ; this . ports . registerSpecificOutPortFunction ( 0x253B , this . tbblueRegisterWriteAccess . bind ( this ) ) ; this . ports . registerSpecificInPortFunction ( 0x253B , this . tbblueRegisterReadAccess . bind ( this ) ) ; this . ulaScreenAddress = 10 * 0x2000 ; } break ; case "CUSTOM" : { this . memoryModel = new CustomMemoryModel ( zsim . customMemory ) ; this . memory = new CustomMemory ( zsim . customMemory ) ; } break ; default : throw Error ( "Unknown memory model: '" + zsim . memoryModel + "'." ) ; } this . memoryModel . init ( ) ; Labels . convertLabelsTo ( this . memoryModel ) ; this . z80Cpu = new Z80Cpu ( this . memory , this . ports , ( ) => { this . emit ( 'vertSync' ) ; } ) ; this . serializeObjects = [ this . z80Cpu , this . memory , this . zxBeeper , ] ; } public async doInitialization ( ) { this . configureMachine ( Settings . launch . zsim ) ; const loadPath = Settings . launch . load ; if ( loadPath ) await this . loadBin ( loadPath ) ; for ( let loadObj of Settings . launch . loadObjs ) { if ( loadObj . path ) { const start = Labels . getNumberFromString64k ( loadObj . start ) ; if ( isNaN ( start ) ) throw Error ( "Cannot evaluate 'loadObjs[].start' (" + loadObj . start + ")." ) ; await this . loadObj ( loadObj . path , start ) ; } } if ( Settings . launch . execAddress ) { const execAddress = Labels . getNumberFromString64k ( Settings . launch . execAddress ) ; if ( isNaN ( execAddress ) ) throw Error ( "Cannot evaluate 'execAddress' (" + Settings . launch . execAddress + ")." ) ; await this . setRegisterValue ( "PC" , execAddress ) ; } const jsPath = Settings . launch . zsim . customCode . jsPath ; if ( jsPath ) { this . customCode = new CustomCode ( jsPath ) ; this . ports . registerGenericInPortFunction ( port => { this . customCode . setTstates ( this . passedTstates ) ; const value = this . customCode . readPort ( port ) ; return value ; } ) ; this . ports . registerGenericOutPortFunction ( ( port , value ) => { this . customCode . setTstates ( this . passedTstates ) ; this . customCode . writePort ( port , value ) ; } ) ; this . customCode . on ( 'interrupt' , ( non_maskable , data ) => { this . z80Cpu . generateInterrupt ( non_maskable , data ) ; } ) ; } this . emit ( 'initialized' ) ; } param handler public async disconnect ( ) { this . stopCpu = true ; this . emit ( 'closed' ) ; } param reg param value protected setRegValue ( reg , value ) { switch ( reg ) { case Z80_REG . PC : this . z80Cpu . pc = value ; break ; case Z80_REG . SP : this . z80Cpu . sp = value ; break ; case Z80_REG . AF : this . z80Cpu . af = value ; break ; case Z80_REG . BC : this . z80Cpu . bc = value ; break ; case Z80_REG . DE : this . z80Cpu . de = value ; break ; case Z80_REG . HL : this . z80Cpu . hl = value ; break ; case Z80_REG . IX : this . z80Cpu . ix = value ; break ; case Z80_REG . IY : this . z80Cpu . iy = value ; break ; case Z80_REG . AF2 : this . z80Cpu . af2 = value ; break ; case Z80_REG . BC2 : this . z80Cpu . bc2 = value ; break ; case Z80_REG . DE2 : this . z80Cpu . de2 = value ; break ; case Z80_REG . HL2 : this . z80Cpu . hl2 = value ; break ; case Z80_REG . IM : this . z80Cpu . im = value ; break ; case Z80_REG . F : this . z80Cpu . f = value ; break ; case Z80_REG . A : this . z80Cpu . a = value ; break ; case Z80_REG . C : this . z80Cpu . c = value ; break ; case Z80_REG . B : this . z80Cpu . b = value ; break ; case Z80_REG . E : this . z80Cpu . e = value ; break ; case Z80_REG . D : this . z80Cpu . d = value ; break ; case Z80_REG . L : this . z80Cpu . l = value ; break ; case Z80_REG . H : this . z80Cpu . h = value ; break ; case Z80_REG . IXL : this . z80Cpu . ixl = value ; break ; case Z80_REG . IXH : this . z80Cpu . ixh = value ; break ; case Z80_REG . IYL : this . z80Cpu . iyl = value ; break ; case Z80_REG . IYH : this . z80Cpu . iyh = value ; break ; case Z80_REG . F2 : this . z80Cpu . f = value ; break ; case Z80_REG . A2 : this . z80Cpu . a = value ; break ; case Z80_REG . C2 : this . z80Cpu . c = value ; break ; case Z80_REG . B2 : this . z80Cpu . b = value ; break ; case Z80_REG . E2 : this . z80Cpu . e = value ; break ; case Z80_REG . D2 : this . z80Cpu . d = value ; break ; case Z80_REG . L2 : this . z80Cpu . l = value ; break ; case Z80_REG . H2 : this . z80Cpu . h = value ; break ; case Z80_REG . R : this . z80Cpu . r = value ; break ; case Z80_REG . I : this . z80Cpu . i = value ; break ; } } param pc protected storeHistoryInfo ( pc ) { const hist = this . z80Cpu . getHistoryData ( ) ; if ( pc != this . previouslyStoredPCHistory ) { this . previouslyStoredPCHistory = pc ; CpuHistory . pushHistoryInfo ( hist ) ; } } param bp1 param bp2 protected async z80CpuContinue ( bp1 , bp2 ) { const limitSpeed = Settings . launch . zsim . limitSpeed ; let limitSpeedPrevTime = Date . now ( ) ; let limitSpeedPrevTstates = this . passedTstates ; while ( true ) { this . z80Cpu . error = undefined ; let breakReasonString = '' ; let breakNumber = BREAK_REASON_NUMBER . NO_REASON ; let breakAddress ; let slots ; const longAddressesUsed = Labels . AreLongAddressesUsed ( ) ; if ( longAddressesUsed ) slots = this . memory . getSlots ( ) ; let pcLong = Z80Registers . createLongAddress ( this . z80Cpu . pc , slots ) ; const leaveAtTstates = this . passedTstates + 5000 * 4 ; try { while ( this . passedTstates < leaveAtTstates ) { const prevPc = this . z80Cpu . pc ; if ( CpuHistory ) this . storeHistoryInfo ( prevPc ) ; if ( this . passedTstates >= this . nextStepTstates ) { this . nextStepTstates += this . timeStep ; if ( this . customCode ) { this . customCode . setTstates ( this . passedTstates ) ; this . customCode . tick ( ) ; } } const tStates = this . z80Cpu . execute ( ) ; this . passedTstates += tStates ; this . memory . setVisualProg ( prevPc ) ; this . codeCoverage ?. storeAddress ( pcLong ) ; if ( this . z80Cpu . error != undefined ) { breakNumber = BREAK_REASON_NUMBER . CPU_ERROR ; breakReasonString = "CPU error: " + this . z80Cpu . error ; break ; } const pc = this . z80Cpu . pc ; if ( longAddressesUsed ) slots = this . memory . getSlots ( ) ; pcLong = Z80Registers . createLongAddress ( pc , slots ) ; const bpInner = this . tmpBreakpoints . get ( pcLong ) ; if ( bpInner ) { const regs = this . z80Cpu . getRegisterData ( ) ; Z80Registers . setCache ( regs ) ; let bp ; for ( const bpElem of bpInner ) { try { const { condition , log } = this . checkConditionAndLog ( bpElem ) ; if ( log ) { const evalLog = await Utility . evalLogString ( log ) ; this . emit ( 'debug_console' , "Log: " + evalLog ) ; } else { if ( condition != undefined ) { bp = bpElem ; break ; } } } catch ( e ) { bp = bpElem ; break ; } } if ( bp ) { breakNumber = BREAK_REASON_NUMBER . BREAKPOINT_HIT ; breakAddress = pcLong ; break ; } } if ( this . memory . hitAddress >= 0 ) { breakNumber = ( this . memory . hitAccess == 'r' ) ? BREAK_REASON_NUMBER . WATCHPOINT_READ : BREAK_REASON_NUMBER . WATCHPOINT_WRITE ; const memAddress = this . memory . hitAddress ; breakAddress = Z80Registers . createLongAddress ( memAddress , slots ) ; break ; } if ( pc == bp1 || pc == bp2 ) { breakAddress = pc ; break ; } if ( this . stopCpu ) { breakNumber = BREAK_REASON_NUMBER . MANUAL_BREAK ; break ; } } } catch ( errorText ) { breakReasonString = "Z80CPU Error: " + errorText ; console . log ( breakReasonString ) ; breakNumber = BREAK_REASON_NUMBER . UNKNOWN ; } if ( this . passedTstates < leaveAtTstates ) { this . stopCpu = true ; Utility . assert ( this . funcContinueResolve ) ; this . funcContinueResolve ! ( { breakNumber , breakAddress , breakReasonString } ) ; return ; } if ( limitSpeed ) { const currentTime = Date . now ( ) ; const usedTime = currentTime - limitSpeedPrevTime ; if ( usedTime > 20 ) { const usedTstates = this . passedTstates - limitSpeedPrevTstates ; const targetTime = 1000 * usedTstates / this . z80Cpu . cpuFreq ; let remainingTime = targetTime - usedTime ; if ( remainingTime >= 1 ) { if ( remainingTime > 500 ) remainingTime = 500 ; await Utility . timeout ( remainingTime ) ; } limitSpeedPrevTime = Date . now ( ) ; limitSpeedPrevTstates = this . passedTstates ; } } await Utility . timeout ( 1 ) ; while ( this . timeoutRequest ) { await Utility . timeout ( 100 ) ; } if ( this . stopCpu ) { breakNumber = BREAK_REASON_NUMBER . MANUAL_BREAK ; breakAddress = 0 ; breakReasonString = await this . constructBreakReasonString ( breakNumber , breakAddress , '' , '' ) ; Utility . assert ( this . funcContinueResolve ) ; if ( this . funcContinueResolve ) this . funcContinueResolve ( { breakNumber , breakAddress , breakReasonString } ) ; return ; } } } /**
     * This method is called before a step (stepOver, stepInto, stepOut,
     * continue) is called.
     * Takes care of code coverage.
     */ public startProcessing ( ) { super . startProcessing ( ) ; this . codeCoverage . clearAll ( ) ; } /**
     * This method should be called after a step (stepOver, stepInto, stepOut,
     * continue) is called.
     * It will clear e.g. the register and the call stack cache.
     * So that the next time they are accessed they are immediately refreshed.
     */ public stopProcessing ( ) { super . stopProcessing ( ) ; this . emit ( 'update' ) ; if ( this . codeCoverage ) { this . emit ( 'coverage' , this . codeCoverage . getAddresses ( ) ) ; this . codeCoverage . clearAll ( ) ; } } /**
     * This is an 'intelligent' remote that does evaluate the breakpoint
     * conditions on it's own.
     * This is done primarily for performance reasons.
     */ /**
     * Deserializes the CPU, memory etc. to restore the state.
     */ protected deserializeState ( data ) { const memBuffer = MemBuffer . from ( data . buffer ) ; this . zxBorderColor = memBuffer . read8 ( ) ; for ( const obj of this . serializeObjects ) obj . deserialize ( memBuffer ) ; this . emit ( 'restored' ) ; return memBuffer . getUint8Array ( ) ; } /**
     * Serializes the CPU, memory etc. to save the state.
     */ protected serializeState ( ) { let size = 0 ; for ( const obj of this . serializeObjects ) size += obj . getSerializedSize ( ) ; const memBuffer = new MemBuffer ( size + 1 ) ; memBuffer . write8 ( this . zxBorderColor ) ; for ( const obj of this . serializeObjects ) obj . serialize ( memBuffer ) ; return memBuffer . getUint8Array ( ) ; } /**
     * Resets the T-States counter. Used before stepping to measure the
     * time.
     */ public async resetTstates ( ) { this . z80Cpu . cpuTstatesCounter = 0 ; } returns public async getTstates ( ) { return this . z80Cpu . cpuTstatesCounter ; } public getTstatesSync ( ) { return this . z80Cpu . cpuTstatesCounter ; } /**
     * Returns the passed T-states since start of simulation.
     */ public getPassedTstates ( ) { return this . passedTstates ; } returns public async getCpuFrequency ( ) { return this . z80Cpu . cpuFreq ; } public getCpuFrequencySync ( ) { return this . z80Cpu . cpuFreq ; } returns public getUlaScreen ( ) { const memory = this . memory . getMemoryData ( ) ; const ulaScreen = memory . slice ( this . ulaScreenAddress , this . ulaScreenAddress + 0x1B00 ) ; return ulaScreen ; } returns public getZxBeeperBuffer ( ) { return this . zxBeeper . getBeeperBuffer ( this . passedTstates ) ; } /**
     * Returns the ZX border color.
     */ public getZxBorderColor ( ) { return this . zxBorderColor ; } /**
     * Loads a .sna file.
     * Loading is intelligent. I.e. if a SNA file from a ZX128 is loaded into a ZX48 or a ZXNEXT
     * it will work,
     * as long as no memory is used that is not present in the memory model.
     * E.g. as long as only 16k banks 0, 2 and 5 are used in the SNA file it
     * is possible to load it onto a ZX48K.
     * See https://faqwiki.zxnet.co.uk/wiki/SNA_format
     */ protected async loadBinSna ( filePath ) { const snaFile = new SnaFile ( ) ; snaFile . readFile ( filePath ) ; await this . sendDzrpCmdSetBorder ( snaFile . borderColor ) ; const slots = this . memory . getSlots ( ) ; const slotCount = ( slots ) ? slots . length : 1 ; const bankSize = 0x10000 / slotCount ; const convAddresses = [ 0xC000 , 0x10000 , 0x8000 , 0x10000 , 0x10000 , 0x4000 , 0x10000 , 0x10000 ] ; for ( const memBank of snaFile . memBanks ) { let addr17 ; if ( ! slots ) { addr17 = convAddresses [ memBank . bank ] ; } else { addr17 = memBank . bank * 0x4000 ; } let offs = 0 ; while ( offs <= 0x4000 ) { const data = memBank . data . slice ( offs , offs + bankSize ) ; this . memory . writeMemoryData ( addr17 + offs , data ) ; offs += bankSize ; } } await this . sendDzrpCmdSetRegister ( Z80_REG . PC , snaFile . pc ) ; await this . sendDzrpCmdSetRegister ( Z80_REG . SP , snaFile . sp ) ; await this . sendDzrpCmdSetRegister ( Z80_REG . AF , snaFile . af ) ; await this . sendDzrpCmdSetRegister ( Z80_REG . BC , snaFile . bc ) ; await this . sendDzrpCmdSetRegister ( Z80_REG . DE , snaFile . de ) ; await this . sendDzrpCmdSetRegister ( Z80_REG . HL , snaFile . hl ) ; await this . sendDzrpCmdSetRegister ( Z80_REG . IX , snaFile . ix ) ; await this . sendDzrpCmdSetRegister ( Z80_REG . IY , snaFile . iy ) ; await this . sendDzrpCmdSetRegister ( Z80_REG . AF2 , snaFile . af2 ) ; await this . sendDzrpCmdSetRegister ( Z80_REG . BC2 , snaFile . bc2 ) ; await this . sendDzrpCmdSetRegister ( Z80_REG . DE2 , snaFile . de2 ) ; await this . sendDzrpCmdSetRegister ( Z80_REG . HL2 , snaFile . hl2 ) ; await this . sendDzrpCmdSetRegister ( Z80_REG . R , snaFile . r ) ; await this . sendDzrpCmdSetRegister ( Z80_REG . I , snaFile . i ) ; await this . sendDzrpCmdSetRegister ( Z80_REG . IM , snaFile . im ) ; } /**
     * Loads a .nex file.
     * Loading is intelligent. I.e. if a NEX file is loaded into a ZX128,
     * ZX48 or even a 64k RAM memory model it will work,
     * as long as no memory is used that is not present in the memory model.
     * E.g. as long as only 16k banks 0, 2 and 5 are used in the NEX file it
     * is possible to load it onto a ZX48K.
     * See https://wiki.specnext.dev/NEX_file_format
     */ protected async loadBinNex ( filePath ) { const nexFile = new NexFile ( ) ; nexFile . readFile ( filePath ) ; await this . sendDzrpCmdSetBorder ( nexFile . borderColor ) ; const slots = this . memory . getSlots ( ) ; const slotCount = ( slots ) ? slots . length : 1 ; const bankSize = 0x10000 / slotCount ; const convAddresses = [ 0xC000 , 0x10000 , 0x8000 , 0x10000 , 0x10000 , 0x4000 , 0x10000 , 0x10000 ] ; for ( const memBank of nexFile . memBanks ) { let addr17 ; if ( ! slots ) { addr17 = convAddresses [ memBank . bank ] ; } else { addr17 = memBank . bank * 0x4000 ; } let offs = 0 ; while ( offs <= 0x4000 ) { const data = memBank . data . slice ( offs , offs + bankSize ) ; this . memory . writeMemoryData ( addr17 + offs , data ) ; offs += bankSize ; } } await this . sendDzrpCmdSetRegister ( Z80_REG . SP , nexFile . sp ) ; await this . sendDzrpCmdSetRegister ( Z80_REG . PC , nexFile . pc ) ; await this . sendDzrpCmdSetRegister ( Z80_REG . IM , 1 ) ; } param cmd returns public async dbgExec ( cmd ) { try { let response = '' ; const tokens = cmd . split ( ' ' ) ; const cmd_name = tokens . shift ( ) ; if ( cmd_name == "help" ) { response = `zsim specific commands:
out port value: Output 'value' to 'port'. E.g. "zsim out 0x9000 0xFE"
in port: Print input value from 'port'. E.g. "zsim in 0x8000"
tstates set value: set t-states to 'value', then create a tick event. E.g. "zsim tstastes set 1000"
tstates add value: add 'value' to t-states, then create a tick event. E.g. "zsim tstastes add 1000"
` ; } else if ( cmd_name == "out" ) { if ( tokens . length != 2 ) { throw new Error ( "Wrong number of arguments: port and value expected." ) ; } const port = Utility . parseValue ( tokens [ 0 ] ) ; const value = Utility . parseValue ( tokens [ 1 ] ) ; this . z80Cpu . ports . write ( port , value ) ; response = "Wrote " + Utility . getHexString ( value , 2 ) + "h to port " + Utility . getHexString ( port , 4 ) + "h" ; return response ; } else if ( cmd_name == "in" ) { if ( tokens . length != 1 ) { throw new Error ( "Wrong number of arguments: port expected." ) ; } const port = Utility . parseValue ( tokens [ 0 ] ) ; const value = this . z80Cpu . ports . read ( port ) ; response = "Read port " + Utility . getHexString ( port , 4 ) + "h: " + Utility . getHexString ( value , 2 ) + "h" ; return response ; } else if ( cmd_name == "tstates" ) { if ( tokens . length != 2 ) { throw new Error ( "Wrong number of arguments." ) ; } const subcmd = tokens [ 0 ] ; const value = Utility . parseValue ( tokens [ 1 ] ) ; if ( subcmd == "set" ) this . passedTstates = value ; else if ( subcmd == "add" ) this . passedTstates += value ; else throw Error ( "Expected 'set' or 'add' but got '" + subcmd + "'." ) ; this . customCode . setTstates ( this . passedTstates ) ; this . customCode . tick ( ) ; response = "T-states set to " + this . passedTstates + "." ; return response ; } response += await super . dbgExec ( cmd ) ; return response ; } catch ( e ) { throw e ; } } returns public async sendDzrpCmdGetRegisters ( ) { return this . z80Cpu . getRegisterData ( ) ; } param regIndex param value public async sendDzrpCmdSetRegister ( regIndex , value ) { this . setRegValue ( regIndex , value ) ; } param bp1Address param bp2Address public async sendDzrpCmdContinue ( bp1Address ? , bp2Address ? ) { if ( bp1Address == undefined ) bp1Address = - 1 ; if ( bp2Address == undefined ) bp2Address = - 1 ; this . stopCpu = false ; this . memory . clearHit ( ) ; await this . z80CpuContinue ( bp1Address , bp2Address ) ; } /**
     * Sends the command to pause a running program.
     */ public async sendDzrpCmdPause ( ) { this . stopCpu = true ; } param bp public async sendDzrpCmdAddBreakpoint ( bp ) { this . lastBpId ++ ; bp . bpId = this . lastBpId ; } param bp public async sendDzrpCmdRemoveBreakpoint ( bp ) { } param address param size param access public async sendDzrpCmdAddWatchpoint ( address , size , access ) { this . memory . setWatchpoint ( address , size , access ) ; } param address param size param access protected async sendDzrpCmdRemoveWatchpoint ( address , size , access ) { this . memory . removeWatchpoint ( address , size , access ) ; } param address param size returns public async sendDzrpCmdReadMem ( address , size ) { const buffer = this . memory . readBlock ( address , size ) ; return buffer ; } param address param dataArray public async sendDzrpCmdWriteMem ( address , dataArray ) { this . memory . writeBlock ( address , dataArray ) ; } param bank param dataArray throws public async sendDzrpCmdWriteBank ( bank , dataArray ) { this . memory . writeBank ( bank , dataArray ) ; } param slot param bank returns public async sendDzrpCmdSetSlot ( slot , bank ) { this . memory . setSlot ( slot , bank ) ; return 0 ; } returns public async sendDzrpCmdReadState ( ) { return this . serializeState ( ) ; } param The public async sendDzrpCmdWriteState ( stateData ) { this . deserializeState ( stateData ) ; } /**
     * Sends the command to set the border.
      */ public async sendDzrpCmdSetBorder ( borderColor ) { this . ports . write ( 0xFE , borderColor ) ; } }