1
0
mirror of https://github.com/livingcomputermuseum/ContrAlto.git synced 2026-02-04 07:33:30 +00:00

Implemented more Disk functionality, fixed bug in ACSOURCE dispatch in Emulator task.

This commit is contained in:
Josh Dersch
2015-09-16 16:27:16 -07:00
parent 08d6063def
commit ee7c7fb035
14 changed files with 387 additions and 89 deletions

View File

@@ -17,7 +17,7 @@ namespace Contralto
public class AltoSystem
{
public AltoSystem()
{
{
_cpu = new AltoCPU(this);
_memBus = new MemoryBus();
_mem = new Memory.Memory();
@@ -27,7 +27,13 @@ namespace Contralto
// Attach memory-mapped devices to the bus
_memBus.AddDevice(_mem);
_memBus.AddDevice(_keyboard);
// Register devices that need clocks
_clockableDevices = new List<IClockable>();
_clockableDevices.Add(_memBus);
_clockableDevices.Add(_diskController);
_clockableDevices.Add(_cpu);
Reset();
}
@@ -35,13 +41,16 @@ namespace Contralto
{
_cpu.Reset();
_memBus.Reset();
_diskController.Reset();
}
public void SingleStep()
{
_memBus.Clock();
_diskController.Clock();
_cpu.ExecuteNext();
// Run every device that needs attention for a single clock cycle.
for (int i = 0; i < _clockableDevices.Count; i++)
{
_clockableDevices[i].Clock();
}
}
public AltoCPU CPU
@@ -62,6 +71,7 @@ namespace Contralto
/// <summary>
/// Time (in msec) for one system clock
/// </summary>
///
public static double ClockInterval
{
get { return 0.00017; } // appx 170nsec, TODO: more accurate value?
@@ -72,5 +82,7 @@ namespace Contralto
private Contralto.Memory.Memory _mem;
private Keyboard _keyboard;
private DiskController _diskController;
private List<IClockable> _clockableDevices;
}
}

View File

@@ -22,7 +22,7 @@ namespace Contralto.CPU
DiskWord = 14,
}
public partial class AltoCPU
public partial class AltoCPU : IClockable
{
public AltoCPU(AltoSystem system)
{
@@ -115,25 +115,9 @@ namespace Contralto.CPU
}
public void ExecuteNext()
public void Clock()
{
if (_currentTask.ExecuteNext())
{
// Invoke the task switch, this will take effect after
// the NEXT instruction, not this one.
TaskSwitch();
}
else
{
// If we have a new task, switch to it now.
if (_nextTask != null)
{
_currentTask = _nextTask;
_nextTask = null;
}
}
_clocks++;
ExecuteNext();
}
/// <summary>
@@ -162,6 +146,27 @@ namespace Contralto.CPU
}
}
private void ExecuteNext()
{
if (_currentTask.ExecuteNext())
{
// Invoke the task switch, this will take effect after
// the NEXT instruction, not this one.
TaskSwitch();
}
else
{
// If we have a new task, switch to it now.
if (_nextTask != null)
{
_currentTask = _nextTask;
_nextTask = null;
}
}
_clocks++;
}
private void TaskSwitch()
{
// Select the highest-priority eligible task

View File

@@ -7,9 +7,9 @@ using System.Threading.Tasks;
namespace Contralto.CPU
{
static class ConstantMemory
static class ControlROM
{
static ConstantMemory()
static ControlROM()
{
Init();
}
@@ -17,6 +17,7 @@ namespace Contralto.CPU
private static void Init()
{
LoadConstants(_constantRoms);
LoadACSource(_acSourceRoms);
}
public static ushort[] ConstantROM
@@ -24,6 +25,11 @@ namespace Contralto.CPU
get { return _constantRom; }
}
public static byte[] ACSourceROM
{
get { return _acSourceRom; }
}
private static void LoadConstants(RomFile[] romInfo)
{
_constantRom = new ushort[256];
@@ -47,7 +53,7 @@ namespace Contralto.CPU
// OR in the data
for (int i = 0; i < length; i++)
{
_constantRom[file.StartingAddress + i] |= (ushort)((DataMap(data[AddressMap(i)]) & 0xf) << file.BitPosition);
_constantRom[file.StartingAddress + i] |= (ushort)((DataMapConstantRom(data[AddressMapConstantRom(i)]) & 0xf) << file.BitPosition);
}
}
}
@@ -59,7 +65,29 @@ namespace Contralto.CPU
}
}
private static int AddressMap(int address)
private static void LoadACSource(RomFile romInfo)
{
_acSourceRom = new byte[256];
using (FileStream fs = new FileStream(Path.Combine("ROM", romInfo.Filename), FileMode.Open, FileAccess.Read))
{
int length = (int)fs.Length;
if (length != 256)
{
throw new InvalidOperationException("ROM file should be 256 bytes in length");
}
byte[] data = new byte[fs.Length];
fs.Read(data, 0, (int)fs.Length);
// Copy in the data, modifying the address as required.
for (int i = 0; i < length; i++)
{
_acSourceRom[i] = (byte)((~data[AddressMapACSourceRom(i)]) & 0xf);
}
}
}
private static int AddressMapConstantRom(int address)
{
// Descramble the address bits as they are in no sane order.
// (See 05a_AIM.pdf, pg. 5 (Page 9 of the orginal docs))
@@ -77,7 +105,7 @@ namespace Contralto.CPU
return mappedAddress;
}
private static int DataMap(int data)
private static int DataMapConstantRom(int data)
{
// Reverse bits 0-4.
int mappedData = 0;
@@ -93,6 +121,23 @@ namespace Contralto.CPU
return mappedData;
}
private static int AddressMapACSourceRom(int data)
{
// Reverse bits 0-7.
int mappedData = 0;
for (int i = 0; i < 8; i++)
{
if ((data & (1 << i)) != 0)
{
mappedData |= (1 << (7 - i));
}
}
// And invert.
return (~mappedData) & 0xff;
}
private static RomFile[] _constantRoms =
{
new RomFile("c0", 0x000, 12),
@@ -101,6 +146,9 @@ namespace Contralto.CPU
new RomFile("c3", 0x000, 0),
};
private static RomFile _acSourceRoms = new RomFile("2kctl.u3", 0x000, 0);
private static ushort[] _constantRom;
private static byte[] _acSourceRom;
}
}

