1
0
mirror of https://github.com/livingcomputermuseum/ContrAlto.git synced 2026-01-17 00:23:24 +00:00

Fixed Trident drive select issues, corrected issue with extended memory bank registers. IFS now runs.

This commit is contained in:
Josh Dersch 2017-08-31 12:23:46 -07:00
parent 523a4bb27f
commit 38124350fb
12 changed files with 227 additions and 138 deletions

View File

@ -94,7 +94,7 @@ namespace Contralto
_displayController.DetachDisplay();
}
public void Shutdown()
public void Shutdown(bool commitDisks)
{
// Kill any host interface threads that are running.
if (_ethernetController.HostInterface != null)
@ -107,15 +107,18 @@ namespace Contralto
//
_audioDAC.Shutdown();
//
// Save disk contents
//
_diskController.CommitDisk(0);
_diskController.CommitDisk(1);
for (int i = 0; i < 8; i++)
if (commitDisks)
{
_tridentController.CommitDisk(i);
//
// Save disk contents
//
_diskController.CommitDisk(0);
_diskController.CommitDisk(1);
for (int i = 0; i < 8; i++)
{
_tridentController.CommitDisk(i);
}
}
}
@ -210,11 +213,11 @@ namespace Contralto
//
switch (Path.GetExtension(path).ToLowerInvariant())
{
case ".t80":
case ".dsk80":
geometry = DiskGeometry.TridentT80;
break;
case ".t300":
case ".dsk300":
geometry = DiskGeometry.TridentT300;
break;
@ -228,11 +231,11 @@ namespace Contralto
if (newImage)
{
newPack = FileBackedDiskPack.CreateEmpty(geometry, path);
newPack = InMemoryDiskPack.CreateEmpty(geometry, path);
}
else
{
newPack = FileBackedDiskPack.Load(geometry, path);
newPack = InMemoryDiskPack.Load(geometry, path);
}
_tridentController.Drives[drive].LoadPack(newPack);
@ -273,6 +276,11 @@ namespace Contralto
get { return _cpu; }
}
public Memory.Memory Memory
{
get { return _mem; }
}
public MemoryBus MemoryBus
{
get { return _memBus; }

View File

@ -190,6 +190,9 @@ namespace Contralto.CPU
Log.Write(LogComponent.CPU, "Silent Boot; microcode banks initialized to {0}", Conversion.ToOctal(_rmr));
UCodeMemory.LoadBanksFromRMR(_rmr);
// Booting / soft-reset of the Alto resets the XM bank registers to zero.
_system.Memory.SoftReset();
// Reset RMR after reset.
_rmr = 0xffff;

View File

@ -126,7 +126,14 @@ namespace Contralto.CPU
/// <returns>An InstructionCompletion indicating whether this instruction calls for a task switch or not.</returns>
public InstructionCompletion ExecuteNext()
{
MicroInstruction instruction = UCodeMemory.GetInstruction(_mpc, _taskType);
MicroInstruction instruction = UCodeMemory.GetInstruction(_mpc, _taskType);
/*
if (_taskType == TaskType.Emulator && UCodeMemory.GetBank(_taskType) == MicrocodeBank.RAM0)
{
Console.WriteLine("{0}: {1}", Conversion.ToOctal(_mpc), UCodeDisassembler.DisassembleInstruction(instruction, _taskType));
}*/
return ExecuteInstruction(instruction);
}
@ -313,12 +320,12 @@ namespace Contralto.CPU
// Do nothing. Well, that was easy.
break;
case SpecialFunction1.LoadMAR:
case SpecialFunction1.LoadMAR:
// Do MAR or XMAR reference based on whether F2 is MD<- (for Alto IIs), indicating an extended memory reference.
_cpu._system.MemoryBus.LoadMAR(
aluData,
_taskType,
_systemType == SystemType.AltoI ? false : instruction.F2 == SpecialFunction2.StoreMD);
aluData,
_taskType,
_systemType == SystemType.AltoI ? false : instruction.F2 == SpecialFunction2.StoreMD);
break;
case SpecialFunction1.Task:

View File

