1
0
mirror of https://github.com/livingcomputermuseum/ContrAlto.git synced 2026-04-28 04:46:08 +00:00

- Fixed two memory state machine issues:

1) The Orbit microcode occasionally expects to be able to do a double-word read in cycles 6 and 7 instead of the usual 5 and 6.
  2) The double-word logic incorrectly handles several species of overlapped read/writes, which Spruce's layout microcode uses.

As a result, Spruce now works, as do the Smalltalk-80 images.

Several tweaks to microcode disassembly (still rough).

Completed Orbit / Dover ROS / Dover Print Engine emulation.  Still some rough edges; it works and can create PDF output.

Tweaked Disk Controller's Restore operation, it is now more accurate though wasn't causing any noticable issues.
This commit is contained in:
Josh Dersch
2017-07-10 16:49:12 -07:00
parent c671b04ee8
commit f849b795a6
32 changed files with 1437 additions and 951 deletions

View File

@@ -242,8 +242,6 @@ namespace Contralto
get { return _scheduler; }
}
public int _novaInst;
private AltoCPU _cpu;
private MemoryBus _memBus;
private Memory.Memory _mem;

View File

@@ -58,6 +58,15 @@
<setting name="AudioDACCapturePath" serializeAs="String">
<value />
</setting>
<setting name="EnablePrinting" serializeAs="String">
<value>False</value>
</setting>
<setting name="PrintOutputPath" serializeAs="String">
<value />
</setting>
<setting name="ReversePageOrder" serializeAs="String">
<value>True</value>
</setting>
</Contralto.Properties.Settings>
</userSettings>
</configuration>

View File

@@ -160,7 +160,7 @@ namespace Contralto.CPU
case InstructionCompletion.MemoryWait:
// We were waiting for memory on this cycle, we do nothing
// (no task switch even if one is pending) in this case.
// (no task switch even if one is pending) in this case.
break;
}
}
@@ -187,13 +187,13 @@ namespace Contralto.CPU
UCodeMemory.LoadBanksFromRMR(_rmr);
// Reset RMR after reset.
_rmr = 0x0;
_rmr = 0xffff;
// Start in Emulator
_currentTask = _tasks[0];
//
// TODO:
// TODO:
// This is a hack of sorts, it ensures that the sector task initializes
// itself as soon as the Emulator task yields after the reset. (CopyDisk is broken otherwise due to the
// sector task stomping over the KBLK CopyDisk sets up after the reset. This is a race of sorts.)
@@ -201,6 +201,7 @@ namespace Contralto.CPU
// in play that are not understood.
//
WakeupTask(CPU.TaskType.DiskSector);
BlockTask(CPU.TaskType.DiskWord);
}
/// <summary>
@@ -250,7 +251,20 @@ namespace Contralto.CPU
public Task NextTask
{
get { return _nextTask; }
}
}
public bool InternalBreak
{
get
{
return _internalBreak;
}
set
{
_internalBreak = value;
}
}
private void TaskSwitch()
{
@@ -295,6 +309,8 @@ namespace Contralto.CPU
private Task[] _tasks = new Task[16];
// The system this CPU belongs to
private AltoSystem _system;
private AltoSystem _system;
private bool _internalBreak;
}
}

View File

@@ -111,7 +111,7 @@ namespace Contralto.CPU
// bus specifies the initial mode of task 15, the highest priority task(recall that task i starts at location i; the
// reset mode register determines only which microinstruction bank will be used at the outset). A task will
// commence in ROM0 if its associated bit in the reset mode register contains the value 1; otherwise it will
// start in RAM0.Upon initial power - up of the Alto, and after each reset operation, the reset mode register
// start in RAM0. Upon initial power-up of the Alto, and after each reset operation, the reset mode register
// is automatically set to all ones, corresponding to starting all tasks in ROM0."
//
_cpu._rmr = _busData;
@@ -125,11 +125,7 @@ namespace Contralto.CPU
// Dispatch function to Ethernet I/O based on contents of AC0.
if ((_busData & 0x8000) != 0)
{
//
// BOOT (soft-reset) operation.
// Reset the CPU using the current RMR (start tasks in RAM or ROM as specified.)
_cpu.SoftReset();
// Since this is a soft reset, we don't want MPC to be taken from the NEXT
// field at the end of the cycle, setting this flag causes the main Task
// implementation to skip updating _mpc at the end of this instruction.
@@ -150,7 +146,7 @@ namespace Contralto.CPU
break;
case 4:
// Orbit
// Orbit
_cpu._system.OrbitController.STARTF(_busData);
break;

View File

@@ -33,24 +33,10 @@ namespace Contralto.CPU
_wakeup = false;
}
public override void OnTaskSwitch()
{
// We put ourselves back to sleep immediately once we've started running.
//_wakeup = false;
}
protected override void ExecuteBlock()
{
//_wakeup = false;
_cpu._system.OrbitController.Stop();
}
protected override InstructionCompletion ExecuteInstruction(MicroInstruction instruction)
{
// TODO: get rid of polling.
//_wakeup = _cpu._system.OrbitController.Wakeup;
return base.ExecuteInstruction(instruction);
}
}
protected override ushort GetBusSource(MicroInstruction instruction)
{
@@ -80,7 +66,7 @@ namespace Contralto.CPU
// "When an S register is being loaded from M, the processor bus receives an
// undefined value rather than being set to zero."
_loadS = true;
return 0x0; // Technically this is an "undefined value," we're defining it as -1.
return 0xffff; // Technically this is an "undefined value," we're defining it as -1.
default:
throw new InvalidOperationException(String.Format("Unhandled bus source {0}", instruction.BS));

View File

@@ -142,13 +142,14 @@ namespace Contralto.CPU
bool swMode = false;
ushort aluData;
ushort nextModifier;
bool softReset = _softReset;
_loadR = false;
_loadS = false;
_rSelect = 0;
_busData = 0;
_softReset = false;
Shifter.Reset();
Shifter.Reset();
//
// Wait for memory state machine if a memory operation is requested by this instruction and
@@ -197,10 +198,10 @@ namespace Contralto.CPU
break;
case BusSource.ReadMD:
_busData = _cpu._system.MemoryBus.ReadMD();
_busData = _cpu._system.MemoryBus.ReadMD();
break;
case BusSource.ReadMouse:
case BusSource.ReadMouse:
// "BUS[12-15]<-MOUSE; BUS[0-13]<- -1"
// (Note -- BUS[0-13] appears to be a typo, and should likely be BUS[0-11]).
_busData = (ushort)(_cpu._system.Mouse.PollMouseBits() | 0xfff0);
@@ -533,10 +534,14 @@ namespace Contralto.CPU
// Select next address, using the address modifier from the last instruction.
// (Unless a soft reset occurred during this instruction)
//
if (!_softReset)
if (!softReset)
{
_mpc = (ushort)(instruction.NEXT | nextModifier);
}
else
{
_cpu.SoftReset();
}
_firstInstructionAfterSwitch = false;
return completion;

View File

@@ -250,10 +250,10 @@ namespace Contralto.CPU
//
// Load T
bool loadTFromALU = false;
if (instruction.LoadT)
{
// Does this operation change the source for T?
bool loadTFromALU = false;
// Does this operation change the source for T?
switch (instruction.ALUF)
{
case AluFunction.Bus:
@@ -275,11 +275,18 @@ namespace Contralto.CPU
{
if (string.IsNullOrEmpty(load))
{
// T not loaded at all, L loaded from ALU
load = String.Format("L← {0}", operation);
}
else if (loadTFromALU)
{
// T loaded from ALU, L loaded from ALU
load = String.Format("L← {0}", load);
}
else
{
load = String.Format("L← {0}", load);
// T loaded from bus source, L loaded from ALU
load = String.Format("L← {0}, {1}", operation, load);
}
}
@@ -287,8 +294,8 @@ namespace Contralto.CPU
if (loadR)
{
load = String.Format("$R{0}← {1}",
Conversion.ToOctal((int)rSelect),
load != String.Empty ? load : operation);
Conversion.ToOctal((int)rSelect),
!string.IsNullOrEmpty(load) ? load : operation);
}
// Do writeback to selected S register from M
@@ -307,7 +314,18 @@ namespace Contralto.CPU
}
}
if (!string.IsNullOrEmpty(load))
// Test for a NOP-like instruction.
if (!instruction.LoadL &&
!instruction.LoadT &&
!loadR &&
!loadS &&
instruction.F1 == SpecialFunction1.None &&
instruction.F2 == SpecialFunction2.None &&
instruction.ALUF == AluFunction.Bus)
{
disassembly.AppendFormat("NOP :{0}", Conversion.ToOctal(instruction.NEXT));
}
else if (!string.IsNullOrEmpty(load))
{
disassembly.AppendFormat("{0}{1}{2} :{3}",
f1,
@@ -333,7 +351,8 @@ namespace Contralto.CPU
switch(task)
{
case TaskType.Emulator:
return DisassembleEmulatorBusSource(instruction, out loadS);
case TaskType.Orbit:
return DisassembleEmulatorBusSource(instruction, out loadS);
default:
loadS = false;
@@ -346,7 +365,10 @@ namespace Contralto.CPU
switch (task)
{
case TaskType.Emulator:
return DisassembleEmulatorSpecialFunction1(instruction);
return DisassembleEmulatorSpecialFunction1(instruction);
case TaskType.Orbit:
return DisassembleOrbitSpecialFunction1(instruction);
default:
return String.Format("F1 {0}", Conversion.ToOctal((int)instruction.F1));
@@ -358,7 +380,10 @@ namespace Contralto.CPU
switch (task)
{
case TaskType.Emulator:
return DisassembleEmulatorSpecialFunction2(instruction);
return DisassembleEmulatorSpecialFunction2(instruction);
case TaskType.Orbit:
return DisassembleOrbitSpecialFunction2(instruction);
default:
return String.Format("F2 {0}", Conversion.ToOctal((int)instruction.F2));
@@ -373,7 +398,15 @@ namespace Contralto.CPU
{
case EmulatorBusSource.ReadSLocation:
loadS = false;
return String.Format("$S{0}", Conversion.ToOctal((int)instruction.RSELECT));
if (instruction.RSELECT == 0)
{
return "M";
}
else
{
return String.Format("$S{0}", Conversion.ToOctal((int)instruction.RSELECT));
}
case EmulatorBusSource.LoadSLocation:
loadS = true;
@@ -415,7 +448,7 @@ namespace Contralto.CPU
return "STARTF ";
default:
return String.Format("F1 {0}", Conversion.ToOctal((int)ef1));
return String.Format("Emulator F1 {0}", Conversion.ToOctal((int)ef1));
}
}
@@ -448,7 +481,62 @@ namespace Contralto.CPU
return "IDISP ";
default:
return String.Format("F2 {0}", Conversion.ToOctal((int)ef2));
return String.Format("Emulator F2 {0}", Conversion.ToOctal((int)ef2));
}
}
private static string DisassembleOrbitSpecialFunction1(MicroInstruction instruction)
{
OrbitF1 of1 = (OrbitF1)instruction.F1;
switch(of1)
{
case OrbitF1.OrbitBlock:
return "OrbitBlock ";
case OrbitF1.OrbitDeltaWC:
return "←OrbitDeltaWC ";
case OrbitF1.OrbitDBCWidthRead:
return "←OrbitDBCWidthRead ";
case OrbitF1.OrbitStatus:
return "←OrbitStatus ";
default:
return String.Format("Orbit F1 {0}", Conversion.ToOctal((int)of1));
}
}
private static string DisassembleOrbitSpecialFunction2(MicroInstruction instruction)
{
OrbitF2 of2 = (OrbitF2)instruction.F2;
switch (of2)
{
case OrbitF2.OrbitDBCWidthSet:
return "OrbitDBCWidthSet← ";
case OrbitF2.OrbitXY:
return "OrbitXY← ";
case OrbitF2.OrbitHeight:
return "OrbitHeight← ";
case OrbitF2.OrbitFontData:
return "OrbitFontData← ";
case OrbitF2.OrbitInk:
return "OrbitInk← ";
case OrbitF2.OrbitControl:
return "OrbitControl← ";
case OrbitF2.OrbitROSCommand:
return "OrbitROSCommand← ";
default:
return String.Format("Orbit F2 {0}", Conversion.ToOctal((int)of2));
}
}
}

View File

@@ -204,6 +204,21 @@ namespace Contralto
/// </summary>
public static string AudioDACCapturePath;
/// <summary>
/// Whether to enable printing via the Orbit / DoverROS interface.
/// </summary>
public static bool EnablePrinting;
/// <summary>
/// Path for print output.
/// </summary>
public static string PrintOutputPath;
/// <summary>
/// Whether to reverse the page order when printing.
/// </summary>
public static bool ReversePageOrder;
/// <summary>
/// The components to enable debug logging for.
/// </summary>
@@ -291,6 +306,9 @@ namespace Contralto
EnableAudioDAC = Properties.Settings.Default.EnableAudioDAC;
EnableAudioDACCapture = Properties.Settings.Default.EnableAudioDACCapture;
AudioDACCapturePath = Properties.Settings.Default.AudioDACCapturePath;
EnablePrinting = Properties.Settings.Default.EnablePrinting;
PrintOutputPath = Properties.Settings.Default.PrintOutputPath;
ReversePageOrder = Properties.Settings.Default.ReversePageOrder;
}
private static void WriteConfigurationWindows()
@@ -308,8 +326,10 @@ namespace Contralto
Properties.Settings.Default.ThrottleSpeed = ThrottleSpeed;
Properties.Settings.Default.EnableAudioDAC = EnableAudioDAC;
Properties.Settings.Default.EnableAudioDACCapture = EnableAudioDACCapture;
Properties.Settings.Default.AudioDACCapturePath = AudioDACCapturePath;
Properties.Settings.Default.AudioDACCapturePath = Properties.Settings.Default.AudioDACCapturePath;
Properties.Settings.Default.AudioDACCapturePath = AudioDACCapturePath;
Properties.Settings.Default.EnablePrinting = EnablePrinting;
Properties.Settings.Default.PrintOutputPath = PrintOutputPath;
Properties.Settings.Default.ReversePageOrder = ReversePageOrder;
Properties.Settings.Default.Save();
}

View File

@@ -18,3 +18,8 @@ InterlaceDisplay = False
BootAddress = 0
BootFile = 0
AlternateBootType = Ethernet
# Printing options
EnablePrinting = true
PrintOutputPath = .
ReversePageOrder = true

View File

