mirror of
https://github.com/livingcomputermuseum/ContrAlto.git
synced 2026-01-17 08:34:15 +00:00
237 lines
10 KiB
C#
237 lines
10 KiB
C#
using System;
|
|
|
|
namespace Contralto.CPU
|
|
{
|
|
public partial class AltoCPU
|
|
{
|
|
/// <summary>
|
|
/// DiskTask provides implementation for disk-specific special functions
|
|
/// (for both Disk Sector and Disk Word tasks, since the special functions are
|
|
/// identical between the two)
|
|
/// </summary>
|
|
private sealed class DiskTask : Task
|
|
{
|
|
public DiskTask(AltoCPU cpu, bool diskSectorTask) : base(cpu)
|
|
{
|
|
_taskType = diskSectorTask ? TaskType.DiskSector : TaskType.DiskWord;
|
|
_wakeup = false;
|
|
}
|
|
|
|
public override void WakeupTask()
|
|
{
|
|
base.WakeupTask();
|
|
}
|
|
|
|
protected override bool ExecuteInstruction(MicroInstruction instruction)
|
|
{
|
|
bool task = base.ExecuteInstruction(instruction);
|
|
|
|
// Deal with SECLATE semantics: If the Disk Sector task wakes up and runs before
|
|
// the Disk Controller hits the SECLATE trigger time, then SECLATE remains false.
|
|
// Otherwise, when the trigger time is hit SECLATE is raised until
|
|
// the beginning of the next sector.
|
|
if (_taskType == TaskType.DiskSector)
|
|
{
|
|
// Sector task is running; clear enable for seclate signal
|
|
_cpu._system.DiskController.DisableSeclate();
|
|
}
|
|
|
|
return task;
|
|
}
|
|
|
|
protected override ushort GetBusSource(int bs)
|
|
{
|
|
DiskBusSource dbs = (DiskBusSource)bs;
|
|
|
|
switch (dbs)
|
|
{
|
|
case DiskBusSource.ReadKSTAT:
|
|
return _cpu._system.DiskController.KSTAT;
|
|
|
|
case DiskBusSource.ReadKDATA:
|
|
return _cpu._system.DiskController.KDATA;
|
|
|
|
default:
|
|
throw new InvalidOperationException(String.Format("Unhandled bus source {0}", bs));
|
|
}
|
|
}
|
|
|
|
protected override void ExecuteSpecialFunction1(MicroInstruction instruction)
|
|
{
|
|
DiskF1 df1 = (DiskF1)instruction.F1;
|
|
|
|
switch (df1)
|
|
{
|
|
case DiskF1.LoadKDATA:
|
|
// "The KDATA register is loaded from BUS[0-15]."
|
|
_cpu._system.DiskController.KDATA = _busData;
|
|
break;
|
|
|
|
case DiskF1.LoadKADR:
|
|
// "This causes the KADR register to be loaded from BUS[8-14].
|
|
// in addition, it causes the head address bit to be loaded from KDATA[13]."
|
|
// (the latter is done by DiskController)
|
|
_cpu._system.DiskController.KADR = (ushort)((_busData & 0xff));
|
|
break;
|
|
|
|
case DiskF1.LoadKCOMM:
|
|
_cpu._system.DiskController.KCOM = (ushort)((_busData & 0x7c00) >> 10);
|
|
break;
|
|
|
|
case DiskF1.CLRSTAT:
|
|
_cpu._system.DiskController.ClearStatus();
|
|
break;
|
|
|
|
case DiskF1.INCRECNO:
|
|
_cpu._system.DiskController.IncrementRecord();
|
|
break;
|
|
|
|
case DiskF1.LoadKSTAT:
|
|
// "KSTAT[12-15] are loaded from BUS[12-15]. (Actually BUS[13] is ORed onto
|
|
// KSTAT[13].)"
|
|
|
|
// From the schematic (and ucode source, based on the values it actually uses for BUS[13]), BUS[13]
|
|
// is also inverted. So there's that, too.
|
|
|
|
// build BUS[12-15] with bit 13 flipped.
|
|
int modifiedBusData = (_busData & 0xb) | ((~_busData) & 0x4);
|
|
|
|
// OR in BUS[12-15] after masking in KSTAT[13] so it is ORed in properly.
|
|
_cpu._system.DiskController.KSTAT = (ushort)(((_cpu._system.DiskController.KSTAT & 0xfff4)) | modifiedBusData);
|
|
break;
|
|
|
|
case DiskF1.STROBE:
|
|
_cpu._system.DiskController.Strobe();
|
|
break;
|
|
|
|
default:
|
|
throw new InvalidOperationException(String.Format("Unhandled disk special function 1 {0}", df1));
|
|
}
|
|
}
|
|
|
|
protected override void ExecuteSpecialFunction2(MicroInstruction instruction)
|
|
{
|
|
DiskF2 df2 = (DiskF2)instruction.F2;
|
|
|
|
switch (df2)
|
|
{
|
|
case DiskF2.INIT:
|
|
_nextModifier |= GetInitModifier(instruction);
|
|
break;
|
|
|
|
case DiskF2.RWC:
|
|
// "NEXT<-NEXT OR (IF current record to be written THEN 3 ELSE IF
|
|
// current record to be checked THEN 2 ELSE 0.")
|
|
// Current record is in bits 8-9 of the command register; this is shifted
|
|
// by INCREC by the microcode to present the next set of bits.
|
|
int command = (_cpu._system.DiskController.KADR & 0x00c0) >> 6;
|
|
|
|
_nextModifier |= GetInitModifier(instruction);
|
|
|
|
switch (command)
|
|
{
|
|
case 0:
|
|
// read, no modification.
|
|
break;
|
|
|
|
case 1:
|
|
// check, OR in 2
|
|
_nextModifier |= 0x2;
|
|
break;
|
|
|
|
case 2:
|
|
case 3:
|
|
// write, OR in 3
|
|
_nextModifier |= 0x3;
|
|
break;
|
|
}
|
|
break;
|
|
|
|
case DiskF2.XFRDAT:
|
|
// "NEXT <- NEXT OR (IF current command wants data transfer THEN 1 ELSE 0)
|
|
_nextModifier |= GetInitModifier(instruction);
|
|
|
|
if (_cpu._system.DiskController.DataXfer)
|
|
{
|
|
_nextModifier |= 0x1;
|
|
}
|
|
break;
|
|
|
|
case DiskF2.RECNO:
|
|
_nextModifier |= GetInitModifier(instruction);
|
|
_nextModifier |= _cpu._system.DiskController.RECNO;
|
|
break;
|
|
|
|
case DiskF2.NFER:
|
|
// "NEXT <- NEXT OR (IF fatal error in latches THEN 0 ELSE 1)"
|
|
// We assume success for now...
|
|
_nextModifier |= GetInitModifier(instruction);
|
|
_nextModifier |= 0x1;
|
|
break;
|
|
|
|
case DiskF2.STROBON:
|
|
// "NEXT <- NEXT OR (IF seek strobe still on THEN 1 ELSE 0)"
|
|
_nextModifier |= GetInitModifier(instruction);
|
|
if ((_cpu._system.DiskController.KSTAT & 0x0040) == 0x0040)
|
|
{
|
|
_nextModifier |= 0x1;
|
|
}
|
|
break;
|
|
|
|
case DiskF2.SWRNRDY:
|
|
// "NEXT <- NEXT OR (IF disk not ready to accept command THEN 1 ELSE 0)
|
|
// for now, always zero (not sure when this would be 1 yet)
|
|
_nextModifier |= GetInitModifier(instruction);
|
|
if (!_cpu._system.DiskController.Ready)
|
|
{
|
|
_nextModifier |= 1;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
throw new InvalidOperationException(String.Format("Unhandled disk special function 2 {0}", df2));
|
|
}
|
|
}
|
|
|
|
protected override void ExecuteBlock()
|
|
{
|
|
//
|
|
// Update the WDINIT signal; this is based on WDALLOW (!_wdInhib) which sets WDINIT (this is done
|
|
// in KCOM way above).
|
|
// WDINIT is reset when BLOCK (a BLOCK F1 is being executed) and WDTSKACT (the disk word task is running) are 1.
|
|
//
|
|
if (_taskType == TaskType.DiskWord)
|
|
{
|
|
_cpu._system.DiskController.WDINIT = false;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// The status of the INIT flag
|
|
/// </summary>
|
|
/// <returns></returns>
|
|
private ushort GetInitModifier(MicroInstruction instruction)
|
|
{
|
|
//
|
|
// "NEXT<-NEXT OR (if WDTASKACT AND WDINIT) then 37B else 0."
|
|
//
|
|
|
|
//
|
|
// A brief discussion of the INIT signal since it isn't really covered in the Alto Hardware docs in any depth
|
|
// (and in fact is completely skipped over in the description of RWC, a rather important detail!)
|
|
// This is where the Alto ref's suggestion to have the uCode *and* the schematic on hand is really quite a
|
|
// valid recommendation.
|
|
//
|
|
// WDINIT is initially set whenever the WDINHIB bit (set via KCOM<-) is cleared (this is the WDALLOW signal).
|
|
// This signals that the microcode is "INITializing" a data transfer (so to speak). During this period,
|
|
// INIT or RWC instructions in the Disk Word task will OR in 37B to the Next field, causing the uCode to jump
|
|
// to the requisite initialization paths. WDINIT is cleared whenever a BLOCK instruction occurs during the Disk Word task,
|
|
// causing INIT to OR in 0 and RWC to or in 0, 2 or 3 (For read, check, or write respectively.)
|
|
//
|
|
|
|
return (_taskType == TaskType.DiskWord && _cpu._system.DiskController.WDINIT) ? (ushort)0x1f : (ushort)0x0;
|
|
}
|
|
}
|
|
}
|
|
}
|