View File

@@ -73,7 +73,7 @@ namespace Contralto.CPU
instruction.F2 == SpecialFunction2.Constant)
{
source += String.Format("C({0})",
OctalHelpers.ToOctal(ConstantMemory.ConstantROM[(instruction.RSELECT << 3) | ((uint)instruction.BS)]));
OctalHelpers.ToOctal(ControlROM.ConstantROM[(instruction.RSELECT << 3) | ((uint)instruction.BS)]));
}
switch (instruction.ALUF)

View File

@@ -96,7 +96,75 @@ namespace Contralto.CPU
case DiskF2.INIT:
// "NEXT<-NEXT OR (if WDTASKACT AND WDINIT) then 37B else 0
// TODO: figure out how WDTASKACT and WDINIT work.
throw new NotImplementedException("INIT not implemented.");
// From the US Patent (4148098):
// "..two multiplexers...allow the setting of the next field bits NEXT(05)-NEXT(09)
// to 1 after an error condition is detected and as soon as the word task active signal
// WDTASKACT is generated..."
// Is this always an error condition?
Console.WriteLine("Warning: assuming 0 for Disk F2 INIT (unimplemented stub)");
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;
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)
// TODO: need to get unshifted bit 14 of the command register. Disk controller should
// save this off somewhere.
if (_cpu._system.DiskController.DataXfer)
{
_nextModifier |= 0x1;
}
break;
case DiskF2.RECNO:
_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 |= 0x1;
break;
case DiskF2.STROBON:
// "NEXT <- NEXT OR (IF seek strobe still on THEN 1 ELSE 0)"
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)
break;
default:
throw new InvalidOperationException(String.Format("Unhandled disk special function 2 {0}", df2));

View File