@@ -97,6 +97,9 @@
<ApplicationManifest>app.manifest</ApplicationManifest>
</PropertyGroup>
<ItemGroup>
<Reference Include="itextsharp, Version=5.5.11.0, Culture=neutral, PublicKeyToken=8354ae6d2174ddca, processorArchitecture=MSIL">
<HintPath>..\packages\iTextSharp.5.5.11\lib\itextsharp.dll</HintPath>
</Reference>
<Reference Include="NAudio, Version=1.8.0.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\packages\NAudio.1.8.0\lib\net35\NAudio.dll</HintPath>
<Private>True</Private>
@@ -127,6 +130,9 @@
<Compile Include="IO\AudioDAC.cs" />
<Compile Include="IO\OrbitController.cs" />
<Compile Include="IO\OrganKeyboard.cs" />
<Compile Include="IO\Printing\IPageSink.cs" />
<Compile Include="IO\Printing\NullPageSink.cs" />
<Compile Include="IO\Printing\PdfPageSink.cs" />
<Compile Include="IO\UDPEncapsulation.cs" />
<Compile Include="IO\IPacketEncapsulation.cs" />
<Compile Include="Properties\Resources.Designer.cs">

View File

@@ -115,8 +115,8 @@ namespace Contralto.IO
if ((_kDataWrite & 0x1) != 0)
{
// Restore operation to cyl. 0:
InitSeek(0);
}
_restore = true;
}
}
}
@@ -145,7 +145,7 @@ namespace Contralto.IO
if (_sendAdr & (_kDataWrite & 0x2) != 0)
{
_seeking = false;
_seeking = false;
}
}
@@ -244,6 +244,7 @@ namespace Contralto.IO
_kDataWriteLatch = false;
_sendAdr = false;
_seeking = false;
_restore = false;
_wdInhib = true;
_xferOff = true;
@@ -311,7 +312,7 @@ namespace Contralto.IO
_kDataRead = 0;
// Load new sector in
SelectedDrive.Sector = _sector;
SelectedDrive.Sector = _sector;
// Only wake up if not actively seeking.
if ((_kStat & STROBE) == 0)
@@ -417,7 +418,7 @@ namespace Contralto.IO
public void Strobe()
{
//
// "Initiates a disk seek operation. The KDATA register must have been loaded previously,
// "Initiates a disk seek [or restore] operation. The KDATA register must have been loaded previously,
// and the SENDADR bit of the KCOMM register previously set to 1."
//
@@ -431,7 +432,14 @@ namespace Contralto.IO
Log.Write(LogComponent.DiskController, "STROBE: Seek initialized.");
InitSeek((_kDataWrite & 0x0ff8) >> 3);
if (_restore)
{
InitSeek(0);
}
else
{
InitSeek((_kDataWrite & 0x0ff8) >> 3);
}
}
private void InitSeek(int destCylinder)
@@ -636,7 +644,8 @@ namespace Contralto.IO
{
// clear Seek bit
_kStat &= (ushort)~STROBE;
_seeking = false;
_seeking = false;
_restore = false;
Log.Write(LogComponent.DiskController, "Seek to {0} completed.", SelectedDrive.Cylinder);
}
@@ -704,6 +713,7 @@ namespace Contralto.IO
private ulong _seekDuration;
private Event _seekEvent;
private bool _seeking;
private bool _restore;
// Selected disk
private int _disk;

View File

