// // FUSETests.swift // Clock Signal // // Created by Thomas Harte on 21/05/2017. // Copyright 2017 Thomas Harte. All rights reserved. // import XCTest import Foundation fileprivate func readHexInt16(from scanner: Scanner) -> UInt16 { var temporary: UInt64 = 0 scanner.scanHexInt64(&temporary) return UInt16(temporary) } fileprivate func readHexInt8(from scanner: Scanner) -> UInt8 { var temporary: UInt64 = 0 scanner.scanHexInt64(&temporary) return UInt8(temporary) } fileprivate struct RegisterState { let af: UInt16, bc: UInt16, de: UInt16, hl: UInt16 let afDash: UInt16, bcDash: UInt16, deDash: UInt16, hlDash: UInt16 let ix: UInt16, iy: UInt16, sp: UInt16, pc: UInt16 let i: UInt8, r: UInt8 let iff1: Bool, iff2: Bool, interruptMode: Int let isHalted: Bool let memptr: UInt16 let tStates: Int func set(onMachine machine: CSTestMachineZ80) { machine.setValue(af, for: .AF) machine.setValue(bc, for: .BC) machine.setValue(de, for: .DE) machine.setValue(hl, for: .HL) machine.setValue(afDash, for: .afDash) machine.setValue(bcDash, for: .bcDash) machine.setValue(deDash, for: .deDash) machine.setValue(hlDash, for: .hlDash) machine.setValue(ix, for: .IX) machine.setValue(iy, for: .IY) machine.setValue(sp, for: .stackPointer) machine.setValue(pc, for: .programCounter) machine.setValue(UInt16(i), for: .I) machine.setValue(UInt16(r), for: .R) machine.setValue(iff1 ? 1 : 0, for: .IFF1) machine.setValue(iff2 ? 1 : 0, for: .IFF2) machine.setValue(UInt16(interruptMode), for: .IM) machine.setValue(memptr, for: .memPtr) // TODO: isHalted } /* Re: bits 3 and 5; the FUSE tests seem to be inconsistent with other documentation in expectations as to 5 and 3 from the FDCB and DDCB pages. So I've disabled 3 and 5 testing until I can make a value judgment. */ init(dictionary: [String: Any]) { // don't test bits 3 and 5 for now af = UInt16(truncating: dictionary["af"] as! NSNumber) bc = UInt16(truncating: dictionary["bc"] as! NSNumber) de = UInt16(truncating: dictionary["de"] as! NSNumber) hl = UInt16(truncating: dictionary["hl"] as! NSNumber) afDash = UInt16(truncating: dictionary["afDash"] as! NSNumber) bcDash = UInt16(truncating: dictionary["bcDash"] as! NSNumber) deDash = UInt16(truncating: dictionary["deDash"] as! NSNumber) hlDash = UInt16(truncating: dictionary["hlDash"] as! NSNumber) ix = UInt16(truncating: dictionary["ix"] as! NSNumber) iy = UInt16(truncating: dictionary["iy"] as! NSNumber) sp = UInt16(truncating: dictionary["sp"] as! NSNumber) pc = UInt16(truncating: dictionary["pc"] as! NSNumber) i = UInt8(truncating: dictionary["i"] as! NSNumber) r = UInt8(truncating: dictionary["r"] as! NSNumber) iff1 = (dictionary["iff1"] as! NSNumber).boolValue iff2 = (dictionary["iff2"] as! NSNumber).boolValue interruptMode = (dictionary["im"] as! NSNumber).intValue isHalted = (dictionary["halted"] as! NSNumber).boolValue memptr = UInt16(truncating: dictionary["memptr"] as! NSNumber) tStates = (dictionary["tStates"] as! NSNumber).intValue } init(machine: CSTestMachineZ80) { // don't test bits 3 and 5 for now af = machine.value(for: .AF) bc = machine.value(for: .BC) de = machine.value(for: .DE) hl = machine.value(for: .HL) afDash = machine.value(for: .afDash) bcDash = machine.value(for: .bcDash) deDash = machine.value(for: .deDash) hlDash = machine.value(for: .hlDash) ix = machine.value(for: .IX) iy = machine.value(for: .IY) sp = machine.value(for: .stackPointer) pc = machine.value(for: .programCounter) i = UInt8(machine.value(for: .I)) r = UInt8(machine.value(for: .R)) iff1 = machine.value(for: .IFF1) != 0 iff2 = machine.value(for: .IFF2) != 0 interruptMode = Int(machine.value(for: .IM)) isHalted = machine.isHalted tStates = 0 // TODO (?) memptr = machine.value(for: .memPtr) } } extension RegisterState: Equatable {} fileprivate func ==(lhs: RegisterState, rhs: RegisterState) -> Bool { return lhs.af == rhs.af && lhs.bc == rhs.bc && lhs.de == rhs.de && lhs.hl == rhs.hl && (lhs.afDash & ~0x0028) == (rhs.afDash & ~0x0028) && lhs.bcDash == rhs.bcDash && lhs.deDash == rhs.deDash && lhs.hlDash == rhs.hlDash && lhs.ix == rhs.ix && lhs.iy == rhs.iy && lhs.sp == rhs.sp && lhs.pc == rhs.pc && lhs.i == rhs.i && lhs.r == rhs.r && lhs.iff1 == rhs.iff1 && lhs.iff2 == rhs.iff2 && lhs.interruptMode == rhs.interruptMode && lhs.isHalted == rhs.isHalted && lhs.memptr == rhs.memptr } class FUSETests: XCTestCase { func testFUSE() { let inputFilename: String! = Bundle(for: type(of: self)).path(forResource: "tests.in", ofType: "json") let outputFilename: String! = Bundle(for: type(of: self)).path(forResource: "tests.expected", ofType: "json") XCTAssert(inputFilename != nil && outputFilename != nil) let inputData: Data! = try? Data(contentsOf: URL(fileURLWithPath: inputFilename)) let outputData: Data! = try? Data(contentsOf: URL(fileURLWithPath: outputFilename)) XCTAssert(inputData != nil && outputData != nil) let inputArray: [Any]! = try! JSONSerialization.jsonObject(with: inputData, options: []) as? [Any] let outputArray: [Any]! = try! JSONSerialization.jsonObject(with: outputData, options: []) as? [Any] XCTAssert(inputArray != nil && outputArray != nil) var index = 0 for item in inputArray { let itemDictionary = item as! [String: Any] let outputDictionary = outputArray[index] as! [String: Any] index = index + 1 let name = itemDictionary["name"] as! String // Provisionally skip the FUSE HALT test. It tests PC during a HALT; this emulator advances // it only upon interrupt, FUSE seems to increment it and then stay still. I need to find // out which of those is correct. if name == "76" { continue } // if name != "10" { // continue; // } // print("\(name)") let initialState = RegisterState(dictionary: itemDictionary["state"] as! [String: Any]) let targetState = RegisterState(dictionary: outputDictionary["state"] as! [String: Any]) let machine = CSTestMachineZ80() machine.portLogic = .returnUpperByte machine.captureBusActivity = true initialState.set(onMachine: machine) let inputMemoryGroups = itemDictionary["memory"] as? [Any] if let inputMemoryGroups = inputMemoryGroups { for group in inputMemoryGroups { let groupDictionary = group as! [String: Any] var address = UInt16(truncating: groupDictionary["address"] as! NSNumber) let data = groupDictionary["data"] as! [NSNumber] for value in data { machine.setValue(UInt8(truncating: value), atAddress: address) address = address + 1 } } } machine.runForNumber(ofCycles: Int32(targetState.tStates)) // Verify that exactly the right number of cycles was hit; this is a primitive cycle length tester. let halfCyclesRun = machine.completedHalfCycles XCTAssert(halfCyclesRun == Int32(targetState.tStates) * 2, "Instruction length off; was \(machine.completedHalfCycles) but should be \(targetState.tStates * 2): \(name)") let finalState = RegisterState(machine: machine) // Compare processor state. XCTAssertEqual(finalState, targetState, "Failed processor state \(name)") // Compare memory state. let outputMemoryGroups = outputDictionary["memory"] as? [Any] if let outputMemoryGroups = outputMemoryGroups { for group in outputMemoryGroups { let groupDictionary = group as! [String: Any] var address = UInt16(truncating: groupDictionary["address"] as! NSNumber) let data = groupDictionary["data"] as! [NSNumber] for value in data { XCTAssert(machine.value(atAddress: address) == UInt8(truncating: value), "Failed memory state \(name)") address = address + 1 } } } // Compare bus operations. // let capturedBusActivity = machine.busOperationCaptures // var capturedBusAcivityIndex = 0; // I presently believe the FUSE unit test bus results for DJNZ — opcode 0x10 — to be // in error by omitting the final offset read. Therefore I am skipping that. // TODO: enquire with the author. // if name == "10" { // continue // } /* let desiredBusActivity = outputDictionary["busActivity"] as? [[String: Any]] if let desiredBusActivity = desiredBusActivity { for action in desiredBusActivity { let type = action["type"] as! String let time = action["time"] as! Int32 let address = action["address"] as! UInt16 let value = action["value"] as? UInt8 if type == "MC" || type == "PC" { // Don't do anything with FUSE's contended memory records; it's // presently unclear to me exactly what they're supposed to communicate continue } // FUSE counts a memory access as occurring at the last cycle of its bus operation; // it counts a port access as occurring on the second. timeOffset is used to adjust // the FUSE numbers as required. var operation: CSTestMachineZ80BusOperationCaptureOperation = .read var alternativeOperation: CSTestMachineZ80BusOperationCaptureOperation = .read var timeOffset: Int32 = 0 switch type { case "MR": operation = .read alternativeOperation = .readOpcode case "MW": alternativeOperation = .write operation = .write case "PR": alternativeOperation = .portRead operation = .portRead timeOffset = 3 case "PW": alternativeOperation = .portWrite operation = .portWrite timeOffset = 3 default: print("Unhandled activity type \(type)!") } XCTAssert( capturedBusActivity[capturedBusAcivityIndex].address == address && capturedBusActivity[capturedBusAcivityIndex].value == value! && capturedBusActivity[capturedBusAcivityIndex].timeStamp == (time + timeOffset) && ( capturedBusActivity[capturedBusAcivityIndex].operation == operation || capturedBusActivity[capturedBusAcivityIndex].operation == alternativeOperation ), "Failed bus operation match \(name) (at time \(time) with address \(address), value was \(value != nil ? value! : 0), tracking index \(capturedBusAcivityIndex) amongst \(capturedBusActivity))") capturedBusAcivityIndex += 1 } }*/ } } }