private ExitConditionException NotifyLifecycleEvent(LifecycleEventType eventType) { ExitConditionException pendingInterruption = null; try { switch (eventType) { case LifecycleEventType.HalfTState: if (HalfTState != null) { HalfTState(new InternalState(this), eventType); } break; case LifecycleEventType.InstructionEnd: if (InstructionEnd != null) { InstructionEnd(new InternalState(this), eventType); } break; case LifecycleEventType.InstructionStart: if (InstructionStart != null) { InstructionStart(new InternalState(this), eventType); } break; case LifecycleEventType.MachineCycleEnd: if (MachineCycleEnd != null) { MachineCycleEnd(new InternalState(this), eventType); } break; case LifecycleEventType.MachineCycleStart: if (MachineCycleStart != null) { MachineCycleStart(new InternalState(this), eventType); } break; } } catch (ExitConditionException e) { pendingInterruption = e; } return(pendingInterruption); }
private ExitConditionException NotifyLifecycleEvent(LifecycleEventType eventType) { ExitConditionException pendingInterruption = null; try { switch (eventType) { case LifecycleEventType.ClockCycle: if (ClockCycle != null) { ClockCycle(this, new InternalState(this), eventType); } break; } } catch (ExitConditionException e) { pendingInterruption = e; } return(pendingInterruption); }
// Main entry point : logic executed on each edge of the clock signal public void CLK_OnEdge() { // Count T States if (halfTStateIndex % 2 == 0) { tStateCounter++; } // Debug interface : breakpoints management ExitConditionException pendingExitException = null; // Start a new instruction if no instruction is currently in progress if (currentInstruction == null) { // If RESET Pin is active, execute a special reset "instruction" if (RESET == SignalState.LOW) { ResetCPUControlState(); StartInstruction(Instruction.RESET); } // If a non maskable interrupt is pending, execute NMI instruction else if (nonMaskableInterruptPending) { nonMaskableInterruptPending = false; StartInstruction(Instruction.NMI); } // If a maskable interrupt is pending, execute INT instruction else if (maskableInterruptPending) { maskableInterruptPending = false; switch (IM) { case 0: StartInstruction(Instruction.INT0); break; case 1: StartInstruction(Instruction.INT1); break; case 2: StartInstruction(Instruction.INT2); break; } } // If no external CPU control Pin is active, read the next instruction opcode in memory else { StartInstruction(Instruction.FetchNewInstruction); } // Debug notification if (pendingExitException == null) { pendingExitException = NotifyLifecycleEvent(LifecycleEventType.InstructionStart); } } // Start a new machine cycle if no machine cycle is currently in progress if (currentMachineCycle == null) { // If a Bus Request is pending, start a special Bus Request Acknowledge machine cycle if (busRequestPending) { busRequestPending = false; StartMachineCycle(MachineCycle.BusRequestAcknowledge); } // The following machine cycles are defined by the documentation of the current instruction else { MachineCycle nextMachineCycle = currentInstruction.ExecutionTimings.MachineCycles[machineCycleIndex]; StartMachineCycle(nextMachineCycle); } // Debug notification if (pendingExitException == null) { pendingExitException = NotifyLifecycleEvent(LifecycleEventType.MachineCycleStart); } } // Execute the current machine cycle logic during one halfTState executeMachineCycle(halfTStateIndex); // Check if the current machine cycle was a bus request bool machineCycleWasBusRequest = currentMachineCycle.Type == MachineCycleType.BRQA; // Check if this halfTState is the rising edge of the last clock period of this machine cycle bool isRisingEdgeOfLastClockPeriodOfCurrentMachineCycle = (halfTStateIndex == currentMachineCycle.PenultimateHalfTStateIndex); // Check if this halfTState is the last one for the current machine cycle bool isLastHalfTStateOfCurrentMachineCycle = (halfTStateIndex == currentMachineCycle.LastHalfTStateIndex); // Check if the current machine cycle was the last machine cycle of the instruction bool isLastMachineCycleOfCurrentInstruction = !machineCycleWasBusRequest && (machineCycleIndex == currentInstruction.LastMachineCycleIndex); // Sample BUSREQ, NMI, and INT signals at the rising edge of the last clock period of any machine cycle if (isRisingEdgeOfLastClockPeriodOfCurrentMachineCycle) { SampleBusControlSignals(); if (isLastMachineCycleOfCurrentInstruction) { SampleInterruptControlSignals(); } } // Debug notification if (pendingExitException == null) { pendingExitException = NotifyLifecycleEvent(LifecycleEventType.HalfTState); } // If machine cycle is finished if (isLastHalfTStateOfCurrentMachineCycle) { // Execute one last time the machine cycle logic // to release buses and controls signals before the next machine cycle executeMachineCycle((byte)(halfTStateIndex + 1)); // Debug notification if (pendingExitException == null) { pendingExitException = NotifyLifecycleEvent(LifecycleEventType.MachineCycleEnd); } EndMachineCycle(); } // If instruction is finished if (isLastMachineCycleOfCurrentInstruction && isLastHalfTStateOfCurrentMachineCycle) { // Debug notification if (pendingExitException == null) { pendingExitException = NotifyLifecycleEvent(LifecycleEventType.InstructionEnd); } EndInstruction(); } // Count half T states whithin one machine cycle if (isLastHalfTStateOfCurrentMachineCycle) { halfTStateIndex = 0; } else { halfTStateIndex++; } // Count machine cycles within one instruction if (isLastHalfTStateOfCurrentMachineCycle) { if (isLastMachineCycleOfCurrentInstruction) { machineCycleIndex = 0; machineCycleCountAfterInstruction = 0; } else { if (!machineCycleWasBusRequest) // Bus request "steals" machine cycles from the instruction { machineCycleIndex++; if (currentInstruction != Instruction.FetchNewInstruction) // Incremented only after instruction decoding { machineCycleCountAfterInstruction++; } } } } // Debug : if a breakpoint was hit, notify the system clock via a specific exception if (pendingExitException != null) { throw pendingExitException; } }
/// <summary> /// All ULA timing states are synchronized to this internal 7MHz clock. /// </summary> public void ExecuteOnPixelClock() { // Debug interface : breakpoints management ExitConditionException pendingExitException = null; // Video signals generation is aligned on a 16 pixels pattern int videoMem16StepsAccessPatternIndex = column % 16; // --- CPU/ULA video memory contention // The ULA accesses video memory from pixels 8 to 15 // outside the border generation periods. To make sure // no CPU memory or IO access can overlap this period // we must stop any CPU memory or IO instruction starting // after pixel 4 and halt the CPU clock until next pixel 0. switch (videoMem16StepsAccessPatternIndex) { case 0: videoMemAccessTimeFrame = false; break; case 4: if (!generateBorder) { videoMemAccessTimeFrame = true; } break; } if (videoMemAccessTimeFrame) { if (!haltCpuClock) // No need to check for CPU memory or IO requests while it is already halted { if ( // CPU is about to access the video memory // CPU is about to access the ULA IO port cpuMemoryOrIORequestHalfTState == 1) { haltCpuClock = true; } } } else { if (haltCpuClock) { haltCpuClock = false; } } // --- CPU interrupt if (line == 248) { if (column == 0 /* Offset : because of the time necessary to read the first pixels in video memory, color output begins only 13 cycles after the master counter */ + 13) { CpuINT = SignalState.LOW; } else if (column == 32 /* Offset : because of the time necessary to read the first pixels in video memory, color output begins only 13 cycles after the master counter */ + 13) { CpuINT = SignalState.HIGH; } } // Connect TapeInput and SoundOutput if (TapeInputSignal.Level == 1) { SpeakerSoundSignal.Level = 1; } else if (!soundOutputWasSetByTheCPU) { SpeakerSoundSignal.Level = 0; } if (!haltCpuClock) { // --- CPU clock CpuCLK = PixelCLK; // C0 is directly used to drive CPU clock, but CPU T state starts with High state while ULA counter should start with C0 low // Check for CPU memory or IO access that could interfere with ULA operations if (cpuMemoryOrIORequestHalfTState == 0) { // CPU is about to access the video memory bool cpuMemoryOrIORequestToVideoAddress = (Address.SampleValue() & A15A14) == VideoMem; // CPU is about to access the ULA IO port cpuIORequestToULA = (CpuIORQ == SignalState.LOW) && ((Address.SampleValue() & A0) == (ULAPort & A0)); if (cpuIORequestToULA) { // Check to see if it is a read operation cpuIORequestToULAIsREAD = CpuWR == SignalState.HIGH; } if (cpuMemoryOrIORequestToVideoAddress || cpuIORequestToULA) { cpuMemoryOrIORequestHalfTState = 1; } } else if (cpuMemoryOrIORequestHalfTState > 0) { cpuMemoryOrIORequestHalfTState++; } // --- CPU IO requests to ULA port if (cpuIORequestToULA) { if (cpuIORequestToULAIsREAD) { if (cpuMemoryOrIORequestHalfTState == 5) { byte inputData = 0; // Read keyboard state KeyboardRD = SignalState.LOW; inputData = (byte)(KeyboardData.SampleValue() & ULAPort_Read_Keyboard); KeyboardRD = SignalState.HIGH; // Load cassette inputData |= (byte)(TapeInputSignal.Level << ULAPort_Read_EAR); // Bits 5 and 7 as read by INning from Port 0xfe are always one inputData |= ULAPort_Read_Bits5And7; Data.SetValue(inputData); } else if (cpuMemoryOrIORequestHalfTState == 6) { Data.ReleaseValue(); } } else { if (cpuMemoryOrIORequestHalfTState == 3) { byte writeData = Data.SampleValue(); // Write borderColor register borderColorRegister = (byte)(writeData & ULAPort_Write_Border); // Output sound SpeakerSoundSignal.Level = (byte)((writeData & ULAPort_Write_Speaker) >> 4); if (SpeakerSoundSignal.Level == 1) { soundOutputWasSetByTheCPU = true; } else { soundOutputWasSetByTheCPU = false; } // Save cassette TapeOuputSignal.Level = (byte)(writeData & ULAPort_Write_MIC); } } } else if (cpuMemoryOrIORequestHalfTState == 3) { // CPU is about to access the ULA IO port cpuIORequestToULA = (CpuIORQ == SignalState.LOW) && ((Address.SampleValue() & A0) == (ULAPort & A0)); if (cpuIORequestToULA) { // Check to see if it is a read operation cpuIORequestToULAIsREAD = CpuWR == SignalState.HIGH; } if (cpuIORequestToULA) { cpuMemoryOrIORequestHalfTState = 1; } } } if (cpuMemoryOrIORequestHalfTState == 6) { cpuMemoryOrIORequestHalfTState = 0; } // --- Compute pixel colors --- if (displayPixels) // First thing to do at the falling edge of the clock { switch (videoMem16StepsAccessPatternIndex) { case 5: displayRegister = displayLatch; break; case 13: displayRegister = displayLatch; break; } } switch (videoMem16StepsAccessPatternIndex) { case 5: MultiplexAttributeLatchWithBorderColor(); break; case 13: MultiplexAttributeLatchWithBorderColor(); break; } // --- Load two display and attribute bytes for 16 pixels from video memory --- if (!generateBorder) { switch (videoMem16StepsAccessPatternIndex) { case 7: ComputeVideoAddresses(); break; case 8: // display address -> address bus VideoAddress.SetValue(displayAddress); VideoMREQ = SignalState.LOW; VideoRD = SignalState.LOW; break; case 9: displayLatch = VideoData.SampleValue(); VideoRD = SignalState.HIGH; VideoMREQ = SignalState.HIGH; VideoAddress.ReleaseValue(); break; case 10: // attribute adress -> address bus VideoAddress.SetValue(attributeAddress); VideoMREQ = SignalState.LOW; VideoRD = SignalState.LOW; break; case 11: attributeLatch = VideoData.SampleValue(); VideoRD = SignalState.HIGH; VideoMREQ = SignalState.HIGH; VideoAddress.ReleaseValue(); ComputeVideoAddresses(); break; case 12: // display colum address -> address bus VideoAddress.SetValue(displayAddress); VideoMREQ = SignalState.LOW; VideoRD = SignalState.LOW; break; case 13: displayLatch = VideoData.SampleValue(); VideoRD = SignalState.HIGH; VideoMREQ = SignalState.HIGH; VideoAddress.ReleaseValue(); break; case 14: // attribute adress -> address bus VideoAddress.SetValue(attributeAddress); VideoMREQ = SignalState.LOW; VideoRD = SignalState.LOW; break; case 15: attributeLatch = VideoData.SampleValue(); VideoRD = SignalState.HIGH; VideoMREQ = SignalState.HIGH; VideoAddress.ReleaseValue(); break; } } // --- For each pixel : Video output signals --- // Generate horizontal and vertical synchronization signals if (column == 320 /* Offset : because of the time necessary to read the first pixels in video memory, color output begins only 13 cycles after the master counter */ + 13) { HSync = SignalState.LOW; } else if (column == 416 /* Offset : because of the time necessary to read the first pixels in video memory, color output begins only 13 cycles after the master counter */ + 13) { HSync = SignalState.HIGH; if (line == 247) { VSync = SignalState.LOW; } else if (line == 255) { VSync = SignalState.HIGH; } } // Compute pixel color for video output if (HSync == SignalState.LOW || VSync == SignalState.LOW) { // Blanking ColorSignal.Level = 0; } else { MultiplexDisplayRegisterWithAttributeRegisterAndFlashClock(); } // Debug notification if (pendingExitException == null) { pendingExitException = NotifyLifecycleEvent(LifecycleEventType.ClockCycle); } // --- Increment master counters and prepare next iteration --- // Shift display register bits displayRegister = (byte)(displayRegister << 1); column++; if (column == 8) { if (!generateBorder) { displayPixels = true; } } else if (column == 256) { generateBorder = true; videoMemAccessTimeFrame = false; } else if (column == 264) { if (displayPixels) { displayPixels = false; } } else if (column == 448) { column = 0; line++; // At each end of line send sound sample signal to the speaker // => 7 Mhz pixel clock / 448 pixels per line = 15.6 Khz sound sampling frequency SoundSampleCLK = SoundSampleCLK == SignalState.LOW ? SignalState.HIGH : SignalState.LOW; if (line < 192) { generateBorder = false; } if (line == 312) { line = 0; generateBorder = false; frame++; // Each 16 frames, invert flash clock if (frame == 16) { frame = 0; flashClock = !flashClock; } } } // Debug : if a breakpoint was hit, notify the pixel clock via a specific exception if (pendingExitException != null) { throw pendingExitException; } }