@@ -1,11 +1,24 @@
using Contralto.Logging;
/*
This file is part of ContrAlto.
ContrAlto is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
ContrAlto is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with ContrAlto. If not, see <http://www.gnu.org/licenses/>.
*/
using Contralto.IO.Printing;
using Contralto.Logging;
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Imaging;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Contralto.IO
{
@@ -22,18 +35,28 @@ namespace Contralto.IO
}
/// <summary>
/// Encapsulates the logic for both the ROS and the printer portions of the
/// print pipeline.
/// Encapsulates the logic for both the ROS (Raster Output Scanner) hardware and
/// the Dover print engine.
///
/// These two should probably be separated out in the event that we want to support
/// other kinds of printers.
///
/// The Dover print engine support is currently very hand-wavy as the documentation
/// isn't extremely specific on some details and we're emulating a mechanical construct
/// moving papers past sensors, etc. It mostly works but it has some rough edges.
/// In particular the "Cold Start" mechanic is not well understood. Many parameters
/// are currently ignored (some for good reason; we don't need to emulate the actual
/// laser scanning and polygon rotation in order to create a bitmap, for example) but
/// some of them might be put to good use.
///
/// None of the diagnostic switches or options are implemented.
/// </summary>
public class DoverROS
{
public DoverROS(AltoSystem system)
{
_system = system;
_cs5Event = new Event(0, null, ColdStartCallback);
_innerLoopEvent = new Event(0, null, InnerLoopCallback);
_pageBuffer = new Bitmap(4096, 4096, PixelFormat.Format1bppIndexed);
_system = system;
_printEngineEvent = new Event(0, null, PrintEngineCallback);
Reset();
}
@@ -54,9 +77,8 @@ namespace Contralto.IO
_packetsOK = true;
_state = PrintState.ColdStart;
_innerLoopState = InnerLoopState.Idle;
_coldStartState = ColdStartState.SendVideoLow;
_state = PrintState.Idle;
_runoutCount = 0;
}
public void RecvCommand(ushort commandWord)
@@ -83,27 +105,40 @@ namespace Contralto.IO
_extendVideo = (argument & 0x20) != 0;
_motorScale = (argument & 0x1c0) >> 6;
_bitScale = (argument & 0xe00) >> 9;
Log.Write(LogComponent.DoverROS, "TestMode {0} CommandBeamOn {1} CommandLocal {2} TestPageSync {3} ExtendVideo {4} MotorScale {5} BitScale {6}",
_testMode,
_commandBeamOn,
_commandLocal,
_testPageSync,
_extendVideo,
_motorScale,
_bitScale);
break;
case AdapterCommand.SetBitClockRegister:
_bitClock = argument;
Log.Write(LogComponent.DoverROS, "BitClock set to {0}", argument);
break;
case AdapterCommand.SetMotorSpeedRegister:
_motorSpeed = argument;
Log.Write(LogComponent.DoverROS, "MotorSpeed set to {0}", argument);
break;
case AdapterCommand.SetLineSyncDelayRegister:
_lineSyncDelay = argument;
Log.Write(LogComponent.DoverROS, "LineSyncDelay set to {0}", argument);
break;
case AdapterCommand.SetPageSyncDelayRegister:
_pageSyncDelay = argument;
Log.Write(LogComponent.DoverROS, "PageSyncDelay set to {0}", argument);
break;
case AdapterCommand.ExternalCommand1:
bool lastPrintRequest = (_externalCommand1 & 0x1) != 0;
bool lastPrintRequest = (_externalCommand1 & 0x1) == 0;
_externalCommand1 = argument;
@@ -113,7 +148,7 @@ namespace Contralto.IO
// sheets.
//
Log.Write(LogComponent.DoverROS, "ExternalCommand1 written {0}", argument);
if (lastPrintRequest && (_externalCommand1 & 0x1) == 0)
if (lastPrintRequest && (_externalCommand1 & 0x1) != 0)
{
PrintRequest();
}
@@ -121,6 +156,7 @@ namespace Contralto.IO
case AdapterCommand.ExternalCommand2:
_videoGate = argument;
Log.Write(LogComponent.DoverROS, "VideoGate set to {0}", argument);
break;
default:
@@ -164,8 +200,6 @@ namespace Contralto.IO
value |= (_local ? 0x2000 : 0);
value |= (_beamEnable ? 0x1000 : 0);
value |= (_statusBeamOn ? 0x0800 : 0);
//Log.Write(LogComponent.DoverROS, "ROS word 0 bits 0-15: {0}", Conversion.ToOctal(value));
break;
case 1:
@@ -255,6 +289,24 @@ namespace Contralto.IO
//
value |= (_lineCount << 12);
value |= _videoGate;
//
// LineCount is not well documented anywhere in the hardware documentation
// available. It is related to the motion of the laser as it sweeps across
// the page. The source code for Spruce (SprucePrinDover.bcpl)
// has this to say:
// "A check, designed chiefly for Dover, verifies that the laser is on and the polygon is
// scanning (SOS/EOS are seeing things), by making sure that the low four bits of the line
// count (indicated in status word) are changing. The code reads the line count, waits
// approximately the time needed for four scan lines (generous margin), then reads the
// count again, reporting a problem if the values are equal."
//
//
// Indeed, if this value does not increment, Spruce fails with a
// "Laser appears to be off" error.
// We fudge this here.
_lineCount++;
break;
case 8:
@@ -270,9 +322,7 @@ namespace Contralto.IO
// 8 - LS4 (adequate paper in tray)
// 11 - LaserOn
// 13 - ReadyTemp
value |= 0x214;
Log.Write(LogComponent.DoverROS, "Dover bits 0-15: {0}", Conversion.ToOctal(value));
value |= 0x0094;
break;
case 9:
@@ -284,8 +334,7 @@ namespace Contralto.IO
// These are:
// 5 - ACMonitor
// 13 - LS24 & LS31
value |= 0x2004;
value |= 0x0404;
Log.Write(LogComponent.DoverROS, "Dover bits 16-31: {0}", Conversion.ToOctal(value));
break;
@@ -302,6 +351,7 @@ namespace Contralto.IO
default:
Log.Write(LogComponent.DoverROS, "Unhandled ROS status word {0}", wordNumber);
value = 0xffff;
break;
}
@@ -310,301 +360,388 @@ namespace Contralto.IO
private void PrintRequest()
{
switch(_state)
switch (_state)
{
case PrintState.ColdStart:
case PrintState.Idle:
if (!_printMode)
{
_state = PrintState.ColdStart;
_printMode = true;
_sendVideo = false;
_sendVideo = true;
_keepGoing = false;
_runoutCount = 0;
// Queue a 250ms event to fire CS-5(0).
// and 990ms item to cancel printing if a second
// print-request isn't received.
_innerLoopState = InnerLoopState.CS5;
_innerLoopEvent.TimestampNsec = (ulong)(120 * Conversion.MsecToNsec);
_system.Scheduler.Schedule(_innerLoopEvent);
ClearPageRaster();
Log.Write(LogComponent.DoverROS, "Cold Start initialized at {0}ms -- CS-5(0) in 250ms.", _system.Scheduler.CurrentTimeNsec * Conversion.NsecToMsec);
//
// Calculate a few things based on ROS parameters
//
// PageSyncDelay is (4096-n/i) where n is the number of
// scan-lines to pass up after receiving PageSync from the engine
// before starting SendVideo. i is 1 for Dover II, and 4 for older
// ones.
// We assume a Dover I here.
_sendVideoStartScanline = 4 * (4096 - _pageSyncDelay);
//
// VideoGate is (4096-n/4) where n is the number of scan-lines to
// pass up after SendVideo starts before stopping SendVideo.
_sendVideoEndScanline = 4 * (4096 - _videoGate) + _sendVideoStartScanline;
Log.Write(LogComponent.DoverROS, "SendVideo start {0}, end {1}",
_sendVideoStartScanline,
_sendVideoEndScanline);
//
// Start printing engine running.
//
_printEngineEvent.TimestampNsec = _printEngineTimestepInterval;
_system.Scheduler.Schedule(_printEngineEvent);
_printEngineTimestep = -375; // Start 250ms before the first CS-5
//
// Start output
//
InitializePrintOutput();
Log.Write(LogComponent.DoverROS, "Cold Start initialized. Engine started.");
}
else
{
Log.Write(LogComponent.DoverROS, "PrintRequest received in cold start at {0}ms.", _system.Scheduler.CurrentTimeNsec * Conversion.NsecToMsec);
_keepGoing = true;
Log.Write(LogComponent.DoverROS, "Unexpected PrintRequest with PrintMode active during Idle.");
}
break;
case PrintState.ColdStart:
Log.Write(LogComponent.DoverROS, "PrintRequest received in cold start.");
_keepGoing = true;
break;
case PrintState.InnerLoop:
if (!_printMode)
{
// PrintRequest too late.
Log.Write(LogComponent.DoverROS, "PrintRequest too late. Ignoring.");
Log.Write(LogComponent.DoverROS, "PrintRequest too late during Inner Loop. Ignoring.");
}
else
{
if (_innerLoopState != InnerLoopState.Idle)
{
Log.Write(LogComponent.DoverROS, "PrintRequest received in inner loop.");
_keepGoing = true;
}
else
{
//
// Currently idle: Kick off the first round of the inner loop.
// Queue a PageSyncDelay (250ms) event to pulse SendVideo
// after the pulse, gather video raster from Orbit
//
Log.Write(LogComponent.DoverROS, "PrintRequest received, starting inner loop.");
_innerLoopState = InnerLoopState.CS5;
_innerLoopEvent.TimestampNsec = 120 * Conversion.MsecToNsec;
_system.Scheduler.Schedule(_innerLoopEvent);
}
Log.Write(LogComponent.DoverROS, "PrintRequest during inner loop. Continuing.");
_keepGoing = true;
}
break;
case PrintState.Runout:
Log.Write(LogComponent.DoverROS, "Runout.");
break;
}
}
private void InnerLoopCallback(ulong timestampNsec, ulong delta, object context)
{
switch(_innerLoopState)
{
case InnerLoopState.CS5:
_countH = false;
_sendVideo = false;
// Keep SendVideo low for 125ms
_innerLoopState = InnerLoopState.SendVideo;
_innerLoopEvent.TimestampNsec = 125 * Conversion.MsecToNsec;
_system.Scheduler.Schedule(_innerLoopEvent);
Log.Write(LogComponent.DoverROS, "Inner loop: CS5");
break;
case InnerLoopState.SendVideo:
_sendVideo = true;
_innerLoopState = InnerLoopState.ReadBands;
_readBands = 0;
// time for one band of 16 scanlines to be read (approx.)
_innerLoopEvent.TimestampNsec = (ulong)(0.2 * Conversion.MsecToNsec);
_system.Scheduler.Schedule(_innerLoopEvent);
Log.Write(LogComponent.DoverROS, "Inner loop: SendVideo");
break;
case InnerLoopState.ReadBands:
// Assume 3000 scanlines for an 8.5" sheet of paper at 350dpi.
// If the Orbit is allowing us to read the output buffer then
// we will do so, otherwise we emit nothing.
if (_readBands > 3000)
if (!_printMode)
{
if (_state == PrintState.ColdStart)
{
_innerLoopState = InnerLoopState.ColdStartEnd;
}
else
{
_innerLoopState = InnerLoopState.CountH;
}
// PrintRequest too late.
Log.Write(LogComponent.DoverROS, "PrintRequest too late during Runout. Ignoring.");
}
else
{
if (_system.OrbitController.SLOTTAKE)
Log.Write(LogComponent.DoverROS, "PrintRequest during Runout. Moving to Inner Loop.");
_state = PrintState.InnerLoop;
_keepGoing = true;
_runoutCount = 0;
}
break;
}
}
private void InitializePrintOutput()
{
//
// Select the appropriate output based on the configuration.
// For now it's either PDF or nothing.
//
if (Configuration.EnablePrinting)
{
_pageOutput = new PdfPageSink();
}
else
{
_pageOutput = new NullPageSink();
}
_pageOutput.StartDoc();
}
/// <summary>
/// This is invoked for every scanline that passes under the virtual print path.
/// At each scanline flags are updated and raster data is pulled from the Orbit and
/// copied to the page as necessary.
/// </summary>
/// <param name="timestampNsec"></param>
/// <param name="delta"></param>
/// <param name="context"></param>
private void PrintEngineCallback(ulong timestampNsec, ulong delta, object context)
{
Log.Write(LogComponent.DoverROS, "Scanline {0} (sendvideo {1})", _printEngineTimestep, _sendVideo);
switch (_state)
{
case PrintState.ColdStart:
{
// Go through the motions but don't rasterize anything
//
// The Cold-start loop starts 250ms before the first CS-5 signal.
if (_printEngineTimestep >= 0 && _printEngineTimestep < _sendVideoStartScanline)
{
// Read in 16 scanlines of data -- this is 256x16 words
for (int y = 0; y < 16; y++)
_sendVideo = false;
}
else
{
_sendVideo = true;
}
if (_printEngineTimestep >= 0 &&
_printEngineTimestep < _sendVideoEndScanline)
{
// Pull rasters one scanline at a time out of Orbit,
// since we're in cold-start, these are discarded.
if (_system.OrbitController.SLOTTAKE)
{
// Read in one scanline of data -- this is 256 words
for (int x = 0; x < 256 - _system.OrbitController.FA; x++)
{
ushort word = _system.OrbitController.GetOutputDataROS();
_pageData[(_readBands + y) * 512 + x * 2] = (byte)(word & 0xff);
_pageData[(_readBands + y) * 512 + x * 2 + 1] = (byte)(word >> 8);
// The assumption is that during this phase the Alto will just
// be sending zeroes, log if this assumption does not hold...
//
if (word != 0)
{
Log.Write(LogComponent.DoverROS, "Read non-zero orbit data during cold-start {0}", Conversion.ToOctal(word));
}
}
Log.Write(LogComponent.DoverROS, "Read cold-start band {0}", _readBands);
}
else
{
// Nothing right now
Log.Write(LogComponent.DoverROS, "No bands available from Orbit.");
}
Log.Write(LogComponent.DoverROS, "Read bands {0}", _readBands);
_readBands++;
}
if (_printEngineTimestep >= 3136)
{
// After appx. 896ms (3136 scanlines) the first Count-H is raised.
_countH = true;
}
_printEngineTimestep++;
if (_printEngineTimestep >= 3500)
{
// End of cold-start "page."
_readBands = 0;
_printEngineTimestep = 0;
_countH = false;
ClearPageRaster();
if (_keepGoing)
{
//
// We got a PrintRequest during ColdStart, so we'll continue to the next page.
//
Log.Write(LogComponent.DoverROS, "End of Cold Start, switching to Inner Loop.");
_state = PrintState.InnerLoop;
}
else
{
//
// No PrintRequest, we will switch to Runout and begin shutting down.
//
Log.Write(LogComponent.DoverROS, "End of Cold Start, switching to Runout.");
_state = PrintState.Runout;
}
_keepGoing = false;
}
_printEngineEvent.TimestampNsec = _printEngineTimestepInterval;
_system.Scheduler.Schedule(_printEngineEvent);
}
break;
case PrintState.InnerLoop:
{
//
// Inner loop starts with CS-5 (timestep 0).
// After which there is a delay (PageSyncDelay) before
// SendVideo goes high and we can start reading raster data.
if (_printEngineTimestep < _sendVideoStartScanline)
{
_sendVideo = false;
}
else
{
// Nothing right now
Log.Write(LogComponent.DoverROS, "No bands available from Orbit.");
_sendVideo = true;
}
_readBands += 16;
if (_readBands > 2500 && !_countH)
if (_printEngineTimestep >= 0 &&
_printEngineTimestep < _sendVideoEndScanline)
{
// Video gate : pull rasters one scanline at a time out of Orbit.
if (_system.OrbitController.SLOTTAKE)
{
// Read in one scanline's worth of data
int scanlineWordCount = 256 - _system.OrbitController.FA;
for (int x = 0; x < scanlineWordCount; x++)
{
ushort word = _system.OrbitController.GetOutputDataROS();
int pageDataIndex = _readBands * scanlineWordCount * 2 + x * 2;
_pageData[pageDataIndex] = (byte)(~word >> 8);
_pageData[pageDataIndex + 1] = (byte)(~word & 0xff);
}
Log.Write(LogComponent.DoverROS, "Read band {0}", _readBands);
}
else
{
// Nothing right now
Log.Write(LogComponent.DoverROS, "No bands available from Orbit.");
}
_readBands++;
}
if (_printEngineTimestep >= 3136)
{
// After appx. 896ms (3136 scanlines) Count-H is raised.
_countH = true;
Log.Write(LogComponent.DoverROS, "Enabling CountH");
}
_printEngineTimestep++;
if (_printEngineTimestep >= 3500)
{
//
// Send page rasters to the print output.
//
_pageOutput.AddPage(_pageData, _readBands, (256 - _system.OrbitController.FA) * 16);
ClearPageRaster();
// End of page.
_countH = false;
_readBands = 0;
_printEngineTimestep = 0;
if (_keepGoing)
{
//
// A PrintRequest was recieved during the Inner Loop, so we'll keep going to the next page.
Log.Write(LogComponent.DoverROS, "End of Page, continuing in Inner Loop.");
_state = PrintState.InnerLoop;
}
else
{
Log.Write(LogComponent.DoverROS, "End of Page, switching to Runout.");
_state = PrintState.Runout;
}
_keepGoing = false;
}
_printEngineEvent.TimestampNsec = _printEngineTimestepInterval;
_system.Scheduler.Schedule(_printEngineEvent);
}
break;
case PrintState.Runout:
{
//
// SendVideo still gets toggled during Runout. CountH is
// toggled by paper moving past a sensor, which does not happen
// during runout.
//
if (_printEngineTimestep < _sendVideoStartScanline)
{
_sendVideo = false;
}
else
{
_sendVideo = true;
}
_printEngineTimestep++;
if (_printEngineTimestep >= 3500)
{
// End of runout cycle.
_countH = false;
_printEngineTimestep = 0;
if (_keepGoing)
{
Log.Write(LogComponent.DoverROS, "End of Runout cycle {0}, continuing in Inner Loop.", _runoutCount);
_state = PrintState.InnerLoop;
}
else
{
Log.Write(LogComponent.DoverROS, "End of Runout cycle {0}.", _runoutCount);
_state = PrintState.Runout;
}
_keepGoing = false;
_runoutCount++;
}
if (_runoutCount > 7)
{
// Just shut off.
_state = PrintState.Idle;
_printMode = false;
_countH = false;
_sendVideo = false;
//
// Finish the output.
//
_pageOutput.EndDoc();
Log.Write(LogComponent.DoverROS, "Runout: shutting down, switching to Idle state. Output completed.");
}
else
{
//
// Go around for another Runout cycle.
_printEngineEvent.TimestampNsec = _printEngineTimestepInterval;
_system.Scheduler.Schedule(_printEngineEvent);
}
}
// time for one band of 16 scanlines to be read (approx.)
_innerLoopEvent.TimestampNsec = (ulong)(0.2 * Conversion.MsecToNsec);
_system.Scheduler.Schedule(_innerLoopEvent);
break;
case InnerLoopState.CountH:
_countH = false;
if (_keepGoing)
{
// PrintRequest during ColdStart -- move to Inner loop
_keepGoing = false;
_state = PrintState.InnerLoop;
_innerLoopState = InnerLoopState.CS5;
Log.Write(LogComponent.DoverROS, "PrintRequest during ColdStart -- moving to inner loop.");
}
else
{
_innerLoopState = InnerLoopState.Idle;
Log.Write(LogComponent.DoverROS, "No PrintRequest during ColdStart -- idling.");
}
break;
case InnerLoopState.ColdStartEnd:
if (_keepGoing)
{
//
// Got a PrintRequest during cold start, start the inner loop.
//
_keepGoing = false;
_state = PrintState.InnerLoop;
_innerLoopState = InnerLoopState.CS5;
Log.Write(LogComponent.DoverROS, "Inner loop: CountH -- continuing");
}
else
{
//
// No Print Request; idle and shut down.
//
_innerLoopState = InnerLoopState.Idle;
Log.Write(LogComponent.DoverROS, "Inner loop: CountH -- idling");
}
// Debug: Put picture in image
/*
BitmapData data = _pageBuffer.LockBits(_pageRect, ImageLockMode.WriteOnly, PixelFormat.Format1bppIndexed);
IntPtr ptr = data.Scan0;
System.Runtime.InteropServices.Marshal.Copy(_pageData, 0, ptr, _pageData.Length);
_pageBuffer.UnlockBits(data);
EncoderParameters p = new EncoderParameters(1);
p.Param[0] = new EncoderParameter(System.Drawing.Imaging.Encoder.Quality, 100L);
_pageBuffer.Save(String.Format("c:\\temp\\raster{0}.png", _rasterNum++), GetEncoderForFormat(ImageFormat.Png), p);
*/
_innerLoopEvent.TimestampNsec = (ulong)(150 * Conversion.MsecToNsec);
_system.Scheduler.Schedule(_innerLoopEvent);
_innerLoopEvent.TimestampNsec = (ulong)(150 * Conversion.MsecToNsec);
_system.Scheduler.Schedule(_innerLoopEvent);
break;
case InnerLoopState.Idle:
_countH = false;
_sendVideo = false;
_printMode = false;
_state = PrintState.ColdStart;
Log.Write(LogComponent.DoverROS, "Inner loop: Idle");
break;
}
}
private void ColdStartCallback(ulong timestampNsec, ulong delta, object context)
private void ClearPageRaster()
{
switch (_coldStartState)
//
// reset to white
//
for(int i=0;i<_pageData.Length;i++)
{
case ColdStartState.SendVideoLow:
_sendVideo = false;
// Keep SendVideo low for 125ms
_coldStartState = ColdStartState.SendVideoHigh;
_cs5Event.TimestampNsec = 125 * Conversion.MsecToNsec;
_system.Scheduler.Schedule(_cs5Event);
Log.Write(LogComponent.DoverROS, "Cold start: toggle SendVideo low");
break;
case ColdStartState.SendVideoHigh:
_sendVideo = true;
_coldStartState = ColdStartState.Timeout;
_cs5Event.TimestampNsec = 800 * Conversion.MsecToNsec;
_system.Scheduler.Schedule(_cs5Event);
Log.Write(LogComponent.DoverROS, "Cold start: toggle SendVideo high");
break;
case ColdStartState.Timeout:
if (_state == PrintState.ColdStart)
{
// Never moved from ColdStart, Alto never sent another PageRequest.
Log.Write(LogComponent.DoverROS, "Cold start timeout. Aborting.");
_sendVideo = false;
_printMode = false;
}
break;
_pageData[i] = 0xff;
}
}
private ImageCodecInfo GetEncoderForFormat(ImageFormat format)
{
ImageCodecInfo[] codecs = ImageCodecInfo.GetImageDecoders();
foreach (ImageCodecInfo codec in codecs)
{
if (codec.FormatID == format.Guid)
{
return codec;
}
}
return null;
}
private enum PrintState
{
ColdStart = 0,
Idle = 0,
ColdStart,
InnerLoop,
Runout
}
private enum ColdStartState
{
SendVideoLow,
SendVideoHigh,
Timeout
}
private enum InnerLoopState
{
Idle = 0,
CS5,
SendVideo,
ReadBands,
CountH,
ColdStartEnd,
}
private PrintState _state;
private InnerLoopState _innerLoopState;
private ColdStartState _coldStartState;
private bool _keepGoing;
private int _readBands;
@@ -653,8 +790,8 @@ namespace Contralto.IO
private bool _switch4;
// ID and serial number
private ushort _id;
private ushort _serialNumber;
private ushort _id = 0;
private ushort _serialNumber = 0;
//
// Dover specific status that we care to report.
@@ -671,15 +808,35 @@ namespace Contralto.IO
//
private bool _countH;
/// <summary>
/// Number of passes through the Runout state.
/// </summary>
private int _runoutCount;
// Events to drive the print state machine
//
private Event _cs5Event;
private Event _innerLoopEvent;
//
private Event _printEngineEvent;
private byte[] _pageData = new byte[4096 * 512];
private Rectangle _pageRect = new Rectangle(0, 0, 4096, 4096);
private Bitmap _pageBuffer;
private int _printEngineTimestep;
private int _sendVideoStartScanline;
private int _sendVideoEndScanline;
/// <summary>
/// The timeslice for a single engine step (one scanline).
/// There are (in theory) 2975 scanlines per 8.5" page (at 350dpi) which moves
/// through the engine in 850ms.
///
/// The cycle time for an entire page is 1000ms, which we pretend
/// is 3500 scanline times.
///
/// </summary>
private ulong _printEngineTimestepInterval = (ulong)((1000.0 / 3500.0) * Conversion.MsecToNsec);
private byte[] _pageData = new byte[3500 * 512];
private int _rasterNum;
private IPageSink _pageOutput;
}
}

View File

