From 68d1358a25617e30c18722ba441b9a37424a64d2 Mon Sep 17 00:00:00 2001 From: Ian C Date: Wed, 11 Jul 2012 23:16:05 +0000 Subject: Work in progress on ZX81 display/ULA emulation. --- WPZX81/WPZX81/Resources/Strings.Designer.cs | 4 +- WPZX81/WPZX81/Resources/Strings.resx | 2 +- WPZX81/WPZX81/ZX81/Emulation.cs | 300 ++++++++++++++-------------- 3 files changed, 149 insertions(+), 157 deletions(-) diff --git a/WPZX81/WPZX81/Resources/Strings.Designer.cs b/WPZX81/WPZX81/Resources/Strings.Designer.cs index b869c1f..580f2cd 100644 --- a/WPZX81/WPZX81/Resources/Strings.Designer.cs +++ b/WPZX81/WPZX81/Resources/Strings.Designer.cs @@ -97,11 +97,11 @@ namespace WPZX81.Resources { } /// - /// Looks up a localized string similar to WPZX81 is Copyright © 2012 noddybox + /// Looks up a localized string similar to WPZX81 is Copyright © 2012 Ian Cowburn /// ///This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. /// - ///This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more detai [rest of string was truncated]";. + ///This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more de [rest of string was truncated]";. /// public static string CopyrightText { get { diff --git a/WPZX81/WPZX81/Resources/Strings.resx b/WPZX81/WPZX81/Resources/Strings.resx index 674127f..cc1f291 100644 --- a/WPZX81/WPZX81/Resources/Strings.resx +++ b/WPZX81/WPZX81/Resources/Strings.resx @@ -130,7 +130,7 @@ Cancel - WPZX81 is Copyright © 2012 noddybox + WPZX81 is Copyright © 2012 Ian Cowburn This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. diff --git a/WPZX81/WPZX81/ZX81/Emulation.cs b/WPZX81/WPZX81/ZX81/Emulation.cs index 8df4c39..5c3e01e 100644 --- a/WPZX81/WPZX81/ZX81/Emulation.cs +++ b/WPZX81/WPZX81/ZX81/Emulation.cs @@ -35,15 +35,14 @@ namespace WPZX81.ZX81 private Clock clock; private byte[] mem; - private bool fast; - private bool started; - private bool waitkey; - private int lastI; + private int vsync; + private bool hsync; + private int lineCounter; + private bool vsyncNoted; + private bool nmiGenerator; private int rambot; private int ramtop; private byte[] matrix; - private uint prev_last_key1; - private uint prev_last_key2; // Some system variables // @@ -150,19 +149,19 @@ namespace WPZX81.ZX81 { Patch(0x2fc, savePatch); Patch(0x347, loadPatch); - Patch(0x4ca, fastPatch); - Patch(0x2bb, keyboardPatch); - Patch(0xf3a, pausePatch); + // Patch(0x4ca, fastPatch); + // Patch(0x2bb, keyboardPatch); + // Patch(0xf3a, pausePatch); // Trust me, we have a ZX81... Honestly. // - mem[0x21c]=0x00; - mem[0x21d]=0x00; + // mem[0x21c]=0x00; + // mem[0x21d]=0x00; // Remove HALTs as we don't do NMI and the NMI generator properly // - mem[0x0079]=0; - mem[0x02ec]=0; + // mem[0x0079]=0; + // mem[0x02ec]=0; } /// @@ -182,105 +181,54 @@ namespace WPZX81.ZX81 // TODO break; - case ED_WAITKEY: - waitkey = true; - started = true; - break; - - case ED_ENDWAITKEY: - waitkey = false; - break; - - case ED_PAUSE: - // TODO - break; - default: break; } } - /// - /// Do various housekeeping of the system variables which we do ourselves due the patching. - /// - private void Housekeeping() + private bool OperationCallback(ICpu sender) { - // British ZX81 - // - mem[MARGIN] = 55; - - // Update FRAMES - // - if (!fast) - { - ushort frame = PeekWord(FRAMES); - frame &= 0x7fff; - - if (frame > 0) - { - frame--; - } - - frame |= 0x8000; - - PokeWord(FRAMES, frame); - } - - // If not started, quit out + // During VSYNC, keep resetting the clock // - if (!started) + if (vsync > 0) { - prev_last_key1 = 0; - prev_last_key2 = 0; - return; + vsync++; + clock.CycleCount = 0; } - - // Update LASTK - // - uint lastk1 = 0; - uint lastk2 = 0; - - for(int row = 0 ; row < 8; row++) + else { - uint b = 0; - - b = (uint)((~matrix[row] & 0x1fu) << 1); - - if (row == 0) + // If we're out of VSYNC, see if we need to generate an interrupt + // + if (clock.CycleCount >= 224) { - uint shift = b & 2; + clock.CycleCount -= 224; - b &= ~2u; - b |= (shift>>1); - } - - if (b > 0) - { - if (b > 1) + if (nmiGenerator) { - lastk1 |= (uint)(1 << row); + Z80.NonMaskableInterrupt(0xff); } - - lastk2 |= b; } } - if (lastk1 > 0 && (lastk1 != prev_last_key1 || - lastk2 != prev_last_key2)) + // Maskable interupts are tied to bit 6 of the refresh register. + // + if ((Z80.Refresh | 0x20) == 0) { - mem[CDFLAG] |= 1; + Z80.MaskableInterrupt(0xff); + } + + // The frame finishes when VSYNC is triggered (ie. HYSNC is off). Note we wait until we've had a good few opcodes with VSYNC happening. + // + if (vsync > 10 && !vsyncNoted) + { + vsyncNoted = true; + return true; } else { - mem[CDFLAG] &= 0xfe; + return false; } - - mem[LASTK1] = (byte)(lastk1^0xff); - mem[LASTK2] = (byte)(lastk2^0xff); - - prev_last_key1 = lastk1; - prev_last_key2 = lastk2; } #endregion @@ -323,32 +271,8 @@ namespace WPZX81.ZX81 /// public void Run() { - Stopwatch s = Stopwatch.StartNew(); - - //for(int f = 1; f <= Settings.FramesPerUpdate; f++) - //{ - // if (fast) - // { - // clock.StartFrame(); - // Z80.Run(); - // clock.StartFrame(); - // Z80.Run(); - // clock.StartFrame(); - // Z80.Run(); - // } - - // clock.StartFrame(); - // Z80.Run(); - // Z80.MaskableInterrupt(0); - - // if (Z80.StackPointer < 0x8000) - // { - // Housekeeping(); - // } - //} - - s.Stop(); - Debug.WriteLine("*** RUN {0} msec {1} ticks (PC = 0x{2:X4}, opcodes = {3})", s.ElapsedMilliseconds, s.ElapsedTicks, Z80.ProgramCounter, Z80.ExecutedInstructions); + vsyncNoted = false; + Z80.Run(OperationCallback); } /// @@ -373,15 +297,15 @@ namespace WPZX81.ZX81 /// public void Reset() { - lastI = -1; - fast = true; - started = false; - waitkey = false; + vsync = 0; + hsync = false; + vsyncNoted = false; + nmiGenerator = false; rambot = Settings.StaticRamAt0x2000 ? 0x2000 : 0x4000; ramtop = Settings.RamPack ? 0x8000 : 0x4400; - mem.Initialize(); + new Random().NextBytes(mem); for(int f = 0 ; f < 8; f++) { @@ -430,7 +354,29 @@ namespace WPZX81.ZX81 byte IMemory.Read(ushort address) { - return mem[address & 0x7fff]; + // Reads above 0x8000 are grabbed by the ULA to generate the display. + // + if (address >= 0x8000) + { + byte b = mem[address & 0x7fff]; + + // Opcodes with bit 6 are still passed to the CPU + // + if ((b & 0x20) == 0x20) + { + return b; + } + else + { + // Return NOP + // + return 0; + } + } + else + { + return mem[address & 0x7fff]; + } } void IMemory.Write(ushort address, byte value) @@ -451,41 +397,60 @@ namespace WPZX81.ZX81 // ULA // - if ((device & 0xff) == 0xfe) + switch(device & 0xff) { - switch(device&0xff00) - { - case 0xfe00: - b=matrix[0]; - break; - case 0xfd00: - b=matrix[1]; - break; - case 0xfb00: - b=matrix[2]; - break; - case 0xf700: - b=matrix[3]; - break; - case 0xef00: - b=matrix[4]; - break; - case 0xdf00: - b=matrix[5]; - break; - case 0xbf00: - b=matrix[6]; - break; - case 0x7f00: - b=matrix[7]; - break; - } - - // Some code expects some of the top bits set... Of course, whether - // or not this may be worse as other code doesn't expect the bits, - // we shall find out! + // ULA // - b |= 0x60; + case 0xfe: + + // Once the ULA is read the vsync video level is triggered and the HYSNC switched off if NMI generation is off. + // Rather than just a boolean, we not how many opcode VSYNC has been active. This stops us getting + // confused by IN A,(FE) / OUT (FF),A pairs used to reset the ULA LINECTR. + // + vsync = 0; + + if (!nmiGenerator) + { + hsync = false; + } + + switch(device&0xff00) + { + case 0xfe00: + b=matrix[0]; + break; + case 0xfd00: + b=matrix[1]; + break; + case 0xfb00: + b=matrix[2]; + break; + case 0xf700: + b=matrix[3]; + break; + case 0xef00: + b=matrix[4]; + break; + case 0xdf00: + b=matrix[5]; + break; + case 0xbf00: + b=matrix[6]; + break; + case 0x7f00: + b=matrix[7]; + break; + } + + // Some code expects some of the top bits set... Of course, whether + // or not this may be worse as other code doesn't expect the bits, + // we shall find out! + // + b |= 0x60; + break; + + default: + break; } return b; @@ -493,6 +458,33 @@ namespace WPZX81.ZX81 void IDevice.Write(ushort device, byte value) { + switch(device & 0xff) + { + // Turns off the NMI generator + // + case 0xfd: + nmiGenerator = false; + break; + + // Turns on the NMI generator + // + case 0xfe: + nmiGenerator = true; + break; + + // The ZX81 sends an OUT FF,A after the VSYNC to start HYSNC generation, + // which puts the ULA back to 'white' output and releases the reset on the + // ULA LINECTR. + // + case 0xff: + vsync = 0; + hsync = true; + lineCounter = 0; + break; + + default: + break; + } } #endregion -- cgit v1.2.3