Exemplo n.º 1
0
        /// <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;
            }
        }