@@ -1,9 +1,21 @@
using Contralto.Logging;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
/*
This file is part of ContrAlto.
ContrAlto is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
ContrAlto is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with ContrAlto. If not, see <http://www.gnu.org/licenses/>.
*/
using Contralto.Logging;
namespace Contralto.IO
{
@@ -17,12 +29,10 @@ namespace Contralto.IO
public OrbitController(AltoSystem system)
{
_system = system;
_ros = new DoverROS(system);
_refreshEvent = new Event(_refreshInterval, null, RefreshCallback);
Reset();
Reset();
}
public bool RefreshTimerExpired
@@ -48,10 +58,7 @@ namespace Contralto.IO
public void Reset()
{
//
// A Reset performs at least the following functions:
// FA <- 0, SLOTTAKE <- 0, band buffer A is assigned
// to the image buffer, the status and control dialogs
// with the adapter are reset.
// Reset to power-up (or reboot) defaults.
//
_fa = 0;
_slottake = false;
@@ -70,10 +77,46 @@ namespace Contralto.IO
_goAway = false;
_behind = false;
_stableROS = true;
_badROS = false;
Log.Write(LogComponent.Orbit, "Orbit system reset.");
UpdateWakeup();
}
private void OrbitReset()
{
//
// A Reset performs at least the following functions:
// FA <- 0, SLOTTAKE <- 0, band buffer A is assigned
// to the image buffer, the status and control dialogs
// with the adapter are reset.
//
_fa = 0;
_slottake = false;
_image = _a;
_output = _b;
_incon = false;
_outputY = 0;
_outputX = 0;
// Cleared "by reseting [sic] Orbit"
_iacs = false;
// Cleared by a buffer switch or an Orbit reset
_goAway = false;
_refresh = false;
_behind = false;
_stableROS = true;
_badROS = false;
Log.Write(LogComponent.Orbit, "Orbit reset.");
Log.Write(LogComponent.Orbit, "Orbit reset.");
UpdateWakeup();
}
public bool Wakeup
@@ -86,17 +129,36 @@ namespace Contralto.IO
}
}
private void UpdateWakeup()
{
if (_system.CPU == null)
return;
if (Wakeup)
{
_system.CPU.WakeupTask(CPU.TaskType.Orbit);
Log.Write(LogComponent.Orbit, "Orbit wakeup.");
}
else
{
_system.CPU.BlockTask(CPU.TaskType.Orbit);
Log.Write(LogComponent.Orbit, "Orbit block.");
}
}
public void STARTF(ushort value)
{
// Kick off the refresh timer if it's not already pending
// Wake up task, etc.
_run = true;
_system.CPU.WakeupTask(CPU.TaskType.Orbit);
// "IACS is cleared by StartIO(4)"
_iacs = false;
// per microcode, GOAWAY is cleared
_goAway = false;
if (!_refreshRunning)
{
_refreshRunning = true;
@@ -105,15 +167,16 @@ namespace Contralto.IO
}
Log.Write(LogComponent.Orbit, "Orbit started.");
UpdateWakeup();
}
public void Stop()
{
_run = false;
_system.CPU.BlockTask(CPU.TaskType.Orbit);
//Log.Write(LogComponent.Orbit, "Orbit blocked.");
Log.Write(LogComponent.Orbit, "Orbit stopped.");
UpdateWakeup();
}
public void Control(ushort value)
@@ -133,7 +196,7 @@ namespace Contralto.IO
//
if ((value & 0x1) != 0)
{
Reset();
OrbitReset();
}
//
@@ -156,11 +219,9 @@ namespace Contralto.IO
{
_fa = auxControl;
_outputY = _fa;
// "The setting of FA provided by the Alto is examined only when
// Orbit reads out the last 16 bits of a scanline..."
// (So we *should* leave _outputY alone here).
// (So we should leave _outputY alone here).
}
else
{
@@ -173,7 +234,6 @@ namespace Contralto.IO
if ((value & 0x8) != 0)
{
_goAway = true;
_system.CPU.BlockTask(CPU.TaskType.Orbit);
}
//
@@ -195,11 +255,13 @@ namespace Contralto.IO
if ((value & 0xc0) == 0xc0)
{
_slottake = true;
}
}
Log.Write(LogComponent.Orbit,
"Set Control: aux {0}, reset {1} refresh {2} which {3} goaway {4} behind {5} slottake {6}",
auxControl, (value & 0x1) != 0, (value & 0x2) != 0, (value & 0x4) != 0, _goAway, _behind, _slottake);
UpdateWakeup();
}
public void SetHeight(ushort value)
@@ -258,10 +320,10 @@ namespace Contralto.IO
_cx = _x;
_cy = _y;
_scanlines = (_x + (_width + 1) < 16) ? (_width + 1): 16 - _x;
Log.Write(LogComponent.Orbit,
"Set DBCWidth: DeltaBC {0}, Width {1}", _deltaBC, _width);
UpdateWakeup();
}
public void WriteFontData(ushort value)
@@ -277,10 +339,13 @@ namespace Contralto.IO
if (!_iacs)
{
Log.Write(LogType.Error,
LogComponent.Orbit, "Unxpected OrbitFontData while IACS false.");
LogComponent.Orbit, "Unexpected OrbitFontData while IACS false.");
return;
}
Log.Write(LogComponent.Orbit,
"Font Data: {0}", Conversion.ToOctal(value));
//
// We insert the word one bit at a time; this is more costly
// computationally but it's a ton simpler than dealing with word
@@ -290,15 +355,16 @@ namespace Contralto.IO
if (_firstWord)
{
startBit = 15 - _deltaBC;
_firstWord = false;
}
for(int i = startBit; i >=0 ;i--)
{
int bitValue = (value & (0x1 << i)) == 0 ? 0 : 1;
if (bitValue != 0 && _cy < 4096) // clip to end of band
{
SetImageRasterBit(_cx, _cy, bitValue);
SetImageRasterBit(_cx, _cy);
}
_cy++;
@@ -316,15 +382,24 @@ namespace Contralto.IO
{
_iacs = false;
Log.Write(LogComponent.DoverROS, "Image band completed.");
//
// A height of 1024 is used by the refresh microcode to refresh the Orbit memory.
// We log this separately to make debugging more clear.
//
if (_height == 1024)
{
Log.Write(LogComponent.Orbit, "Image band completed (refresh).");
}
else
{
Log.Write(LogComponent.Orbit, "Image band completed.");
}
UpdateWakeup();
break;
}
}
}
_firstWord = false;
Log.Write(LogComponent.Orbit,
"Font Data: {0}", Conversion.ToOctal(value));
}
public void WriteInkData(ushort value)
@@ -387,7 +462,7 @@ namespace Contralto.IO
public ushort GetDBCWidth()
{
Log.Write(LogComponent.Orbit,
"Delta DBCWidth {0},{1}", _deltaBCEnd % 16, _width);
"Delta DBCWidth {0},{1}", _deltaBCEnd % 16, _width);
return (ushort)((_deltaBCEnd << 12) | (_width & 0xfff));
}
@@ -417,7 +492,7 @@ namespace Contralto.IO
}
Log.Write(LogComponent.Orbit,
"OrbitStatus {0}", Conversion.ToOctal(result));
"OrbitStatus, address {0}, {1}", _statusAddress, Conversion.ToOctal(result));
return (ushort)result;
}
@@ -425,7 +500,7 @@ namespace Contralto.IO
private ushort GetOutputData()
{
//
// Basically, the function OrbitOutputData reads 16 bits from the output buffer
// "Basically, the function OrbitOutputData reads 16 bits from the output buffer
// into the Alto.
//
// The programmer (or emulator author) needs to be aware of two tricky details
@@ -437,7 +512,7 @@ namespace Contralto.IO
// The usual convention is to read out this one last word. This leaves the first
// word of the new buffer in ROB.
//
// 2. The output band buffer will not be refreshed ... (not a concern here)
// 2. The output band buffer will not be refreshed ... [not a concern here]"
//
// Return the last value from ROB
@@ -456,7 +531,7 @@ namespace Contralto.IO
if (_outputY == 256)
{
Log.Write(LogComponent.DoverROS,
Log.Write(LogComponent.Orbit,
"OutputData - scanline {0} completed", _outputX);
_outputY = _fa;
@@ -466,7 +541,7 @@ namespace Contralto.IO
{
// Done reading output band -- swap buffers!
Log.Write(LogComponent.DoverROS,
Log.Write(LogComponent.Orbit,
"OutputData - buffer read completed.");
SwapBuffers();
@@ -494,23 +569,22 @@ namespace Contralto.IO
// the Alto is now behind the consumer...
//
if (!_goAway)
{
Console.WriteLine("BEHIND");
{
_behind = true;
}
else
{
// "The buffer switch will turn off GOAWAY and consequently
// allow wakeups again."
_goAway = false;
_system.CPU.WakeupTask(CPU.TaskType.Orbit);
_goAway = false;
UpdateWakeup();
}
// Flip buffer bit
_incon = !_incon;
}
private void SetImageRasterBit(int x, int y, int bitValue)
private void SetImageRasterBit(int x, int y)
{
// Pick out the word we're interested in
int wordAddress = y >> 4;
@@ -526,21 +600,22 @@ namespace Contralto.IO
{
_refresh = true;
//Log.Write(LogComponent.Orbit, "Refresh signal raised.");
Log.Write(LogComponent.Orbit, "Refresh signal raised.");
if (_run)
{
_system.CPU.WakeupTask(CPU.TaskType.Orbit);
// do it again
_refreshEvent.TimestampNsec = _refreshInterval;
_system.Scheduler.Schedule(_refreshEvent);
}
else
{
//Log.Write(LogComponent.Orbit, "RUN deasserted, Refresh aborted.");
Log.Write(LogComponent.Orbit, "RUN deasserted, Refresh aborted.");
_refreshRunning = false;
_refresh = false;
}
UpdateWakeup();
}
// Run status
@@ -553,9 +628,7 @@ namespace Contralto.IO
private int _width; // raster width of character
private int _bitCount; // count of processed bits; used to calculate DeltaWC.
private int _deltaBC; // bit of first word to start with
private int _deltaBCEnd; // number of bits left in last word rasterized
private int _scanlines;
private int _deltaBCEnd; // number of bits left in last word rasterized
// FA -- "First Address" of each scanline -- a value between
// 0 and 255 indicating the starting word to be read by the
@@ -630,7 +703,7 @@ namespace Contralto.IO
private readonly ulong _refreshInterval = 2 * Conversion.MsecToNsec; // 2ms in nsec
//
// The ROS we talk to to get raster onto a "page"
// The ROS we talk to to get raster onto a page.
//
private DoverROS _ros;

View File

@@ -1,11 +1,23 @@
using Contralto.CPU;
/*
This file is part of ContrAlto.
ContrAlto is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
ContrAlto is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with ContrAlto. If not, see <http://www.gnu.org/licenses/>.
*/
using Contralto.CPU;
using Contralto.Logging;
using Contralto.Memory;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Contralto.IO
{

View File

@@ -0,0 +1,43 @@
/*
This file is part of ContrAlto.
ContrAlto is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
ContrAlto is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with ContrAlto. If not, see <http://www.gnu.org/licenses/>.
*/
namespace Contralto.IO.Printing
{
/// <summary>
/// IPageSink defines the interface between a ROS device and physical output
/// on the emulated host (be it real paper on a printer or PDF, Postscript, etc).
/// </summary>
public interface IPageSink
{
/// <summary>
/// Starts a new document to be printed.
/// </summary>
void StartDoc();
/// <summary>
/// Adds a new page to the output. This is provided as a byte array containing 'height' scanlines of the specified width.
/// </summary>
/// <param name="raster"></param>
/// <param name="width"></param>
/// <param name="height"></param>
void AddPage(byte[] raster, int width, int height);
/// <summary>
/// Ends the document being printed.
/// </summary>
void EndDoc();
}
}

View File

@@ -0,0 +1,44 @@
/*
This file is part of ContrAlto.
ContrAlto is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
ContrAlto is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with ContrAlto. If not, see <http://www.gnu.org/licenses/>.
*/
namespace Contralto.IO.Printing
{
/// <summary>
/// The NullPageSink: All pages go to the bit-bucket, for the true paperless office.
/// </summary>
public class NullPageSink : IPageSink
{
public NullPageSink()
{
}
public void StartDoc()
{
}
public void AddPage(byte[] rasters, int width, int height)
{
}
public void EndDoc()
{
}
}
}

View File

@@ -0,0 +1,127 @@
/*
This file is part of ContrAlto.
ContrAlto is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
ContrAlto is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with ContrAlto. If not, see <http://www.gnu.org/licenses/>.
*/
using System.IO;
using iTextSharp.text;
using iTextSharp.text.pdf;
using System.Collections.Generic;
using System;
using Contralto.Logging;
namespace Contralto.IO.Printing
{
/// <summary>
/// PdfPageSink takes output from the ROS and turns it into
/// PDF documents in the PrintOutputPath folder.
///
/// This uses the iTextSharp PDF creation libraries to do the hard work.
/// </summary>
public class PdfPageSink : IPageSink
{
public PdfPageSink()
{
_pageImages = new List<Image>();
}
public void StartDoc()
{
_pageImages.Clear();
try
{
// Start a new document.
// All output to a Dover printer is letter-sized.
_pdf = new Document(PageSize.LETTER);
string path = Path.Combine(
Configuration.PrintOutputPath,
String.Format("AltoDocument-{0}.pdf", DateTime.Now.ToString("yyyyMMdd-hhmmss")));
PdfWriter writer = PdfWriter.GetInstance(
_pdf,
new FileStream(path, FileMode.Create));
_pdf.Open();
// Let the Orbit deal with the margins.
_pdf.SetMargins(0, 0, 0, 0);
}
catch(Exception e)
{
//
// Most likely we couldn't create the output file; log the failure.
// All output will be relegated to the bit bucket.
//
_pdf = null;
Log.Write(LogType.Error, LogComponent.DoverROS, "Failed to create output PDF. Error {0}", e.Message);
}
}
public void AddPage(byte[] rasters, int width, int height)
{
if (_pdf != null)
{
Image pageImage = iTextSharp.text.Image.GetInstance(height, width, 1 /* greyscale */, 1 /* 1bpp */, rasters);
pageImage.SetDpi(375, 375);
pageImage.SetAbsolutePosition(0, 0);
pageImage.RotationDegrees = 90;
pageImage.ScaleToFit(_pdf.PageSize);
_pageImages.Add(pageImage);
}
}
public void EndDoc()
{
if (_pdf != null)
{
try
{
// Grab the configuration here so that if some joker changes the configuration
// while we're printing we don't do something weird.
bool reversePageOrder = Configuration.ReversePageOrder;
// Actually write out the pages now, in the proper order.
for (int i = 0; i < _pageImages.Count; i++)
{
_pdf.Add(_pageImages[reversePageOrder ? (_pageImages.Count - 1) - i : i]);
_pdf.NewPage();
}
_pdf.Close();
}
catch (Exception e)
{
// Something bad happened during creation, log an error.
Log.Write(LogComponent.DoverROS, "Failed to create output PDF. Error {0}", e.Message);
}
}
}
/// <summary>
/// List of page images in this document.
/// Since Alto software typically prints the document in reverse-page-order due to the way the
/// Dover produces output, we need be able to produce the PDF in reverse order.
/// This uses extra memory, (about 1.7mb per page printed.)
/// </summary>
private List<Image> _pageImages;
private Document _pdf;
}
}

View File

@@ -78,9 +78,8 @@ namespace Contralto.Memory
_memoryCycle = 0;
_memoryAddress = 0;
_memoryDataLow = 0;
_memoryDataHigh = 0;
_firstWordStored = false;
_firstWordRead = false;
_memoryDataHigh = 0;
_firstWord = false;
_memoryOperationActive = false;
_extendedMemoryReference = false;
}
@@ -186,7 +185,7 @@ namespace Contralto.Memory
case 6:
// End of memory operation
_memoryOperationActive = false;
_memoryOperationActive = false;
break;
}
}
@@ -245,14 +244,18 @@ namespace Contralto.Memory
}
else
{
_memoryOperationActive = true;
_firstWordStored = false;
_firstWordRead = false;
_memoryOperationActive = true;
_firstWord = false;
_memoryAddress = address;
_extendedMemoryReference = extendedMemoryReference;
_task = task;
//
// Memory cycle 1 is the instruction in which a MAR<- is executed,
// per the convention in the Alto HW ref.
//
_memoryCycle = 1;
}
}
}
@@ -285,12 +288,12 @@ namespace Contralto.Memory
throw new InvalidOperationException("Invalid ReadMD request during cycle 3 or 4 of memory operation.");
case 5:
// Single word read
// Single word read
return _memoryDataLow;
case 6:
// Double word read, return other half of double word.
return _memoryDataHigh;
return _memoryDataHigh;
default:
// Invalid state.
@@ -324,12 +327,15 @@ namespace Contralto.Memory
// If this is memory cycle 5 of a double-word *store* (started in cycle 3) then the second word can be *read* here.
// (An example of this is provided in the hardware ref, section 2, pg 8. and is done by the Ethernet microcode on
// the Alto II, see the code block starting at address 0224 -- EPLOC (600) is loaded with the interface status,
// EBLOC (601) is read and OR'd with NWW in the same memory op.)
_firstWordRead = true;
return _firstWordStored ? _memoryDataHigh : _memoryDataLow;
// EBLOC (601) is read and OR'd with NWW in the same memory op.)
ushort memData = _firstWord ? _memoryDataHigh : _memoryDataLow;
_firstWord = !_firstWord;
return memData;
// ***
// NB: Handler for double-word read (cycle 6) is in the "else" clause below; this is kind of a hack.
// NB: Handler for double-word read (cycles 6 and later) is in the "else" clause below; this is kind of a hack.
// ***
default:
@@ -341,12 +347,19 @@ namespace Contralto.Memory
{
//
// Memory state machine not running, just return last latched contents.
// ("Because the Alto II latches memory contents, it is possible to execute _MD anytime after
// ("Because the Alto II latches memory contents, it is possible to execute <-MD anytime after
// cycle 5 of a reference and obtain the results of the read operation")
// We will return the last half of the doubleword to complete a double-word read.
// (If the first half hasn't yet been read, we return the first half instead...)
//
return _firstWordRead ? _memoryDataHigh : _memoryDataLow;
// We will return the proper half of the word as in cycle 5.
//
// Note that the Orbit character transfer microcode will occasionally do a double-word read in memory
// cycles 6 and 7, not 5 and 6, and still expect the correct 32-bit double-word to be read...
//
ushort memData;
memData = _firstWord ? _memoryDataHigh : _memoryDataLow;
_firstWord = !_firstWord;
return memData;
}
}
@@ -392,11 +405,11 @@ namespace Contralto.Memory
_memoryDataWrite = data;
// Start of doubleword write:
WriteToBus(_memoryAddress, data, _task, _extendedMemoryReference);
_firstWordStored = true;
_firstWord = !_firstWord;
break;
case 6:
if (!_firstWordStored)
if (!_firstWord)
{
throw new InvalidOperationException("Unexpected microcode behavior -- LoadMD on cycle 6, no LoadMD on cycle 5.");
}
@@ -405,6 +418,8 @@ namespace Contralto.Memory
ushort actualAddress = (ushort)(_memoryAddress | 1);
WriteToBus(actualAddress, data, _task, _extendedMemoryReference);
_firstWord = !_firstWord;
break;
}
@@ -424,13 +439,14 @@ namespace Contralto.Memory
_memoryDataWrite = data;
// Start of doubleword write:
WriteToBus(_memoryAddress, data, _task, _extendedMemoryReference);
_firstWordStored = true;
_firstWord = !_firstWord;
break;
case 4:
_memoryDataWrite = data;
ushort actualAddress = _firstWordStored ? (ushort)(_memoryAddress ^ 1) : _memoryAddress;
ushort actualAddress = _firstWord ? (ushort)(_memoryAddress ^ 1) : _memoryAddress;
WriteToBus(actualAddress, data, _task, _extendedMemoryReference);
_firstWord = !_firstWord;
break;
case 5:
@@ -533,10 +549,9 @@ namespace Contralto.Memory
// Write data (used for debugger UI only)
private ushort _memoryDataWrite;
// Indicates that the first word of a double-word store has taken place (started on cycle 3)
private bool _firstWordStored;
// Indicates tghat the first word of a double-word read has taken place (started on cycle 5)
private bool _firstWordRead;
// Indicates which word of a double-word read or write was last read (or written).
// true if the first word was processed; false otherwise.
private bool _firstWord;
}
}

