mirror of
				https://github.com/TomHarte/CLK.git
				synced 2025-10-31 05:16:08 +00:00 
			
		
		
		
	Compare commits
	
		
			16 Commits
		
	
	
		
			2020-09-17
			...
			2020-10-02
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|  | dcf8cb14e2 | ||
|  | 38912859e1 | ||
|  | f4a23af5d6 | ||
|  | ce0536cdfa | ||
|  | 669d8e64ab | ||
|  | 9447aa38be | ||
|  | a781c3eb4d | ||
|  | c0b1308dfd | ||
|  | 2d9dd6704a | ||
|  | 94dba70bbe | ||
|  | 022ec20e75 | ||
|  | 41f69405d8 | ||
|  | 5741e22e29 | ||
|  | 8e242eea54 | ||
|  | 703065a0a5 | ||
|  | 291aa42fe1 | 
| @@ -128,13 +128,14 @@ template <class T> class MOS6522: public MOS6522Storage { | |||||||
|  |  | ||||||
| 		void access(int address); | 		void access(int address); | ||||||
|  |  | ||||||
| 		uint8_t get_port_input(Port port, uint8_t output_mask, uint8_t output); | 		uint8_t get_port_input(Port port, uint8_t output_mask, uint8_t output, uint8_t timer_mask); | ||||||
| 		inline void reevaluate_interrupts(); | 		inline void reevaluate_interrupts(); | ||||||
|  |  | ||||||
| 		/// Sets the current intended output value for the port and line; | 		/// Sets the current intended output value for the port and line; | ||||||
| 		/// if this affects the visible output, it will be passed to the handler. | 		/// if this affects the visible output, it will be passed to the handler. | ||||||
| 		void set_control_line_output(Port port, Line line, LineState value); | 		void set_control_line_output(Port port, Line line, LineState value); | ||||||
| 		void evaluate_cb2_output(); | 		void evaluate_cb2_output(); | ||||||
|  | 		void evaluate_port_b_output(); | ||||||
| }; | }; | ||||||
|  |  | ||||||
| } | } | ||||||
|   | |||||||
| @@ -43,7 +43,7 @@ template <typename T> void MOS6522<T>::write(int address, uint8_t value) { | |||||||
| 			registers_.output[1] = value; | 			registers_.output[1] = value; | ||||||
|  |  | ||||||
| 			bus_handler_.run_for(time_since_bus_handler_call_.flush<HalfCycles>()); | 			bus_handler_.run_for(time_since_bus_handler_call_.flush<HalfCycles>()); | ||||||
| 			bus_handler_.set_port_output(Port::B, value, registers_.data_direction[1]); | 			evaluate_port_b_output(); | ||||||
|  |  | ||||||
| 			registers_.interrupt_flags &= ~(InterruptFlag::CB1ActiveEdge | ((registers_.peripheral_control&0x20) ? 0 : InterruptFlag::CB2ActiveEdge)); | 			registers_.interrupt_flags &= ~(InterruptFlag::CB1ActiveEdge | ((registers_.peripheral_control&0x20) ? 0 : InterruptFlag::CB2ActiveEdge)); | ||||||
| 			reevaluate_interrupts(); | 			reevaluate_interrupts(); | ||||||
| @@ -85,6 +85,12 @@ template <typename T> void MOS6522<T>::write(int address, uint8_t value) { | |||||||
| 			registers_.next_timer[0] = registers_.timer_latch[0]; | 			registers_.next_timer[0] = registers_.timer_latch[0]; | ||||||
| 			timer_is_running_[0] = true; | 			timer_is_running_[0] = true; | ||||||
|  |  | ||||||
|  | 			// If PB7 output mode is active, set it low. | ||||||
|  | 			if(timer1_is_controlling_pb7()) { | ||||||
|  | 				registers_.timer_port_b_output &= 0x7f; | ||||||
|  | 				evaluate_port_b_output(); | ||||||
|  | 			} | ||||||
|  |  | ||||||
| 			// Clear existing interrupt flag. | 			// Clear existing interrupt flag. | ||||||
| 			registers_.interrupt_flags &= ~InterruptFlag::Timer1; | 			registers_.interrupt_flags &= ~InterruptFlag::Timer1; | ||||||
| 			reevaluate_interrupts(); | 			reevaluate_interrupts(); | ||||||
| @@ -113,6 +119,13 @@ template <typename T> void MOS6522<T>::write(int address, uint8_t value) { | |||||||
| 		case 0xb:	// Auxiliary control ('ACR'). | 		case 0xb:	// Auxiliary control ('ACR'). | ||||||
| 			registers_.auxiliary_control = value; | 			registers_.auxiliary_control = value; | ||||||
| 			evaluate_cb2_output(); | 			evaluate_cb2_output(); | ||||||
|  |  | ||||||
|  | 			// This is a bit of a guess: reset the timer-based PB7 output to its default high level | ||||||
|  | 			// any timer that timer-linked PB7 output is disabled. | ||||||
|  | 			if(!timer1_is_controlling_pb7()) { | ||||||
|  | 				registers_.timer_port_b_output |= 0x80; | ||||||
|  | 			} | ||||||
|  | 			evaluate_port_b_output(); | ||||||
| 		break; | 		break; | ||||||
| 		case 0xc: {	// Peripheral control ('PCR'). | 		case 0xc: {	// Peripheral control ('PCR'). | ||||||
| //			const auto old_peripheral_control = registers_.peripheral_control; | //			const auto old_peripheral_control = registers_.peripheral_control; | ||||||
| @@ -176,12 +189,12 @@ template <typename T> uint8_t MOS6522<T>::read(int address) { | |||||||
| 		case 0x0:	// Read Port B ('IRB'). | 		case 0x0:	// Read Port B ('IRB'). | ||||||
| 			registers_.interrupt_flags &= ~(InterruptFlag::CB1ActiveEdge | InterruptFlag::CB2ActiveEdge); | 			registers_.interrupt_flags &= ~(InterruptFlag::CB1ActiveEdge | InterruptFlag::CB2ActiveEdge); | ||||||
| 			reevaluate_interrupts(); | 			reevaluate_interrupts(); | ||||||
| 		return get_port_input(Port::B, registers_.data_direction[1], registers_.output[1]); | 		return get_port_input(Port::B, registers_.data_direction[1], registers_.output[1], registers_.auxiliary_control & 0x80); | ||||||
| 		case 0xf: | 		case 0xf: | ||||||
| 		case 0x1:	// Read Port A ('IRA'). | 		case 0x1:	// Read Port A ('IRA'). | ||||||
| 			registers_.interrupt_flags &= ~(InterruptFlag::CA1ActiveEdge | InterruptFlag::CA2ActiveEdge); | 			registers_.interrupt_flags &= ~(InterruptFlag::CA1ActiveEdge | InterruptFlag::CA2ActiveEdge); | ||||||
| 			reevaluate_interrupts(); | 			reevaluate_interrupts(); | ||||||
| 		return get_port_input(Port::A, registers_.data_direction[0], registers_.output[0]); | 		return get_port_input(Port::A, registers_.data_direction[0], registers_.output[0], 0); | ||||||
|  |  | ||||||
| 		case 0x2:	return registers_.data_direction[1];	// Port B direction ('DDRB'). | 		case 0x2:	return registers_.data_direction[1];	// Port B direction ('DDRB'). | ||||||
| 		case 0x3:	return registers_.data_direction[0];	// Port A direction ('DDRA'). | 		case 0x3:	return registers_.data_direction[0];	// Port A direction ('DDRA'). | ||||||
| @@ -218,9 +231,10 @@ template <typename T> uint8_t MOS6522<T>::read(int address) { | |||||||
| 	return 0xff; | 	return 0xff; | ||||||
| } | } | ||||||
|  |  | ||||||
| template <typename T> uint8_t MOS6522<T>::get_port_input(Port port, uint8_t output_mask, uint8_t output) { | template <typename T> uint8_t MOS6522<T>::get_port_input(Port port, uint8_t output_mask, uint8_t output, uint8_t timer_mask) { | ||||||
| 	bus_handler_.run_for(time_since_bus_handler_call_.flush<HalfCycles>()); | 	bus_handler_.run_for(time_since_bus_handler_call_.flush<HalfCycles>()); | ||||||
| 	const uint8_t input = bus_handler_.get_port_input(port); | 	const uint8_t input = bus_handler_.get_port_input(port); | ||||||
|  | 	output = (output & ~timer_mask) | (registers_.timer_port_b_output & timer_mask); | ||||||
| 	return (input & ~output_mask) | (output & output_mask); | 	return (input & ~output_mask) | (output & output_mask); | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -294,9 +308,9 @@ template <typename T> void MOS6522<T>::do_phase2() { | |||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	// Count down timer 2 if it is in timed interrupt mode (i.e. auxiliary control bit 5 is clear). | 	// Count down timer 2 if it is in timed interrupt mode (i.e. auxiliary control bit 5 is clear). | ||||||
| 	// TODO: implement count down on PB6 if this bit isn't set. | 	registers_.timer[1] -= timer2_clock_decrement(); | ||||||
| 	registers_.timer[1] -= 1 ^ ((registers_.auxiliary_control >> 5)&1); |  | ||||||
|  |  | ||||||
|  | 	// TODO: can eliminate conditional branches here. | ||||||
| 	if(registers_.next_timer[0] >= 0) { | 	if(registers_.next_timer[0] >= 0) { | ||||||
| 		registers_.timer[0] = uint16_t(registers_.next_timer[0]); | 		registers_.timer[0] = uint16_t(registers_.next_timer[0]); | ||||||
| 		registers_.next_timer[0] = -1; | 		registers_.next_timer[0] = -1; | ||||||
| @@ -347,20 +361,29 @@ template <typename T> void MOS6522<T>::do_phase1() { | |||||||
| 		reevaluate_interrupts(); | 		reevaluate_interrupts(); | ||||||
|  |  | ||||||
| 		// Determine whether to reload. | 		// Determine whether to reload. | ||||||
| 		if(registers_.auxiliary_control&0x40) | 		if(timer1_is_continuous()) | ||||||
| 			registers_.timer_needs_reload = true; | 			registers_.timer_needs_reload = true; | ||||||
| 		else | 		else | ||||||
| 			timer_is_running_[0] = false; | 			timer_is_running_[0] = false; | ||||||
|  |  | ||||||
| 		// Determine whether to toggle PB7. | 		// Determine whether to toggle PB7. | ||||||
| 		if(registers_.auxiliary_control&0x80) { | 		if(timer1_is_controlling_pb7()) { | ||||||
| 			registers_.output[1] ^= 0x80; | 			registers_.timer_port_b_output ^= 0x80; | ||||||
| 			bus_handler_.run_for(time_since_bus_handler_call_.flush<HalfCycles>()); | 			bus_handler_.run_for(time_since_bus_handler_call_.flush<HalfCycles>()); | ||||||
| 			bus_handler_.set_port_output(Port::B, registers_.output[1], registers_.data_direction[1]); | 			evaluate_port_b_output(); | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
|  | template <typename T> void MOS6522<T>::evaluate_port_b_output() { | ||||||
|  | 	// Apply current timer-linked PB7 output if any atop the stated output. | ||||||
|  | 	const uint8_t timer_control_bit = registers_.auxiliary_control & 0x80; | ||||||
|  | 	bus_handler_.set_port_output( | ||||||
|  | 		Port::B, | ||||||
|  | 		(registers_.output[1] & (0xff ^ timer_control_bit)) | timer_control_bit, | ||||||
|  | 		registers_.data_direction[1] | timer_control_bit); | ||||||
|  | } | ||||||
|  |  | ||||||
| /*! Runs for a specified number of half cycles. */ | /*! Runs for a specified number of half cycles. */ | ||||||
| template <typename T> void MOS6522<T>::run_for(const HalfCycles half_cycles) { | template <typename T> void MOS6522<T>::run_for(const HalfCycles half_cycles) { | ||||||
| 	auto number_of_half_cycles = half_cycles.as_integral(); | 	auto number_of_half_cycles = half_cycles.as_integral(); | ||||||
| @@ -455,10 +478,11 @@ template <typename T> void MOS6522<T>::shift_in() { | |||||||
| } | } | ||||||
|  |  | ||||||
| template <typename T> void MOS6522<T>::shift_out() { | template <typename T> void MOS6522<T>::shift_out() { | ||||||
| 	// When shifting out, the shift register rotates rather than strictly shifts. | 	const bool is_free_running = shift_mode() == ShiftMode::OutUnderT2FreeRunning; | ||||||
| 	// TODO: is that true for all modes? | 	if(is_free_running || shift_bits_remaining_) { | ||||||
| 	if(shift_mode() == ShiftMode::OutUnderT2FreeRunning || shift_bits_remaining_) { | 		// Recirculate bits only if in free-running mode (?) | ||||||
| 		registers_.shift = uint8_t((registers_.shift << 1) | (registers_.shift >> 7)); | 		const uint8_t incoming_bit = (registers_.shift >> 7) * is_free_running; | ||||||
|  | 		registers_.shift = uint8_t(registers_.shift << 1) | incoming_bit; | ||||||
| 		evaluate_cb2_output(); | 		evaluate_cb2_output(); | ||||||
|  |  | ||||||
| 		--shift_bits_remaining_; | 		--shift_bits_remaining_; | ||||||
|   | |||||||
| @@ -34,7 +34,9 @@ class MOS6522Storage { | |||||||
| 			uint8_t peripheral_control = 0; | 			uint8_t peripheral_control = 0; | ||||||
| 			uint8_t interrupt_flags = 0; | 			uint8_t interrupt_flags = 0; | ||||||
| 			uint8_t interrupt_enable = 0; | 			uint8_t interrupt_enable = 0; | ||||||
|  |  | ||||||
| 			bool timer_needs_reload = false; | 			bool timer_needs_reload = false; | ||||||
|  | 			uint8_t timer_port_b_output = 0xff; | ||||||
| 		} registers_; | 		} registers_; | ||||||
|  |  | ||||||
| 		// Control state. | 		// Control state. | ||||||
| @@ -79,12 +81,30 @@ class MOS6522Storage { | |||||||
| 			OutUnderPhase2 = 6, | 			OutUnderPhase2 = 6, | ||||||
| 			OutUnderCB1 = 7 | 			OutUnderCB1 = 7 | ||||||
| 		}; | 		}; | ||||||
| 		ShiftMode shift_mode() const { | 		bool timer1_is_controlling_pb7() const { | ||||||
| 			return ShiftMode((registers_.auxiliary_control >> 2) & 7); | 			return registers_.auxiliary_control & 0x80; | ||||||
|  | 		} | ||||||
|  | 		bool timer1_is_continuous() const { | ||||||
|  | 			return registers_.auxiliary_control & 0x40; | ||||||
| 		} | 		} | ||||||
| 		bool is_shifting_out() const { | 		bool is_shifting_out() const { | ||||||
| 			return registers_.auxiliary_control & 0x10; | 			return registers_.auxiliary_control & 0x10; | ||||||
| 		} | 		} | ||||||
|  | 		int timer2_clock_decrement() const { | ||||||
|  | 			return 1 ^ ((registers_.auxiliary_control >> 5)&1); | ||||||
|  | 		} | ||||||
|  | 		int timer2_pb6_decrement() const { | ||||||
|  | 			return (registers_.auxiliary_control >> 5)&1; | ||||||
|  | 		} | ||||||
|  | 		ShiftMode shift_mode() const { | ||||||
|  | 			return ShiftMode((registers_.auxiliary_control >> 2) & 7); | ||||||
|  | 		} | ||||||
|  | 		bool portb_is_latched() const { | ||||||
|  | 			return registers_.auxiliary_control & 0x02; | ||||||
|  | 		} | ||||||
|  | 		bool port1_is_latched() const { | ||||||
|  | 			return registers_.auxiliary_control & 0x01; | ||||||
|  | 		} | ||||||
| }; | }; | ||||||
|  |  | ||||||
| } | } | ||||||
|   | |||||||
| @@ -860,6 +860,7 @@ | |||||||
| 		4BEF6AAC1D35D1C400E73575 /* DPLLTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BEF6AAB1D35D1C400E73575 /* DPLLTests.swift */; }; | 		4BEF6AAC1D35D1C400E73575 /* DPLLTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BEF6AAB1D35D1C400E73575 /* DPLLTests.swift */; }; | ||||||
| 		4BF437EE209D0F7E008CBD6B /* SegmentParser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BF437EC209D0F7E008CBD6B /* SegmentParser.cpp */; }; | 		4BF437EE209D0F7E008CBD6B /* SegmentParser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BF437EC209D0F7E008CBD6B /* SegmentParser.cpp */; }; | ||||||
| 		4BF437EF209D0F7E008CBD6B /* SegmentParser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BF437EC209D0F7E008CBD6B /* SegmentParser.cpp */; }; | 		4BF437EF209D0F7E008CBD6B /* SegmentParser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BF437EC209D0F7E008CBD6B /* SegmentParser.cpp */; }; | ||||||
|  | 		4BF8D4C82516E27A00BBE21B /* Accelerate.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4BB8617024E22F4900A00E03 /* Accelerate.framework */; }; | ||||||
| 		4BFCA1241ECBDCB400AC40C1 /* AllRAMProcessor.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BFCA1211ECBDCAF00AC40C1 /* AllRAMProcessor.cpp */; }; | 		4BFCA1241ECBDCB400AC40C1 /* AllRAMProcessor.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BFCA1211ECBDCAF00AC40C1 /* AllRAMProcessor.cpp */; }; | ||||||
| 		4BFCA1271ECBE33200AC40C1 /* TestMachineZ80.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4BFCA1261ECBE33200AC40C1 /* TestMachineZ80.mm */; }; | 		4BFCA1271ECBE33200AC40C1 /* TestMachineZ80.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4BFCA1261ECBE33200AC40C1 /* TestMachineZ80.mm */; }; | ||||||
| 		4BFCA1291ECBE7A700AC40C1 /* zexall.com in Resources */ = {isa = PBXBuildFile; fileRef = 4BFCA1281ECBE7A700AC40C1 /* zexall.com */; }; | 		4BFCA1291ECBE7A700AC40C1 /* zexall.com in Resources */ = {isa = PBXBuildFile; fileRef = 4BFCA1281ECBE7A700AC40C1 /* zexall.com */; }; | ||||||
| @@ -1830,6 +1831,7 @@ | |||||||
| 			buildActionMask = 2147483647; | 			buildActionMask = 2147483647; | ||||||
| 			files = ( | 			files = ( | ||||||
| 				4B9F11CA2272433900701480 /* libz.tbd in Frameworks */, | 				4B9F11CA2272433900701480 /* libz.tbd in Frameworks */, | ||||||
|  | 				4BF8D4C82516E27A00BBE21B /* Accelerate.framework in Frameworks */, | ||||||
| 			); | 			); | ||||||
| 			runOnlyForDeploymentPostprocessing = 0; | 			runOnlyForDeploymentPostprocessing = 0; | ||||||
| 		}; | 		}; | ||||||
|   | |||||||
| @@ -67,7 +67,7 @@ | |||||||
|       </Testables> |       </Testables> | ||||||
|    </TestAction> |    </TestAction> | ||||||
|    <LaunchAction |    <LaunchAction | ||||||
|       buildConfiguration = "Release" |       buildConfiguration = "Debug" | ||||||
|       selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" |       selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" | ||||||
|       selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" |       selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" | ||||||
|       enableASanStackUseAfterReturn = "YES" |       enableASanStackUseAfterReturn = "YES" | ||||||
|   | |||||||
| @@ -278,8 +278,9 @@ using BufferingScanTarget = Outputs::Display::BufferingScanTarget; | |||||||
| 	size_t _chromaKernelSize; | 	size_t _chromaKernelSize; | ||||||
| 	std::atomic<bool> _isUsingSupersampling; | 	std::atomic<bool> _isUsingSupersampling; | ||||||
|  |  | ||||||
| 	// The output view. | 	// The output view and its aspect ratio. | ||||||
| 	__weak MTKView *_view; | 	__weak MTKView *_view; | ||||||
|  | 	CGFloat _viewAspectRatio;	// To avoid accessing .bounds away from the main thread. | ||||||
| } | } | ||||||
|  |  | ||||||
| - (nonnull instancetype)initWithView:(nonnull MTKView *)view { | - (nonnull instancetype)initWithView:(nonnull MTKView *)view { | ||||||
| @@ -357,6 +358,7 @@ using BufferingScanTarget = Outputs::Display::BufferingScanTarget; | |||||||
|  @param size New drawable size in pixels |  @param size New drawable size in pixels | ||||||
|  */ |  */ | ||||||
| - (void)mtkView:(nonnull MTKView *)view drawableSizeWillChange:(CGSize)size { | - (void)mtkView:(nonnull MTKView *)view drawableSizeWillChange:(CGSize)size { | ||||||
|  | 	_viewAspectRatio = size.width / size.height; | ||||||
| 	[self setAspectRatio]; | 	[self setAspectRatio]; | ||||||
|  |  | ||||||
| 	@synchronized(self) { | 	@synchronized(self) { | ||||||
| @@ -522,7 +524,6 @@ using BufferingScanTarget = Outputs::Display::BufferingScanTarget; | |||||||
|  |  | ||||||
| - (void)setAspectRatio { | - (void)setAspectRatio { | ||||||
| 	const auto modals = _scanTarget.modals(); | 	const auto modals = _scanTarget.modals(); | ||||||
| 	const auto viewAspectRatio = (_view.bounds.size.width / _view.bounds.size.height); |  | ||||||
| 	simd::float3x3 sourceToDisplay{1.0f}; | 	simd::float3x3 sourceToDisplay{1.0f}; | ||||||
|  |  | ||||||
| 	// The starting coordinate space is [0, 1]. | 	// The starting coordinate space is [0, 1]. | ||||||
| @@ -550,7 +551,7 @@ using BufferingScanTarget = Outputs::Display::BufferingScanTarget; | |||||||
| 	// Determine the correct zoom level. This is a combination of (i) the necessary horizontal stretch to produce a proper | 	// Determine the correct zoom level. This is a combination of (i) the necessary horizontal stretch to produce a proper | ||||||
| 	// aspect ratio; and (ii) the necessary zoom from there to either fit the visible area width or height as per a decision | 	// aspect ratio; and (ii) the necessary zoom from there to either fit the visible area width or height as per a decision | ||||||
| 	// on letterboxing or pillarboxing. | 	// on letterboxing or pillarboxing. | ||||||
| 	const float aspectRatioStretch = float(modals.aspect_ratio / viewAspectRatio); | 	const float aspectRatioStretch = float(modals.aspect_ratio / _viewAspectRatio); | ||||||
| 	const float fitWidthZoom = 1.0f / (float(modals.visible_area.size.width) * aspectRatioStretch); | 	const float fitWidthZoom = 1.0f / (float(modals.visible_area.size.width) * aspectRatioStretch); | ||||||
| 	const float fitHeightZoom = 1.0f / float(modals.visible_area.size.height); | 	const float fitHeightZoom = 1.0f / float(modals.visible_area.size.height); | ||||||
| 	const float zoom = std::min(fitWidthZoom, fitHeightZoom); | 	const float zoom = std::min(fitWidthZoom, fitHeightZoom); | ||||||
|   | |||||||
| @@ -200,7 +200,7 @@ class MOS6502TimingTests: XCTestCase, CSTestMachineTrapHandler { | |||||||
| 	func runTest(_ code: [UInt8], expectedRunLength: UInt32) { | 	func runTest(_ code: [UInt8], expectedRunLength: UInt32) { | ||||||
| 		machine.trapHandler = self | 		machine.trapHandler = self | ||||||
|  |  | ||||||
| 		let immediateCode = Data(bytes: UnsafePointer<UInt8>(code), count: code.count) | 		let immediateCode = Data(code) | ||||||
| 		machine.setData(immediateCode, atAddress: 0x200) | 		machine.setData(immediateCode, atAddress: 0x200) | ||||||
| 		machine.addTrapAddress(UInt16(0x200 + code.count)) | 		machine.addTrapAddress(UInt16(0x200 + code.count)) | ||||||
| 		machine.setValue(0x00, forAddress: 0x0000) | 		machine.setValue(0x00, forAddress: 0x0000) | ||||||
|   | |||||||
| @@ -101,6 +101,116 @@ class MOS6522Tests: XCTestCase { | |||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  |  | ||||||
|  | 	// MARK: PB7 timer 1 tests | ||||||
|  | 	// These follow the same logic and check for the same results as the VICE VIC-20 via_pb7 tests. | ||||||
|  |  | ||||||
|  | 	// Perfoms: | ||||||
|  | 	// | ||||||
|  | 	//	(1) establish initial ACR and port B output value, and grab port B input value. | ||||||
|  | 	//	(2) start timer 1, grab port B input value. | ||||||
|  | 	//	(3) set final ACR, grab port B input value. | ||||||
|  | 	//	(4) allow timer 1 to expire, grab port B input value. | ||||||
|  | 	private func runTest(startACR: UInt8, endACR: UInt8, portBOutput: UInt8) -> [UInt8] { | ||||||
|  | 		var result: [UInt8] = [] | ||||||
|  |  | ||||||
|  | 		// Clear all register values. | ||||||
|  | 		for n: UInt in 0...15 { | ||||||
|  | 			m6522.setValue(0, forRegister: n) | ||||||
|  | 		} | ||||||
|  | 		m6522.run(forHalfCycles: 2) | ||||||
|  |  | ||||||
|  | 		// Set data direction and current port B value. | ||||||
|  | 		m6522.setValue(0xff, forRegister: 2) | ||||||
|  | 		m6522.run(forHalfCycles: 2) | ||||||
|  | 		m6522.setValue(portBOutput, forRegister: 0) | ||||||
|  | 		m6522.run(forHalfCycles: 2) | ||||||
|  |  | ||||||
|  | 		// Set initial ACR and grab the current port B value. | ||||||
|  | 		m6522.setValue(startACR, forRegister: 0xb) | ||||||
|  | 		m6522.run(forHalfCycles: 2) | ||||||
|  | 		result.append(m6522.value(forRegister: 0)) | ||||||
|  | 		m6522.run(forHalfCycles: 2) | ||||||
|  |  | ||||||
|  | 		// Start timer 1 and grab the value. | ||||||
|  | 		m6522.setValue(1, forRegister: 0x5) | ||||||
|  | 		m6522.run(forHalfCycles: 2) | ||||||
|  | 		result.append(m6522.value(forRegister: 0)) | ||||||
|  | 		m6522.run(forHalfCycles: 2) | ||||||
|  |  | ||||||
|  | 		// Set the final ACR value and grab value. | ||||||
|  | 		m6522.setValue(endACR, forRegister: 0xb) | ||||||
|  | 		m6522.run(forHalfCycles: 2) | ||||||
|  | 		result.append(m6522.value(forRegister: 0)) | ||||||
|  | 		m6522.run(forHalfCycles: 2) | ||||||
|  |  | ||||||
|  | 		// Make sure timer 1 has expired. | ||||||
|  | 		m6522.run(forHalfCycles: 512) | ||||||
|  |  | ||||||
|  | 		// Grab the final value. | ||||||
|  | 		result.append(m6522.value(forRegister: 0)) | ||||||
|  |  | ||||||
|  | 		return result | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	func testTimer1PB7() { | ||||||
|  | 		// Original top row. [original Vic-20 screen output in comments on the right] | ||||||
|  | 		XCTAssertEqual(runTest(startACR: 0x00, endACR: 0x00, portBOutput: 0x00), [0x00, 0x00, 0x00, 0x00])	// @@@@	(i.e. 0, 0, 0, 0) | ||||||
|  | 		XCTAssertEqual(runTest(startACR: 0x00, endACR: 0x40, portBOutput: 0x00), [0x00, 0x00, 0x00, 0x00])	// @@@@	(i.e. 0, 0, 0, 0) | ||||||
|  | 		XCTAssertEqual(runTest(startACR: 0x00, endACR: 0x80, portBOutput: 0x00), [0x00, 0x00, 0x80, 0x00])	// @@b@	(i.e. 0, 0, 1, 0) | ||||||
|  | 		XCTAssertEqual(runTest(startACR: 0x00, endACR: 0xc0, portBOutput: 0x00), [0x00, 0x00, 0x80, 0x00])	// @@b@	(i.e. 0, 0, 1, 0) | ||||||
|  |  | ||||||
|  | 		XCTAssertEqual(runTest(startACR: 0x00, endACR: 0x00, portBOutput: 0xff), [0xff, 0xff, 0xff, 0xff])	// cccc	(i.e. 1, 1, 1, 1) | ||||||
|  | 		XCTAssertEqual(runTest(startACR: 0x00, endACR: 0x40, portBOutput: 0xff), [0xff, 0xff, 0xff, 0xff])	// cccc	(i.e. 1, 1, 1, 1) | ||||||
|  | 		XCTAssertEqual(runTest(startACR: 0x00, endACR: 0x80, portBOutput: 0xff), [0xff, 0xff, 0xff, 0x7f])	// ccca	(i.e. 1, 1, 1, 0) | ||||||
|  | 		XCTAssertEqual(runTest(startACR: 0x00, endACR: 0xc0, portBOutput: 0xff), [0xff, 0xff, 0xff, 0x7f])	// ccca	(i.e. 1, 1, 1, 0) | ||||||
|  |  | ||||||
|  | 		// Second row.	[same output as first row] | ||||||
|  | 		XCTAssertEqual(runTest(startACR: 0x40, endACR: 0x00, portBOutput: 0x00), [0x00, 0x00, 0x00, 0x00])	// @@@@ | ||||||
|  | 		XCTAssertEqual(runTest(startACR: 0x40, endACR: 0x40, portBOutput: 0x00), [0x00, 0x00, 0x00, 0x00])	// @@@@ | ||||||
|  | 		XCTAssertEqual(runTest(startACR: 0x40, endACR: 0x80, portBOutput: 0x00), [0x00, 0x00, 0x80, 0x00])	// @@b@ | ||||||
|  | 		XCTAssertEqual(runTest(startACR: 0x40, endACR: 0xc0, portBOutput: 0x00), [0x00, 0x00, 0x80, 0x00])	// @@b@ | ||||||
|  |  | ||||||
|  | 		XCTAssertEqual(runTest(startACR: 0x40, endACR: 0x00, portBOutput: 0xff), [0xff, 0xff, 0xff, 0xff])	// cccc | ||||||
|  | 		XCTAssertEqual(runTest(startACR: 0x40, endACR: 0x40, portBOutput: 0xff), [0xff, 0xff, 0xff, 0xff])	// cccc | ||||||
|  | 		XCTAssertEqual(runTest(startACR: 0x40, endACR: 0x80, portBOutput: 0xff), [0xff, 0xff, 0xff, 0x7f])	// ccca | ||||||
|  | 		XCTAssertEqual(runTest(startACR: 0x40, endACR: 0xc0, portBOutput: 0xff), [0xff, 0xff, 0xff, 0x7f])	// ccca | ||||||
|  |  | ||||||
|  | 		// Third row. | ||||||
|  | 		XCTAssertEqual(runTest(startACR: 0x80, endACR: 0x00, portBOutput: 0x00), [0x80, 0x00, 0x00, 0x00])	// b@@@	(i.e. 1, 0, 0, 0) | ||||||
|  | 		XCTAssertEqual(runTest(startACR: 0x80, endACR: 0x40, portBOutput: 0x00), [0x80, 0x00, 0x00, 0x00])	// b@@@	(i.e. 1, 0, 0, 0) | ||||||
|  | 		XCTAssertEqual(runTest(startACR: 0x80, endACR: 0x80, portBOutput: 0x00), [0x80, 0x00, 0x00, 0x80])	// b@@b	(i.e. 1, 0, 0, 1) | ||||||
|  | 		XCTAssertEqual(runTest(startACR: 0x80, endACR: 0xc0, portBOutput: 0x00), [0x80, 0x00, 0x00, 0x80])	// b@@b	(i.e. 1, 0, 0, 1) | ||||||
|  |  | ||||||
|  | 		XCTAssertEqual(runTest(startACR: 0x80, endACR: 0x00, portBOutput: 0xff), [0xff, 0x7f, 0xff, 0xff])	// cacc	(i.e. 1, 0, 1, 1) | ||||||
|  | 		XCTAssertEqual(runTest(startACR: 0x80, endACR: 0x40, portBOutput: 0xff), [0xff, 0x7f, 0xff, 0xff])	// cacc	(i.e. 1, 0, 1, 1) | ||||||
|  | 		XCTAssertEqual(runTest(startACR: 0x80, endACR: 0x80, portBOutput: 0xff), [0xff, 0x7f, 0x7f, 0xff])	// caac	(i.e. 1, 0, 0, 1) | ||||||
|  | 		XCTAssertEqual(runTest(startACR: 0x80, endACR: 0xc0, portBOutput: 0xff), [0xff, 0x7f, 0x7f, 0xff])	// caac	(i.e. 1, 0, 0, 1) | ||||||
|  |  | ||||||
|  | 		// Final row.	[same output as third row] | ||||||
|  | 		XCTAssertEqual(runTest(startACR: 0xc0, endACR: 0x00, portBOutput: 0x00), [0x80, 0x00, 0x00, 0x00])	// b@@@ | ||||||
|  | 		XCTAssertEqual(runTest(startACR: 0xc0, endACR: 0x40, portBOutput: 0x00), [0x80, 0x00, 0x00, 0x00])	// b@@@ | ||||||
|  | 		XCTAssertEqual(runTest(startACR: 0xc0, endACR: 0x80, portBOutput: 0x00), [0x80, 0x00, 0x00, 0x80])	// b@@b | ||||||
|  | 		XCTAssertEqual(runTest(startACR: 0xc0, endACR: 0xc0, portBOutput: 0x00), [0x80, 0x00, 0x00, 0x80])	// b@@b | ||||||
|  |  | ||||||
|  | 		XCTAssertEqual(runTest(startACR: 0xc0, endACR: 0x00, portBOutput: 0xff), [0xff, 0x7f, 0xff, 0xff])	// cacc | ||||||
|  | 		XCTAssertEqual(runTest(startACR: 0xc0, endACR: 0x40, portBOutput: 0xff), [0xff, 0x7f, 0xff, 0xff])	// cacc | ||||||
|  | 		XCTAssertEqual(runTest(startACR: 0xc0, endACR: 0x80, portBOutput: 0xff), [0xff, 0x7f, 0x7f, 0xff])	// caac | ||||||
|  | 		XCTAssertEqual(runTest(startACR: 0xc0, endACR: 0xc0, portBOutput: 0xff), [0xff, 0x7f, 0x7f, 0xff])	// caac | ||||||
|  |  | ||||||
|  | 		// Conclusions: | ||||||
|  | 		// | ||||||
|  | 		//	after inital ACR and port B value:	[original data if not in PB7 output mode, otherwise 1] | ||||||
|  | 		//	after starting timer 1: 			[original data if not in PB7 output mode, otherwise 0] | ||||||
|  | 		//	after final ACR value:				[original data if not in PB7 output mode, 1 if has transitioned to PB7 mode, 0 if was already in PB7 mode] | ||||||
|  | 		//	after timer 1 expiry:				[original data if not in PB7 mode, 1 if timer has expired while in PB7 mode] | ||||||
|  | 		// | ||||||
|  | 		//	i.e. | ||||||
|  | 		//		(1)	there is separate storage for the programmer-set PB7 and the timer output; | ||||||
|  | 		//		(2)	the timer output is reset upon a timer write only if PB7 output is enabled; | ||||||
|  | 		//		(3)	expiry toggles the output. | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  |  | ||||||
| 	// MARK: Data direction tests | 	// MARK: Data direction tests | ||||||
| 	func testDataDirection() { | 	func testDataDirection() { | ||||||
| 		// set low four bits of register B as output, the top four as input | 		// set low four bits of register B as output, the top four as input | ||||||
|   | |||||||
| @@ -70,12 +70,9 @@ | |||||||
|  |  | ||||||
| - (void)testSeekToSecondBit { | - (void)testSeekToSecondBit { | ||||||
| 	Storage::Disk::PCMSegmentEventSource segmentSource = self.segmentSource; | 	Storage::Disk::PCMSegmentEventSource segmentSource = self.segmentSource; | ||||||
| 	Storage::Time target_time(1, 10); |  | ||||||
|  |  | ||||||
| 	Storage::Time found_time = segmentSource.seek_to(target_time); | 	const float found_time = segmentSource.seek_to(1.0f / 10.0f); | ||||||
| 	found_time.simplify(); | 	XCTAssertTrue(fabsf(found_time - 1.0f / 20.0f) < 0.01f, @"A request to seek to 1/10th should have seeked to 1/20th"); | ||||||
|  |  | ||||||
| 	XCTAssertTrue(found_time.length == 1 && found_time.clock_rate == 20, @"A request to seek to 1/10th should have seeked to 1/20th"); |  | ||||||
|  |  | ||||||
| 	Storage::Disk::Track::Event next_event = segmentSource.get_next_event(); | 	Storage::Disk::Track::Event next_event = segmentSource.get_next_event(); | ||||||
| 	next_event.length.simplify(); | 	next_event.length.simplify(); | ||||||
| @@ -85,12 +82,9 @@ | |||||||
|  |  | ||||||
| - (void)testSeekBeyondFinalBit { | - (void)testSeekBeyondFinalBit { | ||||||
| 	Storage::Disk::PCMSegmentEventSource segmentSource = self.segmentSource; | 	Storage::Disk::PCMSegmentEventSource segmentSource = self.segmentSource; | ||||||
| 	Storage::Time target_time(24, 10); | 	const float found_time = segmentSource.seek_to(2.4f); | ||||||
|  |  | ||||||
| 	Storage::Time found_time = segmentSource.seek_to(target_time); | 	XCTAssertTrue(fabsf(found_time - 47.0f / 20.0f) < 0.01f, @"A request to seek to 24/10ths should have seeked to 47/20ths"); | ||||||
| 	found_time.simplify(); |  | ||||||
|  |  | ||||||
| 	XCTAssertTrue(found_time.length == 47 && found_time.clock_rate == 20, @"A request to seek to 24/10ths should have seeked to 47/20ths"); |  | ||||||
|  |  | ||||||
| 	Storage::Disk::Track::Event next_event = segmentSource.get_next_event(); | 	Storage::Disk::Track::Event next_event = segmentSource.get_next_event(); | ||||||
| 	next_event.length.simplify(); | 	next_event.length.simplify(); | ||||||
|   | |||||||
| @@ -73,16 +73,14 @@ | |||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	Storage::Disk::PCMTrack track(segments); | 	Storage::Disk::PCMTrack track(segments); | ||||||
| 	Storage::Time late_time(967445, 2045454); | 	const float late_time = 967445.0f / 2045454.0f; | ||||||
| 	const auto offset = track.seek_to(late_time); | 	const auto offset = track.seek_to(late_time); | ||||||
| 	XCTAssert(offset <= late_time, "Found location should be at or before sought time"); | 	XCTAssert(offset <= late_time, "Found location should be at or before sought time"); | ||||||
|  |  | ||||||
| 	const auto difference = late_time - offset; | 	const auto difference = late_time - offset; | ||||||
| 	const double difference_duration = difference.get<double>(); | 	XCTAssert(difference >= 0.0 && difference < 0.005, "Next event should occur soon"); | ||||||
| 	XCTAssert(difference_duration >= 0.0 && difference_duration < 0.005, "Next event should occur soon"); |  | ||||||
|  |  | ||||||
| 	const double offset_duration = offset.get<double>(); | 	XCTAssert(offset >= 0.0 && offset < 0.5, "Next event should occur soon"); | ||||||
| 	XCTAssert(offset_duration >= 0.0 && offset_duration < 0.5, "Next event should occur soon"); |  | ||||||
|  |  | ||||||
| 	auto next_event = track.get_next_event(); | 	auto next_event = track.get_next_event(); | ||||||
| 	double next_event_duration = next_event.length.get<double>(); | 	double next_event_duration = next_event.length.get<double>(); | ||||||
|   | |||||||
| @@ -812,7 +812,7 @@ void MainWindow::setWindowTitle() { | |||||||
| 		break; | 		break; | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	if(mouseIsCaptured) title += " (press control+escape to release mouse)"; | 	if(mouseIsCaptured) title += " (press control+escape or F8+F12 to release mouse)"; | ||||||
|  |  | ||||||
| 	QMainWindow::setWindowTitle(title); | 	QMainWindow::setWindowTitle(title); | ||||||
| } | } | ||||||
|   | |||||||
| @@ -142,12 +142,25 @@ void ScanTargetWidget::setMouseDelegate(MouseDelegate *delegate) { | |||||||
| 	setMouseTracking(delegate); | 	setMouseTracking(delegate); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | void ScanTargetWidget::keyReleaseEvent(QKeyEvent *event) { | ||||||
|  | 	// Releasing F8 or F12 needs to be tracked but doesn't actively do anything, | ||||||
|  | 	// so I'm counting that as a Qt ignore. | ||||||
|  | 	if(event->key() == Qt::Key_F8) f8State = false; | ||||||
|  | 	if(event->key() == Qt::Key_F12) f12State = false; | ||||||
|  | 	event->ignore(); | ||||||
|  | } | ||||||
|  |  | ||||||
| void ScanTargetWidget::keyPressEvent(QKeyEvent *event) { | void ScanTargetWidget::keyPressEvent(QKeyEvent *event) { | ||||||
| 	// Use CTRL+Escape to end mouse captured mode, if currently captured; otherwise ignore the event. | 	// Use either CTRL+Escape or F8+F12 to end mouse captured mode, if currently captured; | ||||||
| 	// Empirical note: control actually appears to mean command on the Mac. I have no idea what the | 	// otherwise ignore the event. | ||||||
| 	// Mac's command key would actually be as a modifier. Fingers crossed control means control |  | ||||||
| 	// elsewhere (?). | 	if(event->key() == Qt::Key_F8) f8State = true; | ||||||
| 	if(mouseIsCaptured && event->key() == Qt::Key_Escape && event->modifiers()&Qt::ControlModifier) { | 	if(event->key() == Qt::Key_F12) f12State = true; | ||||||
|  |  | ||||||
|  | 	if(mouseIsCaptured && ( | ||||||
|  | 		(event->key() == Qt::Key_Escape && event->modifiers()&Qt::ControlModifier) || | ||||||
|  | 		(f8State && f12State) | ||||||
|  | 	)) { | ||||||
| 		releaseMouse(); | 		releaseMouse(); | ||||||
|  |  | ||||||
| 		QCursor cursor; | 		QCursor cursor; | ||||||
|   | |||||||
| @@ -42,6 +42,7 @@ class ScanTargetWidget : public QOpenGLWidget { | |||||||
| 		void mouseReleaseEvent(QMouseEvent *) override; | 		void mouseReleaseEvent(QMouseEvent *) override; | ||||||
| 		void mouseMoveEvent(QMouseEvent *) override; | 		void mouseMoveEvent(QMouseEvent *) override; | ||||||
| 		void keyPressEvent(QKeyEvent *) override; | 		void keyPressEvent(QKeyEvent *) override; | ||||||
|  | 		void keyReleaseEvent(QKeyEvent *) override; | ||||||
|  |  | ||||||
| 		void releaseMouse(); | 		void releaseMouse(); | ||||||
| 		void setMouseButtonPressed(Qt::MouseButton, bool); | 		void setMouseButtonPressed(Qt::MouseButton, bool); | ||||||
| @@ -66,6 +67,7 @@ class ScanTargetWidget : public QOpenGLWidget { | |||||||
|  |  | ||||||
| 		MouseDelegate *mouseDelegate = nullptr; | 		MouseDelegate *mouseDelegate = nullptr; | ||||||
| 		bool mouseIsCaptured = false; | 		bool mouseIsCaptured = false; | ||||||
|  | 		bool f8State = false, f12State = false;	// To support F8+F12 as a mouse release combination. | ||||||
|  |  | ||||||
| 	private slots: | 	private slots: | ||||||
| 		void vsync(); | 		void vsync(); | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user