From 24d7a5a8fe5e81eb7bf8a17b9a68665197eec578 Mon Sep 17 00:00:00 2001 From: Josh Dersch Date: Fri, 4 Sep 2015 18:03:47 -0700 Subject: [PATCH] Work begun on Disk controller, stubs for keyboard and a few bugfixes and tweaks. --- Contralto/AltoSystem.cs | 38 +++- Contralto/CPU/ALU.cs | 6 +- Contralto/CPU/CPU.cs | 178 +++++++++++++++---- Contralto/CPU/MicroInstruction.cs | 36 ++++ Contralto/CPU/Shifter.cs | 27 ++- Contralto/Contralto.csproj | 2 + Contralto/IO/DiskController.cs | 225 ++++++++++++++++++++++++ Contralto/IO/Keyboard.cs | 42 +++++ Contralto/Memory/IMemoryMappedDevice.cs | 45 +++++ Contralto/Memory/Memory.cs | 10 ++ Contralto/Memory/MemoryBus.cs | 56 +++++- Contralto/OctalHelpers.cs | 2 +- 12 files changed, 620 insertions(+), 47 deletions(-) create mode 100644 Contralto/IO/DiskController.cs create mode 100644 Contralto/IO/Keyboard.cs 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; } }