View File

@@ -15,10 +15,8 @@
along with ContrAlto. If not, see <http://www.gnu.org/licenses/>.
*/
using Contralto.IO;
using Contralto.SdlUI;
using System;
using System.Collections.Generic;
using System.Windows.Forms;
namespace Contralto
@@ -121,16 +119,15 @@ namespace Contralto
// Commit current configuration to disk
//
Configuration.WriteConfiguration();
}
private static void PrintHerald()
{
Console.WriteLine("ContrAlto v1.2 (c) 2015-2017 Living Computers: Museum+Labs.");
Console.WriteLine("ContrAlto v{0} (c) 2015-2017 Living Computers: Museum+Labs.", typeof(Program).Assembly.GetName().Version);
Console.WriteLine("Bug reports to joshd@livingcomputers.org");
Console.WriteLine();
}
private static AltoSystem _system;
private static AltoSystem _system;
}
}

View File

@@ -32,5 +32,5 @@ using System.Runtime.InteropServices;
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.2.0")]
[assembly: AssemblyFileVersion("1.2.0.0")]
[assembly: AssemblyVersion("1.2.1")]
[assembly: AssemblyFileVersion("1.2.1.0")]

View File

@@ -202,5 +202,41 @@ namespace Contralto.Properties {
this["AudioDACCapturePath"] = value;
}
}
[global::System.Configuration.UserScopedSettingAttribute()]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Configuration.DefaultSettingValueAttribute("False")]
public bool EnablePrinting {
get {
return ((bool)(this["EnablePrinting"]));
}
set {
this["EnablePrinting"] = value;
}
}
[global::System.Configuration.UserScopedSettingAttribute()]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Configuration.DefaultSettingValueAttribute("")]
public string PrintOutputPath {
get {
return ((string)(this["PrintOutputPath"]));
}
set {
this["PrintOutputPath"] = value;
}
}
[global::System.Configuration.UserScopedSettingAttribute()]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Configuration.DefaultSettingValueAttribute("True")]
public bool ReversePageOrder {
get {
return ((bool)(this["ReversePageOrder"]));
}
set {
this["ReversePageOrder"] = value;
}
}
}
}

View File

@@ -47,5 +47,14 @@
<Setting Name="AudioDACCapturePath" Type="System.String" Scope="User">
<Value Profile="(Default)" />
</Setting>
<Setting Name="EnablePrinting" Type="System.Boolean" Scope="User">
<Value Profile="(Default)">False</Value>
</Setting>
<Setting Name="PrintOutputPath" Type="System.String" Scope="User">
<Value Profile="(Default)" />
</Setting>
<Setting Name="ReversePageOrder" Type="System.Boolean" Scope="User">
<Value Profile="(Default)">True</Value>
</Setting>
</Settings>
</SettingsFile>

View File

@@ -52,11 +52,11 @@
// label2
//
this.label2.AutoSize = true;
this.label2.Location = new System.Drawing.Point(20, 59);
this.label2.Location = new System.Drawing.Point(6, 58);
this.label2.Name = "label2";
this.label2.Size = new System.Drawing.Size(46, 13);
this.label2.Size = new System.Drawing.Size(76, 13);
this.label2.TabIndex = 1;
this.label2.Text = "(c) 2016";
this.label2.Text = "(c) 2016, 2017";
//
// label3
//
@@ -109,7 +109,7 @@
// websiteLink
//
this.websiteLink.AutoSize = true;
this.websiteLink.Location = new System.Drawing.Point(65, 59);
this.websiteLink.Location = new System.Drawing.Point(80, 59);
this.websiteLink.Name = "websiteLink";
this.websiteLink.Size = new System.Drawing.Size(163, 13);
this.websiteLink.TabIndex = 7;

View File

@@ -109,9 +109,6 @@
this.StopButton = new System.Windows.Forms.Button();
this.groupBox2 = new System.Windows.Forms.GroupBox();
this._taskData = new System.Windows.Forms.DataGridView();
this.TaskName = new System.Windows.Forms.DataGridViewTextBoxColumn();
this.TaskState = new System.Windows.Forms.DataGridViewTextBoxColumn();
this.TaskPC = new System.Windows.Forms.DataGridViewTextBoxColumn();
this.groupBox3 = new System.Windows.Forms.GroupBox();
this._otherRegs = new System.Windows.Forms.DataGridView();
this.Reg = new System.Windows.Forms.DataGridViewTextBoxColumn();
@@ -136,6 +133,10 @@
this.dataGridViewTextBoxColumn9 = new System.Windows.Forms.DataGridViewTextBoxColumn();
this.dataGridViewTextBoxColumn10 = new System.Windows.Forms.DataGridViewTextBoxColumn();
this.dataGridViewTextBoxColumn11 = new System.Windows.Forms.DataGridViewTextBoxColumn();
this.TaskName = new System.Windows.Forms.DataGridViewTextBoxColumn();
this.TaskState = new System.Windows.Forms.DataGridViewTextBoxColumn();
this.Bank = new System.Windows.Forms.DataGridViewTextBoxColumn();
this.TaskPC = new System.Windows.Forms.DataGridViewTextBoxColumn();
this.Microcode.SuspendLayout();
this.SourceTabs.SuspendLayout();
this.Rom0Page.SuspendLayout();
@@ -855,6 +856,7 @@
this._taskData.Columns.AddRange(new System.Windows.Forms.DataGridViewColumn[] {
this.TaskName,
this.TaskState,
this.Bank,
this.TaskPC});
dataGridViewCellStyle29.Alignment = System.Windows.Forms.DataGridViewContentAlignment.MiddleLeft;
dataGridViewCellStyle29.BackColor = System.Drawing.SystemColors.Window;
@@ -881,31 +883,6 @@
this._taskData.TabIndex = 0;
this._taskData.TabStop = false;
//
// TaskName
//
this.TaskName.AutoSizeMode = System.Windows.Forms.DataGridViewAutoSizeColumnMode.AllCellsExceptHeader;
this.TaskName.HeaderText = "T";
this.TaskName.MinimumWidth = 16;
this.TaskName.Name = "TaskName";
this.TaskName.ReadOnly = true;
this.TaskName.Width = 16;
//
// TaskState
//
this.TaskState.AutoSizeMode = System.Windows.Forms.DataGridViewAutoSizeColumnMode.DisplayedCellsExceptHeader;
this.TaskState.HeaderText = "S";
this.TaskState.MinimumWidth = 16;
this.TaskState.Name = "TaskState";
this.TaskState.ReadOnly = true;
this.TaskState.Width = 16;
//
// TaskPC
//
this.TaskPC.AutoSizeMode = System.Windows.Forms.DataGridViewAutoSizeColumnMode.Fill;
this.TaskPC.HeaderText = "uPC";
this.TaskPC.Name = "TaskPC";
this.TaskPC.ReadOnly = true;
//
// groupBox3
//
this.groupBox3.Controls.Add(this._otherRegs);
@@ -1234,6 +1211,40 @@
this.dataGridViewTextBoxColumn11.Resizable = System.Windows.Forms.DataGridViewTriState.False;
this.dataGridViewTextBoxColumn11.SortMode = System.Windows.Forms.DataGridViewColumnSortMode.NotSortable;
//
// TaskName
//
this.TaskName.AutoSizeMode = System.Windows.Forms.DataGridViewAutoSizeColumnMode.AllCellsExceptHeader;
this.TaskName.HeaderText = "T";
this.TaskName.MinimumWidth = 16;
this.TaskName.Name = "TaskName";
this.TaskName.ReadOnly = true;
this.TaskName.Width = 16;
//
// TaskState
//
this.TaskState.AutoSizeMode = System.Windows.Forms.DataGridViewAutoSizeColumnMode.DisplayedCellsExceptHeader;
this.TaskState.HeaderText = "S";
this.TaskState.MinimumWidth = 16;
this.TaskState.Name = "TaskState";
this.TaskState.ReadOnly = true;
this.TaskState.Width = 16;
//
// Bank
//
this.Bank.AutoSizeMode = System.Windows.Forms.DataGridViewAutoSizeColumnMode.AllCellsExceptHeader;
this.Bank.HeaderText = "B";
this.Bank.MinimumWidth = 16;
this.Bank.Name = "Bank";
this.Bank.ReadOnly = true;
this.Bank.Width = 16;
//
// TaskPC
//
this.TaskPC.AutoSizeMode = System.Windows.Forms.DataGridViewAutoSizeColumnMode.Fill;
this.TaskPC.HeaderText = "uPC";
this.TaskPC.Name = "TaskPC";
this.TaskPC.ReadOnly = true;
//
// Debugger
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
@@ -1306,9 +1317,6 @@
private System.Windows.Forms.DataGridViewTextBoxColumn S;
private System.Windows.Forms.GroupBox groupBox2;
private System.Windows.Forms.DataGridView _taskData;
private System.Windows.Forms.DataGridViewTextBoxColumn TaskName;
private System.Windows.Forms.DataGridViewTextBoxColumn TaskState;
private System.Windows.Forms.DataGridViewTextBoxColumn TaskPC;
private System.Windows.Forms.GroupBox groupBox3;
private System.Windows.Forms.DataGridView _otherRegs;
private System.Windows.Forms.DataGridViewTextBoxColumn Reg;
@@ -1366,5 +1374,9 @@
private System.Windows.Forms.DataGridViewTextBoxColumn Address;
private System.Windows.Forms.DataGridViewTextBoxColumn Data;
private System.Windows.Forms.DataGridViewTextBoxColumn Disassembly;
private System.Windows.Forms.DataGridViewTextBoxColumn TaskName;
private System.Windows.Forms.DataGridViewTextBoxColumn TaskState;
private System.Windows.Forms.DataGridViewTextBoxColumn Bank;
private System.Windows.Forms.DataGridViewTextBoxColumn TaskPC;
}
}

View File

