public byte Peek(ushort address) { // Timer addresses can be handled separately if (address >= Mikey.Addresses.HTIMBKUP && address <= Mikey.Addresses.TIM7CTLB) { int offset = address - Mikey.Addresses.HTIMBKUP; int index = offset >> 2; // Divide by 4 to get index of timer Timer timer = Timers[index]; // TODO: This line introduces a bug in Awesome Golf, but fixes the random blocks in Blockout // Find out what breaks Awesome Golf //timer.Update(device.SystemClock.CompatibleCycleCount); switch (offset % 4) { case 0: // Backup value return(timer.BackupValue); case 1: // Static control return(timer.StaticControlBits.ByteData); case 2: // Current value return(timer.CurrentValue); case 3: // Dynamic control bits return(timer.DynamicControlBits.ByteData); } } if (address >= Mikey.Addresses.AUD0VOL && address <= Mikey.Addresses.AUD3MISC && SoundEnabled) { int offset = address - Mikey.Addresses.AUD0VOL; int index = offset >> 3; // Divide by 8 to get index of audio channel AudioChannel channel = AudioChannels[index]; switch (offset % 8) { case 0: // "8 bit. 2's Complement Volume Control" return((byte)channel.VolumeControl); case 1: // "Shift register feedback enable" return(channel.FeedbackEnable.ByteData); case 2: // "Audio output value" return((byte)channel.OutputValue); case 3: // "Lower 8 Bits of Shift Register" return(channel.LowerShiftRegister); case 4: // "Audio Timer Backup Value" return(channel.BackupValue); case 5: // "Audio Control Bits" return(channel.AudioControl.ByteData); case 6: // "Audio counter" return(channel.CurrentValue); case 7: // "Other control bits" return(channel.OtherControlBits); } } switch (address) { case Mikey.Addresses.MSTEREO: // "The Howard boards were not yet finished, so we went ahead and implemented this stereo on them. // This form of stereo was channel switching controlled by FD50." // TODO: Implement stereo return(0x00); case Mikey.Addresses.MPAN: // TODO: Implement panning return(0x00); case Mikey.Addresses.INTSET: case Mikey.Addresses.INTRST: // "The software reads either the INTSET or INTRST registers (they have duplicate information) ..." return(timerInterruptStatusRegister); case Mikey.Addresses.MAGRDY0: byte magReady0 = MAGRDY0.ByteData; // "B7=edge (1) Reset upon read." MAGRDY0.Edge = false; return(magReady0); case Mikey.Addresses.MAGRDY1: byte magReady1 = MAGRDY0.ByteData; // "B7=edge (1) Reset upon read." MAGRDY0.Edge = false; return(magReady1); case Mikey.Addresses.IODIR: return(this.IODIR.ByteData); // "Note that some lines are used for several functions, please read the spec. // Also note that only the lines that are set to input are actually valid for reading." case Mikey.Addresses.IODAT: byte value = 0x00; if (IODIR.AuxiliaryDigitalInOut == DataDirection.Input && device.Cartridge.AuxiliaryDigitalInOut) { value |= ParallelData.AuxiliaryDigitalInOutMask; } if (IODIR.AuxiliaryDigitalInOut == DataDirection.Output && IODAT.AuxiliaryDigitalInOut) { value |= ParallelData.AuxiliaryDigitalInOutMask; } if (IODIR.Rest == DataDirection.Output && (!IODAT.Rest || !RestActive)) { value |= ParallelData.RestMask; } if ((IODIR.NoExpansion == DataDirection.Input && ComLynxCablePresent) || (IODIR.NoExpansion == DataDirection.Output && IODAT.NoExpansion)) { value |= ParallelData.NoExpansionMask; } if (IODIR.CartAddressData == DataDirection.Output && IODAT.CartAddressData) { value |= ParallelData.CartAddressDataMask; } if (IODIR.ExternalPower == DataDirection.Input || IODAT.ExternalPower) { value |= ParallelData.ExternalPowerMask; } return(value); case Mikey.Addresses.SERCTL: // return comLynx.SERCTL.ByteData; return(ComLynx.SERCTL); case Mikey.Addresses.SERDAT: //comLynx.SERCTL.ReceiveReady = false; return(ComLynx.SERDAT); case Mikey.Addresses.PBKUP: return(PBKUP); case Mikey.Addresses.MIKEYHREV: // "No actual register is implemented" return(0x01); case Mikey.Addresses.MIKEYSREV: // "No actual register is implemented" break; // Write-only addresses case Mikey.Addresses.CPUSLEEP: case Mikey.Addresses.SDONEACK: case Mikey.Addresses.DISPADRL: case Mikey.Addresses.DISPADRH: case Mikey.Addresses.SYSCTL1: case Mikey.Addresses.DISPCTL: //Debug.WriteLineIf(GeneralSwitch.TraceWarning, String.Format("Mikey::Peek - Write-only address ${0:X4} used.", address)); return(0xFF); default: break; } if (address >= Mikey.Addresses.BLUERED0 && address <= Mikey.Addresses.BLUEREDF) { int index = address - Mikey.Addresses.BLUERED0; return(BlueRedColorMap[index]); } if (address >= Mikey.Addresses.GREEN0 && address <= Mikey.Addresses.GREENF) { int index = address - Mikey.Addresses.GREEN0; return(GreenColorMap[index]); } //Trace.WriteLineIf(GeneralSwitch.TraceWarning, String.Format("Mikey::Peek - Unknown address ${0:X4} specified.", address)); return(0x00); }
public void Poke(ushort address, byte value) { // Timer addresses can be handled separately if (address >= Mikey.Addresses.HTIMBKUP && address <= Mikey.Addresses.TIM7CTLB) { int offset = address - Mikey.Addresses.HTIMBKUP; int index = offset >> 2; // Divide by 4 to get index of timer Timer timer = Timers[index]; switch (offset % 4) { case 0: // Backup value timer.BackupValue = value; return; case 1: // Static control StaticControlBits control = new StaticControlBits(value); timer.StaticControlBits = control; // "It is set on time out, reset with the reset timer done bit (xxx1, B6)" if (control.ResetTimerDone) { timer.DynamicControlBits.TimerDone = false; } if (control.EnableCount || control.ResetTimerDone) { timer.Start(device.SystemClock.CompatibleCycleCount); ForceTimerUpdate(); } return; case 2: // Current value timer.CurrentValue = value; ForceTimerUpdate(); return; case 3: // Dynamic control bits timer.DynamicControlBits.ByteData = value; return; } } if (address >= Mikey.Addresses.AUD0VOL && address <= Mikey.Addresses.AUD3MISC && SoundEnabled) { int offset = address - Mikey.Addresses.AUD0VOL; int index = offset >> 3; // Divide by 8 to get index of audio channel AudioChannel channel = AudioChannels[index]; // "FD20 -> FD27 Audio channel 0, links from timer 7" // "FD28 -> FD2F Audio channel 1, links from audio timer 0" // "FD30 -> FD37 Audio channel 2, links from audio timer 1" // "FD38 -> FD3F Audio channel 3, links trom audio timer 2" switch (offset % 8) { case 0: // "8 bit. 2's Complement Volume Control" channel.VolumeControl = (sbyte)value; return; case 1: // "Shift register feedback enable" channel.FeedbackEnable.ByteData = value; return; case 2: // "Audio output value" channel.OutputValue = (sbyte)value; return; case 3: // "Lower 8 Bits of Shift Register" channel.LowerShiftRegister = value; return; case 4: // "Audio Timer Backup Value" channel.BackupValue = value; return; case 5: // "Audio Control Bits" //channel.AudioControl.ByteData = value; AudioControlBits control = new AudioControlBits(value); channel.AudioControl = control; // "It is set on time out, reset with the reset timer done bit (xxx1, B6)" if (control.ResetTimerDone) { channel.DynamicControlBits.TimerDone = false; } if (control.EnableCount || control.ResetTimerDone) { channel.Start(device.SystemClock.CompatibleCycleCount); ForceTimerUpdate(); } return; case 6: // "Audio counter" channel.CurrentValue = value; ForceTimerUpdate(); return; case 7: // "Other control bits" channel.OtherControlBits = value; return; } } // Handle other addresses switch (address) { case Mikey.Addresses.MSTEREO: // "The Howard boards were not yet finished, so we went ahead and implemented this stereo on them. // This form of stereo was channel switching controlled by FD50." // TODO: Implement stereo return; case Mikey.Addresses.MPAN: // TODO: Implement panning return; case Mikey.Addresses.INTRST: // "Read is a poll, write will reset the int that corresponds to a set bit." value ^= 0xff; timerInterruptStatusRegister &= value; // When timer interrupt status register is zero, IRQ line goes back up (not-active) device.Cpu.SignalInterrupt(timerInterruptStatusRegister != 0 ? InterruptType.Irq : InterruptType.None); ForceTimerUpdate(); return; case Mikey.Addresses.INTSET: // "Read is a poll, write will set the int that corresponds to a set bit." timerInterruptStatusRegister |= value; // When timer interrupt status register is zero, IRQ line goes back up (not-active) device.Cpu.SignalInterrupt(timerInterruptStatusRegister != 0 ? InterruptType.Irq : InterruptType.None); ForceTimerUpdate(); return; case Mikey.Addresses.MIKEYSREV: // "No actual register is implemented" return; // "Also note that only the lines that are set to input are actually valid for reading." // "8 bits I/O direction corresponding to the 8 bits at FD8B 0=input, 1= output" case Mikey.Addresses.IODIR: IODIR.ByteData = value; return; // "Mikey Parallel Data(sort of a R/W) 8 bits of general purpose I/O data" case Mikey.Addresses.IODAT: IODAT.ByteData = value; // "One is that it is the data pin for the shifter that holds the cartridge address." device.Cartridge.CartAddressData(IODAT.CartAddressData); // "The other is that it controls power to the cartridge." device.CartridgePowerOn = !IODAT.CartPowerOff; // "In its current use, it is the write enable line for writeable elements in the cartridge." if (IODIR.AuxiliaryDigitalInOut == DataDirection.Output) { device.Cartridge.AuxiliaryDigitalInOut = IODAT.AuxiliaryDigitalInOut; } // "In its current use, it is the write enable line for writeable elements in the cartridge." if (IODIR.AuxiliaryDigitalInOut == DataDirection.Output) { device.Cartridge.WriteEnabled = IODAT.AuxiliaryDigitalInOut; } return; case Mikey.Addresses.SERCTL: ComLynx.SERCTL = value; return; case Mikey.Addresses.SERDAT: ComLynx.SERDAT = value; return; case Mikey.Addresses.SYSCTL1: SYSCTL1.ByteData = value; if (!SYSCTL1.Power) { device.Reset(); // TODO: Enter debug mode if configured } device.Cartridge.CartAddressStrobe(SYSCTL1.CartAddressStrobe); return; case Mikey.Addresses.SDONEACK: // "Write a '00' to SDONEACK, allowing Mikey to respond to sleep commands." // "The Suzy Done Acknowledge address must be written to prior to running the sprite engine. // This is required even prior to the first time the sprite engine is activated. // It it is not written to in the appropriate sequences, the CPU will not go to sleep // when so requested. In addition, if some software accidentally allows a Suzy operation to // complete without then following that completion with a write to SDONEACK, the CPU // will not sleep. So if sprites stop working, something may have gone wrong with your // SDONEACK software." // TODO: Implement state for Suzy Done acknowledgement if necessary return; case Mikey.Addresses.CPUSLEEP: // "A write of '0' to this address will reset the CPU bus request flip flop. // The setting of the flip flop is described in the hardware specification." // BUG: "Sleep does not work if Suzy does not have the bus." // "We assume that everyone knows about this bug and behaves accordingly, so // writing zero here must be to give Suzy access to the bus and it will start drawing // sprites and will signal when it is done." // This is implemented as a new wakeup time by calculating the number of cycles used // and skipping forward in time to that moment. ulong suzyCycles = device.Suzy.RenderSprites(); // TODO: For now use estimate of cycles for sprite drawing. // Needs to be replaced with a better calculation. See also Handy PaintSprites code. suzyCycles = 0; //1000; device.Cpu.TrySleep(suzyCycles); return; case Mikey.Addresses.DISPCTL: DISPCTL.ByteData = value; return; case Mikey.Addresses.PBKUP: // "Additionally, the magic 'P' counter has to be set to match the LCD scan rate. The formula is: // INT((((line time - .5us) / 15) * 4) -1)" PBKUP = value; return; case Mikey.Addresses.DISPADRL: // "DISPADRL (FD94) is lower 8 bits of display address with the bottom 2 bit ignored by the hardware. // The address of the upper left corner of a display buffer must always have '00' in the bottom 2 bits." VideoDisplayStartAddress.LowByte = (byte)(value & 0xFD); return; case Mikey.Addresses.DISPADRH: // "DISPADRH (FD95) is upper 8 bits of display address." VideoDisplayStartAddress.HighByte = value; return; case Mikey.Addresses.MAGRDY0: case Mikey.Addresses.MAGRDY1: case Mikey.Addresses.AUDIN: case Mikey.Addresses.MIKEYHREV: //Debug.WriteLineIf(GeneralSwitch.TraceWarning, String.Format("Mikey::Poke - Read-only address {0:X4} used (value {1:X2}).", address, value)); break; default: break; } if (address > Mikey.Addresses.BLUEREDF) { return; } // Blue and red color map if (address >= Mikey.Addresses.BLUERED0 && address <= Mikey.Addresses.BLUEREDF) { int index = address - Mikey.Addresses.BLUERED0; BlueRedColorMap[index] = value; ArgbColorMap[index] &= 0xFF00FF00; ArgbColorMap[index] |= (uint)((value & 0xF0) << 16); // Blue ArgbColorMap[index] |= (uint)((value & 0x0F) << 4); // Red return; } // Green color map if (address >= Mikey.Addresses.GREEN0) { int index = address - Mikey.Addresses.GREEN0; GreenColorMap[index] = value; ArgbColorMap[index] &= 0xFFFF00FF; ArgbColorMap[index] |= (uint)((value & 0x0F) << 12); return; } if (address >= 0xFD20 && address <= 0xFD3F) { return; } //Trace.WriteLineIf(GeneralSwitch.TraceWarning, String.Format("Mikey::Poke: Unknown address ${0:X4} specified (value={1:X2}).", address,value)); }