1
0
mirror of https://github.com/livingcomputermuseum/ContrAlto.git synced 2026-02-22 15:18:06 +00:00

Refactored drive logic (mostly) to allow for multiple drives. Fixed drive selection logic. Started work on "real" UI.

This commit is contained in:
Josh Dersch
2015-12-11 16:57:02 -08:00
parent 2ee3d64f6c
commit 115432516f
14 changed files with 936 additions and 318 deletions

View File

@@ -65,14 +65,11 @@ namespace Contralto
/// <summary>
/// Attaches an emulated display device to the system.
/// TODO: This is currently tightly-coupled with the Debugger, make
/// more general.
/// </summary>
/// <param name="d"></param>
public void AttachDisplay(Debugger d)
public void AttachDisplay(IAltoDisplay d)
{
_displayController.AttachDisplay(d);
// _fakeDisplayController.AttachDisplay(d);
_displayController.AttachDisplay(d);
}
public void SingleStep()

263
Contralto/AltoWindow.Designer.cs generated Normal file
View File

@@ -0,0 +1,263 @@
namespace Contralto
{
partial class AltoWindow
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
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;
}
}

126
Contralto/AltoWindow.resx Normal file
View File

@@ -0,0 +1,126 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<metadata name="menuStrip1.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<value>17, 17</value>
</metadata>
<metadata name="StatusLine.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<value>126, 17</value>
</metadata>
</root>

View File

@@ -64,6 +64,12 @@
</ItemGroup>
<ItemGroup>
<Compile Include="AltoSystem.cs" />
<Compile Include="AltoWindow.cs">
<SubType>Form</SubType>
</Compile>
<Compile Include="AltoWindow.Designer.cs">
<DependentUpon>AltoWindow.cs</DependentUpon>
</Compile>
<Compile Include="CPU\ALU.cs" />
<Compile Include="CPU\ConstantMemory.cs" />
<Compile Include="CPU\CPU.cs" />
@@ -88,7 +94,9 @@
</Compile>
<Compile Include="Display\FakeDisplayController.cs" />
<Compile Include="Display\DisplayController.cs" />
<Compile Include="Display\IAltoDisplay.cs" />
<Compile Include="IClockable.cs" />
<Compile Include="IO\Diablo30Drive.cs" />
<Compile Include="IO\DiskController.cs" />
<Compile Include="IO\DiabloPack.cs" />
<Compile Include="IO\Keyboard.cs" />
@@ -114,6 +122,9 @@
<None Include="Disk\bcpl.dsk">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="Disk\bravox.dsk">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="Disk\Clark-Games.dsk">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
@@ -260,6 +271,9 @@
</None>
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="AltoWindow.resx">
<DependentUpon>AltoWindow.cs</DependentUpon>
</EmbeddedResource>
<EmbeddedResource Include="Debugger.resx">
<DependentUpon>Debugger.cs</DependentUpon>
</EmbeddedResource>

View File

@@ -12,13 +12,14 @@ using Contralto.CPU;
using System.Threading;
using System.Drawing.Imaging;
using Contralto.IO;
using Contralto.Display;
namespace Contralto
{
/// <summary>
/// A basic & hacky debugger. To be improved.
/// </summary>
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));
}

BIN
Contralto/Disk/bravox.dsk Normal file

Binary file not shown.

View File

@@ -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<ushort> _dataBuffer = new Queue<ushort>(16);
private AltoSystem _system;
private Debugger _display;
private IAltoDisplay _display;
private int _fields;

View File

@@ -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.

View File

@@ -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
{
/// <summary>
/// Renders a word's worth of data to the specified scanline and word offset.
/// </summary>
/// <param name="scanline"></param>
/// <param name="wordOffset"></param>
/// <param name="dataWord"></param>
/// <param name="lowRes"></param>
void DrawDisplayWord(int scanline, int wordOffset, ushort dataWord, bool lowRes);
/// <summary>
/// Causes the display to be rendered
/// </summary>
void Render();
}
}

View File

@@ -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);
}
}
/// <summary>
/// Encapsulates logic that belongs to the drive, including loading/saving packs,
/// seeking and reading sector data.
/// </summary>
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));
}
/// <summary>
/// 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...
/// </summary>
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;
}
}

View File

@@ -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<data.Length;i+=2)
{
byte t = data[i];
data[i] = data[i + 1];
data[i + 1] = t;
}
}
private DiabloDiskSector[,,] _sectors;
private DiabloDiskType _diskType;
private DiskGeometry _geometry;

View File

@@ -10,23 +10,35 @@ namespace Contralto.IO
public DiskController(AltoSystem system)
{
_system = system;
Reset();
// Load the pack
_pack = new DiabloPack(DiabloDiskType.Diablo31);
// Load the drives
_drives = new Diablo30Drive[2];
_drives[0] = new Diablo30Drive(_system);
_drives[1] = new Diablo30Drive(_system);
// TODO: this does not belong here.
FileStream fs = new FileStream("Disk\\nonprog.dsk", FileMode.Open, FileAccess.Read);
DiabloPack p0 = new DiabloPack(DiabloDiskType.Diablo31);
FileStream fs = new FileStream("Disk\\diag.dsk", FileMode.Open, FileAccess.Read);
p0.Load(fs, false);
fs.Close();
_pack.Load(fs);
_drives[0].LoadPack(p0);
fs.Close();
}
DiabloPack p1 = new DiabloPack(DiabloDiskType.Diablo31);
fs = new FileStream("Disk\\bravox.dsk", FileMode.Open, FileAccess.Read);
p1.Load(fs, true);
fs.Close();
_drives[1].LoadPack(p1);
Reset();
}
/// <summary>
/// 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.
/// </summary>
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);
}
/// <summary>
/// "Rotates" the emulated disk platter one clock's worth.
/// </summary>
@@ -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));
}
/// <summary>
/// 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...
/// </summary>
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;

View File

@@ -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;
}

View File

@@ -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();
}
}