@@ -116,7 +116,8 @@ namespace Contralto
{
_taskData.Rows[i].Cells[0].Value = GetTextForTask((TaskType)i);
_taskData.Rows[i].Cells[1].Value = GetTextForTaskState(_system.CPU.Tasks[i]);
_taskData.Rows[i].Cells[2].Value =
_taskData.Rows[i].Cells[2].Value = GetTextForTaskBank(_system.CPU.Tasks[i]);
_taskData.Rows[i].Cells[3].Value =
_system.CPU.Tasks[i] != null ? Conversion.ToOctal(_system.CPU.Tasks[i].MPC, 4) : String.Empty;
}
@@ -532,7 +533,7 @@ namespace Contralto
case "Disassembly":
// TODO: should provide means to disassemble as specific task, not just Emulator.
MicroInstruction instruction = new MicroInstruction(UCodeMemory.UCodeRAM[address]);
e.Value = UCodeDisassembler.DisassembleInstruction(instruction, TaskType.Emulator);
e.Value = UCodeDisassembler.DisassembleInstruction(instruction, _system.CPU.CurrentTask.TaskType);
break;
}
}
@@ -578,6 +579,18 @@ namespace Contralto
}
}
private string GetTextForTaskBank(AltoCPU.Task task)
{
if (task == null)
{
return String.Empty;
}
else
{
return UCodeMemory.GetBank(task.TaskType).ToString();
}
}
private string GetTextForTask(TaskType task)
{
string[] taskText =
@@ -931,38 +944,13 @@ namespace Contralto
case ExecutionType.Normal:
case ExecutionType.NextTask:
case ExecutionType.NextNovaInstruction:
// For debugging floating point microcode:
#if FLOAT_DEBUG
if (_system.CPU.CurrentTask.MPC == 0x10) // MPC is 20(octal) meaning a new Nova instruction.
{
if (_lastFPInstruction == 0)
{
// check for new floating instruction
FloatDebugPre(_system.MemoryBus.DebugReadWord(TaskType.Emulator, _system.CPU.R[6]));
}
else
{
// last instruction was a floating point instruction, check the result
FloatDebugPost();
// And see if this new one is also a floating point instruction...
FloatDebugPre(_system.MemoryBus.DebugReadWord(TaskType.Emulator, _system.CPU.R[6]));
}
}
#endif
if (_system.CPU.CurrentTask.MPC == 0x10) // MPC is 20(octal) meaning a new Nova instruction.
{
// count nova instructions (for profiling)
_system._novaInst++;
}
// See if we need to stop here
if (_execAbort || // The Stop button was hit
_system.CPU.InternalBreak || // Something internal has requested a debugger break
_microcodeBreakpointEnabled[
(int)UCodeMemory.GetBank(
_system.CPU.CurrentTask.TaskType),
_system.CPU.CurrentTask.MPC] || // A microcode breakpoint was hit
_system.CPU.CurrentTask.MPC] || // A microcode breakpoint was hit
(_execType == ExecutionType.NextTask &&
_system.CPU.NextTask != null &&
_system.CPU.NextTask != _system.CPU.CurrentTask) || // The next task was switched to
@@ -970,15 +958,17 @@ namespace Contralto
(_novaBreakpointEnabled[_system.CPU.R[6]] || // A breakpoint is set here
_execType == ExecutionType.NextNovaInstruction))) // or we're running only a single Nova instruction.
{
if (!_execAbort)
{
SetExecutionState(ExecutionState.BreakpointStop);
}
// Stop here as we've hit a breakpoint or have been stopped
// Update UI to indicate where we stopped.
this.BeginInvoke(new StepDelegate(RefreshUI));
this.BeginInvoke(new StepDelegate(Invalidate));
if (!_execAbort)
{
SetExecutionState(ExecutionState.BreakpointStop);
}
_system.CPU.InternalBreak = false;
_execAbort = false;
return true;
@@ -990,379 +980,7 @@ namespace Contralto
return false;
}
#if FLOAT_DEBUG
// vars for float debug
ushort _lastFPInstruction;
ushort _ac0;
ushort _ac1;
ushort _ac2;
ushort _ac3;
ushort _fpRegAddr;
ushort _fpRegCount;
int _fpInstructionCount;
/// <summary>
/// Temporary, for debugging floating point ucode issues
/// </summary>
/// <param name="instruction"></param>
private void FloatDebugPre(ushort instruction)
{
_lastFPInstruction = 0;
// Float instructions are from 70001-70022 octal
if (instruction >= 0x7000 && instruction <= 0x7012)
{
_fpInstructionCount++;
// Save instruction
_lastFPInstruction = instruction;
// Save ACs
_ac0 = _system.CPU.R[3];
_ac1 = _system.CPU.R[2];
_ac2 = _system.CPU.R[1];
_ac3 = _system.CPU.R[0];
Console.Write("{0}:FP: ", _fpInstructionCount);
switch (instruction)
{
case 0x7000:
Console.WriteLine("FPSetup");
_fpRegAddr = _system.CPU.R[3];
_fpRegCount = _system.MemoryBus.DebugReadWord(TaskType.Emulator, _fpRegAddr);
Console.WriteLine(" FP register address {0}, count {1}", Conversion.ToOctal(_fpRegAddr), _fpRegCount);
break;
case 0x7001:
Console.WriteLine("FML {0},{1} ({2},{3})", _ac0, _ac1, GetFloat(_ac0), GetFloat(_ac1));
break;
case 0x7002:
Console.WriteLine("FDV {0},{1} ({2},{3})", _ac0, _ac1, GetFloat(_ac0), GetFloat(_ac1));
break;
case 0x7003:
Console.WriteLine("FAD {0},{1} ({2},{3})", _ac0, _ac1, GetFloat(_ac0), GetFloat(_ac1));
break;
case 0x7004:
Console.WriteLine("FSB {0},{1} ({2},{3})", _ac0, _ac1, GetFloat(_ac0), GetFloat(_ac1));
break;
case 0x7005:
Console.WriteLine("FLD {0},{1} (src {2})", _ac0, _ac1, GetFloat(_ac1));
break;
case 0x7006:
Console.WriteLine("FLDV {0},{1} (src {2})", _ac0, _ac1, GetFloatFromInternalFormat(_ac1));
break;
case 0x7007:
Console.WriteLine("FSTV {0},{1} (src {2})", _ac0, _ac1, GetFloat(_ac0));
break;
case 0x7008:
Console.WriteLine("FLDI {0},{1}", _ac0, Conversion.ToOctal(_ac1));
break;
case 0x7009:
Console.WriteLine("FTR {0} ({1})", _ac0, GetFloat(_ac0));
break;
case 0x700a:
Console.WriteLine("FNEG {0} ({1})", _ac0, GetFloat(_ac0));
break;
case 0x700b:
Console.WriteLine("FSN {0} ({1})", _ac0, GetFloat(_ac0));
break;
case 0x700c:
Console.WriteLine("FCM {0} ({1})", _ac0, GetFloat(_ac0));
break;
case 0x700d:
Console.WriteLine("FST");
break;
case 0x700e:
Console.WriteLine("FLDDP");
break;
case 0x700f:
Console.WriteLine("FSTDP");
break;
case 0x7010:
Console.WriteLine("DPAD");
break;
case 0x7011:
Console.WriteLine("DPSB");
break;
case 0x7012:
Console.WriteLine("FEXP {0},{1} ({2})", _ac0, _ac1, GetFloat(_ac0));
break;
}
/*
Console.WriteLine(" AC0={0} AC1={1} AC2={2} AC3={3}",
Conversion.ToOctal(_ac0),
Conversion.ToOctal(_ac1),
Conversion.ToOctal(_ac2),
Conversion.ToOctal(_ac3)); */
}
}
private void FloatDebugPost()
{
Console.Write("{0}:Post: ", _fpInstructionCount);
switch (_lastFPInstruction)
{
case 0x7000:
Console.WriteLine("FPSetup done.");
break;
case 0x7001:
Console.WriteLine("Result {0}", GetFloat(_ac0));
break;
case 0x7002:
Console.WriteLine("Result {0}", GetFloat(_ac0));
break;
case 0x7003:
Console.WriteLine("Result {0}", GetFloat(_ac0));
break;
case 0x7004:
Console.WriteLine("Result {0}", GetFloat(_ac0));
break;
case 0x7005:
Console.WriteLine("Loaded {0}", GetFloat(_ac0));
break;
case 0x7006:
Console.WriteLine("Loaded {0}", GetFloat(_ac0));
break;
case 0x7007:
Console.WriteLine("FSTV done.");
break;
case 0x7008:
Console.WriteLine("Loaded {0}", GetFloat(_ac0));
break;
case 0x7009:
Console.WriteLine("Result {0}", GetFloat(_ac0));
break;
case 0x700a:
Console.WriteLine("Result {0}", GetFloat(_ac0));
break;
case 0x700b:
Console.WriteLine("Result {0}", _ac3);
break;
case 0x700c:
Console.WriteLine("Result {0}", _ac3);
break;
case 0x700d:
Console.WriteLine("FST done.");
break;
case 0x700e:
Console.WriteLine("FLDDP done.");
break;
case 0x700f:
Console.WriteLine("FSTDP done.");
break;
case 0x7010:
Console.WriteLine("DPAD done.");
break;
case 0x7011:
Console.WriteLine("DPSB done.");
break;
case 0x7012:
Console.WriteLine("Result {0}", GetFloat(_ac0));
break;
default:
throw new InvalidOperationException("Unexpected op for post.");
}
_lastFPInstruction = 0;
}
private double GetFloat(ushort arg)
{
// If arg is less than the number of registers, it's assumed
// to be a register; otherwise an address
if (arg < _fpRegCount)
{
return GetFloatFromUcode(arg);
}
else
{
return GetFloatFromPackedFormat(arg);
}
}
/// <summary>
/// Gets a float from memory in "packed" format
/// </summary>
/// <param name="addr"></param>
/// <returns></returns>
private double GetFloatFromPackedFormat(ushort addr)
{
//
// Packed format is only two words:
// structure FP: [
// sign bit 1 //1 if negative.
// expon bit 8 //excess 128 format (complemented if number <0)
// mantissa1 bit 7 //High order 7 bits of mantissa
// mantissa2 bit 16 //Low order 16 bits of mantissa
// ]
//
uint packedWord =
(uint)(_system.MemoryBus.DebugReadWord(TaskType.Emulator, addr) << 16) |
(uint)(_system.MemoryBus.DebugReadWord(TaskType.Emulator, (ushort)(addr + 1)));
double sign = (packedWord & 0x80000000) != 0 ? -1.0 : 1.0;
uint exponent = (packedWord & 0x7f800000) >> 23;
uint mantissa = (packedWord & 0x007fffff);
double val = 0.0;
for (int i = 0; i < 23; i++)
{
double bit = (mantissa & (0x00400000 >> i)) != 0 ? 1.0 : 0.0;
val += (bit * (1.0 / Math.Pow(2.0, (double)i)));
}
double adjustedExponent = exponent - 128.0;
val = sign * (val) * Math.Pow(2.0, adjustedExponent);
Console.WriteLine("packed: {0}", val);
return val;
}
/// <summary>
/// Gets the float value for register, from alto code
/// </summary>
/// <param name="reg"></param>
/// <returns></returns>
private double GetFloatFromInternalFormat(ushort addr)
{
// Internal format is 4 words long:
// Word 0: sign
// Word 1: exponent
// Word 2-3: mantissa
//
double sign = (_system.MemoryBus.DebugReadWord(TaskType.Emulator, addr)) != 0 ? -1.0 : 1.0;
int exponent = (int)(short)(_system.MemoryBus.DebugReadWord(TaskType.Emulator, (ushort)(addr + 1)));
uint mantissa =
(uint)(_system.MemoryBus.DebugReadWord(TaskType.Emulator, (ushort)(addr + 2)) << 16) |
(uint)(_system.MemoryBus.DebugReadWord(TaskType.Emulator, (ushort)(addr + 3)));
double valMantissa = 0.0;
for (int i = 0; i < 32; i++)
{
double bit = (mantissa & (0x80000000 >> i)) != 0 ? 1.0 : 0.0;
valMantissa += (bit * (1.0 / Math.Pow(2.0, (double)i)));
}
double val = sign * (valMantissa) * Math.Pow(2.0, exponent - 1);
//if (double.IsInfinity(val) || double.IsNaN(val))
{
Console.WriteLine(" Vec: sign {0} exp {1} mantissa {2:x} ({3}) value {4}",
sign,
exponent,
mantissa,
valMantissa,
val);
}
return val;
}
/// <summary>
/// Gets the float value for register, from ucode store
/// </summary>
/// <param name="reg"></param>
/// <returns></returns>
private double GetFloatFromUcode(ushort reg)
{
// Internal format is 4 words long:
// Word 0: sign
// Word 1: exponent
// Word 2-3: mantissa
//
// In current ucode, each word is staggered across the FP buffer space rather than being in linear order to prevent having to do multiplies.
// For FP register N of M total registers starting at offset O:
// Word 0 is at O + N + 1
// Word 1 is at O + N + M + 1
// Word 2 is at O + N + 2*M + 1
// Word 3 is at O + N + 3*M + 1
ushort oreg = reg;
reg += _fpRegAddr;
reg++;
//Console.WriteLine("reg base {0}, num {1} addr {2}", _fpRegAddr, oreg, Conversion.ToOctal(reg));
// reg is now an address; read things in...
double sign = (_system.MemoryBus.DebugReadWord(TaskType.Emulator, reg)) != 0 ? -1.0 : 1.0;
int exponent = (int)(short)(_system.MemoryBus.DebugReadWord(TaskType.Emulator, (ushort)(reg + _fpRegCount)));
uint mantissa =
(uint)(_system.MemoryBus.DebugReadWord(TaskType.Emulator, (ushort)(reg + 2 * _fpRegCount)) << 16) |
(uint)(_system.MemoryBus.DebugReadWord(TaskType.Emulator, (ushort)(reg + 3 * _fpRegCount)));
double valMantissa = 0.0;
for (int i = 0; i < 32; i++)
{
double bit = (mantissa & (0x80000000 >> i)) != 0 ? 1.0 : 0.0;
valMantissa += (bit * (1.0 / Math.Pow(2.0, (double)i)));
}
double val = sign * (valMantissa) * Math.Pow(2.0, exponent - 1);
//if (double.IsInfinity(val) || double.IsNaN(val))
{
Console.WriteLine(" UCode: sign {0} exp {1} mantissa {2:x} ({3}) value {4}",
sign,
exponent,
mantissa,
valMantissa,
val);
}
return val;
}
#endif
private void SetExecutionState(ExecutionState state)
{
_execState = state;

View File

@@ -192,6 +192,9 @@
<metadata name="TaskState.UserAddedColumn" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
<value>True</value>
</metadata>
<metadata name="Bank.UserAddedColumn" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
<value>True</value>
</metadata>
<metadata name="TaskPC.UserAddedColumn" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
<value>True</value>
</metadata>

View File

@@ -28,7 +28,7 @@
/// </summary>
private void InitializeComponent()
{
this.tabControl1 = new System.Windows.Forms.TabControl();
this.OptionsTabs = new System.Windows.Forms.TabControl();
this.tabPage1 = new System.Windows.Forms.TabPage();
this.AltoI1KROMRadioButton = new System.Windows.Forms.RadioButton();
this.AltoII3KRAMRadioButton = new System.Windows.Forms.RadioButton();
@@ -48,16 +48,23 @@
this.tabPage3 = new System.Windows.Forms.TabPage();
this.ThrottleSpeedCheckBox = new System.Windows.Forms.CheckBox();
this.InterlaceDisplayCheckBox = new System.Windows.Forms.CheckBox();
this.tabPage4 = new System.Windows.Forms.TabPage();
this.DACOptionsGroupBox = new System.Windows.Forms.GroupBox();
this.BrowseButton = new System.Windows.Forms.Button();
this.DACOutputCapturePathTextBox = new System.Windows.Forms.TextBox();
this.label2 = new System.Windows.Forms.Label();
this.EnableDACCaptureCheckBox = new System.Windows.Forms.CheckBox();
this.EnableDACCheckBox = new System.Windows.Forms.CheckBox();
this.DialogOKButton = new System.Windows.Forms.Button();
this.DialogCancelButton = new System.Windows.Forms.Button();
this.tabPage4 = new System.Windows.Forms.TabPage();
this.EnableDACCheckBox = new System.Windows.Forms.CheckBox();
this.DACOptionsGroupBox = new System.Windows.Forms.GroupBox();
this.EnableDACCaptureCheckBox = new System.Windows.Forms.CheckBox();
this.label2 = new System.Windows.Forms.Label();
this.DACOutputCapturePathTextBox = new System.Windows.Forms.TextBox();
this.BrowseButton = new System.Windows.Forms.Button();
this.tabControl1.SuspendLayout();
this.PrintingTab = new System.Windows.Forms.TabPage();
this.PrintingOptionsGroupBox = new System.Windows.Forms.GroupBox();
this.button1 = new System.Windows.Forms.Button();
this.PrintOutputPathTextBox = new System.Windows.Forms.TextBox();
this.label5 = new System.Windows.Forms.Label();
this.EnablePrintingCheckBox = new System.Windows.Forms.CheckBox();
this.ReversePageOrderCheckBox = new System.Windows.Forms.CheckBox();
this.OptionsTabs.SuspendLayout();
this.tabPage1.SuspendLayout();
this.tabPage2.SuspendLayout();
this.groupBox1.SuspendLayout();
@@ -65,19 +72,22 @@
this.tabPage3.SuspendLayout();
this.tabPage4.SuspendLayout();
this.DACOptionsGroupBox.SuspendLayout();
this.PrintingTab.SuspendLayout();
this.PrintingOptionsGroupBox.SuspendLayout();
this.SuspendLayout();
//
// tabControl1
// OptionsTabs
//
this.tabControl1.Controls.Add(this.tabPage1);
this.tabControl1.Controls.Add(this.tabPage2);
this.tabControl1.Controls.Add(this.tabPage3);
this.tabControl1.Controls.Add(this.tabPage4);
this.tabControl1.Location = new System.Drawing.Point(3, 5);
this.tabControl1.Name = "tabControl1";
this.tabControl1.SelectedIndex = 0;
this.tabControl1.Size = new System.Drawing.Size(368, 227);
this.tabControl1.TabIndex = 0;
this.OptionsTabs.Controls.Add(this.tabPage1);
this.OptionsTabs.Controls.Add(this.tabPage2);
this.OptionsTabs.Controls.Add(this.tabPage3);
this.OptionsTabs.Controls.Add(this.tabPage4);
this.OptionsTabs.Controls.Add(this.PrintingTab);
this.OptionsTabs.Location = new System.Drawing.Point(3, 5);
this.OptionsTabs.Name = "OptionsTabs";
this.OptionsTabs.SelectedIndex = 0;
this.OptionsTabs.Size = new System.Drawing.Size(368, 227);
this.OptionsTabs.TabIndex = 0;
//
// tabPage1
//
@@ -101,7 +111,6 @@
this.AltoI1KROMRadioButton.Name = "AltoI1KROMRadioButton";
this.AltoI1KROMRadioButton.Size = new System.Drawing.Size(214, 17);
this.AltoI1KROMRadioButton.TabIndex = 4;
this.AltoI1KROMRadioButton.TabStop = true;
this.AltoI1KROMRadioButton.Text = "Alto I, 1K Control ROM, 1K Control RAM";
this.AltoI1KROMRadioButton.UseVisualStyleBackColor = true;
//
@@ -112,7 +121,6 @@
this.AltoII3KRAMRadioButton.Name = "AltoII3KRAMRadioButton";
this.AltoII3KRAMRadioButton.Size = new System.Drawing.Size(236, 17);
this.AltoII3KRAMRadioButton.TabIndex = 3;
this.AltoII3KRAMRadioButton.TabStop = true;
this.AltoII3KRAMRadioButton.Text = "Alto II XM, 1K Control ROM, 3K Control RAM";
this.AltoII3KRAMRadioButton.UseVisualStyleBackColor = true;
this.AltoII3KRAMRadioButton.CheckedChanged += new System.EventHandler(this.OnSystemTypeCheckChanged);
@@ -124,7 +132,6 @@
this.AltoII2KROMRadioButton.Name = "AltoII2KROMRadioButton";
this.AltoII2KROMRadioButton.Size = new System.Drawing.Size(236, 17);
this.AltoII2KROMRadioButton.TabIndex = 2;
this.AltoII2KROMRadioButton.TabStop = true;
this.AltoII2KROMRadioButton.Text = "Alto II XM, 2K Control ROM, 1K Control RAM";
this.AltoII2KROMRadioButton.UseVisualStyleBackColor = true;
this.AltoII2KROMRadioButton.CheckedChanged += new System.EventHandler(this.OnSystemTypeCheckChanged);
@@ -141,6 +148,7 @@
// AltoII1KROMRadioButton
//
this.AltoII1KROMRadioButton.AutoSize = true;
this.AltoII1KROMRadioButton.Checked = true;
this.AltoII1KROMRadioButton.Location = new System.Drawing.Point(14, 53);
this.AltoII1KROMRadioButton.Name = "AltoII1KROMRadioButton";
this.AltoII1KROMRadioButton.Size = new System.Drawing.Size(236, 17);
@@ -287,6 +295,79 @@
this.InterlaceDisplayCheckBox.Text = "Interlaced Display (headache mode)";
this.InterlaceDisplayCheckBox.UseVisualStyleBackColor = true;
//
// tabPage4
//
this.tabPage4.Controls.Add(this.DACOptionsGroupBox);
this.tabPage4.Controls.Add(this.EnableDACCheckBox);
this.tabPage4.Location = new System.Drawing.Point(4, 22);
this.tabPage4.Name = "tabPage4";
this.tabPage4.Padding = new System.Windows.Forms.Padding(3);
this.tabPage4.Size = new System.Drawing.Size(360, 201);
this.tabPage4.TabIndex = 3;
this.tabPage4.Text = "DAC";
this.tabPage4.UseVisualStyleBackColor = true;
//
// DACOptionsGroupBox
//
this.DACOptionsGroupBox.Controls.Add(this.BrowseButton);
this.DACOptionsGroupBox.Controls.Add(this.DACOutputCapturePathTextBox);
this.DACOptionsGroupBox.Controls.Add(this.label2);
this.DACOptionsGroupBox.Controls.Add(this.EnableDACCaptureCheckBox);
this.DACOptionsGroupBox.Location = new System.Drawing.Point(15, 52);
this.DACOptionsGroupBox.Name = "DACOptionsGroupBox";
this.DACOptionsGroupBox.Size = new System.Drawing.Size(335, 139);
this.DACOptionsGroupBox.TabIndex = 1;
this.DACOptionsGroupBox.TabStop = false;
this.DACOptionsGroupBox.Text = "DAC options";
//
// BrowseButton
//
this.BrowseButton.Location = new System.Drawing.Point(251, 53);
this.BrowseButton.Name = "BrowseButton";
this.BrowseButton.Size = new System.Drawing.Size(75, 23);
this.BrowseButton.TabIndex = 3;
this.BrowseButton.Text = "Browse...";
this.BrowseButton.UseVisualStyleBackColor = true;
this.BrowseButton.Click += new System.EventHandler(this.BrowseButton_Click);
//
// DACOutputCapturePathTextBox
//
this.DACOutputCapturePathTextBox.Location = new System.Drawing.Point(127, 55);
this.DACOutputCapturePathTextBox.Name = "DACOutputCapturePathTextBox";
this.DACOutputCapturePathTextBox.Size = new System.Drawing.Size(110, 20);
this.DACOutputCapturePathTextBox.TabIndex = 2;
//
// label2
//
this.label2.AutoSize = true;
this.label2.Location = new System.Drawing.Point(16, 58);
this.label2.Name = "label2";
this.label2.Size = new System.Drawing.Size(105, 13);
this.label2.TabIndex = 1;
this.label2.Text = "Output capture path:";
//
// EnableDACCaptureCheckBox
//
this.EnableDACCaptureCheckBox.AutoSize = true;
this.EnableDACCaptureCheckBox.Location = new System.Drawing.Point(18, 28);
this.EnableDACCaptureCheckBox.Name = "EnableDACCaptureCheckBox";
this.EnableDACCaptureCheckBox.Size = new System.Drawing.Size(156, 17);
this.EnableDACCaptureCheckBox.TabIndex = 0;
this.EnableDACCaptureCheckBox.Text = "Enable DAC output capture";
this.EnableDACCaptureCheckBox.UseVisualStyleBackColor = true;
this.EnableDACCaptureCheckBox.CheckedChanged += new System.EventHandler(this.EnableDACCaptureCheckBox_CheckedChanged);
//
// EnableDACCheckBox
//
this.EnableDACCheckBox.AutoSize = true;
this.EnableDACCheckBox.Location = new System.Drawing.Point(19, 22);
this.EnableDACCheckBox.Name = "EnableDACCheckBox";
this.EnableDACCheckBox.Size = new System.Drawing.Size(275, 17);
this.EnableDACCheckBox.TabIndex = 0;
this.EnableDACCheckBox.Text = "Enable Audio DAC (Used by Smalltalk Music System)";
this.EnableDACCheckBox.UseVisualStyleBackColor = true;
this.EnableDACCheckBox.CheckedChanged += new System.EventHandler(this.OnEnableDACCheckboxChanged);
//
// DialogOKButton
//
this.DialogOKButton.Location = new System.Drawing.Point(211, 239);
@@ -307,78 +388,76 @@
this.DialogCancelButton.UseVisualStyleBackColor = true;
this.DialogCancelButton.Click += new System.EventHandler(this.CancelButton_Click);
//
// tabPage4
// PrintingTab
//
this.tabPage4.Controls.Add(this.DACOptionsGroupBox);
this.tabPage4.Controls.Add(this.EnableDACCheckBox);
this.tabPage4.Location = new System.Drawing.Point(4, 22);
this.tabPage4.Name = "tabPage4";
this.tabPage4.Padding = new System.Windows.Forms.Padding(3);
this.tabPage4.Size = new System.Drawing.Size(360, 201);
this.tabPage4.TabIndex = 3;
this.tabPage4.Text = "DAC";
this.tabPage4.UseVisualStyleBackColor = true;
this.PrintingTab.Controls.Add(this.PrintingOptionsGroupBox);
this.PrintingTab.Controls.Add(this.EnablePrintingCheckBox);
this.PrintingTab.Location = new System.Drawing.Point(4, 22);
this.PrintingTab.Name = "PrintingTab";
this.PrintingTab.Padding = new System.Windows.Forms.Padding(3);
this.PrintingTab.Size = new System.Drawing.Size(360, 201);
this.PrintingTab.TabIndex = 4;
this.PrintingTab.Text = "Printing";
this.PrintingTab.UseVisualStyleBackColor = true;
//
// EnableDACCheckBox
// PrintingOptionsGroupBox
//
this.EnableDACCheckBox.AutoSize = true;
this.EnableDACCheckBox.Location = new System.Drawing.Point(19, 22);
this.EnableDACCheckBox.Name = "EnableDACCheckBox";
this.EnableDACCheckBox.Size = new System.Drawing.Size(275, 17);
this.EnableDACCheckBox.TabIndex = 0;
this.EnableDACCheckBox.Text = "Enable Audio DAC (Used by Smalltalk Music System)";
this.EnableDACCheckBox.UseVisualStyleBackColor = true;
this.EnableDACCheckBox.CheckedChanged += new System.EventHandler(this.OnEnableDACCheckboxChanged);
this.PrintingOptionsGroupBox.Controls.Add(this.ReversePageOrderCheckBox);
this.PrintingOptionsGroupBox.Controls.Add(this.button1);
this.PrintingOptionsGroupBox.Controls.Add(this.PrintOutputPathTextBox);
this.PrintingOptionsGroupBox.Controls.Add(this.label5);
this.PrintingOptionsGroupBox.Location = new System.Drawing.Point(14, 52);
this.PrintingOptionsGroupBox.Name = "PrintingOptionsGroupBox";
this.PrintingOptionsGroupBox.Size = new System.Drawing.Size(335, 139);
this.PrintingOptionsGroupBox.TabIndex = 3;
this.PrintingOptionsGroupBox.TabStop = false;
this.PrintingOptionsGroupBox.Text = "Printing options";
//
// DACOptionsGroupBox
// button1
//
this.DACOptionsGroupBox.Controls.Add(this.BrowseButton);
this.DACOptionsGroupBox.Controls.Add(this.DACOutputCapturePathTextBox);
this.DACOptionsGroupBox.Controls.Add(this.label2);
this.DACOptionsGroupBox.Controls.Add(this.EnableDACCaptureCheckBox);
this.DACOptionsGroupBox.Location = new System.Drawing.Point(15, 52);
this.DACOptionsGroupBox.Name = "DACOptionsGroupBox";
this.DACOptionsGroupBox.Size = new System.Drawing.Size(335, 139);
this.DACOptionsGroupBox.TabIndex = 1;
this.DACOptionsGroupBox.TabStop = false;
this.DACOptionsGroupBox.Text = "DAC options";
this.button1.Location = new System.Drawing.Point(254, 24);
this.button1.Name = "button1";
this.button1.Size = new System.Drawing.Size(75, 23);
this.button1.TabIndex = 3;
this.button1.Text = "Browse...";
this.button1.UseVisualStyleBackColor = true;
//
// EnableDACCaptureCheckBox
// PrintOutputPathTextBox
//
this.EnableDACCaptureCheckBox.AutoSize = true;
this.EnableDACCaptureCheckBox.Location = new System.Drawing.Point(18, 28);
this.EnableDACCaptureCheckBox.Name = "EnableDACCaptureCheckBox";
this.EnableDACCaptureCheckBox.Size = new System.Drawing.Size(156, 17);
this.EnableDACCaptureCheckBox.TabIndex = 0;
this.EnableDACCaptureCheckBox.Text = "Enable DAC output capture";
this.EnableDACCaptureCheckBox.UseVisualStyleBackColor = true;
this.EnableDACCaptureCheckBox.CheckedChanged += new System.EventHandler(this.EnableDACCaptureCheckBox_CheckedChanged);
this.PrintOutputPathTextBox.Location = new System.Drawing.Point(113, 25);
this.PrintOutputPathTextBox.Name = "PrintOutputPathTextBox";
this.PrintOutputPathTextBox.Size = new System.Drawing.Size(125, 20);
this.PrintOutputPathTextBox.TabIndex = 2;
//
// label2
// label5
//
this.label2.AutoSize = true;
this.label2.Location = new System.Drawing.Point(16, 58);
this.label2.Name = "label2";
this.label2.Size = new System.Drawing.Size(105, 13);
this.label2.TabIndex = 1;
this.label2.Text = "Output capture path:";
this.label5.AutoSize = true;
this.label5.Location = new System.Drawing.Point(19, 28);
this.label5.Name = "label5";
this.label5.Size = new System.Drawing.Size(88, 13);
this.label5.TabIndex = 1;
this.label5.Text = "PDF output path:";
//
// DACOutputCapturePathTextBox
// EnablePrintingCheckBox
//
this.DACOutputCapturePathTextBox.Location = new System.Drawing.Point(127, 55);
this.DACOutputCapturePathTextBox.Name = "DACOutputCapturePathTextBox";
this.DACOutputCapturePathTextBox.Size = new System.Drawing.Size(110, 20);
this.DACOutputCapturePathTextBox.TabIndex = 2;
this.EnablePrintingCheckBox.AutoSize = true;
this.EnablePrintingCheckBox.Location = new System.Drawing.Point(19, 22);
this.EnablePrintingCheckBox.Name = "EnablePrintingCheckBox";
this.EnablePrintingCheckBox.Size = new System.Drawing.Size(211, 17);
this.EnablePrintingCheckBox.TabIndex = 2;
this.EnablePrintingCheckBox.Text = "Enable Printing (via Orbit / Dover ROS)";
this.EnablePrintingCheckBox.UseVisualStyleBackColor = true;
this.EnablePrintingCheckBox.CheckedChanged += new System.EventHandler(this.EnablePrintingCheckBox_CheckedChanged);
//
// BrowseButton
// ReversePageOrderCheckBox
//
this.BrowseButton.Location = new System.Drawing.Point(251, 53);
this.BrowseButton.Name = "BrowseButton";
this.BrowseButton.Size = new System.Drawing.Size(75, 23);
this.BrowseButton.TabIndex = 3;
this.BrowseButton.Text = "Browse...";
this.BrowseButton.UseVisualStyleBackColor = true;
this.BrowseButton.Click += new System.EventHandler(this.BrowseButton_Click);
this.ReversePageOrderCheckBox.AutoSize = true;
this.ReversePageOrderCheckBox.Location = new System.Drawing.Point(22, 51);
this.ReversePageOrderCheckBox.Name = "ReversePageOrderCheckBox";
this.ReversePageOrderCheckBox.Size = new System.Drawing.Size(158, 17);
this.ReversePageOrderCheckBox.TabIndex = 4;
this.ReversePageOrderCheckBox.Text = "Reverse Output Page Order";
this.ReversePageOrderCheckBox.UseVisualStyleBackColor = true;
//
// SystemOptions
//
@@ -387,12 +466,12 @@
this.ClientSize = new System.Drawing.Size(371, 271);
this.Controls.Add(this.DialogCancelButton);
this.Controls.Add(this.DialogOKButton);
this.Controls.Add(this.tabControl1);
this.Controls.Add(this.OptionsTabs);
this.Name = "SystemOptions";
this.SizeGripStyle = System.Windows.Forms.SizeGripStyle.Hide;
this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent;
this.Text = "System Options";
this.tabControl1.ResumeLayout(false);
this.OptionsTabs.ResumeLayout(false);
this.tabPage1.ResumeLayout(false);
this.tabPage1.PerformLayout();
this.tabPage2.ResumeLayout(false);
@@ -407,13 +486,17 @@
this.tabPage4.PerformLayout();
this.DACOptionsGroupBox.ResumeLayout(false);
this.DACOptionsGroupBox.PerformLayout();
this.PrintingTab.ResumeLayout(false);
this.PrintingTab.PerformLayout();
this.PrintingOptionsGroupBox.ResumeLayout(false);
this.PrintingOptionsGroupBox.PerformLayout();
this.ResumeLayout(false);
}
#endregion
private System.Windows.Forms.TabControl tabControl1;
private System.Windows.Forms.TabControl OptionsTabs;
private System.Windows.Forms.TabPage tabPage1;
private System.Windows.Forms.RadioButton AltoII3KRAMRadioButton;
private System.Windows.Forms.RadioButton AltoII2KROMRadioButton;
@@ -442,5 +525,12 @@
private System.Windows.Forms.Button BrowseButton;
private System.Windows.Forms.TextBox DACOutputCapturePathTextBox;
private System.Windows.Forms.Label label2;
private System.Windows.Forms.TabPage PrintingTab;
private System.Windows.Forms.GroupBox PrintingOptionsGroupBox;
private System.Windows.Forms.Button button1;
private System.Windows.Forms.TextBox PrintOutputPathTextBox;
private System.Windows.Forms.Label label5;
private System.Windows.Forms.CheckBox EnablePrintingCheckBox;
private System.Windows.Forms.CheckBox ReversePageOrderCheckBox;
}
}