@ -70,15 +70,15 @@ namespace Contralto.CPU
break;
case BusSource.ReadMD:
source = "MD ";
source = "<-MD ";
break;
case BusSource.ReadMouse:
source = "MOUSE ";
source = "<-MOUSE ";
break;
case BusSource.ReadDisp:
source = "DISP ";
source = "<-DISP ";
break;
}
}
@ -164,11 +164,11 @@ namespace Contralto.CPU
if (instruction.F2 == SpecialFunction2.StoreMD)
{
f1 = "XMAR ";
f1 = "XMAR<- ";
}
else
{
f1 = "MAR ";
f1 = "MAR<- ";
}
break;
@ -181,15 +181,15 @@ namespace Contralto.CPU
break;
case SpecialFunction1.LLSH1:
f1 = "L LSH 1 ";
f1 = "<-L LSH 1 ";
break;
case SpecialFunction1.LRSH1:
f1 = "L RSH 1 ";
f1 = "<-L RSH 1 ";
break;
case SpecialFunction1.LLCY8:
f1 = "L LCY 8 ";
f1 = "<-L LCY 8 ";
break;
case SpecialFunction1.Constant:
@ -233,11 +233,11 @@ namespace Contralto.CPU
if ((task == TaskType.TridentInput || task == TaskType.TridentOutput) &&
instruction.BS == BusSource.None)
{
f2 = "MD KDTA ";
f2 = "MD<- KDTA ";
}
else if (instruction.F1 != SpecialFunction1.LoadMAR)
{
f2 = "MD ";
f2 = "MD<- ";
}
break;
@ -273,7 +273,7 @@ namespace Contralto.CPU
break;
}
load = String.Format("T {0}", loadTFromALU ? operation : source);
load = String.Format("T<- {0}", loadTFromALU ? operation : source);
}
// Load L (and M) from ALU
@ -282,24 +282,24 @@ namespace Contralto.CPU
if (string.IsNullOrEmpty(load))
{
// T not loaded at all, L loaded from ALU
load = String.Format("L {0}", operation);
load = String.Format("L<- {0}", operation);
}
else if (loadTFromALU)
{
// T loaded from ALU, L loaded from ALU
load = String.Format("L {0}", load);
load = String.Format("L<- {0}", load);
}
else
{
// T loaded from bus source, L loaded from ALU
load = String.Format("L {0}, {1}", operation, load);
load = String.Format("L<- {0}, {1}", operation, load);
}
}
// Do writeback to selected R register from shifter output
if (loadR)
{
load = String.Format("$R{0} {1}",
load = String.Format("$R{0}<- {1}",
Conversion.ToOctal((int)rSelect),
!string.IsNullOrEmpty(load) ? load : operation);
}
@ -309,12 +309,12 @@ namespace Contralto.CPU
{
if (string.IsNullOrEmpty(load))
{
load = String.Format("$S{0} M",
load = String.Format("$S{0}<- M",
Conversion.ToOctal((int)rSelect));
}
else
{
load = String.Format("$S{0} M, {1}",
load = String.Format("$S{0}<- M, {1}",
Conversion.ToOctal((int)rSelect),
load);
}
@ -448,10 +448,10 @@ namespace Contralto.CPU
return "RDRAM ";
case EmulatorF1.LoadRMR:
return "RMR ";
return "RMR<- ";
case EmulatorF1.LoadESRB:
return "ESRB ";
return "ESRB<- ";
case EmulatorF1.RSNF:
return "RSNF ";
@ -481,13 +481,13 @@ namespace Contralto.CPU
return "MAGIC ";
case EmulatorF2.LoadDNS:
return "DNS ";
return "DNS<- ";
case EmulatorF2.BUSODD:
return "BUSODD ";
case EmulatorF2.LoadIR:
return "IR ";
return "IR<- ";
case EmulatorF2.IDISP:
return "IDISP ";
@ -507,13 +507,13 @@ namespace Contralto.CPU
return "OrbitBlock ";
case OrbitF1.OrbitDeltaWC:
return "OrbitDeltaWC ";
return "<-OrbitDeltaWC ";
case OrbitF1.OrbitDBCWidthRead:
return "OrbitDBCWidthRead ";
return "<-OrbitDBCWidthRead ";
case OrbitF1.OrbitStatus:
return "OrbitStatus ";
return "<-OrbitStatus ";
default:
return String.Format("Orbit F1 {0}", Conversion.ToOctal((int)of1));
@ -527,25 +527,25 @@ namespace Contralto.CPU
switch (of2)
{
case OrbitF2.OrbitDBCWidthSet:
return "OrbitDBCWidthSet ";
return "OrbitDBCWidthSet<- ";
case OrbitF2.OrbitXY:
return "OrbitXY ";
return "OrbitXY<- ";
case OrbitF2.OrbitHeight:
return "OrbitHeight ";
return "OrbitHeight<- ";
case OrbitF2.OrbitFontData:
return "OrbitFontData ";
return "OrbitFontData<- ";
case OrbitF2.OrbitInk:
return "OrbitInk ";
return "OrbitInk<- ";
case OrbitF2.OrbitControl:
return "OrbitControl ";
return "OrbitControl<- ";
case OrbitF2.OrbitROSCommand:
return "OrbitROSCommand ";
return "OrbitROSCommand<- ";
default:
return String.Format("Orbit F2 {0}", Conversion.ToOctal((int)of2));
@ -562,10 +562,10 @@ namespace Contralto.CPU
return "EMPTY ";
case TridentF2.KTAG:
return "KTAG ";
return "KTAG<- ";
case TridentF2.ReadKDTA:
return "KDTA ";
return "<-KDTA ";
case TridentF2.RESET:
return "RESET ";
@ -578,7 +578,7 @@ namespace Contralto.CPU
return "WAIT ";
case TridentF2.WriteKDTA:
return "KDTA ";
return "KDTA<- ";
default:
return String.Format("Trident F2 {0}", Conversion.ToOctal((int)tf2));

View File

@ -134,6 +134,11 @@ namespace Contralto
//
// If configuration specifies fewer than 8 Trident disks, we need to pad the list out to 8 with empty entries.
//
if (TridentImages == null)
{
TridentImages = new StringCollection();
}
int start = TridentImages.Count;
for (int i = start; i < 8; i++)
{

View File

@ -447,7 +447,7 @@ namespace Contralto.IO
for (int head = 0; head < _geometry.Heads; head++)
{
for (int sector = 0; sector < _geometry.Sectors; sector++)
{
{
_sectors[cylinder, head, sector].WriteToStream(imageStream);
}
}
@ -601,9 +601,8 @@ namespace Contralto.IO
private long GetOffsetForSector(int cylinder, int head, int sector)
{
int sectorNumber = (cylinder * _geometry.Heads * _geometry.Sectors) +
(head * _geometry.Sectors) +
sector;
int sectorNumber = (cylinder * _geometry.Heads + head) * _geometry.Sectors + // total sectors for current track
sector; // + current sector
return sectorNumber * _geometry.SectorGeometry.GetSectorSizeBytes();
}

View File

@ -38,6 +38,11 @@ namespace Contralto.IO
{
_system = system;
//
// We initialize 16 drives even though the
// controller only technically supports 8.
// TODO: detail
//
_drives = new TridentDrive[16];
for(int i=0;i<_drives.Length;i++)
@ -55,9 +60,7 @@ namespace Contralto.IO
_seekIncomplete = false;
_headOverflow = false;
_deviceCheck = false;
_notSelected = false;
_notOnline = false;
_notReady = false;
_notSelected = false;
_sectorOverflow = false;
_outputLate = false;
_inputLate = false;
@ -79,7 +82,6 @@ namespace Contralto.IO
_sectorEvent = new Event(0, null, SectorCallback);
_outputFifoEvent = new Event(0, null, OutputFifoCallback);
_seekEvent = new Event(0, null, SeekCallback);
_readWordEvent = new Event(0, null, ReadWordCallback);
// And schedule the first sector pulse.
@ -95,8 +97,6 @@ namespace Contralto.IO
_headOverflow = false;
_deviceCheck = false;
_notSelected = false;
_notOnline = false;
_notReady = false;
_sectorOverflow = false;
_outputLate = false;
_inputLate = false;
@ -249,7 +249,7 @@ namespace Contralto.IO
(_headOverflow ? 0x4000 : 0) |
(_deviceCheck ? 0x2000 : 0) |
(_notSelected ? 0x1000 : 0) |
(_notOnline ? 0x0800 : 0) |
(NotOnline() ? 0x0800 : 0) |
(NotReady() ? 0x0400 : 0) |
(_sectorOverflow ? 0x0200 : 0) |
(_outputLate ? 0x0100 : 0) |
@ -287,7 +287,14 @@ namespace Contralto.IO
private bool NotReady()
{
return _notReady | !SelectedDrive.IsLoaded;
return SelectedDrive.NotReady |
!SelectedDrive.IsLoaded |
_selectedDrive > 7;
}
private bool NotOnline()
{
return _selectedDrive < 8 ? !SelectedDrive.IsLoaded : true;
}
private void WriteOutputFifo(int value)
@ -550,7 +557,21 @@ namespace Contralto.IO
break;
case 3: // Set Drive
// We take all four drive-select bits even though only 8 drives are actually supported.
// The high bit is used by many trident utilities to select an invalid drive to test for
// the presence of the 8-drive multiplexer.
_selectedDrive = fifoWord & 0xf;
if (_selectedDrive > 7)
{
_system.CPU.BlockTask(TaskType.TridentOutput);
_notSelected = true;
}
else
{
_notSelected = false;
}
Log.Write(LogComponent.TridentController, "Command is Set Drive {0}", _selectedDrive);
break;
}
@ -636,48 +657,7 @@ namespace Contralto.IO
private void InitSeek(int destCylinder)
{
if (destCylinder > SelectedDrive.Pack.Geometry.Cylinders - 1)
{
Log.Write(LogType.Error, LogComponent.TridentController, "Specified cylinder {0} is out of range. Seek aborted.", destCylinder);
_deviceCheck = true;
return;
}
int currentCylinder = SelectedDrive.Cylinder;
if (destCylinder != currentCylinder)
{
// Do a seek.
_notReady = true;
_destCylinder = destCylinder;
//
// I can't find a specific formula for seek timing; the Century manual says:
// "Positioning time for seeking to the next cylinder is normally 6ms, and
// for full seeks (814 cylinder differerence) it is 55ms."
//
// I'm just going to fudge this for now and assume a linear ramp; this is not
// accurate but it's not all that important.
//
_seekDuration = (ulong)((6.0 + 0.602 * Math.Abs(currentCylinder - destCylinder)) * Conversion.MsecToNsec);
Log.Write(LogComponent.TridentController, "Commencing seek from {0} to {1}. Seek time is {2}ns", destCylinder, currentCylinder, _seekDuration);
_seekEvent.TimestampNsec = _seekDuration;
_system.Scheduler.Schedule(_seekEvent);
}
else
{
Log.Write(LogComponent.TridentController, "Seek is a no-op.");
}
}
private void SeekCallback(ulong timeNsec, ulong skewNsec, object context)
{
Log.Write(LogComponent.TridentDisk, "Seek to {0} complete.", _destCylinder);
SelectedDrive.Cylinder = _destCylinder;
_notReady = false;
_deviceCheck = !SelectedDrive.Seek(destCylinder);
}
private void InitRead()
@ -786,7 +766,7 @@ namespace Contralto.IO
}
else
{
Log.Write(LogComponent.TridentController, "CHS {0}/{1}/{2} {3} read complete.", SelectedDrive.Cylinder, SelectedDrive.Head, _sector, _sectorBlock);
Log.Write(LogComponent.TridentController, "CHS {0}/{1}/{2} {3} read from drive {4} complete.", SelectedDrive.Cylinder, SelectedDrive.Head, _sector, _sectorBlock, _selectedDrive);
_readState = ReadState.Idle;
_sectorBlock++;
@ -847,7 +827,7 @@ namespace Contralto.IO
_writeWordCount--;
if (_writeWordCount <= 0)
{
Log.Write(LogComponent.TridentController, "CHS {0}/{1}/{2} {3} write complete. {4} words written.", SelectedDrive.Cylinder, SelectedDrive.Head, _sector, _sectorBlock, _writeIndex);
Console.WriteLine( "CHS {0}/{1}/{2} {3} write to drive {4} complete. {5} words written.", SelectedDrive.Cylinder, SelectedDrive.Head, _sector, _sectorBlock, _selectedDrive, _writeIndex);
_writeState = WriteState.Idle;
_writeIndex = 0;
@ -857,19 +837,24 @@ namespace Contralto.IO
}
private void SectorCallback(ulong timeNsec, ulong skewNsec, object context)
{
{
// Move to the next sector.
_sector = (_sector + 1) % 9;
SelectedDrive.Sector = _sector;
// Reset to the first block (header) in the sector.
_sectorBlock = SectorBlock.Header;
// Wake up the Output task if RunEnable is true and the drive is ready.
if (_runEnable && !NotReady())
{
_sector = (_sector + 1) % 9;
SelectedDrive.Sector = _sector;
// Reset to the first block (header) in the sector.
_sectorBlock = SectorBlock.Header;
// Wake up the Output task
_system.CPU.WakeupTask(TaskType.TridentOutput);
}
else
{
// Keep the output task asleep.
_system.CPU.BlockTask(TaskType.TridentOutput);
}
//
// Schedule the next sector pulse
@ -894,6 +879,7 @@ namespace Contralto.IO
{
get { return _drives[_selectedDrive]; }
}
//
// Input FIFO semantics
@ -1033,9 +1019,7 @@ namespace Contralto.IO
private bool _seekIncomplete;
private bool _headOverflow;
private bool _deviceCheck;
private bool _notSelected;
private bool _notOnline;
private bool _notReady;
private bool _notSelected;
private bool _sectorOverflow;
private bool _outputLate;
private bool _inputLate;
@ -1044,20 +1028,13 @@ namespace Contralto.IO
private bool _offset;
// Current sector
private int _sector;
private int _sector;
/// <summary>
/// Drive timings
/// </summary>
private static ulong _sectorDuration = (ulong)((16.66666 / 9.0) * Conversion.MsecToNsec); // time in nsec for one sector -- 9 sectors, 16.66ms per rotation
private Event _sectorEvent;
//
// Seek timings
//
private static ulong _seekDuration = (6 * Conversion.MsecToNsec);
private Event _seekEvent;
private int _destCylinder;
private Event _sectorEvent;
// Output FIFO. This is actually 17-bits wide (the 17th bit is the Tag bit).
private Queue<int> _outputFifo;

View File

@ -29,6 +29,8 @@ namespace Contralto.IO
public TridentDrive(AltoSystem system)
{
_system = system;
_seekEvent = new Event(0, null, SeekCallback);
Reset();
}
@ -38,6 +40,8 @@ namespace Contralto.IO
_cylinder = 0;
_head = 0;
_notReady = false;
UpdateTrackCache();
}
@ -82,6 +86,11 @@ namespace Contralto.IO
get { return _pack != null; }
}
public bool NotReady
{
get { return _notReady; }
}
public IDiskPack Pack
{
get { return _pack; }
@ -124,6 +133,45 @@ namespace Contralto.IO
get { return false; }
}
public bool Seek(int destCylinder)
{
if (destCylinder > _pack.Geometry.Cylinders - 1)
{
Log.Write(LogType.Error, LogComponent.TridentController, "Specified cylinder {0} is out of range. Seek aborted, device check raised.", destCylinder);
return false;
}
int currentCylinder = _cylinder;
if (destCylinder != currentCylinder)
{
// Do a seek.
_notReady = true;
_destCylinder = destCylinder;
//
// I can't find a specific formula for seek timing; the Century manual says:
// "Positioning time for seeking to the next cylinder is normally 6ms, and
// for full seeks (814 cylinder differerence) it is 55ms."
//
// I'm just going to fudge this for now and assume a linear ramp; this is not
// accurate but it's not all that important.
//
_seekDuration = (ulong)((6.0 + 0.602 * Math.Abs(currentCylinder - destCylinder)) * Conversion.MsecToNsec);
Log.Write(LogComponent.TridentController, "Commencing seek from {0} to {1}. Seek time is {2}ns", destCylinder, currentCylinder, _seekDuration);
_seekEvent.TimestampNsec = _seekDuration;
_system.Scheduler.Schedule(_seekEvent);
}
else
{
Log.Write(LogComponent.TridentController, "Seek is a no-op.");
}
return true;
}
public ushort ReadHeader(int wordOffset)
{
if (wordOffset >= SectorGeometry.Trident.HeaderSize)
@ -223,6 +271,14 @@ namespace Contralto.IO
}
}
private void SeekCallback(ulong timeNsec, ulong skewNsec, object context)
{
Log.Write(LogComponent.TridentDisk, "Seek to {0} complete.", _destCylinder);
Cylinder = _destCylinder;
_notReady = false;
}
//
// Sector management. We load in an entire track's worth of sectors at a time.
// When the head or cylinder changes, UpdateTrackCache must be called.
@ -276,6 +332,15 @@ namespace Contralto.IO
// The pack loaded into the drive
IDiskPack _pack;
// Drive status
private bool _notReady;
// Seek status and control
private static ulong _seekDuration;
private Event _seekEvent;
private int _destCylinder;
//
// The track cache
//

View File

@ -56,24 +56,42 @@ namespace Contralto.Memory
get { return _memTop; }
}
/// <summary>
/// Full reset, clears all memory.
/// </summary>
public void Reset()
{
// 4 64K banks, regardless of system type. (Alto Is just won't use the extra memory.)
_mem = new ushort[0x40000];
_xmBanks = new ushort[16];
_xmBanksAlternate = new int[16];
_xmBanksNormal = new int[16];
}
/// <summary>
/// Soft reset, clears XM bank registers.
/// </summary>
public void SoftReset()
{
_xmBanks = new ushort[16];
_xmBanksAlternate = new int[16];
_xmBanksNormal = new int[16];
}
public ushort Read(int address, TaskType task, bool extendedMemory)
{
// Check for XM registers; this occurs regardless of XM flag since it's in the I/O page.
if (address >= _xmBanksStart && address < _xmBanksStart + 16)
{
{
// NB: While not specified in documentatino, some code (IFS in particular) relies on the fact that
// the upper 12 bits of the bank registers are all 1s.
return (ushort)(0xfff0 | _xmBanks[address - _xmBanksStart]);
}
else
{
address += 0x10000 * GetBankNumber(task, extendedMemory);
return _mem[address];
return _mem[address];
}
}
@ -81,17 +99,22 @@ namespace Contralto.Memory
{
// Check for XM registers; this occurs regardless of XM flag since it's in the I/O page.
if (address >= _xmBanksStart && address < _xmBanksStart + 16)
{
{
_xmBanks[address - _xmBanksStart] = data;
// Precalc bank numbers to speed memory accesses that use them
_xmBanksAlternate[address - _xmBanksStart] = data & 0x3;
_xmBanksNormal[address - _xmBanksStart] = (data & 0xc) >> 2;
Log.Write(LogComponent.Memory, "XM register for task {0} set to bank {1} (normal), {2} (xm)",
(TaskType)(address - _xmBanksStart),
(data & 0xc) >> 2,
(data & 0x3));
(data & 0x3));
}
else
{
address += 0x10000 * GetBankNumber(task, extendedMemory);
_mem[address] = data;
_mem[address] = data;
}
}
@ -102,7 +125,7 @@ namespace Contralto.Memory
private int GetBankNumber(TaskType task, bool extendedMemory)
{
return extendedMemory ? (_xmBanks[(int)task]) & 0x3 : ((_xmBanks[(int)task]) & 0xc) >> 2;
return extendedMemory ? _xmBanksAlternate[(int)task] : _xmBanksNormal[(int)task];
}
private readonly MemoryRange[] _addresses;
@ -112,6 +135,8 @@ namespace Contralto.Memory
private ushort[] _mem;
private ushort[] _xmBanks;
private ushort[] _xmBanks;
private int[] _xmBanksAlternate;
private int[] _xmBanksNormal;
}
}

View File

@ -128,7 +128,7 @@ namespace Contralto
{
Console.WriteLine("Exiting...");
_system.Shutdown();
_system.Shutdown(false /* don't commit disks */);
//
// Commit current configuration to disk

View File

@ -87,7 +87,7 @@ namespace Contralto.SdlUI
//
_controller.StopExecution();
_system.Shutdown();
_system.Shutdown(true /* commit disk contents */);
//
// The Alto window was closed, shut down the CLI.

View File

@ -408,7 +408,7 @@ namespace Contralto
// Halt the system and detach our display
_controller.StopExecution();
_system.DetachDisplay();
_system.Shutdown();
_system.Shutdown(true /* commit disk contents */);
//
// Commit current configuration to disk