@@ -192,54 +192,14 @@ namespace Contralto.CPU
// if IR[3-7] = 16B 1 CONVERT
// if IR[3-7] = 37B 17B ROMTRAP -- used by Swat, the debugger
// else 16B ROMTRAP
if ((_cpu._ir & 0x8000) != 0)
{
_nextModifier = (ushort)(3 - ((_cpu._ir & 0xc0) >> 6));
}
else if ((_cpu._ir & 0xc000) == 0xc000)
{
_nextModifier = (ushort)((_cpu._ir & 0x400) >> 10);
}
else if ((_cpu._ir & 0x1f00) == 0)
{
_nextModifier = 2;
}
else if ((_cpu._ir & 0x1f00) == 0x0100)
{
_nextModifier = 5;
}
else if ((_cpu._ir & 0x1f00) == 0x0200)
{
_nextModifier = 3;
}
else if ((_cpu._ir & 0x1f00) == 0x0300)
{
_nextModifier = 6;
}
else if ((_cpu._ir & 0x1f00) == 0x0400)
{
_nextModifier = 7;
}
else if ((_cpu._ir & 0x1f00) == 0x0900)
{
_nextModifier = 4;
}
else if ((_cpu._ir & 0x1f00) == 0x0a00)
{
_nextModifier = 4;
}
else if ((_cpu._ir & 0x1f00) == 0x0e00)
{
_nextModifier = 1;
}
else if ((_cpu._ir & 0x1f00) == 0x1f00)
{
_nextModifier = 0xf;
}
else
{
_nextModifier = 0xe;
}
//
// NOTE: the above table from the Hardware Manual is incorrect (or at least incomplete / misleading).
// There is considerably more that goes into determining the dispatch, which is controlled by a 256x8
// PROM. We just use the PROM rather than implementing the above logic (because it works.)
//
_nextModifier = ControlROM.ACSourceROM[(_cpu._ir & 0xff00) >> 8];
break;
case EmulatorF2.ACDEST:

View File

@@ -185,7 +185,7 @@ namespace Contralto.CPU
else
{
// See also comments below.
_busData = ConstantMemory.ConstantROM[(instruction.RSELECT << 3) | ((uint)instruction.BS)];
_busData = ControlROM.ConstantROM[(instruction.RSELECT << 3) | ((uint)instruction.BS)];
}
// Constant ROM access:
@@ -202,7 +202,7 @@ namespace Contralto.CPU
instruction.F1 == SpecialFunction1.Constant ||
instruction.F2 == SpecialFunction2.Constant)
{
_busData &= ConstantMemory.ConstantROM[(instruction.RSELECT << 3) | ((uint)instruction.BS)];
_busData &= ControlROM.ConstantROM[(instruction.RSELECT << 3) | ((uint)instruction.BS)];
}
// Do ALU operation

View File

@@ -60,6 +60,7 @@
<Compile Include="Debugger.Designer.cs">
<DependentUpon>Debugger.cs</DependentUpon>
</Compile>
<Compile Include="IClockable.cs" />
<Compile Include="IO\DiskController.cs" />
<Compile Include="IO\Keyboard.cs" />
<Compile Include="Memory\IMemoryMappedDevice.cs" />
@@ -75,6 +76,9 @@
<None Include="Disassembly\altoIIcode3.mu">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="ROM\2kctl.u3">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Include="ROM\C0">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
@@ -189,6 +193,9 @@
<DependentUpon>Debugger.cs</DependentUpon>
</EmbeddedResource>
</ItemGroup>
<ItemGroup>
<Content Include="Notes.txt" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.

View File

