2011-01-11 02:55:51 +00:00
|
|
|
|
using System;
|
2011-01-21 03:59:50 +00:00
|
|
|
|
using System.Collections.Generic;
|
2011-01-11 02:55:51 +00:00
|
|
|
|
using System.IO;
|
2011-10-08 23:26:29 +00:00
|
|
|
|
using BizHawk.Emulation.CPUs.M68000;
|
2011-01-11 02:55:51 +00:00
|
|
|
|
using BizHawk.Emulation.CPUs.Z80;
|
|
|
|
|
using BizHawk.Emulation.Sound;
|
|
|
|
|
|
|
|
|
|
namespace BizHawk.Emulation.Consoles.Sega
|
|
|
|
|
{
|
2011-12-24 01:59:51 +00:00
|
|
|
|
[CoreVersion("0.0.0.1", FriendlyName = "MegaHawk")]
|
|
|
|
|
public sealed partial class Genesis : IEmulator
|
2011-07-30 20:49:36 +00:00
|
|
|
|
{
|
2011-12-24 01:59:51 +00:00
|
|
|
|
private int _lagcount = 0;
|
|
|
|
|
private bool lagged = true;
|
|
|
|
|
private bool islag = false;
|
|
|
|
|
|
2011-07-30 20:49:36 +00:00
|
|
|
|
// ROM
|
|
|
|
|
public byte[] RomData;
|
|
|
|
|
|
|
|
|
|
// Machine stuff
|
2011-10-07 03:04:48 +00:00
|
|
|
|
public MC68000 MainCPU;
|
2011-07-30 20:49:36 +00:00
|
|
|
|
public Z80A SoundCPU;
|
|
|
|
|
public GenVDP VDP;
|
|
|
|
|
public SN76489 PSG;
|
|
|
|
|
public YM2612 YM2612;
|
|
|
|
|
public byte[] Ram = new byte[0x10000];
|
|
|
|
|
public byte[] Z80Ram = new byte[0x2000];
|
|
|
|
|
|
|
|
|
|
private bool M68000HasZ80Bus = false;
|
|
|
|
|
private bool Z80Reset = false;
|
|
|
|
|
private bool Z80Runnable { get { return (Z80Reset == false && M68000HasZ80Bus == false); } }
|
|
|
|
|
|
|
|
|
|
private SoundMixer SoundMixer;
|
|
|
|
|
|
|
|
|
|
public void ResetFrameCounter()
|
|
|
|
|
{
|
|
|
|
|
Frame = 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Genesis timings:
|
|
|
|
|
// 53,693,175 Machine clocks / sec
|
|
|
|
|
// 7,670,454 Main 68000 cycles / sec (7 mclk divisor)
|
|
|
|
|
// 3,579,545 Z80 cycles / sec (15 mclk divisor)
|
|
|
|
|
|
|
|
|
|
// At 59.92 FPS:
|
|
|
|
|
// 896,081 mclks / frame
|
|
|
|
|
// 128,011 Main 68000 cycles / frame
|
|
|
|
|
// 59,738 Z80 cycles / frame
|
|
|
|
|
|
|
|
|
|
// At 262 lines/frame:
|
|
|
|
|
// 3420 mclks / line
|
|
|
|
|
// ~ 488.5 Main 68000 cycles / line
|
|
|
|
|
// 228 Z80 cycles / line
|
|
|
|
|
|
|
|
|
|
// Video characteristics:
|
|
|
|
|
// 224 lines are active display. The remaining 38 lines are vertical blanking.
|
|
|
|
|
// In H40 mode, the dot clock is 480 pixels per line.
|
|
|
|
|
// 320 are active display, the remaining 160 are horizontal blanking.
|
|
|
|
|
// A total of 3420 mclks per line, but 2560 mclks are active display and 860 mclks are blanking.
|
|
|
|
|
|
2011-10-01 17:06:25 +00:00
|
|
|
|
public Genesis(GameInfo game, byte[] rom)
|
2011-07-30 20:49:36 +00:00
|
|
|
|
{
|
2011-06-11 22:15:08 +00:00
|
|
|
|
CoreOutputComm = new CoreOutputComm();
|
2011-10-07 03:04:48 +00:00
|
|
|
|
MainCPU = new MC68000();
|
2011-07-30 20:49:36 +00:00
|
|
|
|
SoundCPU = new Z80A();
|
2012-04-29 01:09:06 +00:00
|
|
|
|
YM2612 = new YM2612() { MaxVolume = 23405 };
|
|
|
|
|
PSG = new SN76489() { MaxVolume = 4681 };
|
2011-07-30 20:49:36 +00:00
|
|
|
|
VDP = new GenVDP();
|
2011-10-11 03:52:44 +00:00
|
|
|
|
VDP.DmaReadFrom68000 = ReadWord;
|
2011-07-30 20:49:36 +00:00
|
|
|
|
SoundMixer = new SoundMixer(YM2612, PSG);
|
|
|
|
|
|
2011-10-11 03:52:44 +00:00
|
|
|
|
MainCPU.ReadByte = ReadByte;
|
|
|
|
|
MainCPU.ReadWord = ReadWord;
|
|
|
|
|
MainCPU.ReadLong = ReadLong;
|
|
|
|
|
MainCPU.WriteByte = WriteByte;
|
|
|
|
|
MainCPU.WriteWord = WriteWord;
|
|
|
|
|
MainCPU.WriteLong = WriteLong;
|
2011-07-30 20:49:36 +00:00
|
|
|
|
|
|
|
|
|
SoundCPU.ReadMemory = ReadMemoryZ80;
|
|
|
|
|
SoundCPU.WriteMemory = WriteMemoryZ80;
|
|
|
|
|
SoundCPU.WriteHardware = (a, v) => { Console.WriteLine("Z80: Attempt I/O Write {0:X2}:{1:X2}", a, v); };
|
|
|
|
|
SoundCPU.ReadHardware = x => 0xFF;
|
|
|
|
|
SoundCPU.IRQCallback = () => SoundCPU.Interrupt = false;
|
|
|
|
|
Z80Reset = true;
|
2011-12-24 01:59:51 +00:00
|
|
|
|
RomData = new byte[0x400000];
|
|
|
|
|
for (int i = 0; i < rom.Length; i++)
|
|
|
|
|
RomData[i] = rom[i];
|
2011-07-30 20:49:36 +00:00
|
|
|
|
|
2011-12-24 01:59:51 +00:00
|
|
|
|
SetupMemoryDomains();
|
|
|
|
|
MainCPU.Reset();
|
2011-10-01 17:06:25 +00:00
|
|
|
|
}
|
2011-07-30 20:49:36 +00:00
|
|
|
|
|
|
|
|
|
public void FrameAdvance(bool render)
|
|
|
|
|
{
|
2011-12-24 01:59:51 +00:00
|
|
|
|
lagged = true;
|
|
|
|
|
|
2011-07-30 20:49:36 +00:00
|
|
|
|
Frame++;
|
|
|
|
|
PSG.BeginFrame(SoundCPU.TotalExecutedCycles);
|
2012-04-29 01:09:06 +00:00
|
|
|
|
YM2612.BeginFrame(SoundCPU.TotalExecutedCycles);
|
2011-07-30 20:49:36 +00:00
|
|
|
|
for (VDP.ScanLine = 0; VDP.ScanLine < 262; VDP.ScanLine++)
|
|
|
|
|
{
|
2011-10-16 06:23:15 +00:00
|
|
|
|
//Log.Error("VDP","FRAME {0}, SCANLINE {1}", Frame, VDP.ScanLine);
|
2011-07-30 20:49:36 +00:00
|
|
|
|
|
|
|
|
|
if (VDP.ScanLine < 224)
|
|
|
|
|
VDP.RenderLine();
|
2011-12-24 01:59:51 +00:00
|
|
|
|
|
2011-10-07 03:04:48 +00:00
|
|
|
|
MainCPU.ExecuteCycles(487); // 488??
|
2012-04-29 06:05:15 +00:00
|
|
|
|
if (Z80Runnable)
|
|
|
|
|
{
|
|
|
|
|
//Console.WriteLine("running z80");
|
|
|
|
|
SoundCPU.ExecuteCycles(228);
|
|
|
|
|
SoundCPU.Interrupt = false;
|
|
|
|
|
} else {
|
|
|
|
|
SoundCPU.TotalExecutedCycles += 228; // I emulate the YM2612 synced to Z80 clock, for better or worse. Keep the timer going even if Z80 isn't running.
|
|
|
|
|
}
|
2011-07-30 20:49:36 +00:00
|
|
|
|
|
|
|
|
|
if (VDP.ScanLine == 224)
|
|
|
|
|
{
|
2011-12-24 01:59:51 +00:00
|
|
|
|
MainCPU.ExecuteCycles(16);// stupid crap to sync with genesis plus for log testing
|
2011-07-30 20:49:36 +00:00
|
|
|
|
// End-frame stuff
|
2011-10-08 15:44:41 +00:00
|
|
|
|
if (VDP.VInterruptEnabled)
|
|
|
|
|
MainCPU.Interrupt = 6;
|
2011-07-30 20:49:36 +00:00
|
|
|
|
|
|
|
|
|
if (Z80Runnable)
|
|
|
|
|
SoundCPU.Interrupt = true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
PSG.EndFrame(SoundCPU.TotalExecutedCycles);
|
2012-04-29 01:09:06 +00:00
|
|
|
|
YM2612.EndFrame(SoundCPU.TotalExecutedCycles);
|
2011-12-24 01:59:51 +00:00
|
|
|
|
|
|
|
|
|
Controller.UpdateControls(Frame++);
|
|
|
|
|
if (lagged)
|
|
|
|
|
{
|
|
|
|
|
_lagcount++;
|
|
|
|
|
islag = true;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
islag = false;
|
2011-06-11 22:15:08 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public CoreInputComm CoreInputComm { get; set; }
|
|
|
|
|
public CoreOutputComm CoreOutputComm { get; private set; }
|
|
|
|
|
|
|
|
|
|
public IVideoProvider VideoProvider
|
2011-07-30 20:49:36 +00:00
|
|
|
|
{
|
|
|
|
|
get { return VDP; }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public ISoundProvider SoundProvider
|
|
|
|
|
{
|
|
|
|
|
get { return SoundMixer; }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public int Frame { get; set; }
|
2011-12-24 01:59:51 +00:00
|
|
|
|
public int LagCount { get { return _lagcount; } set { _lagcount = value; } }
|
|
|
|
|
public bool IsLagFrame { get { return islag; } }
|
2011-07-30 20:49:36 +00:00
|
|
|
|
public bool DeterministicEmulation { get; set; }
|
|
|
|
|
public string SystemId { get { return "GEN"; } }
|
|
|
|
|
|
|
|
|
|
public byte[] SaveRam
|
|
|
|
|
{
|
|
|
|
|
get { throw new NotImplementedException(); }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public bool SaveRamModified
|
|
|
|
|
{
|
|
|
|
|
get
|
|
|
|
|
{
|
|
|
|
|
return false; // TODO implement
|
|
|
|
|
}
|
|
|
|
|
set
|
|
|
|
|
{
|
|
|
|
|
throw new NotImplementedException();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void SaveStateText(TextWriter writer)
|
|
|
|
|
{
|
|
|
|
|
throw new NotImplementedException();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void LoadStateText(TextReader reader)
|
|
|
|
|
{
|
|
|
|
|
throw new NotImplementedException();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void SaveStateBinary(BinaryWriter writer)
|
|
|
|
|
{
|
|
|
|
|
throw new NotImplementedException();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void LoadStateBinary(BinaryReader reader)
|
|
|
|
|
{
|
|
|
|
|
throw new NotImplementedException();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public byte[] SaveStateBinary()
|
|
|
|
|
{
|
|
|
|
|
return new byte[0];
|
|
|
|
|
}
|
|
|
|
|
|
2011-12-24 01:59:51 +00:00
|
|
|
|
IList<MemoryDomain> memoryDomains;
|
|
|
|
|
|
|
|
|
|
void SetupMemoryDomains()
|
|
|
|
|
{
|
|
|
|
|
var domains = new List<MemoryDomain>(3);
|
|
|
|
|
var MainMemoryDomain = new MemoryDomain("68000 RAM", Ram.Length, Endian.Big,
|
|
|
|
|
addr => Ram[addr & 0xFFFF],
|
|
|
|
|
(addr, value) => Ram[addr & 0xFFFF] = value);
|
|
|
|
|
var Z80Domain = new MemoryDomain("Z80 RAM", Z80Ram.Length, Endian.Little,
|
|
|
|
|
addr => Z80Ram[addr & 0x1FFF],
|
|
|
|
|
(addr, value) => { Z80Ram[addr & 0x1FFF] = value; });
|
|
|
|
|
|
|
|
|
|
var VRamDomain = new MemoryDomain("Video RAM", VDP.VRAM.Length, Endian.Big,
|
|
|
|
|
addr => VDP.VRAM[addr & 0xFFFF],
|
|
|
|
|
(addr, value) => VDP.VRAM[addr & 0xFFFF] = value);
|
|
|
|
|
|
|
|
|
|
domains.Add(MainMemoryDomain);
|
|
|
|
|
domains.Add(Z80Domain);
|
|
|
|
|
domains.Add(VRamDomain);
|
|
|
|
|
memoryDomains = domains.AsReadOnly();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public IList<MemoryDomain> MemoryDomains { get { return memoryDomains; } }
|
|
|
|
|
public MemoryDomain MainMemory { get { return memoryDomains[0]; } }
|
2011-07-30 20:49:36 +00:00
|
|
|
|
|
|
|
|
|
public void Dispose() { }
|
|
|
|
|
}
|
2011-01-11 02:55:51 +00:00
|
|
|
|
}
|