Deduplicate code common to CPCHawk and ZXHawk

Also fixed indentation and replaced some block comments with #if false
This commit is contained in:
YoshiRulz 2020-03-07 10:06:20 +10:00
parent 7456d81a9c
commit c7c26cd946
No known key found for this signature in database
GPG Key ID: C4DE31C245353FB7
72 changed files with 1562 additions and 7887 deletions

View File

@ -1,25 +0,0 @@
using BizHawk.Common;
namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
{
/// <summary>
/// Represents a beeper/buzzer device
/// </summary>
public interface IBeeperDevice
{
/// <summary>
/// Initialization
/// </summary>
void Init(int sampleRate, int tStatesPerFrame);
/// <summary>
/// Processes an incoming pulse value and adds it to the blipbuffer
/// </summary>
void ProcessPulseValue(bool pulse);
/// <summary>
/// State serialization
/// </summary>
void SyncState(Serializer ser);
}
}

View File

@ -5,6 +5,8 @@ using System.Collections.Generic;
using System.Linq;
using System.Text;
using BizHawk.Emulation.Cores.Computers.CPCSpectrumBase;
namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
{
/// <summary>

View File

@ -1,180 +0,0 @@

namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
{
/// <summary>
/// Used for the sector CHRN structure
/// </summary>
public class CHRN
{
/// <summary>
/// Track
/// </summary>
public byte C { get; set; }
/// <summary>
/// Side
/// </summary>
public byte H { get; set; }
/// <summary>
/// Sector ID
/// </summary>
public byte R { get; set; }
/// <summary>
/// Sector Size
/// </summary>
public byte N { get; set; }
/// <summary>
/// Status register 1
/// </summary>
private byte _flag1;
public byte Flag1
{
get => _flag1;
set => _flag1 = value;
}
/// <summary>
/// Status register 2
/// </summary>
private byte _flag2;
public byte Flag2
{
get => _flag2;
set => _flag2 = value;
}
/// <summary>
/// Used to store the last transmitted/received data bytes
/// </summary>
public byte[] DataBytes { get; set; }
/// <summary>
/// ID for the read/write data command
/// </summary>
public int DataID { get; set; }
#region Helper Methods
/// <summary>
/// Missing Address Mark (Sector_ID or DAM not found)
/// </summary>
public bool ST1MA
{
get => NECUPD765.GetBit(0, _flag1);
set
{
if (value) { NECUPD765.SetBit(0, ref _flag1); }
else { NECUPD765.UnSetBit(0, ref _flag1); }
}
}
/// <summary>
/// No Data (Sector_ID not found, CRC fail in ID_field)
/// </summary>
public bool ST1ND
{
get => NECUPD765.GetBit(2, _flag1);
set
{
if (value) { NECUPD765.SetBit(2, ref _flag1); }
else { NECUPD765.UnSetBit(2, ref _flag1); }
}
}
/// <summary>
/// Data Error (CRC-fail in ID- or Data-Field)
/// </summary>
public bool ST1DE
{
get => NECUPD765.GetBit(5, _flag1);
set
{
if (value) { NECUPD765.SetBit(5, ref _flag1); }
else { NECUPD765.UnSetBit(5, ref _flag1); }
}
}
/// <summary>
/// End of Track (set past most read/write commands) (see IC)
/// </summary>
public bool ST1EN
{
get => NECUPD765.GetBit(7, _flag1);
set
{
if (value) { NECUPD765.SetBit(7, ref _flag1); }
else { NECUPD765.UnSetBit(7, ref _flag1); }
}
}
/// <summary>
/// Missing Address Mark in Data Field (DAM not found)
/// </summary>
public bool ST2MD
{
get => NECUPD765.GetBit(0, _flag2);
set
{
if (value) { NECUPD765.SetBit(0, ref _flag2); }
else { NECUPD765.UnSetBit(0, ref _flag2); }
}
}
/// <summary>
/// Bad Cylinder (read/programmed track-ID different and read-ID = FF)
/// </summary>
public bool ST2BC
{
get => NECUPD765.GetBit(1, _flag2);
set
{
if (value) { NECUPD765.SetBit(1, ref _flag2); }
else { NECUPD765.UnSetBit(1, ref _flag2); }
}
}
/// <summary>
/// Wrong Cylinder (read/programmed track-ID different) (see b1)
/// </summary>
public bool ST2WC
{
get => NECUPD765.GetBit(4, _flag2);
set
{
if (value) { NECUPD765.SetBit(4, ref _flag2); }
else { NECUPD765.UnSetBit(4, ref _flag2); }
}
}
/// <summary>
/// Data Error in Data Field (CRC-fail in data-field)
/// </summary>
public bool ST2DD
{
get => NECUPD765.GetBit(5, _flag2);
set
{
if (value) { NECUPD765.SetBit(5, ref _flag2); }
else { NECUPD765.UnSetBit(5, ref _flag2); }
}
}
/// <summary>
/// Control Mark (read/scan command found sector with deleted DAM)
/// </summary>
public bool ST2CM
{
get => NECUPD765.GetBit(6, _flag2);
set
{
if (value) { NECUPD765.SetBit(6, ref _flag2); }
else { NECUPD765.UnSetBit(6, ref _flag2); }
}
}
#endregion
}
}

View File

@ -1,875 +0,0 @@
using BizHawk.Common;
using BizHawk.Common.NumberExtensions;
using System;
using System.Linq;
namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
{
/// <summary>
/// Floppy drive related stuff
/// </summary>
#region Attribution
/*
Implementation based on the information contained here:
http://www.cpcwiki.eu/index.php/765_FDC
and here:
http://www.cpcwiki.eu/imgs/f/f3/UPD765_Datasheet_OCRed.pdf
*/
#endregion
public partial class NECUPD765 : IFDDHost
{
#region Drive State
/// <summary>
/// FDD Flag - motor on/off
/// </summary>
public bool FDD_FLAG_MOTOR;
/// <summary>
/// The index of the currently active disk drive
/// </summary>
public int DiskDriveIndex
{
get => _diskDriveIndex;
set
{
// when index is changed update the ActiveDrive
_diskDriveIndex = value;
ActiveDrive = DriveStates[_diskDriveIndex];
}
}
private int _diskDriveIndex = 0;
/// <summary>
/// The currently active drive
/// </summary>
private DriveState ActiveDrive;
/// <summary>
/// Array that holds state information for each possible drive
/// </summary>
private DriveState[] DriveStates = new DriveState[4];
#endregion
#region FDD Methods
/// <summary>
/// Initialization / reset of the floppy drive subsystem
/// </summary>
private void FDD_Init()
{
for (int i = 0; i < 4; i++)
{
DriveState ds = new DriveState(i, this);
DriveStates[i] = ds;
}
}
/// <summary>
/// Searches for the requested sector
/// </summary>
private FloppyDisk.Sector GetSector()
{
FloppyDisk.Sector sector = null;
// get the current track
var trk = ActiveDrive.Disk.DiskTracks[ActiveDrive.TrackIndex];
// get the current sector index
int index = ActiveDrive.SectorIndex;
// make sure this index exists
if (index > trk.Sectors.Length)
{
index = 0;
}
// index hole count
int iHole = 0;
// loop through the sectors in a track
// the loop ends with either the sector being found
// or the index hole being passed twice
while (iHole <= 2)
{
// does the requested sector match the current sector
if (trk.Sectors[index].SectorIDInfo.C == ActiveCommandParams.Cylinder &&
trk.Sectors[index].SectorIDInfo.H == ActiveCommandParams.Head &&
trk.Sectors[index].SectorIDInfo.R == ActiveCommandParams.Sector &&
trk.Sectors[index].SectorIDInfo.N == ActiveCommandParams.SectorSize)
{
// sector has been found
sector = trk.Sectors[index];
UnSetBit(SR2_BC, ref Status2);
UnSetBit(SR2_WC, ref Status2);
break;
}
// check for bad cylinder
if (trk.Sectors[index].SectorIDInfo.C == 255)
{
SetBit(SR2_BC, ref Status2);
}
// check for no cylinder
else if (trk.Sectors[index].SectorIDInfo.C != ActiveCommandParams.Cylinder)
{
SetBit(SR2_WC, ref Status2);
}
// incrememnt sector index
index++;
// have we reached the index hole?
if (trk.Sectors.Length <= index)
{
// wrap around
index = 0;
iHole++;
}
}
// search loop has completed and the sector may or may not have been found
// bad cylinder detected?
if (Status2.Bit(SR2_BC))
{
// remove WC
UnSetBit(SR2_WC, ref Status2);
}
// update sectorindex on drive
ActiveDrive.SectorIndex = index;
return sector;
}
#endregion
#region IFDDHost
// IFDDHost methods that fall through to the currently active drive
/// <summary>
/// Parses a new disk image and loads it into this floppy drive
/// </summary>
public void FDD_LoadDisk(byte[] diskData)
{
// we are only going to load into the first drive
DriveStates[0].FDD_LoadDisk(diskData);
}
/// <summary>
/// Ejects the current disk
/// </summary>
public void FDD_EjectDisk()
{
DriveStates[0].FDD_EjectDisk();
}
/// <summary>
/// Signs whether the current active drive has a disk inserted
/// </summary>
public bool FDD_IsDiskLoaded => DriveStates[DiskDriveIndex].FDD_IsDiskLoaded;
/// <summary>
/// Returns the disk object from drive 0
/// </summary>
public FloppyDisk DiskPointer => DriveStates[0].Disk;
public FloppyDisk Disk { get; set; }
#endregion
#region Drive Status Class
/// <summary>
/// Holds specfic state information about a drive
/// </summary>
private class DriveState : IFDDHost
{
#region State
/// <summary>
/// The drive ID from an FDC perspective
/// </summary>
public int ID;
/// <summary>
/// Signs whether this drive ready
/// TRUE if both drive exists and has a disk inserted
/// </summary>
public bool FLAG_READY
{
get
{
if (!FDD_IsDiskLoaded || Disk.GetTrackCount() == 0 || !FDC.FDD_FLAG_MOTOR)
return false;
else
return true;
}
}
/// <summary>
/// Disk is write protected (TRUE BY DEFAULT)
/// </summary>
public bool FLAG_WRITEPROTECT = false;
/// <summary>
/// Storage for seek steps
/// One step for each indexpulse (track index) until seeked track
/// </summary>
public int SeekCounter;
/// <summary>
/// Seek status
/// </summary>
public int SeekStatus;
/// <summary>
/// Age counter
/// </summary>
public int SeekAge;
/// <summary>
/// The current side
/// </summary>
public byte CurrentSide;
/// <summary>
/// The current track index in the DiskTracks array
/// </summary>
public byte TrackIndex;
/// <summary>
/// The track ID of the current cylinder
/// </summary>
public byte CurrentTrackID
{
get
{
// default invalid track
int id = 0xff;
if (Disk == null)
return (byte)id;
if (Disk.DiskTracks.Count() == 0)
return (byte)id;
if (TrackIndex >= Disk.GetTrackCount())
TrackIndex = 0;
else if (TrackIndex < 0)
TrackIndex = 0;
var track = Disk.DiskTracks[TrackIndex];
id = track.TrackNumber;
return (byte)id;
}
set
{
for (int i = 0; i < Disk.GetTrackCount(); i++)
{
if (Disk.DiskTracks[i].TrackNumber == value)
{
TrackIndex = (byte)i;
break;
}
}
}
}
/// <summary>
/// The new track that the drive is seeking to
/// (used in seek operations)
/// </summary>
public int SeekingTrack;
/// <summary>
/// The current sector index in the Sectors array
/// </summary>
public int SectorIndex;
/// <summary>
/// The currently loaded floppy disk
/// </summary>
public FloppyDisk Disk { get; set; }
/// <summary>
/// The parent controller
/// </summary>
private NECUPD765 FDC;
#endregion
#region Lookups
/// <summary>
/// TRUE if we are on track 0
/// </summary>
public bool FLAG_TRACK0
{
get
{
if (TrackIndex == 0) { return true; }
else { return false; }
}
}
#endregion
#region Public Methods
/*
/// <summary>
/// Moves the head across the disk cylinders
/// </summary>
public void MoveHead(SkipDirection direction, int cylinderCount)
{
// get total tracks
int trackCount = Disk.DiskTracks.Count();
int trk = 0;
switch (direction)
{
case SkipDirection.Increment:
trk = (int)CurrentTrack + cylinderCount;
if (trk >= trackCount)
{
// past the last track
trk = trackCount - 1;
}
else if (trk < 0)
trk = 0;
break;
case SkipDirection.Decrement:
trk = (int)CurrentTrack - cylinderCount;
if (trk < 0)
{
// before the first track
trk = 0;
}
else if (trk >= trackCount)
trk = trackCount - 1;
break;
}
// move the head
CurrentTrack = (byte)trk;
}
*/
/*
/// <summary>
/// Finds a supplied sector
/// </summary>
public FloppyDisk.Sector FindSector(ref byte[] resBuffer, CommandParameters prms)
{
int index =CurrentSector;
int lc = 0;
FloppyDisk.Sector sector = null;
bool found = false;
do
{
sector = Disk.DiskTracks[CurrentTrack].Sectors[index];
if (sector != null && sector.SectorID == prms.Sector)
{
// sector found
// check for data errors
if ((sector.Status1 & 0x20) != 0 || (sector.Status2 & 0x20) != 0)
{
// data errors found
}
found = true;
break;
}
// sector doesnt match
var c = Disk.DiskTracks[CurrentTrack].Sectors[index].TrackNumber;
if (c == 255)
{
// bad cylinder
resBuffer[RS_ST2] |= 0x02;
}
else if (prms.Cylinder != c)
{
// cylinder mismatch
resBuffer[RS_ST2] |= 0x10;
}
// increment index
index++;
if (index >= Disk.DiskTracks[CurrentTrack].NumberOfSectors)
{
// out of bounds
index = 0;
lc++;
}
} while (lc < 2);
if ((resBuffer[RS_ST2] & 0x02) != 0)
{
// bad cylinder set - remove no cylinder
UnSetBit(SR2_WC, ref resBuffer[RS_ST2]);
}
// update current sector
CurrentSector = index;
if (found)
return sector;
else
return null;
}
/// <summary>
/// Populates a result buffer
/// </summary>
public void FillResult(ref byte[] resBuffer, CHRN chrn)
{
// clear results
resBuffer[RS_ST0] = 0;
resBuffer[RS_ST1] = 0;
resBuffer[RS_ST2] = 0;
resBuffer[RS_C] = 0;
resBuffer[RS_H] = 0;
resBuffer[RS_R] = 0;
resBuffer[RS_N] = 0;
if (chrn == null)
{
// no chrn supplied
resBuffer[RS_ST0] = ST0;
resBuffer[RS_ST1] = 0;
resBuffer[RS_ST2] = 0;
resBuffer[RS_C] = 0;
resBuffer[RS_H] = 0;
resBuffer[RS_R] = 0;
resBuffer[RS_N] = 0;
}
}
/// <summary>
/// Populates the result buffer with ReadID data
/// </summary>
public void ReadID(ref byte[] resBuffer)
{
if (CheckDriveStatus() == false)
{
// drive not ready
resBuffer[RS_ST0] = ST0;
return;
}
var track = Disk.DiskTracks.Where(a => a.TrackNumber == CurrentTrack).FirstOrDefault();
if (track != null && track.NumberOfSectors > 0)
{
// formatted track
// get the current sector
int index = CurrentSector;
// is the index out of bounds?
if (index >= track.NumberOfSectors)
{
// reset the index
index = 0;
}
// read the sector data
var data = track.Sectors[index];
resBuffer[RS_C] = data.TrackNumber;
resBuffer[RS_H] = data.SideNumber;
resBuffer[RS_R] = data.SectorID;
resBuffer[RS_N] = data.SectorSize;
resBuffer[RS_ST0] = ST0;
// increment the current sector
CurrentSector = index + 1;
return;
}
else
{
// unformatted track?
resBuffer[RS_C] = FDC.CommBuffer[CM_C];
resBuffer[RS_H] = FDC.CommBuffer[CM_H];
resBuffer[RS_R] = FDC.CommBuffer[CM_R];
resBuffer[RS_N] = FDC.CommBuffer[CM_N];
SetBit(SR0_IC0, ref ST0);
resBuffer[RS_ST0] = ST0;
resBuffer[RS_ST1] = 0x01;
return;
}
}
*/
/*
/// <summary>
/// The drive performs a seek operation if necessary
/// Return value TRUE indicates seek complete
/// </summary>
public void DoSeek()
{
if (CurrentState != DriveMainState.Recalibrate &&
CurrentState != DriveMainState.Seek)
{
// no seek/recalibrate has been asked for
return;
}
if (GetBit(ID, FDC.StatusMain))
{
// drive is already seeking
return;
}
RunSeekCycle();
}
/// <summary>
/// Runs a seek cycle
/// </summary>
public void RunSeekCycle()
{
for (;;)
{
switch (SeekState)
{
// seek or recalibrate has been requested
case SeekSubState.Idle:
if (CurrentState == DriveMainState.Recalibrate)
{
// recalibrate always seeks to track 0
SeekingTrack = 0;
}
SeekState = SeekSubState.MoveInit;
// mark drive as busy
// this should be cleared by SIS command
SetBit(ID, ref FDC.StatusMain);
break;
// setup for the head move
case SeekSubState.MoveInit:
if (CurrentTrack == SeekingTrack)
{
// we are already at the required track
if (CurrentState == DriveMainState.Recalibrate &&
!FLAG_TRACK0)
{
// recalibration fail
SeekIntState = SeekIntStatus.Abnormal;
// raise seek interrupt
FDC.ActiveInterrupt = InterruptState.Seek;
// unset DB bit
UnSetBit(ID, ref FDC.StatusMain);
// equipment check
SetBit(SR0_EC, ref FDC.Status0);
SeekState = SeekSubState.PerformCompletion;
break;
}
if (CurrentState == DriveMainState.Recalibrate &&
FLAG_TRACK0)
{
// recalibration success
SeekIntState = SeekIntStatus.Normal;
// raise seek interrupt
FDC.ActiveInterrupt = InterruptState.Seek;
// unset DB bit
UnSetBit(ID, ref FDC.StatusMain);
SeekState = SeekSubState.PerformCompletion;
break;
}
}
// check for error
if (IntStatus >= IC_ABORTED_DISCREMOVED || Disk == null)
{
// drive not ready
FLAG_READY = false;
// drive not ready
SeekIntState = SeekIntStatus.DriveNotReady;
// cancel any interrupt
FDC.ActiveInterrupt = InterruptState.None;
// unset DB bit
UnSetBit(ID, ref FDC.StatusMain);
SeekState = SeekSubState.PerformCompletion;
break;
}
if (SeekCounter > 1)
{
// not ready to seek yet
SeekCounter--;
return;
}
if (FDC.SRT < 1 && CurrentTrack != SeekingTrack)
{
SeekState = SeekSubState.MoveImmediate;
break;
}
// head move
SeekState = SeekSubState.HeadMove;
break;
case SeekSubState.HeadMove:
// do the seek
SeekCounter = FDC.SRT;
if (CurrentTrack < SeekingTrack)
{
// we are seeking forward
var delta = SeekingTrack - CurrentTrack;
MoveHead(SkipDirection.Increment, 1);
}
else if (CurrentTrack > SeekingTrack)
{
// we are seeking backward
var delta = CurrentTrack - SeekingTrack;
MoveHead(SkipDirection.Decrement, 1);
}
// should the seek be completed now?
if (CurrentTrack == SeekingTrack)
{
SeekState = SeekSubState.PerformCompletion;
break;
}
// seek not finished yet
return;
// seek emulation processed immediately
case SeekSubState.MoveImmediate:
if (CurrentTrack < SeekingTrack)
{
// we are seeking forward
var delta = SeekingTrack - CurrentTrack;
MoveHead(SkipDirection.Increment, delta);
}
else if (CurrentTrack > SeekingTrack)
{
// we are seeking backward
var delta = CurrentTrack - SeekingTrack;
MoveHead(SkipDirection.Decrement, delta);
}
SeekState = SeekSubState.PerformCompletion;
break;
case SeekSubState.PerformCompletion:
SeekDone();
SeekState = SeekSubState.SeekCompleted;
break;
case SeekSubState.SeekCompleted:
// seek has already completed
return;
}
}
}
/// <summary>
/// Called when a seek operation has completed
/// </summary>
public void SeekDone()
{
SeekCounter = 0;
SeekingTrack = CurrentTrack;
// generate ST0 register data
// get only the IC bits
IntStatus &= IC_ABORTED_DISCREMOVED;
// drive ready?
if (!FLAG_READY)
{
SetBit(SR0_NR, ref IntStatus);
SetBit(SR0_EC, ref IntStatus);
// are we recalibrating?
if (CurrentState == DriveMainState.Recalibrate)
{
SetBit(SR0_EC, ref IntStatus);
}
}
// set seek end
SetBit(SR0_SE, ref IntStatus);
/*
// head address
if (CurrentSide > 0)
{
SetBit(SR0_HD, ref IntStatus);
// drive only supports 1 head
// set the EC bit
SetBit(SR0_EC, ref IntStatus);
}
*/
/*
// UnitSelect
SetUnitSelect(ID, ref IntStatus);
// move to none state
//CurrentState = DriveMainState.None;
//SeekState = SeekSubState.SeekCompleted;
// set the seek interrupt flag for this drive
// this will be cleared at the next successful senseint
FLAG_SEEK_INTERRUPT = true;
//CurrentState = DriveMainState.None;
}
*/
#endregion
#region Construction
public DriveState(int driveID, NECUPD765 fdc)
{
ID = driveID;
FDC = fdc;
}
#endregion
#region IFDDHost
/// <summary>
/// Parses a new disk image and loads it into this floppy drive
/// </summary>
public void FDD_LoadDisk(byte[] diskData)
{
// try dsk first
FloppyDisk fdd = null;
bool found = false;
foreach (DiskType type in Enum.GetValues(typeof(DiskType)))
{
switch (type)
{
case DiskType.CPCExtended:
fdd = new CPCExtendedFloppyDisk();
found = fdd.ParseDisk(diskData);
break;
case DiskType.CPC:
fdd = new CPCFloppyDisk();
found = fdd.ParseDisk(diskData);
break;
}
if (found)
{
Disk = fdd;
break;
}
}
if (!found)
{
throw new Exception(this.GetType().ToString() +
"\n\nDisk image file could not be parsed. Potentially an unknown format.");
}
}
/// <summary>
/// Ejects the current disk
/// </summary>
public void FDD_EjectDisk()
{
Disk = null;
//FLAG_READY = false;
}
/// <summary>
/// Signs whether the current active drive has a disk inserted
/// </summary>
public bool FDD_IsDiskLoaded
{
get
{
if (Disk != null)
return true;
else
return false;
}
}
#endregion
#region StateSerialization
public void SyncState(Serializer ser)
{
ser.Sync(nameof(ID), ref ID);
ser.Sync(nameof(FLAG_WRITEPROTECT), ref FLAG_WRITEPROTECT);
//ser.Sync(nameof(FLAG_DISKCHANGED), ref FLAG_DISKCHANGED);
//ser.Sync(nameof(FLAG_RECALIBRATING), ref FLAG_RECALIBRATING);
//ser.Sync(nameof(FLAG_SEEK_INTERRUPT), ref FLAG_SEEK_INTERRUPT);
//ser.Sync(nameof(IntStatus), ref IntStatus);
//ser.Sync(nameof(ST0), ref ST0);
//ser.Sync(nameof(RecalibrationCounter), ref RecalibrationCounter);
ser.Sync(nameof(SeekCounter), ref SeekCounter);
ser.Sync(nameof(SeekStatus), ref SeekStatus);
ser.Sync(nameof(SeekAge), ref SeekAge);
ser.Sync(nameof(CurrentSide), ref CurrentSide);
//ser.Sync(nameof(CurrentTrack), ref CurrentTrack);
ser.Sync(nameof(TrackIndex), ref TrackIndex);
ser.Sync(nameof(SeekingTrack), ref SeekingTrack);
//ser.Sync(nameof(CurrentSector), ref CurrentSector);
ser.Sync(nameof(SectorIndex), ref SectorIndex);
//ser.Sync(nameof(RAngles), ref RAngles);
//ser.Sync(nameof(DataPointer), ref DataPointer);
//ser.SyncEnum(nameof(CurrentState), ref CurrentState);
//ser.SyncEnum(nameof(SeekState), ref SeekState);
//ser.SyncEnum(nameof(SeekIntState), ref SeekIntState);
}
#endregion
}
#endregion
}
}

View File

@ -0,0 +1,68 @@
using System;
using BizHawk.Emulation.Cores.Computers.CPCSpectrumBase;
namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
{
public class NECUPD765CPC : NECUPD765<CPCBase, NECUPD765CPC.CPCDriveState>
{
protected override CPCDriveState ConstructDriveState(int driveID, NECUPD765<CPCBase, CPCDriveState> fdc) => new CPCDriveState(driveID, fdc);
protected override void TimingInit()
{
// z80 timing
double frameSize = _machine.GateArray.FrameLength;
double rRate = _machine.GateArray.Z80ClockSpeed / frameSize;
long tPerSecond = (long)(frameSize * rRate);
CPUCyclesPerMs = tPerSecond / 1000;
// drive timing
double dRate = DriveClock / frameSize;
long dPerSecond = (long)(frameSize * dRate);
DriveCyclesPerMs = dPerSecond / 1000;
long TStatesPerDriveCycle = (long)((double)_machine.GateArray.Z80ClockSpeed / DriveClock);
StatesPerDriveTick = TStatesPerDriveCycle;
}
public class CPCDriveState : NECUPD765DriveState
{
public CPCDriveState(int driveID, NECUPD765<CPCBase, CPCDriveState> fdc) : base(driveID, fdc) {}
public override void FDD_LoadDisk(byte[] diskData)
{
// try dsk first
FloppyDisk fdd = null;
bool found = false;
foreach (DiskType type in Enum.GetValues(typeof(DiskType)))
{
switch (type)
{
case DiskType.CPCExtended:
fdd = new CPCExtendedFloppyDisk();
found = fdd.ParseDisk(diskData);
break;
case DiskType.CPC:
fdd = new CPCFloppyDisk();
found = fdd.ParseDisk(diskData);
break;
}
if (found)
{
Disk = fdd;
break;
}
}
if (!found)
{
throw new Exception(this.GetType().ToString() +
"\n\nDisk image file could not be parsed. Potentially an unknown format.");
}
}
}
}
}

View File

@ -7,6 +7,8 @@ using System.Collections;
using System.Collections.Generic;
using System.Linq;
using BizHawk.Emulation.Cores.Computers.CPCSpectrumBase;
namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
{
/// <summary>
@ -23,7 +25,7 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
private CRCT_6845 CRCT => _machine.CRCT;
//private CRTDevice CRT => _machine.CRT;
private IPSG PSG => _machine.AYDevice;
private NECUPD765 FDC => _machine.UPDDiskDevice;
private NECUPD765CPC FDC => _machine.UPDDiskDevice;
private DatacorderDevice DATACORDER => _machine.TapeDevice;
private ushort BUSRQ => CPU.MEMRQ[CPU.bus_pntr];
public const ushort PCh = 1;

View File

@ -1,5 +1,6 @@
using BizHawk.Common;
using BizHawk.Common.NumberExtensions;
using BizHawk.Emulation.Cores.Computers.CPCSpectrumBase;
namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
{

View File

@ -1,6 +1,7 @@
using System.Collections;
using BizHawk.Common;
using BizHawk.Common.NumberExtensions;
using BizHawk.Emulation.Cores.Computers.CPCSpectrumBase;
namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
{

View File

@ -2,6 +2,8 @@
using BizHawk.Emulation.Common;
using System;
using BizHawk.Emulation.Cores.Computers.CPCSpectrumBase;
namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
{
/// <summary>

View File

@ -37,7 +37,7 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
TapeDevice = new DatacorderDevice(autoTape);
TapeDevice.Init(this);
UPDDiskDevice = new NECUPD765();
UPDDiskDevice = new NECUPD765CPC();
UPDDiskDevice.Init(this);
InitializeMedia(files);

View File

@ -3,6 +3,8 @@ using System.Collections.Generic;
using System.Linq;
using System.Text;
using BizHawk.Emulation.Cores.Computers.CPCSpectrumBase;
namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
{
/// <summary>

View File

@ -1,5 +1,6 @@
using BizHawk.Common;
using BizHawk.Emulation.Cores.Components.Z80A;
using BizHawk.Emulation.Cores.Computers.CPCSpectrumBase;
namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
{
@ -7,7 +8,7 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
/// The abstract class that all emulated models will inherit from
/// * Main properties / fields / contruction*
/// </summary>
public abstract partial class CPCBase
public abstract partial class CPCBase : CPCSpectrumBase.CPCSpectrumBase
{
#region Devices
@ -50,7 +51,7 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
/// <summary>
/// The Amstrad disk drive
/// </summary>
public virtual NECUPD765 UPDDiskDevice { get; set; }
public virtual NECUPD765CPC UPDDiskDevice { get; set; }
/// <summary>
/// The Cathode Ray Tube Controller chip

View File

@ -3,6 +3,8 @@ using BizHawk.Common;
using System;
using System.Collections.Generic;
using BizHawk.Emulation.Cores.Computers.CPCSpectrumBase;
namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
{
/// <summary>

View File

@ -3,6 +3,8 @@ using BizHawk.Common;
using System.Collections.Generic;
using System;
using BizHawk.Emulation.Cores.Computers.CPCSpectrumBase;
namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
{
/// <summary>

View File

@ -1,19 +0,0 @@

namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
{
/// <summary>
/// The different disk formats ZXHawk currently supports
/// </summary>
public enum DiskType
{
/// <summary>
/// Standard CPCEMU disk format (used in the built-in +3 disk drive)
/// </summary>
CPC,
/// <summary>
/// Extended CPCEMU disk format (used in the built-in +3 disk drive)
/// </summary>
CPCExtended
}
}

View File

@ -3,6 +3,8 @@ using System.Collections.Generic;
using System.Linq;
using System.Text;
using BizHawk.Emulation.Cores.Computers.CPCSpectrumBase;
namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
{
/// <summary>

View File

@ -0,0 +1,9 @@
using BizHawk.Emulation.Cores.Components.Z80A;
namespace BizHawk.Emulation.Cores.Computers.CPCSpectrumBase
{
public interface CPCSpectrumBase
{
Z80A CPU { get; set; }
}
}

View File

@ -1,6 +1,6 @@
using BizHawk.Common;
namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
namespace BizHawk.Emulation.Cores.Computers.CPCSpectrumBase
{
/// <summary>
/// Represents a beeper/buzzer device

View File

@ -1,5 +1,5 @@

namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
namespace BizHawk.Emulation.Cores.Computers.CPCSpectrumBase
{
/// <summary>
/// Defines an object that can load a floppy disk image

View File

@ -1,15 +1,17 @@

namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
using System;
namespace BizHawk.Emulation.Cores.Computers.CPCSpectrumBase
{
/// <summary>
/// Represents a spectrum joystick
/// </summary>
public interface IJoystick
public interface IJoystick<T> where T : Enum
{
/// <summary>
/// The type of joystick
/// </summary>
JoystickType JoyType { get; }
T JoyType { get; }
/// <summary>
/// Array of all the possibly button press names

View File

@ -1,5 +1,5 @@

namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
namespace BizHawk.Emulation.Cores.Computers.CPCSpectrumBase
{
/// <summary>
/// Represents a device that utilizes port IN & OUT

View File

@ -0,0 +1,179 @@
namespace BizHawk.Emulation.Cores.Computers.CPCSpectrumBase
{
/// <summary>
/// Used for the sector CHRN structure
/// </summary>
public class CHRN
{
/// <summary>
/// Track
/// </summary>
public byte C { get; set; }
/// <summary>
/// Side
/// </summary>
public byte H { get; set; }
/// <summary>
/// Sector ID
/// </summary>
public byte R { get; set; }
/// <summary>
/// Sector Size
/// </summary>
public byte N { get; set; }
/// <summary>
/// Status register 1
/// </summary>
private byte _flag1;
public byte Flag1
{
get => _flag1;
set => _flag1 = value;
}
/// <summary>
/// Status register 2
/// </summary>
private byte _flag2;
public byte Flag2
{
get => _flag2;
set => _flag2 = value;
}
/// <summary>
/// Used to store the last transmitted/received data bytes
/// </summary>
public byte[] DataBytes { get; set; }
/// <summary>
/// ID for the read/write data command
/// </summary>
public int DataID { get; set; }
#region Helper Methods
/// <summary>
/// Missing Address Mark (Sector_ID or DAM not found)
/// </summary>
public bool ST1MA
{
get => NECUPD765<CPCSpectrumBase, NECUPD765DriveState>.GetBit(0, _flag1);
set
{
if (value) { NECUPD765<CPCSpectrumBase, NECUPD765DriveState>.SetBit(0, ref _flag1); }
else { NECUPD765<CPCSpectrumBase, NECUPD765DriveState>.UnSetBit(0, ref _flag1); }
}
}
/// <summary>
/// No Data (Sector_ID not found, CRC fail in ID_field)
/// </summary>
public bool ST1ND
{
get => NECUPD765<CPCSpectrumBase, NECUPD765DriveState>.GetBit(2, _flag1);
set
{
if (value) { NECUPD765<CPCSpectrumBase, NECUPD765DriveState>.SetBit(2, ref _flag1); }
else { NECUPD765<CPCSpectrumBase, NECUPD765DriveState>.UnSetBit(2, ref _flag1); }
}
}
/// <summary>
/// Data Error (CRC-fail in ID- or Data-Field)
/// </summary>
public bool ST1DE
{
get => NECUPD765<CPCSpectrumBase, NECUPD765DriveState>.GetBit(5, _flag1);
set
{
if (value) { NECUPD765<CPCSpectrumBase, NECUPD765DriveState>.SetBit(5, ref _flag1); }
else { NECUPD765<CPCSpectrumBase, NECUPD765DriveState>.UnSetBit(5, ref _flag1); }
}
}
/// <summary>
/// End of Track (set past most read/write commands) (see IC)
/// </summary>
public bool ST1EN
{
get => NECUPD765<CPCSpectrumBase, NECUPD765DriveState>.GetBit(7, _flag1);
set
{
if (value) { NECUPD765<CPCSpectrumBase, NECUPD765DriveState>.SetBit(7, ref _flag1); }
else { NECUPD765<CPCSpectrumBase, NECUPD765DriveState>.UnSetBit(7, ref _flag1); }
}
}
/// <summary>
/// Missing Address Mark in Data Field (DAM not found)
/// </summary>
public bool ST2MD
{
get => NECUPD765<CPCSpectrumBase, NECUPD765DriveState>.GetBit(0, _flag2);
set
{
if (value) { NECUPD765<CPCSpectrumBase, NECUPD765DriveState>.SetBit(0, ref _flag2); }
else { NECUPD765<CPCSpectrumBase, NECUPD765DriveState>.UnSetBit(0, ref _flag2); }
}
}
/// <summary>
/// Bad Cylinder (read/programmed track-ID different and read-ID = FF)
/// </summary>
public bool ST2BC
{
get => NECUPD765<CPCSpectrumBase, NECUPD765DriveState>.GetBit(1, _flag2);
set
{
if (value) { NECUPD765<CPCSpectrumBase, NECUPD765DriveState>.SetBit(1, ref _flag2); }
else { NECUPD765<CPCSpectrumBase, NECUPD765DriveState>.UnSetBit(1, ref _flag2); }
}
}
/// <summary>
/// Wrong Cylinder (read/programmed track-ID different) (see b1)
/// </summary>
public bool ST2WC
{
get => NECUPD765<CPCSpectrumBase, NECUPD765DriveState>.GetBit(4, _flag2);
set
{
if (value) { NECUPD765<CPCSpectrumBase, NECUPD765DriveState>.SetBit(4, ref _flag2); }
else { NECUPD765<CPCSpectrumBase, NECUPD765DriveState>.UnSetBit(4, ref _flag2); }
}
}
/// <summary>
/// Data Error in Data Field (CRC-fail in data-field)
/// </summary>
public bool ST2DD
{
get => NECUPD765<CPCSpectrumBase, NECUPD765DriveState>.GetBit(5, _flag2);
set
{
if (value) { NECUPD765<CPCSpectrumBase, NECUPD765DriveState>.SetBit(5, ref _flag2); }
else { NECUPD765<CPCSpectrumBase, NECUPD765DriveState>.UnSetBit(5, ref _flag2); }
}
}
/// <summary>
/// Control Mark (read/scan command found sector with deleted DAM)
/// </summary>
public bool ST2CM
{
get => NECUPD765<CPCSpectrumBase, NECUPD765DriveState>.GetBit(6, _flag2);
set
{
if (value) { NECUPD765<CPCSpectrumBase, NECUPD765DriveState>.SetBit(6, ref _flag2); }
else { NECUPD765<CPCSpectrumBase, NECUPD765DriveState>.UnSetBit(6, ref _flag2); }
}
}
#endregion
}
}

View File

@ -0,0 +1,7 @@
namespace BizHawk.Emulation.Cores.Computers.CPCSpectrumBase
{
public interface INECUPD765
{
bool FDD_FLAG_MOTOR { get; }
}
}

View File

@ -1,20 +1,20 @@
using BizHawk.Common;
using System;
namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
namespace BizHawk.Emulation.Cores.Computers.CPCSpectrumBase
{
/// <summary>
/// Definitions
/// </summary>
#region Attribution
/*
Implementation based on the information contained here:
http://www.cpcwiki.eu/index.php/765_FDC
and here:
http://www.cpcwiki.eu/imgs/f/f3/UPD765_Datasheet_OCRed.pdf
*/
Implementation based on the information contained here:
http://www.cpcwiki.eu/index.php/765_FDC
and here:
http://www.cpcwiki.eu/imgs/f/f3/UPD765_Datasheet_OCRed.pdf
*/
#endregion
public partial class NECUPD765
public abstract partial class NECUPD765<TMachine, TDriveState>
{
#region Enums
@ -692,10 +692,10 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
/// </summary>
private class Command
{
// /// <summary>
// /// Mask to remove potential parameter bits (5,6, and or 7) in order to identify the command
// /// </summary>
// public int BitMask { get; set; }
// /// <summary>
// /// Mask to remove potential parameter bits (5,6, and or 7) in order to identify the command
// /// </summary>
// public int BitMask { get; set; }
/// <summary>
/// The command code after bitmask has been applied
/// </summary>

View File

@ -3,20 +3,20 @@ using System.Collections;
using System.Collections.Generic;
using System.Linq;
namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
namespace BizHawk.Emulation.Cores.Computers.CPCSpectrumBase
{
/// <summary>
/// FDC State and Methods
/// </summary>
#region Attribution
/*
Implementation based on the information contained here:
http://www.cpcwiki.eu/index.php/765_FDC
and here:
http://www.cpcwiki.eu/imgs/f/f3/UPD765_Datasheet_OCRed.pdf
*/
Implementation based on the information contained here:
http://www.cpcwiki.eu/index.php/765_FDC
and here:
http://www.cpcwiki.eu/imgs/f/f3/UPD765_Datasheet_OCRed.pdf
*/
#endregion
public partial class NECUPD765
public abstract partial class NECUPD765<TMachine, TDriveState>
{
#region Controller State
@ -210,69 +210,69 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
/// Main status register (accessed via reads to port 0x2ffd)
/// </summary>
/*
b0..3 DB FDD0..3 Busy (seek/recalib active, until succesful sense intstat)
b4 CB FDC Busy (still in command-, execution- or result-phase)
b5 EXM Execution Mode (still in execution-phase, non_DMA_only)
b6 DIO Data Input/Output (0=CPU->FDC, 1=FDC->CPU) (see b7)
b7 RQM Request For Master (1=ready for next byte) (see b6 for direction)
*/
b0..3 DB FDD0..3 Busy (seek/recalib active, until succesful sense intstat)
b4 CB FDC Busy (still in command-, execution- or result-phase)
b5 EXM Execution Mode (still in execution-phase, non_DMA_only)
b6 DIO Data Input/Output (0=CPU->FDC, 1=FDC->CPU) (see b7)
b7 RQM Request For Master (1=ready for next byte) (see b6 for direction)
*/
private byte StatusMain;
/// <summary>
/// Status Register 0
/// </summary>
/*
b0,1 US Unit Select (driveno during interrupt)
b2 HD Head Address (head during interrupt)
b3 NR Not Ready (drive not ready or non-existing 2nd head selected)
b4 EC Equipment Check (drive failure or recalibrate failed (retry))
b5 SE Seek End (Set if seek-command completed)
b6,7 IC Interrupt Code (0=OK, 1=aborted:readfail/OK if EN, 2=unknown cmd
or senseint with no int occured, 3=aborted:disc removed etc.)
*/
b0,1 US Unit Select (driveno during interrupt)
b2 HD Head Address (head during interrupt)
b3 NR Not Ready (drive not ready or non-existing 2nd head selected)
b4 EC Equipment Check (drive failure or recalibrate failed (retry))
b5 SE Seek End (Set if seek-command completed)
b6,7 IC Interrupt Code (0=OK, 1=aborted:readfail/OK if EN, 2=unknown cmd
or senseint with no int occured, 3=aborted:disc removed etc.)
*/
private byte Status0;
/// <summary>
/// Status Register 1
/// </summary>
/*
b0 MA Missing Address Mark (Sector_ID or DAM not found)
b1 NW Not Writeable (tried to write/format disc with wprot_tab=on)
b2 ND No Data (Sector_ID not found, CRC fail in ID_field)
b3,6 0 Not used
b4 OR Over Run (CPU too slow in execution-phase (ca. 26us/Byte))
b5 DE Data Error (CRC-fail in ID- or Data-Field)
b7 EN End of Track (set past most read/write commands) (see IC)
*/
b0 MA Missing Address Mark (Sector_ID or DAM not found)
b1 NW Not Writeable (tried to write/format disc with wprot_tab=on)
b2 ND No Data (Sector_ID not found, CRC fail in ID_field)
b3,6 0 Not used
b4 OR Over Run (CPU too slow in execution-phase (ca. 26us/Byte))
b5 DE Data Error (CRC-fail in ID- or Data-Field)
b7 EN End of Track (set past most read/write commands) (see IC)
*/
private byte Status1;
/// <summary>
/// Status Register 2
/// </summary>
/*
b0 MD Missing Address Mark in Data Field (DAM not found)
b1 BC Bad Cylinder (read/programmed track-ID different and read-ID = FF)
b2 SN Scan Not Satisfied (no fitting sector found)
b3 SH Scan Equal Hit (equal)
b4 WC Wrong Cylinder (read/programmed track-ID different) (see b1)
b5 DD Data Error in Data Field (CRC-fail in data-field)
b6 CM Control Mark (read/scan command found sector with deleted DAM)
b7 0 Not Used
*/
b0 MD Missing Address Mark in Data Field (DAM not found)
b1 BC Bad Cylinder (read/programmed track-ID different and read-ID = FF)
b2 SN Scan Not Satisfied (no fitting sector found)
b3 SH Scan Equal Hit (equal)
b4 WC Wrong Cylinder (read/programmed track-ID different) (see b1)
b5 DD Data Error in Data Field (CRC-fail in data-field)
b6 CM Control Mark (read/scan command found sector with deleted DAM)
b7 0 Not Used
*/
private byte Status2;
/// <summary>
/// Status Register 3
/// </summary>
/*
b0,1 US Unit Select (pin 28,29 of FDC)
b2 HD Head Address (pin 27 of FDC)
b3 TS Two Side (0=yes, 1=no (!))
b4 T0 Track 0 (on track 0 we are)
b5 RY Ready (drive ready signal)
b6 WP Write Protected (write protected)
b7 FT Fault (if supported: 1=Drive failure)
*/
b0,1 US Unit Select (pin 28,29 of FDC)
b2 HD Head Address (pin 27 of FDC)
b3 TS Two Side (0=yes, 1=no (!))
b4 T0 Track 0 (on track 0 we are)
b5 RY Ready (drive ready signal)
b6 WP Write Protected (write protected)
b7 FT Fault (if supported: 1=Drive failure)
*/
private byte Status3;
#endregion
@ -385,8 +385,6 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
sectorSize = 0x80 << ActiveCommandParams.SectorSize;
}
var mtc = maxTransferCap;
// get the current track
var track = ActiveDrive.Disk.DiskTracks.FirstOrDefault(a => a.TrackNumber == ActiveDrive.CurrentTrackID);
@ -672,22 +670,13 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
switch (ActiveCommandParams.SectorSize)
{
case 1:
if (CMD_FLAG_MF)
maxTransferCap = 6656;
else
maxTransferCap = 3840;
maxTransferCap = CMD_FLAG_MF ? 6656 : 3840;
break;
case 2:
if (CMD_FLAG_MF)
maxTransferCap = 7680;
else
maxTransferCap = 4096;
maxTransferCap = CMD_FLAG_MF ? 7680 : 4096;
break;
case 3:
if (CMD_FLAG_MF)
maxTransferCap = 8192;
else
maxTransferCap = 4096;
maxTransferCap = CMD_FLAG_MF ? 8192 : 4096;
break;
}
@ -2148,17 +2137,17 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
// second byte is the current track id
ResBuffer[1] = ActiveDrive.CurrentTrackID;
}
/*
else if (ActiveDrive.SeekStatus == SEEK_INTACKNOWLEDGED)
{
// DriveA interrupt has already been acknowledged
ActiveDrive.SeekStatus = SEEK_IDLE;
#if false
else if (ActiveDrive.SeekStatus == SEEK_INTACKNOWLEDGED)
{
// DriveA interrupt has already been acknowledged
ActiveDrive.SeekStatus = SEEK_IDLE;
ResLength = 1;
Status0 = 192;
ResBuffer[0] = Status0;
}
*/
ResLength = 1;
Status0 = 192;
ResBuffer[0] = Status0;
}
#endif
else if (ActiveDrive.SeekStatus == SEEK_IDLE)
{
// SIS with no interrupt
@ -2363,7 +2352,7 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
SetBit(MSR_EXM, ref StatusMain);
SetBit(MSR_CB, ref StatusMain);
// overrun detection
// overrun detection
OverrunCounter++;
if (OverrunCounter >= 64)
{
@ -2383,6 +2372,8 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
break;
}
// if (!CheckTiming()) UnSetBit(MSR_EXM, ref StatusMain);
return StatusMain;
}
@ -2474,7 +2465,7 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
//// we are in command phase
case Phase.Command:
// attempt to process this parameter byte
//ProcessCommand(data);
//ProcessCommand(data);
ActiveCommand.CommandDelegate();
break;
//// we are in execution phase
@ -2553,15 +2544,15 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
CMDIndex = CommandList.Count() - 1;
}
/*
if ((CMD_FLAG_MF && !ActiveCommand.MF) ||
(CMD_FLAG_MT && !ActiveCommand.MT) ||
(CMD_FLAG_SK && !ActiveCommand.SK))
{
// command byte included spurious bit 5,6 or 7 flags
CMDIndex = CommandList.Count() - 1;
}
*/
#if false
if ((CMD_FLAG_MF && !ActiveCommand.MF) ||
(CMD_FLAG_MT && !ActiveCommand.MT) ||
(CMD_FLAG_SK && !ActiveCommand.SK))
{
// command byte included spurious bit 5,6 or 7 flags
CMDIndex = CommandList.Count() - 1;
}
#endif
}
CommCounter = 0;
@ -2571,14 +2562,14 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
// move to command phase
ActivePhase = Phase.Command;
/*
// check for invalid SIS
if (ActiveInterrupt == InterruptState.None && CMDIndex == CC_SENSE_INTSTATUS)
{
CMDIndex = CC_INVALID;
//ActiveCommand.CommandDelegate(InstructionState.StartResult);
}
*/
#if false
// check for invalid SIS
if (ActiveInterrupt == InterruptState.None && CMDIndex == CC_SENSE_INTSTATUS)
{
CMDIndex = CC_INVALID;
//ActiveCommand.CommandDelegate(InstructionState.StartResult);
}
#endif
// set reslength
ResLength = ActiveCommand.ResultByteCount;

View File

@ -0,0 +1,189 @@
using BizHawk.Common;
using BizHawk.Common.NumberExtensions;
using System;
using System.Linq;
namespace BizHawk.Emulation.Cores.Computers.CPCSpectrumBase
{
/// <summary>
/// Floppy drive related stuff
/// </summary>
#region Attribution
/*
Implementation based on the information contained here:
http://www.cpcwiki.eu/index.php/765_FDC
and here:
http://www.cpcwiki.eu/imgs/f/f3/UPD765_Datasheet_OCRed.pdf
*/
#endregion
public abstract partial class NECUPD765<TMachine, TDriveState> : IFDDHost, INECUPD765
{
#region Drive State
/// <summary>
/// FDD Flag - motor on/off
/// </summary>
public bool FDD_FLAG_MOTOR;
bool INECUPD765.FDD_FLAG_MOTOR => FDD_FLAG_MOTOR;
/// <summary>
/// The index of the currently active disk drive
/// </summary>
public int DiskDriveIndex
{
get => _diskDriveIndex;
set
{
// when index is changed update the ActiveDrive
_diskDriveIndex = value;
ActiveDrive = DriveStates[_diskDriveIndex];
}
}
private int _diskDriveIndex = 0;
/// <summary>
/// The currently active drive
/// </summary>
private NECUPD765DriveState ActiveDrive;
/// <summary>
/// Array that holds state information for each possible drive
/// </summary>
private NECUPD765DriveState[] DriveStates = new NECUPD765DriveState[4];
#endregion
#region FDD Methods
protected abstract TDriveState ConstructDriveState(int driveID, NECUPD765<TMachine, TDriveState> fdc);
/// <summary>
/// Initialization / reset of the floppy drive subsystem
/// </summary>
private void FDD_Init()
{
for (int i = 0; i < 4; i++)
{
NECUPD765DriveState ds = ConstructDriveState(i, this);
DriveStates[i] = ds;
}
}
/// <summary>
/// Searches for the requested sector
/// </summary>
private FloppyDisk.Sector GetSector()
{
FloppyDisk.Sector sector = null;
// get the current track
var trk = ActiveDrive.Disk.DiskTracks[ActiveDrive.TrackIndex];
// get the current sector index
int index = ActiveDrive.SectorIndex;
// make sure this index exists
if (index > trk.Sectors.Length)
{
index = 0;
}
// index hole count
int iHole = 0;
// loop through the sectors in a track
// the loop ends with either the sector being found
// or the index hole being passed twice
while (iHole <= 2)
{
// does the requested sector match the current sector
if (trk.Sectors[index].SectorIDInfo.C == ActiveCommandParams.Cylinder &&
trk.Sectors[index].SectorIDInfo.H == ActiveCommandParams.Head &&
trk.Sectors[index].SectorIDInfo.R == ActiveCommandParams.Sector &&
trk.Sectors[index].SectorIDInfo.N == ActiveCommandParams.SectorSize)
{
// sector has been found
sector = trk.Sectors[index];
UnSetBit(SR2_BC, ref Status2);
UnSetBit(SR2_WC, ref Status2);
break;
}
// check for bad cylinder
if (trk.Sectors[index].SectorIDInfo.C == 255)
{
SetBit(SR2_BC, ref Status2);
}
// check for no cylinder
else if (trk.Sectors[index].SectorIDInfo.C != ActiveCommandParams.Cylinder)
{
SetBit(SR2_WC, ref Status2);
}
// incrememnt sector index
index++;
// have we reached the index hole?
if (trk.Sectors.Length <= index)
{
// wrap around
index = 0;
iHole++;
}
}
// search loop has completed and the sector may or may not have been found
// bad cylinder detected?
if (Status2.Bit(SR2_BC))
{
// remove WC
UnSetBit(SR2_WC, ref Status2);
}
// update sectorindex on drive
ActiveDrive.SectorIndex = index;
return sector;
}
#endregion
#region IFDDHost
// IFDDHost methods that fall through to the currently active drive
/// <summary>
/// Parses a new disk image and loads it into this floppy drive
/// </summary>
public void FDD_LoadDisk(byte[] diskData)
{
// we are only going to load into the first drive
DriveStates[0].FDD_LoadDisk(diskData);
}
/// <summary>
/// Ejects the current disk
/// </summary>
public void FDD_EjectDisk()
{
DriveStates[0].FDD_EjectDisk();
}
/// <summary>
/// Signs whether the current active drive has a disk inserted
/// </summary>
public bool FDD_IsDiskLoaded => DriveStates[DiskDriveIndex].FDD_IsDiskLoaded;
/// <summary>
/// Returns the disk object from drive 0
/// </summary>
public FloppyDisk DiskPointer => DriveStates[0].Disk;
public FloppyDisk Disk { get; set; }
#endregion
}
}

View File

@ -3,20 +3,20 @@ using System.Collections;
using System.Collections.Generic;
using System.Text;
namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
namespace BizHawk.Emulation.Cores.Computers.CPCSpectrumBase
{
/// <summary>
/// IPortIODevice
/// </summary>
#region Attribution
/*
Implementation based on the information contained here:
http://www.cpcwiki.eu/index.php/765_FDC
and here:
http://www.cpcwiki.eu/imgs/f/f3/UPD765_Datasheet_OCRed.pdf
*/
Implementation based on the information contained here:
http://www.cpcwiki.eu/index.php/765_FDC
and here:
http://www.cpcwiki.eu/imgs/f/f3/UPD765_Datasheet_OCRed.pdf
*/
#endregion
public partial class NECUPD765 : IPortIODevice
public abstract partial class NECUPD765<TMachine, TDriveState> : IPortIODevice
{
#region Dev Logging
@ -31,15 +31,15 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
/*
* Status read
* Data write
* Data read
* CMD code
* CMD string
* MT flag
* MK flag
* SK flag
* */
Status read
Data write
Data read
CMD code
CMD string
MT flag
MK flag
SK flag
*/
private string[] workingArr = new string[3];
private void BuildCSVLine()

View File

@ -1,19 +1,19 @@
using System.Collections;
namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
namespace BizHawk.Emulation.Cores.Computers.CPCSpectrumBase
{
/// <summary>
/// Static helper methods
/// </summary>
#region Attribution
/*
Implementation based on the information contained here:
http://www.cpcwiki.eu/index.php/765_FDC
and here:
http://www.cpcwiki.eu/imgs/f/f3/UPD765_Datasheet_OCRed.pdf
*/
Implementation based on the information contained here:
http://www.cpcwiki.eu/index.php/765_FDC
and here:
http://www.cpcwiki.eu/imgs/f/f3/UPD765_Datasheet_OCRed.pdf
*/
#endregion
public partial class NECUPD765
public abstract partial class NECUPD765<TMachine, TDriveState>
{
/// <summary>
/// Returns the specified bit value from supplied byte

View File

@ -1,18 +1,18 @@

namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
namespace BizHawk.Emulation.Cores.Computers.CPCSpectrumBase
{
/// <summary>
/// Timimng
/// </summary>
#region Attribution
/*
Implementation based on the information contained here:
http://www.cpcwiki.eu/index.php/765_FDC
and here:
http://www.cpcwiki.eu/imgs/f/f3/UPD765_Datasheet_OCRed.pdf
*/
Implementation based on the information contained here:
http://www.cpcwiki.eu/index.php/765_FDC
and here:
http://www.cpcwiki.eu/imgs/f/f3/UPD765_Datasheet_OCRed.pdf
*/
#endregion
public partial class NECUPD765
public abstract partial class NECUPD765<TMachine, TDriveState>
{
/// <summary>
/// The current Z80 cycle
@ -43,7 +43,7 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
/// <summary>
/// Defines the numbers of Z80 cycles per MS
/// </summary>
private long CPUCyclesPerMs;
protected long CPUCyclesPerMs;
/// <summary>
/// The floppy drive emulated clock speed
@ -73,23 +73,7 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
/// <summary>
/// Initializes the timing routines
/// </summary>
private void TimingInit()
{
// z80 timing
double frameSize = _machine.GateArray.FrameLength;
double rRate = _machine.GateArray.Z80ClockSpeed / frameSize;
long tPerSecond = (long)(frameSize * rRate);
CPUCyclesPerMs = tPerSecond / 1000;
// drive timing
double dRate = DriveClock / frameSize;
long dPerSecond = (long)(frameSize * dRate);
DriveCyclesPerMs = dPerSecond / 1000;
long TStatesPerDriveCycle = (long)((double)_machine.GateArray.Z80ClockSpeed / DriveClock);
StatesPerDriveTick = TStatesPerDriveCycle;
}
protected abstract void TimingInit();
/// <summary>
/// Called by reads to the main status register

View File

@ -1,27 +1,29 @@
using BizHawk.Common;
using System.Collections.Generic;
namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
namespace BizHawk.Emulation.Cores.Computers.CPCSpectrumBase
{
/// <summary>
/// The NEC floppy disk controller (and floppy drive) found in the +3
/// </summary>
#region Attribution
/*
Implementation based on the information contained here:
http://www.cpcwiki.eu/index.php/765_FDC
and here:
http://www.cpcwiki.eu/imgs/f/f3/UPD765_Datasheet_OCRed.pdf
*/
Implementation based on the information contained here:
http://www.cpcwiki.eu/index.php/765_FDC
and here:
http://www.cpcwiki.eu/imgs/f/f3/UPD765_Datasheet_OCRed.pdf
*/
#endregion
public partial class NECUPD765
public abstract partial class NECUPD765<TMachine, TDriveState>
where TMachine : CPCSpectrumBase
where TDriveState : NECUPD765DriveState
{
#region Devices
/// <summary>
/// The emulated spectrum machine
/// The emulated CPC or Spectrum machine
/// </summary>
private CPCBase _machine;
protected TMachine _machine;
#endregion
@ -38,7 +40,7 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
/// <summary>
/// Initialization routine
/// </summary>
public void Init(CPCBase machine)
public void Init(TMachine machine)
{
_machine = machine;
FDD_Init();
@ -94,56 +96,56 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
{
CommandList = new List<Command>
{
// read data
new Command { CommandDelegate = UPD_ReadData, CommandCode = 0x06, MT = true, MF = true, SK = true, IsRead = true,
// read data
new Command { CommandDelegate = UPD_ReadData, CommandCode = 0x06, MT = true, MF = true, SK = true, IsRead = true,
Direction = CommandDirection.OUT, ParameterByteCount = 8, ResultByteCount = 7 },
// read id
new Command { CommandDelegate = UPD_ReadID, CommandCode = 0x0a, MF = true, IsRead = true,
// read id
new Command { CommandDelegate = UPD_ReadID, CommandCode = 0x0a, MF = true, IsRead = true,
Direction = CommandDirection.OUT, ParameterByteCount = 1, ResultByteCount = 7 },
// specify
new Command { CommandDelegate = UPD_Specify, CommandCode = 0x03,
// specify
new Command { CommandDelegate = UPD_Specify, CommandCode = 0x03,
Direction = CommandDirection.OUT, ParameterByteCount = 2, ResultByteCount = 0 },
// read diagnostic
new Command { CommandDelegate = UPD_ReadDiagnostic, CommandCode = 0x02, MF = true, SK = true, IsRead = true,
// read diagnostic
new Command { CommandDelegate = UPD_ReadDiagnostic, CommandCode = 0x02, MF = true, SK = true, IsRead = true,
Direction = CommandDirection.OUT, ParameterByteCount = 8, ResultByteCount = 7 },
// scan equal
new Command { CommandDelegate = UPD_ScanEqual, CommandCode = 0x11, MT = true, MF = true, SK = true, IsRead = true,
// scan equal
new Command { CommandDelegate = UPD_ScanEqual, CommandCode = 0x11, MT = true, MF = true, SK = true, IsRead = true,
Direction = CommandDirection.IN, ParameterByteCount = 8, ResultByteCount = 7 },
// scan high or equal
new Command { CommandDelegate = UPD_ScanHighOrEqual, CommandCode = 0x1d, MT = true, MF = true, SK = true, IsRead = true,
// scan high or equal
new Command { CommandDelegate = UPD_ScanHighOrEqual, CommandCode = 0x1d, MT = true, MF = true, SK = true, IsRead = true,
Direction = CommandDirection.IN, ParameterByteCount = 8, ResultByteCount = 7 },
// scan low or equal
new Command { CommandDelegate = UPD_ScanLowOrEqual, CommandCode = 0x19, MT = true, MF = true, SK = true, IsRead = true,
// scan low or equal
new Command { CommandDelegate = UPD_ScanLowOrEqual, CommandCode = 0x19, MT = true, MF = true, SK = true, IsRead = true,
Direction = CommandDirection.IN, ParameterByteCount = 8, ResultByteCount = 7 },
// read deleted data
new Command { CommandDelegate = UPD_ReadDeletedData, CommandCode = 0x0c, MT = true, MF = true, SK = true, IsRead = true,
// read deleted data
new Command { CommandDelegate = UPD_ReadDeletedData, CommandCode = 0x0c, MT = true, MF = true, SK = true, IsRead = true,
Direction = CommandDirection.OUT, ParameterByteCount = 8, ResultByteCount = 7 },
// write data
new Command { CommandDelegate = UPD_WriteData, CommandCode = 0x05, MT = true, MF = true, IsWrite = true,
// write data
new Command { CommandDelegate = UPD_WriteData, CommandCode = 0x05, MT = true, MF = true, IsWrite = true,
Direction = CommandDirection.IN, ParameterByteCount = 8, ResultByteCount = 7 },
// write id
new Command { CommandDelegate = UPD_WriteID, CommandCode = 0x0d, MF = true, IsWrite = true,
// write id
new Command { CommandDelegate = UPD_WriteID, CommandCode = 0x0d, MF = true, IsWrite = true,
Direction = CommandDirection.IN, ParameterByteCount = 5, ResultByteCount = 7 },
// write deleted data
new Command { CommandDelegate = UPD_WriteDeletedData, CommandCode = 0x09, MT = true, MF = true, IsWrite = true,
// write deleted data
new Command { CommandDelegate = UPD_WriteDeletedData, CommandCode = 0x09, MT = true, MF = true, IsWrite = true,
Direction = CommandDirection.IN, ParameterByteCount = 8, ResultByteCount = 7 },
// seek
new Command { CommandDelegate = UPD_Seek, CommandCode = 0x0f,
// seek
new Command { CommandDelegate = UPD_Seek, CommandCode = 0x0f,
Direction = CommandDirection.OUT, ParameterByteCount = 2, ResultByteCount = 0 },
// recalibrate (seek track00)
new Command { CommandDelegate = UPD_Recalibrate, CommandCode = 0x07,
// recalibrate (seek track00)
new Command { CommandDelegate = UPD_Recalibrate, CommandCode = 0x07,
Direction = CommandDirection.OUT, ParameterByteCount = 1, ResultByteCount = 0 },
// sense interrupt status
new Command { CommandDelegate = UPD_SenseInterruptStatus, CommandCode = 0x08,
// sense interrupt status
new Command { CommandDelegate = UPD_SenseInterruptStatus, CommandCode = 0x08,
Direction = CommandDirection.OUT, ParameterByteCount = 0, ResultByteCount = 2 },
// sense drive status
new Command { CommandDelegate = UPD_SenseDriveStatus, CommandCode = 0x04,
// sense drive status
new Command { CommandDelegate = UPD_SenseDriveStatus, CommandCode = 0x04,
Direction = CommandDirection.OUT, ParameterByteCount = 1, ResultByteCount = 1 },
// version
new Command { CommandDelegate = UPD_Version, CommandCode = 0x10,
// version
new Command { CommandDelegate = UPD_Version, CommandCode = 0x10,
Direction = CommandDirection.OUT, ParameterByteCount = 0, ResultByteCount = 1 },
// invalid
new Command { CommandDelegate = UPD_Invalid, CommandCode = 0x00,
// invalid
new Command { CommandDelegate = UPD_Invalid, CommandCode = 0x00,
Direction = CommandDirection.OUT, ParameterByteCount = 0, ResultByteCount = 1 },
};
}

View File

@ -0,0 +1,651 @@
using System.Linq;
using BizHawk.Common;
namespace BizHawk.Emulation.Cores.Computers.CPCSpectrumBase
{
/// <summary>
/// Holds specfic state information about a drive
/// </summary>
public abstract class NECUPD765DriveState : IFDDHost
{
#region State
/// <summary>
/// The drive ID from an FDC perspective
/// </summary>
public int ID;
/// <summary>
/// Signs whether this drive ready
/// TRUE if both drive exists and has a disk inserted
/// </summary>
public bool FLAG_READY
{
get
{
if (!FDD_IsDiskLoaded || Disk.GetTrackCount() == 0 || !FDC.FDD_FLAG_MOTOR)
return false;
else
return true;
}
}
/// <summary>
/// Disk is write protected (TRUE BY DEFAULT)
/// </summary>
public bool FLAG_WRITEPROTECT = false;
/// <summary>
/// Storage for seek steps
/// One step for each indexpulse (track index) until seeked track
/// </summary>
public int SeekCounter;
/// <summary>
/// Seek status
/// </summary>
public int SeekStatus;
/// <summary>
/// Age counter
/// </summary>
public int SeekAge;
/// <summary>
/// The current side
/// </summary>
public byte CurrentSide;
/// <summary>
/// The current track index in the DiskTracks array
/// </summary>
public byte TrackIndex;
/// <summary>
/// The track ID of the current cylinder
/// </summary>
public byte CurrentTrackID
{
get
{
// default invalid track
int id = 0xff;
if (Disk == null)
return (byte)id;
if (Disk.DiskTracks.Count() == 0)
return (byte)id;
if (TrackIndex >= Disk.GetTrackCount())
TrackIndex = 0;
else if (TrackIndex < 0)
TrackIndex = 0;
var track = Disk.DiskTracks[TrackIndex];
id = track.TrackNumber;
return (byte)id;
}
set
{
for (int i = 0; i < Disk.GetTrackCount(); i++)
{
if (Disk.DiskTracks[i].TrackNumber == value)
{
TrackIndex = (byte)i;
break;
}
}
}
}
/// <summary>
/// The new track that the drive is seeking to
/// (used in seek operations)
/// </summary>
public int SeekingTrack;
/// <summary>
/// The current sector index in the Sectors array
/// </summary>
public int SectorIndex;
/// <summary>
/// The currently loaded floppy disk
/// </summary>
public FloppyDisk Disk { get; set; }
/// <summary>
/// The parent controller
/// </summary>
private INECUPD765 FDC;
#endregion
#region Lookups
/// <summary>
/// TRUE if we are on track 0
/// </summary>
public bool FLAG_TRACK0 => TrackIndex == 0;
#endregion
#region Public Methods
#if false
/// <summary>
/// Moves the head across the disk cylinders
/// </summary>
public void MoveHead(SkipDirection direction, int cylinderCount)
{
// get total tracks
int trackCount = Disk.DiskTracks.Count();
int trk = 0;
switch (direction)
{
case SkipDirection.Increment:
trk = (int)CurrentTrack + cylinderCount;
if (trk >= trackCount)
{
// past the last track
trk = trackCount - 1;
}
else if (trk < 0)
trk = 0;
break;
case SkipDirection.Decrement:
trk = (int)CurrentTrack - cylinderCount;
if (trk < 0)
{
// before the first track
trk = 0;
}
else if (trk >= trackCount)
trk = trackCount - 1;
break;
}
// move the head
CurrentTrack = (byte)trk;
}
#endif
#if false
/// <summary>
/// Finds a supplied sector
/// </summary>
public FloppyDisk.Sector FindSector(ref byte[] resBuffer, CommandParameters prms)
{
int index =CurrentSector;
int lc = 0;
FloppyDisk.Sector sector = null;
bool found = false;
do
{
sector = Disk.DiskTracks[CurrentTrack].Sectors[index];
if (sector != null && sector.SectorID == prms.Sector)
{
// sector found
// check for data errors
if ((sector.Status1 & 0x20) != 0 || (sector.Status2 & 0x20) != 0)
{
// data errors found
}
found = true;
break;
}
// sector doesnt match
var c = Disk.DiskTracks[CurrentTrack].Sectors[index].TrackNumber;
if (c == 255)
{
// bad cylinder
resBuffer[RS_ST2] |= 0x02;
}
else if (prms.Cylinder != c)
{
// cylinder mismatch
resBuffer[RS_ST2] |= 0x10;
}
// increment index
index++;
if (index >= Disk.DiskTracks[CurrentTrack].NumberOfSectors)
{
// out of bounds
index = 0;
lc++;
}
} while (lc < 2);
if ((resBuffer[RS_ST2] & 0x02) != 0)
{
// bad cylinder set - remove no cylinder
UnSetBit(SR2_WC, ref resBuffer[RS_ST2]);
}
// update current sector
CurrentSector = index;
if (found)
return sector;
else
return null;
}
/// <summary>
/// Populates a result buffer
/// </summary>
public void FillResult(ref byte[] resBuffer, CHRN chrn)
{
// clear results
resBuffer[RS_ST0] = 0;
resBuffer[RS_ST1] = 0;
resBuffer[RS_ST2] = 0;
resBuffer[RS_C] = 0;
resBuffer[RS_H] = 0;
resBuffer[RS_R] = 0;
resBuffer[RS_N] = 0;
if (chrn == null)
{
// no chrn supplied
resBuffer[RS_ST0] = ST0;
resBuffer[RS_ST1] = 0;
resBuffer[RS_ST2] = 0;
resBuffer[RS_C] = 0;
resBuffer[RS_H] = 0;
resBuffer[RS_R] = 0;
resBuffer[RS_N] = 0;
}
}
/// <summary>
/// Populates the result buffer with ReadID data
/// </summary>
public void ReadID(ref byte[] resBuffer)
{
if (CheckDriveStatus() == false)
{
// drive not ready
resBuffer[RS_ST0] = ST0;
return;
}
var track = Disk.DiskTracks.Where(a => a.TrackNumber == CurrentTrack).FirstOrDefault();
if (track != null && track.NumberOfSectors > 0)
{
// formatted track
// get the current sector
int index = CurrentSector;
// is the index out of bounds?
if (index >= track.NumberOfSectors)
{
// reset the index
index = 0;
}
// read the sector data
var data = track.Sectors[index];
resBuffer[RS_C] = data.TrackNumber;
resBuffer[RS_H] = data.SideNumber;
resBuffer[RS_R] = data.SectorID;
resBuffer[RS_N] = data.SectorSize;
resBuffer[RS_ST0] = ST0;
// increment the current sector
CurrentSector = index + 1;
return;
}
else
{
// unformatted track?
resBuffer[RS_C] = FDC.CommBuffer[CM_C];
resBuffer[RS_H] = FDC.CommBuffer[CM_H];
resBuffer[RS_R] = FDC.CommBuffer[CM_R];
resBuffer[RS_N] = FDC.CommBuffer[CM_N];
SetBit(SR0_IC0, ref ST0);
resBuffer[RS_ST0] = ST0;
resBuffer[RS_ST1] = 0x01;
return;
}
}
#endif
#if false
/// <summary>
/// The drive performs a seek operation if necessary
/// Return value TRUE indicates seek complete
/// </summary>
public void DoSeek()
{
if (CurrentState != DriveMainState.Recalibrate &&
CurrentState != DriveMainState.Seek)
{
// no seek/recalibrate has been asked for
return;
}
if (GetBit(ID, FDC.StatusMain))
{
// drive is already seeking
return;
}
RunSeekCycle();
}
/// <summary>
/// Runs a seek cycle
/// </summary>
public void RunSeekCycle()
{
for (;;)
{
switch (SeekState)
{
// seek or recalibrate has been requested
case SeekSubState.Idle:
if (CurrentState == DriveMainState.Recalibrate)
{
// recalibrate always seeks to track 0
SeekingTrack = 0;
}
SeekState = SeekSubState.MoveInit;
// mark drive as busy
// this should be cleared by SIS command
SetBit(ID, ref FDC.StatusMain);
break;
// setup for the head move
case SeekSubState.MoveInit:
if (CurrentTrack == SeekingTrack)
{
// we are already at the required track
if (CurrentState == DriveMainState.Recalibrate &&
!FLAG_TRACK0)
{
// recalibration fail
SeekIntState = SeekIntStatus.Abnormal;
// raise seek interrupt
FDC.ActiveInterrupt = InterruptState.Seek;
// unset DB bit
UnSetBit(ID, ref FDC.StatusMain);
// equipment check
SetBit(SR0_EC, ref FDC.Status0);
SeekState = SeekSubState.PerformCompletion;
break;
}
if (CurrentState == DriveMainState.Recalibrate &&
FLAG_TRACK0)
{
// recalibration success
SeekIntState = SeekIntStatus.Normal;
// raise seek interrupt
FDC.ActiveInterrupt = InterruptState.Seek;
// unset DB bit
UnSetBit(ID, ref FDC.StatusMain);
SeekState = SeekSubState.PerformCompletion;
break;
}
}
// check for error
if (IntStatus >= IC_ABORTED_DISCREMOVED || Disk == null)
{
// drive not ready
FLAG_READY = false;
// drive not ready
SeekIntState = SeekIntStatus.DriveNotReady;
// cancel any interrupt
FDC.ActiveInterrupt = InterruptState.None;
// unset DB bit
UnSetBit(ID, ref FDC.StatusMain);
SeekState = SeekSubState.PerformCompletion;
break;
}
if (SeekCounter > 1)
{
// not ready to seek yet
SeekCounter--;
return;
}
if (FDC.SRT < 1 && CurrentTrack != SeekingTrack)
{
SeekState = SeekSubState.MoveImmediate;
break;
}
// head move
SeekState = SeekSubState.HeadMove;
break;
case SeekSubState.HeadMove:
// do the seek
SeekCounter = FDC.SRT;
if (CurrentTrack < SeekingTrack)
{
// we are seeking forward
var delta = SeekingTrack - CurrentTrack;
MoveHead(SkipDirection.Increment, 1);
}
else if (CurrentTrack > SeekingTrack)
{
// we are seeking backward
var delta = CurrentTrack - SeekingTrack;
MoveHead(SkipDirection.Decrement, 1);
}
// should the seek be completed now?
if (CurrentTrack == SeekingTrack)
{
SeekState = SeekSubState.PerformCompletion;
break;
}
// seek not finished yet
return;
// seek emulation processed immediately
case SeekSubState.MoveImmediate:
if (CurrentTrack < SeekingTrack)
{
// we are seeking forward
var delta = SeekingTrack - CurrentTrack;
MoveHead(SkipDirection.Increment, delta);
}
else if (CurrentTrack > SeekingTrack)
{
// we are seeking backward
var delta = CurrentTrack - SeekingTrack;
MoveHead(SkipDirection.Decrement, delta);
}
SeekState = SeekSubState.PerformCompletion;
break;
case SeekSubState.PerformCompletion:
SeekDone();
SeekState = SeekSubState.SeekCompleted;
break;
case SeekSubState.SeekCompleted:
// seek has already completed
return;
}
}
}
/// <summary>
/// Called when a seek operation has completed
/// </summary>
public void SeekDone()
{
SeekCounter = 0;
SeekingTrack = CurrentTrack;
// generate ST0 register data
// get only the IC bits
IntStatus &= IC_ABORTED_DISCREMOVED;
// drive ready?
if (!FLAG_READY)
{
SetBit(SR0_NR, ref IntStatus);
SetBit(SR0_EC, ref IntStatus);
// are we recalibrating?
if (CurrentState == DriveMainState.Recalibrate)
{
SetBit(SR0_EC, ref IntStatus);
}
}
// set seek end
SetBit(SR0_SE, ref IntStatus);
/*
// head address
if (CurrentSide > 0)
{
SetBit(SR0_HD, ref IntStatus);
// drive only supports 1 head
// set the EC bit
SetBit(SR0_EC, ref IntStatus);
}
*/
// UnitSelect
SetUnitSelect(ID, ref IntStatus);
// move to none state
//CurrentState = DriveMainState.None;
//SeekState = SeekSubState.SeekCompleted;
// set the seek interrupt flag for this drive
// this will be cleared at the next successful senseint
FLAG_SEEK_INTERRUPT = true;
//CurrentState = DriveMainState.None;
}
#endif
#endregion
#region Construction
public NECUPD765DriveState(int driveID, INECUPD765 fdc)
{
ID = driveID;
FDC = fdc;
}
#endregion
#region IFDDHost
/// <summary>
/// Parses a new disk image and loads it into this floppy drive
/// </summary>
public abstract void FDD_LoadDisk(byte[] diskData);
/// <summary>
/// Ejects the current disk
/// </summary>
public void FDD_EjectDisk()
{
Disk = null;
//FLAG_READY = false;
}
/// <summary>
/// Signs whether the current active drive has a disk inserted
/// </summary>
public bool FDD_IsDiskLoaded
{
get
{
if (Disk != null)
return true;
else
return false;
}
}
#endregion
#region StateSerialization
public void SyncState(Serializer ser)
{
ser.Sync(nameof(ID), ref ID);
ser.Sync(nameof(FLAG_WRITEPROTECT), ref FLAG_WRITEPROTECT);
//ser.Sync(nameof(FLAG_DISKCHANGED), ref FLAG_DISKCHANGED);
//ser.Sync(nameof(FLAG_RECALIBRATING), ref FLAG_RECALIBRATING);
//ser.Sync(nameof(FLAG_SEEK_INTERRUPT), ref FLAG_SEEK_INTERRUPT);
//ser.Sync(nameof(IntStatus), ref IntStatus);
//ser.Sync(nameof(ST0), ref ST0);
//ser.Sync(nameof(RecalibrationCounter), ref RecalibrationCounter);
ser.Sync(nameof(SeekCounter), ref SeekCounter);
ser.Sync(nameof(SeekStatus), ref SeekStatus);
ser.Sync(nameof(SeekAge), ref SeekAge);
ser.Sync(nameof(CurrentSide), ref CurrentSide);
//ser.Sync(nameof(CurrentTrack), ref CurrentTrack);
ser.Sync(nameof(TrackIndex), ref TrackIndex);
ser.Sync(nameof(SeekingTrack), ref SeekingTrack);
//ser.Sync(nameof(CurrentSector), ref CurrentSector);
ser.Sync(nameof(SectorIndex), ref SectorIndex);
//ser.Sync(nameof(RAngles), ref RAngles);
//ser.Sync(nameof(DataPointer), ref DataPointer);
//ser.SyncEnum(nameof(CurrentState), ref CurrentState);
//ser.SyncEnum(nameof(SeekState), ref SeekState);
//ser.SyncEnum(nameof(SeekIntState), ref SeekIntState);
}
#endregion
}
}

View File

@ -1,5 +1,5 @@

namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
namespace BizHawk.Emulation.Cores.Computers.CPCSpectrumBase
{
/// <summary>
/// The different disk formats ZXHawk currently supports

View File

@ -4,7 +4,7 @@ using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
namespace BizHawk.Emulation.Cores.Computers.CPCSpectrumBase
{
/// <summary>
/// This abstract class defines a logical floppy disk
@ -307,7 +307,7 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
// TrackID is consistent between the sectors although is usually high (233, 237 etc)
// SideID is fairly random looking but with all IDs being even
// SectorID is also fairly random looking but contains both odd and even numbers
//
//
// There doesnt appear to be any CRC errors in this track, but the sector size is always 1 (256 bytes)
// Each sector contains different filler byte
// Once track 0 is loaded the CPU completely reads all the sectors in this track one-by-one.
@ -402,104 +402,104 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
return false;
}
/*
/// <summary>
/// Should be run at the end of the ParseDisk process
/// If speedlock is detected the flag is set in the disk image
/// </summary>
protected virtual void SpeedlockDetection()
{
#if false
/// <summary>
/// Should be run at the end of the ParseDisk process
/// If speedlock is detected the flag is set in the disk image
/// </summary>
protected virtual void SpeedlockDetection()
{
if (DiskTracks.Length == 0)
return;
if (DiskTracks.Length == 0)
return;
// check for speedlock copyright notice
string ident = Encoding.ASCII.GetString(DiskData, 0x100, 0x1400);
if (!ident.ToUpper().Contains("SPEEDLOCK"))
{
// speedlock not found
return;
}
// check for speedlock copyright notice
string ident = Encoding.ASCII.GetString(DiskData, 0x100, 0x1400);
if (!ident.ToUpper().Contains("SPEEDLOCK"))
{
// speedlock not found
return;
}
// get cylinder 0
var cyl = DiskTracks[0];
// get cylinder 0
var cyl = DiskTracks[0];
// get sector with ID=2
var sec = cyl.Sectors.Where(a => a.SectorID == 2).FirstOrDefault();
// get sector with ID=2
var sec = cyl.Sectors.Where(a => a.SectorID == 2).FirstOrDefault();
if (sec == null)
return;
if (sec == null)
return;
// check for already multiple weak copies
if (sec.ContainsMultipleWeakSectors || sec.SectorData.Length != 0x80 << sec.SectorSize)
return;
// check for already multiple weak copies
if (sec.ContainsMultipleWeakSectors || sec.SectorData.Length != 0x80 << sec.SectorSize)
return;
// check for invalid crcs in sector 2
if (sec.Status1.Bit(5) || sec.Status2.Bit(5))
{
Protection = ProtectionType.Speedlock;
}
else
{
return;
}
// check for invalid crcs in sector 2
if (sec.Status1.Bit(5) || sec.Status2.Bit(5))
{
Protection = ProtectionType.Speedlock;
}
else
{
return;
}
// we are going to create a total of 5 weak sector copies
// keeping the original copy
byte[] origData = sec.SectorData.ToArray();
List<byte> data = new List<byte>();
//Random rnd = new Random();
// we are going to create a total of 5 weak sector copies
// keeping the original copy
byte[] origData = sec.SectorData.ToArray();
List<byte> data = new List<byte>();
//Random rnd = new Random();
for (int i = 0; i < 6; i++)
{
for (int s = 0; s < origData.Length; s++)
{
if (i == 0)
{
data.Add(origData[s]);
continue;
}
for (int i = 0; i < 6; i++)
{
for (int s = 0; s < origData.Length; s++)
{
if (i == 0)
{
data.Add(origData[s]);
continue;
}
// deterministic 'random' implementation
int n = origData[s] + i + 1;
if (n > 0xff)
n = n - 0xff;
else if (n < 0)
n = 0xff + n;
// deterministic 'random' implementation
int n = origData[s] + i + 1;
if (n > 0xff)
n = n - 0xff;
else if (n < 0)
n = 0xff + n;
byte nByte = (byte)n;
byte nByte = (byte)n;
if (s < 336)
{
// non weak data
data.Add(origData[s]);
}
else if (s < 511)
{
// weak data
data.Add(nByte);
}
else if (s == 511)
{
// final sector byte
data.Add(nByte);
}
else
{
// speedlock sector should not be more than 512 bytes
// but in case it is just do non weak
data.Add(origData[i]);
}
}
}
if (s < 336)
{
// non weak data
data.Add(origData[s]);
}
else if (s < 511)
{
// weak data
data.Add(nByte);
}
else if (s == 511)
{
// final sector byte
data.Add(nByte);
}
else
{
// speedlock sector should not be more than 512 bytes
// but in case it is just do non weak
data.Add(origData[i]);
}
}
}
// commit the sector data
sec.SectorData = data.ToArray();
sec.ContainsMultipleWeakSectors = true;
sec.ActualDataByteLength = data.Count();
// commit the sector data
sec.SectorData = data.ToArray();
sec.ContainsMultipleWeakSectors = true;
sec.ActualDataByteLength = data.Count();
}
*/
}
#endif
/// <summary>
/// Returns the track count for the disk
@ -587,8 +587,18 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
public byte NumberOfSectors { get; set; }
public byte GAP3Length { get; set; }
public byte FillerByte { get; set; }
public Sector[] Sectors { get; set; }
public virtual Sector[] Sectors { get; set; }
#region UDI
public byte TrackType { get; set; }
public int TLEN { get; set; }
public int CLEN => TLEN / 8 + (TLEN % 8 / 7) / 8;
public byte[] TrackData { get; set; }
#endregion
#if false
/// <summary>
/// Presents a contiguous byte array of all sector data for this track
/// (including any multiple weak/random data)
@ -607,6 +617,7 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
return list.ToArray();
}
}
#endif
}
public class Sector
@ -637,10 +648,8 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
{
return ActualDataByteLength;
}
else
{
return ActualDataByteLength / (ActualDataByteLength / (0x80 << SectorSize));
}
return ActualDataByteLength / (ActualDataByteLength / (0x80 << SectorSize));
}
}
@ -667,10 +676,8 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
return l.ToArray();
}
else
{
return SectorData;
}
return SectorData;
}
else
{
@ -688,14 +695,14 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
return res;
/*
int copies = ActualDataByteLength / (0x80 << SectorSize);
Random rnd = new Random();
int r = rnd.Next(0, copies - 1);
int step = r * (0x80 << SectorSize);
byte[] res = new byte[(0x80 << SectorSize)];
Array.Copy(SectorData, step, res, 0, 0x80 << SectorSize);
return res;
*/
int copies = ActualDataByteLength / (0x80 << SectorSize);
Random rnd = new Random();
int r = rnd.Next(0, copies - 1);
int step = r * (0x80 << SectorSize);
byte[] res = new byte[(0x80 << SectorSize)];
Array.Copy(SectorData, step, res, 0, 0x80 << SectorSize);
return res;
*/
}
}
}

View File

@ -1,5 +1,5 @@

namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
namespace BizHawk.Emulation.Cores.Computers.CPCSpectrumBase
{
/// <summary>
/// Represents the possible commands that can be raised from each tape block

View File

@ -2,7 +2,7 @@
using System.Collections.Generic;
using System.Linq;
namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
namespace BizHawk.Emulation.Cores.Computers.CPCSpectrumBase
{
/// <summary>
/// Represents a tape block
@ -52,31 +52,31 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
set => _blockData = value;
}
/*
/// <summary>
/// An array of bytearray encoded strings (stored in this format for easy Bizhawk serialization)
/// Its basically tape information
/// </summary>
private byte[][] _tapeDescriptionData;
#if false
/// <summary>
/// An array of bytearray encoded strings (stored in this format for easy Bizhawk serialization)
/// Its basically tape information
/// </summary>
private byte[][] _tapeDescriptionData;
/// <summary>
/// Returns the Tape Description Data in a human readable format
/// </summary>
public List<string> TapeDescriptionData
{
get
{
List<string> data = new List<string>();
/// <summary>
/// Returns the Tape Description Data in a human readable format
/// </summary>
public List<string> TapeDescriptionData
{
get
{
List<string> data = new List<string>();
foreach (byte[] b in _tapeDescriptionData)
{
data.Add(Encoding.ASCII.GetString(b));
}
foreach (byte[] b in _tapeDescriptionData)
{
data.Add(Encoding.ASCII.GetString(b));
}
return data;
}
}
*/
return data;
}
}
#endif
#region Block Meta Data

View File

@ -1,29 +0,0 @@

namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
{
/// <summary>
/// Defines an object that can load a floppy disk image
/// </summary>
public interface IFDDHost
{
/// <summary>
/// The currently inserted diskimage
/// </summary>
FloppyDisk Disk { get; set; }
/// <summary>
/// Parses a new disk image and loads it into this floppy drive
/// </summary>
void FDD_LoadDisk(byte[] diskData);
/// <summary>
/// Ejects the current disk
/// </summary>
void FDD_EjectDisk();
/// <summary>
/// Signs whether the current active drive has a disk inserted
/// </summary>
bool FDD_IsDiskLoaded { get; }
}
}

View File

@ -1,34 +0,0 @@

namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
{
/// <summary>
/// Represents a spectrum joystick
/// </summary>
public interface IJoystick
{
/// <summary>
/// The type of joystick
/// </summary>
JoystickType JoyType { get; }
/// <summary>
/// Array of all the possibly button press names
/// </summary>
string[] ButtonCollection { get; set; }
/// <summary>
/// The player number that this controller is currently assigned to
/// </summary>
int PlayerNumber { get; set; }
/// <summary>
/// Sets the joystick line based on key pressed
/// </summary>
void SetJoyInput(string key, bool isPressed);
/// <summary>
/// Gets the state of a particular joystick binding
/// </summary>
bool GetJoyInput(string key);
}
}

View File

@ -1,4 +1,5 @@
using BizHawk.Common;
using BizHawk.Emulation.Cores.Computers.CPCSpectrumBase;
namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
{

View File

@ -1,5 +1,6 @@
using BizHawk.Common;
using BizHawk.Emulation.Common;
using BizHawk.Emulation.Cores.Computers.CPCSpectrumBase;
namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
{

View File

@ -1,19 +0,0 @@

namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
{
/// <summary>
/// Represents a device that utilizes port IN & OUT
/// </summary>
public interface IPortIODevice
{
/// <summary>
/// Device responds to an IN instruction
/// </summary>
bool ReadPort(ushort port, ref int result);
/// <summary>
/// Device responds to an OUT instruction
/// </summary>
bool WritePort(ushort port, int result);
}
}

View File

@ -4,6 +4,8 @@ using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using BizHawk.Emulation.Cores.Computers.CPCSpectrumBase;
using BizHawk.Emulation.Cores.Sound;
namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum

View File

@ -1,180 +0,0 @@

namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
{
/// <summary>
/// Used for the sector CHRN structure
/// </summary>
public class CHRN
{
/// <summary>
/// Track
/// </summary>
public byte C { get; set; }
/// <summary>
/// Side
/// </summary>
public byte H { get; set; }
/// <summary>
/// Sector ID
/// </summary>
public byte R { get; set; }
/// <summary>
/// Sector Size
/// </summary>
public byte N { get; set; }
/// <summary>
/// Status register 1
/// </summary>
private byte _flag1;
public byte Flag1
{
get => _flag1;
set => _flag1 = value;
}
/// <summary>
/// Status register 2
/// </summary>
private byte _flag2;
public byte Flag2
{
get => _flag2;
set => _flag2 = value;
}
/// <summary>
/// Used to store the last transmitted/received data bytes
/// </summary>
public byte[] DataBytes { get; set; }
/// <summary>
/// ID for the read/write data command
/// </summary>
public int DataID { get; set; }
#region Helper Methods
/// <summary>
/// Missing Address Mark (Sector_ID or DAM not found)
/// </summary>
public bool ST1MA
{
get => NECUPD765.GetBit(0, _flag1);
set
{
if (value) { NECUPD765.SetBit(0, ref _flag1); }
else { NECUPD765.UnSetBit(0, ref _flag1); }
}
}
/// <summary>
/// No Data (Sector_ID not found, CRC fail in ID_field)
/// </summary>
public bool ST1ND
{
get => NECUPD765.GetBit(2, _flag1);
set
{
if (value) { NECUPD765.SetBit(2, ref _flag1); }
else { NECUPD765.UnSetBit(2, ref _flag1); }
}
}
/// <summary>
/// Data Error (CRC-fail in ID- or Data-Field)
/// </summary>
public bool ST1DE
{
get => NECUPD765.GetBit(5, _flag1);
set
{
if (value) { NECUPD765.SetBit(5, ref _flag1); }
else { NECUPD765.UnSetBit(5, ref _flag1); }
}
}
/// <summary>
/// End of Track (set past most read/write commands) (see IC)
/// </summary>
public bool ST1EN
{
get => NECUPD765.GetBit(7, _flag1);
set
{
if (value) { NECUPD765.SetBit(7, ref _flag1); }
else { NECUPD765.UnSetBit(7, ref _flag1); }
}
}
/// <summary>
/// Missing Address Mark in Data Field (DAM not found)
/// </summary>
public bool ST2MD
{
get => NECUPD765.GetBit(0, _flag2);
set
{
if (value) { NECUPD765.SetBit(0, ref _flag2); }
else { NECUPD765.UnSetBit(0, ref _flag2); }
}
}
/// <summary>
/// Bad Cylinder (read/programmed track-ID different and read-ID = FF)
/// </summary>
public bool ST2BC
{
get => NECUPD765.GetBit(1, _flag2);
set
{
if (value) { NECUPD765.SetBit(1, ref _flag2); }
else { NECUPD765.UnSetBit(1, ref _flag2); }
}
}
/// <summary>
/// Wrong Cylinder (read/programmed track-ID different) (see b1)
/// </summary>
public bool ST2WC
{
get => NECUPD765.GetBit(4, _flag2);
set
{
if (value) { NECUPD765.SetBit(4, ref _flag2); }
else { NECUPD765.UnSetBit(4, ref _flag2); }
}
}
/// <summary>
/// Data Error in Data Field (CRC-fail in data-field)
/// </summary>
public bool ST2DD
{
get => NECUPD765.GetBit(5, _flag2);
set
{
if (value) { NECUPD765.SetBit(5, ref _flag2); }
else { NECUPD765.UnSetBit(5, ref _flag2); }
}
}
/// <summary>
/// Control Mark (read/scan command found sector with deleted DAM)
/// </summary>
public bool ST2CM
{
get => NECUPD765.GetBit(6, _flag2);
set
{
if (value) { NECUPD765.SetBit(6, ref _flag2); }
else { NECUPD765.UnSetBit(6, ref _flag2); }
}
}
#endregion
}
}

View File

@ -1,826 +0,0 @@
using BizHawk.Common;
using System;
namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
{
/// <summary>
/// Definitions
/// </summary>
#region Attribution
/*
Implementation based on the information contained here:
http://www.cpcwiki.eu/index.php/765_FDC
and here:
http://www.cpcwiki.eu/imgs/f/f3/UPD765_Datasheet_OCRed.pdf
*/
#endregion
public partial class NECUPD765
{
#region Enums
/// <summary>
/// Defines the current phase of the controller
/// </summary>
private enum Phase
{
/// <summary>
/// FDC is in an idle state, awaiting the next initial command byte
/// </summary>
Idle,
/// <summary>
/// FDC is in a state waiting for the next command instruction
/// A command consists of a command byte (eventually including the MF, MK, SK bits), and up to eight parameter bytes
/// </summary>
Command,
/// <summary>
/// During this phase, the actual data is transferred (if any). Usually that are the data bytes for the read/written sector(s), except for the Format Track Command,
/// in that case four bytes for each sector are transferred
/// </summary>
Execution,
/// <summary>
/// Returns up to seven result bytes (depending on the command) that are containing status information. The Recalibrate and Seek Track commands do not return result bytes directly,
/// instead the program must wait until the Main Status Register signalizes that the command has been completed, and then it must (!) send a
/// Sense Interrupt State command to 'terminate' the Seek/Recalibrate command.
/// </summary>
Result
}
/// <summary>
/// The lifecycle of an instruction
/// Similar to phase, this describes the current 'sub-phase' we are in when dealing with an instruction
/// </summary>
private enum InstructionState
{
/// <summary>
/// FDC has received a command byte and is currently reading parameter bytes from the data bus
/// </summary>
ReceivingParameters,
/// <summary>
/// All parameter bytes have been received. This phase allows any neccessary setup before instruction execution starts
/// </summary>
PreExecution,
/// <summary>
/// The start of instruction execution. This may end up with the FDC moving into result phase,
/// but also may also prepare the way for further processing to occur later in execution phase
/// </summary>
StartExecute,
/// <summary>
/// Data is read or written in execution phase
/// </summary>
ExecutionReadWrite,
/// <summary>
/// Execution phase is well under way. This state primarily deals with data transfer between CPU and FDC
/// </summary>
ExecutionWrite,
/// <summary>
/// Execution phase is well under way. This state primarily deals with data transfer between FDC and CPU
/// </summary>
ExecutionRead,
/// <summary>
/// Execution has finished and results bytes are ready to be read by the CPU
/// Initial result setup
/// </summary>
StartResult,
/// <summary>
/// Result processing
/// </summary>
ProcessResult,
/// <summary>
/// Results are being sent
/// </summary>
SendingResults,
/// <summary>
/// Final cleanup tasks when the instruction has fully completed
/// </summary>
Completed
}
/// <summary>
/// Represents internal interrupt state of the FDC
/// </summary>
public enum InterruptState
{
/// <summary>
/// There is no interrupt
/// </summary>
None,
/// <summary>
/// Execution interrupt
/// </summary>
Execution,
/// <summary>
/// Result interrupt
/// </summary>
Result,
/// <summary>
/// Ready interrupt
/// </summary>
Ready,
/// <summary>
/// Seek interrupt
/// </summary>
Seek
}
/// <summary>
/// Possible main states that each drive can be in
/// </summary>
public enum DriveMainState
{
/// <summary>
/// Drive is not doing anything
/// </summary>
None,
/// <summary>
/// Seek operation is in progress
/// </summary>
Seek,
/// <summary>
/// Recalibrate operation is in progress
/// </summary>
Recalibrate,
/// <summary>
/// A scan data operation is in progress
/// </summary>
Scan,
/// <summary>
/// A read ID operation is in progress
/// </summary>
ReadID,
/// <summary>
/// A read data operation is in progress
/// </summary>
ReadData,
/// <summary>
/// A read diagnostic (read track) operation is in progress
/// </summary>
ReadDiagnostic,
/// <summary>
/// A write id (format track) operation is in progress
/// </summary>
WriteID,
/// <summary>
/// A write data operation is in progress
/// </summary>
WriteData,
}
/// <summary>
/// State information during a seek/recalibration operation
/// </summary>
public enum SeekSubState
{
/// <summary>
/// Seek hasnt started yet
/// </summary>
Idle,
/// <summary>
/// Delayed
/// </summary>
Wait,
/// <summary>
/// Setup for head move
/// </summary>
MoveInit,
/// <summary>
/// Seek is currently happening
/// </summary>
HeadMove,
/// <summary>
/// Head move with no delay
/// </summary>
MoveImmediate,
/// <summary>
/// Ready to complete
/// </summary>
PerformCompletion,
/// <summary>
/// Seek operation has completed
/// </summary>
SeekCompleted
}
/// <summary>
/// Seek int code
/// </summary>
public enum SeekIntStatus
{
Normal,
Abnormal,
DriveNotReady,
}
/// <summary>
/// The direction of a specific command
/// </summary>
private enum CommandDirection
{
/// <summary>
/// Data flows from UPD765A to Z80
/// </summary>
OUT,
/// <summary>
/// Data flows from Z80 to UPD765A
/// </summary>
IN
}
/// <summary>
/// Enum defining the different types of result that can be returned
/// </summary>
private enum ResultType
{
/// <summary>
/// Standard 7 result bytes are returned
/// </summary>
Standard,
/// <summary>
/// 1 byte returned - ST3
/// (used for SenseDriveStatus)
/// </summary>
ST3,
/// <summary>
/// 1 byte returned - ST0
/// (used for version & invalid)
/// </summary>
ST0,
/// <summary>
/// 2 bytes returned for sense interrupt status command
/// ST0
/// CurrentCylinder
/// </summary>
Interrupt
}
/// <summary>
/// Possible list of encountered drive status errors
/// </summary>
public enum Status
{
/// <summary>
/// No error detected
/// </summary>
None,
/// <summary>
/// An undefined error has been detected
/// </summary>
Undefined,
/// <summary>
/// Drive is not ready
/// </summary>
DriveNotReady,
/// <summary>
/// Invalid command received
/// </summary>
Invalid,
/// <summary>
/// The disk has its write protection tab enabled
/// </summary>
WriteProtected,
/// <summary>
/// The requested sector has not been found
/// </summary>
SectorNotFound
}
/// <summary>
/// Represents the direction that the head is moving over the cylinders
/// Increment: Track number increasing (head moving from outside of disk inwards)
/// Decrement: Track number decreasing (head moving from inside of disk outwards)
/// </summary>
public enum SkipDirection
{
Increment,
Decrement
}
#endregion
#region Constants
// Command Instruction Constants
// Designates the default postitions within the cmdbuffer array
public const int CM_HEAD = 0;
/// <summary>
/// C - Track
/// </summary>
public const int CM_C = 1;
/// <summary>
/// H - Side
/// </summary>
public const int CM_H = 2;
/// <summary>
/// R - Sector ID
/// </summary>
public const int CM_R = 3;
/// <summary>
/// N - Sector size
/// </summary>
public const int CM_N = 4;
/// <summary>
/// EOT - End of track
/// </summary>
public const int CM_EOT = 5;
/// <summary>
/// GPL - Gap length
/// </summary>
public const int CM_GPL = 6;
/// <summary>
/// DTL - Data length
/// </summary>
public const int CM_DTL = 7;
/// <summary>
/// STP - Step
/// </summary>
public const int CM_STP = 7;
// Result Instruction Constants
// Designates the default postitions within the cmdbuffer array
/// <summary>
/// Status register 0
/// </summary>
public const int RS_ST0 = 0;
/// <summary>
/// Status register 1
/// </summary>
public const int RS_ST1 = 1;
/// <summary>
/// Status register 2
/// </summary>
public const int RS_ST2 = 2;
/// <summary>
/// C - Track
/// </summary>
public const int RS_C = 3;
/// <summary>
/// H - Side
/// </summary>
public const int RS_H = 4;
/// <summary>
/// R - Sector ID
/// </summary>
public const int RS_R = 5;
/// <summary>
/// N - Sector size
/// </summary>
public const int RS_N = 6;
// Main Status Register Constants
// Designates the bit positions within the Main status register
/// <summary>
/// FDD0 Busy (seek/recalib active, until succesful sense intstat)
/// FDD number 0 is in the seek mode. If any of the DnB bits IS set FDC will not accept read or write command.
/// </summary>
public const int MSR_D0B = 0;
/// <summary>
/// FDD1 Busy (seek/recalib active, until succesful sense intstat)
/// FDD number 1 is in the seek mode. If any of the DnB bits IS set FDC will not accept read or write command.
/// </summary>
public const int MSR_D1B = 1;
/// <summary>
/// FDD2 Busy (seek/recalib active, until succesful sense intstat)
/// FDD number 2 is in the seek mode. If any of the DnB bits IS set FDC will not accept read or write command.
/// </summary>
public const int MSR_D2B = 2;
/// <summary>
/// FDD3 Busy (seek/recalib active, until succesful sense intstat)
/// FDD number 3 is in the seek mode. If any of the DnB bits IS set FDC will not accept read or write command.
/// </summary>
public const int MSR_D3B = 3;
/// <summary>
/// FDC Busy (still in command-, execution- or result-phase)
/// A Read or Write command is in orocess. (FDC Busy) FDC will not accept any other command
/// </summary>
public const int MSR_CB = 4;
/// <summary>
/// Execution Mode (still in execution-phase, non_DMA_only)
/// This bit is set only during execution ohase (Execution Mode) in non-DMA mode When DB5 goes low, execution phase has ended and result phase has started.It operates only during
/// non-DMA mode of operation
/// </summary>
public const int MSR_EXM = 5;
/// <summary>
/// Data Input/Output (0=CPU->FDC, 1=FDC->CPU) (see b7)
/// Indicates direction of data transfer between FDC and data regrster If DIO = 1, then transfer is from data register to the
/// processor.If DIO = 0, then transfer is from the processor to data register
/// </summary>
public const int MSR_DIO = 6;
/// <summary>
/// Request For Master (1=ready for next byte) (see b6 for direction)
/// ndicates data register IS ready to send or receive data to or from the processor Both bits DIO and RQM should be
/// used to perform the hand-shaking functions of “ready” and “directron” to the processor
/// </summary>
public const int MSR_RQM = 7;
// Status Register 0 Constants
// Designates the bit positions within the status register 0
/// <summary>
/// Unit Select (driveno during interrupt)
/// This flag IS used to indicate a drive unit number at interrupt
/// </summary>
public const int SR0_US0 = 0;
/// <summary>
/// Unit Select (driveno during interrupt)
/// This flag IS used to indicate a drive unit number at interrupt
/// </summary>
public const int SR0_US1 = 1;
/// <summary>
/// Head Address (head during interrupt)
/// State of the head at interrupt
/// </summary>
public const int SR0_HD = 2;
/// <summary>
/// Not Ready (drive not ready or non-existing 2nd head selected)
/// Not Ready - When the FDD IS in the not-ready state and a Read or Write command IS Issued, this
/// flag IS set If a Read or Write command is issued to side 1 of a single-sided drive, then this flag IS set
/// </summary>
public const int SR0_NR = 3;
/// <summary>
/// Equipment Check (drive failure or recalibrate failed (retry))
/// Equipment check - If a fault srgnal IS received from the FDD, or if the track 0 srgnal fails to occur after 77
/// step pulses(Recalibrate Command) then this flag is set
/// </summary>
public const int SR0_EC = 4;
/// <summary>
/// Seek End (Set if seek-command completed)
/// Seek end - When the FDC completes the Seek command, this flag IS set lo 1 (high)
/// </summary>
public const int SR0_SE = 5;
/// <summary>
/// Interrupt Code (low byte)
/// Interrupt Code (0=OK, 1=aborted:readfail/OK if EN, 2=unknown cmd
/// or senseint with no int occured, 3=aborted:disc removed etc.)
/// </summary>
public const int SR0_IC0 = 6;
/// <summary>
/// Interrupt Code (high byte)
/// Interrupt Code (0=OK, 1=aborted:readfail/OK if EN, 2=unknown cmd
/// or senseint with no int occured, 3=aborted:disc removed etc.)
/// </summary>
public const int SR0_IC1 = 7;
// Status Register 1 Constants
// Designates the bit positions within the status register 1
/// <summary>
/// Missing Address Mark (Sector_ID or DAM not found)
/// Missing address mark - This bit is set i f the FDC does not detect the IDAM before 2 index pulses It is also set if
/// the FDC cannot find the DAM or DDAM after the IDAM i s found.MD bit of ST2 is also set at this time
/// </summary>
public const int SR1_MA = 0;
/// <summary>
/// Not Writeable (tried to write/format disc with wprot_tab=on)
/// Not writable (write protect) - During execution of Write Data, Write Deleted Data or Write ID command. if the FDC
/// detect: a write protect srgnal from the FDD.then this flag is Set
/// </summary>
public const int SR1_NW = 1;
/// <summary>
/// No Data
/// No Data (Sector_ID not found, CRC fail in ID_field)
///
/// During execution of Read Data. Read Deleted Data Write Data.Write Deleted Data or Scan command, if the FDC cannot find
/// the sector specified in the IDR(2)Register, this flag i s set.
///
/// During execution of the Read ID command. if the FDC cannot read the ID field without an error, then this flag IS set
///
/// During execution of the Read Diagnostic command. if the starting sector cannot be found, then this flag is set
/// </summary>
public const int SR1_ND = 2;
/// <summary>
/// Over Run (CPU too slow in execution-phase (ca. 26us/Byte))
/// Overrun - If the FDC i s not serviced by the host system during data transfers within a certain time interval.this flaa i s set
/// </summary>
public const int SR1_OR = 4;
/// <summary>
/// Data Error (CRC-fail in ID- or Data-Field)
/// Data error - When the FDC detects a CRC(1) error in either the ID field or the data field, this flag is set
/// </summary>
public const int SR1_DE = 5;
/// <summary>
/// End of Track (set past most read/write commands) (see IC)
/// End of cylinder - When the FDC tries to access a sector beyond the final sector of a cylinder, this flag I S set
/// </summary>
public const int SR1_EN = 7;
// Status Register 2 Constants
// Designates the bit positions within the status register 2
/// <summary>
/// Missing Address Mark in Data Field (DAM not found)
/// Missing address mark - When data IS read from the medium, i f the FDC cannot find a data address mark or deleted
/// data address mark, then this flag is set
/// </summary>
public const int SR2_MD = 0;
/// <summary>
/// Bad Cylinder (read/programmed track-ID different and read-ID = FF)
/// Bad cylinder - This bit i s related to the ND bit. and when the contents of C on the medium is different
/// from that stored i n the IDR and the contents of C IS FFH.then this flag IS set
/// </summary>
public const int SR2_BC = 1;
/// <summary>
/// Scan Not Satisfied (no fitting sector found)
/// Scan not satisfied - During execution of the Scan command, i f the F D cannot find a sector on the cylinder
/// which meets the condition.then this flag i s set
/// </summary>
public const int SR2_SN = 2;
/// <summary>
/// Scan Equal Hit (equal)
/// Scan equal hit - During execution of the Scan command. i f the condition of “equal” is satisfied, this flag i s set
/// </summary>
public const int SR2_SH = 3;
/// <summary>
/// Wrong Cylinder (read/programmed track-ID different) (see b1)
/// Wrong cylinder - This bit IS related to the ND bit, and when the contents of C(3) on the medium is different
/// from that stored i n the IDR.this flag is set
/// </summary>
public const int SR2_WC = 4;
/// <summary>
/// Data Error in Data Field (CRC-fail in data-field)
/// Data error in data field - If the FDC detects a CRC error i n the data field then this flag is set
/// </summary>
public const int SR2_DD = 5;
/// <summary>
/// Control Mark (read/scan command found sector with deleted DAM)
/// Control mark - During execution of the Read Data or Scan command, if the FDC encounters a sector
/// which contains a deleted data address mark, this flag is set Also set if DAM is
/// found during Read Deleted Data
/// </summary>
public const int SR2_CM = 6;
// Status Register 3 Constants
// Designates the bit positions within the status register 3
/// <summary>
/// Unit select 0
/// Unit Select (pin 28,29 of FDC)
/// </summary>
public const int SR3_US0 = 0;
/// <summary>
/// Unit select 1
/// Unit Select (pin 28,29 of FDC)
/// </summary>
public const int SR3_US1 = 1;
/// <summary>
/// Head address (side select)
/// Head Address (pin 27 of FDC)
/// </summary>
public const int SR3_HD = 2;
/// <summary>
/// Two Side (0=yes, 1=no (!))
/// Two-side - This bit IS used to indicate the status of the two-side signal from the FDD
/// </summary>
public const int SR3_TS = 3;
/// <summary>
/// Track 0 (on track 0 we are)
/// Track 0 - This bit IS used to indicate the status of the track 0 signal from the FDD
/// </summary>
public const int SR3_T0 = 4;
/// <summary>
/// Ready - status of the ready signal from the fdd
/// Ready (drive ready signal)
/// </summary>
public const int SR3_RY = 5;
/// <summary>
/// Write Protected (write protected)
/// Write protect - status of the wp signal from the fdd
/// </summary>
public const int SR3_WP = 6;
/// <summary>
/// Fault - This bit is used to indicate the status of the fault signal from the FDD
/// Fault (if supported: 1=Drive failure)
/// </summary>
public const int SR3_FT = 7;
// Interrupt Code Masks
/// <summary>
/// 1 = aborted:readfail / OK if EN (end of track)
/// </summary>
public const byte IC_OK = 0x00;
/// <summary>
/// 1 = aborted:readfail / OK if EN (end of track)
/// </summary>
public const byte IC_ABORTED_RF_OKEN = 0x40;
/// <summary>
/// 2 = unknown cmd or senseint with no int occured
/// </summary>
public const byte IC_NO_INT_OCCURED = 0x80;
/// <summary>
/// 3 = aborted:disc removed etc
/// </summary>
public const byte IC_ABORTED_DISCREMOVED = 0xC0;
// command code constants
public const int CC_READ_DATA = 0x06;
public const int CC_READ_ID = 0x0a;
public const int CC_SPECIFY = 0x03;
public const int CC_READ_DIAGNOSTIC = 0x02;
public const int CC_SCAN_EQUAL = 0x11;
public const int CC_SCAN_HIGHOREQUAL = 0x1d;
public const int CC_SCAN_LOWOREQUAL = 0x19;
public const int CC_READ_DELETEDDATA = 0x0c;
public const int CC_WRITE_DATA = 0x05;
public const int CC_WRITE_ID = 0x0d;
public const int CC_WRITE_DELETEDDATA = 0x09;
public const int CC_SEEK = 0x0f;
public const int CC_RECALIBRATE = 0x07;
public const int CC_SENSE_INTSTATUS = 0x08;
public const int CC_SENSE_DRIVESTATUS = 0x04;
public const int CC_VERSION = 0x10;
public const int CC_INVALID = 0x00;
// drive seek state constants
public const int SEEK_IDLE = 0;
public const int SEEK_SEEK = 1;
public const int SEEK_RECALIBRATE = 2;
// seek interrupt
public const int SEEK_INTACKNOWLEDGED = 3;
public const int SEEK_NORMALTERM = 4;
public const int SEEK_ABNORMALTERM = 5;
public const int SEEK_DRIVENOTREADY = 6;
#endregion
#region Classes & Structs
/// <summary>
/// Class that holds information about a specific command
/// </summary>
private class Command
{
// /// <summary>
// /// Mask to remove potential parameter bits (5,6, and or 7) in order to identify the command
// /// </summary>
// public int BitMask { get; set; }
/// <summary>
/// The command code after bitmask has been applied
/// </summary>
public int CommandCode { get; set; }
/// <summary>
/// The number of bytes that make up the full command
/// </summary>
public int ParameterByteCount { get; set; }
/// <summary>
/// The number of result bytes that will be generated from the command
/// </summary>
public int ResultByteCount { get; set; }
/// <summary>
/// The command direction
/// IN - Z80 to UPD765A
/// OUT - UPD765A to Z80
/// </summary>
public CommandDirection Direction { get; set; }
/// <summary>
/// Command makes use of the MT bit
/// </summary>
public bool MT;
/// <summary>
/// Command makes use of the MF bit
/// </summary>
public bool MF;
/// <summary>
/// Command makes use of the SK bit
/// </summary>
public bool SK;
/// <summary>
/// Read/Write command that is READ
/// </summary>
public bool IsRead;
/// <summary>
/// Read/Write command that is WRITE
/// </summary>
public bool IsWrite;
/// <summary>
/// Delegate function that is called by this command
/// bool 1: EXECUTE - if TRUE the command will be executed. if FALSE the method will instead parse commmand parameter bytes
/// bool 2: RESULT - if TRUE
/// </summary>
public Action CommandDelegate { get; set; }
}
/// <summary>
/// Storage for command parameters
/// </summary>
public class CommandParameters
{
/// <summary>
/// The requested drive
/// </summary>
public byte UnitSelect;
/// <summary>
/// The requested physical side
/// </summary>
public byte Side;
/// <summary>
/// The requested track (C)
/// </summary>
public byte Cylinder;
/// <summary>
/// The requested head (H)
/// </summary>
public byte Head;
/// <summary>
/// The requested sector (R)
/// </summary>
public byte Sector;
/// <summary>
/// The specified sector size (N)
/// </summary>
public byte SectorSize;
/// <summary>
/// The end of track or last sector value (EOT)
/// </summary>
public byte EOT;
/// <summary>
/// Gap3 length (GPL)
/// </summary>
public byte Gap3Length;
/// <summary>
/// Data length (DTL) - When N is defined as 00, DTL stands for the data length
/// which users are going to read out or write into the sector
/// </summary>
public byte DTL;
/// <summary>
/// Clear down
/// </summary>
public void Reset()
{
UnitSelect = 0;
Side = 0;
Cylinder = 0;
Head = 0;
Sector = 0;
SectorSize = 0;
EOT = 0;
Gap3Length = 0;
DTL = 0;
}
public void SyncState(Serializer ser)
{
ser.BeginSection("ActiveCmdParams");
ser.Sync(nameof(UnitSelect), ref UnitSelect);
ser.Sync(nameof(Side), ref Side);
ser.Sync(nameof(Cylinder), ref Cylinder);
ser.Sync(nameof(Head), ref Head);
ser.Sync(nameof(Sector), ref Sector);
ser.Sync(nameof(SectorSize), ref SectorSize);
ser.Sync(nameof(EOT), ref EOT);
ser.Sync(nameof(Gap3Length), ref Gap3Length);
ser.Sync(nameof(DTL), ref DTL);
ser.EndSection();
}
}
#endregion
}
}

View File

@ -1,876 +0,0 @@
using BizHawk.Common;
using BizHawk.Common.NumberExtensions;
using System;
using System.Linq;
namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
{
/// <summary>
/// Floppy drive related stuff
/// </summary>
#region Attribution
/*
Implementation based on the information contained here:
http://www.cpcwiki.eu/index.php/765_FDC
and here:
http://www.cpcwiki.eu/imgs/f/f3/UPD765_Datasheet_OCRed.pdf
*/
#endregion
public partial class NECUPD765 : IFDDHost
{
#region Drive State
/// <summary>
/// FDD Flag - motor on/off
/// </summary>
public bool FDD_FLAG_MOTOR;
/// <summary>
/// The index of the currently active disk drive
/// </summary>
public int DiskDriveIndex
{
get => _diskDriveIndex;
set
{
// when index is changed update the ActiveDrive
_diskDriveIndex = value;
ActiveDrive = DriveStates[_diskDriveIndex];
}
}
private int _diskDriveIndex = 0;
/// <summary>
/// The currently active drive
/// </summary>
private DriveState ActiveDrive;
/// <summary>
/// Array that holds state information for each possible drive
/// </summary>
private DriveState[] DriveStates = new DriveState[4];
#endregion
#region FDD Methods
/// <summary>
/// Initialization / reset of the floppy drive subsystem
/// </summary>
private void FDD_Init()
{
for (int i = 0; i < 4; i++)
{
DriveState ds = new DriveState(i, this);
DriveStates[i] = ds;
}
}
/// <summary>
/// Searches for the requested sector
/// </summary>
private FloppyDisk.Sector GetSector()
{
FloppyDisk.Sector sector = null;
// get the current track
var trk = ActiveDrive.Disk.DiskTracks[ActiveDrive.TrackIndex];
// get the current sector index
int index = ActiveDrive.SectorIndex;
// make sure this index exists
if (index > trk.Sectors.Length)
{
index = 0;
}
// index hole count
int iHole = 0;
// loop through the sectors in a track
// the loop ends with either the sector being found
// or the index hole being passed twice
while (iHole <= 2)
{
// does the requested sector match the current sector
if (trk.Sectors[index].SectorIDInfo.C == ActiveCommandParams.Cylinder &&
trk.Sectors[index].SectorIDInfo.H == ActiveCommandParams.Head &&
trk.Sectors[index].SectorIDInfo.R == ActiveCommandParams.Sector &&
trk.Sectors[index].SectorIDInfo.N == ActiveCommandParams.SectorSize)
{
// sector has been found
sector = trk.Sectors[index];
UnSetBit(SR2_BC, ref Status2);
UnSetBit(SR2_WC, ref Status2);
break;
}
// check for bad cylinder
if (trk.Sectors[index].SectorIDInfo.C == 255)
{
SetBit(SR2_BC, ref Status2);
}
// check for no cylinder
else if (trk.Sectors[index].SectorIDInfo.C != ActiveCommandParams.Cylinder)
{
SetBit(SR2_WC, ref Status2);
}
// incrememnt sector index
index++;
// have we reached the index hole?
if (trk.Sectors.Length <= index)
{
// wrap around
index = 0;
iHole++;
}
}
// search loop has completed and the sector may or may not have been found
// bad cylinder detected?
if (Status2.Bit(SR2_BC))
{
// remove WC
UnSetBit(SR2_WC, ref Status2);
}
// update sectorindex on drive
ActiveDrive.SectorIndex = index;
return sector;
}
#endregion
#region IFDDHost
// IFDDHost methods that fall through to the currently active drive
/// <summary>
/// Parses a new disk image and loads it into this floppy drive
/// </summary>
public void FDD_LoadDisk(byte[] diskData)
{
// we are only going to load into the first drive
DriveStates[0].FDD_LoadDisk(diskData);
}
/// <summary>
/// Ejects the current disk
/// </summary>
public void FDD_EjectDisk()
{
DriveStates[0].FDD_EjectDisk();
}
/// <summary>
/// Signs whether the current active drive has a disk inserted
/// </summary>
public bool FDD_IsDiskLoaded => DriveStates[DiskDriveIndex].FDD_IsDiskLoaded;
/// <summary>
/// Returns the disk object from drive 0
/// </summary>
public FloppyDisk DiskPointer => DriveStates[0].Disk;
public FloppyDisk Disk { get; set; }
#endregion
#region Drive Status Class
/// <summary>
/// Holds specfic state information about a drive
/// </summary>
private class DriveState : IFDDHost
{
#region State
/// <summary>
/// The drive ID from an FDC perspective
/// </summary>
public int ID;
/// <summary>
/// Signs whether this drive ready
/// TRUE if both drive exists and has a disk inserted
/// </summary>
public bool FLAG_READY
{
get
{
if (!FDD_IsDiskLoaded || Disk.GetTrackCount() == 0 || !FDC.FDD_FLAG_MOTOR)
return false;
else
return true;
}
}
/// <summary>
/// Disk is write protected (TRUE BY DEFAULT)
/// </summary>
public bool FLAG_WRITEPROTECT = false;
/// <summary>
/// Storage for seek steps
/// One step for each indexpulse (track index) until seeked track
/// </summary>
public int SeekCounter;
/// <summary>
/// Seek status
/// </summary>
public int SeekStatus;
/// <summary>
/// Age counter
/// </summary>
public int SeekAge;
/// <summary>
/// The current side
/// </summary>
public byte CurrentSide;
/// <summary>
/// The current track index in the DiskTracks array
/// </summary>
public byte TrackIndex;
/// <summary>
/// The track ID of the current cylinder
/// </summary>
public byte CurrentTrackID
{
get
{
// default invalid track
int id = 0xff;
if (Disk == null)
return (byte)id;
if (Disk.DiskTracks.Count() == 0)
return (byte)id;
if (TrackIndex >= Disk.GetTrackCount())
TrackIndex = 0;
else if (TrackIndex < 0)
TrackIndex = 0;
var track = Disk.DiskTracks[TrackIndex];
id = track.TrackNumber;
return (byte)id;
}
set
{
for (int i = 0; i < Disk.GetTrackCount(); i++)
{
if (Disk.DiskTracks[i].TrackNumber == value)
{
TrackIndex = (byte)i;
break;
}
}
}
}
/// <summary>
/// The new track that the drive is seeking to
/// (used in seek operations)
/// </summary>
public int SeekingTrack;
/// <summary>
/// The current sector index in the Sectors array
/// </summary>
public int SectorIndex;
/// <summary>
/// The currently loaded floppy disk
/// </summary>
public FloppyDisk Disk { get; set; }
/// <summary>
/// The parent controller
/// </summary>
private NECUPD765 FDC;
#endregion
#region Lookups
/// <summary>
/// TRUE if we are on track 0
/// </summary>
public bool FLAG_TRACK0 => TrackIndex == 0;
#endregion
#region Public Methods
/*
/// <summary>
/// Moves the head across the disk cylinders
/// </summary>
public void MoveHead(SkipDirection direction, int cylinderCount)
{
// get total tracks
int trackCount = Disk.DiskTracks.Count();
int trk = 0;
switch (direction)
{
case SkipDirection.Increment:
trk = (int)CurrentTrack + cylinderCount;
if (trk >= trackCount)
{
// past the last track
trk = trackCount - 1;
}
else if (trk < 0)
trk = 0;
break;
case SkipDirection.Decrement:
trk = (int)CurrentTrack - cylinderCount;
if (trk < 0)
{
// before the first track
trk = 0;
}
else if (trk >= trackCount)
trk = trackCount - 1;
break;
}
// move the head
CurrentTrack = (byte)trk;
}
*/
/*
/// <summary>
/// Finds a supplied sector
/// </summary>
public FloppyDisk.Sector FindSector(ref byte[] resBuffer, CommandParameters prms)
{
int index =CurrentSector;
int lc = 0;
FloppyDisk.Sector sector = null;
bool found = false;
do
{
sector = Disk.DiskTracks[CurrentTrack].Sectors[index];
if (sector != null && sector.SectorID == prms.Sector)
{
// sector found
// check for data errors
if ((sector.Status1 & 0x20) != 0 || (sector.Status2 & 0x20) != 0)
{
// data errors found
}
found = true;
break;
}
// sector doesnt match
var c = Disk.DiskTracks[CurrentTrack].Sectors[index].TrackNumber;
if (c == 255)
{
// bad cylinder
resBuffer[RS_ST2] |= 0x02;
}
else if (prms.Cylinder != c)
{
// cylinder mismatch
resBuffer[RS_ST2] |= 0x10;
}
// increment index
index++;
if (index >= Disk.DiskTracks[CurrentTrack].NumberOfSectors)
{
// out of bounds
index = 0;
lc++;
}
} while (lc < 2);
if ((resBuffer[RS_ST2] & 0x02) != 0)
{
// bad cylinder set - remove no cylinder
UnSetBit(SR2_WC, ref resBuffer[RS_ST2]);
}
// update current sector
CurrentSector = index;
if (found)
return sector;
else
return null;
}
/// <summary>
/// Populates a result buffer
/// </summary>
public void FillResult(ref byte[] resBuffer, CHRN chrn)
{
// clear results
resBuffer[RS_ST0] = 0;
resBuffer[RS_ST1] = 0;
resBuffer[RS_ST2] = 0;
resBuffer[RS_C] = 0;
resBuffer[RS_H] = 0;
resBuffer[RS_R] = 0;
resBuffer[RS_N] = 0;
if (chrn == null)
{
// no chrn supplied
resBuffer[RS_ST0] = ST0;
resBuffer[RS_ST1] = 0;
resBuffer[RS_ST2] = 0;
resBuffer[RS_C] = 0;
resBuffer[RS_H] = 0;
resBuffer[RS_R] = 0;
resBuffer[RS_N] = 0;
}
}
/// <summary>
/// Populates the result buffer with ReadID data
/// </summary>
public void ReadID(ref byte[] resBuffer)
{
if (CheckDriveStatus() == false)
{
// drive not ready
resBuffer[RS_ST0] = ST0;
return;
}
var track = Disk.DiskTracks.Where(a => a.TrackNumber == CurrentTrack).FirstOrDefault();
if (track != null && track.NumberOfSectors > 0)
{
// formatted track
// get the current sector
int index = CurrentSector;
// is the index out of bounds?
if (index >= track.NumberOfSectors)
{
// reset the index
index = 0;
}
// read the sector data
var data = track.Sectors[index];
resBuffer[RS_C] = data.TrackNumber;
resBuffer[RS_H] = data.SideNumber;
resBuffer[RS_R] = data.SectorID;
resBuffer[RS_N] = data.SectorSize;
resBuffer[RS_ST0] = ST0;
// increment the current sector
CurrentSector = index + 1;
return;
}
else
{
// unformatted track?
resBuffer[RS_C] = FDC.CommBuffer[CM_C];
resBuffer[RS_H] = FDC.CommBuffer[CM_H];
resBuffer[RS_R] = FDC.CommBuffer[CM_R];
resBuffer[RS_N] = FDC.CommBuffer[CM_N];
SetBit(SR0_IC0, ref ST0);
resBuffer[RS_ST0] = ST0;
resBuffer[RS_ST1] = 0x01;
return;
}
}
*/
/*
/// <summary>
/// The drive performs a seek operation if necessary
/// Return value TRUE indicates seek complete
/// </summary>
public void DoSeek()
{
if (CurrentState != DriveMainState.Recalibrate &&
CurrentState != DriveMainState.Seek)
{
// no seek/recalibrate has been asked for
return;
}
if (GetBit(ID, FDC.StatusMain))
{
// drive is already seeking
return;
}
RunSeekCycle();
}
/// <summary>
/// Runs a seek cycle
/// </summary>
public void RunSeekCycle()
{
for (;;)
{
switch (SeekState)
{
// seek or recalibrate has been requested
case SeekSubState.Idle:
if (CurrentState == DriveMainState.Recalibrate)
{
// recalibrate always seeks to track 0
SeekingTrack = 0;
}
SeekState = SeekSubState.MoveInit;
// mark drive as busy
// this should be cleared by SIS command
SetBit(ID, ref FDC.StatusMain);
break;
// setup for the head move
case SeekSubState.MoveInit:
if (CurrentTrack == SeekingTrack)
{
// we are already at the required track
if (CurrentState == DriveMainState.Recalibrate &&
!FLAG_TRACK0)
{
// recalibration fail
SeekIntState = SeekIntStatus.Abnormal;
// raise seek interrupt
FDC.ActiveInterrupt = InterruptState.Seek;
// unset DB bit
UnSetBit(ID, ref FDC.StatusMain);
// equipment check
SetBit(SR0_EC, ref FDC.Status0);
SeekState = SeekSubState.PerformCompletion;
break;
}
if (CurrentState == DriveMainState.Recalibrate &&
FLAG_TRACK0)
{
// recalibration success
SeekIntState = SeekIntStatus.Normal;
// raise seek interrupt
FDC.ActiveInterrupt = InterruptState.Seek;
// unset DB bit
UnSetBit(ID, ref FDC.StatusMain);
SeekState = SeekSubState.PerformCompletion;
break;
}
}
// check for error
if (IntStatus >= IC_ABORTED_DISCREMOVED || Disk == null)
{
// drive not ready
FLAG_READY = false;
// drive not ready
SeekIntState = SeekIntStatus.DriveNotReady;
// cancel any interrupt
FDC.ActiveInterrupt = InterruptState.None;
// unset DB bit
UnSetBit(ID, ref FDC.StatusMain);
SeekState = SeekSubState.PerformCompletion;
break;
}
if (SeekCounter > 1)
{
// not ready to seek yet
SeekCounter--;
return;
}
if (FDC.SRT < 1 && CurrentTrack != SeekingTrack)
{
SeekState = SeekSubState.MoveImmediate;
break;
}
// head move
SeekState = SeekSubState.HeadMove;
break;
case SeekSubState.HeadMove:
// do the seek
SeekCounter = FDC.SRT;
if (CurrentTrack < SeekingTrack)
{
// we are seeking forward
var delta = SeekingTrack - CurrentTrack;
MoveHead(SkipDirection.Increment, 1);
}
else if (CurrentTrack > SeekingTrack)
{
// we are seeking backward
var delta = CurrentTrack - SeekingTrack;
MoveHead(SkipDirection.Decrement, 1);
}
// should the seek be completed now?
if (CurrentTrack == SeekingTrack)
{
SeekState = SeekSubState.PerformCompletion;
break;
}
// seek not finished yet
return;
// seek emulation processed immediately
case SeekSubState.MoveImmediate:
if (CurrentTrack < SeekingTrack)
{
// we are seeking forward
var delta = SeekingTrack - CurrentTrack;
MoveHead(SkipDirection.Increment, delta);
}
else if (CurrentTrack > SeekingTrack)
{
// we are seeking backward
var delta = CurrentTrack - SeekingTrack;
MoveHead(SkipDirection.Decrement, delta);
}
SeekState = SeekSubState.PerformCompletion;
break;
case SeekSubState.PerformCompletion:
SeekDone();
SeekState = SeekSubState.SeekCompleted;
break;
case SeekSubState.SeekCompleted:
// seek has already completed
return;
}
}
}
/// <summary>
/// Called when a seek operation has completed
/// </summary>
public void SeekDone()
{
SeekCounter = 0;
SeekingTrack = CurrentTrack;
// generate ST0 register data
// get only the IC bits
IntStatus &= IC_ABORTED_DISCREMOVED;
// drive ready?
if (!FLAG_READY)
{
SetBit(SR0_NR, ref IntStatus);
SetBit(SR0_EC, ref IntStatus);
// are we recalibrating?
if (CurrentState == DriveMainState.Recalibrate)
{
SetBit(SR0_EC, ref IntStatus);
}
}
// set seek end
SetBit(SR0_SE, ref IntStatus);
/*
// head address
if (CurrentSide > 0)
{
SetBit(SR0_HD, ref IntStatus);
// drive only supports 1 head
// set the EC bit
SetBit(SR0_EC, ref IntStatus);
}
*/
/*
// UnitSelect
SetUnitSelect(ID, ref IntStatus);
// move to none state
//CurrentState = DriveMainState.None;
//SeekState = SeekSubState.SeekCompleted;
// set the seek interrupt flag for this drive
// this will be cleared at the next successful senseint
FLAG_SEEK_INTERRUPT = true;
//CurrentState = DriveMainState.None;
}
*/
#endregion
#region Construction
public DriveState(int driveID, NECUPD765 fdc)
{
ID = driveID;
FDC = fdc;
}
#endregion
#region IFDDHost
/// <summary>
/// Parses a new disk image and loads it into this floppy drive
/// </summary>
public void FDD_LoadDisk(byte[] diskData)
{
// try dsk first
FloppyDisk fdd = null;
bool found = false;
foreach (DiskType type in Enum.GetValues(typeof(DiskType)))
{
switch (type)
{
case DiskType.CPCExtended:
fdd = new CPCExtendedFloppyDisk();
found = fdd.ParseDisk(diskData);
break;
case DiskType.CPC:
fdd = new CPCFloppyDisk();
found = fdd.ParseDisk(diskData);
break;
case DiskType.IPF:
fdd = new IPFFloppyDisk();
found = fdd.ParseDisk(diskData);
break;
case DiskType.UDI:
fdd = new UDI1_0FloppyDisk();
found = fdd.ParseDisk(diskData);
break;
}
if (found)
{
Disk = fdd;
break;
}
}
if (!found)
{
throw new Exception(this.GetType().ToString() +
"\n\nDisk image file could not be parsed. Potentially an unknown format.");
}
}
/// <summary>
/// Ejects the current disk
/// </summary>
public void FDD_EjectDisk()
{
Disk = null;
//FLAG_READY = false;
}
/// <summary>
/// Signs whether the current active drive has a disk inserted
/// </summary>
public bool FDD_IsDiskLoaded
{
get
{
if (Disk != null)
return true;
else
return false;
}
}
#endregion
#region StateSerialization
public void SyncState(Serializer ser)
{
ser.Sync(nameof(ID), ref ID);
ser.Sync(nameof(FLAG_WRITEPROTECT), ref FLAG_WRITEPROTECT);
//ser.Sync(nameof(FLAG_DISKCHANGED), ref FLAG_DISKCHANGED);
//ser.Sync(nameof(FLAG_RECALIBRATING), ref FLAG_RECALIBRATING);
//ser.Sync(nameof(FLAG_SEEK_INTERRUPT), ref FLAG_SEEK_INTERRUPT);
//ser.Sync(nameof(IntStatus), ref IntStatus);
//ser.Sync(nameof(ST0), ref ST0);
//ser.Sync(nameof(RecalibrationCounter), ref RecalibrationCounter);
ser.Sync(nameof(SeekCounter), ref SeekCounter);
ser.Sync(nameof(SeekStatus), ref SeekStatus);
ser.Sync(nameof(SeekAge), ref SeekAge);
ser.Sync(nameof(CurrentSide), ref CurrentSide);
//ser.Sync(nameof(CurrentTrack), ref CurrentTrack);
ser.Sync(nameof(TrackIndex), ref TrackIndex);
ser.Sync(nameof(SeekingTrack), ref SeekingTrack);
//ser.Sync(nameof(CurrentSector), ref CurrentSector);
ser.Sync(nameof(SectorIndex), ref SectorIndex);
//ser.Sync(nameof(RAngles), ref RAngles);
//ser.Sync(nameof(DataPointer), ref DataPointer);
//ser.SyncEnum(nameof(CurrentState), ref CurrentState);
//ser.SyncEnum(nameof(SeekState), ref SeekState);
//ser.SyncEnum(nameof(SeekIntState), ref SeekIntState);
}
#endregion
}
#endregion
}
}

View File

@ -1,145 +0,0 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Text;
namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
{
/// <summary>
/// IPortIODevice
/// </summary>
#region Attribution
/*
Implementation based on the information contained here:
http://www.cpcwiki.eu/index.php/765_FDC
and here:
http://www.cpcwiki.eu/imgs/f/f3/UPD765_Datasheet_OCRed.pdf
*/
#endregion
public partial class NECUPD765 : IPortIODevice
{
#region Dev Logging
public string outputfile = @"D:\Dropbox\Dropbox\_Programming\TASVideos\BizHawk\output\zxhawkio-" + DateTime.Now.ToString("yyyyMMdd_HHmmss") + ".csv";
public string outputString = "STATUS,WRITE,READ,CODE,MT,MF,SK,CMDCNT,RESCNT,EXECCNT,EXECLEN\r\n";
public bool writeDebug = false;
public List<string> dLog = new List<string>
{
"STATUS,WRITE,READ,CODE,MT,MF,SK,CMDCNT,RESCNT,EXECCNT,EXECLEN"
};
/*
* Status read
* Data write
* Data read
* CMD code
* CMD string
* MT flag
* MK flag
* SK flag
* */
private string[] workingArr = new string[3];
private void BuildCSVLine()
{
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 3; i++)
{
sb.Append(workingArr[i]);
sb.Append(",");
workingArr[i] = "";
}
sb.Append(ActiveCommand.CommandCode).Append(",");
sb.Append(CMD_FLAG_MT).Append(",");
sb.Append(CMD_FLAG_MF).Append(",");
sb.Append(CMD_FLAG_SK).Append(",");
sb.Append(CommCounter).Append(",");
sb.Append(ResCounter).Append(",");
sb.Append(ExecCounter).Append(",");
sb.Append(ExecLength);
//sb.Append("\r\n");
//outputString += sb.ToString();
dLog.Add(sb.ToString());
}
#endregion
/// <summary>
/// Device responds to an IN instruction
/// </summary>
public bool ReadPort(ushort port, ref int data)
{
BitArray bits = new BitArray(new byte[] { (byte)data });
if (port == 0x3ffd)
{
// Z80 is trying to read from the data register
data = ReadDataRegister();
if (writeDebug)
{
workingArr[2] = data.ToString();
//outputString += ",," + data + "," + ActiveCommand.CommandCode + "\r\n";
BuildCSVLine();
}
return true;
}
if (port == 0x2ffd)
{
// read main status register
// this can happen at any time
data = ReadMainStatus();
if (writeDebug)
{
//outputString += data + ",,," + ActiveCommand.CommandCode + "\r\n";
workingArr[0] = data.ToString();
BuildCSVLine();
//System.IO.File.WriteAllText(outputfile, outputString);
}
return true;
}
return false;
}
/// <summary>
/// Device responds to an OUT instruction
/// </summary>
public bool WritePort(ushort port, int data)
{
BitArray bits = new BitArray(new byte[] { (byte)data });
if (port == 0x3ffd)
{
// Z80 is attempting to write to the data register
WriteDataRegister((byte)data);
if (writeDebug)
{
//outputString += "," + data + ",," + ActiveCommand.CommandCode + "\r\n";
workingArr[1] = data.ToString();
BuildCSVLine();
//System.IO.File.WriteAllText(outputfile, outputString);
}
return true;
}
if (port == 0x1ffd)
{
// set disk motor on/off
FDD_FLAG_MOTOR = bits[3];
return true;
}
return false;
}
}
}

View File

@ -1,120 +0,0 @@

namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
{
/// <summary>
/// Timimng
/// </summary>
#region Attribution
/*
Implementation based on the information contained here:
http://www.cpcwiki.eu/index.php/765_FDC
and here:
http://www.cpcwiki.eu/imgs/f/f3/UPD765_Datasheet_OCRed.pdf
*/
#endregion
public partial class NECUPD765
{
/// <summary>
/// The current Z80 cycle
/// </summary>
private long CurrentCPUCycle
{
get
{
if (_machine == null)
return 0;
else
return _machine.CPU.TotalExecutedCycles;
}
}
/// <summary>
/// The last CPU cycle when the FDC accepted an IO read/write
/// </summary>
private long LastCPUCycle;
/// <summary>
/// The current delay figure (in Z80 t-states)
/// This implementation only introduces delay upon main status register reads
/// All timing calculations should be done during the other read/write operations
/// </summary>
private long StatusDelay;
/// <summary>
/// Defines the numbers of Z80 cycles per MS
/// </summary>
private long CPUCyclesPerMs;
/// <summary>
/// The floppy drive emulated clock speed
/// </summary>
public const double DriveClock = 31250;
/// <summary>
/// The number of floppy drive cycles per MS
/// </summary>
public long DriveCyclesPerMs;
/// <summary>
/// The number of T-States in one floppy drive clock tick
/// </summary>
public long StatesPerDriveTick;
/// <summary>
/// Responsible for measuring when the floppy drive is ready to run a cycle
/// </summary>
private long TickCounter;
/// <summary>
/// Internal drive cycle counter
/// </summary>
private int DriveCycleCounter = 1;
/// <summary>
/// Initializes the timing routines
/// </summary>
private void TimingInit()
{
// z80 timing
double frameSize = _machine.ULADevice.FrameLength;
double rRate = _machine.ULADevice.ClockSpeed / frameSize;
long tPerSecond = (long)(frameSize * rRate);
CPUCyclesPerMs = tPerSecond / 1000;
// drive timing
double dRate = DriveClock / frameSize;
long dPerSecond = (long)(frameSize * dRate);
DriveCyclesPerMs = dPerSecond / 1000;
long TStatesPerDriveCycle = (long)((double)_machine.ULADevice.ClockSpeed / DriveClock);
StatesPerDriveTick = TStatesPerDriveCycle;
}
/// <summary>
/// Called by reads to the main status register
/// Returns true if there is no delay
/// Returns false if read is to be deferred
/// </summary>
private bool CheckTiming()
{
// get delta
long delta = CurrentCPUCycle - LastCPUCycle;
if (StatusDelay >= delta)
{
// there is still delay remaining
StatusDelay -= delta;
LastCPUCycle = CurrentCPUCycle;
return false;
}
else
{
// no delay remaining
StatusDelay = 0;
LastCPUCycle = CurrentCPUCycle;
return true;
}
}
}
}

View File

@ -1,248 +0,0 @@
using BizHawk.Common;
using System.Collections.Generic;
namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
{
/// <summary>
/// The NEC floppy disk controller (and floppy drive) found in the +3
/// </summary>
#region Attribution
/*
Implementation based on the information contained here:
http://www.cpcwiki.eu/index.php/765_FDC
and here:
http://www.cpcwiki.eu/imgs/f/f3/UPD765_Datasheet_OCRed.pdf
*/
#endregion
public partial class NECUPD765
{
#region Devices
/// <summary>
/// The emulated spectrum machine
/// </summary>
private SpectrumBase _machine;
#endregion
#region Construction & Initialization
/// <summary>
/// Main constructor
/// </summary>
public NECUPD765()
{
InitCommandList();
}
/// <summary>
/// Initialization routine
/// </summary>
public void Init(SpectrumBase machine)
{
_machine = machine;
FDD_Init();
TimingInit();
Reset();
}
/// <summary>
/// Resets the FDC
/// </summary>
public void Reset()
{
// setup main status
StatusMain = 0;
Status0 = 0;
Status1 = 0;
Status2 = 0;
Status3 = 0;
SetBit(MSR_RQM, ref StatusMain);
SetPhase_Idle();
//FDC_FLAG_RQM = true;
//ActiveDirection = CommandDirection.IN;
SRT = 6;
HUT = 16;
HLT = 2;
HLT_Counter = 0;
HUT_Counter = 0;
IndexPulseCounter = 0;
CMD_FLAG_MF = false;
foreach (var d in DriveStates)
{
//d.SeekingTrack = d.CurrentTrack;
////d.SeekCounter = 0;
//d.FLAG_SEEK_INTERRUPT = false;
//d.IntStatus = 0;
//d.SeekState = SeekSubState.Idle;
//d.SeekIntState = SeekIntStatus.Normal;
}
}
/// <summary>
/// Setup the command structure
/// Each command represents one of the internal UPD765 commands
/// </summary>
private void InitCommandList()
{
CommandList = new List<Command>
{
// read data
new Command { CommandDelegate = UPD_ReadData, CommandCode = 0x06, MT = true, MF = true, SK = true, IsRead = true,
Direction = CommandDirection.OUT, ParameterByteCount = 8, ResultByteCount = 7 },
// read id
new Command { CommandDelegate = UPD_ReadID, CommandCode = 0x0a, MF = true, IsRead = true,
Direction = CommandDirection.OUT, ParameterByteCount = 1, ResultByteCount = 7 },
// specify
new Command { CommandDelegate = UPD_Specify, CommandCode = 0x03,
Direction = CommandDirection.OUT, ParameterByteCount = 2, ResultByteCount = 0 },
// read diagnostic
new Command { CommandDelegate = UPD_ReadDiagnostic, CommandCode = 0x02, MF = true, SK = true, IsRead = true,
Direction = CommandDirection.OUT, ParameterByteCount = 8, ResultByteCount = 7 },
// scan equal
new Command { CommandDelegate = UPD_ScanEqual, CommandCode = 0x11, MT = true, MF = true, SK = true, IsRead = true,
Direction = CommandDirection.IN, ParameterByteCount = 8, ResultByteCount = 7 },
// scan high or equal
new Command { CommandDelegate = UPD_ScanHighOrEqual, CommandCode = 0x1d, MT = true, MF = true, SK = true, IsRead = true,
Direction = CommandDirection.IN, ParameterByteCount = 8, ResultByteCount = 7 },
// scan low or equal
new Command { CommandDelegate = UPD_ScanLowOrEqual, CommandCode = 0x19, MT = true, MF = true, SK = true, IsRead = true,
Direction = CommandDirection.IN, ParameterByteCount = 8, ResultByteCount = 7 },
// read deleted data
new Command { CommandDelegate = UPD_ReadDeletedData, CommandCode = 0x0c, MT = true, MF = true, SK = true, IsRead = true,
Direction = CommandDirection.OUT, ParameterByteCount = 8, ResultByteCount = 7 },
// write data
new Command { CommandDelegate = UPD_WriteData, CommandCode = 0x05, MT = true, MF = true, IsWrite = true,
Direction = CommandDirection.IN, ParameterByteCount = 8, ResultByteCount = 7 },
// write id
new Command { CommandDelegate = UPD_WriteID, CommandCode = 0x0d, MF = true, IsWrite = true,
Direction = CommandDirection.IN, ParameterByteCount = 5, ResultByteCount = 7 },
// write deleted data
new Command { CommandDelegate = UPD_WriteDeletedData, CommandCode = 0x09, MT = true, MF = true, IsWrite = true,
Direction = CommandDirection.IN, ParameterByteCount = 8, ResultByteCount = 7 },
// seek
new Command { CommandDelegate = UPD_Seek, CommandCode = 0x0f,
Direction = CommandDirection.OUT, ParameterByteCount = 2, ResultByteCount = 0 },
// recalibrate (seek track00)
new Command { CommandDelegate = UPD_Recalibrate, CommandCode = 0x07,
Direction = CommandDirection.OUT, ParameterByteCount = 1, ResultByteCount = 0 },
// sense interrupt status
new Command { CommandDelegate = UPD_SenseInterruptStatus, CommandCode = 0x08,
Direction = CommandDirection.OUT, ParameterByteCount = 0, ResultByteCount = 2 },
// sense drive status
new Command { CommandDelegate = UPD_SenseDriveStatus, CommandCode = 0x04,
Direction = CommandDirection.OUT, ParameterByteCount = 1, ResultByteCount = 1 },
// version
new Command { CommandDelegate = UPD_Version, CommandCode = 0x10,
Direction = CommandDirection.OUT, ParameterByteCount = 0, ResultByteCount = 1 },
// invalid
new Command { CommandDelegate = UPD_Invalid, CommandCode = 0x00,
Direction = CommandDirection.OUT, ParameterByteCount = 0, ResultByteCount = 1 },
};
}
#endregion
#region State Serialization
public void SyncState(Serializer ser)
{
void SyncFDDState(Serializer ser1)
{
ser1.Sync(nameof(FDD_FLAG_MOTOR), ref FDD_FLAG_MOTOR);
for (int i = 0; i < 4; i++)
{
ser1.BeginSection("HITDrive_" + i);
DriveStates[i].SyncState(ser1);
ser1.EndSection();
}
ser1.Sync(nameof(DiskDriveIndex), ref _diskDriveIndex);
// set active drive
DiskDriveIndex = _diskDriveIndex;
}
void SyncRegisterState(Serializer ser1)
{
ser1.Sync("_RegMain", ref StatusMain);
ser1.Sync("_Reg0", ref Status0);
ser1.Sync("_Reg1", ref Status1);
ser1.Sync("_Reg2", ref Status2);
ser1.Sync("_Reg3", ref Status3);
}
void SyncControllerState(Serializer ser1)
{
ser1.Sync(nameof(DriveLight), ref DriveLight);
ser1.SyncEnum(nameof(ActivePhase), ref ActivePhase);
#if false
ser1.SyncEnum(nameof(ActiveDirection), ref ActiveDirection);
#endif
ser1.SyncEnum(nameof(ActiveInterrupt), ref ActiveInterrupt);
ser1.Sync(nameof(CommBuffer), ref CommBuffer, false);
ser1.Sync(nameof(CommCounter), ref CommCounter);
ser1.Sync(nameof(ResBuffer), ref ResBuffer, false);
ser1.Sync(nameof(ExecBuffer), ref ExecBuffer, false);
ser1.Sync(nameof(ExecCounter), ref ExecCounter);
ser1.Sync(nameof(ExecLength), ref ExecLength);
ser1.Sync(nameof(InterruptResultBuffer), ref InterruptResultBuffer, false);
ser1.Sync(nameof(ResCounter), ref ResCounter);
ser1.Sync(nameof(ResLength), ref ResLength);
ser1.Sync(nameof(LastSectorDataWriteByte), ref LastSectorDataWriteByte);
ser1.Sync(nameof(LastSectorDataReadByte), ref LastSectorDataReadByte);
ser1.Sync(nameof(LastByteReceived), ref LastByteReceived);
ser1.Sync(nameof(_cmdIndex), ref _cmdIndex);
// resync the ActiveCommand
CMDIndex = _cmdIndex;
ActiveCommandParams.SyncState(ser1);
ser1.Sync(nameof(IndexPulseCounter), ref IndexPulseCounter);
#if false
ser1.SyncEnum(nameof(_activeStatus), ref _activeStatus);
ser1.SyncEnum(nameof(_statusRaised), ref _statusRaised);
#endif
ser1.Sync(nameof(CMD_FLAG_MT), ref CMD_FLAG_MT);
ser1.Sync(nameof(CMD_FLAG_MF), ref CMD_FLAG_MF);
ser1.Sync(nameof(CMD_FLAG_SK), ref CMD_FLAG_SK);
ser1.Sync(nameof(SRT), ref SRT);
ser1.Sync(nameof(HUT), ref HUT);
ser1.Sync(nameof(HLT), ref HLT);
ser1.Sync(nameof(ND), ref ND);
ser1.Sync(nameof(SRT_Counter), ref SRT_Counter);
ser1.Sync(nameof(HUT_Counter), ref HUT_Counter);
ser1.Sync(nameof(HLT_Counter), ref HLT_Counter);
ser1.Sync(nameof(SectorDelayCounter), ref SectorDelayCounter);
ser1.Sync(nameof(SectorID), ref SectorID);
}
void SyncTimingState(Serializer ser1)
{
ser1.Sync(nameof(LastCPUCycle), ref LastCPUCycle);
ser1.Sync(nameof(StatusDelay), ref StatusDelay);
ser1.Sync(nameof(TickCounter), ref TickCounter);
ser1.Sync(nameof(DriveCycleCounter), ref DriveCycleCounter);
}
ser.BeginSection("NEC-UPD765");
SyncFDDState(ser);
SyncRegisterState(ser);
SyncControllerState(ser);
SyncTimingState(ser);
ser.EndSection();
}
#endregion
}
}

View File

@ -0,0 +1,75 @@
using System;
using BizHawk.Emulation.Cores.Computers.CPCSpectrumBase;
namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
{
public class NECUPD765Spectrum : NECUPD765<SpectrumBase, NECUPD765Spectrum.ZXDriveState>
{
protected override ZXDriveState ConstructDriveState(int driveID, NECUPD765<SpectrumBase, ZXDriveState> fdc) => new ZXDriveState(driveID, fdc);
protected override void TimingInit()
{
// z80 timing
double frameSize = _machine.ULADevice.FrameLength;
double rRate = _machine.ULADevice.ClockSpeed / frameSize;
long tPerSecond = (long)(frameSize * rRate);
CPUCyclesPerMs = tPerSecond / 1000;
// drive timing
double dRate = DriveClock / frameSize;
long dPerSecond = (long)(frameSize * dRate);
DriveCyclesPerMs = dPerSecond / 1000;
long TStatesPerDriveCycle = (long)((double)_machine.ULADevice.ClockSpeed / DriveClock);
StatesPerDriveTick = TStatesPerDriveCycle;
}
public class ZXDriveState : NECUPD765DriveState
{
public ZXDriveState(int driveID, NECUPD765<SpectrumBase, ZXDriveState> fdc) : base(driveID, fdc) {}
public override void FDD_LoadDisk(byte[] diskData)
{
// try dsk first
FloppyDisk fdd = null;
bool found = false;
foreach (DiskType type in Enum.GetValues(typeof(DiskType)))
{
switch (type)
{
case DiskType.CPCExtended:
fdd = new CPCExtendedFloppyDisk();
found = fdd.ParseDisk(diskData);
break;
case DiskType.CPC:
fdd = new CPCFloppyDisk();
found = fdd.ParseDisk(diskData);
break;
case DiskType.IPF:
fdd = new IPFFloppyDisk();
found = fdd.ParseDisk(diskData);
break;
case DiskType.UDI:
fdd = new UDI1_0FloppyDisk();
found = fdd.ParseDisk(diskData);
break;
}
if (found)
{
Disk = fdd;
break;
}
}
if (!found)
{
throw new Exception(this.GetType().ToString() +
"\n\nDisk image file could not be parsed. Potentially an unknown format.");
}
}
}
}
}

View File

@ -1,96 +0,0 @@
using System.Collections;
namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
{
/// <summary>
/// Static helper methods
/// </summary>
#region Attribution
/*
Implementation based on the information contained here:
http://www.cpcwiki.eu/index.php/765_FDC
and here:
http://www.cpcwiki.eu/imgs/f/f3/UPD765_Datasheet_OCRed.pdf
*/
#endregion
public partial class NECUPD765
{
/// <summary>
/// Returns the specified bit value from supplied byte
/// </summary>
public static bool GetBit(int bitNumber, byte dataByte)
{
if (bitNumber < 0 || bitNumber > 7)
return false;
BitArray bi = new BitArray(new byte[] { dataByte });
return bi[bitNumber];
}
/// <summary>
/// Sets the specified bit of the supplied byte to 1
/// </summary>
public static void SetBit(int bitNumber, ref byte dataByte)
{
if (bitNumber < 0 || bitNumber > 7)
return;
int db = (int)dataByte;
db |= 1 << bitNumber;
dataByte = (byte)db;
}
/// <summary>
/// Sets the specified bit of the supplied byte to 0
/// </summary>
public static void UnSetBit(int bitNumber, ref byte dataByte)
{
if (bitNumber < 0 || bitNumber > 7)
return;
int db = (int)dataByte;
db &= ~(1 << bitNumber);
dataByte = (byte)db;
}
/// <summary>
/// Returns a drive number (0-3) based on the first two bits of the supplied byte
/// </summary>
public static int GetUnitSelect(byte dataByte)
{
int driveNumber = dataByte & 0x03;
return driveNumber;
}
/// <summary>
/// Sets the first two bits of a byte based on the supplied drive number (0-3)
/// </summary>
public static void SetUnitSelect(int driveNumber, ref byte dataByte)
{
switch (driveNumber)
{
case 0:
UnSetBit(SR0_US0, ref dataByte);
UnSetBit(SR0_US1, ref dataByte);
break;
case 1:
SetBit(SR0_US0, ref dataByte);
UnSetBit(SR0_US1, ref dataByte);
break;
case 2:
SetBit(SR0_US1, ref dataByte);
UnSetBit(SR0_US0, ref dataByte);
break;
case 3:
SetBit(SR0_US0, ref dataByte);
SetBit(SR0_US1, ref dataByte);
break;
}
}
}
}

View File

@ -1,13 +1,15 @@
using System;
using System.Collections.Generic;
using BizHawk.Emulation.Cores.Computers.CPCSpectrumBase;
namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
{
/// <summary>
/// Cursor joystick
/// Maps to a combination of 0xf7fe and 0xeffe
/// </summary>
public class CursorJoystick : IJoystick
public class CursorJoystick : IJoystick<JoystickType>
{
//private int _joyLine;
private SpectrumBase _machine;

View File

@ -1,9 +1,11 @@
using System;
using System.Collections.Generic;
using BizHawk.Emulation.Cores.Computers.CPCSpectrumBase;
namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
{
public class KempstonJoystick : IJoystick
public class KempstonJoystick : IJoystick<JoystickType>
{
private int _joyLine;
private SpectrumBase _machine;

View File

@ -1,12 +1,14 @@
using System;
using System.Collections.Generic;
using BizHawk.Emulation.Cores.Computers.CPCSpectrumBase;
namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
{
/// <summary>
/// A null joystick object
/// </summary>
public class NullJoystick : IJoystick
public class NullJoystick : IJoystick<JoystickType>
{
private int _joyLine;
private SpectrumBase _machine;

View File

@ -1,13 +1,15 @@
using System;
using System.Collections.Generic;
using BizHawk.Emulation.Cores.Computers.CPCSpectrumBase;
namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
{
/// <summary>
/// Sinclair Joystick LEFT
/// Just maps to the standard keyboard and is read the same (from port 0xf7fe)
/// </summary>
public class SinclairJoystick1 : IJoystick
public class SinclairJoystick1 : IJoystick<JoystickType>
{
//private int _joyLine;
private SpectrumBase _machine;

View File

@ -1,13 +1,15 @@
using System;
using System.Collections.Generic;
using BizHawk.Emulation.Cores.Computers.CPCSpectrumBase;
namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
{
/// <summary>
/// Sinclair Joystick RIGHT
/// Just maps to the standard keyboard and is read the same (from port 0xeffe)
/// </summary>
public class SinclairJoystick2 : IJoystick
public class SinclairJoystick2 : IJoystick<JoystickType>
{
//private int _joyLine;
private SpectrumBase _machine;

View File

@ -1,6 +1,8 @@
using System.Collections.Generic;
using System.Linq;
using BizHawk.Emulation.Cores.Computers.CPCSpectrumBase;
namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
{
/// <summary>
@ -280,7 +282,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
/// </summary>
protected void InitJoysticks(List<JoystickType> joys)
{
var jCollection = new List<IJoystick>();
var jCollection = new List<IJoystick<JoystickType>>();
for (int i = 0; i < joys.Count(); i++)
{
@ -298,7 +300,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
/// <summary>
/// Instantiates a new IJoystick object
/// </summary>
public IJoystick InstantiateJoystick(JoystickType type, int playerNumber)
public IJoystick<JoystickType> InstantiateJoystick(JoystickType type, int playerNumber)
{
switch (type)
{
@ -320,7 +322,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
/// <summary>
/// Returns a IJoystick object depending on the type (or null if not found)
/// </summary>
protected IJoystick LocateUniqueJoystick(JoystickType type)
protected IJoystick<JoystickType> LocateUniqueJoystick(JoystickType type)
{
return JoystickCollection.FirstOrDefault(a => a.JoyType == type);
}

View File

@ -3,6 +3,8 @@ using System.Collections.Generic;
using System.Linq;
using System.Text;
using BizHawk.Emulation.Cores.Computers.CPCSpectrumBase;
namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
{
/// <summary>

View File

@ -1,5 +1,6 @@
using BizHawk.Common;
using BizHawk.Emulation.Cores.Components.Z80A;
using BizHawk.Emulation.Cores.Computers.CPCSpectrumBase;
using BizHawk.Emulation.Cores.Sound;
namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
@ -8,7 +9,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
/// The abstract class that all emulated models will inherit from
/// * Main properties / fields / contruction*
/// </summary>
public abstract partial class SpectrumBase
public abstract partial class SpectrumBase : CPCSpectrumBase.CPCSpectrumBase
{
#region Devices
@ -66,12 +67,12 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
/// <summary>
/// The +3 built-in disk drive
/// </summary>
public virtual NECUPD765 UPDDiskDevice { get; set; }
public virtual NECUPD765Spectrum UPDDiskDevice { get; set; }
/// <summary>
/// Holds the currently selected joysticks
/// </summary>
public virtual IJoystick[] JoystickCollection { get; set; }
public virtual IJoystick<JoystickType>[] JoystickCollection { get; set; }
/// <summary>
/// +3/2a printer port strobe

View File

@ -42,7 +42,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
TapeDevice = new DatacorderDevice(spectrum.SyncSettings.AutoLoadTape);
TapeDevice.Init(this);
UPDDiskDevice = new NECUPD765();
UPDDiskDevice = new NECUPD765Spectrum();
UPDDiskDevice.Init(this);
InitializeMedia(files);

View File

@ -3,6 +3,8 @@ using BizHawk.Common;
using System;
using System.Collections.Generic;
using BizHawk.Emulation.Cores.Computers.CPCSpectrumBase;
namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
{
/// <summary>

View File

@ -3,6 +3,8 @@ using BizHawk.Common;
using System;
using System.Collections.Generic;
using BizHawk.Emulation.Cores.Computers.CPCSpectrumBase;
namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
{
/// <summary>

View File

@ -1,735 +0,0 @@
using BizHawk.Common;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
{
/// <summary>
/// This abstract class defines a logical floppy disk
/// </summary>
public abstract class FloppyDisk
{
/// <summary>
/// The disk format type
/// </summary>
public abstract DiskType DiskFormatType { get; }
/// <summary>
/// Disk information header
/// </summary>
public Header DiskHeader = new Header();
/// <summary>
/// Track array
/// </summary>
public Track[] DiskTracks = null;
/// <summary>
/// No. of tracks per side
/// </summary>
public int CylinderCount;
/// <summary>
/// The number of physical sides
/// </summary>
public int SideCount;
/// <summary>
/// The number of bytes per track
/// </summary>
public int BytesPerTrack;
/// <summary>
/// The write-protect tab on the disk
/// </summary>
public bool WriteProtected;
/// <summary>
/// The detected protection scheme (if any)
/// </summary>
public ProtectionType Protection;
/// <summary>
/// The actual disk image data
/// </summary>
public byte[] DiskData;
/// <summary>
/// If TRUE then data on the disk has changed (been written to)
/// This will be used to determine whether the disk data needs to be included
/// in any SyncState operations
/// </summary>
protected bool DirtyData = false;
/// <summary>
/// Used to deterministically choose a 'random' sector when dealing with weak reads
/// </summary>
public int RandomCounter
{
get => _randomCounter;
set
{
_randomCounter = value;
foreach (var trk in DiskTracks)
{
foreach (var sec in trk.Sectors)
{
sec.RandSecCounter = _randomCounter;
}
}
}
}
protected int _randomCounter;
/// <summary>
/// Attempts to parse incoming disk data
/// </summary>
/// <returns>
/// TRUE: disk parsed
/// FALSE: unable to parse disk
/// </returns>
public virtual bool ParseDisk(byte[] diskData)
{
// default result
// override in inheriting class
return false;
}
/// <summary>
/// Examines the floppydisk data to work out what protection (if any) is present
/// If possible it will also fix the disk data for this protection
/// This should be run at the end of the ParseDisk() method
/// </summary>
public virtual void ParseProtection()
{
int[] weakArr = new int[2];
// speedlock
if (DetectSpeedlock(ref weakArr))
{
Protection = ProtectionType.Speedlock;
Sector sec = DiskTracks[0].Sectors[1];
if (!sec.ContainsMultipleWeakSectors)
{
byte[] origData = sec.SectorData.ToArray();
List<byte> data = new List<byte>();
for (int m = 0; m < 3; m++)
{
for (int i = 0; i < 512; i++)
{
// deterministic 'random' implementation
int n = origData[i] + m + 1;
if (n > 0xff)
n = n - 0xff;
else if (n < 0)
n = 0xff + n;
byte nByte = (byte)n;
if (m == 0)
{
data.Add(origData[i]);
continue;
}
if (i < weakArr[0])
{
data.Add(origData[i]);
}
else if (weakArr[1] > 0)
{
data.Add(nByte);
weakArr[1]--;
}
else
{
data.Add(origData[i]);
}
}
}
sec.SectorData = data.ToArray();
sec.ActualDataByteLength = data.Count();
sec.ContainsMultipleWeakSectors = true;
}
}
else if (DetectAlkatraz(ref weakArr))
{
Protection = ProtectionType.Alkatraz;
}
else if (DetectPaulOwens(ref weakArr))
{
Protection = ProtectionType.PaulOwens;
}
else if (DetectHexagon(ref weakArr))
{
Protection = ProtectionType.Hexagon;
}
else if (DetectShadowOfTheBeast())
{
Protection = ProtectionType.ShadowOfTheBeast;
}
}
/// <summary>
/// Detection routine for shadow of the beast game
/// Still cannot get this to work, but at least the game is detected
/// </summary>
public bool DetectShadowOfTheBeast()
{
if (DiskTracks[0].Sectors.Length != 9)
return false;
var zeroSecs = DiskTracks[0].Sectors;
if (zeroSecs[0].SectorID != 65 ||
zeroSecs[1].SectorID != 66 ||
zeroSecs[2].SectorID != 67 ||
zeroSecs[3].SectorID != 68 ||
zeroSecs[4].SectorID != 69 ||
zeroSecs[5].SectorID != 70 ||
zeroSecs[6].SectorID != 71 ||
zeroSecs[7].SectorID != 72 ||
zeroSecs[8].SectorID != 73)
return false;
var oneSecs = DiskTracks[1].Sectors;
if (oneSecs.Length != 8)
return false;
if (oneSecs[0].SectorID != 17 ||
oneSecs[1].SectorID != 18 ||
oneSecs[2].SectorID != 19 ||
oneSecs[3].SectorID != 20 ||
oneSecs[4].SectorID != 21 ||
oneSecs[5].SectorID != 22 ||
oneSecs[6].SectorID != 23 ||
oneSecs[7].SectorID != 24)
return false;
return true;
}
/// <summary>
/// Detect speedlock weak sector
/// </summary>
public bool DetectSpeedlock(ref int[] weak)
{
// SPEEDLOCK NOTES (-asni 2018-05-01)
// ---------------------------------
// Speedlock is one of the more common +3 disk protections and there are a few different versions
// Usually, track 0 sector 1 (ID 2) has data CRC errors that result in certain bytes returning a different value every time they are read
// Speedlock will generally read this track a number of times during the load process
// and if the correct bytes are not different between reads, the load fails
// always must have track 0 containing 9 sectors
if (DiskTracks[0].Sectors.Length != 9)
return false;
// check for SPEEDLOCK ident in sector 0
string ident = Encoding.ASCII.GetString(DiskTracks[0].Sectors[0].SectorData, 0, DiskTracks[0].Sectors[0].SectorData.Length);
if (!ident.ToUpper().Contains("SPEEDLOCK"))
return false;
// check for correct sector 0 lengths
if (DiskTracks[0].Sectors[0].SectorSize != 2 ||
DiskTracks[0].Sectors[0].SectorData.Length < 0x200)
return false;
// sector[1] (SectorID 2) contains the weak sectors
Sector sec = DiskTracks[0].Sectors[1];
// check for correct sector 1 lengths
if (sec.SectorSize != 2 ||
sec.SectorData.Length < 0x200)
return false;
// secID 2 needs a CRC error
//if (!(sec.Status1.Bit(5) || sec.Status2.Bit(5)))
//return false;
// check for filler
bool startFillerFound = true;
for (int i = 0; i < 250; i++)
{
if (sec.SectorData[i] != sec.SectorData[i + 1])
{
startFillerFound = false;
break;
}
}
if (!startFillerFound)
{
weak[0] = 0;
weak[1] = 0x200;
}
else
{
weak[0] = 0x150;
weak[1] = 0x20;
}
return true;
}
/// <summary>
/// Detect Alkatraz
/// </summary>
public bool DetectAlkatraz(ref int[] weak)
{
try
{
var data1 = DiskTracks[0].Sectors[0].SectorData;
var data2 = DiskTracks[0].Sectors[0].SectorData.Length;
}
catch (Exception)
{
return false;
}
// check for ALKATRAZ ident in sector 0
string ident = Encoding.ASCII.GetString(DiskTracks[0].Sectors[0].SectorData, 0, DiskTracks[0].Sectors[0].SectorData.Length);
if (!ident.ToUpper().Contains("ALKATRAZ PROTECTION SYSTEM"))
return false;
// ALKATRAZ NOTES (-asni 2018-05-01)
// ---------------------------------
// Alkatraz protection appears to revolve around a track on the disk with 18 sectors,
// (track position is not consistent) with the sector ID info being incorrect:
// TrackID is consistent between the sectors although is usually high (233, 237 etc)
// SideID is fairly random looking but with all IDs being even
// SectorID is also fairly random looking but contains both odd and even numbers
//
// There doesnt appear to be any CRC errors in this track, but the sector size is always 1 (256 bytes)
// Each sector contains different filler byte
// Once track 0 is loaded the CPU completely reads all the sectors in this track one-by-one.
// Data transferred during execution must be correct, also result ST0, ST1 and ST2 must be 64, 128 and 0 respectively
// Immediately following this track are a number of tracks and sectors with a DAM set.
// These are all read in sector by sector
// Again, Alkatraz appears to require that ST0, ST1, and ST2 result bytes are set to 64, 128 and 0 respectively
// (so the CM in ST2 needs to be reset)
return true;
}
/// <summary>
/// Detect Paul Owens
/// </summary>
public bool DetectPaulOwens(ref int[] weak)
{
try
{
var data1 = DiskTracks[0].Sectors[2].SectorData;
var data2 = DiskTracks[0].Sectors[2].SectorData.Length;
}
catch (Exception)
{
return false;
}
// check for PAUL OWENS ident in sector 2
string ident = Encoding.ASCII.GetString(DiskTracks[0].Sectors[2].SectorData, 0, DiskTracks[0].Sectors[2].SectorData.Length);
if (!ident.ToUpper().Contains("PAUL OWENS"))
return false;
// Paul Owens Disk Protection Notes (-asni 2018-05-01)
// ---------------------------------------------------
//
// This scheme looks a little similar to Alkatraz with incorrect sector ID info in many places
// and deleted address marks (although these do not seem to show the strict relience on removing the CM mark from ST2 result that Alkatraz does)
// There are also data CRC errors but these don't look to be read more than once/checked for changes during load
// Main identifiers:
//
// * There are more than 10 cylinders
// * Cylinder 1 has no sector data
// * The sector ID infomation in most cases contains incorrect track IDs
// * Tracks 0 (boot) and 5 appear to be pretty much the only tracks that do not have incorrect sector ID marks
return true;
}
/// <summary>
/// Detect Hexagon copy protection
/// </summary>
public bool DetectHexagon(ref int[] weak)
{
try
{
var data1 = DiskTracks[0].Sectors.Length;
var data2 = DiskTracks[0].Sectors[8].ActualDataByteLength;
var data3 = DiskTracks[0].Sectors[8].SectorData;
var data4 = DiskTracks[0].Sectors[8].SectorData.Length;
var data5 = DiskTracks[1].Sectors[0];
}
catch (Exception)
{
return false;
}
if (DiskTracks[0].Sectors.Length != 10 || DiskTracks[0].Sectors[8].ActualDataByteLength != 512)
return false;
// check for Hexagon ident in sector 8
string ident = Encoding.ASCII.GetString(DiskTracks[0].Sectors[8].SectorData, 0, DiskTracks[0].Sectors[8].SectorData.Length);
if (ident.ToUpper().Contains("GON DISK PROT"))
return true;
// hexagon protection may not be labelled as such
var track = DiskTracks[1];
var sector = track.Sectors[0];
if (sector.SectorSize == 6 && sector.Status1 == 0x20 && sector.Status2 == 0x60)
{
if (track.Sectors.Length == 1)
return true;
}
// Hexagon Copy Protection Notes (-asni 2018-05-01)
// ---------------------------------------------------
//
//
return false;
}
/*
/// <summary>
/// Should be run at the end of the ParseDisk process
/// If speedlock is detected the flag is set in the disk image
/// </summary>
protected virtual void SpeedlockDetection()
{
if (DiskTracks.Length == 0)
return;
// check for speedlock copyright notice
string ident = Encoding.ASCII.GetString(DiskData, 0x100, 0x1400);
if (!ident.ToUpper().Contains("SPEEDLOCK"))
{
// speedlock not found
return;
}
// get cylinder 0
var cyl = DiskTracks[0];
// get sector with ID=2
var sec = cyl.Sectors.Where(a => a.SectorID == 2).FirstOrDefault();
if (sec == null)
return;
// check for already multiple weak copies
if (sec.ContainsMultipleWeakSectors || sec.SectorData.Length != 0x80 << sec.SectorSize)
return;
// check for invalid crcs in sector 2
if (sec.Status1.Bit(5) || sec.Status2.Bit(5))
{
Protection = ProtectionType.Speedlock;
}
else
{
return;
}
// we are going to create a total of 5 weak sector copies
// keeping the original copy
byte[] origData = sec.SectorData.ToArray();
List<byte> data = new List<byte>();
//Random rnd = new Random();
for (int i = 0; i < 6; i++)
{
for (int s = 0; s < origData.Length; s++)
{
if (i == 0)
{
data.Add(origData[s]);
continue;
}
// deterministic 'random' implementation
int n = origData[s] + i + 1;
if (n > 0xff)
n = n - 0xff;
else if (n < 0)
n = 0xff + n;
byte nByte = (byte)n;
if (s < 336)
{
// non weak data
data.Add(origData[s]);
}
else if (s < 511)
{
// weak data
data.Add(nByte);
}
else if (s == 511)
{
// final sector byte
data.Add(nByte);
}
else
{
// speedlock sector should not be more than 512 bytes
// but in case it is just do non weak
data.Add(origData[i]);
}
}
}
// commit the sector data
sec.SectorData = data.ToArray();
sec.ContainsMultipleWeakSectors = true;
sec.ActualDataByteLength = data.Count();
}
*/
/// <summary>
/// Returns the track count for the disk
/// </summary>
public virtual int GetTrackCount()
{
return DiskHeader.NumberOfTracks * DiskHeader.NumberOfSides;
}
/// <summary>
/// Reads the current sector ID info
/// </summary>
public virtual CHRN ReadID(byte trackIndex, byte side, int sectorIndex)
{
if (side != 0)
return null;
if (DiskTracks.Length <= trackIndex || trackIndex < 0)
{
// invalid track - wrap around
trackIndex = 0;
}
var track = DiskTracks[trackIndex];
if (track.NumberOfSectors <= sectorIndex)
{
// invalid sector - wrap around
sectorIndex = 0;
}
var sector = track.Sectors[sectorIndex];
CHRN chrn = new CHRN();
chrn.C = sector.TrackNumber;
chrn.H = sector.SideNumber;
chrn.R = sector.SectorID;
// wrap around for N > 7
if (sector.SectorSize > 7)
{
chrn.N = (byte)(sector.SectorSize - 7);
}
else if (sector.SectorSize < 0)
{
chrn.N = 0;
}
else
{
chrn.N = sector.SectorSize;
}
chrn.Flag1 = (byte)(sector.Status1 & 0x25);
chrn.Flag2 = (byte)(sector.Status2 & 0x61);
chrn.DataBytes = sector.ActualData;
return chrn;
}
/// <summary>
/// State serialization routines
/// </summary>
public abstract void SyncState(Serializer ser);
public class Header
{
public string DiskIdent { get; set; }
public string DiskCreatorString { get; set; }
public byte NumberOfTracks { get; set; }
public byte NumberOfSides { get; set; }
public int[] TrackSizes { get; set; }
}
public class Track
{
public string TrackIdent { get; set; }
public byte TrackNumber { get; set; }
public byte SideNumber { get; set; }
public byte DataRate { get; set; }
public byte RecordingMode { get; set; }
public byte SectorSize { get; set; }
public byte NumberOfSectors { get; set; }
public byte GAP3Length { get; set; }
public byte FillerByte { get; set; }
public virtual Sector[] Sectors { get; set; }
#region UDI
public virtual byte TrackType { get; set; }
public virtual int TLEN { get; set; }
public virtual int CLEN => TLEN / 8 + (TLEN % 8 / 7) / 8;
public virtual byte[] TrackData { get; set; }
#endregion
/// <summary>
/// Presents a contiguous byte array of all sector data for this track
/// (including any multiple weak/random data)
/// </summary>
public virtual byte[] TrackSectorData
{
get
{
List<byte> list = new List<byte>();
foreach (var sec in Sectors)
{
list.AddRange(sec.ActualData);
}
return list.ToArray();
}
}
}
public class Sector
{
public virtual byte TrackNumber { get; set; }
public virtual byte SideNumber { get; set; }
public virtual byte SectorID { get; set; }
public virtual byte SectorSize { get; set; }
public virtual byte Status1 { get; set; }
public virtual byte Status2 { get; set; }
public virtual int ActualDataByteLength { get; set; }
public virtual byte[] SectorData { get; set; }
public virtual bool ContainsMultipleWeakSectors { get; set; }
public int WeakReadIndex = 0;
public void SectorReadCompleted()
{
if (ContainsMultipleWeakSectors)
WeakReadIndex++;
}
public int DataLen
{
get
{
if (!ContainsMultipleWeakSectors)
{
return ActualDataByteLength;
}
return ActualDataByteLength / (ActualDataByteLength / (0x80 << SectorSize));
}
}
public int RandSecCounter = 0;
public byte[] ActualData
{
get
{
if (!ContainsMultipleWeakSectors)
{
// check whether filler bytes are needed
int size = 0x80 << SectorSize;
if (size > ActualDataByteLength)
{
List<byte> l = new List<byte>();
l.AddRange(SectorData);
for (int i = 0; i < size - ActualDataByteLength; i++)
{
//l.Add(SectorData[i]);
l.Add(SectorData.Last());
}
return l.ToArray();
}
return SectorData;
}
else
{
// weak read neccessary
int copies = ActualDataByteLength / (0x80 << SectorSize);
// handle index wrap-around
if (WeakReadIndex > copies - 1)
WeakReadIndex = copies - 1;
// get the sector data based on the current weakreadindex
int step = WeakReadIndex * (0x80 << SectorSize);
byte[] res = new byte[(0x80 << SectorSize)];
Array.Copy(SectorData, step, res, 0, 0x80 << SectorSize);
return res;
/*
int copies = ActualDataByteLength / (0x80 << SectorSize);
Random rnd = new Random();
int r = rnd.Next(0, copies - 1);
int step = r * (0x80 << SectorSize);
byte[] res = new byte[(0x80 << SectorSize)];
Array.Copy(SectorData, step, res, 0, 0x80 << SectorSize);
return res;
*/
}
}
}
public CHRN SectorIDInfo =>
new CHRN
{
C = TrackNumber,
H = SideNumber,
R = SectorID,
N = SectorSize,
Flag1 = Status1,
Flag2 = Status2,
};
}
}
/// <summary>
/// Defines the type of speedlock detection found
/// </summary>
public enum ProtectionType
{
None,
Speedlock,
Alkatraz,
Hexagon,
Frontier,
PaulOwens,
ShadowOfTheBeast
}
}

View File

@ -5,6 +5,8 @@ using System.Collections.Generic;
using System.Linq;
using System.Text;
using BizHawk.Emulation.Cores.Computers.CPCSpectrumBase;
namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
{
public class IPFFloppyDisk : FloppyDisk

View File

@ -5,6 +5,8 @@ using System.Collections.Generic;
using System.Linq;
using System.Text;
using BizHawk.Emulation.Cores.Computers.CPCSpectrumBase;
namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
{
public class UDI1_0FloppyDisk : FloppyDisk

View File

@ -3,6 +3,8 @@ using System;
using System.Collections.Generic;
using System.Text;
using BizHawk.Emulation.Cores.Computers.CPCSpectrumBase;
namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
{
/// <summary>

View File

@ -3,6 +3,8 @@ using System;
using System.Collections.Generic;
using System.Text;
using BizHawk.Emulation.Cores.Computers.CPCSpectrumBase;
namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
{
/// <summary>

View File

@ -4,6 +4,8 @@ using System.IO;
using System.Linq;
using System.Text;
using BizHawk.Emulation.Cores.Computers.CPCSpectrumBase;
namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
{
/// <summary>

View File

@ -3,6 +3,8 @@ using System.Collections.Generic;
using System.Linq;
using System.Text;
using BizHawk.Emulation.Cores.Computers.CPCSpectrumBase;
namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
{
/// <summary>

View File

@ -1,16 +0,0 @@

namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
{
/// <summary>
/// Represents the possible commands that can be raised from each tape block
/// </summary>
public enum TapeCommand
{
NONE,
STOP_THE_TAPE,
STOP_THE_TAPE_48K,
BEGIN_GROUP,
END_GROUP,
SHOW_MESSAGE,
}
}

View File

@ -1,282 +0,0 @@
using BizHawk.Common;
using System.Collections.Generic;
using System.Linq;
namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
{
/// <summary>
/// Represents a tape block
/// </summary>
public class TapeDataBlock
{
/// <summary>
/// Either the TZX block ID, or -1 in the case of non-tzx blocks
/// </summary>
private int _blockID = -1;
public int BlockID
{
get => _blockID;
set
{
_blockID = value;
if (MetaData == null)
MetaData = new Dictionary<BlockDescriptorTitle, string>();
AddMetaData(BlockDescriptorTitle.Block_ID, value.ToString());
}
}
/// <summary>
/// The block type
/// </summary>
private BlockType _blockType;
public BlockType BlockDescription
{
get => _blockType;
set
{
_blockType = value;
if (MetaData == null)
MetaData = new Dictionary<BlockDescriptorTitle, string>();
}
}
/// <summary>
/// Byte array containing the raw block data
/// </summary>
private byte[] _blockData;
public byte[] BlockData
{
get => _blockData;
set => _blockData = value;
}
/*
/// <summary>
/// An array of bytearray encoded strings (stored in this format for easy Bizhawk serialization)
/// Its basically tape information
/// </summary>
private byte[][] _tapeDescriptionData;
/// <summary>
/// Returns the Tape Description Data in a human readable format
/// </summary>
public List<string> TapeDescriptionData
{
get
{
List<string> data = new List<string>();
foreach (byte[] b in _tapeDescriptionData)
{
data.Add(Encoding.ASCII.GetString(b));
}
return data;
}
}
*/
#region Block Meta Data
/// <summary>
/// Dictionary of block related data
/// </summary>
public Dictionary<BlockDescriptorTitle, string> MetaData { get; set; }
/// <summary>
/// Adds a single metadata item to the Dictionary
/// </summary>
public void AddMetaData(BlockDescriptorTitle descriptor, string data)
{
// check whether entry already exists
bool check = MetaData.ContainsKey(descriptor);
if (check)
{
// already exists - update
MetaData[descriptor] = data;
}
else
{
// create new
MetaData.Add(descriptor, data);
}
}
#endregion
/// <summary>
/// List containing the pulse timing values
/// </summary>
public List<int> DataPeriods = new List<int>();
public bool InitialPulseLevel;
/// <summary>
/// Command that is raised by this data block
/// (that may or may not need to be acted on)
/// </summary>
private TapeCommand _command = TapeCommand.NONE;
public TapeCommand Command
{
get => _command;
set => _command = value;
}
/// <summary>
/// The defined post-block pause
/// </summary>
private int _pauseInMS;
public int PauseInMS
{
get => _pauseInMS;
set => _pauseInMS = value;
}
/// <summary>
/// Returns the data periods as an array
/// (primarily to aid in bizhawk state serialization)
/// </summary>
public int[] GetDataPeriodsArray()
{
return DataPeriods.ToArray();
}
/// <summary>
/// Accepts an array of data periods and updates the DataPeriods list accordingly
/// (primarily to aid in bizhawk state serialization)
/// </summary>
public void SetDataPeriodsArray(int[] periodArray)
{
DataPeriods = periodArray?.ToList() ?? new List<int>();
}
/// <summary>
/// Bizhawk state serialization
/// </summary>
public void SyncState(Serializer ser, int blockPosition)
{
ser.BeginSection("DataBlock" + blockPosition);
ser.Sync(nameof(_blockID), ref _blockID);
//ser.SyncFixedString(nameof(_blockDescription), ref _blockDescription, 200);
ser.SyncEnum(nameof(_blockType), ref _blockType);
ser.Sync(nameof(_blockData), ref _blockData, true);
ser.SyncEnum(nameof(_command), ref _command);
int[] tempArray = null;
if (ser.IsWriter)
{
tempArray = GetDataPeriodsArray();
ser.Sync("_periods", ref tempArray, true);
}
else
{
ser.Sync("_periods", ref tempArray, true);
SetDataPeriodsArray(tempArray);
}
ser.EndSection();
}
}
/// <summary>
/// The types of TZX blocks
/// </summary>
public enum BlockType
{
Standard_Speed_Data_Block = 0x10,
Turbo_Speed_Data_Block = 0x11,
Pure_Tone = 0x12,
Pulse_Sequence = 0x13,
Pure_Data_Block = 0x14,
Direct_Recording = 0x15,
CSW_Recording = 0x18,
Generalized_Data_Block = 0x19,
Pause_or_Stop_the_Tape = 0x20,
Group_Start = 0x21,
Group_End = 0x22,
Jump_to_Block = 0x23,
Loop_Start = 0x24,
Loop_End = 0x25,
Call_Sequence = 0x26,
Return_From_Sequence = 0x27,
Select_Block = 0x28,
Stop_the_Tape_48K = 0x2A,
Set_Signal_Level = 0x2B,
Text_Description = 0x30,
Message_Block = 0x31,
Archive_Info = 0x32,
Hardware_Type = 0x33,
Custom_Info_Block = 0x35,
Glue_Block = 0x5A,
// depreciated blocks
C64_ROM_Type_Data_Block = 0x16,
C64_Turbo_Tape_Data_Block = 0x17,
Emulation_Info = 0x34,
Snapshot_Block = 0x40,
// unsupported / undetected
Unsupported,
// PZX blocks
PZXT,
PULS,
DATA,
BRWS,
PAUS,
// zxhawk proprietry
PAUSE_BLOCK,
WAV_Recording
}
/// <summary>
/// Different title possibilities
/// </summary>
public enum BlockDescriptorTitle
{
Undefined,
Block_ID,
Program,
Data_Bytes,
Bytes,
Pilot_Pulse_Length,
Pilot_Pulse_Count,
First_Sync_Length,
Second_Sync_Length,
Zero_Bit_Length,
One_Bit_Length,
Data_Length,
Bits_In_Last_Byte,
Pause_After_Data,
Pulse_Length,
Pulse_Count,
Text_Description,
Title,
Publisher,
Author,
Year,
Language,
Type,
Price,
Protection,
Origin,
Comments,
Needs_Parsing
}
}

View File

@ -3,6 +3,8 @@ using System.Collections.Generic;
using System.IO;
using System.Text;
using BizHawk.Emulation.Cores.Computers.CPCSpectrumBase;
namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
{
/// <summary>

View File

@ -2,6 +2,8 @@
using System.Linq;
using System.Text;
using BizHawk.Emulation.Cores.Computers.CPCSpectrumBase;
namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
{
/// <summary>