diff --git a/BreaksPPU/PPUPlayer/FormCompositeViewer.cs b/BreaksPPU/PPUPlayer/FormCompositeViewer.cs index 6864a747..ba95f4ce 100644 --- a/BreaksPPU/PPUPlayer/FormCompositeViewer.cs +++ b/BreaksPPU/PPUPlayer/FormCompositeViewer.cs @@ -136,7 +136,8 @@ void ProcessScanComposite() // Detect Color Burst Phase (simulate TV PLL) - int cb_phase = PLL(); + bool odd = (CurrentScan % 2) != 0 && ppu_features.PhaseAlteration != 0; + int cb_phase = PLL(odd, ppu_features.PhaseAlteration != 0 ? 10 : 0); ReadPtr += ppu_features.BackPorchSize * ppu_features.SamplesPerPCLK; @@ -169,7 +170,7 @@ void ProcessScanComposite() float level = ((batch[n].composite - ppu_features.BurstLevel) * normalize_factor) / num_phases; Y += level; I += level * (float)Math.Cos((cb_phase + n + ofs) * (2 * Math.PI / num_phases) + hue_adj); - Q += level * (float)Math.Sin((cb_phase + n + ofs) * (2 * Math.PI / num_phases) + hue_adj); + Q += level * (float)Math.Sin((cb_phase + n + ofs) * (2 * Math.PI / num_phases) + hue_adj) * (odd ? -1.0f : +1.0f); } // 6500K color temperature @@ -195,12 +196,19 @@ void ProcessScanComposite() } } - int PLL() + int prev_cb_shift = 0; + + int PLL(bool odd, int cb) { int ReadPtr = SyncPos; int num_phases = 12; int samples = ppu_features.BackPorchSize * ppu_features.SamplesPerPCLK; + if (odd) + { + return Math.Abs((prev_cb_shift + 5) % num_phases); + } + // Skip HSync while (ScanBuffer[ReadPtr].composite <= ppu_features.SyncLevel) @@ -217,7 +225,7 @@ int PLL() // Get phase shift - int cb_shift = 0; + int cb_shift = cb; if (ScanBuffer[ReadPtr].composite < ppu_features.BurstLevel) { @@ -238,7 +246,9 @@ int PLL() } } - return cb_shift % num_phases; + int res = cb_shift % num_phases; + prev_cb_shift = res; + return res; } float Clamp(float val, float min, float max) diff --git a/BreaksPPU/PPUPlayer/FormMain.cs b/BreaksPPU/PPUPlayer/FormMain.cs index 3293b9f1..8fb04957 100644 --- a/BreaksPPU/PPUPlayer/FormMain.cs +++ b/BreaksPPU/PPUPlayer/FormMain.cs @@ -160,17 +160,17 @@ void RunPPU() return; } + FormSettings.PPUPlayerSettings settings = FormSettings.LoadSettings(); + // If the user did not select anything, but just clicked the `Play` button - notify him that he is in free flight. if (ppu_dump == null && nes_file == null) { string text = DefaultTitle; - text += " - Free Flight"; + text += " - Free Flight (" + settings.PPU_Revision + ")"; this.Text = text; } - FormSettings.PPUPlayerSettings settings = FormSettings.LoadSettings(); - // If the user has specified RegDump - use it. Otherwise, create a dummy RegDump with an `infinite` wait to read register $2002. if (ppu_dump != null) diff --git a/BreaksPPU/PPUPlayer/PPUPlayerInterop.cs b/BreaksPPU/PPUPlayer/PPUPlayerInterop.cs index d578cf54..23992e33 100644 --- a/BreaksPPU/PPUPlayer/PPUPlayerInterop.cs +++ b/BreaksPPU/PPUPlayer/PPUPlayerInterop.cs @@ -89,6 +89,8 @@ public struct VideoSignalFeatures public float WhiteLevel; // IRE = 100 [FieldOffset(28)] public float SyncLevel; + [FieldOffset(32)] + public int PhaseAlteration; // 1: PAL } [DllImport("PPUPlayerInterop.dll", CallingConvention = CallingConvention.Cdecl)] diff --git a/BreaksPPU/PPUPlayer/VideoProcessing.cs b/BreaksPPU/PPUPlayer/VideoProcessing.cs index 93ec35b0..07c89e4e 100644 --- a/BreaksPPU/PPUPlayer/VideoProcessing.cs +++ b/BreaksPPU/PPUPlayer/VideoProcessing.cs @@ -240,7 +240,8 @@ void ProcessScanComposite() // Detect Color Burst Phase (simulate TV PLL) - int cb_phase = PLL(); + bool odd = (CurrentScan % 2) != 0 && ppu_features.PhaseAlteration != 0; + int cb_phase = PLL(odd, ppu_features.PhaseAlteration != 0 ? 10 : 0); ReadPtr += ppu_features.BackPorchSize * ppu_features.SamplesPerPCLK; @@ -273,7 +274,7 @@ void ProcessScanComposite() float level = ((batch[n].composite - ppu_features.BurstLevel) * normalize_factor) / num_phases; Y += level; I += level * (float)Math.Cos((cb_phase + n + ofs) * (2 * Math.PI / num_phases) + hue_adj); - Q += level * (float)Math.Sin((cb_phase + n + ofs) * (2 * Math.PI / num_phases) + hue_adj); + Q += level * (float)Math.Sin((cb_phase + n + ofs) * (2 * Math.PI / num_phases) + hue_adj) * (odd ? -1.0f : +1.0f); } // 6500K color temperature @@ -298,12 +299,19 @@ void ProcessScanComposite() } } - int PLL() + int prev_cb_shift = 0; + + int PLL(bool odd, int cb) { int ReadPtr = SyncPos; int num_phases = 12; int samples = ppu_features.BackPorchSize * ppu_features.SamplesPerPCLK; + if (odd) + { + return Math.Abs((prev_cb_shift + 5) % num_phases); + } + // Skip HSync while (ScanBuffer[ReadPtr].composite <= ppu_features.SyncLevel) @@ -320,7 +328,7 @@ int PLL() // Get phase shift - int cb_shift = 0; + int cb_shift = cb; if (ScanBuffer[ReadPtr].composite < ppu_features.BurstLevel) { @@ -339,10 +347,11 @@ int PLL() cb_shift++; ReadPtr++; } - } - return cb_shift % num_phases; + int res = cb_shift % num_phases; + prev_cb_shift = res; + return res; } float Clamp(float val, float min, float max) diff --git a/BreaksPPU/PPUSim/fsm.cpp b/BreaksPPU/PPUSim/fsm.cpp index 488020c1..e9180577 100644 --- a/BreaksPPU/PPUSim/fsm.cpp +++ b/BreaksPPU/PPUSim/fsm.cpp @@ -260,6 +260,12 @@ namespace PPUSim ppu->wire.EvenOddOut = TriState::Zero; break; } + + case Revision::UMC_UA6538: + { + ppu->wire.EvenOddOut = TriState::Zero; + break; + } } } @@ -273,6 +279,7 @@ namespace PPUSim // PAL PPU does not use EvenOddOut. case Revision::RP2C07_0: + case Revision::UMC_UA6538: { ctrl_latch1.set(NOT(HPLA[23]), n_PCLK); ctrl_latch2.set(VPLA[8], n_PCLK); diff --git a/BreaksPPU/PPUSim/hv_decoder.cpp b/BreaksPPU/PPUSim/hv_decoder.cpp index fa34b36c..3cd9c251 100644 --- a/BreaksPPU/PPUSim/hv_decoder.cpp +++ b/BreaksPPU/PPUSim/hv_decoder.cpp @@ -22,6 +22,7 @@ namespace PPUSim vpla_outputs = 9; break; case Revision::RP2C07_0: + case Revision::UMC_UA6538: // An additional output is used for modified EVEN/ODD logic. vpla_outputs = 10; break; @@ -138,6 +139,53 @@ namespace PPUSim } break; + case Revision::UMC_UA6538: + { + size_t UMC_UA6538_HDecoder[] = { // Same as PAL PPU + 0b01101010011001100100, + 0b01101010101010101000, + 0b10100110101010100101, + 0b00101010101000000000, + 0b10000000000000000010, + 0b01100110011010010101, + 0b10101001010101010101, + 0b00010101010101010101, + 0b10101000000000000001, + 0b01101000000000000001, + 0b10000000000000000011, + 0b00000000000010100001, + 0b00000000000001010000, + 0b00000000000001100000, + 0b01000110100000000001, + 0b10000000000000000001, + 0b00000000000010010000, + 0b01101010101010101000, + 0b10101010101001101000, + 0b01101010011001100100, + 0b01101001100101011000, + 0b01100110101010100100, + 0b01101001011010011000, + 0b01100110011001101000, + }; + + size_t UMC_UA6538_VDecoder[] = { + 0b011010100110101010, + 0b011010101001011001, + 0b101010101010101001, + 0b000101010110101010, + 0b011010011010100101, // triggered 50 lines later + 0b101010101010101010, + 0b000101010110101010, + 0b011010010110010101, + 0b011010010110010101, + 0b011010101001101001, + }; + + h_bitmask = UMC_UA6538_HDecoder; + v_bitmask = UMC_UA6538_VDecoder; + } + break; + // TBD: Add PLA for the rest of the PPU studied. } diff --git a/BreaksPPU/PPUSim/pclk.cpp b/BreaksPPU/PPUSim/pclk.cpp index c7e3088d..028b9e8f 100644 --- a/BreaksPPU/PPUSim/pclk.cpp +++ b/BreaksPPU/PPUSim/pclk.cpp @@ -33,6 +33,7 @@ namespace PPUSim break; case Revision::RP2C07_0: + case Revision::UMC_UA6538: { pclk_1.set(NOR(pclk_6.get(), wire.RES), CLK); pclk_2.set(pclk_1.nget(), n_CLK); diff --git a/BreaksPPU/PPUSim/ppu.cpp b/BreaksPPU/PPUSim/ppu.cpp index dcca586f..19b6a1fc 100644 --- a/BreaksPPU/PPUSim/ppu.cpp +++ b/BreaksPPU/PPUSim/ppu.cpp @@ -162,6 +162,7 @@ namespace PPUSim case Revision::RP2C02G: case Revision::RP2C02H: case Revision::RP2C07_0: + case Revision::UMC_UA6538: { TriState nSLAVE = regs->get_nSLAVE(); @@ -202,6 +203,7 @@ namespace PPUSim case Revision::RP2C02G: case Revision::RP2C02H: case Revision::RP2C07_0: + case Revision::UMC_UA6538: { for (size_t n = 0; n < 4; n++) { diff --git a/BreaksPPU/PPUSim/ppu.h b/BreaksPPU/PPUSim/ppu.h index f1e2b0b0..5829e215 100644 --- a/BreaksPPU/PPUSim/ppu.h +++ b/BreaksPPU/PPUSim/ppu.h @@ -63,6 +63,7 @@ namespace PPUSim float BurstLevel; // IRE = 0 float WhiteLevel; // IRE = 100 float SyncLevel; + int32_t PhaseAlteration; // 1: PAL }; /// diff --git a/BreaksPPU/PPUSim/regs.cpp b/BreaksPPU/PPUSim/regs.cpp index cb4da8db..48d3983a 100644 --- a/BreaksPPU/PPUSim/regs.cpp +++ b/BreaksPPU/PPUSim/regs.cpp @@ -23,9 +23,12 @@ namespace PPUSim sim_RegFFs(); - if (ppu->rev == Revision::RP2C07_0) + switch (ppu->rev) { - sim_PalBLACK(); + case Revision::RP2C07_0: + case Revision::UMC_UA6538: + sim_PalBLACK(); + break; } } @@ -213,6 +216,7 @@ namespace PPUSim // The PAL PPU uses a hidden latch for the VBL signal, which is stored between the open transistor and the inverter in the VBlank INT circuit. case Revision::RP2C07_0: + case Revision::UMC_UA6538: vbl_latch.set(PPU_CTRL0[7].get(), NOT(W0_Enable)); ppu->wire.VBL = vbl_latch.get(); break; diff --git a/BreaksPPU/PPUSim/sprite_eval.cpp b/BreaksPPU/PPUSim/sprite_eval.cpp index d2942716..3084105a 100644 --- a/BreaksPPU/PPUSim/sprite_eval.cpp +++ b/BreaksPPU/PPUSim/sprite_eval.cpp @@ -155,6 +155,7 @@ namespace PPUSim // For the PAL PPU, the $2003 write delay is screwed on. This is most likely how they fight OAM Corruption. case Revision::RP2C07_0: + case Revision::UMC_UA6538: { auto W3 = NOR(n_W3, n_DBE); W3_FF1.set(NOR(NOR(W3, W3_FF1.get()), w3_latch3.nget())); @@ -322,6 +323,7 @@ namespace PPUSim switch (ppu->rev) { case Revision::RP2C07_0: + case Revision::UMC_UA6538: { TriState n_PCLK = ppu->wire.n_PCLK; blnk_latch.set(BLNK, n_PCLK); diff --git a/BreaksPPU/PPUSim/video_out.cpp b/BreaksPPU/PPUSim/video_out.cpp index dbd0a3c2..4023cbee 100644 --- a/BreaksPPU/PPUSim/video_out.cpp +++ b/BreaksPPU/PPUSim/video_out.cpp @@ -15,6 +15,7 @@ namespace PPUSim case Revision::RP2C02G: case Revision::RP2C02H: case Revision::RP2C07_0: + case Revision::UMC_UA6538: LToV[0] = 0.781f; // Synch LToV[1] = 1.000f; // Colorburst L LToV[2] = 1.131f; // Color 0D @@ -43,6 +44,7 @@ namespace PPUSim switch (ppu->rev) { case Revision::RP2C07_0: + case Revision::UMC_UA6538: SetupChromaDecoderPAL(); break; } @@ -168,6 +170,7 @@ namespace PPUSim break; case Revision::RP2C07_0: + case Revision::UMC_UA6538: { TriState n_PCLK = ppu->wire.n_PCLK; if (ppu->v != nullptr) @@ -488,11 +491,14 @@ namespace PPUSim TriState n_PCLK = ppu->wire.n_PCLK; TriState n_PICTURE = ppu->fsm.n_PICTURE; - if (ppu->rev == Revision::RP2C07_0) + switch (ppu->rev) { - npicture_latch1.set(NOT(n_PICTURE), n_PCLK); - npicture_latch2.set(npicture_latch1.nget(), PCLK); - n_PICTURE = npicture_latch2.get(); + case Revision::RP2C07_0: + case Revision::UMC_UA6538: + npicture_latch1.set(NOT(n_PICTURE), n_PCLK); + npicture_latch2.set(npicture_latch1.nget(), PCLK); + n_PICTURE = npicture_latch2.get(); + break; } VidOut_n_PICTURE = n_PICTURE; @@ -511,6 +517,7 @@ namespace PPUSim features.BurstLevel = 1.3f; features.WhiteLevel = 1.6f; features.SyncLevel = 0.781f; + features.PhaseAlteration = false; break; case Revision::RP2C07_0: @@ -521,6 +528,21 @@ namespace PPUSim features.BurstLevel = 1.3f; features.WhiteLevel = 1.6f; features.SyncLevel = 0.781f; + features.PhaseAlteration = true; + break; + + // TBD: Technically the DAC should not differ from the PAL PPU, but there are reports that the colors are brighter. + // Apparently this is due to the slight difference in the crystal area. + + case Revision::UMC_UA6538: + features.SamplesPerPCLK = 10; + features.PixelsPerScan = 341; + features.ScansPerField = 312; + features.BackPorchSize = 42; + features.BurstLevel = 1.3f; + features.WhiteLevel = 1.6f; + features.SyncLevel = 0.781f; + features.PhaseAlteration = true; break; case Revision::RP2C04_0003: @@ -626,9 +648,13 @@ namespace PPUSim float level = ((batch[n].composite - features.BurstLevel) * normalize_factor) / num_phases; Y += level; I += level * cos((cb_phase + n) * (2 * π / num_phases)); - Q += level * sin((cb_phase + n) * (2 * π / num_phases)); + Q += level * sin((cb_phase + n) * (2 * π / num_phases)) * +1.0f; } + // Note to PAL researchers. Read math behind PAL, or just use these: + // cb_phase = 1 + // Q += level * sin((cb_phase + n) * (2 * π / num_phases)) * -1.0f; <---- Minus + delete[] batch; // 6500K color temperature