View File

@@ -113,6 +113,15 @@ namespace Contralto.UI
DACOutputCapturePathTextBox.Text = Configuration.AudioDACCapturePath;
EnableDACCaptureCheckBox.Checked = Configuration.EnableAudioDACCapture;
//
// Printing Tab
//
PrintOutputPathTextBox.Text = Configuration.PrintOutputPath;
EnablePrintingCheckBox.Checked = Configuration.EnablePrinting;
PrintingOptionsGroupBox.Enabled = Configuration.EnablePrinting;
ReversePageOrderCheckBox.Checked = Configuration.ReversePageOrder;
}
private void PopulateNetworkAdapterList(PacketInterfaceType encapType)
@@ -273,10 +282,21 @@ namespace Contralto.UI
Configuration.EnableAudioDACCapture = EnableDACCaptureCheckBox.Checked;
Configuration.AudioDACCapturePath = DACOutputCapturePathTextBox.Text;
// Validate that the output folder exists, if not, warn the user
// Printing
Configuration.EnablePrinting = EnablePrintingCheckBox.Checked;
Configuration.PrintOutputPath = PrintOutputPathTextBox.Text;
Configuration.ReversePageOrder = ReversePageOrderCheckBox.Checked;
// Validate that the DAC output folder exists, if not, warn the user
if (Configuration.EnableAudioDACCapture && !Directory.Exists(Configuration.AudioDACCapturePath))
{
MessageBox.Show("Warning: the specified DAC output capture path does not exist or is inaccessible.");
MessageBox.Show("Warning: The specified DAC output capture path does not exist or is inaccessible.");
}
// Validate that the Print output folder exists, if not, warn the user
if (Configuration.EnablePrinting && !Directory.Exists(Configuration.PrintOutputPath))
{
MessageBox.Show("Warning: The specified Print output path does not exist or is inaccessible.");
}
this.Close();
@@ -306,11 +326,15 @@ namespace Contralto.UI
{
DACOutputCapturePathTextBox.Text = folderDialog.SelectedPath;
}
}
else
{
EnableDACCaptureCheckBox.Checked = false;
}
}
private void OnEnableDACCheckboxChanged(object sender, EventArgs e)
{
DACOptionsGroupBox.Enabled = Configuration.EnableAudioDAC;
DACOptionsGroupBox.Enabled = EnableDACCheckBox.Checked;
}
private void EnableDACCaptureCheckBox_CheckedChanged(object sender, EventArgs e)
@@ -318,10 +342,38 @@ namespace Contralto.UI
if (EnableDACCaptureCheckBox.Checked == true && String.IsNullOrWhiteSpace(DACOutputCapturePathTextBox.Text))
{
//
// When enabled and no output folder is set, force the user to choose an output folder.
// When DAC enabled and no output folder is set, force the user to choose an output folder.
//
BrowseForDACOutputFolder();
}
}
private void BrowseForPrintOutputFolder()
{
FolderBrowserDialog folderDialog = new FolderBrowserDialog();
folderDialog.Description = "Choose a folder to store PDFs of print output.";
folderDialog.ShowNewFolderButton = true;
if (folderDialog.ShowDialog() == DialogResult.OK)
{
PrintOutputPathTextBox.Text = folderDialog.SelectedPath;
}
else
{
EnablePrintingCheckBox.Checked = false;
}
}
private void EnablePrintingCheckBox_CheckedChanged(object sender, EventArgs e)
{
PrintingOptionsGroupBox.Enabled = EnablePrintingCheckBox.Checked;
if (EnablePrintingCheckBox.Checked == true && String.IsNullOrWhiteSpace(PrintOutputPathTextBox.Text))
{
//
// When Printing enabled and no output folder is set, force the user to choose an output folder.
//
BrowseForPrintOutputFolder();
}
}
}
}