@@ -13,6 +13,9 @@ using System.Threading;
namespace Contralto
{
/// <summary>
/// A basic & hacky debugger. To be improved.
/// </summary>
public partial class Debugger : Form
{
public Debugger(AltoSystem system)
@@ -481,9 +484,12 @@ namespace Contralto
// Continuously execute, but do not update UI
// until the "Stop" button is pressed or something bad happens.
//
_execThread = new Thread(new System.Threading.ParameterizedThreadStart(ExecuteProc));
_execThread.Start(ExecutionType.Normal);
SetExecutionState(ExecutionState.Running);
//if (_execThread == null)
{
_execThread = new Thread(new System.Threading.ParameterizedThreadStart(ExecuteProc));
_execThread.Start(ExecutionType.Normal);
SetExecutionState(ExecutionState.Running);
}
}
private void OnStopButtonClicked(object sender, EventArgs e)

17
Contralto/IClockable.cs Normal file
View File

@@ -0,0 +1,17 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Contralto
{
/// <summary>
/// Used by classes implementing devices that are clocked (i.e. that are dependent
/// on time passing in units of a single CPU clock.)
/// </summary>
public interface IClockable
{
void Clock();
}
}

View File

@@ -8,11 +8,15 @@ using Contralto.Memory;
namespace Contralto.IO
{
public class DiskController
public class DiskController : IClockable
{
public DiskController(AltoSystem system)
{
_system = system;
Reset();
// Wakeup the sector task first thing
_system.CPU.WakeupTask(CPU.TaskType.DiskSector);
}
public ushort KDATA
@@ -31,6 +35,10 @@ namespace Contralto.IO
// "In addition, it causes the head address bit to be loaded from KDATA[13]."
_head = (_kData & 0x4) >> 2;
// "0 normally, 1 if the command is to terminate immediately after the correct cylinder
// position is reached (before any data is transferred)."
_dataXfer = (_kAdr & 0x2) != 0x2;
}
}
@@ -47,6 +55,11 @@ namespace Contralto.IO
_bClkSource = (_kCom & 0x04) == 0x04;
_wffo = (_kCom & 0x02) == 0x02;
_sendAdr = (_kCom & 0x01) == 0x01;
if (!_wdInhib && !_xferOff)
{
Console.WriteLine("enabled at sst {0}", _elapsedSectorStateTime);
}
}
}
@@ -61,6 +74,11 @@ namespace Contralto.IO
get { return _recMap[_recNo]; }
}
public bool DataXfer
{
get { return _dataXfer; }
}
public int Cylinder
{
get { return _cylinder; }
@@ -100,6 +118,9 @@ namespace Contralto.IO
_sector = 0;
_head = 0;
_kStat = 0;
_wdInhib = true;
_xferOff = true;
}
public void Clock()
@@ -119,7 +140,14 @@ namespace Contralto.IO
_kStat = (ushort)((_kStat & 0x0fff) | (_sector << 12));
// Reset internal state machine for sector data
_sectorState = SectorState.Leadin;
_sectorWordIndex = 0;
_elapsedSectorStateTime = 0.0;
Console.WriteLine("New sector ({0}), switching to LeadIn state.", _sector);
_system.CPU.WakeupTask(CPU.TaskType.DiskSector);
}
// If seek is in progress, move closer to the desired cylinder...
@@ -175,6 +203,120 @@ namespace Contralto.IO
// _kData = next word
// if (!_wdInhib) DiskSectorTask.Wakeup();
// }
_elapsedSectorStateTime++;
switch(_sectorState)
{
case SectorState.Leadin:
if (_elapsedSectorStateTime > _leadinDuration)
{
_elapsedSectorStateTime -= _leadinDuration;
_sectorState = SectorState.Header;
Console.WriteLine("Switching to Header state.");
}
break;
case SectorState.Header:
if (_sectorWordIndex > 1) // two words
{
_elapsedSectorStateTime -= 2.0 * _wordDuration;
_sectorState = SectorState.HeaderGap;
_sectorWordIndex = 0;
Console.WriteLine("Switching to HeaderGap state.");
}
else if (_elapsedSectorStateTime > _wordDuration)
{
_elapsedSectorStateTime -= _wordDuration;
// Put next word into KDATA if not inhibited from doing so.
if (!_xferOff)
{
_kData = 0xdead; // placeholder
Console.WriteLine(" Header word {0} is {1}", _sectorWordIndex, OctalHelpers.ToOctal(_kData));
}
_sectorWordIndex++;
if (!_wdInhib)
{
_system.CPU.WakeupTask(CPU.TaskType.DiskWord);
}
}
break;
case SectorState.HeaderGap:
if (_elapsedSectorStateTime > _headerGapDuration)
{
_elapsedSectorStateTime -= _headerGapDuration;
_sectorState = SectorState.Label;
Console.WriteLine("Switching to Label state.");
}
break;
case SectorState.Label:
if (_sectorWordIndex > 7) // eight words
{
_elapsedSectorStateTime -= 8.0 * _wordDuration;
_sectorState = SectorState.LabelGap;
_sectorWordIndex = 0;
Console.WriteLine("Switching to LabelGap state.");
}
else if(_elapsedSectorStateTime > _wordDuration)
{
_elapsedSectorStateTime -= _wordDuration;
// Put next word into KDATA if not inhibited from doing so.
if (!_xferOff)
{
_kData = 0xbeef; // placeholder
Console.WriteLine(" Label word {0} is {1}", _sectorWordIndex, OctalHelpers.ToOctal(_kData));
}
_sectorWordIndex++;
if (!_wdInhib)
{
_system.CPU.WakeupTask(CPU.TaskType.DiskWord);
}
}
break;
case SectorState.LabelGap:
if (_elapsedSectorStateTime > _labelGapDuration)
{
_elapsedSectorStateTime -= _labelGapDuration;
_sectorState = SectorState.Data;
Console.WriteLine("Switching to Data state.");
}
break;
case SectorState.Data:
if (_sectorWordIndex > 255) // 256 words
{
_elapsedSectorStateTime -= 256.0 * _wordDuration;
_sectorState = SectorState.Leadout;
_sectorWordIndex = 0;
Console.WriteLine("Switching to Leadout state.");
}
else if (_elapsedSectorStateTime > _wordDuration)
{
_elapsedSectorStateTime -= _wordDuration;
// Put next word into KDATA if not inhibited from doing so.
if (!_xferOff)
{
_kData = 0xda1a; // placeholder
Console.WriteLine(" Sector word {0} is {1}", _sectorWordIndex, OctalHelpers.ToOctal(_kData));
}
_sectorWordIndex++;
if (!_wdInhib)
{
_system.CPU.WakeupTask(CPU.TaskType.DiskWord);
}
}
break;
case SectorState.Leadout:
// Just stay here forever. We will get reset at the start of the next sector.
break;
}
}
public void ClearStatus()
@@ -271,6 +413,9 @@ namespace Contralto.IO
private bool _wffo;
private bool _sendAdr;
// Transfer bit
private bool _dataXfer;
// Current disk position
private int _cylinder;
private int _destCylinder;
@@ -280,7 +425,29 @@ namespace Contralto.IO
// Sector timing. Based on table on pg. 43 of the Alto Hardware Manual
private double _elapsedSectorTime; // elapsed time in this sector (in clocks)
private const double _sectorDuration = (40.0 / 12.0); // time in msec for one sector
private readonly double _sectorClocks = _sectorDuration / AltoSystem.ClockInterval; // number of clock cycles per sector time.
private const double _sectorClocks = _sectorDuration / 0.00017; // number of clock cycles per sector time.
// Sector data timing and associated state. Timings based on educated guesses at the moment.
private enum SectorState
{
Leadin = 0, // gap between sector mark and first Header word
Header, // Header; two words
HeaderGap, // gap between end of Header and first Label word
Label, // Label; 8 words
LabelGap, // gap betweeen the end of Label and first Data word
Data, // Data; 256 words
Leadout // gap between the end of Data and the next sector mark
}
private SectorState _sectorState;
private double _elapsedSectorStateTime;
private int _sectorWordIndex;
private const double _wordDuration = (_sectorClocks / (266.0 + 94.0)); // Based on : 266 words / sector, + 94 "words" for gaps (made up)
private const double _leadinDuration = (_wordDuration * 70.0);
private const double _headerGapDuration = (_wordDuration * 8.0);
private const double _labelGapDuration = (_wordDuration * 8.0);
private const double _leadoutDuration = (_wordDuration * 8.0);
// Cylinder seek timing. Again, see the manual.
// Timing varies based on how many cylinders are being traveled during a seek; see

