2012-08-26 14:39:06 +00:00
#define MUSASHI
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 ;
2012-08-26 14:39:06 +00:00
using Native68000 ;
using System.Runtime.InteropServices ;
2011-01-11 02:55:51 +00:00
namespace BizHawk.Emulation.Consoles.Sega
{
2011-12-24 01:59:51 +00:00
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.
2012-08-26 14:39:06 +00:00
#if MUSASHI
VdpCallback _vdp ;
ReadCallback read8 ;
ReadCallback read16 ;
ReadCallback read32 ;
WriteCallback write8 ;
WriteCallback write16 ;
WriteCallback write32 ;
#endif
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 ;
2012-08-26 14:39:06 +00:00
MainCPU . IrqCallback = InterruptCallback ;
// ---------------------- musashi -----------------------
#if MUSASHI
_vdp = vdpcallback ;
read8 = Read8 ;
read16 = Read16 ;
read32 = Read32 ;
write8 = Write8 ;
write16 = Write16 ;
write32 = Write32 ;
Musashi . RegisterVdpCallback ( Marshal . GetFunctionPointerForDelegate ( _vdp ) ) ;
Musashi . RegisterRead8 ( Marshal . GetFunctionPointerForDelegate ( read8 ) ) ;
Musashi . RegisterRead16 ( Marshal . GetFunctionPointerForDelegate ( read16 ) ) ;
Musashi . RegisterRead32 ( Marshal . GetFunctionPointerForDelegate ( read32 ) ) ;
Musashi . RegisterWrite8 ( Marshal . GetFunctionPointerForDelegate ( write8 ) ) ;
Musashi . RegisterWrite16 ( Marshal . GetFunctionPointerForDelegate ( write16 ) ) ;
Musashi . RegisterWrite32 ( Marshal . GetFunctionPointerForDelegate ( write32 ) ) ;
#endif
// ---------------------- musashi -----------------------
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 ( ) ;
2012-08-26 14:39:06 +00:00
#if MUSASHI
Musashi . Init ( ) ;
Musashi . Reset ( ) ;
2012-09-09 21:15:20 +00:00
VDP . GetPC = ( ) = > Musashi . PC ;
2012-08-26 14:39:06 +00:00
#else
MainCPU . Reset ( ) ;
2012-09-09 21:15:20 +00:00
VDP . GetPC = ( ) = > MainCPU . PC ;
2012-08-26 14:39:06 +00:00
#endif
2012-09-13 04:13:49 +00:00
InitializeCartHardware ( game ) ;
}
void InitializeCartHardware ( GameInfo game )
{
LogCartInfo ( ) ;
2012-09-16 19:03:44 +00:00
InitializeEeprom ( game ) ;
2012-09-13 04:13:49 +00:00
InitializeSaveRam ( game ) ;
2012-08-26 14:39:06 +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 ;
2012-08-26 14:39:06 +00:00
Controller . UpdateControls ( Frame + + ) ;
2011-07-30 20:49:36 +00:00
PSG . BeginFrame ( SoundCPU . TotalExecutedCycles ) ;
2012-04-29 01:09:06 +00:00
YM2612 . BeginFrame ( SoundCPU . TotalExecutedCycles ) ;
2012-08-30 04:29:33 +00:00
// Do start-of-frame events
VDP . HIntLineCounter = VDP . Registers [ 10 ] ;
//VDP.VdpStatusWord &=
unchecked { VDP . VdpStatusWord & = ( ushort ) ~ GenVDP . StatusVerticalBlanking ; }
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
2012-09-01 18:40:52 +00:00
if ( VDP . ScanLine < VDP . FrameHeight )
2011-07-30 20:49:36 +00:00
VDP . RenderLine ( ) ;
2011-12-24 01:59:51 +00:00
2012-09-01 18:40:52 +00:00
Exec68k ( 365 ) ;
RunZ80 ( 171 ) ;
// H-Int now?
VDP . HIntLineCounter - - ;
2012-09-03 16:01:56 +00:00
if ( VDP . HIntLineCounter < 0 & & VDP . ScanLine < 224 ) // FIXME
2012-04-29 06:05:15 +00:00
{
2012-09-01 18:40:52 +00:00
VDP . HIntLineCounter = VDP . Registers [ 10 ] ;
VDP . VdpStatusWord | = GenVDP . StatusHorizBlanking ;
if ( VDP . HInterruptsEnabled )
{
Set68kIrq ( 4 ) ;
//Console.WriteLine("Fire hint!");
}
2012-04-29 06:05:15 +00:00
}
2011-07-30 20:49:36 +00:00
2012-09-01 18:40:52 +00:00
Exec68k ( 488 - 365 ) ;
RunZ80 ( 228 - 171 ) ;
2012-08-26 14:39:06 +00:00
if ( VDP . ScanLine = = 224 )
2011-07-30 20:49:36 +00:00
{
2012-08-26 14:39:06 +00:00
VDP . VdpStatusWord | = GenVDP . StatusVerticalInterruptPending ;
VDP . VdpStatusWord | = GenVDP . StatusVerticalBlanking ;
Exec68k ( 16 ) ; // this is stupidly wrong.
2011-07-30 20:49:36 +00:00
// End-frame stuff
2012-08-26 14:39:06 +00:00
if ( VDP . VInterruptEnabled )
Set68kIrq ( 6 ) ;
2011-07-30 20:49:36 +00:00
2012-09-01 18:40:52 +00:00
SoundCPU . Interrupt = true ;
2012-08-30 04:29:33 +00:00
//The INT output is asserted every frame for exactly one scanline, and it can't be disabled. A very short Z80 interrupt routine would be triggered multiple times if it finishes within 228 Z80 clock cycles. I think (but cannot recall the specifics) that some games have delay loops in the interrupt handler for this very reason.
2011-07-30 20:49:36 +00:00
}
}
PSG . EndFrame ( SoundCPU . TotalExecutedCycles ) ;
2012-04-29 01:09:06 +00:00
YM2612 . EndFrame ( SoundCPU . TotalExecutedCycles ) ;
2011-12-24 01:59:51 +00:00
2012-08-30 04:29:33 +00:00
2012-08-26 14:39:06 +00:00
2011-12-24 01:59:51 +00:00
if ( lagged )
{
_lagcount + + ;
islag = true ;
}
else
islag = false ;
2011-06-11 22:15:08 +00:00
}
2012-08-26 14:39:06 +00:00
void Exec68k ( int cycles )
{
#if MUSASHI
Musashi . Execute ( cycles ) ;
#else
MainCPU . ExecuteCycles ( cycles ) ;
#endif
}
2012-09-01 18:40:52 +00:00
void RunZ80 ( int cycles )
{
// I emulate the YM2612 synced to Z80 clock, for better or worse.
// So we still need to keep the Z80 cycle count accurate even if the Z80 isn't running.
if ( Z80Runnable )
SoundCPU . ExecuteCycles ( cycles ) ;
else
SoundCPU . TotalExecutedCycles + = cycles ;
}
2012-08-26 14:39:06 +00:00
void Set68kIrq ( int irq )
{
#if MUSASHI
Musashi . SetIRQ ( irq ) ;
#else
MainCPU . Interrupt = irq ;
#endif
}
int vdpcallback ( int level ) // Musashi handler
{
InterruptCallback ( level ) ;
return - 1 ;
}
void InterruptCallback ( int level )
{
unchecked { VDP . VdpStatusWord & = ( ushort ) ~ GenVDP . StatusVerticalInterruptPending ; }
}
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" ; } }
2012-09-13 04:13:49 +00:00
2011-07-30 20:49:36 +00:00
2012-05-22 02:25:41 +00:00
public void SaveStateText ( TextWriter writer )
{
2012-09-17 05:48:24 +00:00
/ * writer . WriteLine ( "[MegaDrive]" ) ;
2012-05-22 02:25:41 +00:00
MainCPU . SaveStateText ( writer , "Main68K" ) ;
SoundCPU . SaveStateText ( writer ) ;
PSG . SaveStateText ( writer ) ;
VDP . SaveStateText ( writer ) ;
2012-07-30 13:43:25 +00:00
writer . WriteLine ( "Frame {0}" , Frame ) ;
writer . WriteLine ( "Lag {0}" , _lagcount ) ;
2012-07-30 14:42:52 +00:00
writer . WriteLine ( "IsLag {0}" , islag ) ;
2012-05-22 02:25:41 +00:00
writer . Write ( "MainRAM " ) ;
Ram . SaveAsHex ( writer ) ;
writer . Write ( "Z80RAM " ) ;
Z80Ram . SaveAsHex ( writer ) ;
2012-09-17 05:48:24 +00:00
writer . WriteLine ( "[/MegaDrive]" ) ; * /
2012-05-22 02:25:41 +00:00
}
public void LoadStateText ( TextReader reader )
{
2012-09-17 05:48:24 +00:00
/ * while ( true )
2012-05-22 02:25:41 +00:00
{
string [ ] args = reader . ReadLine ( ) . Split ( ' ' ) ;
if ( args [ 0 ] . Trim ( ) = = "" ) continue ;
if ( args [ 0 ] = = "[MegaDrive]" ) continue ;
if ( args [ 0 ] = = "[/MegaDrive]" ) break ;
if ( args [ 0 ] = = "MainRAM" )
Ram . ReadFromHex ( args [ 1 ] ) ;
else if ( args [ 0 ] = = "Z80RAM" )
Z80Ram . ReadFromHex ( args [ 1 ] ) ;
else if ( args [ 0 ] = = "[Main68K]" )
MainCPU . LoadStateText ( reader , "Main68K" ) ;
else if ( args [ 0 ] = = "[Z80]" )
SoundCPU . LoadStateText ( reader ) ;
2012-07-30 13:43:25 +00:00
else if ( args [ 0 ] = = "Frame" )
Frame = int . Parse ( args [ 1 ] ) ;
else if ( args [ 0 ] = = "Lag" )
_lagcount = int . Parse ( args [ 1 ] ) ;
2012-07-30 14:42:52 +00:00
else if ( args [ 0 ] = = "IsLag" )
islag = bool . Parse ( args [ 1 ] ) ;
2012-05-22 02:25:41 +00:00
else if ( args [ 0 ] = = "[PSG]" )
PSG . LoadStateText ( reader ) ;
else if ( args [ 0 ] = = "[VDP]" )
VDP . LoadStateText ( reader ) ;
else
Console . WriteLine ( "Skipping unrecognized identifier " + args [ 0 ] ) ;
2012-09-17 05:48:24 +00:00
} * /
2012-05-22 02:25:41 +00:00
}
2011-07-30 20:49:36 +00:00
public void SaveStateBinary ( BinaryWriter writer )
{
2012-09-17 05:48:24 +00:00
Musashi . SaveStateBinary ( writer ) ; // 124
SoundCPU . SaveStateBinary ( writer ) ; // 46
PSG . SaveStateBinary ( writer ) ; // 15
VDP . SaveStateBinary ( writer ) ; // 65781
YM2612 . SaveStateBinary ( writer ) ; // 35
writer . Write ( Ram ) ; // 65535
writer . Write ( Z80Ram ) ; // 8192
writer . Write ( Frame ) ; // 4
// lag counter crap TODO
writer . Write ( M68000HasZ80Bus ) ; // 1
writer . Write ( Z80Reset ) ; // 1
// TODO Saveram/EEPROM
2011-07-30 20:49:36 +00:00
}
public void LoadStateBinary ( BinaryReader reader )
{
2012-09-17 05:48:24 +00:00
Musashi . LoadStateBinary ( reader ) ;
SoundCPU . LoadStateBinary ( reader ) ;
PSG . LoadStateBinary ( reader ) ;
VDP . LoadStateBinary ( reader ) ;
YM2612 . LoadStateBinary ( reader ) ;
Ram = reader . ReadBytes ( Ram . Length ) ;
Z80Ram = reader . ReadBytes ( Z80Ram . Length ) ;
Frame = reader . ReadInt32 ( ) ;
M68000HasZ80Bus = reader . ReadBoolean ( ) ;
Z80Reset = reader . ReadBoolean ( ) ;
2011-07-30 20:49:36 +00:00
}
public byte [ ] SaveStateBinary ( )
{
2012-09-17 05:48:24 +00:00
var buf = new byte [ 141485 ] ;
var stream = new MemoryStream ( buf ) ;
var writer = new BinaryWriter ( stream ) ;
SaveStateBinary ( writer ) ;
//Console.WriteLine("buf len = {0}", stream.Position);
writer . Close ( ) ;
return buf ;
2011-07-30 20:49:36 +00:00
}
2011-12-24 01:59:51 +00:00
IList < MemoryDomain > memoryDomains ;
void SetupMemoryDomains ( )
{
var domains = new List < MemoryDomain > ( 3 ) ;
2012-09-01 19:49:40 +00:00
var MainMemoryDomain = new MemoryDomain ( "Main RAM" , Ram . Length , Endian . Big ,
2011-12-24 01:59:51 +00:00
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 ) ;
2012-09-02 01:33:12 +00:00
var RomDomain = new MemoryDomain ( "Rom Data" , RomData . Length , Endian . Big ,
2012-09-02 01:52:16 +00:00
addr = > RomData [ addr ] , //adelikat: For speed considerations, I didn't mask this, every tool that uses memory domains is smart enough not to overflow, if I'm wrong let me know!
2012-09-02 01:33:12 +00:00
( addr , value ) = > RomData [ addr & ( RomData . Length - 1 ) ] = value ) ;
2012-09-01 19:49:40 +00:00
var SystemBusDomain = new MemoryDomain ( "System Bus" , 0x1000000 , Endian . Big ,
addr = > ( byte ) ReadByte ( addr ) ,
( addr , value ) = > Write8 ( ( uint ) addr , ( uint ) value ) ) ;
2011-12-24 01:59:51 +00:00
domains . Add ( MainMemoryDomain ) ;
domains . Add ( Z80Domain ) ;
domains . Add ( VRamDomain ) ;
2012-09-02 01:33:12 +00:00
domains . Add ( RomDomain ) ;
2012-09-01 19:49:40 +00:00
domains . Add ( SystemBusDomain ) ;
2011-12-24 01:59:51 +00:00
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
}