mirror of
https://github.com/livingcomputermuseum/ContrAlto.git
synced 2026-01-11 23:52:43 +00:00
300 lines
9.2 KiB
C#
300 lines
9.2 KiB
C#
/*
|
|
This file is part of ContrAlto.
|
|
|
|
ContrAlto is free software: you can redistribute it and/or modify
|
|
it under the terms of the GNU Affero General Public License as published by
|
|
the Free Software Foundation, either version 3 of the License, or
|
|
(at your option) any later version.
|
|
|
|
ContrAlto is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU Affero General Public License for more details.
|
|
|
|
You should have received a copy of the GNU Affero General Public License
|
|
along with ContrAlto. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
using System.Timers;
|
|
|
|
using Contralto.Logging;
|
|
|
|
namespace Contralto.CPU
|
|
{
|
|
public enum TaskType
|
|
{
|
|
Invalid = -1,
|
|
Emulator = 0,
|
|
DiskSector = 4,
|
|
Ethernet = 7,
|
|
MemoryRefresh = 8,
|
|
DisplayWord = 9,
|
|
Cursor = 10,
|
|
DisplayHorizontal = 11,
|
|
DisplayVertical = 12,
|
|
Parity = 13,
|
|
DiskWord = 14,
|
|
}
|
|
|
|
public partial class AltoCPU : IClockable
|
|
{
|
|
public AltoCPU(AltoSystem system)
|
|
{
|
|
_system = system;
|
|
|
|
_tasks[(int)TaskType.Emulator] = new EmulatorTask(this);
|
|
_tasks[(int)TaskType.DiskSector] = new DiskTask(this, true);
|
|
_tasks[(int)TaskType.DiskWord] = new DiskTask(this, false);
|
|
_tasks[(int)TaskType.DisplayWord] = new DisplayWordTask(this);
|
|
_tasks[(int)TaskType.DisplayHorizontal] = new DisplayHorizontalTask(this);
|
|
_tasks[(int)TaskType.DisplayVertical] = new DisplayVerticalTask(this);
|
|
_tasks[(int)TaskType.Cursor] = new CursorTask(this);
|
|
_tasks[(int)TaskType.MemoryRefresh] = new MemoryRefreshTask(this);
|
|
_tasks[(int)TaskType.Ethernet] = new EthernetTask(this);
|
|
_tasks[(int)TaskType.Parity] = new ParityTask(this);
|
|
|
|
Reset();
|
|
}
|
|
|
|
public Task[] Tasks
|
|
{
|
|
get { return _tasks; }
|
|
}
|
|
|
|
public Task CurrentTask
|
|
{
|
|
get { return _currentTask; }
|
|
}
|
|
|
|
public ushort[] R
|
|
{
|
|
get { return _r; }
|
|
}
|
|
|
|
public ushort[][] S
|
|
{
|
|
get { return _s; }
|
|
}
|
|
|
|
public ushort T
|
|
{
|
|
get { return _t; }
|
|
}
|
|
|
|
public ushort L
|
|
{
|
|
get { return _l; }
|
|
}
|
|
|
|
public ushort M
|
|
{
|
|
get { return _m; }
|
|
}
|
|
|
|
public ushort IR
|
|
{
|
|
get { return _ir; }
|
|
}
|
|
|
|
public ushort ALUC0
|
|
{
|
|
get { return _aluC0; }
|
|
}
|
|
|
|
|
|
public void Reset()
|
|
{
|
|
// Reset registers
|
|
_r = new ushort[32];
|
|
_s = new ushort[8][];
|
|
|
|
for(int i=0;i<_s.Length;i++)
|
|
{
|
|
_s[i] = new ushort[32];
|
|
}
|
|
|
|
_t = 0;
|
|
_l = 0;
|
|
_m = 0;
|
|
_ir = 0;
|
|
_aluC0 = 0;
|
|
_rmr = 0xffff; // Start all tasks in ROM0
|
|
|
|
// Reset tasks.
|
|
for (int i=0;i<_tasks.Length;i++)
|
|
{
|
|
if (_tasks[i] != null)
|
|
{
|
|
_tasks[i].Reset();
|
|
}
|
|
}
|
|
|
|
// Execute the initial task switch.
|
|
TaskSwitch();
|
|
|
|
_currentTask = _nextTask;
|
|
_nextTask = null;
|
|
}
|
|
|
|
public void Clock()
|
|
{
|
|
switch (_currentTask.ExecuteNext())
|
|
{
|
|
case InstructionCompletion.TaskSwitch:
|
|
// Invoke the task switch, this will take effect after
|
|
// the NEXT instruction completes, not this one.
|
|
TaskSwitch();
|
|
break;
|
|
|
|
case InstructionCompletion.Normal:
|
|
if (_nextTask != null)
|
|
{
|
|
// If we have a new task, switch to it now.
|
|
_currentTask = _nextTask;
|
|
_nextTask = null;
|
|
_currentTask.OnTaskSwitch();
|
|
}
|
|
break;
|
|
|
|
case InstructionCompletion.MemoryWait:
|
|
// We were waiting for memory on this cycle, we do nothing
|
|
// (no task switch even if one is pending) in this case.
|
|
break;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// "Silent Boot": (See Section 9.2.2)
|
|
/// Used when a BOOT STARTF is invoked; resets task MPCs and sets
|
|
/// the starting bank (RAM0 or ROM0) appropriately based on the contents
|
|
/// of RMR.
|
|
/// All other register contents are left as-is.
|
|
/// </summary>
|
|
public void SoftReset()
|
|
{
|
|
// Soft-Reset tasks.
|
|
for (int i = 0; i < _tasks.Length; i++)
|
|
{
|
|
if (_tasks[i] != null)
|
|
{
|
|
_tasks[i].SoftReset();
|
|
}
|
|
}
|
|
|
|
Log.Write(LogComponent.CPU, "Silent Boot; microcode banks initialized to {0}", Conversion.ToOctal(_rmr));
|
|
UCodeMemory.LoadBanksFromRMR(_rmr);
|
|
|
|
// Reset RMR after reset.
|
|
_rmr = 0x0;
|
|
|
|
// Start in Emulator
|
|
_currentTask = _tasks[0];
|
|
|
|
//
|
|
// TODO:
|
|
// This is a hack of sorts, it ensures that the sector task initializes
|
|
// itself as soon as the Emulator task yields after the reset. (CopyDisk is broken otherwise due to the
|
|
// sector task stomping over the KBLK CopyDisk sets up after the reset. This is a race of sorts.)
|
|
// Unsure if there is a deeper issue here or if there are other reset semantics
|
|
// in play that are not understood.
|
|
//
|
|
WakeupTask(CPU.TaskType.DiskSector);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Used by hardware devices to cause a specific task to have its
|
|
/// "wakeup" signal triggered
|
|
/// </summary>
|
|
/// <param name="task"></param>
|
|
public void WakeupTask(TaskType task)
|
|
{
|
|
if (_tasks[(int)task] != null)
|
|
{
|
|
// Log.Write(LogComponent.TaskSwitch, "Wakeup enabled for Task {0}", task);
|
|
_tasks[(int)task].WakeupTask();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Used by hardware devices to cause a specific task to have its
|
|
/// "wakeup" signal cleared
|
|
/// </summary>
|
|
/// <param name="task"></param>
|
|
public void BlockTask(TaskType task)
|
|
{
|
|
if (_tasks[(int)task] != null)
|
|
{
|
|
// Log.Write(LogComponent.TaskSwitch, "Removed wakeup for Task {0}", task);
|
|
_tasks[(int)task].BlockTask();
|
|
}
|
|
}
|
|
|
|
public bool IsBlocked(TaskType task)
|
|
{
|
|
if (_tasks[(int)task] != null)
|
|
{
|
|
return _tasks[(int)task].Wakeup;
|
|
}
|
|
else
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Used by the debugger to determine if a task switch is taking
|
|
/// place.
|
|
/// </summary>
|
|
public Task NextTask
|
|
{
|
|
get { return _nextTask; }
|
|
}
|
|
|
|
private void TaskSwitch()
|
|
{
|
|
// Select the highest-priority eligible task
|
|
for (int i = _tasks.Length - 1; i >= 0; i--)
|
|
{
|
|
if (_tasks[i] != null && _tasks[i].Wakeup)
|
|
{
|
|
_nextTask = _tasks[i];
|
|
_nextTask.FirstInstructionAfterSwitch = true;
|
|
|
|
/*
|
|
if (_nextTask != _currentTask && _currentTask != null)
|
|
{
|
|
Log.Write(LogComponent.TaskSwitch, "TASK: Next task will be {0} (pri {1}); current task {2} (pri {3})",
|
|
(TaskType)_nextTask.Priority, _nextTask.Priority,
|
|
(TaskType)_currentTask.Priority, _currentTask.Priority);
|
|
} */
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// AltoCPU registers
|
|
private ushort _t;
|
|
private ushort _l;
|
|
private ushort _m;
|
|
private ushort _ir;
|
|
|
|
// R and S register files and bank select
|
|
private ushort[] _r;
|
|
private ushort[][] _s;
|
|
|
|
// Stores the last carry from the ALU on a Load L
|
|
private ushort _aluC0;
|
|
|
|
// RMR (Reset Mode Register)
|
|
ushort _rmr;
|
|
|
|
// Task data
|
|
private Task _nextTask; // The task to switch two after the next microinstruction
|
|
private Task _currentTask; // The currently executing task
|
|
private Task[] _tasks = new Task[16];
|
|
|
|
// The system this CPU belongs to
|
|
private AltoSystem _system;
|
|
}
|
|
}
|