View File

@@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="iTextSharp" version="5.5.11" targetFramework="net452" />
<package id="NAudio" version="1.8.0" targetFramework="net452" />
<package id="SDL2-CS.dll" version="2.0.0.0" targetFramework="net452" />
<package id="Sharp_Pcap" version="4.2.0" targetFramework="net452" />

View File

@@ -20,13 +20,14 @@ ContrAlto currently emulates the following Alto hardware:
- Ethernet (encapsulated in UDP datagrams on the host machine)
- Standard Keyboard/Mouse/Video
- Audio DAC (used with the Smalltalk Music System)
- The Orbit raster hardware, Dover Raster Output Scanner and Dover print
engine, which provides 384dpi print output (currently PDF only)
1.2 What's Not
--------------
At this time, ContrAlto does not support more exotic hardware such as Trident
disks, printers or audio using the utility port, the Orbit printer interface, or the
keyset input device.
disks, printers or audio using the utility port, or the keyset input device.
The Audio DAC is technically emulated, but output is not connected to an audio
device on non-Windows platforms at this time.
@@ -263,6 +264,11 @@ An example configuration file looks something like:
BootFile = 0
AlternateBootType = Ethernet
# Printing options
EnablePrinting = true
PrintOutputPath = .
ReversePageOrder = true
The following parameters are configurable:
@@ -294,6 +300,19 @@ BootAddress: The address to use with a Keyboard Disk Boot (See section 5.0)
BootFile: The file number to use with a Keyboard Net Boot (again, Section 5.0)
AlternateBootType: The type of boot to default to (Section 5.0)
EnablePrinting: Enables or disables printing via the emulated Orbit / Dover interface.
PrintOutputPath: Specifies the folder that output PDFs are written to. When the Alto
prints a new document, a new PDF file will be created in this
directory containing the printer's output.
ReversePageOrder: Controls the order in which pages are written to the PDF -- due to
the way the original Dover printer worked, most Alto software printed
documents in reverse order (i.e. the last page printed first) so
that the pages didn't have to be reshuffled when picked up from the
tray. By default, leaving this box checked is probably what you want,
but if your documents come out backwards, uncheck it.
4.1 Ethernet Encapsulation Setup
================================
@@ -452,10 +471,17 @@ On Unix and OS X, display and keyboard/mouse input is provided through SDL, see:
https://www.libsdl.org/ and is accessed using the SDL2# wrapper, see:
https://github.com/flibitijibibo/SDL2-CS.
PDF generation is provided by the iTextSharp library, see: https://github.com/itext.
10.0 Change History
===================
V1.2.1
------
- Completed implementation of Orbit, Dover ROS and Dover print engine.
- Bugfixes to memory state machine around overlapped double-word reads/writes.
Smalltalk-80 now runs, as does Spruce.
V1.2
----
- First release supporting Unix / OS X

View File

@@ -21,13 +21,14 @@ ContrAlto currently emulates the following Alto hardware:
on the host machine)
- Standard Keyboard/Mouse/Video
- Audio DAC (used with the Smalltalk Music System)
- The Orbit raster hardware, Dover Raster Output Scanner and Dover print
engine, which provides 384dpi print output (currently PDF only)
1.2 What's Not
--------------
At this time, ContrAlto does not support more exotic hardware such as Trident
disks, printers or audio using the utility port, the Orbit printer interface, or the
keyset input device.
disks, printers or audio using the utility port or the keyset input device.
2.0 Requirements
@@ -297,11 +298,12 @@ The "Interlaced Display" checkbox attempts to simulate the Alto's original
interlaced display. Depending on your monitor and the speed of your computer
this may or may not work well.
4.4 DAC
-------
The DAC tab provides options controlling the Audio DAC used by the Smalltalk
Music System. The "Enable Audio DAC" does what you'd expect -- it enables
Music System. "Enable Audio DAC" does what you'd expect -- it enables
or disables audio output (and audio capture). If this option is enabled,
the "DAC Options" fields become available.
@@ -313,7 +315,26 @@ directory. If the Alto is restarted or if ContrAlto exits, this file will
be closed. WAV files created by ContrAlto are 16-bit, mono at 13Khz.
4.5 Alternate ("keyboard") Boots
4.5 Printing
------------
The Printing tab provides options for the Orbit / Dover print system emulation.
The "Enable Printing" checkbox enables or disables print output. If this option
is enabled, the "Printing Options" fields become available.
The "PDF output path" specifies the folder that output PDFs are written to.
When the Alto prints a new document, a new PDF file will be created in this
directory containing the printer's output.
The "Reverse Output Page Order" checkbox controls the order in which pages are
written to the PDF -- due to the way the original Dover printer worked, most
Alto software printed pages in reverse order (i.e. the last page printed
first) so that the pages didn't have to be reshuffled when picked up from the
tray. By default, leaving this box checked is probably what you want, but
if your documents come out backwards, uncheck it.
4.6 Alternate ("keyboard") Boots
--------------------------------
The Alto allowed the specification of alternate boot addresses by holding down
@@ -493,10 +514,18 @@ On Unix and OS X, display and keyboard/mouse input is provided through SDL, see:
https://www.libsdl.org/ and is accessed using the SDL2# wrapper, see:
https://github.com/flibitijibibo/SDL2-CS.
PDF generation is provided by the iTextSharp library, see: https://github.com/itext.
10.0 Change History
===================
V1.2.1
------
- Completed implementation of Orbit, Dover ROS and Dover print engine.
- Bugfixes to memory state machine around overlapped double-word reads/writes.
Smalltalk-80 now runs, as does Spruce.
V1.2
----
- First release officially supporting Unix / OS X