diff --git a/Contralto/AltoSystem.cs b/Contralto/AltoSystem.cs
index 782740d..1d080ec 100644
--- a/Contralto/AltoSystem.cs
+++ b/Contralto/AltoSystem.cs
@@ -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; }
+ }
+
+ ///
+ /// Time (in msec) for one system clock
+ ///
+ 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;
}
}
diff --git a/Contralto/CPU/ALU.cs b/Contralto/CPU/ALU.cs
index 2f8a20f..dca2374 100644
--- a/Contralto/CPU/ALU.cs
+++ b/Contralto/CPU/ALU.cs
@@ -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);
diff --git a/Contralto/CPU/CPU.cs b/Contralto/CPU/CPU.cs
index dcbde12..c116d26 100644
--- a/Contralto/CPU/CPU.cs
+++ b/Contralto/CPU/CPU.cs
@@ -140,11 +140,11 @@ namespace Contralto.CPU
/// "wakeup" signal triggered
///
///
- 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
///
///
- 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;
}
///
@@ -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;
+ }
+
+ ///
+ /// DiskSectorTask provides implementation for disk-specific special functions
+ ///
+ 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;
diff --git a/Contralto/CPU/MicroInstruction.cs b/Contralto/CPU/MicroInstruction.cs
index cfbea64..a584f2b 100644
--- a/Contralto/CPU/MicroInstruction.cs
+++ b/Contralto/CPU/MicroInstruction.cs
@@ -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)
diff --git a/Contralto/CPU/Shifter.cs b/Contralto/CPU/Shifter.cs
index 8e6cb35..2f75fa4 100644
--- a/Contralto/CPU/Shifter.cs
+++ b/Contralto/CPU/Shifter.cs
@@ -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;
}
+ ///
+ /// TODO: this is kind of clumsy.
+ ///
+ ///
public static void SetMagic(bool magic)
{
- _magic = magic;
+ _magic = magic;
}
///
@@ -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:
diff --git a/Contralto/Contralto.csproj b/Contralto/Contralto.csproj
index c4710ec..229cecc 100644
--- a/Contralto/Contralto.csproj
+++ b/Contralto/Contralto.csproj
@@ -57,6 +57,8 @@
Debugger.cs
+
+
diff --git a/Contralto/IO/DiskController.cs b/Contralto/IO/DiskController.cs
new file mode 100644
index 0000000..a6c048c
--- /dev/null
+++ b/Contralto/IO/DiskController.cs
@@ -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;
+ }
+}
diff --git a/Contralto/IO/Keyboard.cs b/Contralto/IO/Keyboard.cs
new file mode 100644
index 0000000..a0b8511
--- /dev/null
+++ b/Contralto/IO/Keyboard.cs
@@ -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
+{
+ ///
+ /// Currently just a stub indicating that no keys are being pressed.
+ ///
+ 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
+ };
+ }
+}
diff --git a/Contralto/Memory/IMemoryMappedDevice.cs b/Contralto/Memory/IMemoryMappedDevice.cs
index 4c67253..1dfadd8 100644
--- a/Contralto/Memory/IMemoryMappedDevice.cs
+++ b/Contralto/Memory/IMemoryMappedDevice.cs
@@ -6,10 +6,55 @@ using System.Threading.Tasks;
namespace Contralto.Memory
{
+ ///
+ /// Specifies a range of memory from Start to End, inclusive.
+ ///
+ 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;
+ }
+
+ ///
+ /// Specifies an interfaces for devices that appear in mapped memory. This includes
+ /// RAM as well as regular I/O devices.
+ ///
public interface IMemoryMappedDevice
{
+ ///
+ /// Reads a word from the specified address.
+ ///
+ ///
+ ///
ushort Read(int address);
+ ///
+ /// Writes a word to the specified address.
+ ///
+ ///
+ ///
void Load(int address, ushort data);
+
+ ///
+ /// Specifies the range (or ranges) of addresses decoded by this device.
+ ///
+ MemoryRange[] Addresses { get; }
}
}
diff --git a/Contralto/Memory/Memory.cs b/Contralto/Memory/Memory.cs
index c01d535..00801f6 100644
--- a/Contralto/Memory/Memory.cs
+++ b/Contralto/Memory/Memory.cs
@@ -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;
}
}
diff --git a/Contralto/Memory/MemoryBus.cs b/Contralto/Memory/MemoryBus.cs
index 70b7d30..7c905c8 100644
--- a/Contralto/Memory/MemoryBus.cs
+++ b/Contralto/Memory/MemoryBus.cs
@@ -17,10 +17,34 @@ namespace Contralto.Memory
{
public MemoryBus()
{
- _mem = new Memory();
+ _bus = new Dictionary(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
///
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)));
+ }
}
///
@@ -213,11 +245,23 @@ namespace Contralto.Memory
///
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;
+ ///
+ /// Hashtable used for address-based dispatch to devices on the memory bus.
+ ///
+ private Dictionary _bus;
+
private bool _memoryOperationActive;
private int _memoryCycle;
private ushort _memoryAddress;
diff --git a/Contralto/OctalHelpers.cs b/Contralto/OctalHelpers.cs
index 34b8e9f..bafaf12 100644
--- a/Contralto/OctalHelpers.cs
+++ b/Contralto/OctalHelpers.cs
@@ -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;
}
}