mirror of
https://github.com/livingcomputermuseum/ContrAlto.git
synced 2026-01-24 19:31:26 +00:00
Work begun on Disk controller, stubs for keyboard and a few bugfixes and tweaks.
This commit is contained in:
parent
0ced1a2ef8
commit
24d7a5a8fe
@ -5,6 +5,7 @@ using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using Contralto.CPU;
|
||||
using Contralto.IO;
|
||||
using Contralto.Memory;
|
||||
|
||||
namespace Contralto
|
||||
@ -18,19 +19,28 @@ namespace Contralto
|
||||
public AltoSystem()
|
||||
{
|
||||
_cpu = new AltoCPU(this);
|
||||
_mem = new MemoryBus();
|
||||
_memBus = new MemoryBus();
|
||||
_mem = new Memory.Memory();
|
||||
_keyboard = new Keyboard();
|
||||
_diskController = new DiskController(this);
|
||||
|
||||
// Attach memory-mapped devices to the bus
|
||||
_memBus.AddDevice(_mem);
|
||||
_memBus.AddDevice(_keyboard);
|
||||
|
||||
Reset();
|
||||
}
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
_cpu.Reset();
|
||||
_mem.Reset();
|
||||
_memBus.Reset();
|
||||
}
|
||||
|
||||
public void SingleStep()
|
||||
{
|
||||
_mem.Clock();
|
||||
{
|
||||
_memBus.Clock();
|
||||
_diskController.Clock();
|
||||
_cpu.ExecuteNext();
|
||||
}
|
||||
|
||||
@ -41,10 +51,26 @@ namespace Contralto
|
||||
|
||||
public MemoryBus MemoryBus
|
||||
{
|
||||
get { return _mem; }
|
||||
get { return _memBus; }
|
||||
}
|
||||
|
||||
public DiskController DiskController
|
||||
{
|
||||
get { return _diskController; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Time (in msec) for one system clock
|
||||
/// </summary>
|
||||
public static double ClockInterval
|
||||
{
|
||||
get { return 0.00017; } // appx 170nsec, TODO: more accurate value?
|
||||
}
|
||||
|
||||
private AltoCPU _cpu;
|
||||
private MemoryBus _mem;
|
||||
private MemoryBus _memBus;
|
||||
private Contralto.Memory.Memory _mem;
|
||||
private Keyboard _keyboard;
|
||||
private DiskController _diskController;
|
||||
}
|
||||
}
|
||||
|
||||
@ -27,7 +27,7 @@ namespace Contralto.CPU
|
||||
get { return _carry; }
|
||||
}
|
||||
|
||||
public static ushort Execute(AluFunction fn, ushort bus, ushort t)
|
||||
public static ushort Execute(AluFunction fn, ushort bus, ushort t, int skip)
|
||||
{
|
||||
int r = 0;
|
||||
switch (fn)
|
||||
@ -89,7 +89,9 @@ namespace Contralto.CPU
|
||||
break;
|
||||
|
||||
case AluFunction.BusPlusSkip:
|
||||
throw new NotImplementedException("SKIP?");
|
||||
r = bus + skip;
|
||||
_carry = (r > 0xffff) ? 1 : 0;
|
||||
break;
|
||||
|
||||
case AluFunction.BusAndNotT:
|
||||
r = bus & (~t);
|
||||
|
||||
@ -140,11 +140,11 @@ namespace Contralto.CPU
|
||||
/// "wakeup" signal triggered
|
||||
/// </summary>
|
||||
/// <param name="task"></param>
|
||||
public void WakeupTask(int task)
|
||||
public void WakeupTask(TaskType task)
|
||||
{
|
||||
if (_tasks[task] != null)
|
||||
if (_tasks[(int)task] != null)
|
||||
{
|
||||
_tasks[task].WakeupTask();
|
||||
_tasks[(int)task].WakeupTask();
|
||||
}
|
||||
}
|
||||
|
||||
@ -153,11 +153,11 @@ namespace Contralto.CPU
|
||||
/// "wakeup" signal cleared
|
||||
/// </summary>
|
||||
/// <param name="task"></param>
|
||||
public void BlockTask(int task)
|
||||
public void BlockTask(TaskType task)
|
||||
{
|
||||
if (_tasks[task] != null)
|
||||
if (_tasks[(int)task] != null)
|
||||
{
|
||||
_tasks[task].BlockTask();
|
||||
_tasks[(int)task].BlockTask();
|
||||
}
|
||||
}
|
||||
|
||||
@ -183,13 +183,13 @@ namespace Contralto.CPU
|
||||
{
|
||||
_wakeup = false;
|
||||
_mpc = 0xffff; // invalid, for sanity checking
|
||||
_priority = -1; // invalid
|
||||
_taskType = TaskType.Invalid;
|
||||
_cpu = cpu;
|
||||
}
|
||||
|
||||
public int Priority
|
||||
{
|
||||
get { return _priority; }
|
||||
get { return (int)_taskType; }
|
||||
}
|
||||
|
||||
public bool Wakeup
|
||||
@ -207,12 +207,11 @@ namespace Contralto.CPU
|
||||
// From The Alto Hardware Manual (section 2, "Initialization"):
|
||||
// "...each task start[s] at the location which is its task number"
|
||||
//
|
||||
_mpc = (ushort)_priority;
|
||||
_mpc = (ushort)_taskType;
|
||||
}
|
||||
|
||||
public virtual void BlockTask()
|
||||
{
|
||||
// Used only by hardware interfaces, where applicable
|
||||
{
|
||||
_wakeup = false;
|
||||
}
|
||||
|
||||
@ -370,7 +369,7 @@ namespace Contralto.CPU
|
||||
}
|
||||
|
||||
// Do ALU operation
|
||||
aluData = ALU.Execute(instruction.ALUF, _busData, _cpu._t);
|
||||
aluData = ALU.Execute(instruction.ALUF, _busData, _cpu._t, _skip);
|
||||
|
||||
// Reset shifter op
|
||||
Shifter.SetOperation(ShifterOp.None, 0);
|
||||
@ -393,8 +392,10 @@ namespace Contralto.CPU
|
||||
break;
|
||||
|
||||
case SpecialFunction1.Block:
|
||||
// "...this function is reserved by convention only; it is *not* done by the microprocessor"
|
||||
throw new InvalidOperationException("BLOCK should never be invoked by microcode.");
|
||||
// Technically this is to be invoked by the hardware device associated with a task.
|
||||
// That logic would be circituous and unless there's a good reason not to that is discovered
|
||||
// later, I'm just going to directly block the current task here.
|
||||
_cpu.BlockTask(this._taskType);
|
||||
break;
|
||||
|
||||
case SpecialFunction1.LLSH1:
|
||||
@ -505,16 +506,6 @@ namespace Contralto.CPU
|
||||
_cpu._t = loadTFromALU ? aluData : _busData;
|
||||
}
|
||||
|
||||
// Load L (and M) from ALU
|
||||
if (instruction.LoadL)
|
||||
{
|
||||
_cpu._l = aluData;
|
||||
_cpu._m = aluData;
|
||||
|
||||
// Save ALUC0 for use in the next ALUCY special function.
|
||||
_cpu._aluC0 = (ushort)ALU.Carry;
|
||||
}
|
||||
|
||||
// Do writeback to selected R register from shifter output
|
||||
if (loadR)
|
||||
{
|
||||
@ -527,6 +518,16 @@ namespace Contralto.CPU
|
||||
_cpu._s[_cpu._rb][_rSelect] = _cpu._m;
|
||||
}
|
||||
|
||||
// Load L (and M) from ALU
|
||||
if (instruction.LoadL)
|
||||
{
|
||||
_cpu._l = aluData;
|
||||
_cpu._m = aluData;
|
||||
|
||||
// Save ALUC0 for use in the next ALUCY special function.
|
||||
_cpu._aluC0 = (ushort)ALU.Carry;
|
||||
}
|
||||
|
||||
//
|
||||
// Select next address, using the address modifier from the last instruction.
|
||||
//
|
||||
@ -565,8 +566,12 @@ namespace Contralto.CPU
|
||||
//
|
||||
protected AltoCPU _cpu;
|
||||
protected ushort _mpc;
|
||||
protected int _priority;
|
||||
protected bool _wakeup;
|
||||
protected TaskType _taskType;
|
||||
protected bool _wakeup;
|
||||
|
||||
// Emulator Task-specific data. This is placed here because it is used by the ALU and it's easier to reference in the
|
||||
// base class even if it does break encapsulation. See notes in the EmulatorTask class for meaning.
|
||||
protected int _skip;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -576,7 +581,7 @@ namespace Contralto.CPU
|
||||
{
|
||||
public EmulatorTask(AltoCPU cpu) : base(cpu)
|
||||
{
|
||||
_priority = 0;
|
||||
_taskType = TaskType.Emulator;
|
||||
|
||||
// The Wakeup signal is always true for the Emulator task.
|
||||
_wakeup = true;
|
||||
@ -629,6 +634,10 @@ namespace Contralto.CPU
|
||||
throw new NotImplementedException();
|
||||
break;
|
||||
|
||||
case EmulatorF1.SWMODE:
|
||||
// nothing! for now.
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new InvalidOperationException(String.Format("Unhandled emulator F1 {0}.", ef1));
|
||||
}
|
||||
@ -670,7 +679,10 @@ namespace Contralto.CPU
|
||||
// instruction dispatch."
|
||||
// TODO: is this an AND or an OR operation? (how is the "merge" done?)
|
||||
// Assuming for now this is an OR operation like everything else that modifies NEXT.
|
||||
_nextModifier = (ushort)(((_busData & 0x8000) >> 6) | ((_busData & 0x0700) >> 2));
|
||||
_nextModifier = (ushort)(((_busData & 0x8000) >> 12) | ((_busData & 0x0700) >> 8));
|
||||
|
||||
// "IR<- clears SKIP"
|
||||
_skip = 0;
|
||||
break;
|
||||
|
||||
case EmulatorF2.IDISP:
|
||||
@ -688,7 +700,7 @@ namespace Contralto.CPU
|
||||
// elseif IR[4-7] = 16B 6
|
||||
// else IR[4-7]
|
||||
// NB: as always, Xerox labels bits in the opposite order from modern convention;
|
||||
// (bit 0 is bit 15...)
|
||||
// (bit 0 is the msb...)
|
||||
if ((_cpu._ir & 0x8000) != 0)
|
||||
{
|
||||
_nextModifier = (ushort)(3 - ((_cpu._ir & 0xc0) >> 6));
|
||||
@ -801,7 +813,7 @@ namespace Contralto.CPU
|
||||
case EmulatorF2.BUSODD:
|
||||
// "...merges BUS[15] into NEXT[9]."
|
||||
// TODO: is this an AND or an OR?
|
||||
_nextModifier = (ushort)((_nextModifier & 0xffbf) | ((_busData & 0x1) << 6));
|
||||
_nextModifier |= (ushort)(_busData & 0x1);
|
||||
break;
|
||||
|
||||
case EmulatorF2.MAGIC:
|
||||
@ -812,7 +824,111 @@ namespace Contralto.CPU
|
||||
default:
|
||||
throw new InvalidOperationException(String.Format("Unhandled emulator F2 {0}.", ef2));
|
||||
}
|
||||
}
|
||||
|
||||
// From Section 3, Pg. 31:
|
||||
// "The emulator has two additional bits of state, the SKIP and CARRY flip flops. CARRY is distinct from the
|
||||
// microprocessor’s ALUC0 bit, tested by the ALUCY function. CARRY is set or cleared as a function of IR and
|
||||
// many other things(see section 3.1) when the DNS<-(do novel shifts, F2= 12B) function is executed. In
|
||||
// particular, if IR[12] is true, CARRY will not change. DNS also addresses R from (3-IR[3 - 4]), causes a store
|
||||
// into R unless IR[12] is set, and sets the SKIP flip flop if appropriate(see section 3.1). The emulator
|
||||
// microcode increments PC by 1 at the beginning of the next emulated instruction if SKIP is set, using
|
||||
// BUS+SKIP(ALUF= 13B). IR_ clears SKIP."
|
||||
//
|
||||
// NB: _skip is in the encapsulating AltoCPU class to make it easier to reference since the ALU needs to know about it.
|
||||
private int _carry;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// DiskSectorTask provides implementation for disk-specific special functions
|
||||
/// </summary>
|
||||
private class DiskSectorTask : Task
|
||||
{
|
||||
public DiskSectorTask(AltoCPU cpu) : base(cpu)
|
||||
{
|
||||
_taskType = TaskType.DiskSector;
|
||||
_wakeup = false;
|
||||
}
|
||||
|
||||
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(int f1)
|
||||
{
|
||||
DiskF1 df1 = (DiskF1)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]."
|
||||
// TODO: do the latter (likely inside the controller)
|
||||
_cpu._system.DiskController.KADR = (ushort)((_busData & 0xfe) >> 1);
|
||||
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].)"
|
||||
|
||||
// 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)) | (_busData & 0xf));
|
||||
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(int f2)
|
||||
{
|
||||
DiskF2 df2 = (DiskF2)f2;
|
||||
|
||||
switch(df2)
|
||||
{
|
||||
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.");
|
||||
|
||||
default:
|
||||
throw new InvalidOperationException(String.Format("Unhandled disk special function 2 {0}", df2));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -833,7 +949,7 @@ namespace Contralto.CPU
|
||||
// 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];
|
||||
private Task[] _tasks = new Task[16];
|
||||
|
||||
private long _clocks;
|
||||
|
||||
|
||||
@ -69,6 +69,10 @@ namespace Contralto.CPU
|
||||
//
|
||||
// Task-specific enumerations follow
|
||||
//
|
||||
|
||||
//
|
||||
// Emulator
|
||||
//
|
||||
enum EmulatorF1
|
||||
{
|
||||
SWMODE = 8,
|
||||
@ -99,6 +103,38 @@ namespace Contralto.CPU
|
||||
LoadSLocation = 4, // SLOCATION<- store to S reg from M
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// Disk (both sector and word tasks)
|
||||
//
|
||||
enum DiskF1
|
||||
{
|
||||
STROBE = 9,
|
||||
LoadKSTAT = 10,
|
||||
INCRECNO = 11,
|
||||
CLRSTAT = 12,
|
||||
LoadKCOMM = 13,
|
||||
LoadKADR = 14,
|
||||
LoadKDATA = 15,
|
||||
}
|
||||
|
||||
enum DiskF2
|
||||
{
|
||||
INIT = 8,
|
||||
RWC = 9,
|
||||
RECNO = 10,
|
||||
XFRDAT = 11,
|
||||
SWRNRDY = 12,
|
||||
NFER = 13,
|
||||
STROBON = 14,
|
||||
}
|
||||
|
||||
enum DiskBusSource
|
||||
{
|
||||
ReadKSTAT = 3,
|
||||
ReadKDATA = 4,
|
||||
}
|
||||
|
||||
public class MicroInstruction
|
||||
{
|
||||
public MicroInstruction(UInt32 code)
|
||||
|
||||
@ -16,6 +16,13 @@ namespace Contralto.CPU
|
||||
RotateRight,
|
||||
}
|
||||
|
||||
//NOTE: FOR NOVA (NOVEL) SHIFTS (from aug '76 manual):
|
||||
// The emulator has two additional bits of state, the SKIP and CARRY flip flops.CARRY is identical
|
||||
// to the Nova carry bit, and is set or cleared as appropriate when the DNS+- (do Nova shifts)
|
||||
// function is executed.DNS also addresses R from(1R[3 - 4] XOR 3), and sets the SKIP flip flop if
|
||||
// appropriate.The PC is incremented by 1 at the beginning of the next emulated instruction if
|
||||
// SKIP is set, using ALUF DB.IR4- clears SKIP.
|
||||
|
||||
public static class Shifter
|
||||
{
|
||||
static Shifter()
|
||||
@ -31,9 +38,13 @@ namespace Contralto.CPU
|
||||
_count = count;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// TODO: this is kind of clumsy.
|
||||
/// </summary>
|
||||
/// <param name="magic"></param>
|
||||
public static void SetMagic(bool magic)
|
||||
{
|
||||
_magic = magic;
|
||||
_magic = magic;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -56,10 +67,24 @@ namespace Contralto.CPU
|
||||
|
||||
case ShifterOp.ShiftLeft:
|
||||
output = (ushort)(input << _count);
|
||||
|
||||
if (_magic)
|
||||
{
|
||||
// "MAGIC places the high order bit of T into the low order bit of the
|
||||
// shifter output on left shifts..."
|
||||
output |= (ushort)((t & 0x8000) >> 15);
|
||||
}
|
||||
break;
|
||||
|
||||
case ShifterOp.ShiftRight:
|
||||
output = (ushort)(input >> _count);
|
||||
|
||||
if (_magic)
|
||||
{
|
||||
// "...and places the low order bit of T into the high order bit position
|
||||
// of the shifter output on right shifts."
|
||||
output |= (ushort)((t & 0x1) << 15);
|
||||
}
|
||||
break;
|
||||
|
||||
case ShifterOp.RotateLeft:
|
||||
|
||||
@ -57,6 +57,8 @@
|
||||
<Compile Include="Debugger.Designer.cs">
|
||||
<DependentUpon>Debugger.cs</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="IO\DiskController.cs" />
|
||||
<Compile Include="IO\Keyboard.cs" />
|
||||
<Compile Include="Memory\IMemoryMappedDevice.cs" />
|
||||
<Compile Include="Memory\Memory.cs" />
|
||||
<Compile Include="Memory\MemoryBus.cs" />
|
||||
|
||||
225
Contralto/IO/DiskController.cs
Normal file
225
Contralto/IO/DiskController.cs
Normal file
@ -0,0 +1,225 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using Contralto.Memory;
|
||||
|
||||
namespace Contralto.IO
|
||||
{
|
||||
public class DiskController
|
||||
{
|
||||
public DiskController(AltoSystem system)
|
||||
{
|
||||
_system = system;
|
||||
}
|
||||
|
||||
public ushort KDATA
|
||||
{
|
||||
get { return _kData; }
|
||||
set { _kData = value; }
|
||||
}
|
||||
|
||||
public ushort KADR
|
||||
{
|
||||
get { return _kAdr; }
|
||||
set
|
||||
{
|
||||
_kAdr = value;
|
||||
_recNo = 0;
|
||||
|
||||
// "In addition, it causes the head address bit to be loaded from KDATA[13]."
|
||||
_head = (_kData & 0x4) >> 2;
|
||||
}
|
||||
}
|
||||
|
||||
public ushort KCOM
|
||||
{
|
||||
get { return _kCom; }
|
||||
set
|
||||
{
|
||||
_kCom = value;
|
||||
|
||||
//
|
||||
}
|
||||
}
|
||||
|
||||
public ushort KSTAT
|
||||
{
|
||||
get { return _kStat; }
|
||||
set { _kStat = value; }
|
||||
}
|
||||
|
||||
public ushort RECNO
|
||||
{
|
||||
get { return _recMap[_recNo]; }
|
||||
}
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
ClearStatus();
|
||||
_recNo = 0;
|
||||
_elapsedSectorTime = 0.0;
|
||||
_cylinder = 0;
|
||||
_sector = 0;
|
||||
_head = 0;
|
||||
_kStat = 0;
|
||||
}
|
||||
|
||||
public void Clock()
|
||||
{
|
||||
_elapsedSectorTime++;
|
||||
|
||||
// TODO: only signal sector changes if disk is loaded, etc.
|
||||
if (_elapsedSectorTime > _sectorClocks)
|
||||
{
|
||||
//
|
||||
// Next sector; save fractional part of elapsed time (to more accurately keep track of time), move to next sector
|
||||
// and wake up sector task.
|
||||
//
|
||||
_elapsedSectorTime -= _sectorClocks;
|
||||
|
||||
_sector = (_sector + 1) % 12;
|
||||
|
||||
_kStat = (ushort)((_kStat & 0x0fff) | (_sector << 12));
|
||||
|
||||
_system.CPU.WakeupTask(CPU.TaskType.DiskSector);
|
||||
}
|
||||
|
||||
// If seek is in progress, move closer to the desired cylinder...
|
||||
// TODO: move bitfields to enums / constants, this is getting silly.
|
||||
if ((_kStat & 0x0040) != 0)
|
||||
{
|
||||
_elapsedSeekTime++;
|
||||
if (_elapsedSeekTime > _seekClocks)
|
||||
{
|
||||
_elapsedSectorTime -= _seekClocks;
|
||||
|
||||
if (_cylinder < _destCylinder)
|
||||
{
|
||||
_cylinder++;
|
||||
}
|
||||
else
|
||||
{
|
||||
_cylinder--;
|
||||
}
|
||||
|
||||
// Are we *there* yet?
|
||||
if (_cylinder == _destCylinder)
|
||||
{
|
||||
// clear Seek bit
|
||||
_kStat &= 0xffbf;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void ClearStatus()
|
||||
{
|
||||
// "...clears KSTAT[13]." (chksum error flag)
|
||||
_kStat &= 0xfffb;
|
||||
}
|
||||
|
||||
public void IncrementRecord()
|
||||
{
|
||||
// "Advances the shift registers holding the KADR register so that they present the number and read/write/check status of the
|
||||
// next record to the hardware."
|
||||
// "RECORD" in this context indicates the sector field corresponding to the 2 bit "action" field in the KADR register
|
||||
// (i.e. one of Header, Label, or Data.)
|
||||
// INCRECNO shifts the data over two bits to select from Header->Label->Data.
|
||||
_kAdr = (ushort)(_kAdr << 2);
|
||||
_recNo++;
|
||||
|
||||
if (_recNo > 3)
|
||||
{
|
||||
// sanity check for now
|
||||
throw new InvalidOperationException("Unexpected INCRECORD past rec 3.");
|
||||
}
|
||||
}
|
||||
|
||||
public void Strobe()
|
||||
{
|
||||
//
|
||||
// "Initiates a disk seek operation. The KDATA register must have been loaded previously,
|
||||
// and the SENDADR bit of the KCOMM register previously set to 1."
|
||||
//
|
||||
|
||||
// sanity check: see if SENDADR bit is set, if not we'll signal an error (since I'm trusting that
|
||||
// the official Xerox uCode is doing the right thing, this will help ferret out emulation issues.
|
||||
// eventually this can be removed.)
|
||||
if ((_kCom & 0x1) != 1)
|
||||
{
|
||||
throw new InvalidOperationException("STROBE while SENDADR bit of KCOMM not 1. Unexpected.");
|
||||
}
|
||||
|
||||
_destCylinder = (_kData & 0x0ff8) >> 3;
|
||||
|
||||
// set "seek fail" bit based on selected cylinder (if out of bounds) and do not
|
||||
// commence a seek if so.
|
||||
if (_destCylinder < 203)
|
||||
{
|
||||
_kStat |= 0x0080;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Otherwise, start a seek.
|
||||
|
||||
// Clear the fail bit.
|
||||
_kStat &= 0xff7f;
|
||||
|
||||
// Set seek bit
|
||||
_kStat |= 0x0040;
|
||||
|
||||
// And figure out how long this will take.
|
||||
_seekClocks = CalculateSeekTime();
|
||||
_elapsedSeekTime = 0.0;
|
||||
}
|
||||
}
|
||||
|
||||
private double CalculateSeekTime()
|
||||
{
|
||||
// How many cylinders are we moving?
|
||||
int dt = Math.Abs(_destCylinder - _cylinder);
|
||||
|
||||
//
|
||||
// From the Hardware Manual, pg 43:
|
||||
// "Seek time (approx.): 15 + 8.6 * sqrt(dt) (msec)
|
||||
//
|
||||
double seekTimeMsec = 15.0 + 8.6 * Math.Sqrt(dt);
|
||||
|
||||
return seekTimeMsec / AltoSystem.ClockInterval;
|
||||
}
|
||||
|
||||
private ushort _kData;
|
||||
private ushort _kAdr;
|
||||
private ushort _kCom;
|
||||
private ushort _kStat;
|
||||
|
||||
private int _recNo;
|
||||
private ushort[] _recMap =
|
||||
{
|
||||
0, 2, 3, 1
|
||||
};
|
||||
|
||||
// Current disk position
|
||||
private int _cylinder;
|
||||
private int _destCylinder;
|
||||
private int _head;
|
||||
private int _sector;
|
||||
|
||||
// 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.
|
||||
|
||||
// Cylinder seek timing. Again, see the manual.
|
||||
// Timing varies based on how many cylinders are being traveled during a seek; see
|
||||
// CalculateSeekTime() for more.
|
||||
private double _elapsedSeekTime;
|
||||
private double _seekClocks;
|
||||
|
||||
|
||||
private AltoSystem _system;
|
||||
}
|
||||
}
|
||||
42
Contralto/IO/Keyboard.cs
Normal file
42
Contralto/IO/Keyboard.cs
Normal file
@ -0,0 +1,42 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using Contralto.Memory;
|
||||
|
||||
namespace Contralto.IO
|
||||
{
|
||||
/// <summary>
|
||||
/// Currently just a stub indicating that no keys are being pressed.
|
||||
/// </summary>
|
||||
public class Keyboard : IMemoryMappedDevice
|
||||
{
|
||||
public Keyboard()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public ushort Read(int address)
|
||||
{
|
||||
// TODO: implement; return nothing pressed for any address now.
|
||||
return 0xffff;
|
||||
}
|
||||
|
||||
public void Load(int address, ushort data)
|
||||
{
|
||||
// nothing
|
||||
}
|
||||
|
||||
public MemoryRange[] Addresses
|
||||
{
|
||||
get { return _addresses; }
|
||||
}
|
||||
|
||||
private readonly MemoryRange[] _addresses =
|
||||
{
|
||||
new MemoryRange(0xfe1c, 0xfe1f), // 177034-177037
|
||||
};
|
||||
}
|
||||
}
|
||||
@ -6,10 +6,55 @@ using System.Threading.Tasks;
|
||||
|
||||
namespace Contralto.Memory
|
||||
{
|
||||
/// <summary>
|
||||
/// Specifies a range of memory from Start to End, inclusive.
|
||||
/// </summary>
|
||||
public struct MemoryRange
|
||||
{
|
||||
public MemoryRange(ushort start, ushort end)
|
||||
{
|
||||
if (!(end > start))
|
||||
{
|
||||
throw new ArgumentOutOfRangeException("end must be greater than start.");
|
||||
}
|
||||
|
||||
Start = start;
|
||||
End = end;
|
||||
}
|
||||
|
||||
public bool Overlaps(MemoryRange other)
|
||||
{
|
||||
return ((other.Start >= this.Start && other.Start <= this.End) ||
|
||||
(other.End >= this.Start && other.End <= this.End));
|
||||
}
|
||||
|
||||
public ushort Start;
|
||||
public ushort End;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Specifies an interfaces for devices that appear in mapped memory. This includes
|
||||
/// RAM as well as regular I/O devices.
|
||||
/// </summary>
|
||||
public interface IMemoryMappedDevice
|
||||
{
|
||||
/// <summary>
|
||||
/// Reads a word from the specified address.
|
||||
/// </summary>
|
||||
/// <param name="address"></param>
|
||||
/// <returns></returns>
|
||||
ushort Read(int address);
|
||||
|
||||
/// <summary>
|
||||
/// Writes a word to the specified address.
|
||||
/// </summary>
|
||||
/// <param name="address"></param>
|
||||
/// <param name="data"></param>
|
||||
void Load(int address, ushort data);
|
||||
|
||||
/// <summary>
|
||||
/// Specifies the range (or ranges) of addresses decoded by this device.
|
||||
/// </summary>
|
||||
MemoryRange[] Addresses { get; }
|
||||
}
|
||||
}
|
||||
|
||||
@ -23,6 +23,16 @@ namespace Contralto.Memory
|
||||
_mem[address] = data;
|
||||
}
|
||||
|
||||
public MemoryRange[] Addresses
|
||||
{
|
||||
get { return _addresses; }
|
||||
}
|
||||
|
||||
private readonly MemoryRange[] _addresses =
|
||||
{
|
||||
new MemoryRange(0, 0xfdff), // to 176777; IO page above this.
|
||||
};
|
||||
|
||||
private ushort[] _mem;
|
||||
}
|
||||
}
|
||||
|
||||
@ -17,10 +17,34 @@ namespace Contralto.Memory
|
||||
{
|
||||
public MemoryBus()
|
||||
{
|
||||
_mem = new Memory();
|
||||
_bus = new Dictionary<ushort, IMemoryMappedDevice>(65536);
|
||||
Reset();
|
||||
}
|
||||
|
||||
public void AddDevice(IMemoryMappedDevice dev)
|
||||
{
|
||||
//
|
||||
// Add the new device to the hash; this is done by adding
|
||||
// one entry for every address claimed by the device. Since we have only 64K of address
|
||||
// space, this isn't too awful.
|
||||
//
|
||||
foreach(MemoryRange range in dev.Addresses)
|
||||
{
|
||||
for(ushort addr = range.Start; addr <= range.End; addr++)
|
||||
{
|
||||
if (_bus.ContainsKey(addr))
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
String.Format("Memory mapped address collision for dev {0} at address {1}", dev, OctalHelpers.ToOctal(addr)));
|
||||
}
|
||||
else
|
||||
{
|
||||
_bus.Add(addr, dev);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
_memoryCycle = 0;
|
||||
@ -201,8 +225,16 @@ namespace Contralto.Memory
|
||||
/// <returns></returns>
|
||||
private ushort ReadFromBus(ushort address)
|
||||
{
|
||||
// TODO: actually dispatch to I/O
|
||||
return _mem.Read(address);
|
||||
// Look up address in hash; if populated ask the device
|
||||
// to return a value otherwise throw.
|
||||
if (_bus.ContainsKey(address))
|
||||
{
|
||||
return _bus[address].Read(address);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new NotImplementedException(String.Format("Read from unimplemented memory-mapped I/O device at {0}.", OctalHelpers.ToOctal(address)));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -213,11 +245,23 @@ namespace Contralto.Memory
|
||||
/// <returns></returns>
|
||||
private void WriteToBus(ushort address, ushort data)
|
||||
{
|
||||
_mem.Load(address, data);
|
||||
// Look up address in hash; if populated ask the device
|
||||
// to store a value otherwise throw.
|
||||
if (_bus.ContainsKey(address))
|
||||
{
|
||||
_bus[address].Load(address, data);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new NotImplementedException(String.Format("Write to unimplemented memory-mapped I/O device at {0}.", OctalHelpers.ToOctal(address)));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private Memory _mem;
|
||||
/// <summary>
|
||||
/// Hashtable used for address-based dispatch to devices on the memory bus.
|
||||
/// </summary>
|
||||
private Dictionary<ushort, IMemoryMappedDevice> _bus;
|
||||
|
||||
private bool _memoryOperationActive;
|
||||
private int _memoryCycle;
|
||||
private ushort _memoryAddress;
|
||||
|
||||
@ -15,7 +15,7 @@ namespace Contralto
|
||||
|
||||
public static string ToOctal(int i, int digits)
|
||||
{
|
||||
string octalString = Convert.ToString(i, 8);
|
||||
string octalString = Convert.ToString(i, 8);
|
||||
return new String('0', digits - octalString.Length) + octalString;
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user