diff --git a/Contralto/AltoSystem.cs b/Contralto/AltoSystem.cs index 42782ec..9344db5 100644 --- a/Contralto/AltoSystem.cs +++ b/Contralto/AltoSystem.cs @@ -65,14 +65,11 @@ namespace Contralto /// /// Attaches an emulated display device to the system. - /// TODO: This is currently tightly-coupled with the Debugger, make - /// more general. /// /// - public void AttachDisplay(Debugger d) + public void AttachDisplay(IAltoDisplay d) { - _displayController.AttachDisplay(d); - // _fakeDisplayController.AttachDisplay(d); + _displayController.AttachDisplay(d); } public void SingleStep() diff --git a/Contralto/AltoWindow.Designer.cs b/Contralto/AltoWindow.Designer.cs new file mode 100644 index 0000000..44454ce --- /dev/null +++ b/Contralto/AltoWindow.Designer.cs @@ -0,0 +1,263 @@ +namespace Contralto +{ + partial class AltoWindow + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.DisplayBox = new System.Windows.Forms.PictureBox(); + this.menuStrip1 = new System.Windows.Forms.MenuStrip(); + this.fileToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.exitToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.settingsToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.SystemStartMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.SystemResetMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.drive0ToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.loadToolStripMenuItem1 = new System.Windows.Forms.ToolStripMenuItem(); + this.unloadToolStripMenuItem1 = new System.Windows.Forms.ToolStripMenuItem(); + this.drive1ToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.loadToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.unloadToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.optionsToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.debuggerToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.showDebuggerToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.helpToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.aboutToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.StatusLine = new System.Windows.Forms.StatusStrip(); + this.StatusLabel = new System.Windows.Forms.ToolStripStatusLabel(); + ((System.ComponentModel.ISupportInitialize)(this.DisplayBox)).BeginInit(); + this.menuStrip1.SuspendLayout(); + this.StatusLine.SuspendLayout(); + this.SuspendLayout(); + // + // DisplayBox + // + this.DisplayBox.BackColor = System.Drawing.SystemColors.Window; + this.DisplayBox.Location = new System.Drawing.Point(0, 27); + this.DisplayBox.Name = "DisplayBox"; + this.DisplayBox.Size = new System.Drawing.Size(606, 808); + this.DisplayBox.TabIndex = 1; + this.DisplayBox.TabStop = false; + this.DisplayBox.MouseDown += new System.Windows.Forms.MouseEventHandler(this.OnDisplayMouseDown); + this.DisplayBox.MouseMove += new System.Windows.Forms.MouseEventHandler(this.OnDisplayMouseMove); + this.DisplayBox.MouseUp += new System.Windows.Forms.MouseEventHandler(this.OnDisplayMouseUp); + // + // menuStrip1 + // + this.menuStrip1.Items.AddRange(new System.Windows.Forms.ToolStripItem[] { + this.fileToolStripMenuItem, + this.settingsToolStripMenuItem, + this.debuggerToolStripMenuItem, + this.helpToolStripMenuItem}); + this.menuStrip1.Location = new System.Drawing.Point(0, 0); + this.menuStrip1.Name = "menuStrip1"; + this.menuStrip1.Size = new System.Drawing.Size(608, 24); + this.menuStrip1.TabIndex = 2; + this.menuStrip1.Text = "menuStrip1"; + // + // fileToolStripMenuItem + // + this.fileToolStripMenuItem.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { + this.exitToolStripMenuItem}); + this.fileToolStripMenuItem.Name = "fileToolStripMenuItem"; + this.fileToolStripMenuItem.Size = new System.Drawing.Size(35, 20); + this.fileToolStripMenuItem.Text = "File"; + // + // exitToolStripMenuItem + // + this.exitToolStripMenuItem.Name = "exitToolStripMenuItem"; + this.exitToolStripMenuItem.Size = new System.Drawing.Size(92, 22); + this.exitToolStripMenuItem.Text = "Exit"; + // + // settingsToolStripMenuItem + // + this.settingsToolStripMenuItem.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { + this.SystemStartMenuItem, + this.SystemResetMenuItem, + this.drive0ToolStripMenuItem, + this.drive1ToolStripMenuItem, + this.optionsToolStripMenuItem}); + this.settingsToolStripMenuItem.Name = "settingsToolStripMenuItem"; + this.settingsToolStripMenuItem.Size = new System.Drawing.Size(54, 20); + this.settingsToolStripMenuItem.Text = "System"; + // + // SystemStartMenuItem + // + this.SystemStartMenuItem.Name = "SystemStartMenuItem"; + this.SystemStartMenuItem.Size = new System.Drawing.Size(152, 22); + this.SystemStartMenuItem.Text = "Start"; + this.SystemStartMenuItem.Click += new System.EventHandler(this.OnSystemStartMenuClick); + // + // SystemResetMenuItem + // + this.SystemResetMenuItem.Enabled = false; + this.SystemResetMenuItem.Name = "SystemResetMenuItem"; + this.SystemResetMenuItem.Size = new System.Drawing.Size(152, 22); + this.SystemResetMenuItem.Text = "Reset"; + // + // drive0ToolStripMenuItem + // + this.drive0ToolStripMenuItem.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { + this.loadToolStripMenuItem1, + this.unloadToolStripMenuItem1}); + this.drive0ToolStripMenuItem.Name = "drive0ToolStripMenuItem"; + this.drive0ToolStripMenuItem.Size = new System.Drawing.Size(152, 22); + this.drive0ToolStripMenuItem.Text = "Drive 0"; + // + // loadToolStripMenuItem1 + // + this.loadToolStripMenuItem1.Name = "loadToolStripMenuItem1"; + this.loadToolStripMenuItem1.Size = new System.Drawing.Size(119, 22); + this.loadToolStripMenuItem1.Text = "Load..."; + // + // unloadToolStripMenuItem1 + // + this.unloadToolStripMenuItem1.Name = "unloadToolStripMenuItem1"; + this.unloadToolStripMenuItem1.Size = new System.Drawing.Size(119, 22); + this.unloadToolStripMenuItem1.Text = "Unload..."; + // + // drive1ToolStripMenuItem + // + this.drive1ToolStripMenuItem.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { + this.loadToolStripMenuItem, + this.unloadToolStripMenuItem}); + this.drive1ToolStripMenuItem.Name = "drive1ToolStripMenuItem"; + this.drive1ToolStripMenuItem.Size = new System.Drawing.Size(152, 22); + this.drive1ToolStripMenuItem.Text = "Drive 1"; + // + // loadToolStripMenuItem + // + this.loadToolStripMenuItem.Name = "loadToolStripMenuItem"; + this.loadToolStripMenuItem.Size = new System.Drawing.Size(119, 22); + this.loadToolStripMenuItem.Text = "Load..."; + // + // unloadToolStripMenuItem + // + this.unloadToolStripMenuItem.Name = "unloadToolStripMenuItem"; + this.unloadToolStripMenuItem.Size = new System.Drawing.Size(119, 22); + this.unloadToolStripMenuItem.Text = "Unload..."; + // + // optionsToolStripMenuItem + // + this.optionsToolStripMenuItem.Name = "optionsToolStripMenuItem"; + this.optionsToolStripMenuItem.Size = new System.Drawing.Size(152, 22); + this.optionsToolStripMenuItem.Text = "Options..."; + // + // debuggerToolStripMenuItem + // + this.debuggerToolStripMenuItem.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { + this.showDebuggerToolStripMenuItem}); + this.debuggerToolStripMenuItem.Name = "debuggerToolStripMenuItem"; + this.debuggerToolStripMenuItem.Size = new System.Drawing.Size(66, 20); + this.debuggerToolStripMenuItem.Text = "Debugger"; + // + // showDebuggerToolStripMenuItem + // + this.showDebuggerToolStripMenuItem.Name = "showDebuggerToolStripMenuItem"; + this.showDebuggerToolStripMenuItem.Size = new System.Drawing.Size(150, 22); + this.showDebuggerToolStripMenuItem.Text = "Show Debugger"; + // + // helpToolStripMenuItem + // + this.helpToolStripMenuItem.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { + this.aboutToolStripMenuItem}); + this.helpToolStripMenuItem.Name = "helpToolStripMenuItem"; + this.helpToolStripMenuItem.Size = new System.Drawing.Size(40, 20); + this.helpToolStripMenuItem.Text = "Help"; + // + // aboutToolStripMenuItem + // + this.aboutToolStripMenuItem.Name = "aboutToolStripMenuItem"; + this.aboutToolStripMenuItem.Size = new System.Drawing.Size(103, 22); + this.aboutToolStripMenuItem.Text = "About"; + // + // StatusLine + // + this.StatusLine.Items.AddRange(new System.Windows.Forms.ToolStripItem[] { + this.StatusLabel}); + this.StatusLine.Location = new System.Drawing.Point(0, 837); + this.StatusLine.Name = "StatusLine"; + this.StatusLine.Size = new System.Drawing.Size(608, 22); + this.StatusLine.TabIndex = 3; + this.StatusLine.Text = "statusStrip1"; + this.StatusLine.KeyDown += new System.Windows.Forms.KeyEventHandler(this.OnKeyDown); + // + // StatusLabel + // + this.StatusLabel.Name = "StatusLabel"; + this.StatusLabel.Size = new System.Drawing.Size(63, 17); + this.StatusLabel.Text = "StatusLabel"; + // + // AltoWindow + // + this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.ClientSize = new System.Drawing.Size(608, 859); + this.Controls.Add(this.StatusLine); + this.Controls.Add(this.DisplayBox); + this.Controls.Add(this.menuStrip1); + this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedSingle; + this.MainMenuStrip = this.menuStrip1; + this.MinimizeBox = false; + this.Name = "AltoWindow"; + this.SizeGripStyle = System.Windows.Forms.SizeGripStyle.Hide; + this.Text = "ContrAlto"; + this.KeyDown += new System.Windows.Forms.KeyEventHandler(this.OnKeyDown); + this.KeyUp += new System.Windows.Forms.KeyEventHandler(this.OnKeyUp); + ((System.ComponentModel.ISupportInitialize)(this.DisplayBox)).EndInit(); + this.menuStrip1.ResumeLayout(false); + this.menuStrip1.PerformLayout(); + this.StatusLine.ResumeLayout(false); + this.StatusLine.PerformLayout(); + this.ResumeLayout(false); + this.PerformLayout(); + + } + + #endregion + + private System.Windows.Forms.PictureBox DisplayBox; + private System.Windows.Forms.MenuStrip menuStrip1; + private System.Windows.Forms.ToolStripMenuItem fileToolStripMenuItem; + private System.Windows.Forms.ToolStripMenuItem settingsToolStripMenuItem; + private System.Windows.Forms.ToolStripMenuItem helpToolStripMenuItem; + private System.Windows.Forms.ToolStripMenuItem aboutToolStripMenuItem; + private System.Windows.Forms.ToolStripMenuItem exitToolStripMenuItem; + private System.Windows.Forms.ToolStripMenuItem SystemStartMenuItem; + private System.Windows.Forms.ToolStripMenuItem SystemResetMenuItem; + private System.Windows.Forms.ToolStripMenuItem drive0ToolStripMenuItem; + private System.Windows.Forms.ToolStripMenuItem loadToolStripMenuItem1; + private System.Windows.Forms.ToolStripMenuItem unloadToolStripMenuItem1; + private System.Windows.Forms.ToolStripMenuItem drive1ToolStripMenuItem; + private System.Windows.Forms.ToolStripMenuItem loadToolStripMenuItem; + private System.Windows.Forms.ToolStripMenuItem unloadToolStripMenuItem; + private System.Windows.Forms.ToolStripMenuItem optionsToolStripMenuItem; + private System.Windows.Forms.ToolStripMenuItem debuggerToolStripMenuItem; + private System.Windows.Forms.ToolStripMenuItem showDebuggerToolStripMenuItem; + private System.Windows.Forms.StatusStrip StatusLine; + private System.Windows.Forms.ToolStripStatusLabel StatusLabel; + } +} \ No newline at end of file diff --git a/Contralto/AltoWindow.resx b/Contralto/AltoWindow.resx new file mode 100644 index 0000000..501e458 --- /dev/null +++ b/Contralto/AltoWindow.resx @@ -0,0 +1,126 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 17, 17 + + + 126, 17 + + \ No newline at end of file diff --git a/Contralto/Contralto.csproj b/Contralto/Contralto.csproj index 0a43734..d3be055 100644 --- a/Contralto/Contralto.csproj +++ b/Contralto/Contralto.csproj @@ -64,6 +64,12 @@ + + Form + + + AltoWindow.cs + @@ -88,7 +94,9 @@ + + @@ -114,6 +122,9 @@ PreserveNewest + + PreserveNewest + PreserveNewest @@ -260,6 +271,9 @@ + + AltoWindow.cs + Debugger.cs diff --git a/Contralto/Debugger.cs b/Contralto/Debugger.cs index 7dc2021..a21b622 100644 --- a/Contralto/Debugger.cs +++ b/Contralto/Debugger.cs @@ -12,13 +12,14 @@ using Contralto.CPU; using System.Threading; using System.Drawing.Imaging; using Contralto.IO; +using Contralto.Display; namespace Contralto { /// /// A basic & hacky debugger. To be improved. /// - public partial class Debugger : Form + public partial class Debugger : Form, IAltoDisplay { public Debugger(AltoSystem system) { @@ -76,7 +77,7 @@ namespace Contralto RefreshUI(); } - public void RefreshAltoDisplay() + public void Render() { BeginInvoke(new StepDelegate(RefreshDisplayBox)); } diff --git a/Contralto/Disk/bravox.dsk b/Contralto/Disk/bravox.dsk new file mode 100644 index 0000000..1b2a22e Binary files /dev/null and b/Contralto/Disk/bravox.dsk differ diff --git a/Contralto/Display/DisplayController.cs b/Contralto/Display/DisplayController.cs index 3d081ac..d955adc 100644 --- a/Contralto/Display/DisplayController.cs +++ b/Contralto/Display/DisplayController.cs @@ -16,7 +16,7 @@ namespace Contralto.Display Reset(); } - public void AttachDisplay(Debugger display) + public void AttachDisplay(IAltoDisplay display) { _display = display; } @@ -207,7 +207,7 @@ namespace Contralto.Display // Done with field. // Draw the completed field to the emulated display. - _display.RefreshAltoDisplay(); + _display.Render(); // And start over FieldStart(); @@ -370,7 +370,7 @@ namespace Contralto.Display private Queue _dataBuffer = new Queue(16); private AltoSystem _system; - private Debugger _display; + private IAltoDisplay _display; private int _fields; diff --git a/Contralto/Display/FakeDisplayController.cs b/Contralto/Display/FakeDisplayController.cs index f0e8cd6..2d7f071 100644 --- a/Contralto/Display/FakeDisplayController.cs +++ b/Contralto/Display/FakeDisplayController.cs @@ -32,7 +32,7 @@ _clocks -= _frameClocks; RenderDisplay(); - _display.RefreshAltoDisplay(); + _display.Render(); } } @@ -51,7 +51,7 @@ } } - _display.RefreshAltoDisplay(); + _display.Render(); return; } @@ -89,7 +89,7 @@ _display.DrawDisplayWord(scanline, wordOffset, (ushort)(dcb.whiteOnBlack ? 0x0 : 0xffff), false); } - _display.RefreshAltoDisplay(); + _display.Render(); // decrement scan line counter for this DCB, if < 0, grab next DCB. dcb.scanlineCount--; @@ -144,7 +144,7 @@ private double _clocks; private AltoSystem _system; - private Debugger _display; + private IAltoDisplay _display; // Timing constants // 38uS per scanline; 4uS for hblank. diff --git a/Contralto/Display/IAltoDisplay.cs b/Contralto/Display/IAltoDisplay.cs new file mode 100644 index 0000000..d2d5cd2 --- /dev/null +++ b/Contralto/Display/IAltoDisplay.cs @@ -0,0 +1,25 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Contralto.Display +{ + public interface IAltoDisplay + { + /// + /// Renders a word's worth of data to the specified scanline and word offset. + /// + /// + /// + /// + /// + void DrawDisplayWord(int scanline, int wordOffset, ushort dataWord, bool lowRes); + + /// + /// Causes the display to be rendered + /// + void Render(); + } +} diff --git a/Contralto/IO/Diablo30Drive.cs b/Contralto/IO/Diablo30Drive.cs new file mode 100644 index 0000000..10b449f --- /dev/null +++ b/Contralto/IO/Diablo30Drive.cs @@ -0,0 +1,328 @@ +using Contralto.Logging; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Contralto.IO +{ + + // The data for the current sector + public enum CellType + { + Data, + Gap, + Sync, + } + + public struct DataCell + { + public DataCell(ushort data, CellType type) + { + Data = data; + Type = type; + } + + public ushort Data; + public CellType Type; + + public override string ToString() + { + return String.Format("{0} {1}", Data, Type); + } + } + + /// + /// Encapsulates logic that belongs to the drive, including loading/saving packs, + /// seeking and reading sector data. + /// + public class Diablo30Drive + { + public Diablo30Drive(AltoSystem system) + { + _system = system; + Reset(); + } + + public void Reset() + { + _sector = 0; + _cylinder = 0; + _head = 0; + + InitSector(); + LoadSector(); + } + + public void LoadPack(DiabloPack pack) + { + _pack = pack; + } + + public void UnloadPack() + { + _pack = null; + } + + public bool IsLoaded() + { + return _pack != null; + } + + public int Sector + { + get { return _sector; } + set + { + // If the last sector was modified, + // commit it before moving to the next. + if (_sectorModified) + { + CommitSector(); + _sectorModified = false; + } + + _sector = value; + LoadSector(); + } + } + + public int Head + { + get { return _head; } + set + { + if (value != _head) + { + // If we switch heads, we need to reload the sector. + + // If the last sector was modified, + // commit it before moving to the next. + if (_sectorModified) + { + CommitSector(); + _sectorModified = false; + } + + _head = value; + LoadSector(); + } + } + } + + public int Cylinder + { + get { return _cylinder; } + set + { + if (value != _cylinder) + { + // If we switch cylinders, we need to reload the sector. + // If the last sector was modified, + // commit it before moving to the next. + if (_sectorModified) + { + CommitSector(); + _sectorModified = false; + } + + _cylinder = value; + LoadSector(); + } + } + } + + public DataCell ReadWord(int index) + { + return _sectorData[index]; + } + + public void WriteWord(int index, ushort data) + { + if (index < _sectorData.Length) + { + if (_sectorData[index].Type == CellType.Data) + { + _sectorData[index].Data = data; + } + else + { + Log.Write(LogType.Warning, LogComponent.DiskController, "Data written to non-data section (Sector {0} Word {1} Rec {2} Data {3})", _sector, index, 666, Conversion.ToOctal(data)); + } + + _sectorModified = true; + } + } + + + private void LoadSector() + { + if (_pack == null) + { + return; + } + + // + // Pull data off disk and pack it into our faked-up sector. + // Note that this data is packed in in REVERSE ORDER because that's + // how it gets written out and it's how the Alto expects it to be read back in. + // + DiabloDiskSector sector = _pack.GetSector(_cylinder, _head, _sector); + + // Header (2 words data, 1 word cksum) + for (int i = _headerOffset + 1, j = 1; i < _headerOffset + 3; i++, j--) + { + // actual data to be loaded from disk / cksum calculated + _sectorData[i] = new DataCell(sector.Header[j], CellType.Data); + } + + ushort checksum = CalculateChecksum(_sectorData, _headerOffset + 1, 2); + _sectorData[_headerOffset + 3].Data = checksum; + Log.Write(LogType.Verbose, LogComponent.DiskController, "Header checksum for C/H/S {0}/{1}/{2} is {3}", _cylinder, _head, _sector, Conversion.ToOctal(checksum)); + + // Label (8 words data, 1 word cksum) + for (int i = _labelOffset + 1, j = 7; i < _labelOffset + 9; i++, j--) + { + // actual data to be loaded from disk / cksum calculated + _sectorData[i] = new DataCell(sector.Label[j], CellType.Data); + } + + checksum = CalculateChecksum(_sectorData, _labelOffset + 1, 8); + _sectorData[_labelOffset + 9].Data = checksum; + Log.Write(LogType.Verbose, LogComponent.DiskController, "Label checksum for C/H/S {0}/{1}/{2} is {3}", _cylinder, _head, _sector, Conversion.ToOctal(checksum)); + + // sector data (256 words data, 1 word cksum) + for (int i = _dataOffset + 1, j = 255; i < _dataOffset + 257; i++, j--) + { + // actual data to be loaded from disk / cksum calculated + _sectorData[i] = new DataCell(sector.Data[j], CellType.Data); + } + + checksum = CalculateChecksum(_sectorData, _dataOffset + 1, 256); + _sectorData[_dataOffset + 257].Data = checksum; + Log.Write(LogType.Verbose, LogComponent.DiskController, "Data checksum for C/H/S {0}/{1}/{2} is {3}", _cylinder, _head, _sector, Conversion.ToOctal(checksum)); + + } + + + + /// + /// Commits modified sector data back to the emulated disk. + /// Intended to be called at the end of the sector / beginning of the next. + /// TODO: we should modify this so that checksums are persisted, possibly... + /// + private void CommitSector() + { + if (_pack == null) + { + return; + } + + DiabloDiskSector sector = _pack.GetSector(_cylinder, _head, _sector); + + // Header (2 words data, 1 word cksum) + for (int i = _headerOffset + 1, j = 1; i < _headerOffset + 3; i++, j--) + { + // actual data to be loaded from disk / cksum calculated + sector.Header[j] = _sectorData[i].Data; + } + + // Label (8 words data, 1 word cksum) + for (int i = _labelOffset + 1, j = 7; i < _labelOffset + 9; i++, j--) + { + // actual data to be loaded from disk / cksum calculated + sector.Label[j] = _sectorData[i].Data; + } + + // sector data (256 words data, 1 word cksum) + for (int i = _dataOffset + 1, j = 255; i < _dataOffset + 257; i++, j--) + { + // actual data to be loaded from disk / cksum calculated + sector.Data[j] = _sectorData[i].Data; + } + } + + private void InitSector() + { + // Fill in sector with default data (basically, fill in non-data areas). + + // + // header delay, 22 words + for (int i = 0; i < _headerOffset; i++) + { + _sectorData[i] = new DataCell(0, CellType.Gap); + } + + _sectorData[_headerOffset] = new DataCell(1, CellType.Sync); + // inter-reccord delay between header & label (10 words) + for (int i = _headerOffset + 4; i < _labelOffset; i++) + { + _sectorData[i] = new DataCell(0, CellType.Gap); + } + + _sectorData[_labelOffset] = new DataCell(1, CellType.Sync); + // inter-reccord delay between label & data (10 words) + for (int i = _labelOffset + 10; i < _dataOffset; i++) + { + _sectorData[i] = new DataCell(0, CellType.Gap); + } + + _sectorData[_dataOffset] = new DataCell(1, CellType.Sync); + // read-postamble + for (int i = _dataOffset + 258; i < _sectorWordCount; i++) + { + _sectorData[i] = new DataCell(0, CellType.Gap); + } + } + + private ushort CalculateChecksum(DataCell[] sectorData, int offset, int length) + { + // + // From the uCode, the Alto's checksum algorithm is: + // 1. Load checksum with constant value of 521B (0x151) + // 2. For each word in the record, cksum <- word XOR cksum + // 3. Profit + // + ushort checksum = 0x151; + + for (int i = offset; i < offset + length; i++) + { + // Sanity check that we're checksumming actual data + if (sectorData[i].Type != CellType.Data) + { + throw new InvalidOperationException("Attempt to checksum non-data area of sector."); + } + + checksum = (ushort)(checksum ^ sectorData[i].Data); + } + + return checksum; + } + + private AltoSystem _system; + + // + // Current disk position + // + private int _cylinder; + private int _head; + private int _sector; + + // offsets in words for start of data in sector + private const int _headerOffset = 22; + private const int _labelOffset = _headerOffset + 14; + private const int _dataOffset = _labelOffset + 20; + + private bool _sectorModified; + + private static int _sectorWordCount = 269 + 22 + 34; + + private DataCell[] _sectorData = new DataCell[_sectorWordCount]; + + + // The pack loaded into the drive + DiabloPack _pack; + } +} diff --git a/Contralto/IO/DiabloPack.cs b/Contralto/IO/DiabloPack.cs index 2e06379..0438e22 100644 --- a/Contralto/IO/DiabloPack.cs +++ b/Contralto/IO/DiabloPack.cs @@ -83,7 +83,7 @@ namespace Contralto.IO get { return _geometry; } } - public void Load(Stream imageStream) + public void Load(Stream imageStream, bool reverseByteOrder) { for(int cylinder = 0; cylinder < _geometry.Cylinders; cylinder++) { @@ -117,6 +117,13 @@ namespace Contralto.IO throw new InvalidOperationException("Short read while reading sector data."); } + if (reverseByteOrder) + { + SwapBytes(header); + SwapBytes(label); + SwapBytes(data); + } + _sectors[cylinder, track, sector] = new DiabloDiskSector(header, label, data); } } @@ -133,6 +140,16 @@ namespace Contralto.IO return _sectors[cylinder, track, sector]; } + private void SwapBytes(byte[] data) + { + for(int i=0;i /// TODO: this is messy; the read and write sides of KDATA are distinct hardware. /// According to docs, on a Write, eventually it appears on the Read side during an actual write to the disk - /// but not right away. For now, this never happens (since we don't yet support writing). + /// but not right away. /// public ushort KDATA { @@ -52,14 +64,9 @@ namespace Contralto.IO _syncWordWritten = false; // "In addition, it causes the head address bit to be loaded from KDATA[13]." - int newHead = (_kDataWrite & 0x4) >> 2; + int newHead = (_kDataWrite & 0x4) >> 2; - if (newHead != _head) - { - // If we switch heads, we need to reload the sector - _head = newHead; - LoadSector(); - } + SelectedDrive.Head = newHead; // "0 normally, 1 if the command is to terminate immediately after the correct cylinder // position is reached (before any data is transferred)." @@ -76,12 +83,17 @@ namespace Contralto.IO Log.Write(LogComponent.DiskController, " -Disk Address ({0}) is C/H/S {1}/{2}/{3}, Drive {4} Restore {5}", Conversion.ToOctal(_kDataWrite), (_kDataWrite & 0x0ff8) >> 3, - _head, + newHead, (_kDataWrite & 0xf000) >> 12, (_kDataWrite & 0x2) >> 1, (_kDataWrite & 0x1)); - Log.Write(LogComponent.DiskController, " -Selected disk is {0}", _disk); + //if ((_kAdr & 0x1) != 0) + { + //_disk = ((_kDataWrite & 0x2) >> 1); + } + + Log.Write(LogComponent.DiskController, " -Selected disk is {0}", _disk); if ((_kDataWrite & 0x1) != 0) { @@ -113,10 +125,11 @@ namespace Contralto.IO _wdInit = true; } - if (_sendAdr) + + if (_sendAdr & (_kDataWrite & 0x2) != 0) { // Select disk if _sendAdr is true - _disk = ((_kDataWrite & 0x2) >> 1) ^ (_kAdr & 0x1); + _disk = (_kAdr & 0x1); if (_disk != 0) { @@ -139,50 +152,50 @@ namespace Contralto.IO public ushort KSTAT { get - { + { // Bits 4-7 of KSTAT are always 1s (it's a shortcut allowing the disk microcode to write // "-1" to bits 4-7 of the disk status word at 522 without extra code.) return (ushort)(_kStat | (0x0f00)); } set { - _kStat = value; + _kStat = value; } } public ushort RECNO { - get { return _recMap[_recNo]; } + get { return _recMap[_recNo]; } } public bool DataXfer { get { return _dataXfer; } } - + public int Cylinder { - get { return _cylinder; } + get { return SelectedDrive.Cylinder; } } public int SeekCylinder { - get { return _destCylinder; } + get { return _destCylinder; } } public int Head { - get { return _head; } + get { return SelectedDrive.Head; } } public int Sector { - get { return _sector; } + get { return SelectedDrive.Sector; } } public int Drive { - get { return 0; } + get { return 0; } } public double ClocksUntilNextSector @@ -202,10 +215,8 @@ namespace Contralto.IO public void Reset() { ClearStatus(); - _recNo = 0; - _cylinder = _destCylinder = 0; + _recNo = 0; _sector = 0; - _head = 0; _disk = 0; _kStat = 0; _kDataRead = 0; @@ -219,21 +230,22 @@ namespace Contralto.IO _wdInit = false; _syncWordWritten = false; - _sectorModified = false; _diskBitCounterEnable = false; - _sectorWordIndex = 0; + _sectorWordIndex = 0; - InitSector(); + // Reset drives + _drives[0].Reset(); + _drives[1].Reset(); // Wakeup the sector task first thing _system.CPU.WakeupTask(CPU.TaskType.DiskSector); - + // Create events to be reused during execution _sectorEvent = new Event(_sectorDuration, null, SectorCallback); _wordEvent = new Event(_wordDuration, null, WordCallback); - _seekEvent = new Event(0, null, SeekCallback); _seclateEvent = new Event(_seclateDuration, null, SeclateCallback); + _seekEvent = new Event(_seekDuration, null, SeekCallback); // And schedule the first sector pulse. _system.Scheduler.Schedule(_sectorEvent); @@ -249,17 +261,10 @@ namespace Contralto.IO private void SectorCallback(ulong timeNsec, ulong skewNsec, object context) { - // Write last sector out if it was modified - if (_sectorModified) - { - CommitSector(); - _sectorModified = false; - } - // // Next sector; move to next sector and wake up Disk Sector task. // - _sector = (_sector + 1) % 12; + _sector = (_sector + 1) % 12; _kStat = (ushort)((_kStat & 0x0fff) | (_sector << 12)); @@ -267,15 +272,15 @@ namespace Contralto.IO _sectorWordIndex = 0; _syncWordWritten = false; - _kDataRead = 0; + _kDataRead = 0; // Load new sector in - LoadSector(); + SelectedDrive.Sector = _sector; // Only wake up if not actively seeking. if ((_kStat & 0x0040) == 0) { - Log.Write(LogType.Verbose, LogComponent.DiskController, "Waking up sector task for C/H/S {0}/{1}/{2}", _cylinder, _head, _sector); + Log.Write(LogType.Verbose, LogComponent.DiskController, "Waking up sector task for C/H/S {0}/{1}/{2}", SelectedDrive.Cylinder, SelectedDrive.Head, _sector); _system.CPU.WakeupTask(CPU.TaskType.DiskSector); // Reset SECLATE @@ -295,7 +300,7 @@ namespace Contralto.IO { // Schedule next sector pulse _sectorEvent.TimestampNsec = _sectorDuration - skewNsec; - _system.Scheduler.Schedule(_sectorEvent); + _system.Scheduler.Schedule(_sectorEvent); } } @@ -313,49 +318,19 @@ namespace Contralto.IO { // // Schedule next sector pulse immediately _sectorEvent.TimestampNsec = skewNsec; - _system.Scheduler.Schedule(_sectorEvent); - } - } - - private void SeekCallback(ulong timeNsec, ulong skewNsec, object context) - { - if (_cylinder < _destCylinder) - { - _cylinder++; - } - else if (_cylinder > _destCylinder) - { - _cylinder--; - } - - Log.Write(LogComponent.DiskController, "Seek progress: cylinder {0} reached.", _cylinder); - - // Are we *there* yet? - if (_cylinder == _destCylinder) - { - // clear Seek bit - _kStat &= 0xffbf; - - Log.Write(LogComponent.DiskController, "Seek to {0} completed.", _cylinder); - } - else - { - // Nope. - // Schedule next seek step. - _seekEvent.TimestampNsec = _seekDuration - skewNsec; - _system.Scheduler.Schedule(_seekEvent); + _system.Scheduler.Schedule(_sectorEvent); } } private void SeclateCallback(ulong timeNsec, ulong skewNsec, object context) { if (_seclateEnable) - { + { _seclate = true; _kStat |= 0x0010; // TODO: move to constant field! Log.Write(LogComponent.DiskSectorTask, "SECLATE for sector {0}.", _sector); } - } + } public void ClearStatus() { @@ -388,7 +363,7 @@ namespace Contralto.IO // "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.) @@ -398,25 +373,24 @@ namespace Contralto.IO } Log.Write(LogComponent.DiskController, "STROBE: Seek initialized."); - - InitSeek((_kDataWrite & 0x0ff8) >> 3); + + InitSeek((_kDataWrite & 0x0ff8) >> 3); } private void InitSeek(int destCylinder) { - _destCylinder = destCylinder; - // set "seek fail" bit based on selected cylinder (if out of bounds) and do not // commence a seek if so. - if (_destCylinder > 202) + if (destCylinder > 202) { _kStat |= 0x0080; - Log.Write(LogComponent.DiskController, "Seek failed, specified cylinder {0} is out of range.", _destCylinder); + Log.Write(LogComponent.DiskController, "Seek failed, specified cylinder {0} is out of range.", destCylinder); } else { // Otherwise, start a seek. + _destCylinder = destCylinder; // Clear the fail bit. _kStat &= 0xff7f; @@ -425,29 +399,15 @@ namespace Contralto.IO _kStat |= 0x0040; // And figure out how long this will take. - _seekDuration = (ulong)(CalculateSeekTime() / (ulong)(Math.Abs(_destCylinder - _cylinder) + 1)); + _seekDuration = (ulong)(CalculateSeekTime() / (ulong)(Math.Abs(_destCylinder - SelectedDrive.Cylinder) + 1)); _seekEvent.TimestampNsec = _seekDuration; _system.Scheduler.Schedule(_seekEvent); - Log.Write(LogComponent.DiskController, "Seek to {0} from {1} commencing. Will take {2} nsec.", _destCylinder, _cylinder, _seekDuration); + Log.Write(LogComponent.DiskController, "Seek to {0} from {1} commencing. Will take {2} nsec.", _destCylinder, SelectedDrive.Cylinder, _seekDuration); } } - private ulong 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 (ulong)(seekTimeMsec * Conversion.MsecToNsec); - } - /// /// "Rotates" the emulated disk platter one clock's worth. /// @@ -478,7 +438,7 @@ namespace Contralto.IO // and we may not actually end up doing anything with it, but we may // need it to decide whether to do anything at all. // - ushort diskWord = _sectorData[_sectorWordIndex].Data; + DataCell diskWord = SelectedDrive.ReadWord(_sectorWordIndex); bool bWakeup = false; // @@ -486,8 +446,8 @@ namespace Contralto.IO // then we will wake up the word task now. // if (!_seclate && !_wdInhib && !_bClkSource) - { - bWakeup = true; + { + bWakeup = true; } // @@ -509,9 +469,9 @@ namespace Contralto.IO Log.Write(LogType.Warning, LogComponent.DiskController, "--- missed sector word {0}({1}) ---", _sectorWordIndex, _kDataRead); } - Log.Write(LogType.Verbose, LogComponent.DiskWordTask, "Sector {0} Word {1} read into KDATA", _sector, Conversion.ToOctal(diskWord)); - _kDataRead = diskWord; - _debugRead = _sectorData[_sectorWordIndex].Type == CellType.Data; + Log.Write(LogType.Verbose, LogComponent.DiskWordTask, "Sector {0} Word {1} read into KDATA", _sector, Conversion.ToOctal(diskWord.Data)); + _kDataRead = diskWord.Data; + _debugRead = diskWord.Type == CellType.Data; } else { @@ -524,18 +484,10 @@ namespace Contralto.IO _kDataWriteLatch = false; } - if (_syncWordWritten && _sectorWordIndex < _sectorData.Length) + if (_syncWordWritten) { - if (_sectorData[_sectorWordIndex].Type == CellType.Data) - { - _sectorData[_sectorWordIndex].Data = _kDataWrite; - } - else - { - Log.Write(LogType.Warning, LogComponent.DiskController, "Data written to non-data section (Sector {0} Word {1} Rec {2} Data {3})", _sector, _sectorWordIndex, _recNo, Conversion.ToOctal(_kDataWrite)); - } - - _sectorModified = true; + // Commit actual data to disk now that the sync word has been laid down + SelectedDrive.WriteWord(_sectorWordIndex, _kDataWrite); } } } @@ -552,8 +504,8 @@ namespace Contralto.IO // the clock. This occurs late in the cycle so that the NEXT word // (not the sync word) is actually read. TODO: this should only happen on reads. // - if (!IsWrite() && !_wffo && diskWord == 1) - { + if (!IsWrite() && !_wffo && diskWord.Data == 1) + { _diskBitCounterEnable = true; } else if (IsWrite() && _wffo && _kDataWrite == 1 && !_syncWordWritten) @@ -563,7 +515,7 @@ namespace Contralto.IO // "Adjust" the write index to the start of the data area for the current record. // This is cheating. - switch(_recNo) + switch (_recNo) { case 0: _sectorWordIndex = _headerOffset; @@ -583,143 +535,11 @@ namespace Contralto.IO { Log.Write(LogType.Verbose, LogComponent.DiskWordTask, "Word task awoken for word {0}.", _sectorWordIndex); _system.CPU.WakeupTask(TaskType.DiskWord); - } + } // Last, move to the next word. _sectorWordIndex++; - - } - private void LoadSector() - { - // - // Pull data off disk and pack it into our faked-up sector. - // Note that this data is packed in in REVERSE ORDER because that's - // how it gets written out and it's how the Alto expects it to be read back in. - // - DiabloDiskSector sector = _pack.GetSector(_cylinder, _head, _sector); - - // Header (2 words data, 1 word cksum) - for (int i = _headerOffset + 1, j = 1; i < _headerOffset + 3; i++, j--) - { - // actual data to be loaded from disk / cksum calculated - _sectorData[i] = new DataCell(sector.Header[j], CellType.Data); - } - - ushort checksum = CalculateChecksum(_sectorData, _headerOffset + 1, 2); - _sectorData[_headerOffset + 3].Data = checksum; - Log.Write(LogType.Verbose, LogComponent.DiskController, "Header checksum for C/H/S {0}/{1}/{2} is {3}", _cylinder, _head, _sector, Conversion.ToOctal(checksum)); - - // Label (8 words data, 1 word cksum) - for (int i = _labelOffset + 1, j = 7; i < _labelOffset + 9; i++, j--) - { - // actual data to be loaded from disk / cksum calculated - _sectorData[i] = new DataCell(sector.Label[j], CellType.Data); - } - - checksum = CalculateChecksum(_sectorData, _labelOffset + 1, 8); - _sectorData[_labelOffset + 9].Data = checksum; - Log.Write(LogType.Verbose, LogComponent.DiskController, "Label checksum for C/H/S {0}/{1}/{2} is {3}", _cylinder, _head, _sector, Conversion.ToOctal(checksum)); - - // sector data (256 words data, 1 word cksum) - for (int i = _dataOffset + 1, j = 255; i < _dataOffset + 257; i++, j--) - { - // actual data to be loaded from disk / cksum calculated - _sectorData[i] = new DataCell(sector.Data[j], CellType.Data); - } - - checksum = CalculateChecksum(_sectorData, _dataOffset + 1, 256); - _sectorData[_dataOffset + 257].Data = checksum; - Log.Write(LogType.Verbose, LogComponent.DiskController, "Data checksum for C/H/S {0}/{1}/{2} is {3}", _cylinder, _head, _sector, Conversion.ToOctal(checksum)); - - } - - /// - /// Commits modified sector data back to the emulated disk. - /// Intended to be called at the end of the sector / beginning of the next. - /// TODO: we should modify this so that checksums are persisted, possibly... - /// - private void CommitSector() - { - DiabloDiskSector sector = _pack.GetSector(_cylinder, _head, _sector); - - // Header (2 words data, 1 word cksum) - for (int i = _headerOffset + 1, j = 1; i < _headerOffset + 3; i++, j--) - { - // actual data to be loaded from disk / cksum calculated - sector.Header[j] = _sectorData[i].Data; - } - - // Label (8 words data, 1 word cksum) - for (int i = _labelOffset + 1, j = 7; i < _labelOffset + 9; i++, j--) - { - // actual data to be loaded from disk / cksum calculated - sector.Label[j] = _sectorData[i].Data; - } - - // sector data (256 words data, 1 word cksum) - for (int i = _dataOffset + 1, j = 255; i < _dataOffset + 257; i++, j--) - { - // actual data to be loaded from disk / cksum calculated - sector.Data[j] = _sectorData[i].Data; - } - } - - private void InitSector() - { - // Fill in sector with default data (basically, fill in non-data areas). - - // - // header delay, 22 words - for (int i=0; i < _headerOffset; i++) - { - _sectorData[i] = new DataCell(0, CellType.Gap); - } - - _sectorData[_headerOffset] = new DataCell(1, CellType.Sync); - // inter-reccord delay between header & label (10 words) - for (int i = _headerOffset + 4; i < _labelOffset; i++) - { - _sectorData[i] = new DataCell(0, CellType.Gap); - } - - _sectorData[_labelOffset] = new DataCell(1, CellType.Sync); - // inter-reccord delay between label & data (10 words) - for (int i = _labelOffset + 10; i < _dataOffset; i++) - { - _sectorData[i] = new DataCell(0, CellType.Gap); - } - - _sectorData[_dataOffset] = new DataCell(1, CellType.Sync); - // read-postamble - for (int i = _dataOffset + 258; i < _sectorWordCount;i++) - { - _sectorData[i] = new DataCell(0, CellType.Gap); - } - } - - private ushort CalculateChecksum(DataCell[] sectorData, int offset, int length) - { - // - // From the uCode, the Alto's checksum algorithm is: - // 1. Load checksum with constant value of 521B (0x151) - // 2. For each word in the record, cksum <- word XOR cksum - // 3. Profit - // - ushort checksum = 0x151; - - for(int i = offset; i < offset + length;i++) - { - // Sanity check that we're checksumming actual data - if (sectorData[i].Type != CellType.Data) - { - throw new InvalidOperationException("Attempt to checksum non-data area of sector."); - } - - checksum = (ushort)(checksum ^ sectorData[i].Data); - } - - return checksum; } private bool IsWrite() @@ -727,6 +547,55 @@ namespace Contralto.IO return ((_kAdr & 0x00c0) >> 6) == 2 || ((_kAdr & 0x00c0) >> 6) == 3; } + private void SeekCallback(ulong timeNsec, ulong skewNsec, object context) + { + if (SelectedDrive.Cylinder < _destCylinder) + { + SelectedDrive.Cylinder++; + } + else if (SelectedDrive.Cylinder > _destCylinder) + { + SelectedDrive.Cylinder--; + } + + Log.Write(LogComponent.DiskController, "Seek progress: cylinder {0} reached.", SelectedDrive.Cylinder); + + // Are we *there* yet? + if (SelectedDrive.Cylinder == _destCylinder) + { + // clear Seek bit + _kStat &= 0xffbf; + + Log.Write(LogComponent.DiskController, "Seek to {0} completed.", SelectedDrive.Cylinder); + } + else + { + // Nope. + // Schedule next seek step. + _seekEvent.TimestampNsec = _seekDuration - skewNsec; + _system.Scheduler.Schedule(_seekEvent); + } + } + + private ulong CalculateSeekTime() + { + // How many cylinders are we moving? + int dt = Math.Abs(_destCylinder - SelectedDrive.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 (ulong)(seekTimeMsec * Conversion.MsecToNsec); + } + + private Diablo30Drive SelectedDrive + { + get { return _drives[_disk]; } + } + private ushort _kDataRead; private ushort _kDataWrite; private bool _kDataWriteLatch; @@ -750,12 +619,16 @@ namespace Contralto.IO // Transfer bit private bool _dataXfer; - // Current disk position - private int _cylinder; - private int _destCylinder; - private int _head; + // Current sector private int _sector; + // + // Seek state + // + private int _destCylinder; + private ulong _seekDuration; + private Event _seekEvent; + // Selected disk private int _disk; @@ -766,7 +639,6 @@ namespace Contralto.IO private bool _wdInit; private bool _syncWordWritten; - private bool _sectorModified; // Sector timing. Based on table on pg. 43 of the Alto Hardware Manual @@ -785,7 +657,7 @@ namespace Contralto.IO private Event _sectorEvent; private Event _wordEvent; - + // offsets in words for start of data in sector private const int _headerOffset = 22; @@ -797,44 +669,10 @@ namespace Contralto.IO private static ulong _seclateDuration = (ulong)(85.0 * Conversion.UsecToNsec * _scale); private bool _seclateEnable; private bool _seclate; - private Event _seclateEvent; + private Event _seclateEvent; - // Cylinder seek time (in nsec) Again, see the manual. - // Timing varies based on how many cylinders are being traveled during a seek; see - // CalculateSeekTime() for more. - private ulong _seekDuration; - private Event _seekEvent; - - // The data for the current sector - private enum CellType - { - Data, - Gap, - Sync, - } - - private struct DataCell - { - public DataCell(ushort data, CellType type) - { - Data = data; - Type = type; - } - - public ushort Data; - public CellType Type; - - public override string ToString() - { - return String.Format("{0} {1}", Data, Type); - } - } - - private DataCell[] _sectorData = new DataCell[_sectorWordCount]; - - - // The pack loaded into the drive - DiabloPack _pack; + // Attached drives + private Diablo30Drive[] _drives; private AltoSystem _system; diff --git a/Contralto/Logging/Log.cs b/Contralto/Logging/Log.cs index 721d9a6..ddfd896 100644 --- a/Contralto/Logging/Log.cs +++ b/Contralto/Logging/Log.cs @@ -44,7 +44,7 @@ namespace Contralto.Logging static Log() { // TODO: make configurable - _components = LogComponent.None; // LogComponent.DiskController | LogComponent.DiskSectorTask; + _components = LogComponent.DiskController | LogComponent.DiskSectorTask; _type = LogType.Normal | LogType.Warning | LogType.Error; } diff --git a/Contralto/Program.cs b/Contralto/Program.cs index d79f369..1e0eb53 100644 --- a/Contralto/Program.cs +++ b/Contralto/Program.cs @@ -6,14 +6,23 @@ namespace Contralto { static void Main(string[] args) { - AltoSystem system = new AltoSystem(); + AltoSystem system = new AltoSystem(); - // for now everything is driven through the debugger + // for now everything is driven through the debugger + + AltoWindow mainWindow = new AltoWindow(); + + mainWindow.AttachSystem(system); + + /* Debugger d = new Debugger(system); system.AttachDisplay(d); d.LoadSourceCode(MicrocodeBank.ROM0, "Disassembly\\altoIIcode3.mu"); d.LoadSourceCode(MicrocodeBank.ROM1, "Disassembly\\MesaROM.mu"); d.ShowDialog(); + */ + mainWindow.ShowDialog(); + } }