diff --git a/Contralto/AltoSystem.cs b/Contralto/AltoSystem.cs index 605b056..4781963 100644 --- a/Contralto/AltoSystem.cs +++ b/Contralto/AltoSystem.cs @@ -242,8 +242,6 @@ namespace Contralto get { return _scheduler; } } - public int _novaInst; - private AltoCPU _cpu; private MemoryBus _memBus; private Memory.Memory _mem; diff --git a/Contralto/App.config b/Contralto/App.config index 7212975..9fb50f4 100644 --- a/Contralto/App.config +++ b/Contralto/App.config @@ -58,6 +58,15 @@ + + False + + + + + + True + diff --git a/Contralto/CPU/CPU.cs b/Contralto/CPU/CPU.cs index b68c7e4..f6e0116 100644 --- a/Contralto/CPU/CPU.cs +++ b/Contralto/CPU/CPU.cs @@ -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); } /// @@ -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; } } diff --git a/Contralto/CPU/Tasks/EmulatorTask.cs b/Contralto/CPU/Tasks/EmulatorTask.cs index 7c6953e..bb2f4f4 100644 --- a/Contralto/CPU/Tasks/EmulatorTask.cs +++ b/Contralto/CPU/Tasks/EmulatorTask.cs @@ -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; diff --git a/Contralto/CPU/Tasks/OrbitTask.cs b/Contralto/CPU/Tasks/OrbitTask.cs index 83dbc15..e192a14 100644 --- a/Contralto/CPU/Tasks/OrbitTask.cs +++ b/Contralto/CPU/Tasks/OrbitTask.cs @@ -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)); diff --git a/Contralto/CPU/Tasks/Task.cs b/Contralto/CPU/Tasks/Task.cs index 0c8612d..aa9ce98 100644 --- a/Contralto/CPU/Tasks/Task.cs +++ b/Contralto/CPU/Tasks/Task.cs @@ -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; diff --git a/Contralto/CPU/UCodeDisassembler.cs b/Contralto/CPU/UCodeDisassembler.cs index 73855fb..a460f54 100644 --- a/Contralto/CPU/UCodeDisassembler.cs +++ b/Contralto/CPU/UCodeDisassembler.cs @@ -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)); } } } diff --git a/Contralto/Configuration.cs b/Contralto/Configuration.cs index be6f8f0..bd6be45 100644 --- a/Contralto/Configuration.cs +++ b/Contralto/Configuration.cs @@ -204,6 +204,21 @@ namespace Contralto /// public static string AudioDACCapturePath; + /// + /// Whether to enable printing via the Orbit / DoverROS interface. + /// + public static bool EnablePrinting; + + /// + /// Path for print output. + /// + public static string PrintOutputPath; + + /// + /// Whether to reverse the page order when printing. + /// + public static bool ReversePageOrder; + /// /// The components to enable debug logging for. /// @@ -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(); } diff --git a/Contralto/Contralto.cfg b/Contralto/Contralto.cfg index c8f6bcb..e0e177a 100644 --- a/Contralto/Contralto.cfg +++ b/Contralto/Contralto.cfg @@ -18,3 +18,8 @@ InterlaceDisplay = False BootAddress = 0 BootFile = 0 AlternateBootType = Ethernet + +# Printing options +EnablePrinting = true +PrintOutputPath = . +ReversePageOrder = true diff --git a/Contralto/Contralto.csproj b/Contralto/Contralto.csproj index df2439c..3798567 100644 --- a/Contralto/Contralto.csproj +++ b/Contralto/Contralto.csproj @@ -97,6 +97,9 @@ app.manifest + + ..\packages\iTextSharp.5.5.11\lib\itextsharp.dll + ..\packages\NAudio.1.8.0\lib\net35\NAudio.dll True @@ -127,6 +130,9 @@ + + + diff --git a/Contralto/IO/DiskController.cs b/Contralto/IO/DiskController.cs index 2e6dbd6..0e04068 100644 --- a/Contralto/IO/DiskController.cs +++ b/Contralto/IO/DiskController.cs @@ -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; diff --git a/Contralto/IO/DoverROS.cs b/Contralto/IO/DoverROS.cs index 075d580..ffaa637 100644 --- a/Contralto/IO/DoverROS.cs +++ b/Contralto/IO/DoverROS.cs @@ -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 . +*/ + + +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 } /// - /// 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. /// 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(); + } + + /// + /// 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. + /// + /// + /// + /// + 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; + /// + /// Number of passes through the Runout state. + /// + 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; + + + /// + /// 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. + /// + /// + private ulong _printEngineTimestepInterval = (ulong)((1000.0 / 3500.0) * Conversion.MsecToNsec); + + private byte[] _pageData = new byte[3500 * 512]; private int _rasterNum; + + private IPageSink _pageOutput; } } diff --git a/Contralto/IO/OrbitController.cs b/Contralto/IO/OrbitController.cs index bb43560..bde5f97 100644 --- a/Contralto/IO/OrbitController.cs +++ b/Contralto/IO/OrbitController.cs @@ -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 . +*/ + +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; diff --git a/Contralto/IO/OrganKeyboard.cs b/Contralto/IO/OrganKeyboard.cs index f9e72ac..9e4479b 100644 --- a/Contralto/IO/OrganKeyboard.cs +++ b/Contralto/IO/OrganKeyboard.cs @@ -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 . +*/ + +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 { diff --git a/Contralto/IO/Printing/IPageSink.cs b/Contralto/IO/Printing/IPageSink.cs new file mode 100644 index 0000000..5ecefba --- /dev/null +++ b/Contralto/IO/Printing/IPageSink.cs @@ -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 . +*/ +namespace Contralto.IO.Printing +{ + /// + /// 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). + /// + public interface IPageSink + { + /// + /// Starts a new document to be printed. + /// + void StartDoc(); + + /// + /// Adds a new page to the output. This is provided as a byte array containing 'height' scanlines of the specified width. + /// + /// + /// + /// + void AddPage(byte[] raster, int width, int height); + + /// + /// Ends the document being printed. + /// + void EndDoc(); + } +} diff --git a/Contralto/IO/Printing/NullPageSink.cs b/Contralto/IO/Printing/NullPageSink.cs new file mode 100644 index 0000000..c6b995a --- /dev/null +++ b/Contralto/IO/Printing/NullPageSink.cs @@ -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 . +*/ +namespace Contralto.IO.Printing +{ + /// + /// The NullPageSink: All pages go to the bit-bucket, for the true paperless office. + /// + public class NullPageSink : IPageSink + { + public NullPageSink() + { + + } + + public void StartDoc() + { + + } + + public void AddPage(byte[] rasters, int width, int height) + { + + } + + public void EndDoc() + { + + } + } +} diff --git a/Contralto/IO/Printing/PdfPageSink.cs b/Contralto/IO/Printing/PdfPageSink.cs new file mode 100644 index 0000000..14741f7 --- /dev/null +++ b/Contralto/IO/Printing/PdfPageSink.cs @@ -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 . +*/ + +using System.IO; + +using iTextSharp.text; +using iTextSharp.text.pdf; +using System.Collections.Generic; +using System; +using Contralto.Logging; + +namespace Contralto.IO.Printing +{ + /// + /// 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. + /// + public class PdfPageSink : IPageSink + { + public PdfPageSink() + { + _pageImages = new List(); + } + + 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); + } + } + } + + /// + /// 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.) + /// + private List _pageImages; + + private Document _pdf; + } +} diff --git a/Contralto/Memory/MemoryBus.cs b/Contralto/Memory/MemoryBus.cs index 43ed2e0..fe02287 100644 --- a/Contralto/Memory/MemoryBus.cs +++ b/Contralto/Memory/MemoryBus.cs @@ -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; } } diff --git a/Contralto/Program.cs b/Contralto/Program.cs index d878630..ca7d4d7 100644 --- a/Contralto/Program.cs +++ b/Contralto/Program.cs @@ -15,10 +15,8 @@ along with ContrAlto. If not, see . */ -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; } } diff --git a/Contralto/Properties/AssemblyInfo.cs b/Contralto/Properties/AssemblyInfo.cs index b342b51..bcb06e9 100644 --- a/Contralto/Properties/AssemblyInfo.cs +++ b/Contralto/Properties/AssemblyInfo.cs @@ -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")] diff --git a/Contralto/Properties/Settings.Designer.cs b/Contralto/Properties/Settings.Designer.cs index 9cedbe1..f0c06fd 100644 --- a/Contralto/Properties/Settings.Designer.cs +++ b/Contralto/Properties/Settings.Designer.cs @@ -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; + } + } } } diff --git a/Contralto/Properties/Settings.settings b/Contralto/Properties/Settings.settings index f8f12bb..6ce2097 100644 --- a/Contralto/Properties/Settings.settings +++ b/Contralto/Properties/Settings.settings @@ -47,5 +47,14 @@ + + False + + + + + + True + \ No newline at end of file diff --git a/Contralto/UI/AboutBox.Designer.cs b/Contralto/UI/AboutBox.Designer.cs index 5cd6ec4..d207941 100644 --- a/Contralto/UI/AboutBox.Designer.cs +++ b/Contralto/UI/AboutBox.Designer.cs @@ -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; diff --git a/Contralto/UI/Debugger.Designer.cs b/Contralto/UI/Debugger.Designer.cs index a9fbe28..610a4e4 100644 --- a/Contralto/UI/Debugger.Designer.cs +++ b/Contralto/UI/Debugger.Designer.cs @@ -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; } } \ No newline at end of file diff --git a/Contralto/UI/Debugger.cs b/Contralto/UI/Debugger.cs index 0f04f9b..68baf69 100644 --- a/Contralto/UI/Debugger.cs +++ b/Contralto/UI/Debugger.cs @@ -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; - - /// - /// Temporary, for debugging floating point ucode issues - /// - /// - 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); - } - } - - /// - /// Gets a float from memory in "packed" format - /// - /// - /// - 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; - - } - - /// - /// Gets the float value for register, from alto code - /// - /// - /// - 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; - } - - /// - /// Gets the float value for register, from ucode store - /// - /// - /// - 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; diff --git a/Contralto/UI/Debugger.resx b/Contralto/UI/Debugger.resx index a03a29e..c2f72dc 100644 --- a/Contralto/UI/Debugger.resx +++ b/Contralto/UI/Debugger.resx @@ -192,6 +192,9 @@ True + + True + True diff --git a/Contralto/UI/SystemOptions.Designer.cs b/Contralto/UI/SystemOptions.Designer.cs index 5ffb22d..014156c 100644 --- a/Contralto/UI/SystemOptions.Designer.cs +++ b/Contralto/UI/SystemOptions.Designer.cs @@ -28,7 +28,7 @@ /// 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; } } \ No newline at end of file diff --git a/Contralto/UI/SystemOptions.cs b/Contralto/UI/SystemOptions.cs index 7d4af17..23a599b 100644 --- a/Contralto/UI/SystemOptions.cs +++ b/Contralto/UI/SystemOptions.cs @@ -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(); + } + } } } diff --git a/Contralto/packages.config b/Contralto/packages.config index 400781d..22bb297 100644 --- a/Contralto/packages.config +++ b/Contralto/packages.config @@ -1,5 +1,6 @@  + diff --git a/Contralto/readme-mono.txt b/Contralto/readme-mono.txt index 41571fa..b523b34 100644 --- a/Contralto/readme-mono.txt +++ b/Contralto/readme-mono.txt @@ -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 diff --git a/Contralto/readme.txt b/Contralto/readme.txt index b8763d9..41e43ea 100644 --- a/Contralto/readme.txt +++ b/Contralto/readme.txt @@ -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 diff --git a/ContraltoSetup/Product.wxs b/ContraltoSetup/Product.wxs index 6349803..db96d77 100644 --- a/ContraltoSetup/Product.wxs +++ b/ContraltoSetup/Product.wxs @@ -112,6 +112,9 @@ + + +