View File

@@ -13,7 +13,7 @@ namespace Contralto.Memory
Store
}
public class MemoryBus
public class MemoryBus : IClockable
{
public MemoryBus()
{
@@ -243,11 +243,13 @@ namespace Contralto.Memory
else
{
throw new NotImplementedException(String.Format("Read from unimplemented memory-mapped I/O device at {0}.", OctalHelpers.ToOctal(address)));
//Console.WriteLine("Read from unimplemented memory-mapped I/O device at {0}.", OctalHelpers.ToOctal(address));
return 0;
}
}
/// <summary>
/// Dispatches writes to memory mapped hardware (RAM, I/O
/// Dispatches writes to memory mapped hardware (RAM, I/O)
/// </summary>
/// <param name="address"></param>
/// <param name="data"></param>
@@ -262,7 +264,8 @@ namespace Contralto.Memory
}
else
{
throw new NotImplementedException(String.Format("Write to unimplemented memory-mapped I/O device at {0}.", OctalHelpers.ToOctal(address)));
throw new NotImplementedException(String.Format("Write to unimplemented memory-mapped I/O device at {0}.", OctalHelpers.ToOctal(address)));
//Console.WriteLine("Write to unimplemented memory-mapped I/O device at {0}.", OctalHelpers.ToOctal(address));
}
}

5
Contralto/Notes.txt Normal file
View File

@@ -0,0 +1,5 @@
Current issue:
DCB @ 521 is set to 1 by emulator uCode; then clobbered once sector task runs (set to zero) so the disk controller never picks up a control
block and never does anything.

BIN
Contralto/ROM/2kctl.u3 Normal file

Binary file not shown.